0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PlayMaker FSMの例外耐性を強化する

Posted at

はじめに

Playmakerを使ったUnityプロジェクトでは、
NullReferenceException / MissingReferenceException / 予期せぬError が原因で
FSMが停止し、ゲームが進行不能になるケースがあります。

特に開発中は、複数オブジェクトにまたがる多数のFSMを扱っている場合、
ステートブラウザで停止ステート自体は確認できるものの、
例外の発生原因や伝播元までは把握しづらく、調査に時間がかかることがあります。

また、例外発生時に処理をスキップしたり、特定のアクションへ遷移させるなど、
明示的なエラーハンドリングを行いたいケースもあります。

そこで、本記事では PlayMaker FSM における例外対策構成 を整理します。


全体構成

Playmakerの例外耐性を改善するには、以下の構成が想定されます。

  1. Nullチェック → 無効参照の予防(事前検証)
  2. Unityログフック → 例外検知(事後対応)
  3. ネイティブクラッシュ対策 → OSレベルクラッシュの監視(最終レイヤー)

1. Nullチェック(事前検証)

なぜ必要?

Playmakerアクションの多くは参照先が「null に弱い」。
NullReferenceException / MissingReferenceException が起きた瞬間に
そのステートごとFSMが停止 する。

推奨アクション

  • GameObject Is Null(標準アクション)
  • Array Contains(標準アクション)
  • GameObjectsAreNull(カスタムアクション)←複数参照の一括チェックが可能
using System.Collections.Generic;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory("General")]
    [Tooltip("複数のGameObject変数に null が含まれているかを判定するアクションです。")]
    public class GameObjectsAreNull : FsmStateAction
    {
        [RequiredField]
        [UIHint(UIHint.Variable)]
        [Tooltip("チェック対象となる GameObject 型のFsm変数配列")]
        public FsmGameObject[] gameObjects;

        [Tooltip("1つでも null が見つかった場合に送信されるイベント")]
        public FsmEvent isNull;

        [Tooltip("すべて null でなかった場合に送信されるイベント")]
        public FsmEvent isNotNull;

        [UIHint(UIHint.Variable)]
        [Tooltip("判定結果(true = nullあり)を格納するオプションのBool変数")]
        public FsmBool storeResult;

        [Tooltip("毎フレームチェックする場合は true。false の場合は開始時のみチェック")]
        public bool everyFrame;

        public override void Reset()
        {
            // デフォルト値を初期化
            gameObjects = null;
            isNull = null;
            isNotNull = null;
            storeResult = null;
            everyFrame = false;
        }

        public override void OnEnter()
        {
            // ステート突入時にチェック
            DoAreGameObjectsNull();

            // 毎フレームチェック不要ならすぐ終了
            if (!everyFrame) Finish();
        }

        public override void OnUpdate()
        {
            // everyFrame = true の場合、毎フレームチェックし続ける
            DoAreGameObjectsNull();
        }

        void DoAreGameObjectsNull()
        {
            bool anyNull = false;

            // 配列の中に null が存在するかループして確認
            foreach (var go in gameObjects)
            {
                // FsmGameObject.Value が UnityEngine.Object の参照
                if (go.Value == null)
                {
                    anyNull = true;
                    break;
                }
            }

            // 結果を変数に保存(任意)
            if (storeResult != null)
                storeResult.Value = anyNull;

            // null が見つかったかどうかでイベントを送信
            Fsm.Event(anyNull ? isNull : isNotNull);
        }
    }
}

導入ポイント

  • シーン遷移直後
  • プレハブ生成直後
  • 外部データ読込後
  • 参照が不安定なステート開始直後

この段階で止めるだけで 多くの例外が未然に防げます。


2. Unityログフック(事後対応)

どう動く?

Unity の Application.logMessageReceived
Editor / 開発ビルド / リリースビルド問わず実行されるログコールバック

ここで LogType.ExceptionLogType.Error を拾い、
Playmakerの FSM にイベント送信する。

おすすめ構成

例外を捕捉 → FSMイベントを送信 → エラー処理ステートへ遷移

using UnityEngine;
using HutongGames.PlayMaker;

[ActionCategory("General")]
[Tooltip("Unity のログ出力(Console)に送られる例外やエラーを検知し、FSM にイベント送信するアクション")]
public class CatchExceptions : FsmStateAction
{
    [UIHint(UIHint.FsmEvent)]
    [Tooltip("例外やエラーが検知された際に送信されるイベント")]
    public FsmEvent onException;

    [UIHint(UIHint.Variable)]
    [Tooltip("検知した例外メッセージ(logString)を格納する文字列変数")]
    public FsmString exceptionMessage;

    public override void OnEnter()
    {
        // Unity のログコールバックへハンドラを登録
        // これにより、「例外/エラーがUnity Consoleへ送られる瞬間」を拾える
        Application.logMessageReceived += HandleLog;
    }

    public override void OnExit()
    {
        // このステートから離れるときは確実に登録を解除
        Application.logMessageReceived -= HandleLog;
    }

    /// <summary>
    /// Unity がログを出力するたびに呼び出されるコールバック
    /// logString   : メッセージ部分(例外メッセージ含む)
    /// stackTrace  : スタックトレース(今回は未使用)
    /// type        : LogType(Log / Warning / Error / Exception)
    /// </summary>
    private void HandleLog(string logString, string stackTrace, LogType type)
    {
        // Error または Exception の場合のみ検知
        // → Info や Warning はエラーではないので無視
        if (type == LogType.Exception || type == LogType.Error)
        {
            // 例外メッセージを PlayMaker の FsmString に格納
            exceptionMessage.Value = logString;

            // PlayMaker FSM に例外イベントを送信
            Fsm.Event(onException);
        }
    }
}


対応できる例外

  • NullReferenceException
  • MissingReferenceException
  • ArgumentException
  • IndexOutOfRangeException
  • Playmakerアクション内部の例外
  • 開発者が throw した例外

→ リリースビルドでも動く

活用例

  • 例外発生→エラー画面へ誘導
  • セーブデータ退避
  • ログ送信
  • 再起動誘導
  • 安全モード起動

3. ネイティブクラッシュ対策(最終レイヤー)

Unity では拾えない例外が存在する

  • OSによる強制終了
  • ネイティブプラグインの abort
  • メモリ不足による落ち
  • GPUドライバクラッシュ

これらは Unity のログに届かないため、
logMessageReceived でも拾えない

推奨対策

  • Android:Firebase Crashlytics
  • iOS:Xcode Symbolicated Crash Log / Firebase Crashlytics
  • PC:Unity Cloud Diagnostics

OSレベルのクラッシュログも収集できる。


最終構成案

ーーーーーーーーーーーーーーーーーーーーーーー
    1. Nullチェック(事前検証)
  GameObjectsAreNull、Find 失敗検知など
ーーーーーーーーーーーーーーーーーーーーーーー
                   ↓
ーーーーーーーーーーーーーーーーーーーーーーー
    2. Unityログフック(例外監視)
  LogType.Error / Exception → FSMイベント送信
ーーーーーーーーーーーーーーーーーーーーーーー
                   ↓
ーーーーーーーーーーーーーーーーーーーーーーー
    エラー専用FSM(フォールバック処理)
  ・安全モード移行
  ・保存データの退避
  ・ユーザー通知
ーーーーーーーーーーーーーーーーーーーーーーー
                   ↓
ーーーーーーーーーーーーーーーーーーーーーーー
    3. ネイティブクラッシュ対策(最終レイヤー)
  Crashlytics / Unity Cloud Diagnostics
ーーーーーーーーーーーーーーーーーーーーーーー

この構成にするメリット

  • Playmakerステートグラフ内で「try-catch と同等の例外耐性」を実現
  • リリースビルドでも例外を拾える
  • ユーザークラッシュの原因を正しく特定できる

まとめ

Playmakerは便利な反面、例外耐性が弱いという根本的な弱点があります。
しかし本記事の構成を導入することで、

  • 事前のNull検証
  • 実行時例外の即時検知
  • OSクラッシュの収集(オプション)

という3段階の安全網で、安心!x3

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?