前言

在很多第一人称或者第三人称射击游戏的单人模式中,玩家的乐趣往往来源于和各式各样的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

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

  1. java servlet例子_Servlet学习教程(三)---- 一个简单的Servlet例子

    我们用个最简单的Servlet例子来解说一下Servlet简单配置以及Servlet类实现类的写法. 第一,我们新建一个Dynamic Web Project,起名Servlet 点击NEXT,设置D ...

  2. Flutter学习笔记 —— 完成一个简单的新闻展示页

    Flutter学习笔记 -- 完成一个简单的新闻展示页 前言 思路分析 案例代码 结束语 上图 前言 刚学Flutter不久,今天我们来看看如何使用 Container & ListView实 ...

  3. golang游戏开发学习笔记-开发一个简单的2D游戏(基础篇)

    此文写在golang游戏开发学习笔记-创建一个能自由探索的3D世界之后,感兴趣可以先去那篇文章了解一些基础知识,在这篇文章里我们要创建一个简单的2D游戏场景以及配套的人物,并实现人物运动和碰撞检测功能 ...

  4. java学习之—实现一个简单的ArrayList

    package thread1;/*** 实现一个简单的ArrayList** @Title: uminton*/ public class SimpleArrayList<T> {//数 ...

  5. XPO学习(6)----实现一个简单的留言薄(ASP.NET)

    l留言簿是网站上很常见的一个功能.一个访问者可以通过这个留言簿留下姓名,EMAIL,消息标题及内容.内容显示用ASPxNewscontrol控件.类似界面如下: 步骤: 1.界面设计 2.定义一个持久 ...

  6. 【Java学习】从一个简单的HelloWorld项目中入门maven

    创建一个maven项目 这里推荐官方文档:maven官方文档 [注]此篇文章也是笔者学习笔记,如有错误,请见谅. [注]我把Goal翻译成命令.比如Plugin Goal: ps:我他喵的写了一大半的 ...

  7. Windows程序设计学习笔记(1):一个简单的windows程序

    <Windows程序设计>(第五版)(美Charles Petzold著) 1 #include<windows.h> 2 3 LRESULT CALLBACK WndProc ...

  8. 学习笔记:latex.一个简单的个人实验报告模板及部分语法说明

    首先建立一个  my_report.cls  的配置文件.(以后可能会随着使用进行一些修改和补充) \ProvidesClass{my_report}%------------------------ ...

  9. 老程序员教女儿学习java从一个简单的猜数游戏开始

    为了教会女儿学习java花了三天的下班时间编写了一个java猜数字游戏,为了让她感兴趣,同时能理解编程的思路,可惜目标没有达成,现在共享给大家学习. package test; import java ...

最新文章

  1. python 批量下载文件
  2. python ==》 内置函数
  3. HDU-1878 欧拉回路 判定是否存在欧拉回路
  4. offsetTop,offsetHeight,scrollHeight,scrollTop的区别
  5. Linux Socket通信 C/S模型
  6. oracle12c xtts迁移,记录一次XTTS迁移碰到的问题
  7. linux系统mount命令挂载windows系统共享文件夹
  8. java实现阴历、阳历互相转换
  9. 苹果手机装android应用程序,Android/iOS手机安装HP打印机的详细方法和操作步骤
  10. Win10 打开图片,提示文件系统错误(-2147219196)
  11. 你是万能的数据分析师?干什么呢?
  12. Python:打印九九乘法口诀表
  13. 6种优秀的浏览器兼容性测试工具
  14. 苦尽甘来 一个月学通JavaWeb(三十五 数据库)
  15. pgsql 一键修改PGSQL表名、字段名为小写
  16. 循环语句—do…while循环
  17. 蓝桥杯 初赛题目 高斯日记
  18. 再次学习MOOC《Geogebra的教学应用》的过程记录与体会(1)
  19. VRTK4 入门指南
  20. 使用C#解决二元一次方程问题

热门文章

  1. iOS tabview 适配问题
  2. Hive的安装和配置
  3. asp.net MVC 权限设计(续)
  4. 对网络数据包进行嗅探
  5. c++ --- 字符串中的标点符号
  6. leetcode-551-Student Attendance Record I(判断是否出现连续几个相同字符)
  7. Spring 框架的JDBC模板技术
  8. python抓取网站URL小工具
  9. ubuntu下搭载LNMP环境,解决 fpm监听失败
  10. ASP.NET控件开发基础5