需求分析
需要编写脚本控制敌人的行为,完成包括巡逻、射击、追击、逃跑四个功能
解决思路
考虑机器人的行为受一个决策树的影响,编写有限状态自动机形成决策树,通过条件分支语句来对机器人的行为进行限制和控制。
解决方案
本文的解决方案借鉴了以下博文,侵权删 有限状态自动机介绍以及框架编写方法
了解有限状态自动机
有限状态机,英语:Finite-state machine,FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。它反映从系统开始到现在时刻的输入变化,转移指示状态变更,并且用必须满足来确使转移发生的条件来描述它;动作是在给定时刻要进行的活动的描述。
关于巡逻功能的开发
巡逻功能借助于Unity3D自带的导航系统,利用导航系统代理,来规划机器人的巡逻,具体的流程见文章 Unity导航系统 那么在我的设置中,在地图中设置了4个空物体,利用循环数组来实现机器人在这四个地方一直巡逻,直到触发事件 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class partorl : MonoBehaviour
{
public Transform target;
private Vector3 targetPoint;
private NavMeshAgent agent;
private List<Vector3> dstPoints;
private int nextIdx=0;
private float calcdist = 5f;
void Start()
{
dstPoints = new List<Vector3>();
dstPoints.Add(GameObject.Find("LactionMarkingFlag_F4_Buff").transform.position);
dstPoints.Add(GameObject.Find("LactionMarkingFlag_bule_spawn").transform.position);
dstPoints.Add(GameObject.Find("LactionMarkingFlag_red_spawn").transform.position);
dstPoints.Add(GameObject.Find("LactionMarkingFlag_F5_Buff").transform.position);
agent = this.transform.GetComponent<NavMeshAgent>();
}
void Update()
{
if(Vector3.Distance(this.transform.position,dstPoints[nextIdx])< calcdist)
{
if (nextIdx == dstPoints.Count - 1)
{
nextIdx = 0;
}
else
{
nextIdx++;
}
}
agent.SetDestination(dstPoints[nextIdx]);
}
}
巡逻功能是比较简单的,完成了巡逻,下面来考虑对自动机器人的开火和血条的脚本代码进行重编写 但是需要思考一点是,巡逻功能和以下这几个功能如何配合? 我们想到,巡逻这个是作为entry(入口状态),以下的这几个功能都是只有当AI机器人遇到了我的机器人才有可能发生的,因此 巡逻这个功能的真正作用是作为一个触发器,用来检测判断我的机器人是否遇上了AI机器人,并且需要触发条件函数了
关于自动射击功能的开发
自动射击的功能实现:初步预想为,当遇上敌人并且进入射程的时候,就进入这个状态,这个状态的实现分为两步: 第一步:机器人云台枪口转换位置对准敌人的中心(可以调整误差加减2左右)为开火范围,开火 第二步:做碰撞检测,对AI机器人的弹药量进行调整,以及对玩家机器人进行扣血的判定
关于追击敌人功能的开发
首先追击敌人这个状态需要进行判断,如果自身的血量大于500而且弹药量大于30的时候就判定可以追击,否则遇到敌人就进入逃跑状态 实际上这个功能与自动射击功能应当是具有一个先后关系的,只有追击到了敌人才能开枪,因此追击的状态实现可以分为两步 第一步:在视野中发现敌人(考虑实现),停止普通巡逻状态 第二步:进入警戒状态,根据新的寻路规向敌人逼近
关于逃跑功能的开发
这个功能与追击是对立的,那么触发条件当然也是对立的,自身血量小于50而且弹药量小于30的时候就逃跑,逃跑的路线规划与追击是不同的,逃跑要求尽可能地远离敌人,这就要求需要动态规划逃跑路线了
代码实现
1.关于敌人视野的实现
这里采用了一个带有isTrigger的碰撞体作为敌人视野的检测(也就是跟随镜头的的一个区域),来作为触发器来进行检测,来判断玩家是否进入了视野了 ,由于触发器的知识已经学习过了,因此很快能写出代码
bool isFirst = false;
public float fileldOfView = 100f;
public bool playerInsight = false;
public Vector3 playerLastSight;
private BoxCollider col;
private GameObject player;
void Start()
{
Debug.Log("检测视野的脚本正常运行中");
BoxCollider[] colArr = GetComponents<BoxCollider>();
col = colArr[1];
player = GameObject.FindWithTag("PlayerRobot");
}
private void OnTriggerStay(Collider other)
{
if (other.gameObject == player)
{
playerInsight = false;
Vector3 dir = other.transform.position - transform.position;
float angle = Vector3.Angle(dir,transform.forward);
if(angle < this.fileldOfView*0.5)
{
Debug.Log("进入了机器人视野中");
RaycastHit raycastHit;
if (Physics.Raycast(transform.position + transform.up, dir.normalized, out raycastHit, col.size.z))
{
if(raycastHit.collider.gameObject == player)
{
playerInsight = true;
playerLastSight = player.transform.position;
Debug.Log("没有障碍物遮挡");
}
}
}
}
}
private void OnTriggerExit(Collider other)
{
if (isFirst == false)
{
isFirst = true;
return;
}
if (other.gameObject == player)
{
playerInsight = false;
Debug.Log("玩家离开了视野");
}
}
void Update()
{
}
2.在敌人捕捉到视野后,驱动敌人的行动
添加成员变量
public Transform target;
private Vector3 targetPoint;
private NavMeshAgent agent;
private List<Vector3> dstPoints;
private int nextIdx = 0;
private float calcdist = 5f;
public float chaseSpeed = 15f;
public float chaseWait = 5f;
private float chaseTimer = 0f;
public float sqrPlayerDist = 4f;
private bool isChasing = false;
public float shootRotSpeed =4f;
public float shootFreeTime = 2f;
private float shootTimer = 0f;
private AIRobotSight aiRobotSight;
private Transform player;
public GameObject bullet;
public Transform muzzle;
public Text ammorText;
private float speed;
public int ammorCnt;
private string robotType;
private string UIType;
public Slider slider;
攻击函数的逻辑
void shooting()
{
Vector3 lookPos = player.position;
lookPos.y = transform.position.y;
Vector3 targetDir = lookPos - transform.position;
transform.rotation = Quaternion.Slerp(transform.rotation,Quaternion.LookRotation(targetDir),Mathf.Min(1f,Time.deltaTime*shootRotSpeed));
agent.isStopped = true;
if (Vector3.Angle(transform.forward,targetDir) <2)
{
if(shootTimer > shootFreeTime)
{
shootTimer = 0f;
GameObject bulletClone;
bulletClone = Instantiate(bullet, muzzle.position,Quaternion.LookRotation(player.position-muzzle.position));
bulletClone.AddComponent<destory>();
bulletClone.GetComponent<Rigidbody>().velocity = transform.TransformDirection(Vector3.forward * speed);
ammorCnt--;
ammorText.text = "子弹余量:" + ammorCnt + "/300";
slider.value = ammorCnt;
}
shootTimer += Time.deltaTime;
}
}
追击函数的逻辑
void chasing()
{
agent.isStopped = false;
Vector3 sightDelPos = aiRobotSight.playerLastSight-transform.position;
if(sightDelPos.sqrMagnitude > sqrPlayerDist)
{
agent.destination = aiRobotSight.playerLastSight;
}
agent.speed = chaseSpeed;
if(agent.remainingDistance < agent.stoppingDistance)
{
chaseTimer += Time.deltaTime;
if (chaseTimer > chaseWait)
{
isChasing = false;
chaseTimer = 0f;
}
}
else
{
chaseTimer = 0f;
}
}
逃跑函数的逻辑
void runaway()
{
Vector3 playerPos = player.position - dstPoints[0];
float minDst = playerPos.sqrMagnitude;
int minIdx=0;
for(int i = 1; i < dstPoints.Count; i++)
{
Vector3 temp = player.position - dstPoints[i];
if (minDst < temp.sqrMagnitude)
{
minIdx = i;
minDst = temp.sqrMagnitude;
}
}
agent.speed = 20f;
agent.SetDestination(dstPoints[minIdx]);
}
4.项目优化,修改血条UI和弹药UI中数据紊乱的bug
5.修改玩家移动方式
6.衔接BUFF效果
|