1、AI角色感知的信息多种多样,通常会包含视觉和听觉信息,也可能包括脚步声、死去的同伴或敌人等。
其中,视线查询几乎是必不可少的,一般通过 Raycast 调用实现
在游戏中,AI角色可以通过两种方式来获得游戏信息——轮询和事件驱动
轮询是积极地观察世界获得信息
而事件驱动是通过坐等消息地方式来获得信息
2、在事件驱动的感知系统中,有一个中心检测系统,被称为“事件管理器”。
——记录每个AI角色所感兴趣的事件,并负责检查、处理和分发事件。
事件检测机制与事件管理器也常常分开实现,在采用基于触发器的事件检测方法中:
对事件感兴趣的角色通常称为“监听者(listener)”,必须事先向事件管理器注册,以确定被告知何种信息
告知的方法最简单的就是以 事件 为参数,调用某个函数
3、事件驱动感知系统
Trigger、Sensor和TriggerSystemManager
using System.Collections; using System.Collections.Generic; using UnityEngine; //所有触发器的基类,视觉触发器和听觉触发器是它的派生类 //包含所有触发器的共有信息和方法,如当前位置、作用半径、是否已完成使命而需要被移除等 public class Trigger : MonoBehaviour { //保存管理中心对象 protected TriggerSystemManager manager; //触发器的位置 protected Vector3 position; //触发器的半径 public int radius; //当前触发器是否需要被移除 public bool toBeRemoved; //这个方法检查作为参数的感知器s是否在触发器的作用范围内(或当前触发器是否能真正被感知器s感知到) //如果是,那么采取相应的行为。这个方法需要在派生类中实现 public virtual void Try(Sensor s) { } //这个方法更新触发器内部状态,例如,声音触发器的剩余有效时间等; public virtual void Updateme() { } //这个方法检查感知器s是否在触发器的作用范围内(或当前触发器是否能真正被感知器s感觉到) //如果是,返回true,如果不是,返回false,它被Try()调用;需要在派生类中实现 protected virtual bool isTouchingTrigger(Sensor sensor) { return false; } private void Awake() { //查找管理器并保存 manager = FindObjectOfType<TriggerSystemManager>(); } protected void Start() { //这时不需要被移除,置为false; toBeRemoved = false; } // Update is called once per frame void Update () { } }
using System.Collections; using System.Collections.Generic; using UnityEngine; //所有感知器的基类,视觉感知其和听觉感知去是它的派生类 //包含对感知器类型的枚举定义和变量,还保存了事件管理器 public class Sensor : MonoBehaviour { protected TriggerSystemManager manager; public enum SensorType { sight, sound, health } public SensorType sensorType; private void Awake() { //查找管理器并保存 manager = FindObjectOfType<TriggerSystemManager>(); } // Use this for initialization void Start () { } // Update is called once per frame void Update () { } public virtual void Notify(Trigger t) { } }
using System.Collections; using System.Collections.Generic; using UnityEngine; //事件管理器,负责管理触发器的集合,维护一个当前所有触发器的列表,当每个触发器被创建时,都会向这个管理器注册自身,加入到列表 //事件管理器负责更新和处理所有的触发器,过期时从列表中删除它们 //事件管理器还维护了一个感知器列表,每当感知器被创建时,向这个管理器注册,加入到感知器列表中 public class TriggerSystemManager : MonoBehaviour { //初始化当前感知器列表; List<Sensor> currentSensors = new List<Sensor>(); //初始化当前触发器列表; List<Trigger> currentTriggers = new List<Trigger>(); //记录当前时刻需要被移除的感知器,例如感知体死亡,需要移除感知器时; List<Sensor> sensorsToRemove; //记录当前时刻需要被移除的触发器,例如触发器已过期时; List<Trigger> triggersToRomove; // Use this for initialization void Start () { sensorsToRemove = new List<Sensor>(); triggersToRomove = new List<Trigger>(); } private void UpdateTriggers() { //对于当前触发器列表中的每个触发器t foreach(Trigger t in currentTriggers) { //如果t需要被移除 if (t.toBeRemoved) { //将t加入需要移除的触发器列表中(这是由于不能再foreach中直接移除,否则会报错) triggersToRomove.Add(t); } else { //更新触发器内部信息 t.Updateme(); } } //对于需要移除的触发器列表中的每个触发器t,从当前触发器列表中移除t foreach (Trigger t in triggersToRomove) currentTriggers.Remove(t); } private void TryTriggers() { //对于当前感知器列表中的每个感知器s foreach(Sensor s in currentSensors) { //如果s所对应的感知器还存在(没有因死亡而被销毁) if (s.gameObject != null) { //对于当前触发器列表中的每个触发器t foreach(Trigger t in currentTriggers) { //检查s是否在t的作用范围内,并且做出相应的响应 t.Try(s); } } else { //将感知器s加入到需要移除的感知器列表中 sensorsToRemove.Add(s); } } //对于需要移除的感知器列表中的每个感知器s,从当前感知器列表中移除s; foreach (Sensor s in sensorsToRemove) currentSensors.Remove(s); } // Update is called once per frame void Update () { //更新所有触发器内部状态 UpdateTriggers(); //迭代所有感知器和触发器,做出相应的行为; TryTriggers(); } //用于注册触发器 public void RegisterTrigger(Trigger t) { print("rigistering trigger:" + t.name); //将参数触发器t加入到当前触发器列表中 currentTriggers.Add(t); } //用于注册感知器 public void RegisterSensor(Sensor s) { print("rigistering sensor:" + s.name); //将参数感知器s加入到当前感知器列表中 currentSensors.Add(s); } }
4、视觉感知、听觉感知、触觉感知、记忆感知及其他类型的感知