Depuración y calidad de Blueprints en Unreal Engine

Capítulo 10

Tiempo estimado de lectura: 11 minutos

+ Ejercicio

Herramientas de depuración en Blueprints (cuándo usar cada una)

Depurar en Blueprints consiste en observar el flujo de ejecución y el estado de los datos mientras el juego corre. La clave es elegir la herramienta adecuada según el tipo de problema: si necesitas confirmar “¿llega aquí?”, usa trazas; si necesitas ver “¿por qué tomó esta rama?”, usa breakpoints y paso a paso; si necesitas saber “¿qué valor tiene esta variable justo antes de fallar?”, usa Watch Values y el Blueprint Debugger.

Print String (trazas rápidas)

Print String es la forma más rápida de confirmar que un evento se ejecuta y qué valores están entrando/saliendo. Úsalo para validar hipótesis, no como solución permanente.

  • Cuándo usarlo: confirmar que un evento se dispara, verificar valores de entrada, detectar ramas que no se ejecutan.
  • Cuándo evitarlo: cuando ya necesitas inspección detallada (mejor breakpoints/Debugger) o cuando ensucia la pantalla/log.

Guía práctica:

  • Inserta Print String justo antes de una decisión (por ejemplo, antes de un Branch).
  • Concatena información útil: nombre del actor, valor de una variable, resultado de un cast. Ejemplo: "Stamina=" + ToString(Stamina) + " IsSprinting=" + ToString(bIsSprinting).
  • Activa Print to Log si quieres revisar el historial en el Output Log.

Breakpoints (pausar en un nodo)

Un breakpoint detiene la ejecución cuando el flujo llega a un nodo específico, permitiéndote inspeccionar el estado del Blueprint en ese instante.

Guía práctica:

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

  • En el Blueprint, haz clic derecho sobre un nodo y selecciona Add Breakpoint (o usa el atajo disponible en tu editor).
  • Ejecuta el juego en Play In Editor.
  • Cuando se detenga, observa el flujo resaltado y revisa variables en el panel de detalles/depuración.
  • Desactiva o elimina el breakpoint cuando termines para evitar pausas accidentales.

Step / Resume (paso a paso)

Con la ejecución pausada, Step avanza nodo a nodo y Resume continúa hasta el siguiente breakpoint o hasta que termine el flujo. Esto es ideal para entender por qué una condición se evalúa de cierta manera o en qué orden se ejecutan llamadas.

Guía práctica:

  • Coloca un breakpoint antes de la zona sospechosa (por ejemplo, antes de un Sequence o un conjunto de ramas).
  • Cuando se pause, usa Step para avanzar y observa qué nodos se iluminan y qué valores cambian.
  • Si ya confirmaste el problema, usa Resume para volver al flujo normal.

Watch Values (vigilar variables y pines)

Watch te permite “anclar” el valor de una variable o incluso de un pin para ver cómo cambia durante la ejecución, especialmente útil cuando el valor se modifica en varios lugares.

Guía práctica:

  • Durante depuración, haz clic derecho sobre una variable o un pin y selecciona Watch this value.
  • Ejecuta la acción en el juego que reproduce el bug.
  • Observa el valor en el panel de Watch; si cambia inesperadamente, ya tienes una pista del origen.

Blueprint Debugger (vista centralizada de ejecución)

El Blueprint Debugger reúne información de instancias, breakpoints, pila de llamadas y valores observados. Es especialmente útil cuando hay múltiples instancias del mismo Blueprint (por ejemplo, varios enemigos) y necesitas depurar una instancia específica.

Guía práctica:

  • Abre el Blueprint Debugger desde las herramientas del editor.
  • Selecciona la instancia correcta del Blueprint (por ejemplo, el enemigo que está fallando, no otro).
  • Revisa breakpoints activos y la ejecución resaltada.
  • Combina con Watch Values para ver variables clave sin saturar con Print String.

Errores comunes y cómo identificarlos rápidamente

1) Referencias nulas (None / Null)

Una referencia nula ocurre cuando intentas usar un objeto que no existe (o aún no está asignado). En Blueprints suele manifestarse como errores en el log del tipo “Accessed None trying to read property…”.

Señales típicas:

  • Errores “Accessed None” al ejecutar una interacción.
  • El bug aparece solo en ciertas condiciones (por ejemplo, al respawnear o al cambiar de nivel).

Checklist de depuración:

  • Coloca un breakpoint justo antes del nodo que falla y revisa la referencia.
  • Agrega una validación con Is Valid antes de usar la referencia.
  • Confirma el momento de asignación: ¿se asigna en BeginPlay, al overlap, al spawn, o depende de otra acción?
// Patrón típico (visual): Reference --> Is Valid? --> (Valid) usar --> (Not Valid) log/recuperar

2) Casts fallidos

Un cast fallido sucede cuando intentas convertir una referencia a un tipo que no corresponde. El flujo “Cast Failed” se ejecuta, pero a veces no se maneja y el sistema queda en un estado incompleto.

Señales típicas:

  • La lógica “a veces funciona” dependiendo de qué actor esté interactuando.
  • Variables específicas del tipo casteado nunca se actualizan.

Checklist de depuración:

  • Coloca Print String en la salida Cast Failed con el nombre del actor recibido (Get Display Name).
  • Verifica el origen de la referencia: ¿viene de un overlap genérico? ¿de un hit? ¿de un array?
  • Si el cast se usa solo para “llamar una acción”, considera reemplazarlo por una Interface (reduce acoplamiento y fallos).

3) Lógica innecesaria en Tick

Usar Event Tick para tareas que podrían ser event-driven (por eventos, timers o cambios de estado) genera consumo constante y dificulta la depuración, porque el flujo se ejecuta cientos de veces por segundo.

Señales típicas:

  • Caídas de rendimiento sin razón aparente.
  • Valores que “parpadean” o se sobreescriben continuamente.
  • Difícil reproducir el bug porque el estado cambia cada frame.

Checklist de refactor:

  • Pregunta: “¿Esto necesita actualizarse cada frame?” Si no, muévelo a un evento (por ejemplo, al presionar un input, al comenzar/terminar un estado, al entrar/salir de un trigger).
  • Para actualizaciones periódicas, usa un Timer (intervalo fijo) en lugar de Tick.
  • Para cambios de UI o efectos, actualiza solo cuando cambie el valor (patrón: SetX que dispara actualización).

4) Colisiones mal configuradas (eventos que no se disparan)

Muchos “bugs de lógica” son en realidad problemas de colisión: el evento no se dispara porque el canal, la respuesta o la generación de eventos no está configurada como se espera.

Señales típicas:

  • No se ejecuta BeginOverlap o Hit aunque los actores se toquen.
  • Funciona en un actor pero no en otro (por presets distintos).

Checklist de depuración:

  • Verifica que el componente correcto tenga Generate Overlap Events activado.
  • Confirma que ambos actores/componentes tengan respuestas compatibles (Overlap vs Block vs Ignore) en el canal correspondiente.
  • Usa Print String en el evento de overlap/hit para confirmar qué actor está entrando y con qué componente.
  • Si hay múltiples componentes, asegúrate de estar escuchando el evento en el componente que realmente colisiona (no en el root si no colisiona).

Buenas prácticas para Blueprints mantenibles (calidad y legibilidad)

Nombres claros y consistentes

  • Variables booleanas: prefijo b o nombres que indiquen estado: bIsSprinting, bHasKeycard.
  • Funciones: verbos + objeto: ApplyDamage, StartInteract, UpdateStaminaUI.
  • Eventos personalizados: intención clara: OnStaminaDepleted, OnInteractionStarted.

Comentarios útiles (no decorativos)

Comenta el “por qué” y las suposiciones, no lo obvio. Un buen comentario explica una regla de negocio o una razón técnica.

  • Malo: “Resta stamina”.
  • Bueno: “La stamina solo se drena si el jugador está en sprint y está en el suelo; evita drenaje en el aire para no penalizar saltos”.

Funciones pequeñas y con una sola responsabilidad

Divide la lógica en funciones que puedas probar mentalmente. Si una función hace demasiadas cosas, será difícil de depurar y reutilizar.

  • Ejemplo de separación: CanSprint (validación) + StartSprint (cambio de estado) + DrainStamina (consumo) + UpdateSprintFX (feedback).

Categorías y organización de variables

Usa categorías para agrupar variables por sistema (por ejemplo: Movement, Combat, UI, Debug). Esto acelera la inspección durante depuración y reduce errores por editar la variable equivocada.

Reutilización mediante componentes e interfaces

  • Componentes: encapsulan una mecánica reutilizable (por ejemplo, “StaminaComponent”, “InteractComponent”). Mejoran testeo y reducen duplicación.
  • Interfaces: permiten llamar acciones sin cast directo (por ejemplo, BPI_Interactable con Interact). Disminuyen casts fallidos y acoplamiento.

Sesión guiada de refactor: de Blueprint desordenado a Blueprint mantenible

En esta sesión, tomaremos un ejemplo típico: un Blueprint de personaje con una mecánica (por ejemplo, sprint con stamina) implementada “rápido” en un solo gráfico con ramas, prints y lógica repetida. El objetivo es reorganizarlo en variables bien tipadas, funciones pequeñas, eventos claros y puntos de depuración limpios.

Escenario inicial (síntomas del Blueprint desordenado)

  • Gran cantidad de nodos conectados en una sola zona (spaghetti).
  • Uso de Tick para drenar stamina aunque el jugador no esté sprintando.
  • Variables sin categorías y nombres ambiguos (por ejemplo, Value, Temp).
  • Varios Print String permanentes.
  • Cast a un tipo específico para actualizar UI, con fallos ocasionales.

Paso 1: Congelar el comportamiento actual (para no romperlo)

Antes de refactorizar, crea un “punto de referencia” para comparar.

  • Reproduce el caso: iniciar sprint, drenar stamina, detener sprint, recuperar stamina.
  • Agrega temporalmente 2–3 trazas clave (no más):
  • Print String al iniciar sprint: muestra Stamina y bIsSprinting.
  • Print String al detener sprint.
  • Print String cuando stamina llega a 0.

Esto te permite confirmar que el comportamiento final del refactor coincide con el original.

Paso 2: Definir variables bien tipadas y con categorías

Crea/renombra variables con intención clara. Ejemplo para un sistema de stamina:

VariableTipoCategoríaNotas
StaminaFloatStaminaValor actual
MaxStaminaFloatStaminaConstante editable
StaminaDrainPerSecondFloatStaminaConsumo al sprintar
StaminaRegenPerSecondFloatStaminaRegeneración al no sprintar
bIsSprintingBooleanMovementEstado
StaminaTickTimerHandleTimerHandleStaminaPara timers (si aplica)

Regla práctica: si una variable representa un “estado” (encendido/apagado), debe ser boolean y nombrarse como estado. Si representa una “cantidad”, debe ser numérica y con unidad implícita (por segundo, máximo, actual).

Paso 3: Extraer funciones (1 responsabilidad por función)

Identifica bloques repetidos o decisiones centrales y conviértelos en funciones. Propuesta:

  • CanStartSprint() → devuelve Boolean (y opcionalmente una razón para debug).
  • StartSprint() → setea bIsSprinting, aplica cambios de movimiento, inicia drenaje.
  • StopSprint() → revierte estado y detiene drenaje.
  • ModifyStamina(Delta) → suma/resta y clampa entre 0 y Max.
  • OnStaminaChanged() → punto único para actualizar UI/feedback.
  • HandleStaminaDepleted() → se llama cuando llega a 0 (detener sprint, etc.).

Tip de calidad: cuando una función tiene más de 2–3 ramas internas y además llama a UI, sonido y movimiento, probablemente debas dividirla.

Paso 4: Reemplazar Tick por eventos/timers (si corresponde)

En lugar de drenar stamina en cada frame, usa un timer mientras el jugador está sprintando. Esto reduce carga y hace la depuración determinista.

Implementación sugerida (patrón):

  • En StartSprint: inicia un timer repetitivo (por ejemplo, cada 0.1s) que llame a DrainStaminaTick.
  • En StopSprint: limpia el timer de drenaje y, si quieres, inicia otro timer para regeneración (o regenera por eventos de estado).
DrainStaminaTick(): ModifyStamina(-StaminaDrainPerSecond * Interval); if Stamina <= 0 -> HandleStaminaDepleted()

Para regeneración, aplica el mismo patrón cuando bIsSprinting sea falso, o regenera solo cuando el estado cambie (por ejemplo, iniciar un timer de regen al detener sprint).

Paso 5: Centralizar la actualización (UI/feedback) en un solo punto

Un error común es actualizar UI desde múltiples lugares, lo que genera inconsistencias. En su lugar:

  • Haz que ModifyStamina llame a OnStaminaChanged solo cuando el valor realmente cambie.
  • Dentro de OnStaminaChanged, llama a la UI mediante una Interface (si tu UI o HUD implementa una interfaz), evitando casts directos.

Ejemplo conceptual: si existe BPI_StaminaListener con OnStaminaUpdated(Current, Max), el personaje solo necesita llamar a la interfaz en el objeto que corresponda, sin asumir su clase concreta.

Paso 6: Añadir puntos de depuración “limpios” (sin ruido)

En vez de muchos Print String dispersos, crea un pequeño bloque de debug reutilizable:

  • Variable bEnableDebugStamina (Categoría: Debug).
  • Función DebugLogStamina(Message) que solo imprime si bEnableDebugStamina está activa.
DebugLogStamina(Message): if bEnableDebugStamina -> Print String(Message, Print to Log=true)

Así puedes activar/desactivar depuración sin borrar nodos.

Paso 7: Validaciones para evitar referencias nulas y casts fallidos

En los puntos donde interactúas con otros objetos (UI, componentes, actores), aplica estas reglas:

  • Antes de usar una referencia: Is Valid.
  • Si dependes de una instancia específica (por ejemplo, un widget): inicialízala en un punto controlado y guarda la referencia; si no existe, loguea una vez (no cada frame).
  • Si solo necesitas “enviar un mensaje”: usa Interface en lugar de cast.

Paso 8: Verificación final con Blueprint Debugger

Ahora compara contra el comportamiento “congelado” del Paso 1.

  • Coloca breakpoints en StartSprint, StopSprint y HandleStaminaDepleted.
  • Usa Step para confirmar el orden: CanStartSprintStartSprint → timer → ModifyStaminaOnStaminaChanged.
  • Agrega Watch a Stamina y bIsSprinting para ver cambios sin imprimir.

Mini-checklist de calidad antes de dar por terminado un Blueprint

  • ¿Hay nodos en Tick que podrían ser eventos o timers?
  • ¿Hay casts que podrían ser interfaces?
  • ¿Las referencias externas están validadas con Is Valid?
  • ¿Las funciones son pequeñas y con nombres verbales?
  • ¿Las variables tienen categorías y tipos correctos (sin “Temp” permanentes)?
  • ¿La actualización de UI/feedback está centralizada?
  • ¿Los prints están detrás de un flag de debug o eliminados?

Ahora responde el ejercicio sobre el contenido:

Si estás depurando un Blueprint y necesitas entender por qué una condición tomó una rama específica y en qué orden se ejecutan los nodos, ¿qué enfoque es el más adecuado?

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

¡Tú error! Inténtalo de nuevo.

Los breakpoints permiten pausar en un punto exacto y Step/Resume ayuda a ver el orden de ejecución y por qué una condición toma cierta rama, inspeccionando valores durante la pausa.

Siguiente capítulo

Proyecto integrador de Unreal Engine: prototipo jugable basado en Blueprints

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

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.