Sistema de Movimiento sobre Raíles

Un servicio de raíles pasivo basado en snapshots que rastrea el progreso normalizado de 0 a 1 a lo largo de caminos de nodos con nombre, manteniendo los sistemas de movimiento, IA y cámara completamente desacoplados.

Para qué sirve este sistema

El movimiento sobre raíles parece sencillo hasta que la cámara, el controlador del jugador, el director de IA y el secuenciador de cinemáticas necesitan leer la misma posición del camino sin pisarse entre sí. Un MonoBehaviour que posee el transform y emite eventos parece funcionar al principio, pero colapsa en cuanto dos sistemas necesitan escribir o leer el progreso en el mismo frame.

El módulo Game Rail de Serenity resuelve eso separando el estado del camino de su visualización. Un único servicio posee el progreso normalizado para cada raíl con nombre. Cualquier sistema que necesite saber dónde está algo llama a GetSnapshot y lee un valor inmutable. Nadie pelea por la propiedad.

El problema en Unity

El movimiento sobre raíles en Unity suele acabar como un único componente que mueve un transform, dispara eventos en los waypoints e intenta ser la fuente de verdad para posición, progreso y finalización. Cuando aparece un segundo consumidor —un rig de cámara, un enemigo que espeja el camino del jugador, una barra de progreso en la UI, un disparador de cinemáticas— el componente acumula condicionales, campos expuestos y referencias directas que lo hacen imposible de reutilizar entre escenas.

El problema de fondo es acoplar estado a visualización. En el momento en que un MonoBehaviour posee tanto el valor de progreso como el transform que mueve, las pruebas y la reutilización se vuelven impracticables. Los cambios de game mode, los reinicios del spawner y las transiciones entre escenas necesitan resetear o inspeccionar el progreso desde fuera, y un componente autocontenido no tiene una superficie limpia para eso.

Cómo lo aborda Serenity

Serenity expone el estado de los raíles a través de IGameRailService, que incluye ConfigureRail y RegisterRail para definir caminos con nombre a partir de RailPathConfig o arrays de RailNodePosition, AdvanceProgress y SetProgress para escribir deltas de movimiento normalizados, GetProgress para leer un value object RailProgress y GetSnapshot para obtener un RailSnapshot inmutable que contiene la identidad del raíl, el progreso actual y el número de nodos.

El servicio es intencionadamente pasivo. Nada en su interior mueve un transform ni dispara eventos. Los sistemas externos —el game loop, un controlador de IA, un reproductor de secuencias— llaman a AdvanceProgress cada frame. El renderizado lo gestiona UnityRailFollowerView, un MonoBehaviour que lee GetSnapshot en cada actualización y aplica una pose interpolada a cualquier transform objetivo con orientación look-ahead opcional y suavizado exponencial.

Cómo encaja en Serenity

Game Rail vive en el namespace Serenity.GameRail y sigue la estructura por capas de la foundation. La capa de Dominio define los value objects RailId, RailProgress, RailNodePosition y RailSnapshot, la entidad RailPath y el struct de configuración RailPathConfig, todos en C# puro sin dependencias del motor. La capa de Aplicación expone IGameRailService. La capa de Infraestructura proporciona UnityGameRailService, la implementación MonoBehaviour, y UnityRailFollowerView para aplicar el transform. La instalación la gestiona UnityGameRailInstaller.

Game Rail coopera con Game Mode para que el modo activo pueda registrar y resetear raíles en los límites de fase o wave, con el Spawner para que los actores instanciados puedan vincularse a un raíl al crearse, con Game Session para secuencias de fase y wave sobre raíles, y con sistemas de reproducción de secuencias o cinemáticas que necesitan un progreso preciso y controlado externamente a lo largo de un camino de cámara o cinematográfico.

Flujo de trabajo práctico

  1. Define los caminos de raíl como structs RailPathConfig con un RailId y un array ordenado de waypoints RailNodePosition.
  2. Llama a ConfigureRail en IGameRailService durante la inicialización, típicamente desde el installer o un script de configuración de nivel.
  3. Avanza el progreso cada frame llamando a AdvanceProgress con un delta escalado en tiempo desde tu game loop, controlador de IA o reproductor de secuencias.
  4. Añade UnityRailFollowerView a cualquier GameObject que deba seguir el raíl; vincula el RailId objetivo y configura el muestreo de orientación y el suavizado en el inspector.
  5. Llama a GetSnapshot desde cualquier otro sistema —rig de cámara, barra de progreso en la UI, disparador de evento— para leer el estado inmutable del raíl sin acoplarte al objeto que se mueve.
  6. Llama a ResetProgress o SetProgress desde Game Mode o el Spawner para reiniciar o saltar a un punto concreto cuando una fase se reinicia.

Qué incluye

  • IGameRailService con ConfigureRail, RegisterRail, AdvanceProgress, SetProgress, GetProgress, GetSnapshot y ResetProgress
  • Struct de configuración inmutable RailPathConfig que combina RailId y arrays ordenados de RailNodePosition
  • Value object RailProgress normalizado de 0 a 1 con Advance acotado y helpers IsAtStart e IsAtEnd
  • Modelo de lectura inmutable RailSnapshot que expone RailId, RailProgress y NodeCount para lecturas sin acoplamiento
  • Identidad con nombre RailId que desacopla el registro del consumo entre sistemas
  • MonoBehaviour UnityRailFollowerView con fase de actualización configurable, orientación look-ahead y suavizado exponencial
  • Diseño intencionadamente pasivo: el progreso siempre lo impulsan sistemas externos, nunca se auto-actualiza
  • Integración con Game Mode, Spawner y Game Session para secuencias de fase y wave sobre raíles

Cuándo usarlo

  • Proyectos de rail shooter o runner donde el jugador, los enemigos y la cámara necesitan compartir la misma posición del camino.
  • Sistemas de cámara cinematográfica que siguen un camino con script controlado por un reproductor de secuencias o cinemáticas en lugar de un componente auto-animado.
  • Diseños de fase o wave donde Game Mode o el Spawner necesita resetear o saltar a una posición concreta del raíl en tiempo de ejecución.
  • Cualquier proyecto donde más de un sistema necesite leer el progreso del raíl sin conflictos de propiedad ni referencias directas entre componentes.

Sistemas relacionados

Usa Serenity cuando necesites movimiento sobre raíles que sea legible por cualquier sistema, escribible por uno solo y reutilizable entre escenas, sin que un único MonoBehaviour intente poseer el transform, el estado y los eventos a la vez.

Volver a la página principal