这个系列是我阅读 UNITY5.X AI PROGRAMMING COOKBOOK 的笔记。有好又坏 请酌情阅读,有不当之处还请指出。
 
本章主要概述让 AI 更智能的移动。对应到原书也就是第一章节。
使AI移动 这三个类的工作如下。
Agent负责 计算当前帧的移动与下一帧的移动。 
AgentBehaviour负责抽象Steering的计算过程。 
Steering包含了Agent需要计算的数据。 
 
每当需要扩展一个新的移动行为,则只需要增加一个 AgentBehaviour 类型的派生即可。
脚本时序  所有的脚本按照优先级调用。例如 检测与执行AI的移动的Agent脚本调用最频繁。其他由 AgentBehaviour 派生的脚本次之。
基础结构 
这里详细介绍了利用 Agent/AgentBehaviour/Steering 三个类进行基础的 AI 结构搭建。当然代码里有一些 Bad Smell ,比如 Steering 类的 new ,每个行为返回一个Steering毫无例外的都会new一次。当然这完全是可以规避的,这里并不作代码优化的讨论 所以只是提到。
 
Agent 代理 Agent 通过 Update 与 LateUpdate 来计算当前帧与下一帧的移动速度角度。 其中实际给 GameObject 做位移的代码就只有两句 Translate/Rotate 。
Update :执行当前帧的物体的移动与角度 
LateUpdate :计算下一帧的物体的移动与角度数值 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 using  UnityEngine;using  System.Collections;public  class  Agent  : MonoBehaviour {     public  float  maxSpeed;     public  float  maxAccel;     public  float  orientation;     public  float  rotation;     public  Vector3 velocity;     protected  Steering steering;     void  Start  ()     {         velocity = Vector3.zero;         steering = new  Steering();     }     public  void  SetSteering  (Steering steering )     {         this .steering = steering;     }     public  virtual  void  Update  ()     {         Vector3 displacement = velocity * Time.deltaTime;         orientation += rotation * Time.deltaTime;                           if  (orientation < 0.0f )             orientation += 360.0f ;         else  if  (orientation > 360.0f )             orientation -= 360.0f ;         transform.Translate(displacement, Space.World);         transform.rotation = new  Quaternion();         transform.Rotate(Vector3.up, orientation);     }     public  virtual  void  LateUpdate  ()     {         velocity += steering.linear * Time.deltaTime;         rotation += steering.angular * Time.deltaTime;         if  (velocity.magnitude > maxSpeed)         {             velocity.Normalize();             velocity = velocity * maxSpeed;         }         if  (steering.angular == 0.0f )         {             rotation = 0.0f ;         }         if  (steering.linear.sqrMagnitude == 0.0f )         {             velocity = Vector3.zero;         }         steering = new  Steering();     } } 
 
Steering 数据载体 Steering 承载了代理(Agent)需要计算的原始数据。数据的表现直接会影响到计算的AgentBehaviour / Agent  的计算结果。
1 2 3 4 5 6 7 8 9 10 11 12 using  UnityEngine;using  System.Collections;public  class  Steering {     public  float  angular;     public  Vector3 linear;     public  Steering  ()     {         angular = 0.0f ;         linear = new  Vector3();     } } 
 
AgentBehaviour 代理行为 由于 gentBehaviour 是Agent的数据提供方。所以他主要的职责就是派生与扩展。 新的类型将会继承自 AgentBehaviour 并且会重写 GetSteering() 函数来丰富表现。 接下来都是介绍 AgentBehaviour 脚本派生的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using  UnityEngine;using  System.Collections;public  class  AgentBehaviour  : MonoBehaviour {     public  GameObject target;     protected  Agent agent;     public  virtual  void  Awake  ()     {         agent = gameObject.GetComponent<Agent>();     }     public  virtual  void  Update  ()     {         agent.SetSteering(GetSteering());     }     public  virtual  Steering GetSteering  ()     {         return  new  Steering();     } } 
 
更加智能的移动 Seek 搜索 上面说到 AgentBehaviour 代理行为,是可以派生的。我们可以直接重写其 GetSteering 函数,来增加不同的表现。 这里干了两件事
获取朝向目标的向量 
目标朝向目标的向量归一化,乘以当前速度。也就是获得了 朝向目标的速度的向量。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 using  UnityEngine;using  System.Collections;public  class  Seek  : AgentBehaviour {     public  override  Steering GetSteering ()     {         Steering steering = new  Steering();         steering.linear = target.transform.position - transform.position;         steering.linear.Normalize();         steering.linear = steering.linear * agent.maxAccel;         return  steering;     }  } 
 
Purse 追逐 在 GetSteering() 函数中有这样的逻辑存在。 追逐的目标 targetAux , 所以需要预先计算(预判单位时间内) targetAux 可能到达的位置(这里用target表示)。maxPrediction变量在这里用来控制预判的时间。 我们知道 任何数值都需要规范化的 例如常用的 Math.Clamp 函数。我们应该尽量将 maxPrediction 值控制在正常范围之内。
通过  speed <= distance / maxPrediction 
speed :为当前追踪的物体真实速度。 
distance :为当前追踪的物体一共走过的距离。 
maxPrediction :预设的预测时间。 
 
这样可以大胆猜测 为当前追踪的物体 正处于匀速 或者 加速中
 
distance / maxPrediction 我们最终得到的 速度 就是物体的 预测速度。我们拿着 预测速度 与 speed 做一个比较。如果预测的速度小于真实速度,那么这个预测值出乎意料的错误。我们需要重新计算一个更真实合理的预测值。prediction = distance / speed;  这里计算了一个中规中矩的预测值。 最终通过 targetAgent.velocity * prediction 获取 预测时间 内运动的距离。
对于刚才的猜测结果,有一种条件下是有问题的,那就是当 当前追踪的物体 突然静止/或者本身就是静止的。由于脚本执行的时序问题,追逐到物体的时候会直接穿插过去(追过头了),然后接着反复运动。这里就要用到 Arraying 脚本了,后文就会介绍到。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public  class  Pursue  : Seek  {     public  float  maxPrediction;     private  GameObject targetAux;     private  Agent targetAgent;     public  override  void  Awake ()     {            base .Awake();            targetAgent = target.GetComponent<Agent>();            targetAux = target;            target = new  GameObject();     }     void  OnDestroy  ()     {            Destroy(targetAux);     }     public  override  Steering GetSteering ()     {            Vector3 direction = targetAux.transform.position - transform.position;            float  distance = direction.magnitude;            float  speed = agent.velocity.magnitude;            float  prediction;            if  (speed <= distance / maxPrediction)                prediction = maxPrediction;            else                 prediction = distance / speed;            target.transform.position = targetAux.transform.position;            target.transform.position += targetAgent.velocity * prediction;            return  base .GetSteering();      } } 
 
Flee 逃离 顾名思义 Flee 肯定和 Seek 相反。事实也正是如此,获取了与目标相反方向的速度的向量。
1 2 3 4 5 6 7 8 9 10 11 12 13 using  UnityEngine;using  System.Collections;public  class  Flee  : AgentBehaviour {     public  override  Steering GetSteering ()     {         Steering steering = new  Steering();         steering.linear = transform.position - target.transform.position;         steering.linear.Normalize();         steering.linear = steering.linear * agent.maxAccel;         return  steering;     }  } 
 
Evade 规避 规避(Evade) 代码与 Pursue 代码一致。在 Flee/Seek 层,二者做了区分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 using  UnityEngine;using  System.Collections;public  class  Evade  : Flee {     public  float  maxPrediction;     private  GameObject targetAux;     private  Agent targetAgent;     public  override  void  Awake ()     {         base .Awake();         targetAgent = target.GetComponent<Agent>();         targetAux = target;         target = new  GameObject();     }     public  override  Steering GetSteering ()     {         Vector3 direction = targetAux.transform.position - transform.position;         float  distance = direction.magnitude;         float  speed = agent.velocity.magnitude;         float  prediction;         if  (speed <= distance / maxPrediction)             prediction = maxPrediction;         else              prediction = distance / speed;         target.transform.position = targetAux.transform.position;         target.transform.position += targetAgent.velocity * prediction;         return  base .GetSteering();     }     void  OnDestroy  ()     {         Destroy(targetAux);     } } 
 
本篇到此结束,本文主要介绍了 AI 行为的基础结构 与 其何如派生出来行为。下一篇会着重介绍派生出的其他行为与详细算法(实现)。
 
本文标题: AI Design 1
文章作者: Keyle
发布时间: 2017-03-01
最后更新: 2024-08-20
原始链接: https://vrast.cn/posts/d1301104/ 
版权声明: ©Keyle's Blog. 本站采用署名-非商业性使用-相同方式共享 4.0 国际进行许可