这一篇是对追踪机器人的总结,首先我们需要分析一下机器人的行为特点,在这里我们用有限状态机(FSM)进行设计。
首先,每个机器人都会有巡逻(Patrol)、追踪(Chase)、攻击(Attack)和死亡(Die)这四个最基本的状态,事件可以引起不同状态之间的转换,关系如下:
图中矩形代表状态,菱形代表事件,“受到伤害”是后面机器人脚本中的一个方法调用,用来判定是否掉血。
这样机器人的动画逻辑也顺便做好了:
所有的状态和事件都用枚举的方式进行定义。已经进入死亡状态的话就不会再转换了,而三个图中的转换关系也不是固定的,但是在某一状态下,一个事件发生会指定转换为另一状态。这种一一对应的关系,自然的联想到字典。
到这里,至少需要创建四个状态类,每个类中都要具有一个字典的存储结构,来存储每一个事件和其转换到的状态。此外,每个状态类中还必须有一个维持该状态动作的方法和一个判断是否会触发事件的方法,那么可以抽象出一个状态基类,定义这两个方法为抽象方法,并添加一些必要的函数和数据:
using System.Collections.Generic;
using UnityEngine;
public abstract class FSMStateBase {
protected CharacterController m_npc;//机器人的碰撞器
public Dictionary<Event,State>m_map=new Dictionary<Event, State>();//每个状态下发生某件事时都会转换至唯一其他状态
public Vector3 m_destination;//目标点
protected float m_movespeed;//移动速度
protected float m_rotatespeed;//转身速度
public abstract void JudgeEventHappen(Transform player);//判断是否有事件发生
public abstract void StateAction( Transform player);//保持状态的动作
public void TurnAndMove()//转身并向目标点移动
{
//行走时有一定的偏差,并y轴与本身平行
Vector3 des = new Vector3(m_destination.x+Random.Range(0,0.5f), m_npc.transform.position.y, m_destination.z+Random.Range(0, 0.5f));
//身体平滑转向目标点
Quaternion targetdirection = Quaternion.LookRotation(des- m_npc.transform.position);
m_npc.transform.rotation = Quaternion.Slerp(m_npc.transform.rotation, targetdirection, Time.deltaTime * m_rotatespeed);
//向目标点移动
m_npc.Move(m_npc.gameObject.transform.forward*m_movespeed*Time.deltaTime);
}
}
但是有这些状态类明显还不够,因为他们都没有继承MonoBehaviour,需要有一个机器人控制脚本来存储它们,给它们传达玩家和地图的信息,这样他们才能判断是否有事件触发等。所以在详细写四个状态类之前先写一个机器人控制基类,含有所有机器人都有的共同属性:
using System.Collections.Generic;
using UnityEngine;
public enum State
{
PATROL,
CHASE,
ATTACK,
DIE
}
public enum Event
{
FINDPLAYER,
NOHP,
CANATTACKED,
LOSEPLAYER,
}
public class FSMBase :MonoBehaviour {
protected int m_hp=100;
public float M_hp
{
get { return m_hp; }
}
protected int m_hpbefore;//记录之前的血量
protected FSMStateBase m_curstate;//现在的状态
public FSMStateBase M_curstate { get { return m_curstate; } }
protected Dictionary<State,FSMStateBase>m_statmap=new Dictionary<State, FSMStateBase>();
virtual public void ChangeState(Event eve) { }
public void BeDamaged(int damage)
{
m_hpbefore = m_hp;
m_hp -= damage;
}
public bool JudgeDamaged()//判断是否有掉血
{
if (m_hp != m_hpbefore)
{
m_hpbefore = m_hp;
return true;
}
else return false;
}
}
然后是详细的四个状态类:
巡逻类:
using System.Collections.Generic;
using UnityEngine;
public class Patrol : FSMStateBase {
private FSMBase m_npcfsm;
private float m_patrolrange;
private float m_discoverrange;
private List<GameObject> m_patrolpoionts;//存储巡逻点的数组
//构造函数
public Patrol
(List<GameObject>partrolpoints, float movespeed,float rotatespeed,
float discoverrange,float patrolrange,FSMBase aicontrollor)
{
m_patrolpoionts = partrolpoints;
m_destination = m_patrolpoionts[0].transform.position;
m_movespeed = movespeed;
m_rotatespeed = rotatespeed;
m_discoverrange = discoverrange;
m_patrolrange = patrolrange;
m_npcfsm = aicontrollor;
m_npc = m_npcfsm.GetComponentInParent<CharacterController>();
}
public void FindNextPoint()//找下一个巡逻点
{
int i = Random.Range(0, m_patrolpoionts.Count);//随机一个索引
m_destination = m_patrolpoionts[i].transform.position;
}
public override void JudgeEventHappen(Transform player)
{
//发现敌人
if(Vector3.Distance(m_npc.transform.position, player.position)<m_discoverrange&&
player.GetComponent<playerhp>().M_hp >0)//如果在发现范围内
{
m_npcfsm.ChangeState(Event.FINDPLAYER);
}
if(m_npcfsm.JudgeDamaged())//如果受到伤害
{
if (m_npcfsm.M_hp > 0)
{
m_npcfsm.ChangeState(Event.FINDPLAYER);
}
else m_npcfsm.ChangeState(Event.NOHP);
}
}
public override void StateAction( Transform player)
{
TurnAndMove();
//到达一个巡逻点,找下一个点
if (Vector3.Distance(m_npc.transform.position, m_destination) < m_patrolrange )
{
FindNextPoint();
}
m_npc.GetComponentInParent<Animator>().SetBool("IsRun", false);
}
}
追逐类:
using UnityEngine;
public class Chase : FSMStateBase
{
private FSMBase m_npcfsm;
private float m_attackrange;
private float m_chaserange;
public Chase(float movespeed, float rotatespeed, float attackrange, float chaserange, FSMBase aicontrollor)
{
m_movespeed = movespeed;
m_rotatespeed = rotatespeed;
m_attackrange = attackrange;
m_chaserange = chaserange;
m_npcfsm = aicontrollor;
m_npc = m_npcfsm.GetComponentInParent<CharacterController>();
}
public override void JudgeEventHappen(Transform player)
{
//攻击敌人
if (Vector3.Distance(m_npc.transform.position, player.position) < m_attackrange&&
player.GetComponent<playerhp>().M_hp > 0)
{
m_npcfsm.ChangeState(Event.CANATTACKED);
}
//受到伤害
if (m_npcfsm.JudgeDamaged())
{
if (m_npcfsm.M_hp <= 0)
{
m_npcfsm.ChangeState(Event.NOHP);
}
}
//丢失敌人
if (Vector3.Distance(m_npc.transform.position, player.position) > m_chaserange||
player.GetComponent<playerhp>().M_hp<=0)
{
m_npcfsm.ChangeState(Event.LOSEPLAYER);
}
}
public override void StateAction(Transform player)
{
m_destination = player.transform.position;
TurnAndMove();
//动画逻辑
AnimatorStateInfo info = m_npc.GetComponentInParent<Animator>().GetCurrentAnimatorStateInfo(0);
if (info.IsName("walk"))
{
m_npc.GetComponentInParent<Animator>().SetBool("IsRun", true);
}
if (info.IsName("attack"))
m_npc.GetComponentInParent<Animator>().SetBool("IsAttack", false);
}
}
攻击类:
using UnityEngine;
public class Attack : FSMStateBase
{
FSMBase m_npcfsm;
private float m_attackrange;
public Attack(float attackrange,float rotatespeed,FSMBase aicontroller)
{
m_attackrange = attackrange;
m_movespeed = 0;
m_npcfsm = aicontroller;
m_npc = m_npcfsm.GetComponentInParent<CharacterController>();
}
public override void JudgeEventHappen(Transform player)
{
//受到伤害
if(m_npcfsm.JudgeDamaged())
{
if(m_npcfsm.M_hp<=0)
m_npcfsm.ChangeState(Event.NOHP);
}
//丢失玩家
if(Vector3.Distance(m_npc.transform.position, player.position) > m_attackrange||
player.GetComponent<playerhp>().M_hp <= 0)
{
m_npcfsm.ChangeState(Event.LOSEPLAYER);
}
}
public override void StateAction(Transform player)
{
m_destination = player.position;
TurnAndMove();
m_npc.GetComponentInParent<Animator>().SetBool("IsAttack", true);
}
}
死亡类:
using UnityEngine;
public class Die : FSMStateBase
{
FSMBase m_npcfsm;
public Die(FSMBase aicontrollor)
{
m_npcfsm = aicontrollor;
m_npc = m_npcfsm.GetComponentInParent<CharacterController>();
}
public override void JudgeEventHappen(Transform player)
{
}
public override void StateAction(Transform player)
{
datamanager.AddGameScore();
GameObject.Destroy( m_npc.gameObject);
}
}
最后是给具体的一个机器人创建控制脚本,继承前面的控制基类,下面是一个最简单的近距离攻击的机器人(机器人要有CharacterController组件):
using System.Collections.Generic;
using UnityEngine;
public class RobotController : FSMBase
{
public List<GameObject> m_patrolpostions = new List<GameObject>();
[SerializeField]
private float m_attackrange;
[SerializeField]
private float m_discoverrange;
[SerializeField]
private float m_chaserange;
public float M_chaserange
{
set { m_chaserange = value; }
}
[SerializeField]
private float m_patrolrange;
[SerializeField]
private float m_walkspeed;
[SerializeField]
private float m_runspeed;
[SerializeField]
private float m_rotatespeed;
private GameObject m_player;
void Start()
{
m_hpbefore = m_hp;
FSMBase temp = this;
m_player = GameObject.FindGameObjectWithTag("Player");
//添加状态
m_statmap.Add(State.PATROL, new Patrol
(m_patrolpostions, m_walkspeed, m_rotatespeed, m_discoverrange, m_patrolrange, temp));
m_statmap.Add(State.CHASE, new Chase
(m_runspeed, m_rotatespeed, m_attackrange, m_chaserange, temp));
m_statmap.Add(State.ATTACK, new Attack
(m_attackrange, m_rotatespeed, temp));
m_statmap.Add(State.DIE, new Die(temp));
//初始化每一个状态的字典
m_statmap[State.PATROL].m_map.Add(Event.FINDPLAYER, State.CHASE);
m_statmap[State.PATROL].m_map.Add(Event.NOHP, State.DIE);
m_statmap[State.CHASE].m_map.Add(Event.NOHP, State.DIE);
m_statmap[State.CHASE].m_map.Add(Event.LOSEPLAYER, State.PATROL);
m_statmap[State.CHASE].m_map.Add(Event.CANATTACKED, State.ATTACK);
m_statmap[State.ATTACK].m_map.Add(Event.LOSEPLAYER, State.CHASE);
m_statmap[State.ATTACK].m_map.Add(Event.NOHP, State.DIE);
m_curstate = m_statmap[State.PATROL];
}
void FixedUpdate()
{
//设置机器人的音效的音量
gameObject.GetComponent<AudioSource>().volume = datamanager.m_volume;
m_curstate.JudgeEventHappen(m_player.transform);
m_curstate.StateAction(m_player.transform);
}
//根据事件改变当前状态
public override void ChangeState(Event eve)
{
State temp = m_curstate.m_map[eve];
m_curstate = m_statmap[temp];
}
}
再给机器人添加一个造成伤害的脚本(通过动画的播放来判断),添加前要给机器人设置好AudioSource
using UnityEngine;
public class MakeDamage : MonoBehaviour
{
private GameObject m_player;
[SerializeField]
private int m_damage;
private bool m_hasdamaged=false;
private Animator m_animator;
private AudioSource m_audio;
void Start()
{
m_player = GameObject.FindGameObjectWithTag("Player");
m_animator = gameObject.GetComponent<Animator>();
m_audio = gameObject.GetComponent<AudioSource>();
}
void Update()
{
//下载的进攻音效不是很完美 进行一个截取
if (m_audio.time > 1) m_audio.Stop();
//获取动画层 0 指Base Layer.
AnimatorStateInfo info = m_animator.GetCurrentAnimatorStateInfo(0);
//normalizedTime为规范化时间,当动画循环播放时该值不断累加(播放一次累加1)
//下面这句表示每一次当攻击动画播放了十分之八时
if (info.IsName("attack") && info.normalizedTime % 1 > 0.8f && !m_hasdamaged)
{
m_player.GetComponent<playerhp>().bedammaged(m_damage);
m_audio.Play();
m_hasdamaged = true;
}
if (info.IsName("attack") && info.normalizedTime % 1 < 0.8f && m_hasdamaged)
{
m_hasdamaged = false;
}
}
}
这样子机器人就初步完成了(没准以后还会加上A*寻路什么的),若是不同的机器人有不同的攻击方式(如射击)再添加脚本进行表现就行了。
下一篇总结游戏的ui界面