🐕

[UEFN][verse] UEFNのカスタムイベント実装を考える[3] Eventクラスでイベントを実装する。

2023/08/19に公開

こちらの続きです。

https://zenn.dev/t_tutiya/articles/74f65fbe46dee5

今回はEventクラスを使ったイベント処理を実装します。

サンプルコードの挙動の説明

サンプルコードは、ボタンデバイスにインタラクトすると、3つのスイッチデバイスがトグルする(ON/OFFが切り替わる)という物です。完全なサンプルコードは記事の最後にアップしてあります。

pub_sub_deviceというデバイスをレベルに配置し、detailタブ上で1つのボタンと3つのスイッチを設定します。

各スイッチは個別にON/OFFできます。

上記の状態でボタンにインタラクトすると、以下の様にスイッチが切り替わります。

実行処理としては、各スイッチに対応するタスクを生成して並行処理で実行しています。各タスクは常に待機状態を取り、ボタンがインタラクトされるとスイッチを操作します。

Eventクラス

Eventクラスはタスク(並行処理させている複数の処理単位のこと)の待機状況を制御する機能を持ちます。

https://dev.epicgames.com/documentation/en-us/uefn/verse-api/versedotorg/verse/event/event(t)

Eventクラスは、タスク内で待機状態に入るAwait()メソッドと、その待機状態を(外部から)解除させるためのSignal()メソッドを持ちます。この2つのメソッドを使って、イベント制御を行います。

switch_eventクラス

switch_event := class():
    Switch<public> : switch_device = switch_device{}
    var NowAgent<public>:?agent = false

    Event<public>:event() = event(){}

    Emit<public>(Agent : agent):void=
        set NowAgent = option{Agent}
        Event.Signal()

各タスクが保持し続けるEventクラスのラッパーオブジェクトです(デバイスではありません)。
 Eventクラスインスタンスと、制御対象のスイッチデバイスを保持しています。実装の都合上agentオブジェクトを使い回す必要があり、少し変なコードになっています(もっと上手く書けるのかもしれないけど分からなかった)

※すみませんここ説明が漏れてました。良く見るとevent(){}と、関数呼び出しになっています。これについては次回説明しています。

pub_sub_deviceクラス

pub_sub_device := class(creative_device):
    @editable MyButton : button_device = button_device{}
    @editable SwitchDevices : []switch_device = array{}

    var SwitchEvents : []switch_event = array{}

ボタン(MyButton)とスイッチの配列(SwitchDevices)をdetialタブに表示します。SwitchEventsは動的に生成されるswitch_eventを後から参照するために格納する配列です。

OnBeginメソッド

    OnBegin<override>()<suspends>:void=
        #1
        MyButton.InteractedWithEvent.Subscribe(HandleButtonInteraction) 

        #2
        for:
            SwitchDevice : SwitchDevices
        do:
            #3
            SwitchEvent := switch_event{Switch := SwitchDevice} #3-1
            set SwitchEvents += array{SwitchEvent} #3-2
            spawn{SubscribeHandle(SwitchEvent)} #3-3

ゲーム開始時に実行されるメソッドです。

#1:ボタンのインタラクトイベントにHandleButtonInteractionメソッドを登録します。これ自体は今回の「イベント処理実装」とは直接関係しません。

#2:Detailタブで設定したスイッチを巡回します。

#3-1:switch_eventオブジェクトを生成します。アーキタイプでスイッチを設定しています。
#3-2:生成したオブジェクトを配列に追加します。この配列はHandleButtonInteractionメソッド内で参照します。
#3-3:spwan式でタスクを生成し、SubscribeHandleメソッドを並行処理で実行します。このタスクがイベントハンドラになります。

HandleButtonInteractionメソッド

    HandleButtonInteraction(Agent : agent) : void =
        for:
            SwitchEvent : SwitchEvents
        do:
            SwitchEvent.Emit(Agent)            # publish example

ボタンがインタラクトした時に実行されるメソッドです。SwitchEvents配列を巡回し、個々のswitch_event#Emit()メソッドが実行されます。
念のために書いておくと、今回はイベント処理を自前実装するためにこのような処理になっていますが、そうでない場合は(SwitchEventsではなく)SwitchDevicesを巡回して直接トグルさせた方が楽です。

SubscribeHandleメソッド

    SubscribeHandle(SwitchEvent:switch_event)<suspends>:void=
        SwitchEvent.Event.Await() #1

        #2
        if(Agent := SwitchEvent.NowAgent?){
            SwitchEvent.Switch.ToggleState(Agent)
        }
        
        #3
        spawn{SubscribeHandle(SwitchEvent)}

イベントハンドラ本体です。タスクとして並行処理で実行されています。

#1:Event.Await()メソッドを実行すると、タスクは待機状態になります。つまり、SubscribeHandle()メソッドは実行直後に待機状態になるわけです。
この待機状態は、同じEventオブジェクトのSignal()メソッドが実行されると解除されます。

#2:switch_device#Emit()が実行されると、その内部でEvent.Signal()が実行され、待機状態が解除され、処理が続行されます。保持していたagentオブジェクトを取得し、それを使ってスイッチをトグルします。

#3:再度spawn式を使ってタスクを生成します。これによって、「待機状態解除→スイッチデバイスをトグルする→タスクを生成→そのタスクを待機状態にする」というサイクルを実現しています。
spwan式を実行せずにメソッドを終了すると、イベント処理サイクルを終了できます。なお、spawn式の代わりにloop式を使うことも出来ます。

まとめ

Eventクラスでイベント処理を実装してみました。できるだけ短いコードにしてみたんですが、解説はどうしても長くなってしまいました。また、改めて見直してみると、デバイスが提供しているサブスクライブ周りの処理フローと若干ずれているような気もしていて、少し反省。

今回コードを書いてみて、「もしかして、switch_eventが狭義の「イベントハンドラ」で、HandleButtonInteractionは「イベントハンドラ関数」なんじゃないか?」という気がしてきました。この辺りの用語定義に詳しい方がいましたら、教えて頂けると嬉しいです。

listenableインターフェイスを用いて組み込みの処理フローと互換性を持つコードが書ければとも思うのですが、これは実際出来るかどうかわからないので予告はしないでおきます。

サンプルコード

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

switch_event := class():
    Switch<public> : switch_device = switch_device{}
    var NowAgent<public>:?agent = false

    Event<public>:event() = event(){}

    Emit<public>(Agent : agent):void=
        set NowAgent = option{Agent}
        Event.Signal()

pub_sub_device := class(creative_device):
    @editable MyButton : button_device = button_device{}
    @editable SwitchDevices : []switch_device = array{}

    var SwitchEvents : []switch_event = array{}

    OnBegin<override>()<suspends>:void=
        MyButton.InteractedWithEvent.Subscribe(HandleButtonInteraction)

        for:
            SwitchDevice : SwitchDevices
        do:
            SwitchEvent := switch_event{Switch := SwitchDevice}

            spawn{SubscribeHandle(SwitchEvent)}

            set SwitchEvents += array{SwitchEvent}

    HandleButtonInteraction(Agent : agent) : void =
        for:
            SwitchEvent : SwitchEvents
        do:
            SwitchEvent.Emit(Agent)            # publish example

    SubscribeHandle(SwitchEvent:switch_event)<suspends>:void=
        SwitchEvent.Event.Await()

        if(Agent := SwitchEvent.NowAgent?){
            SwitchEvent.Switch.ToggleState(Agent)
        }
        
        spawn{SubscribeHandle(SwitchEvent)}

続き

https://zenn.dev/t_tutiya/articles/8a531b039e473e

参考リンク

今回のコードはこの投稿を参考にしています(thanx jon4iffy.page)
https://dev.epicgames.com/community/snippets/DeKg/fortnite-awaiting-custom-event

お知らせ

verse言語とUEFNの記事を他にも書いているので御覧下さい。
https://zenn.dev/t_tutiya

最後まで読んで頂きありがとうございました。この記事がお役に立てたようであれば、是非LIKEとフォローをお願いします(今後の執筆のモチベーションに繋がります)。

#Verse #UEFN #Fortnite #Verselang #UnrealEngine

Discussion