- 状態を管理するクラス
- 状態を管理したいオブジェクトごとに作成する
- 後述の状態定義を登録し、Update などから呼び出して更新する
using UnityEngine;
using System.Collections.Generic;
using System;
public class FiniteStateMachine <T,U> {
private T Owner;
public FSMState<T,U> CurrentState { get; private set; }
private FSMState<T,U> PreviousState;
private Dictionary<U,FSMState<T,U>> stateRef;
public void Awake()
{
CurrentState = null;
PreviousState = null;
}
public FiniteStateMachine(T owner) {
Owner = owner;
stateRef = new Dictionary<U, FSMState<T, U>>();
}
public void Update()
{
foreach (U nextStateId in CurrentState.NextStateIDs) {
FSMState<T, U> nextState = stateRef [nextStateId];
if (nextState.CanEnter (CurrentState)) {
ChangeState (nextState);
return;
}
}
if (CurrentState != null) CurrentState.Execute();
}
public void ChangeState(FSMState<T,U> NewState)
{
PreviousState = CurrentState;
if (CurrentState != null)
CurrentState.Exit();
CurrentState = NewState;
if (CurrentState != null)
CurrentState.Enter();
}
public void RevertToPreviousState()
{
if (PreviousState != null)
ChangeState(PreviousState);
}
public FSMState<T, U> RegisterState(FSMState<T,U> state)
{
state.RegisterEntity(Owner);
stateRef.Add(state.StateID, state);
return state;
}
public void UnregisterState(FSMState<T,U> state)
{
stateRef.Remove(state.StateID);
}
};
状態定義の基底クラス
- その状態のID、次の状態のID、その状態に遷移できるか?、初期化処理、更新処理、終了処理、などを状態毎に定義できるようなクラス
using System;
using System.Collections.Generic;
public class FSMState <T, U> {
protected T entity;
public void RegisterEntity(T entity)
{
this.entity = entity;
}
virtual public U StateID
{
get{
throw new ArgumentException("State ID not spicified in child class");
}
}
virtual public List<U> NextStateIDs {
get {
throw new ArgumentException("Next State ID List not spicified in child class");
}
}
virtual public bool CanEnter (FSMState<T, U> currentState){
return false;
}
virtual public void Enter (){}
virtual public void Execute (){}
virtual public void Exit(){}
}
使用例
- 状態管理するクラスの定義と状態の定義をし、Unity Test Tools を使って動作確認をする。
- Main クラスが、Init, Run, Goal の3つの状態を持つ場合。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
namespace FSM.Test {
public class Main {
public FiniteStateMachine<Main, Main.States> FSM;
public bool Initialized = false;
public int Counter = 0;
public enum States {
Init,
Run,
Goal,
}
public Main() {
FSM = new FiniteStateMachine<Main, Main.States> (this);
FSM.ChangeState (FSM.RegisterState (new StateInit ()));
FSM.RegisterState (new StateRun ());
FSM.RegisterState (new StateGoal ());
}
}
public class StateInit : FSMState<Main, Main.States> {
public override Main.States StateID { get { return Main.States.Init; } }
public override List<Main.States> NextStateIDs { get { return new List<Main.States> { Main.States.Run }; } }
public override void Enter () {
entity.Initialized = true;
}
public override void Execute () {
}
public override void Exit () {
}
}
public class StateRun : FSMState<Main, Main.States> {
public override Main.States StateID { get { return Main.States.Run; } }
public override List<Main.States> NextStateIDs { get { return new List<Main.States> { Main.States.Goal }; } }
public override bool CanEnter (FSMState<Main, Main.States> currentState) {
if (entity.Initialized) {
return true;
} else {
return false;
}
}
public override void Enter () {
}
public override void Execute () {
entity.Counter++;
}
public override void Exit () {
}
}
public class StateGoal : FSMState<Main, Main.States> {
public override Main.States StateID { get { return Main.States.Goal; } }
public override bool CanEnter (FSMState<Main, Main.States> currentState) {
if (entity.Counter >= 3) {
return true;
} else {
return false;
}
}
public override void Enter () {
}
public override void Execute () {
}
public override void Exit () {
}
}
[TestFixture]
[Category("FSM")]
internal class FSMTest {
[Test]
public void MyFSMTest () {
Main main = new Main ();
Assert.That (main.FSM.CurrentState.StateID == Main.States.Init);
Assert.That (main.Initialized == true);
main.FSM.Update ();
main.FSM.Update ();
Debug.Log (main.Counter.ToString ());
Assert.That (main.FSM.CurrentState.StateID == Main.States.Run);
Assert.That (main.Counter == 1);
main.FSM.Update ();
main.FSM.Update ();
Assert.That (main.Counter == 3);
main.FSM.Update ();
Assert.That (main.FSM.CurrentState.StateID == Main.States.Goal);
Assert.That (main.Counter == 3);
}
}
}