[UEFN][Verse]GamePlayTagを使ってレベル上のアクターにVerseからアクセスする

# はじめに

UE5でも存在する「GamePlayTag」が、UEFNでも使用可能ということで使い方をメモします。
Unityの「Tag」と近い使用感で、アクターにタグを付けることで、レベル上にあるアクターやデバイスの情報にVerse上からアクセスすることができます。

# GamePlayTagをざっくり説明

https://docs.unrealengine.com/4.27/ja/ProgrammingAndScripting/Tags/

元々のUEのドキュメントでは

> Gameplay Tag は概念的なものであり、ユーザー定義の名前を持つ階層ラベルです。

と説明されています。個人的にポイントは **階層**ラベルという点で、Unityと違って階層的にラベリングできるところが便利ポイントに見えます。階層ラベルのイメージは以下です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96970/edc55fc3-234a-5794-8b53-70c2f69c78ae.png)

# UEFNでのGamePlayTag

https://dev.epicgames.com/documentation/ja-jp/uefn/gameplay-tags-in-verse

UEFNでタグ付けできる要素は

- 小道具 (Prop)
    - Fortnite Creativeで提供されているMeshActor
    - 自ら作成するBuilding Prop
- 既存の仕掛け (Devices)
- Verseで作成した仕掛け

です。

# GamePlayTagを定義する

Verseファイル上でTag名を定義する必要があります。

例えば

- major_tags
    - editable_tags
- minor_tags
    - spatial
    - normal

といったタグを定義したいとしましょう。以下のようなVerseファイルを作ります。

```python:tag.verse
using { /Verse.org/Simulation/Tags }

major_tags := class(tag){}
editable_tags := class(major_tags){}
minor_tags := class(tag){}
spatial := class(minor_tags){}
normal := class(minor_tags){}
```

# GamePlayTagを割り当てる

- UEFN の [Outliner (アウトライナー)] で、タグ付けするActorを選択し、[Details (詳細)] パネルを開きます。
- [Details] パネルで、[Add New Component (新しいコンポーネントを追加)] をクリックして、ドロップダウンから [Verse Tag Markup] を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96970/ef28b812-de7a-6e4b-64af-2a2bb200a612.png)

- VerseTagMarkup コンポーネントを選択し、[Details] パネルにその設定を表示します。
- [Gameplay Tags (ゲームプレイ タグ)] で、Tags プロパティを編集し、GameplayTagを追加します。この例では、editable_tagsが仕掛けに追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96970/bb588e11-fc3a-2fec-c22a-a5599a33a679.png)


# GamePlayTagでVerseコードからアクターを見つける

`GetCreativeObjectsWithTag(tag{})`を使って次のように、アクターを検索することができます。

```python
TaggedActors := GetCreativeObjectsWithTag(editable_tags{})
```
これで`editable_tags`タグのついたアクターをVerseから見つけることができます。

for文でタグがついたものを検索し、if文でフィルタリングします。

```python
TaggedActors := GetCreativeObjectsWithTag(editable_tags{})
    for (TaggedActor : TaggedActors):
        if(Prop := creative_prop[TaggedActor]):
            Prop.Hide() //Propに関する処理
```

このif文のフィルタリングが難しいので更に説明します。

# if文でフィルタリングする方法

次の文がやっていることはTaggedActorの中でcreative_prop型にキャストできるもののみを取り出す。といった処理です。
```
if(Prop := creative_prop[TaggedActor]):
```
では、どのようなclassがキャストできるでしょうか。

VerseのソースコードのDevices周りのクラス図を可視化するとわかります。

`GetCreativeObjectsWithTag`の返り値の型は`creative_object_interface[]`です。つまり`creative_object_interface`を実装しているもの、継承しているものがキャストできるものの対象になります。

![Monosnap data_image_svg+xml;base64,PD94bWwgdmVyc2l.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96970/1ab3789f-5171-4571-cf42-6b55449ad471.png)

図から次のようなclassにアクセスできることがわかります。
- creative_device_base (Fortniteが用意した仕掛け/Deviceすべて)
    - score_manager_device (スコア管理するデバイス)
    - その他100種類以上のデバイス
- creative_prop
    - Fortniteが準備したMeshActor
    - 自分でBuildingPropで作ったActor
- creative_device (Verseで作成した仕掛けのすべて)
    - original_verse_device (Verseで作成した仕掛けの具体class)


major_tagsで取得したActorをそれぞれフィルタリングすると次のような実装になります。

```python
AllCreativeObjects : []creative_object_interface := GetCreativeObjectsWithTag(major_tags{})

# major_tags を持つすべての creative_prop アクタの位置をプリントします
for (Prop : AllCreativeObjects):
    if (Prop := creative_prop[Prop]):
        Print("Prop found with all_tag at position: {Prop.GetTransform().Translation}")

# major_tags を持つすべての仕掛けアクタの位置をプリントします
for (Device:AllCreativeObjects):
    if (Device := creative_device_base[Device]):
        Print("Creative device found with all_tag at position: {Device.GetTransform().Translation}")

# major_tags を持つすべての「Verse で作成した仕掛け」アクタの位置をプリントします
for (VerseAuthoredDevice : AllCreativeObjects):
    if (VerseAuthoredDevice := creative_device[VerseAuthoredDevice]):
        Print("User device found with all_tag at position: {VerseAuthoredDevice.GetTransform().Translation}")
```


# おまけ: plantumlから生成されたもの


```plantuml
@startuml
package Devices{
    creative_device --|> creative_object_interface
    creative_device_base --|> creative_object
    creative_object --|> creative_object_interface
    creative_prop --|> creative_object
    timer_device --|> creative_device_base
    teleporter_device --|> creative_device_base
    score_manager_device --|> creative_device_base

    abstract class creative_device_base {
    }
    
    interface creative_object_interface {
    }
}
original_verse_device --|> creative_device
```

もっと大規模に

```plantuml
@startuml

package Devices{
    creative_device --|> creative_object_interface
    creative_device_base --|> creative_object
    creative_object --|> creative_object_interface
    creative_prop --|> creative_object
    creative_object -> move_to_result
    DeviceMethods -> spawn_prop_result
    trigger_device --|> trigger_base_device
    trigger_base_device --|> creative_device_base
    vending_machine_device --|> creative_device_base
    timer_device --|> creative_device_base
    teleporter_device --|> creative_device_base
    score_manager_device --|> creative_device_base
    DeviceMethods ---> creative_prop_asset

    class DeviceMethods{
      +  []creative_object_interface GetCreativeObjectsWithTag(Tag:tag)
      +  []creative_object_interface GetCreativeObjectsWithTags(SearchCriteria:tag_search_criteria)
      +  tag_view GetTags(CreativeObject:creative_object_interface)
      +  fort_playspace GetPlayspace(CreativeObject:creative_object_interface)
      +  tuple(?creative_prop, spawn_prop_result) SpawnProp(Asset:creative_prop_asset,Transform:transform)
    }
    
    class creative_device {
        + void OnBegin()
        + void OnEnd()
        + transform GetTransform()
        + void TeleportTo(Position:vector3, Rotation:rotation)
        + move_to_result MoveTo(Position:vector3, Rotation:rotation, OverTime:float)
        + void Show()
        + void Hide()
    }
    
    abstract class creative_device_base {
    }
    
    class creative_device_asset {
    }
    
    interface creative_object_interface {
    }
    
    class creative_object {
        + transform GetTransform()
        + void TeleportTo(Position:vector3, Rotation:rotation)
        + move_to_result MoveTo(Position:vector3, Rotation:rotation, OverTime:float)
    }
    
    class creative_prop {
        + void Dispose()
        + void IsValid()
        + void SetMesh(Mesh:mesh)
        + void SetMaterial(Material:material, ?Index:int)
        + void Show()
        + void Hide()
    }
    
    class creative_prop_asset {
    }
    
    enum spawn_prop_result {
        Ok,
        UnknownError,
        InvalidSpawnPoint,
        SpawnPointOutOfBounds,
        InvalidAsset,
        TooManyProps
    }
    
    enum move_to_result {
        DestinationReached,
        WillNotReachDestination
    }



    class trigger_device{

    }
    abstract class trigger_base_device{

    }

}

package Game{
    creative_object_interface --|> positional
    interface positional{
        + transform GetTransform()
    }
}

package Verse{
    creative_prop --|> invalidatable
    invalidatable --|> disposable
    interface invalidatable{
        + void IsValid()
    }

    interface  disposable{
        + void Dispose()
    }
}

package Simulation {
    package Tags{
        DeviceMethods -> tag
        abstract class tag{

        }
        interface tag_view{
            + void Has(TagToCheck:tag)
            + void HasAny(InTags:[]tag)
            + void HasWall(InTags:[]tag)
        }

        enum tag_search_sort_type {
            Unsorted
            Sorted
        }

        class tag_search_criteria{
            + []tag RequiredTags()
            + []tag PreferredTags()
            + []tag ExclusionTags()
            + tag_search_sort_type SortType()
        }
    }

    player --|> agent
    class agent{

    }
    class player{

    }
}
package Playspaces{
    DeviceMethods -> fort_playspace
    fort_playspace -> player
    interface fort_playspace {
        +[]player GetPlayers()
        + fort_team_collection GetTeamCollection()
        + listenable(player) PlayerAddedEvent
        + listenable(player) PlayerRemovedEvent
    }
    
}

original_verse_device --|> creative_device

```