Game Development Design Mode-state mode & amp; Finite State Machine & amp; c # Delegate events (unity3d example implementation)

Source: Internet
Author: User

Game Development Design Mode-state mode & amp; Finite State Machine & amp; c # Delegate event (unity3d example implementation)

 

 

Starting with the state mode, game developers must first think of the finite state machine FSMs of AI. The state mode is indeed a method to implement the finite state machine. Later I will talk about the advanced layered state machine (hierarchical state machine) of the state machine, and pushdown machine (pushdown automation). This article will talk about the state machine mode with the finite state machine controlled by people, the example in this article is a combination of the state mode and the observer mode. The status is changed by obtaining the player's key message.

If you want to implement character control in a 2d or 3d game without using a finite state machine (This article uses the 2d game as an example), you can let the characters go and run. I believe many people will write this: 

if (Input.GetKey (KeyCode.D))
        {
… Set direction to the right ..
            if (Input.GetKey (KeyCode.LeftShift))
            {
.. Move .. Play running animation ..
            }
            else
            {
.. Move .. Play walking animation ..
            }

        }
        else if (Input.GetKey (KeyCode.A))
        {
        … Set direction to the left ..
            if (Input.GetKey (KeyCode.LeftShift))
            {
.. Move .. Play running animation ..
            }
            else
            {
.. Move .. Play walking animation ..
            }
        }




Then add the jump function, what should I do, I ca n’t perform the operation of walking when jumping to play the animation of walking, add a bool to judge, and then the code becomes like this:
bool isJump = false;
    if (Input.GetKeyDown (KeyCode.W))
{
isJump = true;
}

    if (Input.GetKey (KeyCode.D))
        {
… Set direction to the right ..
            if (Input.GetKey (KeyCode.LeftShift))
            {
if (! isJump)
.. Move .. Play running animation ..
else
.. Move .. Play Jump Animation ..
            }
            else
            {
if (! isJump)
.. Move .. Play walking animation ..
else
.. Move .. Play Jump Animation ..
            }

        }
        else if (Input.GetKey (KeyCode.A))
        {
        … Set direction to the left ..
if (! isJump)
.. Move .. Play running animation ..
else
.. Move .. Play Jump Animation ..
            }
            else
            {
if (! isJump)
.. Move .. Play walking animation ..
else
.. Move .. Play Jump Animation ..
            }
        }


Then we want the character to press D key to achieve squatting, what to do, add a bool, and then judge!
bool isCrouch = false;
    if (Input.GetKeyDown (KeyCode.S))
{
isCrouch = true;
}


bool isJump = false;
    if (Input.GetKeyDown (KeyCode.W) &&! isCrouch)
{
isJump = true;
}

    if (Input.GetKey (KeyCode.D))
        {
… Set direction to the right ..
            if (Input.GetKey (KeyCode.LeftShift))
            {
if (! isJump &&! isCrouch)
.. Move .. Play running animation ..
else if (! isCrouch)
.. Move .. Play Jump Animation ..
else
.. Move .. Play squat animation ..
            }
            else
            {
if (! isJump &&! isCrouch)
.. Move .. Play running animation ..
else if (! isCrouch)
.. Move .. Play Jump Animation ..
else
.. Move .. Play squat animation ..}

        }
        else if (Input.GetKey (KeyCode.A))
        {
        … Set direction to the left ..
if (! isJump &&! isCrouch)
.. Move .. Play running animation ..
else if (! isCrouch)
.. Move .. Play Jump Animation ..
else
.. Move .. Play squat animation ..
            }
            else
            {
if (! isJump &&! isCrouch)
.. Move .. Play running animation ..
else if (! isCrouch)
.. Move .. Play Jump Animation ..
else
.. Move .. Play squat animation ..
            }
        }



Then join the attack, jump, sneak attack, stand defense, squat defense, and continue to add if else and bool How much we can tolerate such entangled ifelse? How many mistakes can be made by carelessness? Is it complicated to debug? . . . This method is obviously wrong! There are a large number of complex branches that are prone to bugs. However, the savior is coming, it is the finite state machine
One of the simplest finite state machine
The Turing machine proposed by Alan Turing is a state machine, which refers to an abstract machine, which has an infinitely long paper tape TAPE, which is divided into small squares one by one, each square has a different color. There is a head HEAD moving around on the paper tape. The machine head has a set of internal states and some fixed procedures. At each moment, the machine head must read a grid of information from the current paper tape, and then look up the program table with its own internal state, output information to the paper tape grid according to the program, and convert its internal state To move.

Preparations The conditions that the state machine needs to meet:
1. A set of fixed states (idle, walk, jump, attack ...)
2. The state machine can only be in one state at a time. In the simplest example, we cannot squat while jumping.
3. Some player inputs or events are sent to the state machine to change the existing state
4. There is a transition between the state and the state transition. In this transition, no input from the player is accepted. For example, during the transition from standing to squatting, the player does not respond when pressing the jump or walking at this time (being ignore).

According to the above conditions, one of the things we must do before writing a state machine is to draw a state diagram, so that you can sort out your ideas, facilitate the addition of states and functions, and reduce the omission of your programming.
For example, we want to realize a person's walking, running, jumping, attacking, and defense. The state diagram can be drawn like this:


enum & switch
Then we complete the most streamlined state machine, which is a combination of enum and switch.
We need to put a group of states in the enum, named after the name of the state you need, such as idle-idle, and also need a variable to store the current state of the controlling character:
    public enum CharacterState
    {
        Idling = 0,
        Walking = 1,
        Jumping = 2,
        acting = 3,
        defending = 4,
}
public CharacterState heroState = CharacterState. Idling;


Set up a function handleInput to deal specifically with determining player input and status operations, and put this function in the update polling every frame.
void handleInput ()
    {
switch (heroState)
{
case CharacterState. Idling:
… Play idle animations ..
if… Input.GetKey –A, D.
this. heroState = CharacterState. Walking;
else if… Input.GetKey –w.
this. heroState = CharacterState. Jumping;
else if… Input.GetKey –J.
this. heroState = CharacterState. acting;
else if… Input.GetKey –I.
this. heroState = CharacterState. defending;
break;
case CharacterState. Walking:
if… Input.GetKey –A, D.
… CharacterController mobile operation ..
else… Input.GetKeyUp – A, D…
this. heroState = CharacterState. Idling;
break;
case CharacterState. Jumping:
if (Input.GetKeyUp (KeyCode.W))
… CharacterController mobile operation ..
if (CharacterController.isGrounded)
{
this. heroState = CharacterState. Idling;
}
break;
case CharacterState. acting:
... playing an attack animation.
chargeTime + = Time.timeScale / Time.deltaTime;
if (chargeTime> maxTime)
{
this. heroState = CharacterState. Idling;
chargeTime = 0;
}
break;
case CharacterState. defending:
… Play defense animation.
if (Input.GetKeyUp (KeyCode.I))
this. heroState = CharacterState. Idling;
break;
}
    }


Here, we need to change the state through time, such as the attack state. We press the attack button to execute an attack animation. When the animation ends, we want the character to return to the idle state. At this time, we need a chargeTime variable to record the attack state. How long does it last, add the running time of this frame in each frame of ChargeTime, which is how long the animation is playing now, we get the total length of the animation as maxTime, when the chargeTime reaches maxTime, it means that an attack action is done When it is over (an animation is over), it will switch to the idle state, and then reset chargeTime to 0 when it transitions to the space state.
In this way, a simple state machine can be realized, and all state operations are integrated together. The principle is to poll the current state, process the current state operations, and accept player input to change the state.
At this time, there is a problem in such a state opportunity. The character we control cannot attack when jumping, and the state machine stipulates that it can only be in one state at a time, so the solution is to split it into several state machines, such as the mobile It is a state machine, and attack defense is another state machine. These two state machines run concurrently, and you can implement operations such as jumping attacks or submarine attacks.
Of course, such a state machine has many disadvantages, such as inconvenience to add a new state, this function will eventually write longer and chaotic. A good way to achieve this is to encapsulate each state and its operation and the judgment of user input in this state, that is, all the processing of this state is encapsulated in a class, which is the state mode.
Before implementing the state pattern, let us first understand the delegation and events of c #.

c # delegation and events Speaking of delegation and events, it must be linked to the observer pattern.
The delegate delegate can use this delegate to call one or more functions in other classes
The event event is usually used in conjunction with the delegate, that is, the event sender, through the delegate to call the function in the event receiver to send the event to the event receiver
In the finite state machine, our sender is the button in the update, and the receiver is the state object (will be described later) waiting to process the button. Determine the player input in update, and then send the input key as an event to the state object for processing. It is such a process.
Here is an example of the state of the attack
First of all, we first write a subclass of EventArgs event data class, which is the message to be transmitted by the event, that is, the message our receiver-state object wants to receive. This message can be defined by ourselves. Here, we want to transmit the key information Deal with the recipient
using UnityEngine;
using System.Collections;
using System;

public class InputEventArgs: EventArgs
{
    public string input;
    public string addition1;
    public string addition2;
    public InputEventArgs (string _input, string _addition1, string _addition2)
    {
        this.input = _input;
        this.addition1 = _addition1;
        this.addition2 = _addition2;
    }
}


The parameters in the class are used to store information. The input here is the key, addition1, addition2, and leave it blank, so that future development requires new information. As the attack state addition1, addition2 can be the type of weapon (different attack methods, different animations, different skills, etc.) can also be judged on the key combination (in some games, the key combination can allow the character to issue certain special skills )

Then take a look at the event sender, create a new hero class to specifically control the character state operation, heroStateActVer stores the current state, in the update function
  public delegate void InputEventHandler (object sender, InputEventArgs e);
public event InputEventHandler InputTran;

    InputEventArgs inputArgs = new InputEventArgs (,,);
State heroStateActVer;
    void Start ()
    {
        personCtrl = this.GetComponent ();
        heroStateActVer = new IdleStateActVer (this.gameObject);
        InputTran + = new InputEventHandler (heroStateActVer.handleInput);
    }
    void Input_events (InputEventArgs e)
    {
        if (e! = null)
        {
            InputTran (this, e);
        }
    }
    void Update ()
{
        if (Input.anyKeyDown)
        {
            foreach (char c in Input.inputString)
            {
                if (c! = null)
                {
                    inputArgs.input = c.ToString ();
                    Input_events (inputArgs);
                }
            }
        }
heroStateActVer.UpDate ();
}


State's UpDate () stores the real-time operation of the state, heroStateActVer.UpDate (); To deal with the current state of the operation, such as walking state is CharacterController.move, we will explain later
We define the delegate and event in the sending class, we receive the user button, store it in the event information class InputEventArgs in the form of a string and send it to the recipient state object
The reason why we use foreach is because Input.inputString can receive multiple keys, such as A key and D key at the same time, Input.inputString is "ad", as shown, so we traverse all the keys pressed in a frame, come Send a message.


Then is our receiving class, which is also the subscriber's state. There is a method in the state object to receive the event information sent by the sender, that is, the function executed when the event occurs
  public void handleInput (object sender, InputEventArgs e)
    {
        input = e.input;
        switch (input)
        {
            case j: // attack
… Becoming attacked ..
                break;
            case i: // defense
… Turn to defensive status ...
                break;
        }
    }



State Mode Foursome said:
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Change the behavior of an object when its state changes. The object will change its class.
Each of the sub-states inherited from the parent class state has its operations and judgments on user input and external events in this state, that is, all processing of this state is encapsulated in a class for the user The input judgment and external events are processed, and the state of the object is changed if necessary.
In this class, we need to accept the method handleInput () of the event to handle the current state operation in real time (such as the move state is to deal with the movement of characters) Update (). Note that we do not inherit from MonoBehaviour, so Update () here is not Inherited from MonoBehaviour, it is defined by ourselves, and then it will be executed in the Update () of MonoBehaviour in the character sending class. Operation Start () when entering the state, Exit () when exiting the state (can be added as needed or not)
First of all, we need to define a parent class State, so that various states are inherited from him. We save the objects of the character sending class and the operation class to facilitate the control of the character in the update () in the state. The chargeTime mentioned above is the duration of a state.
using UnityEngine;
using System.Collections;
public class State
{
    protected static string input;
    protected GameObject Person;
    protected Hero Person_ctrl;
    protected float chargeTime;
    protected float MaxTime;
    public State (GameObject _Person)
    {
        this.Person = _Person;
        this.Person_ctrl = _Person.GetComponent ();
    }
    public virtual void handleInput (object sender, InputEventArgs e)
    {
    }
    public virtual void UpDate ()
    {
    }
    public virtual void UpDate ()
    {
    }
    public virtual void Start ()
    {
}
}


The Start () function is a function called when the previous state ends and starts the next state. It is called once. UpDate () is to be run in Real Time in UpDate () in the character sending class. Call processing is responsible for transitioning the state. We put the judgment of all the corresponding keys in this state in it.
Then take a look at the sub-class attack status
using UnityEngine;
using System.Collections;

public class ActState: State
{
    public ActState (GameObject _Person)
        : base (_Person)
    {
    }
    public override void Start ()
    {
        this.chargeTime = 0.0f;
        this.MaxTime =: Attack animation time ...
.. Play Attack Animation ..
    }
    public override void handleInput (object sender, InputEventArgs e)
    {

        input = e.input;
        switch (input)
        {
            case j: // combo
                if (chargeTime> MaxTime-0.1f)
                {
                    Person_ctrl.GetActState (1) .Start ();
                }
                break;
            case i: // transition to defensive state
                if (chargeTime> MaxTime-0.1f)
                {
                    Person_ctrl.SetActState (2);
                    Person_ctrl.GetNowActState (). Start ();
                }
                break;
        }
    }
    public override void UpDate ()
    {
        if (chargeTime <MaxTime)
            chargeTime + = Time.timeScale / Time.deltaTime;
        else
        {
            this.chargeTime = 0.0f;
            Person_ctrl.SetActState (0);
            Person_ctrl.GetNowActState (). Start ();
        }
    }
}


As you can see, in the start function, which is the state start, refresh chargeTime, play the attack animation, in the update function, update the time chargeTime, determine whether it exceeds the specified time MaxTime (here is the attack animation time), if it exceeds, switch the current The state is idle, handleInput receives the event input, object sender is the event sender, which is the Hero class in the example, InputEventArgs e is the incoming key information, in this function, it is determined whether the state needs to be converted, or the corresponding operation is made. Therefore, the state transition is implemented in our encapsulated state object.
We have two methods to get the state object. One is to define a state class and declare the state as static to get it. It does not need to consume memory to instantiate. If you want to attack the state, AllState. ActState can be:
using UnityEngine;
using System.Collections;

public class AllState
{
public static State actState = new ActState ();
public static State jumpState = new JumpState ();
......
}


However, this method cannot be shared by two characters, and the state time chargeTime cannot be shared. This problem is very important. Therefore, there is another way to instantiate the state object in the Hero class, which can realize the multi-task shared state. There are instances of state in the character class.


private State [] hero_act_state = new State [3];
        hero_act_state [0] = new IdleStateActVer (this.gameObject);
        hero_act_state [1] = new ActState (this.gameObject);
        hero_act_state [2] = new DefenseState (this.gameObject);


Then write a get and set method to make the state variable more secure. I won't do code demonstration here.

Summary In the finite state machine, the observer mode and the state mode are generally used together. The state mode encapsulates each state into a class to process each input message (key), completely free from the entanglement of a lot of if else, which reduces A large number of logic errors may occur, but there are also many shortcomings because the state is limited after all. When the state is small, it can be used freely. When the state is more than 10, the structure is very complicated and it is easy to make mistakes. The 6 decision-making methods of game artificial intelligence development mentioned that if it is used in AI, it will cause some problems, so I will post an article on the advanced state machine-layered finite state machine.
I originally wrote the state mode, so I have to mention the finite state machine. Of course, the finite state machine must include the observer mode, so the idea is a bit confusing when writing this article. The delegation and events are only mentioned a little. . If there are errors, or suggestions, please comment or private message.

Part of the code has been shared to github

 

Command mode: Command mode for game development design mode (unity3d sample implementation)

Object pool mode: object pool mode of game development design mode (unity3d sample implementation)

Prototype mode: Prototype mode of game development design mode & use of unity3d JSON (unity3d sample implementation)
Bloggers' recent renderings: some renderings recently made with unity5

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.