Unity advanced development-FSM finite state machine
# Unity advanced development-FSM finite state machine
Foreword
When we develop, to a certain extent, we will encounter dozens of states. If we continue to use Unity’s Animator controller, a large number of bool and float type variables will appear, and these intricate variables are just like the Animatator controller. The combination of maze version connections will become extremely complex and cannot be well maintained and expanded. The emergence of a BUG will cause developers to bear great mental strength during the development process. At this time, using finite state machines or AI behavior trees becomes An excellent choice, This article only records the development of finite state machines
Using finite state machines for state management and switching can greatly reduce the difficulty of development. During the development process, you only need to pay attention to the switching between each state
Illustration of FSM working process:
We can see that the FSM script is divided into two large parts, one of which is inherited from IState, which stores the behaviors executed in the state, such as entering the state, leaving the state, and logical switching (used to determine whether to switch to other states), PlayerState implements this interface and inherits the ScriptableObject class. When we see ScriptableObject, we know the role of PlayerState. The objects of the ScriptableObject class do not depend on the objects in the scene, but are independent data files. Here, PlayState saves the behavioral logic functions of the state. The essential function of these behavioral logic functions is to execute relevant behaviors when conditions are met. Of course, use an example in FSM to illustrate that when the player is in the running state, the PlayState file finds that the logic for executing the running behavior is met, and the running animation needs to be played. We know that the data of ScriptableObject will not change by itself, so all judgment logic can only be used once. How should we solve this problem? This requires the class of the second section of FSM to implement it
It is not difficult to find that StateMachine inherits MonoBehaviour, which needs to be mounted into the scene, and the Update function can also be used to change the detected information at any time. Then, as mentioned above, PlayerState data cannot Actively change, and the data of StateMachine can change, so we can use a function to detect the data in StateMachine and send it to PlayerState, so that the data files created by PlayerState can continuously make logical judgments
Okay, now start coding and continue talking using code
No.1 IState
public interface IState
{
//Enter state
void Enter() { }
//exit status
void Exit() { }
//Status logic update
void LogicUpdate() { }
}
Here is the behavior function that needs to be executed inside the state
No.2 StateMachine
//
/// Hold all status classes, manage and switch them
/// Responsible for updating the current status
///
public class StateMachine : MonoBehaviour
{
IState currentState; //current state
public Dictionary stateTable; //Dictionary, used to save state and query state, convenient for state switching //Search
public void Update()
{
currentState.LogicUpdate(); //Execute the logical switching function of each state, which can enable the state to detect //Transform, similar to the function in Update that receives information in real time
}
//switch state
protected void SwitchOn(IState newState)
{
//The current state changes to the new state
currentState = newState;
//Enter new state
currentState.Enter();
}
//
public void SwitchState(IState newState)
{
//exit status
currentState.Exit();
//Enter new state
SwitchOn(newState);
}
//Switch state function overload
public void SwitchState(System.Type newStateType)
{
SwitchState(stateTable[newStateType]); //Pass in the state in the dictionary
}
}
No.3 PlayetState
public class PlayerState : ScriptableObject, IState
{
/******************Physical Detection******************/
protected bool isGround; //Whether it is on the ground
/*************basic information*************/
protected bool isRun; //Whether to run
protected bool isJump; //Whether to jump
protected bool isIdle; //Whether it is still
/******************Related components****************/
protected Rigidbody2D my_Body2D; //Rigid body component, used to obtain the rigid body properties of the objectEnter the state and play the Idle animation by default.
animator.Play("PlayerJump");
}
//Logic switching function, when a certain state is detected, the data file in that state will be executed immediately
//In this script, other actions cannot be performed after jumping. You can add judgment if necessary.
public override void LogicUpdate()
{
if(my_Body2D.velocity.y<0&&!isGround)
{
stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Fall)]);
}
}
}
No.7 PlayerState_Fall
[CreateAssetMenu(menuName = "StateMachine/PlayerState/Fall", fileName = "PlayerState_Fall")]//Create file
public class PlayerState_Jump : PlayerState
{
/************************Physical Detection************************/
public override void Enter()
{
//Execute the state data file, first execute the entry state function, and then perform related behaviors in the entry state function
//Enter the state and play the Fall animation by default
animator.Play("PlayerFall");
}
//Logic switching function, when a certain state is detected, the data file in that state will be executed immediately
//In this script, other actions cannot be performed when falling. You can add judgment if necessary.
public override void LogicUpdate()
{
if(isGround)
{//Enter Idle state after landing
stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Idle)]);
}
}
}
No.8 PlayerState_Run
[CreateAssetMenu(menuName = "StateMachine/PlayerState/Run", fileName = "PlayerState_Run")]//Create file
public class PlayerState_Idle : PlayerState
{
/************************Physical Detection************************/
public override void Enter()
{
//Execute the state data file, first execute the entry state function, and then perform related behaviors in the entry state function
//Enter the state and play the Run animation by default
animator.Play("PlayerRun");
}
//Logic switching function, when a certain state is detected, the data file in that state will be executed immediately
public override void LogicUpdate()
{
if(isIdle)
{
stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Idle)]);
}
if(isJump)
{
stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Jump)]);
}
if(my_Body2D.velocity.y<0&&!isGround)
{
stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Fall)]);
}
}
}
The above is a simple state machine
Summary and Extension
FSM can greatly reduce the judgment and switching between player animations. You only need to pay attention to the switching conditions from one state to another. If the conditions are met, switch, and if not, continue execution
The use of FSM on players is not obvious, because here, after entering a certain state, we only play animations and do not perform other actions. It will be more convenient when we use it in monster AI. For example, if the monster is in patrol state, play the patrol animation, and execute the patrol script file (create a class specifically to store the monster AI status behavior function, and pass the class to The object is passed to EnemyState, which is operated by EnemyState). The monster is in the state of chasing the player, plays the chasing animation, and executes the behavior function of chasing the player. In the monster AI, the application of FSM will make it easier to manage monsters, and monsters can use skills. Enter the skill state, and when a player is found, he will start chasing the player. One condition and one action are more conducive to FSM management
Supplement
The following is an EnemyAI structural diagram I wrote myself. This example is used to show the general application of FSM in monster AI (not the project version, just a rough representation)