0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

簡単なステートマシンを作ってみた

Posted at

はじめに

これはArbor3とうまく自分の作成中のゲームを組み合わせられず, でもとりあえず簡単なステートマシンだけ欲しかった人が簡易的にステートマシン(有限オートマトン)を実装してみたので, それの記事です。

以下がそのコードです。

StateMachine.cs
using System;
using System.Collections.Generic;
using UniRx;

namespace Assets.Scripts.StateMachine
{
    public abstract class State<T> where T: struct{


        protected Dictionary<T, State<T>> NextState = new Dictionary<T, State<T>>();

        public void Connect(T message, State<T> state)
        {
            NextState[message] = state;
        }

        public readonly string Name;

        /// <summary>
        /// このStateに入ったときに発行されます。イベントの値は前のStateです
        /// </summary>
        /// <returns></returns>
        public abstract IObservable<State<T>> OnEnterAsObservable();

        /// <summary>
        /// このStateにから出る時に発行されます。イベントの値は次のStateです
        /// </summary>
        /// <returns></returns>
        public abstract IObservable<State<T>> OnExitAsObservable();

        protected State(string name)
        {
            Name = name;
        }

        public override string ToString()
        {
            return $"{Name} State({typeof(T)})";
        }
    }
    
    public class StateMachine<T> where T : struct
    {
        private class InnerState : State<T>
        {


            public Subject<State<T>> OnEnter;
            public Subject<State<T>> OnExit;

            public override IObservable<State<T>> OnEnterAsObservable() => OnEnter = OnEnter ?? new Subject<State<T>>();

            public override IObservable<State<T>> OnExitAsObservable() => OnExit = OnExit ?? new Subject<State<T>>();

            public InnerState(string name) : base(name)
            {
            }

            public InnerState SendMessage(T message)
            {
                return (InnerState)NextState[message];
            }
        }

        public readonly State<T> Entry;

        private InnerState current;
        public State<T> CurrentState => current;

        public string CurrentStateName => current?.Name;

        public StateMachine()
        {
            Entry = current = new InnerState(default);
        }

        /// <summary>
        /// メッセージを送出します
        /// </summary>
        /// <param name="message"></param>
        public void SendMessage(T message)
        {
            var next = current.SendMessage(message);
            var prev = current;

            current.OnExit?.OnNext(next);
            current = next;
            next.OnEnter?.OnNext(current);
        }

        private Dictionary<string, InnerState> states = new Dictionary<string, InnerState>();

        public State<T> Create(string name)
        {
            var c = new InnerState(name);

            states.Add(name, c); // 例外出すため

            return c;
        }

        public void Jump(string name)
        {
            var next = states[name];
            var prev = current;

            current.OnExit?.OnNext(next);
            current = next;
            next.OnEnter.OnNext(current);
        }

        public State<T> GetState(string name)
        {
            return states[name];
        }



    }
}

使い方

ステートマシンの定義

  1. メッセージ用のenumを作成します
  2. Createメソッドで状態を作成します
  3. State.ConnectメソッドでそのStateの状態でメッセージが来た場合に遷移する状態を定義します。
PlayerStateMachine.cs
using Assets.Scripts.StateMachine;

namespace Assets.Scripts.StateMachine
{
    public enum PlayerMessage
    {
        Start,
        TakeDamage,

    }

    class PlayerStateMachine : StateMachine<PlayerMessage>
    {
        public State<PlayerMessage> Alive { get; private set; } 
        public State<PlayerMessage> Dead { get; private set; }

        public PlayerStateMachine()
        {
            Alive = Create("Alive");
            Dead = Create("Dead");


            Entry.Connect(PlayerMessage.Start, Alive);

            Alive.Connect(PlayerMessage.TakeDamage, Dead);
        }
    }
}

ステートマシンの使用

  1. ステートマシンは必ずEntryから開始されます
  2. StateにはOnEnterAsObservableとOnExitAsObservableという状態になった時状態から遷移したときのIObservableを入れています
  3. SendMessageメソッドでメッセージを送って遷移します
UseStateMachine.cs

var playerState = new PlayerStateMachine();
playerState.Dead.OnEnterAsObservable().subscribe(_ =>{/*死んだ*/});

playerState.SendMessage(PlayerMessage.Start);

// playerがダメージをうけたら
playerState.SendMessage(PlayerMessage.TakeDamage);

まとめ

というわけで簡単なステートマシン作ってみました。拡張はしやすいかも??

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?