こちらはCluster Creator Kit Advent Calendar 2020 12月19日の記事です。
Cluster Creator Kitには、Item Timer・Global Timer・Player Timerという秒数カウント系のコンポーネントが提供されています。しかし、これらには弱点があり、タイマーを止めることができないという仕様になっています。今回は、この仕様を回避する方法をご紹介します。題して、外からイベント発火する強制Invoke、です!
Contents
Item Timer系の弱点
Item TimerやGlobal Timerでも時限発火のメッセージを送ることができます。しかし、これらは
- イベント発火をキャンセルできない
という弱点があります。例えば、「特定エリアに二秒待機でイベントが発火する」みたいなルールだとします。「二秒未満でエリアを出てしまった」というケースだとどうなるでしょうか?
この場合、二秒後にTimerはイベントを発火します。Timerは、指定秒数後にメッセージを発火を予約する機能だからです。予約、というキーワードがとても重要です。そして、一度予約したTimerは残念ながらキャンセルすることができません。
強制Invokeのやり方
今回紹介するギミックはおそらく推奨されていない外法です。公式ドキュメントで言及されていないので、将来使用できなくなる恐れもあります。
先にも説明したように、Timer系はイベントが確定している場合にしか適しません。「イベントを発火を猶予して、猶予中のキャンセルを可能にする」というケースを、今回紹介する強制Invokeで実装することができます。
使用コンポーネント
使用するコンポーネントは以下の通りです。
- Play Timeline Gimmick
- Stop Timeline Gimmick
- Playable Director
- Signal Receiver
- Trigger系コンポーネント
実装方法
強制Invokeは、タイムラインのシグナルを利用します。本来のシグナルの機能は、タイムラインのある地点でシグナルを発信し、あらかじめ登録してあるコールバック(≒イベント)を発火させることです。
実は、このコールバックは特定の条件を満たせば、どんなスクリプトも登録できてしまいます。もちろん、これはCluster Creator Kitも例外ではありません。詳しい実装方法を解説していきます。
まずは、タイムラインとシグナルを作成します。これらを作成するには、プロジェクトウィンドウ上で右クリックし、 Create > Timeline/Signalを押下します。
タイムラインとシグナルを作成したら、Window > Swquencing > Timelineで編集ウィンドウを開きます。
次にSignal Trackを作成し、
そして、遅延したい秒数にSignal Emitterを追加します。
追加したシグナルエミッターを選択し、インスペクターから先ほど作成しておいたシグナルを指定します。これで、シグナル発火地点にタイムラインが到達すると、指定したシグナルが発信するように設定できました。
次に、発信したシグナルを受信するSignal Receiverを設定します。適当なオブジェクトにSignal Receiverを追加し、Signalをタイムラインでセットしたシグナルにし、Ractionには強制Invoke用のTriggerを指定します。そして、隣のプルダウンからInvoke()を指定します(※重要!)。
これで、タイムラインからTrigger系コンポーネントにセットしたイベントを強制的に発火させることができます。
あとは、Play Timeline GimmickやStop Timeline Gimmickで、タイムラインの進行をコントロールするだけです。シグナル発火までにStopさせることができれば、イベントの発火をキャンセルすることができます。
Invokeについて
Cluster Creator Kitに含まれるOnCreateItemTriggerの中身を見てみます。 Clusterの詳しい仕様についてはわかりませんが、Invokeメソッドから、インスペクタに登録されているイベントリストが呼び出されていると読み取れます。そして、Invoke()はpublicに指定されているので、基本的にどこからでもアクセスできるようになっています。
public class OnCreateItemTrigger : MonoBehaviour, IOnCreateItemTrigger
{
.....略.....
public event TriggerEventHandler TriggerEvent;
public void Invoke()
{
TriggerEvent?.Invoke(this, new TriggerEventArgs(triggers.Select(t => t.Convert()).ToArray()));
}
.....略.....
}
ちなみに、On Release Item Triggerだと、このInvokeはprivateに指定されており、そとからアクセスすることができません。同じTrigger系でも、この強制Invokeを使用できるものとできないものがあるので、注意しましょう。
タイムラインから叩けるメソッドはInvokeに限りません。たとえば、Itemコンポーネントを持つオブジェクトに対するGameObject.SetActiveは、オンライン上で同期動作することを確認しました。この他にも、いろいろなコールバックを設定することができます。呼べたとしてもClusterのサーバー上で動作する保証はありませんので、実装する際は、本番環境でのテストを忘れないようにしてください。
※2020/12/29 追記。On Create Item TriggerのInvoke()ですが、サーバー上だと同期しないようです。On Player Join Triggerを使ってください。
強制Invokeの欠点
むりやりイベントを持たせるために、Trigger系コンポーネントを配置していますが、そのTrigger系コンポーネントが普通に使用できることが最大の欠点です。
プレハブ構造上、Play Timeline Gimmickを持つオブジェクトに、イベント保持用のTriggerコンポーネントを配置する必要があるかもしれません。その場合、予期せぬタイミングで強制Invoke用のイベントが発火してしまう恐れがあります。
これを回避するための方法は、On Create Item Triggerを強制Invoke用に使ってしまうのが一番安全だと思います。 On Createは普通のコントロールで呼ばれることがないので、基本的に安全です。 もちろん、On Createはルーム生成時に必ず呼ばれてしまうのですが、ここはロジックで回避することができます。
とりあえず、イベント保持用のTriggerコンポーネントに迷ったらOn Create Item Triggerにしておくといいでしょう。
ロジックの弱点
「二秒後にロジックで判定すればいいのでは?」と考えたあなた、半分正しいです。場合によってはロジックで済む場合もあると思います。
しかし、ロジックだと、次のような場合に判定ができません。先と同じように、二秒待機でイベントを発火する例で考えてみると、
- 二秒後のイベント発火を予約し、その間にエリアを出たり入ったりする
もしゲームのルールが、「一度エリアを出ると数え直し」というものであれば、これでは都合が悪いです。ロジックを使用すると、イベントの発火自体をキャンセルしているわけではないので、必ずメッセージが飛びます。また、エリアに出たり入ったりするということは、その回数分イベント発火が予約されるということです。これも、場合によっては都合が悪いでしょう。
まとめ
このようにタイムラインを経由すると、Unityの仕様上Invokeを呼べてしまいます。すこしややこしい実装にはなってしまいますが、タイムラインと絡めることで、時間に依存するメカニクスを構築できる可能性が非常に広くなると思います。
ぜひ、この強制Invokeをワールド制作に生かしてみてはいかがでしょう。
このほかにも、Clusterゲームワールドコンテストで大賞を受賞したワールドの解説記事を公開しています。ワールド制作の初心者が気を付けるポイントも紹介しています、ぜひこちらもご覧になっていってください。