在游戏中,AI角色可以通过两种方式获得游戏世界的信息——轮询和事件驱动。
1、严格来说,感知系统并不算是游戏AI的一部分,但是,它的实现质量直接关系到AI系统的好坏,因此,对感知系统拥有良好的理解,将会非常有利于构建更强大的AI系统。
2、在游戏中,感知的开销可能会很大,因此,许多情况下,感知不能也不需要在每帧中进行。
3、视线查询(Line-of-Sight),在Unity3D中,Raycast调用可以实现视线查询,遗憾的是速度相对较慢,当场景中有大量物体时进行调用,或调用过于频繁时,开销很大。
触发器
触发器这个概念是与事件驱动系统相对应的,触发器是AI角色能对其做出反应的任何“刺激源”,是它们触发了AI角色感兴趣的事件。例如,听觉或视觉刺激,例如枪声、爆炸、临近的敌人或尸体,也可能由游戏中的非AI角色产生。许多触发器具有这样的特性,即当游戏实体进入触发器所在的范围内时,这个触发器就会被触发。触发器范围一般是以触发器为中心的一个区域,在二维游戏中通常是圆形或矩形,在三维游戏中通常是球体、立方体或圆柱体的。
在游戏设计中,触发器是非常常见的,可以用它们创建各种事件和行为。
如果只考虑模拟人的感觉,那么上面提到过的触发器似乎已经够了,味觉和嗅在游戏中很少使用,而且也可以模拟听觉感知的方式实现。但是游戏中还有一些其他种类的触发器。例如:
由于每个AI角色的特点和能力不同,AI角色可以自己决定对哪些触发器做出反应,而忽略另一些触发器,例如,可能有些AI角色是聋的,无法对声音做出反应,或者听觉能力较弱,只能对很近的声音做出反应等。
常用感知类型的实现
游戏中最常用的感知类型是视觉和听觉。对于视觉,需要配对的视觉触发器和视觉感知器;为了实现听觉,需要配对的声音触发器和声音感知器。总的来说,游戏中有多个触发器以及多个感知器,可以通过一个管理中心——事件管理器,统一对它们进行管理。
另外,游戏中还常常需要模拟人的记忆。例如,如果玩家为了躲避AI角色射击,向右跨一步,躲到墙的后面,如果这时AI角色马上就忘了玩家,重新进入巡逻状态,那就太不真实了。为此,感知系统还要包括一个记忆感知器。
触觉感知
触觉感知可以交给Unity3D的物理引擎来处理。通过为一个游戏物体加上碰撞体,并选中Inspector面板中的isTrigger属性,就可以把它标记为“触发器”。触发器不受物理引擎的控制,当触发器和另一个Collider发生碰撞时(其中至少有一个附加了Rigidbody组件),会发出3个触发信息,分别是OnTriggerEnter(当碰撞体Collider进入trigger触发器时调用),OnTriggerExit(当碰撞体Collider停止触发trigger时调用),OnTriggerStay(当碰撞体Collider接触trigger触发器时,这个函数将在每帧被调用)。在这3个函数中编写相应的代码,就可以实现触觉感知了。
因此,Unity3D已经为触觉感知提供了事件管理器,所以在事件感知器中,不再需要编写触觉相关的代码。
灵活应用触觉感知可以实现许多事件,比如显示信息、自动门的开启、生命值供给器、武器供给器等。
==========================感知系统框架代码=============================
using UnityEngine; using System.Collections; using System.Collections.Generic; /// <summary> /// 这个类负责管理触发器的集合。它维护一个当前所有触发器的列表,当每个触发器被创建时, /// 都会向这个管理器注册自身,加入到这个列表中,事件管理器负责更新和处理所有的触发器, /// 并且当触发器已过期需要被移除时,从列表中删除它们。 /// 事件管理器还维护了一个感知器列表,每个感知器被创建时,向这个管理器注册,加入到感知器列表中。 /// </summary> public class TriggerSystemManager : MonoBehaviour { //初始化当前感知器列表 List<Sensor> currentSensors = new List<Sensor>(); //初始化当前触发器列表 List<Trigger> currentTriggers = new List<Trigger>(); //记录当前时刻需要被移除的感知器,例如感知体死亡,需要移除感知器时; List<Sensor> sensorsToRemove; //记录当前时刻需要被移除的触发器,例如触发器已过期时; List<Trigger> triggersToRemove; void Start () { sensorsToRemove = new List<Sensor> (); triggersToRemove = new List<Trigger> (); } private void UpdateTriggers() { foreach (Trigger t in currentTriggers) { if(t.toBeRemoved){ triggersToRemove.Add(t); }else{ t.Updateme(); } } foreach (Trigger t in triggersToRemove) currentTriggers.Remove (t); } private void TryTriggers() { foreach (Sensor s in currentSensors) { //如果s所对应的感知体还存在(没有因死亡而被销毁) if(s.gameObject != null){ foreach (Trigger t in currentTriggers){ t.Try(s); } }else{ sensorsToRemove.Add(s); } } foreach (Sensor s in sensorsToRemove) currentSensors.Remove (s); } void Update () { //更新所有触发器内部状态 UpdateTriggers (); //迭代所有感知器和触发器,做出相应的行为 TryTriggers (); } //用于注册触发器 public void RegisterTrigger(Trigger t) { currentTriggers.Add (t); } //用于注册感知器 public void RegisterSensor(Sensor s) { currentSensors.Add (s); } }
using UnityEngine; using System.Collections; /// <summary> /// 这个类是所有触发器的基类,视觉触发器和听觉触发器都是它的派生类。 /// </summary> public class Trigger : MonoBehaviour { //保存管理中心对象 protected TriggerSystemManager manager; //触发器的位置 protected Vector3 position; //触发器的半径 public int radius; //当前触发器是否需要被移除 public bool toBeRemoved; //这个方法检查作为参数的感知器s是否在触发器的作用范围内 //(或当前触发器是否能真正被感知器sensor感觉到),如果是,那么采取相应的行为。 //这个方法需要在派生类中实现。 public virtual void Try( Sensor sensor ) { } //这个方法更新触发器的内部状态,例如,声音触发器的剩余有效时间等。 public virtual void Updateme() { } //这个方法检查感知器sensor是否在触发器的作用范围内 //或当前触发器是否能真正被感知器s感觉到,如果是,返回true,如果不是,返回false //它被Try()调用;需要在派生类中实现。 protected virtual bool isTouchingTrigger( Sensor sensor) { return false; } void Awake () { //查找管理器并保存 manager = FindObjectOfType<TriggerSystemManager> (); } protected void Start () { //这时不需要移除,置为false toBeRemoved = false; } void Update () { } }
using UnityEngine; using System.Collections; /// <summary> /// Sensor类是所有感知器的基类,视觉感知器和听觉感知器都是它的派生类。 /// 这个类中包含了对感知器类型的枚举定义和变量,还保存了事件管理器。 /// </summary> public class Sensor : MonoBehaviour { protected TriggerSystemManager manager; public enum SensorType { sight, sound, health } public SensorType sensorType; void Awake () { //查找管理器并保存 manager = FindObjectOfType<TriggerSystemManager> (); } void Start () { } void Update () { } public virtual void Notify (Trigger t) { } }
========================== End ====================================