Unity3D学习(五):实现一个简单的视觉感知

前言

在很多第一人称或者第三人称射击游戏的单人模式中,玩家的乐趣往往来源于和各式各样的AI敌人的战斗。而战斗的爆发很多时候是因为这些AI在“看见”玩家后就会立即做出反应,比如开火、呼叫同伴、躲藏或者逃跑等。

所以这些AI到底是如何探测,或者说”看到“玩家位置的?

别人的例子

参考了知乎 给猫看的游戏AI实战(二)视觉感知初步 这篇文章。

这篇文章中,原作者让玩家站在敌人的角度来探测目标,它通过向正前方一定扇形区域发射一堆射线来探测目标的位置,如下图:

 这种方法虽然实现起来比较简单,但它主要有两个弊端:

1.同一时间内发射大量的射线,对游戏本身的优化来说很不好,容易造成卡顿。

2.如果要探测的物体比较小,甚至比两条射线之间的间隔还小,那么射线是无法探测到这个物体的。

另一种解决思路

原文的评论中,有人提到可以基于探测者自身构建一个球体来探测周围的物体。

因此我们可以用Unity自带的Sphere触发器或者Physics里的OverLaps来构建一个球体探测区域,如下图:

 这里我用的是Overlaps,代码如下:

玩家,即探测者

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Player : MonoBehaviour {public float moveSpeed;    //移动速度public float EyeViewDistance; //视野距离public float viewAngle = 120f; //视野角度private Rigidbody rb;private Collider[] SpottedEnemies; //附近的敌人// Use this for initializationvoid Start () {rb = GetComponent<Rigidbody>();}private void FixedUpdate(){DetectEnemy();}// Update is called once per framevoid Update (){//AutoMove();
        MoveAndTurn();Debug.DrawLine(transform.position, transform.forward * 100, Color.red); //红色射线,面对的方向
    }void AutoMove()  //向面对的方向自动移动
    {transform.position += transform.forward * moveSpeed * Time.deltaTime;}void MoveAndTurn()  //玩家移动
    {Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hitInfo = new RaycastHit();//shoot a ray from cam to mouse position which is only detected by gameobject with "Plane" layer.Physics.Raycast(ray, out hitInfo, 100, LayerMask.GetMask("Plane"));if (hitInfo.collider != null){transform.LookAt(new Vector3(hitInfo.point.x, transform.position.y, hitInfo.point.z));}rb.velocity = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized * moveSpeed;}void DetectEnemy()  //探测敌人
    {//OverlapSphere内的敌人SpottedEnemies = Physics.OverlapSphere(transform.position, EyeViewDistance, LayerMask.GetMask("Enemy"));for(int i = 0;i < SpottedEnemies.Length;i++) //检测每一个敌人是否在视野区中
        {Vector3 EnemyPosition = SpottedEnemies[i].transform.position; //敌人的位置//Debug.Log(transform.forward + " 面对的方向");//Debug.Log("夹角为:" + Vector3.Angle(transform.forward, EnemyPosition - transform.position));
Debug.DrawRay(transform.position, EnemyPosition - transform.position, Color.yellow); //玩家位置到敌人位置的向量if (Vector3.Angle(transform.forward, EnemyPosition - transform.position) <= viewAngle/2)  //这个敌人是否在视野内
            {//如果在视野内RaycastHit info = new RaycastHit();int layermask = LayerMask.GetMask("Enemy", "Obstacles"); //指定射线碰撞的对象Physics.Raycast(transform.position, EnemyPosition - transform.position, out info,EyeViewDistance,layermask); //向敌人位置发射射线
                Debug.Log(info.collider.gameObject.name);if(info.collider == SpottedEnemies[i]) //如果途中无其他障碍物,那么射线就会碰撞到敌人
                {DiscoveredEnemy(SpottedEnemies[i]);}}}}void DiscoveredEnemy(Collider Enemy) //发现敌人
    {//Do somethingDebug.Log("发现敌军:" + Enemy.gameObject.name);Enemy.GetComponent<Enemy>().BeDiscovered();}
}

SpottedEnemies是一个Collider数组,我用它来保存这一帧当中处于OverlapSphere形成的球体区域的所有敌人对象,(LayerMask可以让Overlaps的球体只和指定layer的对象发生交互)。然后计算玩家面对的方向和探测到的目标方向的夹角Vector3.Angle(transform.forward, EnemyPosition - transform.position),如下图:

forwar向量代表玩家面朝的方向,v1代表探测到的物体相对于玩家位置的方向,红色扇形区域代表玩家的视野范围。那么计算这两个向量的夹角,然后判断下这个夹角是否小于扇形区夹角的一半(即探测目标是否在玩家视野内)就行了。如果夹角小于视野夹角的一半,那么我们再向目标位置发射一根射线,然后看下射线碰撞到的物体是否是目标对象就行,因为如果玩家和目标之间有障碍物的话,那么射线是会被障碍物挡下来的(也就是说玩家的"视野"被障碍物"遮挡"了)。

Enemy,被探测的目标

这里设定被探测的目标脱离玩家视野一定时间后重新进入隐形状态。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Enemy : MonoBehaviour {public float HideCoolDown = 0.3f; //隐藏自己的冷却时间public float AppearTime;   //最后一次被发现的时刻private MeshRenderer mr;// Use this for initializationvoid Awake(){mr = GetComponent<MeshRenderer>();}private void Start(){mr.enabled = false;}// Update is called once per framevoid Update (){if (Time.time - AppearTime < HideCoolDown) return;if (mr.enabled)mr.enabled = false;}public void BeDiscovered() //被发现了
    {mr.enabled = true;AppearTime = Time.time;}
}

最后实现的效果图如下:

这种方法就不用发射大量的射线,而且只会对进入球形探测区域且处于视野范围内的物体发射射线,并且也避免了小物体无法被探测的bug。

 

参考资料

知乎:U3d开发中大部分事件都是用数学进行计算判定的吗? 最高票答案  作者:Meta42

猫看的游戏AI实战(二)视觉感知初步    作者:马遥

转载于:https://www.cnblogs.com/0kk470/p/7667655.html

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

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

相关文章

BeanFactory中String FACTORY_BEAN_PREFIX = ““;解析

此接口定义了Bean的一些基本信息判断和获取bean比如 isSingleton isTypeMatch 还有一个需要注意的地方&#xff0c;看这个常量: String FACTORY_BEAN_PREFIX "&"; 如果我们在获取Bean的时候&#xff0c;使用&则获取的是FactoryBean本身对象&#xff0c;否则…

.NET Core简介

转载于:https://www.cnblogs.com/wxc-kingsley/p/7660878.html

spring中DefaultListableBeanFactory是bean加载的核心部分,是spring注册和加载bean的默认实现方式

DefaultListableBeanFactory介绍 BeanFactory是个Factory&#xff0c;也就是IOC容器或对象工厂&#xff0c;而DefaultListableBeanFactory是Bean工厂的一个默认实现&#xff0c;DefaultListableBeanFactory提供了原始的BeanFactory的功能&#xff0c;如&#xff1a;对外提供ge…

使用MyBatis集成阿里巴巴druid连接池(不使用spring)

在工作中发现mybatis默认的连接池POOLED&#xff0c;运行时间长了会报莫名其妙的连接失败错误。因此采用阿里巴巴的Druid数据源&#xff08;码云链接 &#xff0c;中文文档链接&#xff09;。 mybatis更多数据源参考博客链接 。 1 环境 eclipse mars2 maven3.3.9 mysql5.7 2 …

docker search从Docker Hub查找(搜索)镜像

镜像搜索 docker search 名称 列表说明&#xff1a;NAME&#xff1a;镜像名(镜像仓库源的名称)DESCRIPTION&#xff1a;对该镜像的描述STARS&#xff1a;类似 Github 里面的 star&#xff0c;表示点赞、喜欢的意思OFFICIAL&#xff1a;是否 docker 官方发布AUTOMATED&#xff1…

mysql求差集

mysql怎么求差集? mysql如何查询两个字段数不同的表中数据不一致的记录 一般可用NOT EXISTS&#xff08;非存在子句&#xff09;或 LEFT JOIN左&#xff08;右&#xff09;连接后所产生空字段值来筛选两表的差集 classinfo表 student表 1、NOT EXISTS not exists在比对字段…

html5表单实现简单计算器

html5表单实现简单计算器 <!DOCTYPE html><html><head> <title>this is a html page</title></head><body> <form οninput"res.value n1.valueAsNumber * n2.valueAsNumber"> <input type"number" …

python学习笔记(15)循环设计

python学习笔记&#xff08;15&#xff09;循环设计 原链&#xff1a;http://www.cnblogs.com/vamei/archive/2012/07/09/2582435.html 注意&#xff1a;zip()在python2 3里面不一致 #第15讲 循环设计#循环在前面是有学习的&#xff0c;简单的循环for i in range(10):print (i*…

docker search 镜像关键词

查找镜像 docker search 镜像关键词 比如我要搜索nginx docker search nginx 参数说明&#xff1a; NAME: 镜像仓库源的名称 DESCRIPTION: 镜像的描述 OFFICIAL: 是否 docker 官方发布 stars: 类似 Github 里面的 star&#xff0c;表示点赞、喜欢的意思。 AUTOMATED: …

005-JQuery之CSS

CSS位置尺寸CSS css(name|pro|[,val|fn]) &#xff1a;访问匹配元素的样式属性示例&#xff1a; 1 // 获取color样式属性的值 2 $(p).css("color"); 3 // 将所有段落的字体颜色设为红色并且背景为蓝色 4 $("p").css({ color: "#ff0011", backgro…

扩展巴科斯范式

2019独角兽企业重金招聘Python工程师标准>>> 扩展巴科斯范式 维基百科&#xff0c;自由的百科全书 扩展巴科斯-瑙尔范式(EBNF)是表达作为描述计算机编程语言和形式语言的正规方式的上下文无关文法的元语法符号表示法。它是基本巴科斯范式(BNF)元语法符号表示法的一种…

20100519 学习记录:asp CreateFolder/上传附件

新增一个上传附件的功能。在网上找了一下&#xff0c;基本都是在化境HTTP上传程序基础上改的&#xff0c;灰常感谢这个源代码的开发者&#xff0c;深深鞠躬。不过这个代码要求在上传图片时&#xff0c;输入的文件夹必须是已存在的文件夹&#xff0c;不然就会出错&#xff0c;于…

删除容器docker rm和强制删除容器docker rm -f

显示所有容器 docker ps -a删除容器&#xff0c;运行中的容器不行 docker rm 容器id强制删除容器&#xff0c;运行中的容器也是可以删除的 docker rm -f 容器iddocker rm 删除运行中的容器会报错 Error response from daemon: You cannot remove a running container 8c03b6c4d…

Exchange2007 从零到入门(2)---收件人管理

Exchange2007 从零到入门---收件人管理在上一次的文章中讲到了如何为企业部署安装exchange2007服务器。但是部署完成的服务器还并不能使用。因为现在的服务器中还没有用户。在exchange中&#xff0c;用户被称作为“收件人”&#xff0c;exchange中用户管理的主要任务就是对“收…

Error response from daemon: You cannot remove a running container 8c03b6c4da31d435bcaf8c4a6c59938c3b

Error response from daemon: You cannot remove a running container 8c03b6c4da31d435bcaf8c4a6c59938c3b668eaff36903b13583329034508135. Stop the container before attempting removal or force remove原因&#xff1a;该容器正在运行中&#xff0c;不能直接删除 方法一…

Android零基础入门第81节:Activity数据传递

在Android开发中&#xff0c;经常要在Activity之间传递数据。前面也学习了Activity和Intent相关基础&#xff0c;接下来一起来学习Activity的数据传递。 一、简介 通过前面的学习知道&#xff0c;Intent可以用来开启Activity&#xff0c;同样它也可以用来在Activity之间传递数据…

(转)java内部类详解

本文转自http://www.cnblogs.com/dolphin0520/p/3811445.html&#xff0c;谢谢作者 说起内部类这个词&#xff0c;想必很多人都不陌生&#xff0c;但是又会觉得不熟悉。原因是平时编写代码时可能用到的场景不多&#xff0c;用得最多的是在有事件监听的情况下&#xff0c;并且即…