有关巡逻机器人的内容较多,在正式开始之前,先为主角添加一个简单的Hp脚本。
using UnityEngine;
using UnityEngine.UI;
public class playerhp : MonoBehaviour
{
private int m_hp;
public GameObject m_weapon;//获得枪的对象,用来播放死亡的动画
public Text m_hptext;//获取画布中的一个text
void Start()
{
m_hp = 100;
}
void Update()
{
if (m_hp <= 0) m_weapon.GetComponent<Animation>().Play("Die");
FreshHpText();
}
void FreshHpText()
{
m_hptext.text = "HP:" + m_hp.ToString();
}
public void bedammaged(int dam)
{
m_hp -= dam;
}
}
好了,现在我们需要分析一下机器人的行为特点,在这里我们用有限状态机(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>();//每个状态下发生某件事时都会转换至唯一其他状态
protected 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()//转身并向目标点移动
{
Vector3 des = new Vector3(m_destination.x, m_npc.transform.position.y, m_destination.z);
//身体平滑转向目标点
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;
public int M_hp
{
get { return m_hp; }
}
protected int m_hpbefore;//记录之前的血量
protected FSMStateBase 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 UnityEngine;
public class Patrol : FSMStateBase {
private FSMBase m_npcfsm;
private float m_patrolrange;
private float m_discoverrange;
private Transform[] m_patrolpoionts;//存储巡逻点的数组
//构造函数
public Patrol
(Transform[]partrolpoints,float movespeed,float rotatespeed,float discoverrange,float patrolrange,FSMBase aicontrollor)
{
m_patrolpoionts = partrolpoints;
m_destination = m_patrolpoionts[0].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 = UnityEngine.Random.Range(0, m_patrolpoionts.Length);//随机一个索引
m_destination = m_patrolpoionts[i].position;
}
public override void JudgeEventHappen(Transform player)
{
//发现敌人
if(Vector3.Distance(m_npc.transform.position, player.position)<m_discoverrange)//如果在发现范围内
{
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)
{
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)
{
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)
{
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;
}
public override void JudgeEventHappen(Transform player)
{
Debug.Log(" is dead");
}
public override void StateAction(Transform player)
{
m_npcfsm.GetComponentInParent<Animator>().SetTrigger("Die");
//后面把这句加上,死亡之后存到对象池里
// pool.PrefabReturn(m_npc.gameObject);
}
}
最后是给具体的一个机器人创建控制脚本,继承前面的控制基类,下面是一个最简单的近距离攻击的机器人:
using UnityEngine;
public class RobotController : FSMBase
{
[SerializeField]
private Transform[] m_patrolpostions;
[SerializeField]
private float m_attackrange;
[SerializeField]
private float m_discoverrange;
[SerializeField]
private float m_chaserange;
[SerializeField]
private float m_patrolrange;
[SerializeField]
private float m_walkspeed;
[SerializeField]
private float m_runspeed;
[SerializeField]
private float m_rotatespeed;
public Transform m_bornpoint;
public Transform m_player;
void Start()
{
m_hpbefore = m_hp = 100;
FSMBase temp = gameObject.GetComponent<RobotController>();
//添加状态
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 Update()
{
m_curstate.JudgeEventHappen(m_player);
m_curstate.StateAction(m_player);
}
public override void ChangeState(Event eve)
{
State temp = m_curstate.m_map[eve];
m_curstate = m_statmap[temp];
}
}
再给机器人添加一个造成伤害的脚本(通过动画的播放来判断):
using UnityEngine;
public class MakeDamage : MonoBehaviour
{
public GameObject m_player;
[SerializeField]
private int m_damage;
private Animator m_animator;
private bool m_hasdamaged;
void Start()
{
m_animator = gameObject.GetComponent<Animator>();
m_hasdamaged = false;
}
void FixedUpdate()
{
//获取动画层 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_hasdamaged = true;
}
if (info.IsName("attack") && info.normalizedTime % 1 < 0.8f && m_hasdamaged)
{
m_hasdamaged = false;
}
}
}
最后,需要把上一篇武器系统中的武器脚本添加几句代码,就是当打到机器人 时调用机器人掉血的方法:
if (Physics.Raycast(m_camera.position, direction, out hit, shootrange))
{
Debug.Log(hit.collider.gameObject.name);
if (hit.collider.gameObject.name == "Robot1")
hit.collider.GetComponentInParent<RobotController>().BeDamaged(damage);
//这里的代码后面会增加,根据击中不同的物体产生不同的效果
PlayHitEffect(hit.point, direction);
}
在枪械脚本的110行,另外最好区分一下打到机器人和打到其他物体上的特效(修改PlayHitEffect)
这样子机器人就初步完成了(没准以后还会加上A*寻路什么的),若是不同的机器人有不同的攻击方式(如射击)再添加脚本进行表现就行了。
那么下一篇。。。还没想好写啥&_&