Construcción de mecánicas de gameplay con Blueprints

Capítulo 8

Tiempo estimado de lectura: 2 minutos

+ Ejercicio

Ensamblar sistemas de gameplay como “bloques”

Una mecánica de gameplay rara vez es un solo Blueprint: normalmente es un conjunto de sistemas que colaboran (salud/daño, energía, inventario/contador, condiciones de victoria/derrota y UI). La clave para que el proyecto escale es separar responsabilidades y reutilizar lógica mediante Actor Components. Un Actor (Jugador, Enemigo, Hazard) “tiene” componentes; cada componente encapsula una parte del comportamiento y expone eventos/funciones para integrarse con el resto.

Objetivo del capítulo

  • Construir un sistema de Salud/Daño reutilizable con un Actor Component.
  • Crear un hazard/enemigo que aplica daño por contacto (instantáneo o por intervalo).
  • Reflejar la vida en un HUD y actualizarlo mediante eventos, sin depender de Tick.
  • Integrar un contador simple (inventario/coleccionables) y condiciones de victoria/derrota basadas en eventos.

Diseño con Actor Components: separar responsabilidades

Piensa en un componente como una “caja negra” con:

  • Estado: variables internas (por ejemplo, VidaActual).
  • API: funciones públicas (ApplyDamage, Heal, GetHealthPercent).
  • Eventos: dispatchers que notifican cambios (OnHealthChanged, OnDeath).

Ventajas prácticas:

  • Reutilizas el mismo componente en jugador, enemigos y objetos destructibles.
  • La UI no necesita “preguntar” cada frame; se actualiza cuando ocurre un cambio.
  • Las reglas de victoria/derrota se conectan a eventos (muerte, contador completado).

Componente reutilizable: BP_HealthComponent

1) Crear el Actor Component

Crea un Blueprint de tipo Actor Component llamado BP_HealthComponent.

2) Variables recomendadas

  • MaxHealth (Float, default 100)
  • CurrentHealth (Float)
  • bIsDead (Bool)
  • InvincibilityTime (Float, opcional)
  • LastDamageTime (Float, opcional)

En BeginPlay del componente, inicializa CurrentHealth = MaxHealth y bIsDead = false.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

3) Event Dispatchers (para UI y gameplay)

Agrega dos dispatchers:

  • OnHealthChanged con parámetros: NewHealth (Float), Delta (Float), InstigatorActor (Actor)
  • OnDeath con parámetros: KillerActor (Actor)

4) Funciones públicas

Crea estas funciones en el componente:

  • ApplyDamage(DamageAmount: float, InstigatorActor: Actor)
  • Heal(HealAmount: float)
  • GetHealthPercent() -> float

5) Implementación de ApplyDamage (paso a paso)

  1. Si bIsDead es true, salir.
  2. Clamp del daño: Damage = Max(DamageAmount, 0).
  3. Calcular nueva vida: NewHealth = Clamp(CurrentHealth - Damage, 0, MaxHealth).
  4. Delta = NewHealth - CurrentHealth (será negativo).
  5. Asignar CurrentHealth = NewHealth.
  6. Lanzar OnHealthChanged pasando CurrentHealth, Delta e InstigatorActor.
  7. Si CurrentHealth <= 0 y bIsDeadbIsDead = true y lanzar OnDeath.

En Blueprints, esto se traduce a una secuencia de nodos con Branch, Float - Float, Clamp (float) y llamadas a Call OnHealthChanged / Call OnDeath.

6) Implementación de Heal

  1. Si bIsDead es true, decidir si permites revivir (en principiantes: no).
  2. NewHealth = Clamp(CurrentHealth + HealAmount, 0, MaxHealth)
  3. Delta = NewHealth - CurrentHealth (positivo)
  4. Asignar y disparar OnHealthChanged (InstigatorActor puede ser null o el dueño).

7) GetHealthPercent

Devuelve CurrentHealth / MaxHealth (maneja MaxHealth == 0 devolviendo 0).

Integración en el jugador: sin Tick para UI

1) Añadir el componente al jugador

En el Blueprint del jugador (por ejemplo BP_PlayerCharacter), agrega el componente BP_HealthComponent desde el panel de Components.

2) Crear el HUD (Widget) con barra de vida

Crea un Widget Blueprint WBP_HUD con:

  • Un ProgressBar llamado PB_Health.
  • (Opcional) Un TextBlock para mostrar valores numéricos.

En el jugador o PlayerController, crea el widget en BeginPlay y añádelo al viewport (esto ya lo habrás hecho en capítulos anteriores; aquí nos enfocamos en la actualización por eventos).

3) Conectar eventos del componente a la UI

Hay dos patrones comunes. El más directo para principiantes: el jugador crea el HUD y se suscribe a los eventos del componente.

  1. En BP_PlayerCharacter, guarda una referencia a WBP_HUD (variable HUDRef).
  2. Tras crear el widget, obtén referencia al HealthComponent.
  3. Usa Bind Event to OnHealthChanged del componente.
  4. Crea un Custom Event (por ejemplo HandleHealthChanged) con la misma firma: NewHealth, Delta, InstigatorActor.
  5. Dentro de HandleHealthChanged, calcula el porcentaje: NewHealth / MaxHealth (puedes llamar a GetHealthPercent) y llama una función del widget (por ejemplo SetHealthPercent).

En el widget WBP_HUD, crea una función SetHealthPercent(Percent: float) que haga PB_Health.SetPercent(Percent). Así, la UI se actualiza únicamente cuando cambia la vida, sin Tick.

4) Manejar muerte (derrota) por evento

En el jugador, también enlaza OnDeath:

  • Bind Event to OnDeathHandlePlayerDeath
  • En HandlePlayerDeath: deshabilita input, reproduce animación/efecto, y notifica al sistema de GameMode/Manager que la partida terminó en derrota.

Caso práctico: hazard/enemigo que aplica daño por contacto o por intervalo

Construiremos un Actor llamado BP_DamageHazard (puede ser una trampa, fuego, ácido, etc.). La lógica será configurable: daño instantáneo al entrar o daño periódico mientras permanezca dentro.

1) Crear BP_DamageHazard

Componentes sugeridos:

  • StaticMesh (visual)
  • BoxCollision o CapsuleCollision (zona de daño)

Variables:

  • DamageAmount (Float, por ejemplo 10)
  • bDamageOverTime (Bool)
  • DamageInterval (Float, por ejemplo 1.0)
  • ActorsInside (Array de Actor, opcional si quieres soportar múltiples)
  • DamageTimerHandle (TimerHandle)

2) Aplicar daño instantáneo (al entrar)

  1. En el BoxCollision, usa el evento OnComponentBeginOverlap.
  2. Del OtherActor, intenta obtener el componente de salud: Get Component by Class (BP_HealthComponent).
  3. Si existe, llama ApplyDamage(DamageAmount, Self).

Esto hace que cualquier actor con BP_HealthComponent sea compatible (jugador, enemigo, NPC), sin hardcodear clases.

3) Daño por intervalo (mientras permanece dentro) usando Timers

Para daño periódico, evita Tick y usa un Timer.

  1. En OnComponentBeginOverlap:
  • Si bDamageOverTime es true, agrega OtherActor a ActorsInside (si no está).
  • Si el timer no está activo, inicia un timer repetitivo: Set Timer by Function Name o Set Timer by Event con intervalo DamageInterval.
  1. Crea una función/evento DealDamageTick que:
  • Itere ActorsInside.
  • Para cada actor válido, obtenga BP_HealthComponent y llame ApplyDamage(DamageAmount, Self).
  • Remueva actores inválidos (destroyed) del array.
  1. En OnComponentEndOverlap:
  • Remueve OtherActor de ActorsInside.
  • Si el array queda vacío, limpia el timer: Clear and Invalidate Timer.

Con esto, el hazard aplica daño cada X segundos solo cuando hay alguien dentro, sin costo constante por frame.

4) Variante: enemigo que daña por contacto con cooldown

Si quieres que el enemigo aplique daño al tocar pero con enfriamiento (para no “derretir” al jugador en un solo frame), puedes:

  • Guardar un Map<Actor, float> de último tiempo de daño (o un array paralelo).
  • Al overlap, comparar GetGameTimeInSeconds con el último daño y aplicar si pasó el cooldown.

En proyectos principiantes, el enfoque de timer por intervalo suele ser más simple y legible.

Contador simple (inventario mínimo) y condiciones de victoria

Un “inventario simple” para principiantes suele ser un contador de objetos recolectados (monedas, llaves, piezas). La idea es la misma que con salud: estado + evento.

1) Componente BP_CollectibleCounterComponent (opcional pero recomendado)

Crea un Actor Component BP_CollectibleCounterComponent con:

  • CurrentCount (Int)
  • TargetCount (Int)
  • Dispatcher OnCountChanged(NewCount:int)
  • Dispatcher OnTargetReached()

Función Add(int Amount):

  1. CurrentCount += Amount
  2. Disparar OnCountChanged
  3. Si CurrentCount >= TargetCount disparar OnTargetReached (una sola vez si quieres, usando un bool).

2) Recolectable BP_Collectible

En el actor recolectable:

  • Al ser recogido, obtiene del jugador el componente BP_CollectibleCounterComponent y llama Add(1).
  • Luego se destruye o se desactiva.

3) UI del contador sin Tick

En el jugador, enlaza OnCountChanged para actualizar un TextBlock del HUD (por ejemplo “3/10”). Igual que con salud: la UI se actualiza por evento.

4) Condición de victoria

En lugar de revisar constantemente si ya se llegó al objetivo, enlaza OnTargetReached a una acción de victoria:

  • Mostrar pantalla de victoria
  • Deshabilitar input
  • Detener spawns o timers

Condiciones de derrota: salud a cero

La derrota se activa naturalmente desde BP_HealthComponent con OnDeath. Esto evita que el GameMode tenga que “preguntar” cada frame por la vida del jugador.

Patrón recomendado: un “manager” escucha eventos

Si quieres mantener el jugador limpio, puedes crear un actor BP_GameplayManager (o usar GameMode) que:

  • Obtiene referencia al jugador al iniciar.
  • Se suscribe a HealthComponent.OnDeath y CounterComponent.OnTargetReached.
  • Decide si termina en victoria o derrota.

Así, el jugador solo se ocupa de ser jugador; el manager se ocupa de reglas de partida.

Tabla de “quién hace qué” (responsabilidades)

ElementoResponsabilidadCómo se comunica
BP_HealthComponentGestionar vida, aplicar daño/curación, emitir cambios y muerteDispatchers OnHealthChanged / OnDeath
BP_DamageHazardDetectar presencia y aplicar daño (instantáneo o por intervalo)Llama ApplyDamage en el componente del otro actor
WBP_HUDMostrar vida y contadorFunciones SetHealthPercent / SetCountText llamadas desde eventos
BP_CollectibleCounterComponentContar objetos y emitir cambios/objetivo cumplidoDispatchers OnCountChanged / OnTargetReached
Manager/GameModeReglas de victoria/derrotaSe suscribe a eventos y ejecuta fin de partida

Checklist de depuración (cuando “no funciona”)

  • ¿El actor que recibe daño realmente tiene BP_HealthComponent agregado?
  • ¿El hazard está llamando Get Component by Class sobre OtherActor (no sobre sí mismo)?
  • ¿El Timer se inicia solo cuando hay actores dentro y se limpia al salir?
  • ¿El HUD está guardado en una variable (referencia válida) antes de llamar funciones?
  • ¿Los eventos están correctamente “Bind” (suscritos) una sola vez (por ejemplo en BeginPlay)?
  • ¿La barra de vida usa valores 0..1 (Percent) y no 0..100?

Ahora responde el ejercicio sobre el contenido:

¿Cuál enfoque permite actualizar el HUD (por ejemplo, la barra de vida) sin usar Tick y manteniendo el sistema reutilizable entre distintos actores?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

La idea es separar responsabilidades con un Actor Component que encapsule el estado y dispare eventos (dispatchers). El HUD se enlaza a esos eventos (por ejemplo, OnHealthChanged) y se actualiza solo cuando hay cambios, sin depender de Tick.

Siguiente capítulo

Interfaces de usuario con UMG y conexión con gameplay

Arrow Right Icon
Portada de libro electrónico gratuitaUnreal Engine para Principiantes: Fundamentos de Blueprints y Lógica de Gameplay
73%

Unreal Engine para Principiantes: Fundamentos de Blueprints y Lógica de Gameplay

Nuevo curso

11 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.