Cada combate en Beast Card Clash está controlado por una máquina de estados finitos (FSM) de cinco fases. En lugar de tener un script de batalla monolítico, cada fase del juego (configuración, turnos de bots, turno del humano, puntuación y pantalla de resultados) está encapsulada en su propia clase BattleState. El nodo BattleManager posee el estado actual y activa las transiciones.

Este diseño permite probar cada fase de forma independiente y extenderla fácilmente sin afectar a lógica no relacionada.

Diagrama de estados

stateDiagram-v2
    [*] --> BattleStart
    BattleStart --> BattleLoop
    BattleStart --> BattleTurn
    BattleTurn --> BattleLoop
    BattleLoop --> BattleTurn
    BattleTurn --> BattleReferee
    BattleLoop --> BattleReferee
    BattleReferee --> BattleTurn
    BattleReferee --> BattleLoop
    BattleReferee --> BattleEnd
    BattleEnd --> [*]

Jerarquía de clases

Todos los estados de la batalla comparten una base común. La cadena de herencia es:

Node
└── BaseState
    └── BattleState
        ├── BattleStart
        ├── BattleLoop
        ├── BattleTurn
        ├── BattleReferee
        └── BattleEnd

BaseState es una base de estado genérica que proporciona una propiedad controlled_node. BattleState la especializa con una propiedad tipada manager que le da a cada estado acceso directo al BattleManager:

class_name BattleState extends BaseState

var manager: BattleManager:
    set(value):
        controlled_node = value
    get:
        return controlled_node

Esto significa que cualquier estado puede acceder a los datos compartidos de la batalla y activar transiciones a través de manager sin acoplar los estados entre sí.

Los cinco estados

BattleStart — configuración

Archivo: assets/battle/states/start.gd

BattleStart se ejecuta una sola vez al inicio de cada combate. Su trabajo consiste en construir el mundo de juego inicial antes de que actúe cualquier jugador o bot.

Responsabilidades:

  • Llama a MusicManager.play_music("battle") para iniciar la música de batalla.
  • Llama a manager.setup_player(): crea al jugador humano a partir de PlayerStats y genera su baraja.
  • Llama a manager.setup_bots(): crea entre 1 y 3 bots con barajas aleatorias y mezcla el orden de los turnos.
  • Llama a manager.setup_ui(): actualiza el panel de estadísticas de los jugadores, inicializa la visualización de la mano y oculta la UI de fin de partida.
  • Llama a manager.setup_world(): desactiva el dado mediante BattleWorld.

Transiciones:

Condición Siguiente estado
El jugador humano va primero BattleTurn
Un bot va primero BattleLoop

BattleLoop — turnos de los bots

Archivo: assets/battle/states/loop.gd

BattleLoop gestiona todos los turnos automatizados (bots). Se ejecuta sin interacción del jugador y entra en bucle hasta que es el turno del humano o termina la ronda.

Responsabilidades:

  • Determina a quién le toca el turno en el orden establecido.
  • Lanza el dado para el bot activo.
  • Selecciona una roca a la cual se moverá el bot.
  • Elige una carta de la mano del bot y la juega.
  • Avanza el contador de turnos.

Transiciones:

Condición Siguiente estado
El siguiente en el orden es un bot Permanece en BattleLoop (bucle)
El siguiente en el orden es el humano BattleTurn
No quedan turnos en la ronda BattleReferee

BattleTurn — turno del humano

Archivo: assets/battle/states/turn.gd

BattleTurn es la fase interactiva en la que el jugador humano realiza su acción. La escena espera la entrada del usuario en cada paso.

Responsabilidades:

  • Activa el dado 3D: el jugador hace clic para lanzarlo.
  • Resalta las posiciones de roca válidas según el resultado del dado.
  • Mueve el personaje del jugador a la roca seleccionada.
  • Muestra la mano del jugador y activa la selección de cartas.
  • Juega la carta seleccionada.
  • Pasa el turno una vez jugada la carta.

Transiciones:

Condición Siguiente estado
El siguiente en el orden es un bot BattleLoop
El siguiente en el orden es el humano Permanece en BattleTurn
No quedan turnos en la ronda BattleReferee

La lógica para pasar el turno utiliza los mismos criterios tanto en BattleTurn como en BattleLoop. Cualquier cambio en las reglas de orden de turnos debe aplicarse de forma consistente en ambos estados.

BattleReferee — resolución de la ronda

Archivo: assets/battle/states/referee.gd

BattleReferee se ejecuta después de que todos los jugadores hayan tomado su turno en la ronda. Evalúa las cartas jugadas, aplica daño y decide si el combate continúa.

Responsabilidades:

  • Recopila todas las cartas jugadas durante la ronda.
  • Compara las cartas según las reglas elementales y los valores numéricos.
  • Aplica daño a los jugadores correspondientes.
  • Elimina a los jugadores derrotados (aquellos cuya vida llegue a cero).
  • Determina si la partida debe continuar.

Transiciones:

Condición Siguiente estado
Quedan 2 o más jugadores, nueva ronda BattleLoop o BattleStart (reconfiguración)
Solo queda 1 jugador BattleEnd

Consulta Mecánicas de batalla para ver las reglas de comparación de cartas que implementa este estado.

BattleEnd — resultados

Archivo: assets/battle/states/end.gd

BattleEnd es el estado final de la máquina. Se ejecuta una vez que BattleReferee determina un ganador.

Responsabilidades:

  • Calculates el ranking final del jugador según su orden de eliminación.
  • Muestra la pantalla final con los resultados (ganador, posiciones).
  • Espera a que el jugador pulse el botón de salir.

Transiciones:

Condición Siguiente estado
El jugador pulsa salir/volver Regresa al menú de inicio a través de SceneManager

Cómo controla las transiciones BattleManager

El nodo BattleManager mantiene una referencia al estado activo actual. Los estados no realizan la transición por sí mismos, sino que llaman a un método en manager para solicitar la transición, pasando el nombre o referencia del siguiente estado. Esto permite auditar fácilmente el grafo de estados en un único lugar.

# Ejemplo: solicitar una transición desde el interior de un estado
manager.transition_to(manager.state_loop)

Consulta Gestor de batalla para ver todos los detalles de la implementación.

Cómo añadir un nuevo estado

Sigue estos pasos para introducir una nueva fase en la batalla:

  1. Crea el script de estado: Crea un nuevo archivo GDScript bajo assets/battle/states/. Extiende BattleState e implementa los métodos enter(), exit() y update() heredados de BaseState.
    class_name BattleMiFase extends BattleState
    func enter() -> void:
        # Se ejecuta cuando este estado se activa
        pass
    func exit() -> void:
        # Se ejecuta justo antes de salir de este estado
        pass
    
  2. Registra el estado en BattleManager: Abre assets/battle/battle_manager.gd (o el archivo de la escena) and añade tu nuevo estado como un nodo hijo o una variable exportada, siguiendo el mismo patrón que los cinco estados existentes.
  3. Define las transiciones: En los estados que deban dirigir hacia tu nuevo estado, añade una llamada a manager.transition_to(manager.state_mi_fase) en el momento adecuado. Actualiza también cualquier estado hacia el cual deba realizar la transición tu nuevo estado.
  4. Actualiza el diagrama: Mantén el diagrama de estados en .docs/ (y en esta página) sincronizado con las nuevas transiciones para que el siguiente desarrollador disponga de un mapa preciso.

Páginas relacionadas


This site uses Just the Docs, a documentation theme for Jekyll.