はじめに
Playmakerを使ったUnityプロジェクトでは、
NullReferenceException / MissingReferenceException / 予期せぬError が原因で
FSMが停止し、ゲームが進行不能になるケースがあります。
特に開発中は、複数オブジェクトにまたがる多数のFSMを扱っている場合、
ステートブラウザで停止ステート自体は確認できるものの、
例外の発生原因や伝播元までは把握しづらく、調査に時間がかかることがあります。
また、例外発生時に処理をスキップしたり、特定のアクションへ遷移させるなど、
明示的なエラーハンドリングを行いたいケースもあります。
そこで、本記事では PlayMaker FSM における例外対策構成 を整理します。
全体構成
Playmakerの例外耐性を改善するには、以下の構成が想定されます。
- Nullチェック → 無効参照の予防(事前検証)
- Unityログフック → 例外検知(事後対応)
- ネイティブクラッシュ対策 → 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.Exception と LogType.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