将Behavior插件和AStarPathFinding插件结合

在上一篇博文中,简单写了一个A*插件的简单移动和动态生成网格,可以看到,在那里,有一个StarPath方法一直没有使用,那是因为我故意留给行为树这里用的哈哈,接下来把代码改一改,把没必要的代码注释掉,我们把这个脚本封装起来,以后只提供给其他脚本实现寻路。AStar.cs 脚本代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class AStar : MonoBehaviour {
  //  public Transform target;
    private Seeker seeker;
    private CharacterController characterController;
    public Path path;
    private float speed = 100;
    private float angularSpeed = 10;
    private float nextWaypointDistance = 1.5f;
    private int currentWaypoint = 0;

    void Start () {
    }

    public void StarPath(Transform target)
    {
        seeker = GetComponent<Seeker>();
        characterController = GetComponent<CharacterController>();
        seeker.pathCallback += OnPathComplete; //寻路的一个回调
        seeker.StartPath(transform.position, target.position, OnPathComplete);
    }
    private void FixedUpdate()
    {
        Move();
    }
    public void Move()
    {
        if (path == null)   //如果路径为空,直接返回
        {
            return;
        }
        if (currentWaypoint >= path.vectorPath.Count)
        {
            return;
        }
        if (characterController != null && characterController.enabled == true)
        {
            Vector3 dir = (path.vectorPath[currentWaypoint] - transform.position).normalized;
            dir *= speed * Time.deltaTime;
            characterController.SimpleMove(dir);
            Quaternion targetRotation = Quaternion.LookRotation(dir);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.fixedDeltaTime * angularSpeed);
            if (Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]) < nextWaypointDistance)
            {
                currentWaypoint++;
                return;
            }
        }
    }
    public void OnPathComplete(Path p)
    {
        if (!p.error)
        {
            path = p;   //如果不发生错误,把p传给path
            currentWaypoint = 0;
        }
    }
    private void OnDisable()
    {
        seeker.pathCallback -= OnPathComplete;
    }
    //提供get set方法供其他脚本使用
    #region GetSet      
    public float Speed
    {
        get { return speed; }
        set { speed = value; }
    }
    public float AngularSpeed
    {
        get { return angularSpeed; }
        set { angularSpeed = value; }
    }
    public float ArriveDistance
    {
        get { return nextWaypointDistance; }
        set { nextWaypointDistance = value; }
    }
    #endregion

}

一定要记得加头文件!一定要记得加头文件!一定要记得加头文件!一定要记得加头文件!一定要记得加头文件!

这部分代码仅供寻路移动使用。接下来,我们开始准备行为树的部分。首先导入BehaviorDesigner.1.5.7.unitypackage和Behavior Designer - Movement Pack v1.5.2.unitypackage。这两个包,前一个是基础包,后一个是行为树移动的包。大家可以去官网下载,如果只是学习使用,也可以去网上找一下资源包。导入后,选择我们的角色,点击Unity上面的Tools->Behavior Designer->Editor。
这里写图片描述

然后会出现一个灰色面板,我们鼠标右键Add Behavior Tree或者点击上面的那个+号添加我们的行为树。(这里只简单实现A*和行为树的结合,不再对行为树进行详细的讲解,网上也有很多大神详细的讲了,有兴趣可以自行百度。)。添加后,我们来自己写一个巡逻过程。我们先看看自带的巡逻是怎么样的。

这里写图片描述

先添加上巡逻任务,然后单击这个任务图标,可以看到巡逻的一系列信息。图片如下:
这里写图片描述

Speed是巡逻的速度,然后是旋转速度,以及距离目标点多远算抵达。后面的Stop On Task End如果选中了,则会在巡逻后停止。Waypoint Pause Duration则表示到达一个目标点之后停留多久。 Waypoints这里可以设置巡逻点个数,以及巡逻点的位置。我们参照自带的自己写一个巡逻MyPatrol.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviorDesigner.Runtime.Tasks;
using BehaviorDesigner.Runtime;
using Pathfinding;
public class MyPatrol : Action {
    public SharedFloat speed;   //行动的速度。
    public SharedFloat angularSpeed;    //旋转速度
    public SharedFloat arriveDistance;  //多远算到达目的地
    public SharedBool stopOnTaskEnd;    //需不需要在巡逻完所有的点后停止
    public SharedFloat waypointPauseDuration;   //巡逻到目标点后停留的间隙
    public SharedGameObjectList waypoints;
    private bool isOverPatrol = false;


    private AStar aStar;
    public override void OnAwake()
    {
        aStar = GetComponent<AStar>();
        if (aStar != null && aStar.enabled == true)
        {
            aStar.Speed = speed.Value;
            aStar.AngularSpeed = angularSpeed.Value;
            aStar.ArriveDistance = arriveDistance.Value;
        }
    }
    public override void OnStart()
    {

        StartCoroutine(_myPatrol());    //调用巡逻
    }
    public override TaskStatus OnUpdate()
    {
        if (waypoints == null)  //如果目标点为空,则返回失败
        {
            Debug.LogError("No Set Waypoint!");
            return TaskStatus.Failure;
        }
        if (stopOnTaskEnd.Value && isOverPatrol)    //如果选择了巡逻完后停止,并且巡逻完毕了。返回true
        {
            return TaskStatus.Success;
        }
        return TaskStatus.Running;
    }
    IEnumerator _myPatrol()
    {
        if (stopOnTaskEnd.Value)    //如果需要巡逻后停住
        {
            foreach (GameObject item in waypoints.Value)    //巡逻完所有的节点
            {
                aStar.StarPath(item.transform);     //设置一个目标点
                while (true)
                {
                    if (Vector3.Distance(transform.position, item.transform.position) <= arriveDistance.Value)
                    {
                        break;
                    }
                    yield return null;
                }
                yield return new WaitForSeconds(waypointPauseDuration.Value);   //停留多少间隙
            }
            isOverPatrol = true;    //已经完成巡逻
            yield return null;
        }
        else
        {
            while (true)    //完成巡逻后重新设置对象
            {
                foreach (GameObject item in waypoints.Value)
                {
                    aStar.StarPath(item.transform);
                    while (true)
                    {
                        if (Vector3.Distance(transform.position,item.transform.position) <= arriveDistance.Value)
                        {
                            break;
                        }
                        yield return null;
                    } 
                    yield return new WaitForSeconds(waypointPauseDuration.Value);   //停留多少间隙
                }
            }
        }

    }

}

这里主要是使用了IEnumerator实现了巡逻的主要逻辑。首先定义一些行为树自带的共享变量,这样就可以在各个脚本间引用一些值了,比如移动速度等。要注意的地方是:共享变量必须定义为publi,且要用.Value的形式才能引用出里面的值。比如public SharedFloat speed;想用speed必须是speed.Value才行。我们先将一些必要的变量定义好,然后写一个IEnumertor,里面分别针对巡逻完后是否需要停止写逻辑。如果需要停止,我们就遍历巡逻目标点waypoints这个List,首先用aStar.StarPath(item.transform)寻路并移动(注意:不能一直寻路移动,只需要执行一次就行了),然后用一个死循环判断目标点和自己的距离,如果小于arriveDistance则认为是到达了,到达了直接break跳出去寻路到下一个目标点。否则就yield return null跳过这一帧(这里一定要注意!这个死循环里判断,如果不满足一定要跳过这一帧,否则会在这一帧不停得判断,导致Unity卡死。这个地方卡了我好久)。还有种情况,巡逻完不需要停止,即反复巡逻,这里我们只需要把刚才那段复制进一个死循环即可,当目标点都走完了,我们再从第一个开始,继续巡逻。
就这样,我们就写了一个简单的小例子,有兴趣的朋友可以自己试试。

最后这里放上我们学习用的插件:
行为树插件 http://download.csdn.net/download/l1606468155/9923832
AStarPathfinding插件 http://download.csdn.net/download/l1606468155/9923826

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页