The technical principle of Unity's behavioral decision tree

Unity finite state machine (FSM) is a commonly used game AI technology for describing the state and behavior of game characters. FSM consists of a series of states and transitions, each state represents the state of a game character, such as "standby", "moving", "attack" and so on; each transition represents a condition from one state to another, such as "if If the enemy is within the field of view, it will switch from the standby state to the attack state." In this article, we will explain in detail the technical principles of the Unity finite state machine, and give the corresponding code implementation.

right! There is a game development exchange group here, which gathers a group of zero-based beginners who love to learn games, and there are also some technical experts who are engaged in game development. You are welcome to exchange and learn.

1. The basic concept of finite state machine

A finite state machine is a mathematical model used to describe the behavior of discrete event systems. In game development, we can use finite state machines to describe the state and behavior of game characters. A finite state machine consists of a series of states and transitions, each state represents the state of a game character, and each transition represents a condition from one state to another.

In a finite state machine, the behavior of a game character is determined by the current state and transition conditions. When the game character is in a certain state, it will act according to the behavior of the current state; when a transition condition is met, the game character will transition from the current state to another state and execute the behavior of the state. The execution process of the finite state machine starts from the initial state, and gradually transitions to different states according to the transition conditions until it reaches the terminal state.

The state of a finite state machine can be divided into three types: initial state, final state, and intermediate state. The initial state represents the initial state of the finite state machine; the final state represents the end state of the finite state machine; the intermediate state represents the intermediate state of the finite state machine. The transitions of finite state machines can be divided into two types: conditional transitions and unconditional transitions. Conditional transition means that the transition from one state to another state needs to meet certain conditions; unconditional transition means that the transition from one state to another state does not need to meet any conditions.

Second, the realization of the finite state machine

Unity provides a finite state machine plug-in - State Machine, which can help developers create the state and behavior of game characters. State Machine provides a series of state nodes and transition nodes. Developers can customize node types according to game needs and add them to the finite state machine.

The node scripts of State Machine are written in C#, and each node script inherits from the StateMchineBehaviour class. Every node script implements a series of callback methods to perform different behaviors in different states. In the callback method of the node, developers can access the properties and methods of the game object to realize the state and behavior of the game character.

State Machine's finite state machines are saved and loaded using Unity's serialization mechanism. Developers can save the finite state machine as an .asset file and load it in the game. After loading the finite state machine in the game, you can execute the finite state machine and get the result through the API provided by State Machine.

3. An example of a finite state machine

Let's look at a simple finite state machine example, which describes the patrol behavior of a game character.

First we need to create a finite state machine and add three state nodes: standby state, moving state and attack state. In the standby state, we need to use coroutines to implement the waiting function; in the moving state, we need to access the position property of the game object and calculate the distance from the target position. In the attack state, we need to access the game object's attack method, and attack the enemy. In the callback method of the state node, developers can access the properties and methods of the game object to realize the state and behavior of the game character.

Then we need to add transition nodes and connect them to state nodes. In the transition node, we need to set the transition condition and connect the transition node to the target state node. In this example, we need to add three transition nodes, which respectively represent from standby state to mobile state, from mobile state to attack state, and from attack state to standby state. code show as below:

using UnityEngine;
using System.Collections;

public class Patrol : StateMachineBehaviour
{
    public float moveSpeed = 5f;
    public float stoppingDistance = 1f;
    public float waitTime = 1f;

    private Transform targetTransform;
    private Transform agentTransform;
    private Coroutine waitCoroutine;

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        targetTransform = animator.GetComponent<PatrolAI>().target.transform;
        agentTransform = animator.transform;

        waitCoroutine = animator.StartCoroutine(WaitForSeconds());
    }

    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float distance = Vector3.Distance(targetTransform.position, agentTransform.position);
        if (distance < stoppingDistance)
        {
            animator.SetTrigger("Move");
        }
        else
        {
            agentTransform.position = Vector3.MoveTowards(agentTransform.position, targetTransform.position, moveSpeed * Time.deltaTime);
        }
    }

    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.StopCoroutine(waitCoroutine);
    }

    IEnumerator WaitForSeconds()
    {
        while (true)
        {
            yield return new WaitForSeconds(waitTime);
            animator.SetTrigger("Move");
        }
    }
}

public class Move : StateMachineBehaviour
{
    public float moveSpeed = 5f;
    public float stoppingDistance = 1f;

    private Transform targetTransform;
    private Transform agentTransform;

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        targetTransform = animator.GetComponent<PatrolAI>().target.transform;
        agentTransform = animator.transform;
    }

    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float distance = Vector3.Distance(targetTransform.position, agentTransform.position);
        if (distance > stoppingDistance)
        {
            agentTransform.position = Vector3.MoveTowards(agentTransform.position, targetTransform.position, moveSpeed * Time.deltaTime);
        }
        else
        {
            animator.SetTrigger("Attack");
        }
    }
}

public class Attack : StateMachineBehaviour
{
    public float attackRange = 2f;
    public float attackInterval = 1f;

    private Transform targetTransform;
    private Transform agentTransform;
    private float lastAttackTime = -1f;

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        targetTransform = animator.GetComponent<PatrolAI>().target.transform;
        agentTransform = animator.transform;
    }

    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float distance = Vector3.Distance(targetTransform.position, agentTransform.position);
        if (distance > attackRange)
        {
            animator.SetTrigger("Move");
        }
        else
        {
            if (Time.time - lastAttackTime > attackInterval)
            {
                agentTransform.GetComponent<PatrolAI>().Attack();
                lastAttackTime = Time.time;
            }
        }
    }
}

In the above code, the Patrol node represents the "standby" state, the Move node represents the "moving" state, and the Attack node represents the "attack" state. We can add these three nodes to the finite state machine and set the corresponding parameters.

Finally, we need to add the finite state machine to the game object and execute it in the game. code show as below:

using UnityEngine;
using System.Collections;

public class Patrol : StateMachineBehaviour
{
    public float moveSpeed = 5f;
    public float stoppingDistance = 1f;
    public float waitTime = 1f;

    private Transform targetTransform;
    private Transform agentTransform;
    private Coroutine waitCoroutine;

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        targetTransform = animator.GetComponent<PatrolAI>().target.transform;
        agentTransform = animator.transform;

        waitCoroutine = animator.StartCoroutine(WaitForSeconds());
    }

    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float distance = Vector3.Distance(targetTransform.position, agentTransform.position);
        if (distance < stoppingDistance)
        {
            animator.SetTrigger("Move");
        }
        else
        {
            agentTransform.position = Vector3.MoveTowards(agentTransform.position, targetTransform.position, moveSpeed * Time.deltaTime);
        }
    }

    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.StopCoroutine(waitCoroutine);
    }

    IEnumerator WaitForSeconds()
    {
        while (true)
        {
            yield return new WaitForSeconds(waitTime);
            animator.SetTrigger("Move");
        }
    }
}

public class Move : StateMachineBehaviour
{
    public float moveSpeed = 5f;
    public float stoppingDistance = 1f;

    private Transform targetTransform;
    private Transform agentTransform;

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        targetTransform = animator.GetComponent<PatrolAI>().target.transform;
        agentTransform = animator.transform;
    }

    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float distance = Vector3.Distance(targetTransform.position, agentTransform.position);
        if (distance > stoppingDistance)
        {
            agentTransform.position = Vector3.MoveTowards(agentTransform.position, targetTransform.position, moveSpeed * Time.deltaTime);
        }
        else
        {
            animator.SetTrigger("Attack");
        }
    }
}

public class Attack : StateMachineBehaviour
{
    public float attackRange = 2f;
    public float attackInterval = 1f;

    private Transform targetTransform;
    private Transform agentTransform;
    private float lastAttackTime = -1f;

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        targetTransform = animator.GetComponent<PatrolAI>().target.transform;
        agentTransform = animator.transform;
    }

    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float distance = Vector3.Distance(targetTransform.position, agentTransform.position);
        if (distance > attackRange)
        {
            animator.SetTrigger("Move");
        }
        else
        {
            if (Time.time - lastAttackTime > attackInterval)
            {
                agentTransform.GetComponent<PatrolAI>().Attack();
                lastAttackTime = Time.time;
            }
        }
    }
}

In the above code, the PatrolAI script is used to add the finite state machine to the game object and execute it in the game. We can add the script to the game object and set the corresponding parameters.

Guess you like

Origin blog.csdn.net/voidinit/article/details/130272814