Unity——模拟AI视觉

2023-09-12 16:37:34

人类的视觉系统有以下几个特点:

  1. 距离有限。近处看得清,远处看不清
  2. 容易被遮挡。不能穿过任何不透明的障碍物
  3. 视野范围大约为90度。实现正前方信息丰富,具有色彩和细节;实现外侧的部分只有轮廓和运动信息
  4. 注意力有限。当关注某个具体的方位或物体时,其他部分被忽略,如魔术中的障眼法总是能骗过观众

对AI视觉的模拟就是基于以上这些基本特点,在此基础上有各种各样的实现思路。如果从第二个特点出发,很容易联想到射线也具有不能穿过物体的特点,而且涉嫌也可以设定发射距离。最大的难点在于“视野范围大约为90°”这一特点,在3D自由视角的游戏中,视野范围是一个圆锥体,在俯视角游戏中视野范围是一个扇形区域。无论圆锥体还是扇形区域,都无法直接用射线、球形射线或盒子射线等简单形状模拟,必须找到一种变通的解决方案。

针对用射线模拟扇形区域的问题,这里给出一个易于理解的方法:用多条射线模拟区域。

脚本和效果图如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AIEnemy : MonoBehaviour
{
    public int viewRadius = 4;  //视野距离
    public int viewLines = 30; //射线数量
    void Start(){ }

   
    void Update()
    {
        FieldOfView();
         
    }
    void FieldOfView()
    {
        //获得最左边那条射线的向量,相对正前方,角度是-45°
        Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * viewRadius;
        //依次处理每条射线
        for(int i = 0; i <= viewLines; i++)
        {
            Vector3 v = Quaternion.Euler(0, (90.0f / viewLines) * i, 0) * forward_left;
            //角色位置+v,就是射线终点pos
            Vector3 pos = transform.position + v;
            //从玩家为之到pos画线段,只会在编辑器里看到
            Debug.DrawLine(transform.position, pos, Color.red);
        }
    }
}

 6580e9a0e296452c9a8e469c04c4a939.png

但这只是第一步,实际还需要将射线发射出去才可以。只有将射线发射出去才能判断是否碰到了障碍物,如果碰到障碍物,视线端点就落在碰撞点上。将FieldOfView的代码修改如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AIEnemy : MonoBehaviour
{
    public int viewRadius = 4;  //视野距离
    public int viewLines = 30; //射线数量
    void Start(){ }

   
    void Update()
    {
        FieldOfView();
    }
    void FieldOfView()
    {
        //获得最左边那条射线的向量,相对正前方,角度是-45°
        Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * viewRadius;
        //依次处理每条射线
        for(int i = 0; i <= viewLines; i++)
        {
            Vector3 v = Quaternion.Euler(0, (90.0f / viewLines) * i, 0) * forward_left;
            //角色位置+v,就是射线终点pos
            Vector3 pos = transform.position + v;

            //实际发射射线。注意RayCast的参数,重载很多容易搞错
            RaycastHit hitInfo;
            if(Physics.Raycast(transform.position,v,out hitInfo, viewRadius))
            {
                //碰到物体,终点改为碰到的点
                pos = hitInfo.point;
            }

            //从玩家位置到pos画线段,只会在编辑器里看到
            Debug.DrawLine(transform.position, pos, Color.red);
        }
    }
}

效果图:

81d26d83217049f3a3c636cc1f97dce6.png

修改之后 ,用任意物体阻碍射线,会发现射线确实出现了被阻挡的效果。

在实际游戏中,当射线集中玩家时,就表示敌人发现了玩家,这时就需要进行进一步处理。简单来说,就是把逻辑代码插入上述代码的if代码段里。

以上方法虽然实现了逻辑功能,但没有清晰表现出视野范围。在经典的潜入为游戏中,为了给玩家提示具体的敌人视野范围,会加入明显的画面表现。例如在《崩坏3》中的一些关卡里,就有机器人前面有一个非常夸张的红色扇形,用来显示敌人的视野区域。

下面利用程序建模的方法,将视野范围显示出来,准备工作如下:

  1. 给敌人新建一个空物体作为子物体,命名为view,位置归0
  2. 添加Mesh Renderer组件和Mesh Filter组件
  3. 新建一个材质,将其渲染模式改为Transparent(透明),颜色改为绿色,透明度改为150左右。为检验材质设置可以将它拖曳到立方体上进行测试,观察透明度是否合适
  4. 将材质球拖曳到新建的view物体上。由于view物体暂时没有模型,因此显示不出来

由于视野范围是动态变化的,因此需要用代码拼出一个扇形平面模型并赋予view物体,以表现视野范围。由于改动较多,将修改后的完整脚本展示如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AIEnemy : MonoBehaviour
{
    public int viewRadius = 4;  //视野距离
    public int viewLines = 30; //射线数量
    public MeshFilter viewMeshFilter;
    List<Vector3> viewVerts;  //定点列表
    List<int> viewIndices;  //定点序号列表
    void Start()
    {
        Transform view = transform.Find("view");
        viewMeshFilter = view.GetComponent<MeshFilter>();
        viewVerts=new List<Vector3>();
        viewIndices = new List<int>();
    }

   
    void Update()
    {
        FieldOfView();
    }
    void FieldOfView()
    {
        viewVerts.Clear();
        viewVerts.Add(Vector3.zero);  //加入起点坐标,局部坐标系


        //获得最左边那条射线的向量,相对正前方,角度是-45°
        Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * viewRadius;
        //依次处理每条射线
        for(int i = 0; i <= viewLines; i++)
        {
            Vector3 v = Quaternion.Euler(0, (90.0f / viewLines) * i, 0) * forward_left;
            //角色位置+v,就是射线终点pos
            Vector3 pos = transform.position + v;

            //实际发射射线。注意RayCast的参数,重载很多容易搞错
            RaycastHit hitInfo;
            if(Physics.Raycast(transform.position,v,out hitInfo, viewRadius))
            {
                //碰到物体,终点改为碰到的点
                pos = hitInfo.point;
            }
            //将每个点的位置加入列表,注意转为局部坐标系
            Vector3 p = transform.InverseTransformPoint(pos);
            viewVerts.Add(p);
         
        }
        //根据顶点绘制模型
        RefreshView();
    }
    void RefreshView()
    {
        viewIndices.Clear();
        //逐个加入三角面,每个三角面都以起点开始
        for(int i = 1; i < viewVerts.Count-1; i++)
        {
            viewIndices.Add(0);
            viewIndices.Add(i);
            viewIndices.Add(i+1);
        }
        //填写Mesh信息
       Mesh mesh = new Mesh();
        mesh.vertices= viewVerts.ToArray();
        mesh.triangles = viewIndices.ToArray();
        viewMeshFilter.mesh = mesh;
    }
}

简单来说,网格信息是由"顶点"和"顶点序号"组成的。顶点是空间位置,因此用Vector3表示,所有顶点放在一个大数组中,每个顶点对应一个数组的下标。

而顶点序号指的正是数组的下标。由于网格是由三角面组成的,因此3个序号为一组,3个又3个地填写顶点序号,就代表着1个又1个的三角面。

三角面的正反:

三角面是由3个顶点序号组成的,而3个点的顺序可能有两种,分别是a-b-c和a-c-b,即顺时针和逆时针。一般的材质默认都是单面渲染,每个面只能从一侧看到,而从另一侧看不到。

只要是顺时针具体点的先后顺序不同是没有影响的,如a-b-c=b-c-a=c-a-b,而c-b-a就是相反的。

如果在写代码时看不到三角面,那么就查看反面是否显示。交换3个点中的任意2个序号的顺序,即可将三角面反向。

准备好顶点列表和顶点序列表后,就可以创建Mesh对象了。Mesh对象的几个属性都是数组类型,因此需要用List.ToArray方法将列表转为数组。最后将Mesh对象赋予Mesh FIlter组件即可。

按照上述步骤操作并运行,就会得到一个半透明的绿色扇面模型。而且绿色范围被障碍物阻挡时仍能正确显示范围。

更多推荐

SOME/IP

介绍SOME/IP是一种汽车中间件解决方案,可用于控制消息。它从一开始就被设计为完美地适应不同尺寸和不同操作系统的设备。这包括小型设备,如相机、AUTOSAR设备,以及头戴设备或远程通信设备。它还确保SOME/IP支持信息娱乐域以及车辆中其他域的功能,从而允许SOME/IP用于大多数替换场景以及更传统的CAN场景。SO

4G版本云音响设置教程腾讯云平台版本

文章目录4G本云音响设置教程介绍一、申请设备三元素1.腾讯云物联网平台2.创建产品3.设置产品参数4.添加设备5.获取三元素二、设置设备三元素1.打开MQTTConfigTools2.计算MQTT参数3.使用USB连接设备4.设置参数三、腾讯云物联网套件协议使用说明1.推送协议信息2.topic规则说明3.播放协议说明

逻辑漏洞挖掘之XSS漏洞原理分析及实战演练 | 京东物流技术团队

一、前言2月份的1.2亿条用户地址信息泄露再次给各大公司敲响了警钟,数据安全的重要性愈加凸显,这也更加坚定了我们推行安全测试常态化的决心。随着测试组安全测试常态化的推进,有更多的同事对逻辑漏洞产生了兴趣,本系列文章旨在揭秘逻辑漏洞的范围、原理及预防措施,逐步提升大家的安全意识。作为开篇第一章,本文选取了广为熟知的XSS

vue3 封装公共弹窗函数

前言:博主封装了一个公共弹窗函数接收四个参数,(title:弹窗标题,ContentComponent:弹窗中显示的组件内容,opt:接收弹窗本身的属性和props,beforeSure:点击确定做的操作(请求后端接口))封装的公共函数:import{defineComponent,h,ref,getCurrentIn

提升预算管控精度,助力保险资管协会财务管理数字化转型

数字化转型是当前中国经济社会发展的重要趋势和根本方向。中国保险资产管理业协会(以下称“协会”)是专门履行保险资产管理自律职能的全国性金融自律组织。过去几年,协会一直在积极探索应用信息化手段,加强预算管理。近期,协会与百望云合作,重构了预算项目,整合了核算、报销、OA系统,通过智能管票、系统控制,搭建全流程自动化、智能化

为什么我们总是被赶着走

最近发生了一些事情,让shigen不禁的思考:为什么我们总是被各种事情赶着走。一第一件事情就是工作上的任务,接触的是一个老系统ERP,听说是2018年就在线上运行的,现在出现问题了,需要我去修改一下。在这里,我需要记录一下技术背景:ERP系统背景后端采用的是jfinal框架,让我觉得很奇葩的地方有:接受前端的参数采用的

HDFS的Shell操作

1、进程启停管理1.1、一键启停脚本HadoopHDFS组件内置了HDFS集群的一键启停脚本。1.1.1、一键启动HDFS集群$HADOOP_HOME/sbin/start-dfs.sh,一键启动HDFS集群start-dfs.sh执行原理:在执行此脚本的机器上,启动SecondaryNameNode。读取core-s

GIT常用命令

GIT常用命令1、版本控制!什么是版本控制版本迭代,新的版本!版本管理器版本控制(Revisioncontrol)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。实现跨区域多人协同开发追踪和记载一个或者多个文件的历史记录组织和保护你的源代码

聊聊编程是什么

前言前言不看没关系,不影响。半夜睡不着,想写点啥,浅聊下我理解的编程的,我认为编程就是解决问题,就像互联网是依附于实体业,是处理解决实际问题的。刚学编程的时候总是很恐慌的,天赋不够,我这么认为的原因,一是当时流行一种说法叫不是热爱编程的是坚持不下去或者没什么发展的,后来发现是一些阴谋家为了减少竞争的言论;二是确实遇上了

Axios笔记

1、Axios介绍Axios基于promise网络请求库,作用于node.js和浏览器中(即同一套代码可以运行在node.js和浏览器中),在服务器中他使用原生node.jshttp,在浏览器端则使用XMLHttpRequest。特性:(1)、支持PromiseAPI(2)、拦截请求和响应、转换请求和响应数据、取消请求

Java手写启发式搜索算法和启发式搜索算法应用拓展案例

Java手写启发式搜索算法和启发式搜索算法应用拓展案例1.算法思维导图以下是使用Mermanid代码表示的启发式搜索算法的实现原理:#mermaid-svg-3nox60fbtHCDRqqD{font-family:"trebuchetms",verdana,arial,sans-serif;font-size:16p

热文推荐