第五步 敌人动画创建
添加上下左右四个动画 因为敌人需要方向变化的动画,所以需要添加条件来控制动画的转换 尝试后发现动画转向有延迟 将Can Transition To 禁用可以让动画在装换时,不会让动画自己转换自己
第六步 敌人AI创建
敌人AI是由三个不同状态组成,因此需要使用到状态机。
状态机的定义 状态机也叫有限状态机(FSM)是表示有限个状态以及这些状态之间的转移和动作等行为的数学模型。 状态机是一个算法思想,其主要用于描述对象在它的生命周期内所经历的状态过程,以及如何响应来自外界的各种事件 状态机的四大概念(组成) 1、状态(State) 一个状态机至少要包含两个状态 2、事件(Event) 事件就是执行某个操作的触发条件 3、动作(Action) 事件发生以后要执行动作 4、变换(Transition) 从一个状态变化为另一个状态
三个状态:路点状态、巡逻状态、A*追踪状态
状态机代码
public class StateMachine<Towner> : MonoBehaviour
{
private State<Towner> curState = null;
private Towner owner;
public void Init(Towner owner,State<Towner> initialState )
{
this.owner = owner;
ChangeState(initialState);
}
public void ChangeState(State<Towner> newState)
{
print(string.Format("{0}=>{1}", curState == null ? "n/a" : curState.GetType().Name, newState.GetType().Name));
if (curState != null)
{
curState.Exit(owner);
}
curState = newState;
curState.Enter(owner);
}
public void Update()
{
curState.Update(owner);
}
}
状态类代码
public class State<Towner>
{
public virtual void Enter(Towner e)
{ }
public virtual void Update(Towner e)
{ }
public virtual void Exit(Towner e)
{ }
}
代码分析 第一步 声明一个当前状态类(CurState) 第二步 声明一个状态机的拥有者(onwer,游戏中的怪物拥有着不同的状态,所以成为状态机的拥有者) 第三步 对状态机和状态进行初始化
public void Init(Towner owner,State<Towner> initialState )
{
this.owner = owner;
ChangeState(initialState);
}
第四步 进行状态转换
public void ChangeState(State<Towner> newState)
{
print(string.Format("{0}=>{1}", curState == null ? "n/a" : curState.GetType().Name, newState.GetType().Name));
if (curState != null)
{
curState.Exit(owner);
}
curState = newState;
curState.Enter(owner);
}
解析: 将新的状态传入 进行检测有无当前状态。 如何没有当前状态,将当前状态变为新状态 如果有当前状态,就将当前状态清理 然后将当状态变为新状态 最后对已经从新状态变为当前状态的状态进行初始化
第五步 对状态进行更新。
路点状态
代码
class WaypointState : State<Monster>
{
private List<Vector2> path;
private int index;
public WaypointState(List<Vector2> path)
{
this.path = path;
this.index = 0;
}
public override void Update(Monster e)
{
Vector2 p = Vector2.MoveTowards(e.transform.position, path[index], e.speed * Time.fixedDeltaTime);
e.gameObject.GetComponent<Rigidbody2D>().MovePosition(p);
if ((Vector2)e.transform.position == path[index])
{
index++;
if (index == path.Count)
{
}
else
{
Vector2 dir = path[index]-path[index-1];
e.GetComponent<Animator>().SetFloat("DirX", dir.x);
e.GetComponent<Animator>().SetFloat("DirY", dir.y);
}
}
}
}
代码分析 路点的核心就在于存储路径点的路径 首先声明一个List用来存储路径点。然后添加路点 然后声明一个构造器来初始化。 接着对State类的Update方法进行重写,将路点作为移动的方向,使游戏物体按照路点的位置进行移动。
巡逻状态
代码
class PatrolState : State<Monster>
{
public Vector2 dest;
private Vector2 curDir;
private Vector2[] dirs = new Vector2[] {Vector2.up,Vector2.down,Vector2.left,Vector2.right };
public override void Enter(Monster e)
{
dest = e.transform.position;
curDir = e.transform.position;
}
public override void Update(Monster e)
{
Vector2 p = Vector2.MoveTowards(e.transform.position, dest, e.speed * Time.fixedDeltaTime);
e.gameObject.GetComponent<Rigidbody2D>().MovePosition(p);
if ((Vector2)e.transform.position == dest)
{
List<Vector2> walkable = new List<Vector2>();
for (int i = 0; i < dirs.Length; i++)
{
if (dirs[i] == -curDir)
{
continue;
}
if (e.CanGo(dirs[i]))
{
walkable.Add(dirs[i]);
}
}
int index = Random.Range(0, walkable.Count);
curDir = walkable[index];
e.GetComponent<Animator>().SetFloat("DirX", curDir.x);
e.GetComponent<Animator>().SetFloat("DirY", curDir.y);
dest += curDir;
}
}
}
private bool CanGo(Vector2 dir)
{
RaycastHit2D hit = Physics2D.Linecast(this.transform.position, (Vector2)this.transform.position + dir, 1 << LayerMask.NameToLayer("Map"));
return hit!= true;
}
if (index >= path.Count)
{
PatrolState patrolState = new PatrolState();
e.stateMachine.ChangeState(patrolState);
}
代码分析 巡逻的核心就在于生成一个上下左右的数组,对数组进行遍历。在遍历后判断方向然后进行游戏物体的移动。但不同的是巡逻状态比路点状态多一个初始化的重写
|