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();
}
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();
}
// 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 currentSensors = new List();
//初始化当前触发器列表;
List currentTriggers = new List();
//记录当前时刻需要被移除的感知器,例如感知体死亡,需要移除感知器时;
List sensorsToRemove;
//记录当前时刻需要被移除的触发器,例如触发器已过期时;
List triggersToRomove;
// Use this for initialization
void Start () {
sensorsToRemove = new List();
triggersToRomove = new List();
}
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);
}
}