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 dePlayerStatsy 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 medianteBattleWorld.
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
BattleTurncomo enBattleLoop. 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:
- Crea el script de estado: Crea un nuevo archivo GDScript bajo
assets/battle/states/. ExtiendeBattleStatee implementa los métodosenter(),exit()yupdate()heredados deBaseState.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 - 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. - 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. - 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.