利用Unity协程实现一个简单的怪物寻路与跟随AI,通过分析怪物行为与逻辑,实现简单的平面怪物寻路与跟随效果。

分析

对于游戏中怪物的行为,简单归纳为如下几部分:

  1. 怪物在预设范围内随机移动。
  2. 玩家走入怪物视野范围,怪物跟随玩家移动。
  3. 进入攻击范围,怪物攻击玩家。
  4. 玩家脱离怪物视野范围,怪物状态回到1。

如下内容需要每帧检测一次:

  • 玩家是否进入怪物的视野范围
  • 玩家是否进入怪物的攻击范围

如下内容需要持续执行:

  • 怪物在预设范围内随机移动

怪物拥有的属性至少有如下内容:

  • 视野角度(ViewAngle),如字意。
  • 视野半径(ViewRadius),即怪物能看到的最远距离。
  • 攻击半径(AttackRadius),即怪物能攻击到的最远距离。
  • 移动速度(MoveSpeed),即怪物的移动速度。
  • 玩家引用,因没有使用射线检测,因此需要此引用来计算距离与角度。
    • 为什么不用射线?因为射线还可以再水一篇。

同时需要的属性还有:

  • 进入视野区域状态标识viewFlag。
  • 其他内容按需添加,如动画等。

怪物拥有的方法至少有如下几个:

  • 自由移动Move
  • 扫描视野 SeeOther
  • 跟随玩家 FollowTarget
  • 攻击玩家 Attack

同时还需要实现的方法:

  • OnDrawGizmos 用来在场景视图中画出视野区域
  • 其他内容按需添加,如播放动画等。

思路

思路:在Update中持续扫描视野,设置进入视野区域状态的值。在一个死循环中执行自由移动,当检测到视野区域状态标识改变后跟随玩家,如玩家脱离视野则继续自由移动,否则持续跟随玩家,当进入攻击距离中攻击玩家。因死循环会阻塞脚本执行,因此可将其放置在协程中。同时也可不使用死循环,直接将其内容放置于update中,但这样就用不到协程了。

实现

搭建场景如下:
在此使用两个立方体代表玩家和怪物,使用四个空物体(Point)代表寻路坐标。

创建两个脚本挂载到怪物cube上,名称随意。一个用来画视野区域,一个用来做AI。

unity3d协程AI寻路点

首先用Gizmos画出扇形区域方便观察测试

//DrawArc.cs
//视野角度
[Range(0,180)]
public int viewAngle;
//视野半径
public float viewRadius = 2;

void OnDrawGizmos()
    {
    Color color = Handles.color;
        //设置画笔颜色
        Handles.color = Color.red;
        //求起使边
        int angle = viewAngle/2;
        Vector3 startLine = Quaternion.Euler(0,-angle,0) * this.transform.forward;
        //画扇形
        Handles.DrawSolidArc(this.transform.position,this.transform.up,startLine,viewAngle,viewRadius);
        //恢复颜色
        Handles.color = color;
    }

效果如下:
gizmous

随机移动方法

//寻路坐标点数组
public Transform[] points;
//坐标索引
public int pointIndex;
public float moveSpeed;
void MoveSelf()
{
    //看向选定坐标点
    Vector3 nextPosition = points[pointIndex].position;
    this.transform.LookAt(nextPosition);
    //前进,自身坐标系,使用Vector3.forward即可
    this.transform.Translate(moveSpeed * Vector3.forward * Time.deltaTime,Space.Self);
    //判断距离
    if (Vector3.Distance(this.transform.position,nextPosition)< 0.1) {
    //更换下一个坐标点
        pointIndex = Random.Range(0,4);//随机范围{0,1,2,3}
    }
}

可以尝试在Update中调用MoveSelf(),效果如下:

跟随方法

void FollowTarget()
{
    //获取调用时目标坐标
    Vector3 targetPostion = target.transform.position;
    this.transform.LookAt(targetPostion);
    this.transform.Translate(moveSpeed * Vector3.forward * Time.deltaTime,Space.Self);
}

可以在Update中测试一下FollowTarget()效果

当然由于目前没有判断任何状态,所以怪物永远追随玩家。

攻击方法

void Attack()
{
    //自由添加内容,例如动画播放等
    //需要注意动画播放等可能需要判断攻击状态是否完成再执行下一次攻击
    //否则在攻击范围内每帧都会打一下
    //这个问题就留到接下来的文章吧
    Debug.Log("打你");
}

这个没有必要测试。

下面就到了关键的时刻了(其实如果用射线下面这些都不用写……)。

为了画图方便,我将怪物的视角角度与半径都放在了DrawArc中,但是怪物的移动等是放在MosterAI中的。在MosterAI脚本中我并不知道怪物的视角范围,也无法计算是否人物是否进入视角范围,那要怎么办呢?

这时,前面说的viewFlag就派上用场了。我们可以在DrawArc脚本中持续判断玩家是否进入视角范围,根据结果去修改MosterAI中的viewFlag,而MosterAI根据这个值来进行跟随或移动等操作。

因此,在MosterAI中添加:

public bool viewFlag;

在DrawArc中写SeeOther方法:

//目标
public GameObject target;
//对MosterAI的引用
public MosterAI mosterAI;
void SeeOther()
{
    //计算距离
    float distance = Vector3.Distance(this.transform.position,target.transform.position);
    //求怪物指向角色的向量
    Vector3 myVector3 = target.transform.position - this.transform.position;
    //计算两个向量的角度
    float angle = Vector3.Angle(myVector3,this.transform.forward);
    //距离小于半径
    if (distance &lt;= viewRadius &amp;&amp; angle &lt;= viewAngle/2) {
    //在视线范围内
    mosterAI.viewFlag = true;
    }else
    {
        //不在视线范围内
        mosterAI.viewFlag = false;
    }
}

在 DrawArc 脚本的Update中调用,能够获取与更新玩家是否进入视线范围的状态,也就能够在MosterAI中写逻辑了。
逻辑为循环执行如下内容:

  • 判断玩家是否进入视线范围
  • 如果没有进入,自由移动。
  • 若进入视线范围,判断是否进入攻击范围。
    • 如果进入视线范围但未进入攻击范围,则跟随目标。
    • 如果进入视线范围且进入攻击范围,则攻击目标。

在MosterAI脚本中写一个协程:

IEnumerator MosterGO()
{
    while (true) {
        //等待固定帧更新完成
        yield return new WaitForFixedUpdate();
        if (viewFlag) {
            //求二者距离
            float distance = Vector3.Distance(target.transform.position,this.transform.position);
            if (distance &lt;= attackDis) {
                Debug.Log("attack");
                continue;
            }
            FollowTarget();
            continue;
        }
        //没有看到目标,自由移动
        MoveSelf();
    }
}

在Start中开启本协程即可。

效果如图,且攻击时控制台输出attack。

总结

这只是个入门的demo,实际上使用射线来检测要方便的多。通过本例可以更好的理解Transform.forwardvector3.up的区别,同时掌握协程的使用并理解Gizmos的作用。

引用资料

1、[头图]【Unity】Unity-Japan UnityChanSD角色

我来吐槽

*

*

3位绅士参与评论

  1. 野兔10-17 19:42 回复

    恩,已将您添加至此 友情链接页面。

  2. c0smxsec10-16 21:57 回复

    等到你用Unity做的三A大作(滑稽

  3. xema10-16 19:20 回复

    老哥学做游戏了嘛

  4. 岁月小筑10-16 16:55 回复

    哇,大佬啊~~~~~~ヽ(°▽°)ノ