はじめに
連休中、アプリ制作を再開していました。PlaymakerのFSMでオブジェクト参照できなかったら例外処理でそのまま次の処理に飛ばしたいなーとか思い、いくつか方法を考えていました。Playmakerはtry catch的なことがステート間でできません。抜け漏れに弱い。今まで作ってきたよく発生するエラーは、ダントツNullReferenceExceptionやMissingReferenceExceptionだと思います。
対策
シーンの切り替え途中でループが走っていて、シーンのオブジェクトが参照できないとか。変数がnullだったとか。そんな時はエラーで処理が止まってしまうので、せめてユーザーに向けてエラーポップアップを表示したり、一時的にセーブデータを作ったり、ログを吐いたり何か処理をしたい。処理も止めたくない、null前提で動く仕様もあるかと思います。
その1
オブジェクトが破棄されているか、無効になっているかを確認するために、アクセスする前に null チェックを行います。Playmakerの既存アクションでいえば、Game Object Is Nullです。
主だったオブジェクトを参照する前処理にこいつを入れておいてNullの場合何か処理を走らせる、です。一番簡単かつ明示的。でも参照が多いといたるところにこの処理を入れることになるので、あるていど必要な箇所をしぼった実装が現実的ですね。
一度に複数検証したいGameObjectがある場合は、配列とテンプレートを駆使して、複数件対応したnullチェックアクション作ればいいと思います。
オリジナルベースに複数対応のカスタムアクションを作りました。
using System.Collections.Generic;
using UnityEngine;
namespace HutongGames.PlayMaker.Actions
{
[ActionCategory("General")]
[Tooltip("Tests if multiple GameObject Variables have null values. E.g., If the FindGameObject action failed to find objects.")]
public class GameObjectsAreNull : FsmStateAction
{
[RequiredField]
[UIHint(UIHint.Variable)]
[Tooltip("The GameObject variables to test.")]
public FsmGameObject[] gameObjects;
[Tooltip("Event to send if any GameObject is null.")]
public FsmEvent isNull;
[Tooltip("Event to send if all GameObjects are NOT null.")]
public FsmEvent isNotNull;
[UIHint(UIHint.Variable)]
[Tooltip("Store the result in a bool variable.")]
public FsmBool storeResult;
[Tooltip("Repeat every frame. Useful if you want to wait for GameObject variables to be not null (or null) then send an event.")]
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()
{
DoAreGameObjectsNull();
}
void DoAreGameObjectsNull()
{
bool anyNull = false;
foreach (var go in gameObjects)
{
if (go.Value == null)
{
anyNull = true;
break;
}
}
if (storeResult != null)
{
storeResult.Value = anyNull;
}
Fsm.Event(anyNull ? isNull : isNotNull);
}
}
}
直前のステートではなく、その実行ステート内で検証できたほうがフローのとりまわしは良かった。
その2
エラーログからハンドリングするカスタムアクションを作りました。ログのエラーメッセージからLogType.Exception、LogType.Errorの場合Fsm.Eventを走らせます。空のGameObjectにPlaymaker FSMを追加してこのアクションを追加「OnException」イベントを設定して、エラー時の処理を書いてください。「ExceptionMessage」変数にエラー内容が入るので、ログや UI に表示も可能になります。実機側の挙動はまだ確認してませんが・・・。
using UnityEngine;
using HutongGames.PlayMaker;
[ActionCategory("General")]
public class CatchExceptions : FsmStateAction
{
[UIHint(UIHint.FsmEvent)]
public FsmEvent onException;
[UIHint(UIHint.Variable)]
public FsmString exceptionMessage;
public override void OnEnter()
{
Application.logMessageReceived += HandleLog;
}
public override void OnExit()
{
Application.logMessageReceived -= HandleLog;
}
private void HandleLog(string logString, string stackTrace, LogType type)
{
if (type == LogType.Exception || type == LogType.Error)
{
exceptionMessage.Value = logString;
Fsm.Event(onException);
}
}
}
処理が止まった時点でのセーブデータ作成や、不測の例外エラーが出た場合エラーポップアップを表示することでユーザーに伝える仕組みが作れるようになりました。
おわりに
エラーが発生したアプリは止まった方が健全です。
Qittaでも書いたよ。

