Unity state machine-based process control

 When we make games, we often have process control. There are many methods of process control, such as behavioral decision trees, state machines, etc. The essential difference is not big, that is, each piece of execution logic is made into nodes one by one, and a certain node is executed according to conditions and switched to a certain node. Today I would like to share with you how to control the game process based on state machines.

1 A simple state machine case

  Let's first dismantle a use case so that everyone can have a basic understanding of the process control of the state machine. First we build some state nodes and put them into the state machine. Write pseudo code as follows:

Create a state machine: FiniteStateMachine _fsm = new  FiniteStateMachine()

Add all logical nodes that control the process state to the state machine:

_fsm.AddNode(new NodeInit());

_fsm.AddNode(new NodeLogin());

_fsm.AddNode(new NodeTown());

Initialization logical node NodeInit is used for initialization logic control, NodeLogin is used for logic control of login scenes, and NodeTown node is used for logic control of game battle scenes.

Each state machine node has several unified fixed entrances. How these entrances are designed is related to the industry. For example, in our game industry, the design of the state machine node interface is generally as follows:

Name: The name of the state machine node;

OnEnter: Executed when the state machine enters this state node, generally used for initialization;

OnExit: Executed when the state machine opens this state node. Generally, some resources are destroyed and released when the user ends;

OnUpdate: The update of the state machine node will be called every frame, and many transactions processed in each frame can be placed on OnUpdate;

OnFixedUpdate: Each FixedUpdate will call the OnFixedUpdate function of the state machine. Some updates with a fixed number of iterations can be placed in this interface.

OnHandleMessage(object msg): This interface is called when an event message is triggered for the state machine node to serve as the control entry for the state machine node to process the event message.

Each state machine node implements the interface corresponding to IFsmNode and is placed in the state machine for unified management. In the case, we first execute the NodeInit status node when the game starts to complete the initialization of the game.

public void StartGame()

{

_fsm.Run(nameof(NodeInit));

}

Let’s first look at the logic of NodeInit node processing. NodeInit only implements initialization-related logic in OnEnter. Other interfaces do not have any logic processing. code show as below

void IFsmNode.OnEnter()

{

AudioPlayerSetting.InitAudioSetting();

// Initialize using coroutine

MotionEngine.StartCoroutine(Init());

}

private IEnumerator Init()

{

// Load UIRoot

var uiRoot = WindowManager.Instance.CreateUIRoot<CanvasRoot>("UIPanel/UIRoot");

yield return uiRoot;

// Load the resident panel

yield return GameObjectPoolManager.Instance.CreatePool("UIPanel/UILoading", true);

// Enter the login process

FsmManager.Instance.Change(nameof(NodeLogin));

}

As shown in the above code, when the state machine executes the NodeInit node state, the OnEnter interface will be called during initialization. In the OnEnter interface of NodeInit, the Init function is called for initialization. First, a UIRoot will be created, and then the resource loading interface will be displayed. Come out, and after completing the resource loading, enter the login logical node scene. Note that here, the state machine switches from the original NodeInit to the NodeLogin state machine node. When entering the NodeLogin node, its OnEnter interface will be executed. Next, let's look at the logical processing of the login node as follows:

void IFsmNode.OnEnter()

{

var uiwindow = UITools.OpenWindow<UILogin>();

uiwindow.Completed += Uiwindow_Completed;

string sceneName = "Scene/Login";

SceneManager.Instance.ChangeMainScene(sceneName, null);

}

Display a login UI interface and switch the scene to the login scene at the same time, so that our state machine control logic switches to the login scene, as shown in the figure:

Next, enter the username + password, click the "Run Game" button, and see how the RunGame button is processed:

private void OnClickLogin()

{

// Send login event

var message = new LoginEvent.ConnectServer

{

Account = _account.text,

Password = _password.text

};

EventManager.Instance.SendMessage(message);

}

Send a login event message to the node of the state machine, so that the event processing function of the state machine node can be called.

private void OnHandleEvent(IEventMessage msg)

{

if(msg is LoginEvent.ConnectServer)

{

FsmManager.Instance.Change(nameof(NodeTown));

}

}

In the event handling function, call the state machine to switch to the NodeTown state machine node to run. Finally, let’s take a look at node processing in the battle scene of the NodeTown game. The OnEnter interface is initialized as follows:

void IFsmNode.OnEnter()

{

string sceneName = "Scene/Town";

SceneManager.Instance.ChangeMainScene(sceneName, OnSceneLoad);

UITools.OpenWindow<UILoading>(sceneName);

UITools.OpenWindow<UIMain>();

AudioManager.Instance.PlayMusic("Audio/Music/town", true);

}

Switch to the game battle scene, display the main UI of the battle , and play the background music of the game. Let’s look at other interfaces. OnUpdate iterates changes in the game world, and OnExit deletes the game world and releases resources. The code is as follows:

void IFsmNode.OnUpdate()

{

if (_initWorld)

_gameWorld.Update();

}

void IFsmNode.OnFixedUpdate()

{

}

void IFsmNode.OnExit()

{

_gameWorld.Destroy();

UITools.CloseWindow<UIMain>();

}

as the picture shows:

Through the analysis of this case, we determined the design of the game state machine, which is summarized as follows:

Step1: Design some game state nodes, and implement some specific logical processing interfaces in the nodes;

Step2: Add the game state node to the game state machine ;

Step3: Write the "switch node" interface for the state machine. Before entering the node, first call the OnExit interface of the previous node, and then call the OnEnter interface of the new node. According to the needs of the game, each Update, FixedUpdate, iteration state machine The node's OnUpdate and OnFixedUpdate interfaces.

2 Specific implementation and design based on state machine control

  With the above analysis, we have a very clear understanding of the state machine. Naturally, it is very simple to design a state machine to control the jump control logic of the game. We divide the state machine-based control in the game into " It is designed and processed in two parts: "irrelevant to the project" and "relevant to the game project" . Let’s first look at the design of the state machine part that is “not related to the project”: two codes: IFsmNode.cs and FiniteStateMachine.cs. The IFsmNode.cs code is responsible for defining the interface of the state machine node. The code above has been given for game development. Common interfaces for state machine nodes. When developers implement specific business logic, they only need to inherit this interface and implement it.

FiniteStateMachine.cs, mainly implements the management of state machine nodes. The main data members and interfaces are as follows:

private  readonly  List<IFsmNode> _nodes = new  List<IFsmNode>(); Define a data member to save all state machine nodes.

private IFsmNode _curNode;

private IFsmNode _preNode;

Define two data members, curNode and prevNode, to save the currently running status node and the previous status node;

public  void  AddNode(IFsmNode node) defines an interface to add a new state node to the state machine;

public  void  Run( string  entryNode) defines an interface as the interface for executing the first status node;

public  void  Transition( string  nodeName) defines an interface as an interface for switching from the current state to a new state machine node;

Based on Update, call the Update, FixedUpdate, and HandleMessage interfaces of the currently executed state machine node.

public void Update()

{

if  (_curNode != null )

_curNode.OnUpdate();

}

public void FixedUpdate()

{

if  (_curNode != null )

_curNode.OnFixedUpdate();

}

public void HandleMessage(object msg)

{

if  (_curNode != null )

_curNode.OnHandleMessage(msg);

}

This drives the calling and execution of the relevant interfaces of the state machine node. After writing the two codes of FiniteStateMachine and IFsmNode, the state machine has been designed, and the next step is to use it in specific game projects. That is, the code related to use. It’s actually very simple, there are three main steps

Step1: Create a state machine object;

Step1: We want to add a logical node of the state machine. We only need to inherit IFsmNode, implement the relevant interface, and put the logical node into the state machine object for unified management.

Step3: Switch the nodes of the running state machine according to the business logic. So as to achieve the purpose of logic control.

3: Extend some special state control based on state machine

  After the state machine design is completed, we can also perform some special state control based on the state machine to make our logic code clearer and easier to maintain, such as the most common sequential execution state machine ProcedureFsm. That is to say, after executing one state node, the second state node will be executed immediately. In this way, it is very convenient for us to do sequential processes, such as the hot update sequential process state machine: 

   1: Check the version status node;

   2: Incremental download information comparison node;

   3: Incrementally download resource nodes;

   4: After the download is completed, enter the game node;

Add these state machine nodes to ProcedureFsm, then it will start running from the first node, and each subsequent node will be executed in sequence.

Whether to use a state machine as your logic control in the project can be analyzed based on specific needs. There is no absolute good or bad, just whatever suits you best.

Guess you like

Origin blog.csdn.net/Unity_RAIN/article/details/134176932