[UEFN][verse] UEFNのカスタムイベント実装を考える[6] イベントリスナの実装
前回はこちら
カスタムイベント実装を考える6回目。今回はイベントリスナをカスタム実装してみます。
イベントリスナとは
イベントリスナとは、特定のイベントの発生を監視するメカニズムです。イベントが発生したら[1]、あらかじめ登録されているイベントハンドラを実行します。「イベント駆動」と言った場合、この「イベント発生→処理を実行」という仕組みを指します。
サンプルコードの挙動の説明
サンプルコードでは、スイッチデバイスを4つ用意し、1つのスイッチをトグルにするたびに、残りの3つのスイッチがトグルするという物です。完全なサンプルコードは記事の最後にアップしてあります。
pub_sub_device2というデバイスをレベルに配置し、detailタブ上で1つのスイッチと3つのトグルスイッチを設定します。
前回のサンプルコードでは「ボタン1個+スイッチ3個」の組み合わせだったのですが、Verseから「ボタンを押したタイミング」を監視する方法が無い[2]ため、全部スイッチになっています。わかりにくいので「監視スイッチ(1個)」と「対象スイッチ(3個)」と呼び分けることにします。
イベントリスナの起動とイベントハンドラの登録
まずはpub_sub_device2.OnBegin()
メソッドから見て行きます。
OnBegin<override>()<suspends>:void=
#1
spawn{Listener()}
#2
for:
Switch : Switches
do:
EventObj:switch_event = switch_event{Switch := Switch} #2-1
set SwitchHandelers += array{EventObj.HandleSwitchInteraction} #2-2
#1 イベントリスナの役割を持つListner()
メソッド(後述します)を実行します。Listner()
メソッドはspawn
式によって別タスクで並行処理で実行され、OnBegin()
側の処理は継続します。
#2 Detailタブで設定した対象スイッチをデリゲートとして登録します。
#2-1 イベント発生時に対象スイッチを制御するデリゲートを生成します。デリゲートについては下記記事を参照して下さい。
#2-2 生成したデリゲートのメソッド(≒イベントハンドラ)を配列に追加します。この追加作業は、以前のサンプルで言う下記の処理に対応しています。
MyButton.InteractedWithEvent.Subscribe(HandleButtonInteraction)
余談ですが、本来は、イベントハンドラが登録されて初めてイベントリスナによる監視が始まる構造にすべきかもしれません。
イベントハンドラ(デリゲート)
switch_event := class():
Switch :switch_device
HandleSwitchInteraction(Agent : agent) :void =
Switch.ToggleState(Agent)
監視スイッチがトグルするとHandleSwitchInteraction()
メソッドが実行されます。このメソッドでは割り当てられている対象スイッチをトグルします。
イベントリスナ
#イベント発生を監視する
Listener()<suspends>:void =
#1 監視スイッチの状態初期化
var SwitchStatus :logic = false
#2
loop:
if:
#3 監視スイッチの状態を取得
NowhStatus := logic{MySwitch.GetCurrentState[]}
#4 監視スイッチが変化した場合
SwitchStatus <> NowhStatus
#5 仮のAgent情報を取得
Agent := GetPlayspace().GetPlayers()[0]
then:
#6 監視スイッチの状態を保存
set SwitchStatus = NowhStatus
#7 登録されている対象スイッチの処理を呼びだす
for:
SwitchHandle : SwitchHandelers
do:
SwitchHandle(Agent)
#8 待機
Sleep(0.0)
Listener()
メソッドがイベントリスナ本体になります。このメソッドはタスクで並行処理で実行されています。
#1 スイッチがトグルしたことを判定する為に、前回のスイッチの状態をlogic
型で保持します。ここではfalse
で初期化していますが、本来はゲーム開始時点のスイッチの状態を改めて取得すべきかもしれません。
#2 loop
式でこのタスクを回し続けます。
#3 switch_device.GetCurrentState()
メソッドは失敗許容式で、スイッチのON/OFFを成功/失敗で返します。logic{}
を使うと、失敗許容式の結果をlogic型に変換出来ます[3]。
#4 監視スイッチの現在の状態を以前の状態と比較します。変化している場合のみthen
節が実行されます。
#5 スイッチをトグルする為にagent
オブジェクトが必要な為、ここで適当なagentを取得しています。このコードの意味については別記事で解説予定です。
#6 監視スイッチの状態を更新します
#7 登録されているイベントハンドラを実行します。スイッチのトグルにはagent
オブジェクトが必要なので、イベントハンドラの引数に設定しています。
#8 Sleep(0.0)
は、次のupdateまでAwaitするイディオムだと考えて良いです。これが無いとシステムに処理が戻らないため無限ループになってしまいます[4]。
サンプルコード(全体)
using { /Verse.org/Simulation/Tags }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
custom_event2 := class():
Switch :switch_device
HandleSwitchInteraction(Agent : agent) :void =
Switch.ToggleState(Agent)
pub_test_device2 := class(creative_device):
@editable MySwitch : switch_device = switch_device{}
@editable Switches : []switch_device = array{switch_device{}}
var SwitchHandelers: [](agent->void) = array{}
OnBegin<override>()<suspends>:void=
#イベントリスナを起動
spawn{Listener()}
#対象スイッチを登録する
for:
Switch : Switches
do:
EventObj:custom_event2 = custom_event2{Switch := Switch}
set SwitchHandelers += array{EventObj.HandleSwitchInteraction}
#イベント発生を監視する
Listener()<suspends>:void =
#監視スイッチの状態初期化
var SwitchStatus :logic = false
loop:
if:
#監視スイッチの状態を取得
NowhStatus := logic{MySwitch.GetCurrentState[]}
#監視スイッチが変化した場合
SwitchStatus <> NowhStatus
#仮のAgent情報を取得
Agent := GetPlayspace().GetPlayers()[0]
then:
#監視スイッチの状態を保存
set SwitchStatus = NowhStatus
#登録されている対象スイッチの処理を呼びだす
for:
SwitchHandle : SwitchHandelers
do:
SwitchHandle(Agent)
#待機
Sleep(0.0)
余談
ちなみになんですが、今回はイベントリスナを実装する事が目的だったので長めのコードを書いていますが、通常は組み込みのイベントリスナを使った方が良いです。方法については下の記事を参照。
なお、switch_device.TurnedOnEvent.Await()
の戻り値がagentなので、これを使ってデバイスを制御出来ます。
補足
当初、どう書いてもなぜかしっくり来なくてなんでだろうと悩んでいたんですが、最終的に、これまでサンプルコードで使っていたevent
クラスが今回については必要が無いという事に気づきようやく完成しました。まあこういう事もある。
一点気になってるのは、今のサンプルコード、イベントリスナをspawn
で並行実行してるんですが、これ正しいのかなあ? 公式ドキュメントでは、できるだけbranch
を使うように案内しているんですが、どうもbranch
で上手く実装する方法がイメージできません。
おわりに
イベントまわりについてはほぼほぼ説明できたように思います。やり残した事があるとしたらsubscribable/cancelable
インターフェイス互換性を持つ実装かなあ(そこまでやる必要があるのかという気もしている)。これについては上手いサンプルコードが思いついたら考えます。
続き
お知らせ
verse言語とUEFNの記事を他にも書いているので御覧下さい。
最後まで読んで頂きありがとうございました。この記事がお役に立てたようであれば、是非LIKEとフォローをお願いします(今後の執筆のモチベーションに繋がります)。
#Verse #UEFN #Fortnite #Verselang #UnrealEngine
宣伝
「Unityシェーダープログラミングの教科書」シリーズ1~5をBOOTHで頒布中です。
Discussion