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

Mysql数据库的瓶颈处理一点建议

Mysql数据库的瓶颈处理一点建议我们在使用Mysql数据库是常见的两个瓶颈是CPU和I/O的瓶颈&#xff0c;CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候。磁盘I/O瓶颈的出现呢发生在装入数据远大于内存容量的时候&#xff0c;如果应用分布在网络上&#xff0c;那么…

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 …

捣鼓了一个月的SharePoint

从7月底到现在&#xff0c;对SharePoint有了比较大概的理解。除了文档管理、搜索功能&#xff0c;它的整合能力很强。由于我们缺少人手、理解有限&#xff0c;有没有这方面的专家愿意兼职有关SharePoint开发&#xff1f;请联系 towangjinshenghotmail.com私事&#xff1a;由于我…

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

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

微信js-sdk使用

var getAppidfunction(){$.ajax({url: "http://bos.foreverlynn.com/weixin/getWXUrl",dataType: "jsonp",data: {url: window.location.href},success: function (res) {if (res.code ! 200) {}wx.config({debug: false, // 开启调试模式,调用的所有api的…

***清除工具的进化

想就***清除工具的革新换代说说&#xff0c;算抛砖引玉吧&#xff0c;对付***这些东西&#xff0c;不会有穷尽的那一天&#xff0c;清除工具革新是围绕病毒***更新展开的&#xff0c;未来肯定还会有更好地清 除思路。一切为了用户的电脑更安全&#xff0c;让清除***的过程变得更…

Codeforces Round #441 D. Sorting the Coins(模拟)

http://codeforces.com/contest/876/problem/D 题意&#xff1a;题意真是难懂&#xff0c;就是给一串序列&#xff0c;第i次操作会在p[x]&#xff08;1<x<i&#xff09;这些位置放上硬币&#xff0c;然后从左到右观察&#xff0c;如果第i个位置有硬币但第i1个位置没有硬币…

Oracle 数据库实例启动关闭过程

-- -->Oracle 数据库实例启动关闭过程 -- /* Oracle数据库实例的启动&#xff0c;严格来说应该是实例的启动&#xff0c;数据库仅仅是在实例启动后进行装载。Oracle数据启动的过程被划分为 几个不同的步骤&#xff0c;在不同的启动过程中&#xff0c;我们可以对其实现不同的…

mysql求差集

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

C#判断字符串是否为数字字符串

在进行C#编程时候&#xff0c;有的时候我们需要判断一个字符串是否是数字字符串&#xff0c;我们可以通过以下两种方法来实现。 【方法一】&#xff1a;使用 try{} catch{} 语句。 我们可以在try语句块中试图将string类型的字符串变量转换为int类型&#xff0c;如果该字符…

SQLPLUS命令使用大全

SQL*PLUS命令的使用大全Oracle的sql*plus是与oracle进行交互的客户端工具。在sql*plus中&#xff0c;可以运行sql*plus命令与sql*plus语句。 我们通常所说的DML、DDL、DCL语句都是sql*plus语句&#xff0c;它们执行完后&#xff0c;都可以保存在一个被称为sql buffer的内存区域…

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" …

[举一反三]使用javascript转换字符串为dom对象(字符串动态创建dom)

前言&#xff1a; 在javascript里面动态创建标准dom对象一般使用&#xff1a; var obj document.createElement(div); 然后再给obj设置一些属性。 但是&#xff0c;在实际使用过程中&#xff0c;有些人可能会想&#xff0c;要是能这样创建标准的dom对象就好了 伪代码&#xff…

propattr区别和用法,以多选框为例

1.比较 相同点 : prop和attr作为jquery的方法都可以获取属性值; 不同点 : (1) 对于HTML元素本身就带有的固有属性&#xff0c;使用prop方法, attr获取checkbox的checked属性时选中的时候可以取到值,值为"checked"但没选中获取值就是undefined。 jq提供新的方法“prop…

20100506网络互联技术实验

请下载附件转载于:https://blog.51cto.com/network0546/310493

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*…