【UEFN】Verseでプレイヤーの状態変更の起点を取得していろいろやってみる

## はじめに
本日は二日目ですよろしくお願いします。
今回は自分の操作しているプレイヤーの状態変更の起点をVerseで取得する方法を書いていきます。

## やること

### 動画
<iframe width="560" height="315" src="https://www.youtube.com/embed/KN9jFeZhvaU?si=wJ62oZzcfsY1BLdi" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

### 利用するモジュール
`ChracterModule`の持つ`fort_character`インターフェースを利用してみます。
以下が今回利用する`fort_character`インターフェースになります。敵を倒したときのイベントやプレイヤーがしゃがんでるときや走っている状態などが取得できそうですね。
```
 fort_character<native><public> := interface<unique><epic_internal>(positional, healable, healthful, damageable, shieldable, game_action_instigator, game_action_causer):
        # Returns the agent associated with this `fort_character`. Use this when interacting with APIs that require an `agent` reference.
        GetAgent<public>()<transacts><decides>:agent

        # Signaled when this `fort_character` is eliminated from the match.
        EliminatedEvent<public>():listenable(elimination_result)

        # Returns the rotation where this `fort_character` is looking or aiming at.
        GetViewRotation<public>()<transacts>:rotation

        # Returns the location where this `fort_character` is looking or aiming from.
        GetViewLocation<public>()<transacts>:vector3

        # Signaled when this `fort_character` jumps. Returns a listenable with a payload of this `fort_character`.
        JumpedEvent<public>():listenable(fort_character)

        # Signaled when this `fort_character` changes crouch state.
        # Sends `tuple` payload:
        #  - 0: the `fort_character` that changed crouch states.
        #  - 1: `true` if the character is crouching. `false` if the character is not crouching.
        CrouchedEvent<public>():listenable(tuple(fort_character, logic))

        # Signaled when this `fort_character` changes sprint state.
        # Sends `tuple` payload:
        #  - 0: the `fort_character` that changed sprint state.
        #  - 1: `true` if the character is sprinting. `false` if the character stopped sprinting.
        SprintedEvent<public>():listenable(tuple(fort_character, logic))

        # Succeeds if this `fort_character` is in the world and has not been eliminated. Most fort_character actions will silently fail if this fails. Please test IsActive if you want to handle these failure cases rather than allow them to silently fail.
        IsActive<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is in the 'Down But Not Out' state. In this state the character is down but can still be revived by teammates for a period of time.
        IsDownButNotOut<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is crouching.
        IsCrouching<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is standing on the ground.
        IsOnGround<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is standing in the air.
        IsInAir<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is inside water volume.
        IsInWater<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is in falling locomotion state.
        IsFalling<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is in gliding locomotion state.
        IsGliding<public>()<transacts><decides>:void

        # Succeeds if this `fort_character` is in flying locomotion state.
        IsFlying<public>()<transacts><decides>:void

        # Puts this `fort_character` into stasis, preventing certain types of movement specified by `Args`.
        PutInStasis<public>(Args:stasis_args)<transacts>:void

        # Release this `fort_character` from stasis.
        ReleaseFromStasis<public>()<transacts>:void

        # Sets this `fort_character` visibility to visible.
        Show<public>():void

        # Sets this `fort_character` visibility to invisible.
        Hide<public>():void

        # Control if this `fort_character` can be damaged.
        SetVulnerability<public>(Vulnerable:logic)<transacts>:void

        # Succeeds if this `fort_character` can be damaged. Fails if this `fort_character` cannot be damaged.
        IsVulnerable<public>()<transacts><decides>:void

        # Teleports this `fort_character` to the provided `Position` and applies the yaw of `Rotation`. Will fail if the `Position` specified is e.g. outside of the playspace or specifies a place where the character cannot fit.
        TeleportTo<public>(Position:vector3, Rotation:rotation)<transacts><decides>:void
```
## 実践
### `fort_character`をトリガーで取得してみる

```
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Characters }<-これは忘れずに追加すること！！
using { /UnrealEngine.com/Temporary/Diagnostics }

player_event_device := class(creative_device):

    @editable
    trigger:trigger_device=trigger_device{}

    OnBegin<override>()<suspends>:void=
        trigger.TriggeredEvent.Subscribe(OnPlayerInitialize)

    OnPlayerInitialize(QAgent : ?agent):void=
        if (Agent:=QAgent?):
            if (FortCharacter:= Agent.GetFortCharacter[]):
```
これでトリガーを踏んだプレイヤーの`fort_character`が取得できるようになります！

usingを確実に宣言することを忘れないでください！！↓
```
using { /Fortnite.com/Characters }
```
if文で囲って取得するのにちょっと違和感感じますよねｗ

```
if (Agent:=QAgent?):
            if (FortCharacter:= Agent.GetFortCharacter[]):
```

### プレイヤーの状態変更を起点に何かする
```
    @editable
    JumpText:billboard_device=billboard_device{}

    @editable
    SprintText:billboard_device=billboard_device{}

    @editable
    Crouched:billboard_device=billboard_device{}

    var jumpCount : int = 0

    JumpMessage<localizes>(msg:string):message="{msg}"
    SprintMessage<localizes>(msg:string):message="{msg}"
    CrouchedMessage<localizes>(msg:string):message="{msg}"


OnPlayerInitialize(QAgent : ?agent):void=
        if (Agent:=QAgent?):
            if (FortCharacter:= Agent.GetFortCharacter[]):
                FortCharacter.JumpedEvent().Subscribe(OnJumpedEvent)
                FortCharacter.SprintedEvent().Subscribe(OnSprintedEvent)
                FortCharacter.CrouchedEvent().Subscribe(OnCrouchedEvent)

OnJumpedEvent(FortCharacter:fort_character):void=
        set jumpCount += 1
        JumpText.SetText(JumpMessage("Jumped Count : {jumpCount} "))
                
    OnSprintedEvent(FortCharacter:fort_character,isSprint:logic):void=
        if (isSprint = true):
            SprintText.SetText(SprintMessage("Sprinted"))
        else:
            SprintText.SetText(SprintMessage("Stopped Sprinting"))

    OnCrouchedEvent(FortCharacter:fort_character,isCrouch:logic):void=
        if (isCrouch = true):
            Crouched.SetText(CrouchedMessage("Crouched"))
        else:
            Crouched.SetText(CrouchedMessage("Stopped Crouching"))
```

最初に貼った`fort_character`の中に戻り値が`listenable`と宣言されてあるものだとその状態変更を起点に自分で指定したメソッドを宣言することができます。

今回はジャンプしたときや走ったとき、しゃがんだ時にbillboardのテキストを変更するようなものを作成しました。

### コード全文
```
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/Diagnostics }

player_event_device := class(creative_device):

    @editable
    trigger:trigger_device=trigger_device{}

    @editable
    JumpText:billboard_device=billboard_device{}

    @editable
    SprintText:billboard_device=billboard_device{}

    @editable
    Crouched:billboard_device=billboard_device{}

    var jumpCount : int = 0

    JumpMessage<localizes>(msg:string):message="{msg}"
    SprintMessage<localizes>(msg:string):message="{msg}"
    CrouchedMessage<localizes>(msg:string):message="{msg}"

    OnBegin<override>()<suspends>:void=
        JumpText.SetText(JumpMessage("Start"))
        SprintText.SetText(SprintMessage("Start"))
        Crouched.SetText(CrouchedMessage("Start"))
        trigger.TriggeredEvent.Subscribe(OnPlayerInitialize)

    OnPlayerInitialize(QAgent : ?agent):void=
        if (Agent:=QAgent?):
            if (FortCharacter:= Agent.GetFortCharacter[]):
                FortCharacter.JumpedEvent().Subscribe(OnJumpedEvent)
                FortCharacter.SprintedEvent().Subscribe(OnSprintedEvent)
                FortCharacter.CrouchedEvent().Subscribe(OnCrouchedEvent)

    OnJumpedEvent(FortCharacter:fort_character):void=
        set jumpCount += 1
        JumpText.SetText(JumpMessage("Jumped Count : {jumpCount} "))
                
    OnSprintedEvent(FortCharacter:fort_character,isSprint:logic):void=
        if (isSprint = true):
            SprintText.SetText(SprintMessage("Sprinted"))
        else:
            SprintText.SetText(SprintMessage("Stopped Sprinting"))

    OnCrouchedEvent(FortCharacter:fort_character,isCrouch:logic):void=
        if (isCrouch = true):
            Crouched.SetText(CrouchedMessage("Crouched"))
        else:
            Crouched.SetText(CrouchedMessage("Stopped Crouching"))

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692976/77f0228e-55f0-64ae-7fe8-8c41f6036e37.png)


## まとめ
`listenable`はとても便利ですね。いろんなアクションごとに効果音を付けるとかジャンプした回数スコアを追加するとかいろんなことができそうですね！！

がんばっていきましょー

## 余談
この度UEFN/Verseに関するオープンコミュニティサーバーを建ち上げました。ちょっとでも興味があれば奮ってご参加くださいませ。

https://discord.gg/VcJAfxxr