游戏开发中的有限状态机(FMS)
这个可以看成是一个AI模板,无论大小游戏中敌人以及一些NPC都是是游戏中不可或缺的一些元素,他们AI的好坏会严重影响我们的游戏体验,而有限状态机可以制作一些简单的AI,并且可以保持逻辑的有序性以及代码的可阅读性。
用干净的代码实现一些基本功能。
创建一个最基本的接口,所有状态都基于这个接口
public interface IState
{
void OnEnter();//进入状态
void OnUpdate();//状态进行中
void OnExit();//离开状态
}
创建几个基本的状态作为示范
Idle状态
public class IdleState : IState
{
private FMS fms;
private Character character;
private float timer;
public IdleState(FMS fms,Character character)//在状态机中实例化的时候可以指定是哪个状态机与属性类
{
this.fms = fms;
this.character = character;
}
public void OnEnter()
{
fms.PlayAnimation("Idle");
}
public void OnExit()
{
timer = 0;
}
public void OnUpdate()
{
timer += Time.deltaTime;
if (timer > 3)
{
fms.SetState(Enum.StateType.walk);
}
}
}
Walk状态
public class WalkState : IState
{
private FMS fms;
private Character character;
public WalkState(FMS fms, Character character)
{
this.fms = fms;
this.character = character;
}
public void OnEnter()
{
fms.PlayAnimation("Walking");
}
public void OnExit()
{
character.StopMove();
fms.positionIndex++;
if (fms.positionIndex > fms.position.Length-1)
fms.positionIndex = 0;
character.SetTarget(fms.position[fms.positionIndex]);
}
public void OnUpdate()
{
if (character.ToWard())
{
if (character.isAttack)
fms.SetState(Enum.StateType.attack);
else
fms.SetState(Enum.StateType.idle);
}
}
}
Attack状态
public class AttackState : IState
{
private FMS fms;
private Character character;
public AttackState(FMS fms, Character character)
{
this.fms = fms;
this.character = character;
}
public void OnEnter()
{
fms.PlayAnimation("Attacking");
}
public void OnExit()
{
}
public void OnUpdate()
{
if (!character.isAttack)
fms.SetState(Enum.StateType.idle);
}
}
状态机的实现
public class FMS : MonoBehaviour
{
private IState currentState;
private Dictionary<Enum.StateType, IState> states = new Dictionary<Enum.StateType, IState>();
private Character character;
private Animator animator;
public Transform[] position;
public int positionIndex = 0;
private void Awake()
{
character = GetComponent<Character>();
animator = GetComponent<Animator>();
}
private void Start()
{
states.Add(Enum.StateType.idle, new IdleState(this,character));
states.Add(Enum.StateType.walk, new WalkState(this,character));
states.Add(Enum.StateType.attack, new AttackState(this,character));
SetState(Enum.StateType.idle);
character.SetTarget(position[positionIndex]);
}
private void Update()
{
currentState.OnUpdate();
}
public void SetState(Enum.StateType stateType)//设置状态,如果当前状态不为空,调用当前状态的离开方法,设置当前状态,调用当前状态的进入方法
{
if (currentState != null)
{
currentState.OnExit();
}
currentState = states[stateType];
currentState.OnEnter();
}
public void PlayAnimation(string name)
{
animator.Play(name);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag != this.tag)
{
character.isAttack = true;
character.SetEenemy(collision.gameObject.GetComponent<Character>());
SetState(Enum.StateType.attack);
}
}
}
基本属性类的实现
public class Character : MonoBehaviour
{
private Animator animator;
private new Rigidbody2D rigidbody2D;
[SerializeField]private Transform target;
[SerializeField]private float moveSpeed;
public bool isAttack = false;
[SerializeField] private float hp;
[SerializeField] private float atk;
[SerializeField] private float df;
private Character enmemyCharacter;//敌人的属性脚本
void Start()
{
animator = GetComponent<Animator>();
rigidbody2D = GetComponent<Rigidbody2D>();
}
public bool ToWard()//移向目标
{
if (Vector2.Distance(target.position, transform.position) < 0.1f)
{
return true;
}
if (target.position.x < transform.position.x)
{
transform.localScale = new Vector3(-1,1);
}
else
{
transform.localScale = new Vector3(1, 1);
}
rigidbody2D.velocity=((target.position-transform.position).normalized)*moveSpeed;
return false;
}
public void StopMove()
{
rigidbody2D.velocity = Vector2.zero;
}
public void SetTarget(Transform target)
{
this.target = target;
}
public void SetEenemy(Character character)
{
enmemyCharacter = character;
}
public void GetDamage(float atk)
{
hp -= atk - df;
}
public void Attack()
{
enmemyCharacter.GetDamage(atk);
}
}
通过FMS与Character这两个脚本便可以实现一些战斗功能,具体效果如下:
只能放动态图勉强看一下
双方会走向绿点在过程中遭遇然后战斗。
这样简单的有限状态机就完成了,大家可以根据需求完善脚本。