Unity 的行为决策树的技术原理

Unity有限状态机(FSM)是一种常用的游戏AI技术,用于描述游戏角色的状态和行为。FSM由一系列状态和转换组成,每个状态表示一个游戏角色的状态,如“待机”、“移动”、“攻击”等;每个转换表示从一个状态到另一个状态的条件,如“如果敌人在视野范围内,则从待机状态转换到攻击状态”。在本文中,我们将详细讲解Unity有限状态机的技术原理,并给出相应的代码实现。

对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大佬,欢迎你来交流学习。

一、有限状态机的基本概念

有限状态机是一种数学模型,用于描述离散事件系统的行为。在游戏开发中,我们可以使用有限状态机来描述游戏角色的状态和行为。有限状态机由一系列状态和转换组成,每个状态表示一个游戏角色的状态,每个转换表示从一个状态到另一个状态的条件。

在有限状态机中,游戏角色的行为是由当前状态和转换条件共同决定的。当游戏角色处于某个状态时,它会根据当前状态的行为进行动作;当某个转换条件成立时,游戏角色会从当前状态转换到另一个状态,并执行该状态的行为。有限状态机的执行过程是从初始状态开始,逐步根据转换条件转换到不同的状态,直到达到终止状态为止。

有限状态机的状态可以分为三种类型:起始状态、终止状态和中间状态。起始状态表示有限状态机的初始状态;终止状态表示有限状态机的结束状态;中间状态表示有限状态机的中间状态。有限状态机的转换可以分为两种类型:条件转换和无条件转换。条件转换表示从一个状态到另一个状态的转换需要满足一定的条件;无条件转换表示从一个状态到另一个状态的转换不需要满足任何条件。

二、有限状态机的实现

Unity中提供了一个有限状态机插件——State Machine,它可以帮助开发人员创建游戏角色的状态和行为。State Machine提供了一系列状态节点和转换节点,开发人员可以根据游戏需求自定义节点类型,并将它们添加到有限状态机中。

State Machine的节点脚本使用C#编写,每个节点脚本继承自StateMchineBehaviour类。每个节点脚本都实现了一系列回调方法,用于在不同的状态下执行不同的行为。在节点的回调方法中,开发人员可以访问游戏对象的属性和方法,从而实现游戏角色的状态和行为。

State Machine的有限状态机使用Unity的序列化机制进行保存和加载。开发人员可以将有限状态机保存为一个.asset文件,并在游戏中加载它。在游戏中加载有限状态机后,可以通过State Machine提供的API来执行有限状态机并获取结果。

三、有限状态机的实例

下面我们来看一个简单的有限状态机实例,该实例描述了一个游戏角色的巡逻行为。

首先我们需要创建一个有限状态机,并添加三个状态节点:待机状态、移动状态和攻击状态。在待机状态中,我们需要使用协程来实现等待功能;在移动状态中,我们需要访问游戏对象的位置属性,并计算出与目标位置的距离。在攻击状态中,我们需要访问游戏对象的攻击方法,并攻击敌人。在状态节点的回调方法中,开发人员可以访问游戏对象的属性和方法,从而实现游戏角色的状态和行为。

然后我们需要添加转换节点,并将它们连接到状态节点上。在转换节点中,我们需要设置转换条件,并将转换节点连接到目标状态节点上。在本例中,我们需要添加三个转换节点,分别表示从待机状态到移动状态、从移动状态到攻击状态、从攻击状态到待机状态。代码如下:

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;
            }
        }
    }
}

在上面的代码中,Patrol节点表示“待机”状态,Move节点表示“移动”状态,Attack节点表示“攻击”状态。我们可以将这三个节点添加到有限状态机中,并设置相应的参数。

最后,我们需要将有限状态机添加到游戏对象上,并在游戏中执行它。代码如下:

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;
            }
        }
    }
}

在上面的代码中,PatrolAI脚本用于将有限状态机添加到游戏对象上,并在游戏中执行它。我们可以将该脚本添加到游戏对象上,并设置相应的参数。

猜你喜欢

转载自blog.csdn.net/voidinit/article/details/130272814