文章写于2016-1-31,后有修改。

本文为本人原创,转载请注明。

以下为正文

……………………………………………………………………………………………………………………………………

游戏AI常常分为三大部分——感知层、分析层(包含记忆层)、决策层。

在早期的游戏中,AI只有分析层和决策层,虽然效果也是不错的,但是缺乏了真实感,直到《神偷》在感知层做出了巨大的突破后,游戏才逐渐变得真实有趣。

其中最有意思的是敌人视野的感知,比如说《盟军敢死队》。(童年回忆)

本文目的在于用Unity来实现一个观测敌人的炮台,做出类似盟军敢死队中敌人视野范围的效果(见下图):

视野范围感知的基本原理:

在Unity中,用一个Sphere Collider与角度差的计算来模拟敌人的视角。

当敌人进入Sphere Collider中后,进行角度差计算,如果目标与自己的角度差小于自己的视角,并且朝目标发射一条射线没有其他物体阻挡时,判断为看见敌人。完成后的场景图如下:

难点是射线碰撞的运用:

RaycastHit hitInfo;
if(Physics.Raycast(this.transform.position+Vector3.up,vec,out hitInfo,20)){GameObject gameObj = hitInfo.collider.gameObject;if(gameObj.tag == "Player"){//相关操作}
}//若射线与物体产生碰撞,则整个表达式为true,否则为false
//起点为this.transform.position+Vector3.up
//方向为vec,这里的vec是指向目标的向量
//out hitInfo碰撞如果返回true,hitInfo将包含碰到器碰撞的更多信息。
//20为射线的长度
//注意:最后还有一个LayerMask参数未写进去,用于只选定Layermask层内的碰撞器,其它层内碰撞器忽略。

然后是敌人(炮台)的状态机设计图:

这里要注意的是,敌人有两种模式,正常模式和警戒模式。

警戒模式中,敌人的视野范围和视角都会变得更大,同时视角灯光会呈现黄色。

警戒模式在进入攻击模式时开启,再次回到正常模式时才会关闭。

由以上的情况,C#代码分为几大板块:

①角度差计算:计算目标与自身的角度差。

②目标是否可见的判断:判断目标是否在自身视野范围内。

③状态机:每种状态的切换以及相应要做的动作。

④目标进入与离开感知范围:捕捉目标GameObject或者释放目标GameObject。

接下来是完整的C#代码:(当时技术较菜,直接用 switch 来写了)

using UnityEngine;
using System.Collections;public class Enemy01Rewrite : MonoBehaviour
{//声音AudioSource m_audio;public AudioClip m_Shot;public AudioClip m_EnterAlert;public AudioClip m_ExitAlert;public AudioClip m_ExitWait;//功能GameObject Target = null;float DeltaShotTime = 0.5f;//攻击间隔float AngleSpeed = 90f;//转动速度float PerceiveRadius = 8f;//感知范围float AlertTime = 10f;//警报时间float WaitTime = 4f;//失去目标等待时间float LightRange = 0f;//灯光距离(在Start()中根据SpotLight参数设定)float LightIntensity = 0f;//灯光亮度(在Start()中根据SpotLight参数设定)float ViewAngle = 60f;//视野角度bool isInView = false;//是否在扇形视野范围内bool isRayCast = false;//是否通过射线能看到bool isAlert = false;//是否警报bool isWait = false;//是否等待float TargetAngle = 0;float SelfAngle = 0;float AngleDiff = 0;float MinAngle = 3f;public GameObject m_Light;public GameObject m_Bullet;public GameObject m_Launcher;int state = 0;float time_shot = 0;float time_alert = 0;float time_wait = 0;/////角度差计算函数///void CalculateAngle(){if (Target != null){float AtanAngle = (Mathf.Atan((Target.transform.position.z - this.transform.position.z) /(Target.transform.position.x - this.transform.position.x))* 180.0f / 3.14159f);//Debug.Log (this.transform.rotation.eulerAngles+"   "+AtanAngle);//1象限角度转换if ((Target.transform.position.z - this.transform.position.z) > 0&&(Target.transform.position.x - this.transform.position.x) > 0){TargetAngle = 90f - AtanAngle;//Debug.Log ("象限1 "+TargetAngle);}//2象限角度转换if ((Target.transform.position.z - this.transform.position.z) <= 0&&(Target.transform.position.x - this.transform.position.x) > 0){TargetAngle = 90f + -AtanAngle;//Debug.Log ("象限2 "+TargetAngle);}//3象限角度转换if ((Target.transform.position.z - this.transform.position.z) <= 0&&(Target.transform.position.x - this.transform.position.x) <= 0){TargetAngle = 90f - AtanAngle + 180f;//Debug.Log ("象限3 "+TargetAngle);}//4象限角度转换if ((Target.transform.position.z - this.transform.position.z) > 0&&(Target.transform.position.x - this.transform.position.x) <= 0){TargetAngle = 270f + -AtanAngle;//Debug.Log ("象限4 "+TargetAngle);}//调整TargetAnglefloat OriginTargetAngle = TargetAngle;if (Mathf.Abs(TargetAngle + 360 - this.transform.rotation.eulerAngles.y)<Mathf.Abs(TargetAngle - this.transform.rotation.eulerAngles.y)){TargetAngle += 360f;}if (Mathf.Abs(TargetAngle - 360 - this.transform.rotation.eulerAngles.y)<Mathf.Abs(TargetAngle - this.transform.rotation.eulerAngles.y)){TargetAngle -= 360f;}//输出角度差AngleDiff = Mathf.Abs(TargetAngle - this.transform.rotation.eulerAngles.y);Debug.Log("角度差:" + TargetAngle + "(" + OriginTargetAngle + ")-" + this.transform.rotation.eulerAngles.y + "=" + AngleDiff);}}////感知视野的相关计算 判断isRayCast和isInView//void JudgeView(){//感知角度相关计算if (Target != null){//指向玩家的向量计算Vector3 vec = new Vector3(Target.transform.position.x - this.transform.position.x,0f,Target.transform.position.z - this.transform.position.z);//射线碰撞判断RaycastHit hitInfo;if (Physics.Raycast(this.transform.position + Vector3.up, vec, outhitInfo, 20)){GameObject gameObj = hitInfo.collider.gameObject;//Debug.Log("Object name is " + gameObj.name);if (gameObj.tag == "Player")//当射线碰撞目标为boot类型的物品 ,执行拾取操作{//Debug.Log("Seen!");isRayCast = true;}else{isRayCast = false;}}//画出碰撞线Debug.DrawLine(this.transform.position, hitInfo.point, Color.red, 1);//视野中的射线碰撞判断结束//视野范围判断//物体在范围角度内,警戒模式下范围为原来1.5倍if (AngleDiff * 2 <(isAlert ? ViewAngle * 1.5f : ViewAngle)){isInView = true;}else{isInView = false;}//Debug.Log ("角度差 "+AngleDiff);}}// Use this for initializationvoid Start(){LightRange = m_Light.GetComponent<Light>().range;LightIntensity = m_Light.GetComponent<Light>().intensity;this.GetComponent<SphereCollider>().radius = PerceiveRadius;m_audio = this.GetComponent<AudioSource>();}// Update is called once per framevoid Update(){//Debug.Log("state:" + state + " time_alert:" + time_alert);if (isAlert){//警戒模式m_Light.GetComponent<Light>().range = LightRange * 2f;m_Light.GetComponent<Light>().color = new Color(1f, 1f, 0);m_Light.GetComponent<Light>().intensity = LightIntensity * 2f;m_Light.GetComponent<Light>().spotAngle = ViewAngle * 1.5f;this.GetComponent<SphereCollider>().radius = PerceiveRadius * 1.5f;}else{//正常模式m_Light.GetComponent<Light>().range = LightRange;m_Light.GetComponent<Light>().color = new Color(1f, 1f, 1f);m_Light.GetComponent<Light>().intensity = LightIntensity;m_Light.GetComponent<Light>().spotAngle = ViewAngle;this.GetComponent<SphereCollider>().radius = PerceiveRadius;}//计算角度差CalculateAngle();//感知视野判断(判断isRayCast与isInView)JudgeView();//状态机 共4个状态switch (state){//状态1:正常模式——旋转扫视case 0:this.transform.Rotate(new Vector3(0, 1f, 0f), Time.deltaTime * AngleSpeed);//发现敌人 进入攻击模式if (isRayCast && isInView){if (!isAlert) m_audio.PlayOneShot(m_EnterAlert);isAlert = true;time_wait = 0;state = 1;}break;//状态2:攻击模式——朝目标发射子弹case 1://根据角度跟踪Targetif (this.transform.rotation.eulerAngles.y < TargetAngle){if (AngleDiff >= MinAngle){//顺时针旋转this.transform.Rotate(new Vector3(0, 1f, 0f), Time.deltaTime * AngleSpeed);}}else{if (AngleDiff >= MinAngle){//逆时针旋转this.transform.Rotate(new Vector3(0, -1f, 0f), Time.deltaTime * AngleSpeed);}}//子弹发射计时 子弹发射间隔time_shot += Time.deltaTime * 1;if (time_shot >= DeltaShotTime){Instantiate(m_Bullet, m_Launcher.transform.position, m_Launcher.transform.rotation);time_shot = 0;m_audio.PlayOneShot(m_Shot);}//敌人离开视野 进入等待模式if (!isRayCast){isWait = true;time_wait = 0;state = 2;}break;//状态3:敌人离开可见区域——等待case 2://进入旋转警戒模式 计时time_wait += Time.deltaTime * 1;if (time_wait >= WaitTime){m_audio.PlayOneShot(m_ExitWait);time_alert = 0;isWait = false;state = 3;}if (isRayCast && isInView){if (!isAlert) m_audio.PlayOneShot(m_EnterAlert);isAlert = true;time_wait = 0;state = 1;}break;//状态4:警戒模式——旋转扫视case 3://回到正常模式 计时time_alert += Time.deltaTime * 1;if (time_alert >= AlertTime){m_audio.PlayOneShot(m_ExitAlert);time_alert = 0;isAlert = false;state = 0;}//发现敌人 进入攻击模式if (isRayCast && isInView){if (!isAlert) m_audio.PlayOneShot(m_EnterAlert);isAlert = true;time_wait = 0;state = 1;}this.transform.Rotate(new Vector3(0, 1f, 0f), Time.deltaTime * AngleSpeed);break;default:break;}}//玩家进入感知层void OnTriggerEnter(Collider other){if (other.gameObject.tag == "Player"){Target = other.gameObject;//提前计算角度差CalculateAngle();time_shot = 0;}}//玩家进入视野void OnTriggerStay(Collider other){if (other.gameObject.tag == "Player"){if (Target == null){Target = other.gameObject;}}}//玩家离开感知层void OnTriggerExit(Collider other){if (other.gameObject.tag == "Player"){Target = null;isInView = false;isRayCast = false;}}
}

Unity杂谈:敌人视野感知的实现相关推荐

  1. unity将敌人空间位置显示在屏幕坐标中(可用于空战游戏)

    作为皇牌空战系列新粉,再加上最近开始尝试制作联网游戏,于是手痒痒开始捣鼓空战网游. 在这之中呢遇到一个问题,就是如何将敌方单位锁定效果做出来.(就像下图那样,显示当前目标TU-95及其距离信息) 虽然 ...

  2. Unity狙击枪的视野放大和缩小

    直接上代码 using UnityEngine; using System.Collections; /*任务:控制摄像机视野的放大和缩小,望远镜功能* 原理:放大事业:就是减小摄像机的垂直视野范围( ...

  3. 【Unity】可视化视野

    用GL实现,性能要好一些 using System.Collections; using System.Collections.Generic; using UnityEngine;public cl ...

  4. Unity杂谈(二)字体缺字?加一个!

    在做的项目中所使用的字体是华康标题宋w9.好歹不歹夏侯惇中的"惇"字在这个字体中不存在.一般情况下是需要换另外一种字体的,但是还不是其他办法么.. 1.下载并安装 FontCrea ...

  5. 关于Untiy编写敌人可视化视野

    关于Untiy编写敌人可视化视野 最近遇到了一个编写可视化视野范围的问题,写出来了以后记录一下可视化视野的方法,因为用到的方法较多,我又想把原理讲解明白,所以本文的篇幅较长,如果你是没有耐心的话,建议 ...

  6. unity开发之游戏视野剔除显示FieldOfViewRenderer

    游戏视野剔除显示FieldOfViewRenderer 中文 简介 主要功能 图片预览 部分代码 图形绘制器 DrawHelper 基础扇形类 定义shader美观优化 使用步骤: 下载链接 更新: ...

  7. Unity3d学习日记 (2)C#脚本编写优化以及全自动敌人脚本实现实战

    需求分析 需要编写脚本控制敌人的行为,完成包括巡逻.射击.追击.逃跑四个功能 解决思路 考虑机器人的行为受一个决策树的影响,编写有限状态自动机形成决策树,通过条件分支语句来对机器人的行为进行限制和控制 ...

  8. AI设计:如何给动作游戏的敌人设计人工智能?

    游戏中的AI可以简单的理解为计算机控制的智能角色,这些智能角色能够通过周遭环境或者事件的变化进行逻辑判断,从而同玩家进行产生特定的行为交互. 组成AI的三个基本元素: AI的基本逻辑,AI的基本能力, ...

  9. unity实践————第一人称射击游戏

    一. 环境 地图资源,包含了全地图以及灯光.天空盒子. 扩展部分. 不可穿透的部分需要在bake时设置为static. 二. 设置 1. 相机 1> 主相机 挂载控制视角的c#组件. tag为m ...

最新文章

  1. int和Integer区别
  2. 手机端html5 面试,今日头条 张祖俭 - H5动画在移动平台上的性能优化实践
  3. java第三次实验代码_JAVA第三次实验
  4. JAVA学习-JAVA实现客户端与服务器端的TCP通信
  5. Leetcode 392.判断子序列
  6. 946. Validate Stack Sequences验证栈序列
  7. 如何注册域名邮箱?个人域名邮箱怎么弄?域名邮箱登录入口?
  8. 第三方平台可以通过微信公众平台素材管理接口实现同步了
  9. 2021-04-17 ffmpeg视频合并报错;视频合并中间添加空白
  10. 台式电脑打不开计算机c盘,电脑打不开显示C盘损坏怎么办
  11. QQ邮箱模拟登陆(2022.9.12)
  12. ,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microso ft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies
  13. rpm mysql 忘记密码_centos7 yum安装mysql5.7并在root密码忘记的情况下重设密码
  14. 【3d地图】vue3.0中使用echarts geo3D
  15. oracle xe 11g下载和安装教程
  16. C语言常见的编程题(1)
  17. oracle应付创建会计科目,月结AP创建会计分录失败问题
  18. 31省份全体居民、城镇居民、农村居民人均消费支出(1990-2022)
  19. if 我是前端团队 Leader,怎么制定前端协作规范?
  20. RS485 MODBUS转PROFINET网关案例丨汇川变频器接入到1200 PROFINET

热门文章

  1. element 手机适配_手机端rem适配方案_蓝戒的博客
  2. linux下vim设置
  3. opencv播放录制的视频,速度特别快
  4. Linux系统she-bang介绍
  5. IT忍者神龟之Java网络爬虫回顾
  6. js正则匹配某个汉字
  7. linux系统读不到硬盘,window可以
  8. Dubbo服务注册与发现的流程?
  9. 计算机网络七层模型OSI
  10. 7. webpack 生产模式