
Desarrollo del Videojuego “SpeedAndJump” en Unity – Superación de Retos hasta el Castillo Final
Este documento describe el proceso de desarrollo del videojuego SpeedAndJump, un proyecto en Unity diseñado en perspectiva de primera persona. El juego consiste en guiar a un personaje a través de diversos retos, plataformas móviles, enemigos y trampas, con el objetivo final de alcanzar un castillo. Durante el desarrollo se implementaron sistemas de vida, detección de caídas, animaciones controladas por condiciones, inteligencia artificial básica en enemigos (como abejas voladoras), y una interfaz de usuario que refleja el estado del jugador. Además, se incluyeron menús interactivos con música y video, así como escenas de Game Over y Victoria. El documento incluye también los scripts utilizados, explicados con su funcionalidad y propósito, brindando una visión completa del diseño técnico y jugable del proyecto.


figura 1
Importación de Assets y Configuración Inicial del Personaje
Todos los modelos 3D y animaciones utilizados en el juego fueron importados desde el paquete Ultimate Platformer Pack de Quaternius, descargado desde el sitio oficial de Unity Asset Store. El paquete fue incorporado al proyecto arrastrando su carpeta directamente al panel Project de Unity, permitiendo así su reconocimiento automático por el motor. Desde esta ubicación se seleccionaron los modelos, animaciones y objetos físicos requeridos para el desarrollo del entorno y la mecánica del juego.
figura 2
figura 3
figura 4
Se llevaron a cabo los siguientes pasos clave para la configuración inicial del personaje principal y los elementos físicos del entorno:
- Importación del Modelo del Jugador: El modelo 3D fue importado en formato
.
FBX
desde el paquete Ultimate Platformer Pack. En la pestaña Rig del Inspector, se configuró el tipo de animación como Humanoid para facilitar la integración con el sistema de animación de Unity.

- Sistema de Animaciones: Las animaciones correspondientes fueron arrastradas a un Animator Controller, donde se definieron los estados y transiciones. Se configuraron parámetros como
isRunning
(booleano) para controlar el movimiento, yDie
(Trigger) para gestionar la animación de muerte. Se desactivó la opción Has Exit Time en la transición hacia la animación de muerte para permitir una respuesta inmediata ante eventos de colisión letal.

- Física del Entorno: A los elementos del entorno (como plataformas, obstáculos y superficies) se les asignaron componentes Rigidbody (cuando se requería dinámica) y Collider (BoxCollider, MeshCollider o CapsuleCollider, según el objeto), con el fin de gestionar las colisiones y simular interacciones físicas realistas.

- Control del Personaje: Se utilizó el componente CharacterController para el movimiento del jugador, acompañado de scripts personalizados que gestionan la lógica de salto, detección de caída, y condiciones de muerte (por ejemplo, caída desde cierta altura o colisión con elementos hostiles).

figura 8
Sistema de Vida y Detección de Caídas
Se implementó un sistema de vidas que determina cuántas veces el jugador puede caer desde una altura peligrosa antes de activar el estado de Game Over. La lógica se basa en dos condiciones principales:

figura 9
Detección de Caída por Altura: Si el jugador cae desde una altura superior al valor definido como minFallHeight
, se considera una caída peligrosa y se decrementa el contador de vidas disponibles.

figura 10
Límite de Posición Vertical: Independientemente de la altura desde la que se cayó, si la posición vertical del jugador (transform.position.y
) desciende por debajo de un umbral crítico definido (por ejemplo, deathYThreshold
), se ejecuta automáticamente la pérdida de una vida. Esta condición asegura que caídas prolongadas fuera del escenario también sean detectadas como fallos
figura 11
Sistemas de animación
Las animaciones del personaje fueron gestionadas a través de un Animator Controller, que definió el flujo de estados como:
Idle
– cuando el personaje está detenido.
Walk
/ Run
– durante el movimiento a baja o alta velocidad.
Die
– animación de muerte activada tras una caída o evento letal.
figura 12
Para activar la animación de muerte, se definió un parámetro de tipo Trigger llamado Die
, el cual es invocado desde el script del controlador de vida en el momento en que el jugador pierde una vida.
Con el fin de asegurar que la animación de muerte se reproduzca completamente antes de que el personaje reaparezca, se utilizó una corrutina (Coroutine
) que introduce un retardo basado en la duración exacta de la animación. Solo después de ese intervalo se restablece la posición del jugador al punto de inicio o respawn
.

figura 13
Interfaz de Usuario (UI) y Control de Daño
El sistema de interfaz de usuario fue diseñado para reflejar en tiempo real el estado de vidas del jugador. Para ello, se implementó un script llamado UIManager
, encargado de actualizar dinámicamente los elementos visuales (como íconos de vidas o contadores numéricos) cada vez que se produce una pérdida de vida.

figura 14
Creación e Integración del Enemigo: Abeja
Importación del Modelo 3D
El modelo de la abeja fue importado en formato .FBX
y ubicado en la ruta: Assets/Enemies/Bee
.
En el panel Inspector, se configuró el tipo de Rig como Humanoid
o Generic
, dependiendo del formato del asset original y la compatibilidad de las animaciones.
Configuración del Prefab
- Se generó un Prefab denominado
Bee
arrastrando el modelo desde el proyecto a la carpetaAssets/Prefabs
. - Se añadieron los siguientes componentes:
- Animator: con el
BeeAnimatorController
asignado. - Rigidbody: configurado con:
Mass = 1
Drag = 0.5
Use Gravity = false
(para simular vuelo continuo).
- SphereCollider: utilizado para detectar colisiones físicas. Se mantuvo
Is Trigger = false
para permitir interacción física con el jugador.
- Animator: con el
- El objeto fue etiquetado con:
Tag = Enemy
Layer = Enemies
Esto facilita la identificación mediante scripts y la gestión de colisiones con capas específicas.

figura 15
Configuración del Animator Controller
Se configuró el BeeAnimatorController
con los siguientes parámetros y estados:
- Parámetros:
isFlying
(bool): activado por defecto para mantener el estado de vuelo.Attack
(Trigger): utilizado para iniciar la animación de ataque cuando se detecta al jugador.
- Estados:
BeeFlying
: clip de animación de vuelo, configurado como Default State (flecha naranja), con la opción Loop Time activada.Attack
: animación de ataque activada mediante trigger.
- Transiciones:
Any State → Attack
: condiciónAttack
(Trigger activado).Any State → BeeFlying
: condiciónisFlying == true
.
- Se desactivaron Has Exit Time y Write Defaults en transiciones específicas para asegurar una respuesta inmediata y optimizar el control sobre los parámetros del Animator.
figura 16
Script BeeMovement.cs
— Movimiento y Ataque del Enemigo Abeja
El script BeeMovement.cs
controla el comportamiento de patrullaje, persecución y ataque del enemigo tipo abeja. Sus principales componentes funcionales son:
Inicialización (Start()
)
void Start() {
animator = GetComponent<Animator>();
animator.SetBool("isFlying", true); // Estado inicial de vuelo
player = GameObject.FindGameObjectWithTag("Player").transform; // Referencia al jugador
}
Actualización de Comportamiento (Update()
)
En cada frame, se evalúa la distancia al jugador y se ejecutan distintas acciones:
void Update() {
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
if (distanceToPlayer <= followRange) {
// Movimiento hacia el jugador
transform.position = Vector3.MoveTowards(transform.position, player.position, speed * Time.deltaTime);
animator.SetBool("isFlying", true);
// Ataque si está en rango y respetando el cooldown
if (distanceToPlayer <= attackRange && Time.time - lastAttackTime >= attackCooldown) {
animator.SetTrigger("Attack");
lastAttackTime = Time.time;
}
} else {
// Patrullaje flotante (Idle)
FloatIdle();
}
}
followRange
: distancia máxima para comenzar a seguir al jugador.attackRange
: distancia mínima para activar el ataque.lastAttackTime
yattackCooldown
: variables para controlar la frecuencia de ataque y evitar ataques continuos.

figura 17
Método de Movimiento Flotante (Idle)
void FloatIdle() {
float yOffset = Mathf.Sin(Time.time * floatSpeed) * floatAmplitude;
transform.position = new Vector3(transform.position.x, initialY + yOffset, transform.position.z);
}
- Simula un movimiento vertical suave con Mathf.Sin para dar sensación de vuelo o patrullaje sin jugador cerca.
- Se recomienda almacenar el valor
initialY
enStart()
para conservar el nivel base del eje Y.
figura 18
Evento de Ataque: DealDamage()
public void DealDamage() {
// Este método se llama desde un evento en el clip de animación 'Attack'
if (Vector3.Distance(transform.position, player.position) <= attackRange) {
player.GetComponent<PlayerHealth>().TakeDamage(damageAmount);
}
}
- Este método se conecta directamente desde el evento de animación del clip de ataque.
- Asegura que el daño se inflija solo si el jugador está dentro del rango al momento del impacto.
- Llama al método
TakeDamage()
del componentePlayerHealth
para reducir vidas

figura 19
Implementación de Menú Principal con Canvas
Creación de Escena MainMenu
- Se creó una nueva escena
MainMenu.unity
. - Se añadió a Build Settings para garantizar su correcta carga como primera escena del juego.

figura 20
Configuración del Canvas
- Se añadió un
Canvas
al escenario. - Render Mode:
Screen Space - Overlay
para una interfaz nítida y adaptativa en cualquier resolución. - Se ajustaron dimensiones y anclajes para una experiencia responsiva.

figura 21
Elementos de UI
- Botón “Play”:
- Tipo:
Button (TextMeshPro)
. - Evento
OnClick()
asignado aMainMenuController.PlayGame()
, encargado de cargar la siguiente escena de juego.
- Tipo:
- Texto descriptivo:
- Añadido al Canvas.
- Tipografía elegante con
TextMeshPro
, tamaño y color ajustados para legibilidad y estética. - Centrado y estilizado para acompañar el diseño general del menú.

figura 22
Fondo de Video
- Se creó la carpeta
Assets/Videos
e importó un archivo.mp4
. - Se añadió un componente
VideoPlayer
al Canvas:- Render Mode:
Camera Near Plane
(se renderiza detrás de los elementos UI). - Target Camera:
MainCamera
. - Loop: activado.
- Play On Awake: activado.
- Audio Output Mode:
None
(audio silenciado para no interferir con la música de fondo).
- Render Mode:
- Se ajustó el orden de render para mantener los elementos UI por encima del video.

figura 23
Música de Fondo
- Se importó
MenuMusic.mp3
en la carpetaAssets/Audio
. - Se creó un
GameObject
llamadoMusicPlayer
con componenteAudioSource
:- AudioClip: asignado a
MenuMusic.mp3
. - Play On Awake: activado.
- Loop: activado.
- Volumen: ajustado a
0.3
para no opacar efectos futuros del juego.
- AudioClip: asignado a

figura 24
Anexo Completo de Scripts con Código y Descripción
AnimationEventRelay.cs.
Sirve como conexión entre una acción animada y la forma en que el jugador ataca.Captura un Evento de Animación y redirige la solicitud al método de ataque del PlayerController. Coordina las animaciones con la implementación del daño, garantizando precisión en el tiempo. con tecla E
using UnityEngine;
public class AnimationEventRelay : MonoBehaviour
{
public PlayerController playerController;
// Este método será llamado por el evento de animación
public void PerformAttack()
{
if (playerController != null)
{
playerController.PerformAttack();
}
}
}
BeeMovement.cs
Regula las acciones de la abeja rival: flotación sin movimiento, seguimiento al jugador, animación de ataque y asistencia de daño.Administra los estados de inactividad en el aire y la persecución/ataque según las distancias.Presenta un enemigo que se mueve de forma dinámica y pone a prueba al jugador.
using UnityEngine;
public class BeeMovement : MonoBehaviour
{
public float floatSpeed = 2f;
public float floatAmount = 0.2f;
public float followRange = 6f;
public float attackRange = 1.2f;
public float moveSpeed = 3f;
public float attackCooldown = 1.0f;
private Vector3 startPosition;
private Transform player;
private Animator animator;
private float lastAttackTime = -Mathf.Infinity;
void Start()
{
startPosition = transform.position;
animator = GetComponent<Animator>();
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null)
player = playerObj.transform;
}
void Update()
{
if (player == null)
{
FloatIdle();
return;
}
float distance = Vector3.Distance(transform.position, player.position);
if (distance <= followRange)
{
// Seguir al jugador
Vector3 targetPos = player.position;
targetPos.y = transform.position.y;
transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);
// Atacar si está en rango
if (distance <= attackRange)
{
if (Time.time - lastAttackTime >= attackCooldown)
{
if (animator != null)
animator.SetTrigger("Attack");
lastAttackTime = Time.time;
}
}
}
else
{
FloatIdle();
}
}
void FloatIdle()
{
float newY = startPosition.y + Mathf.Sin(Time.time * floatSpeed) * floatAmount;
transform.position = new Vector3(transform.position.x, newY, transform.position.z);
}
// Este método debe ser llamado por un evento en la animación de ataque
public void DealDamage()
{
if (player == null) return;
float distance = Vector3.Distance(transform.position, player.position);
if (distance <= attackRange)
{
PlayerLifeSystem pls = player.GetComponent<PlayerLifeSystem>();
if (pls != null && pls.CurrentLives > 0)
pls.TakeDamage();
}
}
}
CameraFollow.cs
Establece una cámara en tercera persona que se maneje con el ratón y que tenga un seguimiento fluido. Modifica el movimiento vertical y horizontal con el ratón, y haz que la posición y la vista hacia el jugador sean más suaves. Brinda al jugador un control visual total y una experiencia inmersiva.
using UnityEngine;
using UnityEngine.InputSystem;
public class CameraFollow : MonoBehaviour
{
public Transform target;
public Vector3 offset = new Vector3(0, 3, -6);
public float followSpeed = 10f;
public float mouseSensitivity = 2f;
public float minY = -35f;
public float maxY = 60f;
private float currentYaw = 0f;
private float currentPitch = 10f;
void LateUpdate()
{
if (!enabled || target == null) return;
float mouseX = Mouse.current.delta.x.ReadValue() * mouseSensitivity;
float mouseY = Mouse.current.delta.y.ReadValue() * mouseSensitivity;
currentYaw += mouseX;
currentPitch -= mouseY;
currentPitch = Mathf.Clamp(currentPitch, minY, maxY);
target.rotation = Quaternion.Euler(0, currentYaw, 0);
Quaternion rotation = Quaternion.Euler(currentPitch, currentYaw, 0);
Vector3 desiredPosition = target.position + rotation * offset;
transform.position = Vector3.Lerp(transform.position, desiredPosition, followSpeed * Time.deltaTime);
transform.LookAt(target.position + Vector3.up * offset.y * 0.5f);
}
}
CoinSpawner.cs
Crea monedas de vida sobre cada dificultad.Al comenzar, identifica elementos con la etiqueta ‘Obstáculo’ y crea monedas. Automatiza las recogidas, incentivando la exploración.
using UnityEngine;
public class CoinSpawner : MonoBehaviour
{
public GameObject coinPrefab; // Asigna el prefab desde el editor
public float coinOffsetY = 1.0f; // Altura sobre el cubo
void Start()
{
GameObject[] obstacles = GameObject.FindGameObjectsWithTag("Obstacle");
foreach (GameObject obstacle in obstacles)
{
Vector3 spawnPosition = obstacle.transform.position + new Vector3(0, coinOffsetY, 0);
Instantiate(coinPrefab, spawnPosition, Quaternion.identity);
}
}
}
Enemy.cs
Clase fundamental para oponentes con puntos de vida y fin de existencia.Reduce los puntos de vida y elimina el objeto cuando llegan a cero.Estructura consistente para todos los oponentes, permite una fácil ampliación.
using UnityEngine;
public class Enemy : MonoBehaviour, IDamageable
{
public int maxHealth = 3;
private int currentHealth;
void Start()
{
currentHealth = maxHealth;
}
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0)
{
Die();
}
}
void Die()
{
Destroy(gameObject);
}
}
FallingPlatform.cs
Plataforma que cae al pisarla y resetea su estado. Retraso, caída con gravedad extra y reinicio.
using UnityEngine;
public class FallingPlatform : MonoBehaviour
{
public float fallDelay = 0.5f;
public float extraGravityMultiplier = 3f;
private Rigidbody rb;
private bool hasFallen = false;
private bool isInactive = false;
private Vector3 initialPosition;
private Quaternion initialRotation;
private Collider platformCollider;
private Renderer platformRenderer;
void Start()
{
rb = GetComponent<Rigidbody>();
platformCollider = GetComponent<Collider>();
platformRenderer = GetComponent<Renderer>();
initialPosition = transform.position;
initialRotation = transform.rotation;
rb.isKinematic = true;
}
public void TriggerFall()
{
if (!hasFallen && !isInactive)
{
hasFallen = true;
Invoke(nameof(Fall), fallDelay);
}
}
void Fall()
{
rb.isKinematic = false;
rb.velocity = Vector3.down * Physics.gravity.magnitude * extraGravityMultiplier;
}
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("MainPlatform") && !isInactive)
{
isInactive = true;
platformCollider.enabled = false;
platformRenderer.enabled = false;
rb.isKinematic = true;
}
}
public void ResetPlatform()
{
isInactive = false;
transform.position = initialPosition;
transform.rotation = initialRotation;
rb.isKinematic = true;
hasFallen = false;
if (platformCollider != null) platformCollider.enabled = true;
if (platformRenderer != null) platformRenderer.enabled = true;
}
}
GameOverUI.cs
Controlador del menú Game Over.Reinicia o cierra el juego y permite al jugador reintentarlo o salir.
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameOverUI : MonoBehaviour
{
public void Retry()
{
SceneManager.LoadScene("SampleScene");
}
public void QuitGame()
{
Application.Quit();
}
}
GreenCoin.cs
RMoneda que otorga vida al jugador. Comprueba si necesita vida y la otorga.
using UnityEngine;
public class GreenCoin : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
PlayerLifeSystem playerLife = other.GetComponent<PlayerLifeSystem>();
if (playerLife != null)
{
if (playerLife.NeedsLife())
{
playerLife.GainLife();
Destroy(gameObject);
}
else
{
Debug.Log("Vidas completas, no se puede recoger la moneda.");
}
}
}
}
KillZone.cs
Zona letal que daña al jugador. OnTriggerEnter quita una vida y define zonas de peligro en el nivel.
using UnityEngine;
public class KillZone : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag(“Player”))
{
PlayerLifeSystem pls = other.GetComponent<PlayerLifeSystem>();
if (pls != null)
{
pls.TakeDamage();
}
}
}
}
MainMenu.cs
Maneja botones del menú principal.Inicia, muestra opciones y cierra con navegación inicial del juego.
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public void StartGame()
{
SceneManager.LoadScene("YourGameScene");
}
public void Options()
{
Debug.Log("Opciones");
}
public void QuitGame()
{
Application.Quit();
Debug.Log("Juego cerrado");
}
}
MainMenuManager.cs
Controla inicialización y transición del menú al juego.Pausa, oculta UI y reanuda con Play.Transición fluida entre UI y gameplay.
using UnityEngine;
public class MainMenuManager : MonoBehaviour
{
public CameraFollow cameraFollowScript;
public GameObject Canvas;
void Start()
{
Time.timeScale = 0;
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
cameraFollowScript.enabled = false;
Canvas.SetActive(false);
}
public void PlayGame()
{
Time.timeScale = 1;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
cameraFollowScript.enabled = true;
Canvas.SetActive(true);
gameObject.SetActive(false);
}
}
MovingPlatform.cs
Plataforma que se mueve entre dos puntos,Kinematic MoveBetween A-B con velocidad definida. Desafío de sincronización y dinamismo al nivel.
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class MovingPlatform : MonoBehaviour
{
public Vector3 pointA = Vector3.zero;
public Vector3 pointB = new Vector3(3, 0, 0);
public float speed = 2f;
public float tolerance = 0.05f;
private Vector3 worldPointA, worldPointB, target, velocity;
private Rigidbody rb;
private void Start()
{
rb = GetComponent<Rigidbody>();
rb.isKinematic = true;
rb.interpolation = RigidbodyInterpolation.Interpolate;
worldPointA = transform.position + pointA;
worldPointB = transform.position + pointB;
target = worldPointB;
}
private void FixedUpdate()
{
Vector3 current = rb.position;
Vector3 newPos = Vector3.MoveTowards(current, target, speed * Time.fixedDeltaTime);
velocity = (newPos - current) / Time.fixedDeltaTime;
rb.MovePosition(newPos);
if (Vector3.Distance(newPos, target) < tolerance)
target = target == worldPointA ? worldPointB : worldPointA;
}
public Vector3 GetVelocity() => velocity;
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
Vector3 a = Application.isPlaying ? worldPointA : transform.position + pointA;
Vector3 b = Application.isPlaying ? worldPointB : transform.position + pointB;
Gizmos.color = Color.green; Gizmos.DrawSphere(a, 0.1f);
Gizmos.color = Color.red; Gizmos.DrawSphere(b, 0.1f);
Gizmos.color = Color.yellow;Gizmos.DrawLine(a, b);
}
#endif
}
PlayerController.cs
Control principal de jugador: movimiento, salto, ataque y plataforma. Input System, animaciones, detección OverlapSphere y TriggerFall.
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
public float speed = 5f; public float gravity = -9.81f; public float jumpHeight = 2f; public float speedSmooth = 10f;
public float attackRange = 1.5f; public int attackDamage = 1; public LayerMask enemyLayer;
private CharacterController controller; private PlayerControls inputActions;
private Vector2 moveInput; private Vector3 velocity; private bool jumpRequested = false;
private Animator animator; private float currentAnimSpeed = 0f;
private void Awake()
{
controller = GetComponent<CharacterController>();
inputActions = new PlayerControls();
animator = GetComponentInChildren<Animator>();
}
private void OnEnable()
{
inputActions.Player.Enable();
inputActions.Player.Move.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
inputActions.Player.Move.canceled += ctx => moveInput = Vector2.zero;
inputActions.Player.Jump.performed += ctx => jumpRequested = true;
inputActions.Player.Attack.performed += ctx => animator.SetTrigger("Attack");
}
public void PerformAttack()
{
Collider[] hits = Physics.OverlapSphere(transform.position, attackRange, enemyLayer);
foreach (Collider c in hits)
c.GetComponent<IDamageable>()?.TakeDamage(attackDamage);
}
private void Update()
{
bool isGrounded = controller.isGrounded;
animator.SetBool("isGrounded", isGrounded);
float targetSpeed = new Vector2(moveInput.x, moveInput.y).magnitude;
currentAnimSpeed = Mathf.Lerp(currentAnimSpeed, targetSpeed, speedSmooth * Time.deltaTime);
animator.SetFloat("Speed", currentAnimSpeed);
animator.SetBool("isRunning", targetSpeed > 0.1f);
if (isGrounded)
{
if (velocity.y < 0) velocity.y = -2f;
if (jumpRequested)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
jumpRequested = false;
animator.ResetTrigger("Jump"); animator.SetTrigger("Jump");
}
}
velocity.y += gravity * Time.deltaTime;
Vector3 move = transform.right * moveInput.x + transform.forward * moveInput.y;
controller.Move((move * speed + Vector3.up * velocity.y) * Time.deltaTime);
}
private void OnControllerColliderHit(ControllerColliderHit hit)
{
hit.gameObject.GetComponent<FallingPlatform>()?.TriggerFall();
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
}
}
PlayerLifeSystem.cs
Sistema de puntos de vida del jugador con respawn y Game Over.Caídas, caída fuera del mapa, UI de vidas, reinicio plataformas. Control del ciclo de vida del jugador y gestión de errores en caída.
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerLifeSystem : MonoBehaviour
{
public int maxLives = 3; public Transform respawnPoint; public float minFallHeight = 2.357f; public float deathYLimit = -20f;
public UIManager uiManager;
private int currentLives; private float highestY; private bool isFalling = false; private bool wasGroundedLastFrame = true; private bool lifeLostThisFall = false;
private CharacterController controller; private GameObject lastGroundedObject;
public Vector3 velocity = Vector3.zero;
public int CurrentLives => currentLives;
void Start()
{
controller = GetComponent<CharacterController>();
if (controller == null || respawnPoint == null) { enabled = false; return; }
currentLives = maxLives; highestY = transform.position.y;
uiManager?.UpdateLives(currentLives);
}
void Update()
{
bool isGrounded = controller.isGrounded; float currentY = transform.position.y;
if (!isGrounded)
{
if (currentY > highestY) highestY = currentY;
if (!isFalling) { isFalling = true; lifeLostThisFall = false; }
}
if (isGrounded && !wasGroundedLastFrame && isFalling)
{
float fallDistance = highestY - currentY;
RaycastHit hit; if (Physics.Raycast(transform.position, Vector3.down, out hit, 2f)) lastGroundedObject = hit.collider.gameObject;
if (fallDistance >= minFallHeight && !lifeLostThisFall && lastGroundedObject.CompareTag("MainPlatform"))
{ LoseLife(); lifeLostThisFall = true; }
ResetFallTracking();
}
if (transform.position.y < deathYLimit && !lifeLostThisFall) { LoseLife(); lifeLostThisFall = true; }
wasGroundedLastFrame = isGrounded;
}
void OnControllerColliderHit(ControllerColliderHit hit) { if (controller.isGrounded) lastGroundedObject = hit.gameObject; }
void LoseLife()
{
currentLives--; uiManager?.UpdateLives(currentLives);
if (currentLives > 0) Respawn(); else GameOver();
}
void Respawn()
{
velocity = Vector3.zero; controller.enabled = false; transform.position = respawnPoint.position; controller.enabled = true; ResetFallTracking();
foreach (var p in FindObjectsByType<FallingPlatform>(FindObjectsSortMode.None)) p.ResetPlatform();
}
void GameOver() { SceneManager.LoadScene("GameOverScene"); }
void ResetFallTracking() { highestY = transform.position.y; isFalling = false; }
public bool NeedsLife() => currentLives < maxLives;
public void GainLife() { if (NeedsLife()) { currentLives++; uiManager?.UpdateLives(currentLives); } }
public void TakeDamage() { LoseLife(); }
}
Roller.cs
Rodillo que daña al jugador y se destruye al finalizar recorrido.Detecta triggers y colisiones con el Player.
using UnityEngine;
public class Roller : MonoBehaviour
{
public bool HasLeftPlatform { get; private set; } = false;
public bool IsDone { get; private set; } = false;
public bool HasTouchedPlazaRoll { get; private set; } = false;
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("PlazaRoll"))
HasTouchedPlazaRoll = true;
else if (other.CompareTag("RollerEnd"))
{
HasLeftPlatform = true;
IsDone = true;
Destroy(gameObject);
}
}
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Player"))
{
collision.gameObject.GetComponent<PlayerLifeSystem>()?.TakeDamage();
}
}
}
RollerSpawner.cs
Generador alternado de rodillos en puntos izquierdo/derecho. Controla spawn y evita repeticiones.Ritmo de obstáculos y variabilidad.
using UnityEngine;
using System.Collections;
public class RollerSpawner : MonoBehaviour
{
public GameObject[] rollerPrefabs;
public Transform leftSpawnPoint;
public Transform rightSpawnPoint;
public float spawnDelay = 1.5f;
private bool spawnLeftNext = true;
private int lastPrefabIndex = -1;
void Start()
{
StartCoroutine(SpawnRollersAlternately());
}
IEnumerator SpawnRollersAlternately()
{
while (true)
{
Transform sp = spawnLeftNext ? leftSpawnPoint : rightSpawnPoint;
int idx = GetRandomPrefabIndex();
GameObject roller = Instantiate(rollerPrefabs[idx], sp.position, Quaternion.identity);
Roller rs = roller.GetComponent<Roller>();
if (rs != null)
yield return new WaitUntil(() => rs.HasTouchedPlazaRoll);
else
yield return new WaitForSeconds(spawnDelay);
spawnLeftNext = !spawnLeftNext;
}
}
int GetRandomPrefabIndex()
{
int idx;
do { idx = Random.Range(0, rollerPrefabs.Length); }
while (idx == lastPrefabIndex);
lastPrefabIndex = idx;
return idx;
}
}
UIManager.cs
Gestiona UI de vidas.Actualiza contador en pantalla.Feedback inmediato al jugador sobre sus vidas restantes.
using UnityEngine;
using TMPro;
public class UIManager : MonoBehaviour
{
public TextMeshProUGUI livesText;
public void UpdateLives(int currentLives)
{
livesText.text = "Vidas: " + currentLives;
}
}
VictoryTrigger.cs
Detecta llegada a meta y activa UI de victoria
using UnityEngine;
public class VictoryTrigger : MonoBehaviour
{
public VictoryUIManager victoryUI;
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Player"))
{
victoryUI.ShowVictoryScreen();
}
}
}
VictoryUIManager.cs
Controla panel de victoria y navegación entre niveles.
using UnityEngine;
using UnityEngine.SceneManagement;
public class VictoryUIManager : MonoBehaviour
{
public GameObject victoryPanel;
void Start()
{
if (victoryPanel != null)
victoryPanel.SetActive(false);
}
public void ShowVictoryScreen()
{
if (victoryPanel != null)
{
Time.timeScale = 0f;
victoryPanel.SetActive(true);
}
}
public void RetryLevel()
{
Time.timeScale = 1f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
public void NextLevel()
{
Time.timeScale = 1f;
int nextSceneIndex = SceneManager.GetActiveScene().buildIndex + 1;
if (nextSceneIndex < SceneManager.sceneCountInBuildSettings)
SceneManager.LoadScene(nextSceneIndex);
else
Debug.Log("¡No hay más niveles!");
}
}
Video explicación
¿Cómo se juega SpeedAndJump?
🎯 Objetivo principal:
Sobrevivir y avanzar por un entorno de plataformas mientras esquivas peligros, derrotas enemigos (como abejas), recoges monedas de vida y llegas al final del nivel (zona de victoria).
🕹️ Controles del jugador:
Movimiento: Con teclado (probablemente con WASD
o flechas).
Cámara: Controlada con el ratón (rotación horizontal y vertical).
Saltar: Presionar una tecla (probablemente Espacio
) para realizar un salto.
Atacar: Usar una tecla (probablemente Click izquierdo
o Ctrl
) para ejecutar una animación de ataque que daña enemigos cercanos.
❤️ Sistema de vidas:
- Empiezas con 3 vidas.
- Pierdes una vida si:
- Caes desde una altura muy alta.
- Te caes del mapa.
- Te ataca un enemigo.
- Entras en una zona letal (
KillZone
).
- Puedes recuperar vidas recogiendo monedas verdes (
GreenCoin
), pero solo si no tienes el máximo.
🐝 Enemigos:
Hay abejas voladoras que siguen al jugador y atacan cuando están cerca.
Al recibir daño, el jugador pierde una vida.
Las abejas se comportan de forma dinámica: siguen, flotan y atacan.
⚠️ Obstáculos y plataformas:
Hay plataformas móviles y otras que caen al pisarlas, aumentando el desafío.
Rodillos que se mueven y pueden dañar al jugador (Roller
).
Zona letal (KillZone
) que mata automáticamente al jugador si entra.
🧩 Recursos y recolección:
Monedas de vida sobre los obstáculos. Aparecen automáticamente al inicio (CoinSpawner
).
Sistema de UI que te muestra cuántas vidas te quedan.
🏁 Fin del juego:
Ganas si llegas al final del nivel y activas la zona de victoria (VictoryTrigger
).
Pierdes si se te acaban las vidas y se carga la escena de Game Over (GameOverScene
).
Video Juego SpeedAndJump
Créditos
Autor: Diana Marcela Sanchez Sanchez
Editor: Carlos Iván Pinzón Romero
Código: UCMV-9 – Modelado 3D y Videojuegos
Universidad: Universidad central
Referencia
Unity Technologies. (2025). Software de desarrollo de juegos: Crea juegos 2D y 3D. Unity. https://unity.com/es/games
Unity Technologies. (s.f.). Guía de inicio rápido para el desarrollo de juegos 3D. Recuperado el 23 de mayo de 2025, de https://docs.unity3d.com/6000.1/Documentation/Manual/Quickstart3D.html
Unity Technologies. (2025). Unity Asset Store: The Best Assets for Game Making. Unity. https://assetstore.unity.com/
Pixabay. (s.f.). Efectos de sonido de bosque horror - búsqueda. https://pixabay.com/es/sound-effects/search/bosque-horror/
Unity Technologies. (2022). Tiling Textures - 3D Microgame Add-Ons. AssetStore Unity. https://assetstore.unity.com/packages/2d/textures-materials/tiling-textures-3d-microgame-add-ons-174461