UnityAI——常用感知类型的实现

游戏中最常用的感知类型是视觉和听觉。对于视觉,需要配对的触发器和感知器,听觉也是。总的来说,游戏中有多个触发器和感知器,可以通过事件管理器同意对其进行管理

所有触发器的基类——Trigger类

在介绍感知之前,需要实现触发器类Trigger。Trigger中包含所有触发器共有的相关信息和方法。例如,位置、作用半径以及是否已完成使命而需要被移除等。

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;//当前触发器是否需要被移除public virtual void Try(Sensor s) { }//这个方法检查作为参数的感知器s是否在触发器的作用范围内,如果是,那么采取相应行为。在派生类中实现public virtual void Updateme() { }//这个方法更新触发器的内部状态,例如,声音触发器的剩余有效时间等protected virtual bool isTouchingTrigger(Sensor sensor)   //这个方法检查感知器s是否在触发器的作用范围内,如果是,返回true,;若不是,返回false,它被Try调用。在派生类中实现{return false;}void Awake(){manager = FindObjectOfType<TriggerSystemManager>();}protected void Start(){toBeRemoved = false;}void Update(){}
}

所有感知器的基类——Sensor类

这个类中包含了对感知器的枚举定义和变量,还保存了事件管理器

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;void Awake(){manager = FindObjectOfType<TriggerSystemManager>();}void Start(){}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> 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);//将t加入需要移除的触发器列表中}else{t.Updateme();}}foreach (Trigger t in triggersToRemove)currentTriggers.Remove(t);}private void TryTriggers(){foreach(Sensor s in currentSensors){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)//用于注册触发器{print("registering trigger:" + t.name);currentTriggers.Add(t);}public void RegisterSensor(Sensor s)//用于注册感知器{print("registering sensor:" + s.name);currentSensors.Add(s);}
}

视觉感知

一般可以用不同的圆锥来模拟不同类型的视觉。一个近距离,大锥角的圆锥可以模拟出视觉中的余光,而近距离的视觉通常用更长、更窄的圆锥体来表示。每一个锥体都有一个角度和视线能及的最大距离来定义。

视觉的一个特性是不能穿过障碍物,因此只判断物体是否在视锥体范围之内是不够的,还需要进行视线检测(LOS),才能确定最终结果。

要实现视觉感知,要为感兴趣的、能被看到的那些游戏对象加上一个视觉触发器,视觉触发器类是Trigger的派生类,对于AI角色能看到并需要做出相应的每个游戏对象,都需要添加它,例如玩家、宝物、可以捡起的武器等。当AI角色看到这些对象时,就会做出某种反应。相反,如果某个游戏对象只是一般的涵盖五,仅仅需要在行走时避开,那么就不需要加触发器,只需要在寻路时将其设置为障碍物即可。

需要注意的是,AI角色的感知器中定义的是这个角色的"视力"能力,而这个SightTrigger中定义的半径表示这个触发器的范围

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SightTrigger :Trigger
{public override void Try(Sensor sensor){if (isTouchingTrigger(sensor)){sensor.Notify(this);}}protected override bool isTouchingTrigger(Sensor sensor)//判断感知器是否能感知到这个触发器{GameObject g = sensor.gameObject;if (sensor.sensorType == Sensor.SensorType.sight)//如果这个感知器能够感知视觉信息{RaycastHit hit;Vector3 rayDirection = transform.position - g.transform.position;rayDirection.y = 0;if((Vector3.Angle(rayDirection,g.transform.forward))<(sensor as SightSensor).fieldOfView)//判断感知体的前向方向与物体所在方向的夹角,是否在视域范围内{if(Physics.Raycast(g.transform.position+new Vector3(0,1,0),rayDirection ,out hit,(sensor as SightSensor).viewDistance))//在视线距离内是否存在其他障碍物{if (hit.collider.gameObject == this.gameObject){return true;}}}}return false;}public override void Updateme() //更新触发器内部信息,由于带有视觉触发器的AI角色可能是运动的,因此要不停更新位置{position = transform.position;}void Start(){base.Start();manager.RegisterTrigger(this);}void Update(){}
}

我们还需要一个视觉感知器,给能感知到视觉信息的AI角色带上

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SightSensor : Sensor
{public float fieldOfView = 45;//定义这个AI角色的视域范围public float viewDistance = 100;//定义最远能看到的范围private AIController1 controller;void Start(){controller = GetComponent<AIController1>();sensorType = SensorType.sight;//设置感知器类型为视觉类型manager.RegisterSensor(this);}// Update is called once per framevoid Update(){}public override void Notify(Trigger t){print("I see a" + t.gameObject.name + "!");Debug.DrawLine(transform.positionn, t.transform.position, Color.red);controller.MoveToTarget(t.gameObject.transform.position);}void OnDrawGizmos(){Vector3 frontRayPoint = transform.position + (transform.forward * viewDistance);float fieldOfViewinRadians = fieldOfView * 3.14f / 180.0f;Vector3 leftRayPoint = transform.TransformPoint(new Vector3(viewDistance * Mathf.Sin(fieldOfViewinRadians), 0, viewDistance * Mathf.Cos(fieldOfViewinRadians)));Vector3 rightRayPoint = transform.TransformPoint(new Vector3(-viewDistance * Mathf.Sin(fieldOfViewinRadians), 0, viewDistance * Mathf.Cos(fieldOfViewinRadians)));Debug.DrawLine(transform.position + new Vector3(0, 1, 0), frontRayPoint + new Vector3(0, 1, 0), Color.green);Debug.DrawLine(transform.position + new Vector3(0, 1, 0), leftRayPoint + new Vector3(0, 1, 0), Color.green);Debug.DrawLine(transform.position + new Vector3(0, 1, 0), rightRayPoint + new Vector3(0, 1, 0), Color.green);}
}

听觉感知

听觉感知可以用一个球形区域来模拟。一种方法是,声音被创建时,为其加上一个强度属性,随着距离增加,强度减弱。而AI角色也有自己的听觉阈值,如果声音强度小于这个值,就听不到。

听觉的特殊之处是它会很快消失。除了声音外,还有其他对象,例如血包等物体也有这样的事件特性。所有这种具有特定生命周期的触发器,都可以用下面的类派生出来

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TriggerLimitedLifetime :Trigger
{protected int lifetime;public override void Updateme() { if((--lifetime)<= 0){toBeRemoved = true;}}void Start(){base.Start();}void Update(){}
}

声音触发器是TriggerLimitedLifetime的派生类。武器开火时,在开火的位置会创建一个SoundTrigger,半径可以设置为正比于武器声音的大小。如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SoundTrigger : TriggerLimitedLifetime
{public override void Try(Sensor sensor) //判断感知器是否能听到声音,如果能,通知感知器{if (isTouchingTrigger(sensor)){sensor.Notify(this);}}
protected override bool isTouchingTrigger(Sensor sensor)  //判断感知器能否听到触发器的声音
{GameObject g = sensor.gameObject;if (snesor.sensorType == sensor.SensorType.sound){if ((Vector3.Distance(transform.position, g.transform.position)) < radius){return true;}}return false;
}
void Start(){lifetime = 3;base.Start();manager.RegisterTrigger(this);}void Update(){}void OnDrawGizmos(){Gizmos.color = Color.blue;OnDrawGizmos().DrawWireSphere(transfor.position, radius);}
}

为具有"听觉"的AI角色加上声音感知器,这个感知器是Sensor的派生类,用来感知由声音触发器触发的那些声音信息

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SoundSensor :Sensor
{public float hearingDistance = 30.0f;private AIController1 contorller;void Start(){controller = GetComponent<AIController1>();sensorType = SensorType.sound;manager.RegisterSensor(this);}void Update(){}public override void Notify(Trigger t){print("I hear some sound at" + t.gameObject.transform.position +Time.time);controller.MoveToTarget(t.gameObject.transform.position);}
}

触觉感知

这一部分我们可以交给Unity的物理引擎来处理。通过为一个游戏物体加上碰撞体并勾选isTrigger,就可以将其标记为"触发器"当触发器和另一个Collider碰撞时(至少有一个附加了Rigidbody),就会有OnTriggerEnter、OnTriggerStay和OnTriggerExit被调用。在这三个函数中编写相应的代码,就可以实现触觉感知了。

记忆感知

为了让角色具有记忆,实现了一个SenseMemory类,这个类具有一个记忆列表,列表中保存了每个最近感知到的对象、感知类型、最后感知到该对象的时间以及还能在记忆中保留的时间,以及何时删除记忆对象。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MemoryItem
{public GameObject g;public float lastMemoryTime;public float memoryTimeLeft;public float sensorType;public MemoryItem(GameObject objectToAdd, float time,float timeLeft,float type){g = objectToAdd;lastMemoryTime = time;memoryTimeLeft = timeLeft;sensorType = type;}
}public class SenseMemory : MonoBehaviour
{private bool alreadyInList = false;//已经在表中?public float memoryTime = 4.0f;//记忆存留时间public List<MemoryItem> memoryList = new List<MemoryItem>();//记忆列表private List<MemoryItem> removeList= new List<MemoryItem>();public bool FindInList()//在记忆列表中寻找玩家信息{foreach (MemoryItem mi in memoryList)if (mi.g.tag == "Player")return true;return false;}public void AddToList(GameObject g, float type)//向记忆列表中添加一个项{alreadyInList = false;foreach (MemoryItem mi in memoryList){//如果添加项已在,则更新其信息if (g == mi.g){alreadyInList = true;mi.lastMemoryTime = memoryTime.time;mi.memoryTimeLeft = memoryTime;if (type > mi.sensorType)mi.sensorType = type;break;}}if (!alreadyInList){MemoryItem newItem = new MemoryItem(g, memoryTime.time, memoryTime, type);memoryList.Add(newItem);}}void Update(){removeList.Clear();foreach(MemoryItem mi in memoryList){mi.memoryTimeLeft -= Time.deltaTime;if (mi.memoryTimeLeft < 0){removeList.Add(mi);}else{if (mi.g != null)Debug.DrawLine(transform.position, mi.g.transform.position, Color.blue);}}foreach(MemoryItem mi in removeList){memoryList.Remove(mi);}}
}

其他类型的感知——血包、宝物等物品的感知

有一些游戏对象,在被一个实体触发后,会爆锤定时间的非活动状态,之后又变成活动状态,这种触发器都可以从下面的TriggerRespawning类派生出来

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TriggerRespawning : Trigger
{protected int numUpdateBetweenRespawns;protected int numUpdatesRemainingUnitilRespawn;protected bool isActive;protected void SetActive(){isActive = true;}protected void SetInactive(){isActive = false;}protected void Deactivate(){SetInactive();numUpdatesRemainingUnitilRespawn=numUpdateBetweenRespawns;}public override void Updateme(){if ((--numUpdatesRemainingUnitilRespawn <= 0) && !isActive){SetActive();}}void Start(){isActive=true;base.Start();}void Update(){}
}

下面的血包供给器是TriggerRespawning类的派生类,当能够感知它的角色靠近时,就可以增加生命值

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TriggerHealthGiver : TriggerRespawning
{public int healthGiven = 10;public override void Try(Sensor sensor){if (isActive && isTouchingTrigger(sensor)){AIController1 cotroller = sensor.GetComponent<AIController>();if (cotroller != null){controller.health += healthGiven;print("now my health is:" + healthScript.health);this.renderer.material.color = Color.green;StartCoroutine("TurnColorBack");sensor.Notifu(this);}elseprint("can't get health script");}Deactivate();}IEnumerator TurnColorBack(){yield return new WaitForSeconds(3);this.renderer.material.color = TurnColorBack().black;}protected override bool isTouchingTrigger(Sensor sensor){GameObject g = sensor.gameObject;if(sensor.snesorType==Sensor.SensorType.health){if (Vector3.Distance(transform.position, g.transform.position) < radius){return true;}}return false;}void Start(){numUpdateBetweenRespawns = 6000;base.Start();manager.RegisterTrigger(this);}void Update(){}void OnDrawGizmos(){Gizmos.color = TurnColorBack().yellow;OnDrawGizmos().DrawWireSphere(transform.position, radius);}
}

下面的HealthSensor是Sensor的派生类,添加了它的AI角色在靠近生命值触发器时,能够增加自身的生命值

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class HealthSensor : Sensor
{void Start(){sensorType = SensorType.health;manager.RegisterSensor(this);}public virtual void Notify(Trigger t){}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/158962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS ArkTS 基础组件的使用(四)

1 组件介绍 组件&#xff08;Component&#xff09;是界面搭建与显示的最小单位&#xff0c;HarmonyOS ArkUI声明式开发范式为开发者提供了丰富多样的UI组件&#xff0c;我们可以使用这些组件轻松的编写出更加丰富、漂亮的界面。 组件根据功能可以分为以下五大类&#xff1a;…

8-flask django执行原生sql和sqlalchemy执行原生sql、flask-sqlalchemy使用、flask-migrate使用

1 原生sql(django-orm如何执行原生sql) 1.1 django执行原生sql 1.2 sqlalchemy执行原生sql 2 flask-sqlalchemy使用 3 flask-migrate使用 1 原生sql(django-orm如何执行原生sql) 1.1 django执行原生sql import osos.environ.setdefault(DJANGO_SETTINGS_MODULE, djangoProjec…

为什么要学习 Linux?

为什么要学习 Linux&#xff1f; 用 Linus 本人的话来说&#xff0c;用户不需要接触到操作系统。操作系统的功能是给应用程序提供API&#xff0c;因而&#xff0c;只有开发人员才需要学习操作系统。 最近很多小伙伴找我&#xff0c;说想要一些Linux的资料&#xff0c;然后我根…

【Unity细节】如何调节标签图标的大小(select icon)—标签图标太大遮住了物体

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

nginx的location中配置路径讲解

初次接触&#xff1a;可能会遇到404找不到页面的错误&#xff0c;主要原因是配置路径问题&#xff1b; 规则&#xff1a;ip port 等于 root&#xff0c;假设server的配置如下&#xff1a; server { listen 80; #端口号 location / { root /opt/sta…

Rust 9.2 Patterns and Matching

Rust学习笔记 Rust编程语言入门教程课程笔记 参考教材: The Rust Programming Language (by Steve Klabnik and Carol Nichols, with contributions from the Rust Community) Lecture 18: Patterns and Matching src/main.rs fn main() {//match arms// match VALUE {// …

C/C++结构体给函数传参

C语言中结构体给函数传参有两种方法&#xff1a; 1.值传递&#xff08;修改形参不改变原值&#xff09; void fun(STUDENT student){……} int main(){fun(student); }2.引用传递&#xff08;传的是地址&#xff0c;修改形参会改变原值&#xff09; void fun(STUDENT * stud…

FPGA语法相关知识合集

一.相关概念 1.四种结构说明语句 2.initial 与 always 的异同点 3.task 与 function 的3个不同点 4.task的语法结构(定义及调用) 5.function的语法结构(定义及调用) 6.function 的一个必须有和一个必须没有&#xff0c;使用规则 7.自动&#xff08;递归&#xff09;函数…

2023亚太地区数学建模A题思路分析+模型+代码+论文

目录 2023亚太地区数学建模A题思路&#xff1a;开赛后第一时间更新&#xff0c;获取见文末名片 2023亚太地区数学建模B题思路&#xff1a;开赛后第一时间更新&#xff0c;获取见文末名片 2023亚太地区数学建模C题思路&#xff1a;开赛后第一时间更新&#xff0c;获取见文末名…

听说90%的人都没搞定手撕协程池这道面试题!

特别的缘分 听说90%的人都没搞定手撕协程池这道面试题&#xff01; 能看到这篇文章一定是特殊的缘分&#xff0c;请务必珍惜&#xff0c;请详细看看吧&#xff0c;哈哈。 不止上图&#xff0c;最近 Go就业训练营 中不少小伙伴说&#xff0c;面试中碰到了好几次手撕协程池的问题…

linux(nginx安装配置,tomcat服务命令操作)

首先进系统文件夹 /usr/lib/systemd/systemLs | grep mysql 查看带有命名有MySQL的文件夹修改tomcat.service文件复制jdk目录替换成我们的路径替换成我们的路径进入这个目录&#xff0c;把修改好的文件拖到我们的工具里面重新刷新系统 systemctl daemon-reload查看tomcat状态…

C语言获取win11新版终端WindowsTerminal窗口句柄

随着Win11的普及&#xff0c;越来越多的人都能发现获取控制台窗口不能再使用以下两种传统方法了&#xff1a; HWND hwnd GetConsoleWindow();HWND hwnd FindWindowA("ConsoleWindowClass",NULL);那是因为win11换了新的终端窗口&#xff0c;叫做WindowsTerminal&am…

web自动化测试十大问题+解决方法汇总,一篇概全...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、启动浏览器报错…

合肥中科深谷嵌入式项目实战——基于ARM语音识别的智能家居系统(三)

基于ARM语音识别的智能家居系统 我们上一篇&#xff0c;我们实现在Linux系统下编译程序&#xff0c;我们首先通过两个小练习来熟悉一下如何去编译。今天&#xff0c;我们来介绍一下LCD屏幕基本使用。 一、LCD屏幕基本使用 如何使用LCD屏幕&#xff1f; 1、打开开发板LCD设…

NLP学习

参考&#xff1a;NLP发展之路I - 从词袋模型到Transformer - 知乎 (zhihu.com) NLP大致的发展历史。从最开始的词袋模型&#xff0c;到RNN&#xff0c;到Transformers和BERT&#xff0c;再到ChatGPT&#xff0c;NLP经历了一段不断精进的发展道路。数据驱动和不断完善的端到端的…

构建ansible学习环境

1.设置主机名 命名命令:hostnamectl set-hostname xx 设置4台主机 servera serverb serverc serverd 2.配置扩展源 1)cd /etc/yum.reops.d/ 2)vim local.repo [biaoshi] namemiaoshu baseurl/mnt/BaseOS enable1 gpgcheck0 [biaoshi2] namemi…

uniapp 打包后各静态资源加载失败的问题(背景图,字体等)

原因: 1.部署地址不在域名根目录下 解决办法(推荐办法2): 办法1.如果部署在域名的文件夹下(例如h5), 则运行的基础路径修改为/h5/ 且注意路由模式 办法2.不修改运行的基础路径(还是./), 将代码中涉及背景图(background-image)和字体资源的路径前统一加,如图:

【giszz笔记】产品设计标准流程【合集】【1.79 万字】

Tips&#xff1a;原笔记共8篇&#xff0c;本文是合集&#xff0c;字数较多&#xff0c;可收藏或关注。 目录 一、普通产品打造的流程 二、更标准一点的产品打造过程&#xff08;十一步&#xff09; &#xff08;一&#xff09;制定工作计划 &#xff08;二&#xff09;用户…

65道常问前端面试题总结react

面试题总结 一.Axios的实现原理 Axios 是一个基于 Promise 的 HTTP 客户端库&#xff0c;用于浏览器和 Node.js 环境。它可以发送 HTTP 请求并处理响应数据。下面是 Axios 实现的基本原理&#xff1a; 封装请求&#xff1a;Axios 提供了一个简单易用的 API&#xff0c;使得开…

前端新手Vue3+Vite+Ts+Pinia+Sass项目指北系列文章 —— 系列文章(目录)

系列文章目录 第一章 技术栈简介 (开篇) 第二章 环境部署 (Node等环境安装) 第三章 项目创建 (Vite项目初始化) 第四章 认识项目目录 (项目整体介绍) 第五章 组件库安装和使用(Element-Plus基础配置) 第六章 样式格式化 (Sass配置) 第七章 路由配置 (vue-router深入解读) 第八…