您的位置:首页 > 大数据 > 人工智能

有限状态机实现简单的AI状态(巡逻,追逐玩家,攻击)

2018-02-15 21:34 423 查看
点击打开有限状态机的实现基类
去Wiki.Unity3D就可以搜索到有关基类,下面是我自己修改了一小部分的基类public class FSMSystem
{
private Dictionary<StateID, FSMState> states;

private StateID currentStateID;
public StateID CurrentStateID { get { return currentStateID; } }
private FSMState currentState;
public FSMState CurrentState { get { return currentState; } }

public FSMSystem()
{
states = new Dictionary<StateID, FSMState>();
}

public void AddState(FSMState s)
{

if (s == null)
{
Debug.LogError("FSMState不能为空");
}

if (states.ContainsKey(s.ID))
{
Debug.LogError("状态" + s.ID + "已存在,不能再添加");
return;
}
//第一个设置的状态就是当前状态
if (currentState == null)
{
currentState = s;
currentStateID = s.ID;
}
states.Add(s.ID, s);
}

public void DeleteState(StateID id)
{
if (id == StateID.NullStateID)
{
Debug.LogError("无法删除空状态");
return;
}
if (states.ContainsKey(id)==false)
{
Debug.LogError("无法删除不存在的状态:"+id);
return;
}
states.Remove(id);
}

public void PerformTransition(Transition trans)
{
if (trans == Transition.NullTransition)
{
Debug.LogError("无法执行空条件");
return;
}

StateID id = currentState.GetOutputState(trans);

if (id == StateID.NullStateID)
{
Debug.LogWarning("当前状态" + currentStateID + "无法根据转换条件" + trans+"发生转换");
return;
}
if (states.ContainsKey(id) == false)
{
Debug.LogError("在状态机中不存在该状态,无法发生转换");
return;
}

FSMState nextState = states[id];

currentState.DoBeforeLeaving();
currentState = nextState;
currentStateID = id;
currentState.DoBeforeEntering();

}

}
public enum Transition
{
NullTransition = 0,
RestOver,
PatrolOver,
OutAttackRange,
SeePlayer,
LostPlayer,
ClosePlayer
}

public enum StateID
{
NullStateID = 0,
Idle,
Patrol,
Chase,
Attack,
Dead
}

public abstract class FSMState
{

protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
protected StateID stateID;
public StateID ID { get { return stateID; } }

protected FSMSystem fsm;
public FSMState(FSMSystem fsm)
{
this.fsm = fsm;
}

public void
4000
AddTransition(Transition trans, StateID id)
{
if (trans == Transition.NullTransition)
{
Debug.LogError("不允许添加NullTransition");
return;
}

if (id == StateID.NullStateID)
{
Debug.LogError("不允许添加NullState");
return;
}

if (map.ContainsKey(trans))
{
Debug.LogError("该条件"+trans+"已经存在");
return;
}

map.Add(trans, id);
}

public void DeleteTransition(Transition trans)
{
if (trans == Transition.NullTransition)
{
Debug.LogError("不允许删除NullTransition");
return;
}

if (map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
Debug.LogError("转换条件 " + trans + " 不存在于字典中 ");
}

public StateID GetOutputState(Transition trans)
{
if (map.ContainsKey(trans))
{
return map[trans];
}
return StateID.NullStateID;
}

public virtual void DoBeforeEntering() { }
public virtual void DoBeforeLeaving() { }
//在状态中判断是否转换至下一状态
public abstract void Reason(GameObject player, GameObject npc);
//该状态用来调用动画播放
public abstract void Act(GameObject player, GameObject npc,Animation anim);

}
所有的动画状态继承FSMState,新建一个Enemy脚本继承Monobehavior用来调用生命周期函数,在其中初始化一个FSM状态机,并赋值给相关State类,让其他State类获得该状态机的引用,Enemy脚本将挂载在一只哥布林并在场景中启用。public class Enemy : MonoBehaviour {

private FSMSystem fsm;
private GameObject player;

private Animation anim;
void Awake()
{
player = GameObject.Find("Player");
anim = GetComponentInChildren<Animation>();

InitFSM();
}

// Update is called once per frame
void Update()
{
fsm.CurrentState.Act(player,gameObject,anim);
fsm.CurrentState.Reason(player,gameObject);
}

void InitFSM()
{
fsm = new FSMSystem();

IdleState idleState = new IdleState(fsm);
idleState.AddTransition(Transition.RestOver, StateID.Patrol);
idleState.AddTransition(Transition.SeePlayer, StateID.Chase);

PatrolState patrolState = new PatrolState(fsm);
patrolState.AddTransition(Transition.PatrolOver, StateID.Idle);
patrolState.AddTransition(Transition.SeePlayer, StateID.Chase);

ChaseState chaseState = new ChaseState(fsm);
chaseState.AddTransition(Transition.LostPlayer, StateID.Idle);
chaseState.AddTransition(Transition.ClosePlayer, StateID.Attack);

AttackState attackState = new AttackState(fsm);
attackState.AddTransition(Transition.OutAttackRange, StateID.Chase);

fsm.AddState(idleState);
fsm.AddState(patrolState);
fsm.AddState(chaseState);
fsm.AddState(attackState);
}
}最后是各个相关状态的脚本public class IdleState : FSMState
{
private float restTime = 2.0f;
private float restTimer = 0.0f;

private float viewDistance = 3.0f;

public IdleState(FSMSystem fsm) : base(fsm)
{
stateID = StateID.Idle;
}

public override void Act(GameObject player, GameObject npc,Animation anim)
{
anim.CrossFade("idle");
}

public override void Reason(GameObject player, GameObject npc)
{
restTimer += Time.deltaTime;
if (restTimer > restTime)
{
restTimer = 0;
fsm.PerformTransition(Transition.RestOver);
}

Vector3 vec = player.transform.position - npc.transform.position;
if (vec.magnitude < viewDistance)
{
fsm.PerformTransition(Transition.SeePlayer);
}
}
}
public class PatrolState : FSMState
{
private float patrolTime = 2.0f;
private float patrolTimer = 0.0f;

private float patrolSpeed = 2.0f;
private Vector3 patrolDir=Vector3.zero;
private float viewDistance = 3.0f;
private bool turnOver = false;
public PatrolState(FSMSystem fsm) : base(fsm)
{
stateID = StateID.Patrol;
}

public override void Act(GameObject player, GameObject npc,Animation anim)
{
anim.CrossFade("run");
if (!turnOver)
{
npc.transform.Rotate(patrolDir);
turnOver = true;
}
npc.transform.Translate(Vector3.forward * Time.deltaTime * patrolSpeed);
}

public override void Reason(GameObject player, GameObject npc)
{
patrolTimer += Time.deltaTime;
if (patrolTimer > patrolTime)
{
patrolTimer = 0;
turnOver = false;
fsm.PerformTransition(Transition.PatrolOver);
}

Vector3 vec = player.transform.position - npc.transform.position;
if (vec.magnitude < viewDistance)
{
fsm.PerformTransition(Transition.SeePlayer);
}
}
public override void DoBeforeEntering()
{
patrolDir = new Vector3(0, Random.Range(-180.0f, 180.0f), 0);
}
}
实际开发中可以把Idle和Patrol状态改为同一个状态例如NonIntruder,这里我是一开始为了试验状态机的基类有没有bug用的,所以现在的状态如下
             


public class ChaseState : FSMState
{
private float chaseSpeed = 3.0f;

private float lostViewDistance = 5.0f;
private float attackRange = 1.0f;
public ChaseState(FSMSystem fsm) : base(fsm)
{
stateID = StateID.Chase;
}

public override void Act(GameObject player, GameObject npc, Animation anim)
{
npc.transform.LookAt(player.transform);
npc.transform.Translate(Vector3.forward * Time.deltaTime * chaseSpeed);
anim.CrossFade("run");
}

public override void Reason(GameObject player, GameObject npc)
{
Vector3 vec = player.transform.position - npc.transform.position;

if (vec.magnitude> lostViewDistance)
{
fsm.PerformTransition(Transition.LostPlayer);
}
if(vec.magnitude < attackRange)
{
fsm.PerformTransition(Transition.ClosePlayer);
}
}
}public class AttackState : FSMState
{
private float waitTime = 2.0f;
private float waitTimer = 2.0f;

private float attackRange = 1.0f;
private float attackAngle = 60.0f;

public AttackState(FSMSystem fsm) : base(fsm)
{
stateID = StateID.Attack;
}

public override void Act(GameObject player, GameObject npc, Animation anim)
{
if (waitTimer < waitTime)
{
waitTimer += Time.deltaTime;
anim.CrossFadeQueued("idle");
}
else
{
npc.transform.LookAt(player.transform);
anim.Play("attack");
waitTimer = 0;
}

}

public override void Reason(GameObject player, GameObject npc)
{
Vector3 vec = player.transform.position - npc.transform.position;
if (vec.magnitude > attackRange)
{
fsm.PerformTransition(Transition.OutAttackRange);
}
}
public override void DoBeforeEntering()
{
base.DoBeforeEntering();
waitTimer = 2.0f;
}
}当前状态涉及到的只有GameObject,Animation,如果涉及到更复杂的情况就要传入许多参数,后面可以尝试新建一个EnemyData类,挂载在需要的哥布林身上,让Enemy类去获取哥布林身上的EnemyData组件,再直接传入给State类,就不需要每次Update都要传入给Act方法大量参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: