FSMによる自動ドアの作り方


今回はセンサー付き自動ドアの作り方を解説します。

センサー内への出入りをMonoBehaviourスクリプトで管理し、作成したセンサースクリプトをStateBehaviourから参照して遷移するように組みます。

このチュートリアルで、ステートの区切りを超えて参照されるセンサーのようなスクリプトとArborFSMとの連携方法や、実践的なスクリプトの作成方法について学べます。

Contents

動作環境

このチュートリアルは、以下の環境で作成しております。

Unity 2019.4.40f1
Arbor 3.9.0

異なるバージョンを使用する場合は説明と異なる箇所があるかと思いますのが適宜読み替えてください。

プロジェクトの準備

プロジェクトの作成

チュートリアル用にプロジェクトを作成します。

プロジェクト名 ArborTutorial06
テンプレート 3D
インポートするアセット Arbor 3: FSM & BT Graph Editor

プロジェクトの作成やArborのインポートについては「Arborを使用するための準備」を参照してください。

チュートリアル用パッケージのインポート

今回は、ステージの配置などを予め行ったパッケージを用意しました。
このパッケージはArbor同梱のサンプルを参照しているため、必ず先にArborをインポートしておいてください。

“Tutorial06” をダウンロード

Tutorial06.unitypackage – 829 回のダウンロード – 4.58 KB

上記ボタンからダウンロードを行い、unitypackageをプロジェクトにインポートします。

アセットパッケージのインポートの方法については、Unity公式マニュアル「ローカルアセットパッケージのインポート」を参照してください。

パッケージが破損していた場合

以前ブラウザによってはダウンロードした際にパッケージファイルが破損することがありました。

もしパッケージのインポートに失敗する場合は、他のブラウザでのダウンロードも試してみてください。

チュートリアル用シーンを開く

用意してあるAutoDoorシーンを開きます。

  • Projectウィンドウから「Assets/AutoDoor/AutoDoor.unity」をダブルクリック。

シーンの現状解説

  • Main Camera
    Playerを追尾するカメラです。
    Arbor同梱サンプルのFollowCameraを使用しています。
  • Directional Light
    全体を照らします。
  • Player
    プレイヤーキャラクターです。
    WASDや方向キーで移動、左シフトキーを押しながら移動すると走ります。
    Arbor同梱サンプルのPlayerプレハブを使用しています。
  • Stage
    ステージです。床と壁を配置しています。
  • AutoDoor
    自動ドアです。今回のチュートリアルで動作を実装します。

    • Door
      ドア本体です。
      まだ動きません。
    • Sensor
      プレイヤーが入っているかどうかを感知するエリアです。
      まだ感知しません。
    • Indicator
      自動ドアの状態を視覚化するオブジェクトです。
      まだ変化しません。

このままプレイしてプレイヤーキャラクターをSensor内に移動してもドアは開きません。

GIF

概要

今回は自動ドアをどのように作成するかの概要から説明します。

センサー

  • MonoBehaviourを継承したSensorスクリプトを作成
  • OnTriggerEnter/OnTriggerExitでBoxColliderに入ったオブジェクトの数を管理
  • SensorオブジェクトにSensorスクリプトを追加。

自動ドア

ドアの動作をステートに分割すると以下のようになります。

Closedステート

  • Doorが閉まっているステート。
  • Sensor内に誰か入るとOpeningステートに遷移する。
  • Indicatorは赤色に点灯する。

Openingステート

  • Doorを移動させて開く動作を行うステート。
  • 開ききるとOpenedステートに遷移する。
  • Indicatorはマゼンタ色に点灯する。

Openedステート

  • Doorが開いているステート。
  • Sensor内に誰もいなければClosingステートに遷移する。
  • Indicatorは緑色に点灯する。

Closingステート

  • Doorを移動させて閉じる動作を行うステート。
  • 閉じきるとClosedステートに遷移する。
  • Indicatorはオレンジ色に点灯する。

それでは自動ドアの作成を行っていきましょう。

センサーの作成

Unityのトリガー機能を使用してBoxColliderの中に入っているキャラクターを格納するスクリプトを作成していきます。

今回はArborの機能を使用せず、MonoBehaviourを継承したクラスを作ります。

スクリプトファイルの作成

  • ProjectウィンドウでAutoDoorフォルダを選択。
  • +ボタンをクリックし、メニューから「C# Script」を選択。
  • 名前を「Sensor」にして確定。
    • メニューから選択すると自動的に名前入力モードになります。
      その状態で名前を入力して確定してください。
    • デフォルト名のまま一度確定してしまったり、名前を間違って後から変更した場合はクラス名も変更する必要があります。
      コードの修正箇所がわからない場合は、一度スクリプトを削除して作り直してください。
GIF

コーディング

  • 作成したSensorスクリプトをダブルクリックしコードエディタを開く。
  • 以下のコードを記入。

センサーの設定

Sensorオブジェクトを設定します。

  • HierarchyのAutoDoor/Sensorオブジェクト選択。
  • Add Componentボタンをクリック。
  • コンポーネント選択ウィンドウから「ArborTutorial06 > Sensor」を選択。
GIF

この段階でプレイして、プレイヤーをセンサー内へ出入りさせるとConsoleに個数が表示されます。
センサーに反応してドアを開く動作はまだできていないため、ゲーム画面だけ見ると変化はありません。

GIF

センサーによる遷移スクリプトの作成

SensorクラスのenteredCountを参照して遷移するStateBehaviourスクリプトを作成します。

今回は簡易に、センサー内に1個以上入っているかどうかで遷移させるようにします。

スクリプトファイルの作成

  • ProjectウィンドウでAutoDoorフォルダを選択。
  • +ボタンをクリックし、メニューから「Arbor > StateBehaviour C# Script」を選択。
  • 名前を「SensorTransition」にして確定。
    • メニューから選択すると自動的に名前入力モードになります。
      その状態で名前を入力して確定してください。
    • デフォルト名のまま一度確定してしまったり、名前を間違って後から変更した場合はクラス名も変更する必要があります。
      コードの修正箇所がわからない場合は、一度スクリプトを削除して作り直してください。
GIF

コーディング

  • 作成したSensorTransitionスクリプトをダブルクリックしコードエディタで開く。
  • 以下のコードを記入。

コードの補足

FlexibleComponentについて

  • コンポーネントへの参照を柔軟に取り扱えるクラスです。
  • このようなFlexible**クラスを使用すると直接指定に加えて、データフローやパラメータからの入力なども柔軟に扱えます。
  • SlotType属性はFlexibleComponent型などで参照する型を指定するための特別な属性です。
  • また、Unity2020以降ではジェネリック型のシリアライズが可能になりましたので、SlotType属性を使用せずにFlexibleComponent<Sensor>もフィールドに直接使用可能になりました。

自動ドアのステートマシンの作成

ArborFSMコンポーネントの追加

AutoDoorにステートマシン用コンポーネントを追加します。

  • HierarchyからAutoDoorオブジェクトを選択。
  • InspectorのAdd Componentボタンをクリック。
  • コンポーネント選択ウィンドウから「Arbor > ArborFSM」を選択。
  • 追加されたArborFSMのOpen EditorボタンをクリックしてArbor Editorウィンドウを開く。
GIF

パラメータの作成

複数のステートで同じ値を使用する場合はパラメータを設定しておくと後からの変更にも便利です。

今回はコンポーネント用パラメータを作成します。

グラフに紐づいたParameterContainerの作成

  • サイドパネルのパラメータタブを選択。
  • 「作成」ボタンをクリック。
GIF

Doorパラメータ

DoorオブジェクトのTransformコンポーネントを参照するパラメータを作成します。

  • パラメータタブの+ボタンをクリック。
  • パラメータのタイプ選択ウィンドウから「UnityObject > Component」を選択。
  • 名前を「Door」に設定。
  •  型のドロップダウンをクリック。
  • 型選択ウィンドウから「UnityEngine > Transform」を選択。
  • Valueを「Doorオブジェクト」に設定。
GIF

Sensorパラメータ

SensorオブジェクトのSensorコンポーネントを参照するパラメータを作成します。

  • パラメータタブの+ボタンをクリック。
  • パラメータのタイプ選択ウィンドウから「UnityObject > Component」を選択。
  • 名前を「Sensor」に設定。
  • 型のドロップダウンをクリック。
  • 型選択ウィンドウから「ArborTutorial06 > Sensor」を選択。
  • Valueを「Sensorオブジェクト」に設定。
GIF

Indicatorパラメータ

IndicatorオブジェクトのRendererコンポーネントを参照するパラメータを作成します。

  • パラメータタブの+ボタンをクリック。
  • パラメータのタイプ選択ウィンドウから「UnityObject > Component」を選択。
  • 名前を「Indicator」に設定。
  • 型のドロップダウンをクリック。
  • 型選択ウィンドウから「UnityEngine > Renderer」を選択。
  • Valueを「Indicatorオブジェクト」に設定。
GIF

Closedステート

Closedステートはドアが閉じきった状態です。
Indicatorは赤。Sensor内に誰かいるなら開く動作に遷移します。

Closedステートの作成

Closedステートを作成します。

  • グラフ上を右クリック。
  • メニューから「ステート作成」を選択。
  • 名前を「Closed」にして確定。
GIF

SetRendererColorの追加

Indicatorの色を変更します。

色の変更には今回SetRendererColorを使用します。

詳細はSetRendererColorを参照してください。

ClosedステートにSetRendererColorを追加します。

今回はステートの設定メニューではなく「挙動挿入」ボタンから追加してみましょう。

  • Closedステート下部中央にマウスカーソルを移動。
  • 表示された「挙動挿入」ボタンをクリック。
  • メニューから「挙動追加」を選択。
  • 挙動選択ウィンドウから「Renderer > SetRendererColor」を選択。
GIF

SetRendererColorの設定

SetRendererColorを以下のように設定します。

Renderer Indicatorパラメータ

  • パラメータタブからIndicatorパラメータのピンをドラッグ。
  • SetRendererColorのRendererフィールドに表示された「Drop Parameter」へドロップ。

GIF
Color 赤(FF0000)

SensorTransitionの追加

Sensor内の判定には先ほど作成したSensorTransitionを使用します。

ClosedステートにSensorTransitionを追加してください。

  • Closedステート下部中央にマウスカーソルを移動。
  • 表示された「挙動挿入」ボタンをクリック。
  • メニューから「挙動追加」を選択。
  • 挙動選択ウィンドウから「ArborTutorial06 > SensorTransition」を選択。
GIF

SensorTransitionの設定

SensorTransitionを以下のように設定します。

Sensor Sensorパラメータ

  • パラメータタブからSensorパラメータのピンをドラッグ。
  • SensorTransitionのSensorフィールドに表示された「Drop Parameter」へドロップ。

GIF
Entered Openingステートを作成して接続

  • Enteredをドラッグ。
  • ノード右側で離す。
  • メニューから「ステート作成」を選択。
  • 名前を「Opening」にして確定。

GIF

Closedステートはこのようになります。

Openingステート

Openingステートはドアを徐々に開く動作をする状態です。
Indicatorはマゼンタ。Doorを徐々に開き、開ききったら遷移します。

SetRendererColorのコピー&ペースト

OpeningステートでのIndicatorの色を設定するため、ClosedステートからSetRendererColorをコピー&ペーストします。

今回は挙動のタイトルバーのドラッグ&ドロップで複製してみましょう。

  • ClosedステートのSetRendererColorのタイトルバーをドラッグ。
  • (Windows)Ctrl/(Mac)commandを押しながらOpeningステートにドロップ。
GIF

SetRendererColorの設定

SetRendererColorを以下のように設定します。

Color マゼンタ(FF00FF)

TweenPositionの追加

開く動作にはTweenPositionを使用します。

詳細はTweenPositionを参照してください。

OpeningステートにTweenPositionを追加します。

  • Openingステート下部中央にマウスカーソルを移動。
  • 表示された「挙動挿入」ボタンをクリック。
  • メニューから「挙動追加」を選択。
  • 挙動選択ウィンドウから「Tween > TweenPosition」を選択。
GIF

TweenPositionの設定

TweenPositionを以下のように設定します。

Target Doorパラメータ

  • パラメータタブからDoorパラメータのピンをドラッグ。
  • TweenPositionのTargetフィールドに表示された「Drop Parameter」へドロップ。

GIF
TweenMoveType To Absolute
To 3, 0, 0
Next State Openedステートを作成して接続。

  • Next Stateをドラッグ。
  • ノード右側で離す。
  • メニューから「ステート作成」を選択。
  • 名前を「Opened」にして確定。

GIF

Openingステートはこのようになります。

Openedステート

Opendステートはドアが開ききった状態です。
Indicatorは緑。Sensor内に誰もいなくなったら閉じる動作に遷移します。

SetRendererColorのコピー&ペースト

Indicatorの色を設定するため、OpeningステートからSetRendererColorをコピー&ペーストします。

  • OpeningステートのSetRendererColorのタイトルバーをドラッグ。
  • (Windows)Ctrl/(Mac)commandを押しながらOpenedステートにドロップ。
GIF

SetRendererColorの設定

SetRendererColorを以下のように設定します。

Color 緑(00FF00)

SensorTransitionのコピー&ペースト

Sensor内の判定のため、ClosedステートからSensorTransitionをコピー&ペーストします。

ここでは多少ステート間の距離がありますので、ドラッグでのコピーではなく、メニューからコピーし「挙動挿入」ボタンで貼り付けを行ってみましょう。

  • ClosedステートのSensorTransitionの設定アイコンをクリック。
  • メニューから「コピー」を選択。
  • Openedステート下部中央にマウスカーソルを移動。
  • 表示された「挙動挿入」ボタンをクリック。
  • メニューから「挙動を貼り付け」を選択。
GIF

SensorTransitionの設定

SensorTransitionを以下のように設定します。

Entered 切断

  • 接続線を右クリック。
  • メニューから「切断」を選択。

GIF
Nothing Closingステートを作成して接続。

  • Nothingをドラッグ。
  • ノード右側で離す。
  • メニューから「ステート作成」を選択。
  • 名前を「Closing」にして確定。

GIF

Openedステートはこのようになります。

Closingステート

Closingステートはドアを徐々に閉じる動作をする状態です。
Indicatorはオレンジ。Doorを徐々に閉じ、閉じきったらClosedステートに遷移します。

SetRendererColorのコピー&ペースト

Indicatorの色の設定のため、OpenedステートからSetRendererColorをコピー&ペーストします。

  • OpenedステートのSetRendererColorのタイトルバーをドラッグ。
  • (Windows)Ctrl/(Mac)commandを押しながらClosingステートにドロップ。
GIF

SetRendererColorの設定

SetRendererColorを以下のように設定します。

Color オレンジ(FF8000)

TweenPositionのコピー&ペースト

閉じる動作のため、OpeningステートからTweenPositionをコピー&ペーストします。

  • OpeningステートのTweenPositionの設定アイコンをクリック。
  • メニューから「コピー」を選択。
  • Closingステート下部中央にマウスカーソルを移動。
  • 表示された「挙動挿入」ボタンをクリック。
  • メニューから「挙動を貼り付け」を選択。
GIF

TweenPositionの設定

TweenPositionを以下のように設定します。

To 0, 0, 0
Next State Closedステートに接続。

グラフ全体がこのようになります。

接続線の整理

接続線の方向が1つだけ違い、線とノードが重なって見えづらくなってしまいましたので、リルートノードを使って整理しましょう。

  • Closingステートに近い接続線上を右クリック。
  • メニューから「リルート」を選択。
  • Closingステートの上方右端にリルートノードを移動。
GIF
  • Closedステートに近い接続線上を右クリック。
  • メニューから「リルート」を選択。
  • Closedステートの上方左端にリルートノードを移動。
GIF

これで、Closingステートの右側から出て上方を迂回し、Closedステートの左側に入る接続線ができました。

動作確認

ここでプレイして確認してみましょう。

GIF

赤い半透明エリアまで移動するとドアが開き、そこから出ると閉じます。

自動ドアの改善

基本的な動作はできましたが、まだ細部で気になる点があります。

これから動作を改善していきましょう。

Closingステートの改善

この自動ドアは一度閉じ始めたら閉じきるまで動作は切り替わりません。
これではキャラが挟まってしまい意図せずめり込んでしまう可能性もあります。

GIF

閉じてる動作中も誰か入ってきたなら開きなおすように改善しましょう。

SensorTransitionのコピー&ペースト

ClosedステートからSensorTransitionをコピー&ペーストします。

  • ClosedステートのSensorTransitionの設定アイコンをクリック。
  • メニューから「コピー」を選択。
  • Closingステート下部中央にマウスカーソルを移動。
  • 表示された「挙動挿入」ボタンをクリック。
  • メニューから「挙動を貼り付け」を選択。
GIF

遷移先はOpeningになっているので、設定を変える必要はありません。

開きなおす動作についての補足

OpeningステートのTweenPositionでは、Tween Move Typeが「To Absolute」になっていますのでステート開始時点での位置からToに指定した位置まで徐々に移動するようになります。

例えばClosingステートで半分まで閉じた状態からOpeningステートに遷移した場合は、半分閉じた位置から徐々に開ききる位置まで移動するようになります。

ただしDurationは固定で1秒ですので、開ききるまでの移動速度は通常時に比べて遅くなってしまいます。
等速移動が必要であれば、他の挙動に変えたり自作のスクリプトを作成すると良いかと思います。

接続線の整理

接続線が重なってしまっていますのでリルートで整理します。

  • Openingステートに近い接続線上を右クリック。
  • メニューから「リルート」を選択。
  • Openingステートの下方左側にリルートノードを移動。
GIF

これで、Closingステートの左側から出て下方を通って、Openingステートの右側に入る接続線になりました。

動作確認

プレイして確認してみましょう。

GIF

閉じてる動作中にセンサー内に入ると開きなおす動作に切り替わるのが確認できます。

Waitステートの作成

誰もいなくなってからすぐに閉じ始めると、センサー内に出入りするのを繰り返したときに動きが敏感になってしまいますので、待機時間を設けて余裕を持たせてみましょう。

ここでは、Waitステートは開ききって誰もいない時に1秒待機する状態とします。
Indicatorは黄色。1秒経過したら、Closingステートに遷移します。

ノードの移動

OpenedステートとClosingステートの間に1ノード分スペースを確保します。

  • ClosingステートとClosing上方にあるリルートノードを範囲選択で選択。
  • おおよそ1ノード分右に移動させる。
GIF

Waitステートの作成

OpenedステートのSensorTransitionのNothingからノード右側にWaitステートを作成して接続します。

  • OpenedステートのSensorTransitionのNothingをドラッグ。
  • ノード右側で離す。
  • メニューから「ステート作成」を選択。
  • 名前を「Wait」にして確定。
GIF

SetRendererColorのコピー&ペースト

OpenedステートからSetRendererColorをコピー&ペーストします。

  • OpenedステートのSetRendererColorのタイトルバーをドラッグ
  • (Windows)Ctrl/(Mac)commandを押しながらWaitステートにドロップ。
GIF

SetRendererColorの設定

SetRendererColorを以下のように設定します。

Color 黄色(FFFF00)

TimeTransitionの追加

TimeTransitionを追加します。

  • Waitステート下部中央にマウスカーソルを移動。
  • 表示された「挙動挿入」ボタンをクリック。
  • メニューから「挙動追加」を選択。
  • 挙動選択ウィンドウから「Transition > TimeTransition」を選択。
GIF

TimeTransitionの設定

TimeTransitionを以下のように設定します。

Seconds 1
Next State Closingステートに接続。

動作確認

プレイして確認してみましょう。

GIF

開ききってセンサーから出ると、Indicatorが黄色になり1秒後に閉じ始めます。

Waitステートの改善

Closingステートにあった欠陥がこのステートにも同様にあります。
待機中に再度入っても、1秒立ったら強制的に閉じようとしてしまます。
ただしClosingステートですでに改善済みなのでドア自体は動きませんが、ステート自体は遷移されています。
(Arbor Editorウィンドウを見るか、Indicatorの色が切り替わっているかで確認できます)

GIF

待機中にセンサーの中に入って来たらOpenedステートに戻しましょう。

SensorTransitionのコピー&ペースト

ClosingステートからSensorTransitionをコピー&ペーストします。

  • ClosingステートのSensorTransitionのタイトルバーをドラッグ
  • (Windows)Ctrl/(Mac)commandを押しながらWaitステート下部にドロップ。
GIF

SensorTransitionの設定

SensorTransitionを以下のように設定します。

Entered Openedステートに接続。

AutoDoorのArborFSMは最終的に以下のようになります。

最終確認

最後にもう一度プレイして、センサー内の出入りと自動ドアの連動、Indicatorの色が正しいか確認してみてください。

GIF

以上でこのチュートリアルは完了です。

課題

最後に、このチュートリアルからのステップアップとしていくつか課題を記しますので参考にしてみてください。

移動速度の改善

開きなおす動作についての補足でも触れたとおり、TweenPositionは時間指定の移動補間を行う挙動となっています。
例えばWaitステートからClosingステートに切り替わった直後にセンサー内に入ると、ドアがほぼ開いているにもかかわらず必ず1秒間Openingステートを実行してしまいます(Indicatorが1秒間マゼンタになる)。
遷移したタイミングによって移動速度が遅くなるのを改善したいので、速さ指定の移動補間スクリプトが必要になります。
Arbor3.9.0現在では始点と終点を速さ指定で補間移動する挙動は組み込まれていませんので、スクリプトを自作して改善してください。

実用化

このチュートリアルでは説明のためスクリプトは簡略化しています。
センサーは今回のような自動ドアだけでなく、敵AIの視覚判定や重さ検知スイッチなど幅広く活用できますので、実用化していくと良いでしょう。

Sensorスクリプトの改善

  • Playerタグを決め打ちしているので、複数のタグやレイヤーなどで柔軟にマスクできるようにする。
  • Unityの仕様上、トリガー内でDestroyされるとOnTriggerExitは呼ばれないのでその対処。
  • _EnteredObjectsの中から一番近い対象の取得などができると敵AIにも活用できる。
  • 何かが入った瞬間、出た瞬間のコールバック
  • 子オブジェクトに複数のColliderがある場合に、Collider個別に扱うかキャラクターとして扱うのかの指定。
    • Collider.attachedRigidbodyを介すか、など
  • 障害物に隠れている対象の除外
    • センサー内に入ってきたオブジェクトが他の障害物に隠れていた場合に検知しない機能。
    • Raycastで判定するなどして、センサーと対象の間に障害物がないかを検出する。
  • トリガー判定部分とセンサー内に入っているオブジェクト管理のクラスを整理する。
    • TriggerSensorを別途作成しSensorクラスから派生。
    • Sensorクラスは出入りしたオブジェクト管理だけにする。
    • TriggerSensorはOnTriggerEnter/Exitで出入りしたオブジェクトの登録/解除だけを行う。
    • Raycastで判定するRaycastSensorスクリプトなども必要に応じてSensorクラスから派生して作成する。
  • 入っているオブジェクトの信号の強さ(重さなどの追加要素)を加味する。
    • SignalWeightスクリプトを追加し、信号の強さを設定できるようにする。
    • Sensorは出入りしたオブジェクトのSignalWeightコンポーネントを取得し、信号の強さを合算する。
    • また距離や視線方向により信号の強さを減衰する機能なども必要に応じて追加。

SensorTransitionスクリプトの改善

  • 何体(あるいは信号の強さ)との比較演算。
  • タグやレイヤーなどの条件判定をさらに行う。

SensorEnterTransition/SensorExitTransitionスクリプトの追加

  • 入った瞬間、出た瞬間の遷移。
  • 出入りした対象をOutputSlotGameObjectで出力してターゲットにできるようにする。

GetSensorClosestObjectCalculatorスクリプトの追加

  • センサー内にいる一番近い対象を返す演算ノード用スクリプト。

デコレータ

  • StateBehaviourスクリプトだけではなくDecoratorスクリプトも作成する。

実用的なセンサーシステムを本格的に作るのも大変ですので他社製センサーアセットを導入し、Arborのグラフで使用するスクリプトだけを作成するのも良いかと思います。