Dividir y conquistar: uso de FSM en Unity

Una arquitectura competente juega un papel clave en el desarrollo de cualquier producto de software. Los problemas de rendimiento, extensibilidad o comprensibilidad más comunes tienen su origen en su ausencia. La falta de una estructura de proyecto estrictamente definida priva a los desarrolladores de la capacidad de pensar en abstracciones, comprender el código escrito por un colega de un vistazo y predecir dónde se produce el error. Y en algunos casos, una persona puede confundirse incluso en su propio código, sobresaturado con entidades y componentes. Pero casi todos los programadores, tarde o temprano, ya sea por su cuenta o con la ayuda de un libro inteligente, se familiarizan con soluciones que son buenas independientemente del contexto. Son tan efectivos y versátiles que encuentran un lugar en la solución de muchos problemas, y ... Sí, lo sé, no se puede continuar, todos ya entendieron que estaba hablando.patrones de diseño. Algunos rezan por ellos, otros encuentran sus bicicletas entre ellos. Algunos afirman en la entrevista que los estudiaron por dentro y por fuera y quedaron atrapados en una completa inutilidad. Pero todos, de una forma u otra, se enteraron de ellos. Hoy hablaremos sobre uno de los patrones: "Estado" . Más precisamente, sobre máquinas de estados finitos. Incluso si pertenece al último de los grupos enumerados anteriormente, probablemente se haya encontrado con la siguiente herramienta:





Animador de personaje principal mínimo en plataformas
Animador de personaje principal mínimo en plataformas

Los animadores de Unity se basan en máquinas de estado. Cada animación de un grupo de objetos se representa como un estado. Las condiciones y el orden de las transiciones entre ellas se determinan en el animador, que es una máquina de estados. Además, se ha planteado repetidamente el tema del uso de máquinas de estados finitos para describir la lógica del trabajo de objetos con comportamiento complejo. Bots de IA, control del personaje principal, eso es todo.





. . , - , . , , , . , . , - :





  • . , , . , Play . - , . , isPaused, , .





  • , . , , , .





  • , , . , , , , , AI.





  • , . Play, WaitMatch "match_ready", , , "room_left" .





  • . , , , . , , "" .





. , . . . . , .





- . , . , .





, . :





FSM





AState





- public FSM(AState initState)





- public void Signal(string name, object data = null)





- private void ChangeState(AState newState)





- void Enter()





- void Exit()





- AState Signal()





, 2 :





. . , , . , . Exit



Enter



. , :





 public class FSM
 {
   private AState currentState;

   public FSM(AState initState) => ChangeState(initState);
   
   private void ChangeState(AState newState)
   {
     if (newState == null) return;
     currentState?.Exit();
     currentState = newState;
     currentState.Enter();
   }

   public void Signal(string name, object arg = null)
   {
     var result = currentState.Signal(name, arg);
     ChangeState(result);
   }
 }
      
      



. , , .





public class AState
{
  public virtual void Enter() => null;
  public virtual void Exit() => null;
  public virtual AState Signal(string name, object arg) => null;
}
      
      







public class SLoad : AState
{
    public override void Enter()
    {
        Game.Data.Set("loader_visible",true);
        var load = SceneManager.LoadSceneAsync("SceneGameplay");
        load.completed+=a=>Game.Fsm.Signal("scene_loaded");
    }

    public override void Exit()
    {
        Game.Data.Set("loader_visible",false);
    }
    
    public override AState Signal(string name, object arg)
    {
        if (name == "scene_loaded")
            return new SLobby();
        return null;
    }
    
}
      
      



, , . , , . 3 , . - , - . ! , , . , , ,





public class SMessage : AState
{
    private string msgText;
    private AState next;
    public SMessage(string messageText, AState nextState)
    {
        msgText = messageText;
        btnText = buttonText;
        next = nextState;
    }
    
    public override void Enter()
    {
        Game.Data.Set("message_text", msgText);
        Game.Data.Set("window_message_visible",true);
    }

    public override void Exit()
    {
        Game.Data.Set("window_message_visible",false);
    }
    
    public override AState Signal(string name, object arg)
    {
        if (name == "message_btn_ok") 
            return next;
        return null;
    }
}
      
      



, c , .





...
case "iap_ok":
	return new SMessage("Item purchased! Going back to store.", new SStore());
...
      
      



Game.Data



, , , , "". , , UI, . , . , .





public class ButtonFSM : MonoBehaviour, IPointerClickHandler
{
    public string key;
    
    public override void OnPointerClick(PointerEventData eventData)
    {
        Game.Fsm.Signal(key);
    }
}
      
      



Es decir, cuando hacemos clic en un botón (de hecho, a cualquier CanvasRenderer), enviamos la señal correspondiente a la máquina. Al realizar la transición entre estados, podemos activar y desactivar diferentes Canvas de la forma que nos resulte más conveniente, cambiar las máscaras utilizadas Physics.Raycast



e incluso, a veces, cambiar Time.timeScale. No importa cuán horrible e incivilizado pueda parecer a primera vista, siempre y cuando lo que se hace en Enter



se cancele Exit



, se garantiza que no causará ningún inconveniente, ¡así que adelante! Lo principal es no exagerar.








All Articles