[UEFN]Verseでタイクーンにおけるコインが加算されていく仕組みを作ってみる

## 1. はじめに
タイクーンの基本的な仕組みである「コインが毎秒加算される仕組み」をVerseを使って実装しました。
また、ゲームの途中で何かしらのイベントやインタラクトをきっかけにコインが加算される倍率が変更される仕組みも一緒に作ってみましたのでその記録を共有します。

## 2. 完成動画
<iframe width="560" height="315" src="https://www.youtube.com/embed/jDqJBTMzNB0?si=ceAw2rc_Pmk6ieJ0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## 3. 概要
#### コインがたまる仕掛けの作成（game_systemクラス）
1. コインの数量となる変数CoinValueを定義
1. コインがたまる度合いとなる変数AddCoinValueを定義
1.  関数AddCoinでCoinValueを1秒に1ずつ加算
1.  CoinValueをmessage型に変換してHUDMesssageDeviceへSetText
1.  3→4をloop式で繰り返す

#### コインの加算の倍率の変更の仕掛けの作成(level_up_deviceクラス)
1. game_systemクラスのAddCoinValueを2倍にする関数ChangeAddCoinValueを定義する
1. TrrigerDeviceでOnTrrigerEvent時に関数ChangeAddCoinValueが実行されるようにバインドする


## 4. コインがたまる仕掛けの作成
1. レベル上にTriggerDeviceとHUDMessageDeviceを1つずつ配置
TriggerDeviceを起動したらコインの加算が開始され、HUDへのテキスト表示はHUDMessageDeviceを介して行われるものとしました。
　
2. HUDMessageDeviceの設定
    今回はとりあえず以下のように設定しました。
 
    <img width="300" alt="スクリーンショット (373).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3731314/b62d379f-f8d5-adec-c529-88156785f6ff.png">
    <img width="300" alt="スクリーンショット (374).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3731314/e97d24bc-3a83-e490-ebf7-5466a98a380f.png">

    　　
3. CreativeDeviceを作成
    今回はとりあえず名前をgame_systemとしました。
    　
4. 変数の定義
    変数を以下のように定義しました。
    ```
    using { /Fortnite.com/Devices }
    using { /Verse.org/Simulation }
    using { /UnrealEngine.com/Temporary/Diagnostics }

    game_system := class(creative_device):
       #HUDメッセージデバイス：コインの枚数をHUDに表示
        @editable
        GameHUD : hud_message_device = hud_message_device{}

        @editable
        Trigger : trigger_device = trigger_device{}

        #コイン枚数
        var CoinValue : int = 0

        #コイン増加量
        var AddCoinValue : int = 1
    ```
    - HUDMessageDeviceはコインの量をHUDに表示させる
    - CoinValueはHUD上に実際表示させる現在コインを所持している数量
    - AddCoinValueは毎秒増加させるコイン量
    　
1. 関数の定義
    次に関数を以下のように定義しました。
    ```
   #コインの追加開始
    StartAddCoin(Agent : ?agent) : void =
        spawn{AddCoin(Agent)}

    #毎秒コイン枚数の増加処理
    AddCoin(Agent : ?agent)<suspends> : void =
        loop:
            set CoinValue += AddCoinValue
            GameHUD.SetText(SetCoinText())
            Sleep(1.0)       

    #コインの増加倍率を変更する処理
    SetAddCoinValue(Value : int) : void =
        set AddCoinValue = Value 
            
    #CoiuValueをmessageへ変換         
    SetCoinText():message = 
        var Text : string= "Coin : {CoinValue}"
        CoinText : message = StringToMessage(Text)
        return CoinText

    #String→Messageへ変換するメソッド
    StringToMessage<localizes>(String : string) : message ="{String}"     
    ```
    varseからHUDMessageDeviceにテキストデータを入れ込む場合、データの型をmessage型とする必要があるので、string型をmessage型へ変換する関数StringToMessage<localizes>(String : string)を用意しました。
    ```
    #CoiuValueをmessageへ変換         
    SetCoinText():message = 
        var Text : string= "Coin : {CoinValue}"
        CoinText : message = StringToMessage(Text)
        return CoinText
    ```
    そしてSetCoinText()内で「Coin : 〇〇」というstring型データを格納した変数Textを一度作成して、これを関数StringToMessage<localizes>(String : string)に入れ込んでmessage型のデータに変換しました。
    ここで、関数StringToMessage<localizes>(String : string)は戻り値無しのvoidでなく戻り値をmessage型とすることで、この関数内での処理が終わると「Coin : 〇〇」というmessage型のデータが返されて変数CoinText内へ格納されます。
    同じように関数SetCoinText()の戻り値もmessage型で、この関数内の処理が終わるとSetCoinText()はmessage型のデータを格納した変数CoinTextを返します。
    
    #### AddCoin(Agent : ?agent)<suspends>と StartAddCoin(Agent : ?agent)について
    ```
    #コインの追加開始
    StartAddCoin(Agent : ?agent) : void =
        spawn{AddCoin(Agent)}

    #毎秒コイン枚数の増加処理
    AddCoin(Agent : ?agent)<suspends> : void =
        loop:
            set CoinValue += AddCoinValue
            GameHUD.SetText(SetCoinText())
            Sleep(1.0)       
    ```
    AddCoin(Agent : ?agent)内では、まず変数CoinValueのコイン量を加算する計算処理を行っています。次にHUDMessageDeviceにmessage型データを入れ込むための関数SetText()を呼び出して引数として関数SetCoinText()を渡しています。
    前述のとおり関数SetCoinText()は処理が終了すると変数CoinTextを返すので、この処理は関数SetText()に引数として関数SetCoinText()内の変数CoinTextを渡したのと同じことになります。
    最後に毎秒加算が行われるようにするためSleep()で1秒待つ処理を入れてこの関数 AddCoin(Agent : ?agent)<suspends>内の処理をloop式でゲーム中は繰り返されるように設定にしました。
    しかし、このままでは無限ループとなるので実際のゲーム内で実装する場合はゲーム終了時にこのループが終了するようにif文とbreakを加えておくとよいと思われます。今回は実装の確認だけなので省略させていただきました。
    
    また、loop式はsuspends指定子を付けた関数内で記述する必要があるのでこの関数には```<suspends>```が付いています。
    ところがsuspends指定子が付いた関数は後述する関数Subscribe()の引数として記述することができません。そのため別にsuspends指定子が付いていない関数StartAddCoin(Agent : ?agent)を定義しておいて、その中のspawn式ブロック内にAddCoin()を記述しておきます。この関数StartAddCoin(Agent : ?agent)の形にすれば関数Subscribe()の引数として渡すことができます。

    #### SetAddCoinValue(Value : int)について
    ```
    #コインの増加倍率の変更処理
    SetAddCoinValue(Value : int) : void =
        set AddCoinValue = Value 
    ```
    この関数内には渡した引数の値にAddCoinValue内の値を変更する処理を記述しています。
    今回は外部の他の仕掛けからgame_systemにアクセスしてこの関数SetAddCoinValue(Value : int)を実行することでコインの加算度合いを変更することを想定して記述しました。
    後述する別クラスの「コインの加算の度合いを変更する仕掛け」の中で実際に呼び出し処理を記述します。
    
    　
1. OnBegin()内を記述
    以下のようにOnBegin()部分を記述しました。
    ```
    OnBegin<override>()<suspends>:void=
        #コイン枚数をSetTextしloop処理で毎秒ごとに更新
        GameHUD.SetText(SetCoinText())
        Trigger.TriggeredEvent.Subscribe(StartAddCoin)
    ```
    #### GameHUD.SetText(SetCoinText())について
    ```
        GameHUD.SetText(SetCoinText())
    ```
    ゲーム開始と同時に初期のコイン量をHUD上に表示するためHUDMessageDeviceへ現在のコイン量をSetTextします。当然まだコインの加算処理が開始されていないのでこの時点でHUD上には「Coin : 0」と表示されます。
    
    #### Trigger.TriggeredEvent.Subscribe(StartAddCoin)について
    ```
        Trigger.TriggeredEvent.Subscribe(StartAddCoin)
    ```
    Triggerを起動させたらコインの加算処理であるStartAddCoin()を開始するようにTriggeredEventにバインドしました。
    　
1. レベル上に配置して動作を確認
     ![スクリーンショット (378).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3731314/2a81270d-ee05-3a59-33a5-039d2e1bff04.png)
    レベル上に作成したgame_systemを配置してTriggerDeviceとHUDMessageDeviceをそれぞれGemeHUDとTriggerに割り当てをしたあと、セッションを開始して動作を確認します。
ゲーム画面中でTriggerDeviceを踏むとコインの加算が開始されることが確認できると思います。

    今回の実装では2回TriggerDeviceを踏むと加算処理が二重に実行されてコインが過剰に加算されるバグが発生してしまいますので実際のゲームでは2回踏めないようにTriggerDeviceを無効化する等の処理を加えた方がいいと思います。

    次にもう1つTriggerDeviceを用意してそれを踏むとコインの加算倍率が変化する仕掛けを作ります。

## 5. コインの加算倍率を変更する仕掛けの作成
1. CreativeDeviceをもう一つ作成
    今回はとりあえず名称をlevel_up_deviceとしました。
    　
2. レベル上にTriggerDeviceをもう一つ配置
    　
1. 変数の定義
    以下のように変数を定義しました。
    ```
    level_up_device := class(creative_device):
        #game_systemクラスのインスタンス
        @editable
        GameSystemDevice : game_system = game_system{}

        #書き換えるコイン増加量を定義
        @editable
        AddMultiplier : int = 2

        #TriggerDevice：コイン書き換え処理をトリガー
        @editable
        Trigger : trigger_device = trigger_device{}

    ```
    今回のTriggerDeviceは上記の「2.」でレベル上に配置したTriggerDeviceを割り当てる予定で、起動するとコインの加算度合いが増加するように実装します。
    変数GameSystemDeviceはgame_systemクラスのインスタンスでgame_system内の関数SetAddCoinValue()を呼び出すために用意しました。
        　
1. 関数の定義
    以下のように関数を定義しました。
    ```
    #コイン書き換え処理
    ChangeAddCoinValue(Agent : ?agent) : void =
        #game_systemクラスのメンバ関数SetAddcoinValueを呼び出し
         GameSystemDevice.SetAddCoinValue(AddMultiplier)
    ```
    変数GameSystemDeviceにはgame_systemクラスのインスタンス情報が格納されているのでgame_systemクラス内の変数や関数を呼び出すことができます。
    このため変数GameSystemDeviceからコインの加算倍率を変更する関数SetCoinMultiplier()を呼び出して実行することができます。さらに引数として今回定義した変数AddMultiplierを渡すことでコインの加算度合いを変更する処理を作ることができます。
        　
1. OnBegin()内の記述
    以下のようにOnBegin()内を記述しました。
    
    ```
      OnBegin<override>()<suspends>:void=
        #トリガーでコイン書き換え処理実行
        Trigger.TriggeredEvent.Subscribe(ChangeAddCoinValue)
    ```
    TriggerDeviceのTriggeredEvent時にChangeAddCoinValue()が実行されるようにバインドします。
    　
1. レベル上に配置して動作確認
   ![スクリーンショット (379).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3731314/bc7bccba-16af-af32-f181-d24774a8c9f8.png)
    level_up_deviceをレベル上に配置してTriggerに「2.」で配置した2つ目のTriggerDeviceを、GameSystemDeviceには先ほど作成しレベル上に配置したCreativeDeviceのgame_systemを割り当てた後、セッションを開始し動作を確認します。
    セッションを開始後コインの加算を開始する画面左側のTriggerDeviceを起動したあと、「2.」で配置した画面右側のTriggerDeviceを起動させるとコインの量が2ずつ増加するようになります。
    
    「なぜわざわざ全ての処理をgame_systemクラスに実装せずに別にlevel_up_deviceクラスを作成したのか」については特に深い意味はありません。ただ今回は違うクラスからのアクセスに関しても一緒に試してみたかったのでこのような構造にしてみました。
    クラスを2つに分けずに全てをgame_systemクラス内に記述しても今回の実装は作ることができます。

## 6. 完成したスクリプト
### game_systemクラス
```
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

game_system := class(creative_device):
   #HUDメッセージデバイス：コインの枚数をHUDに表示
    @editable
    GameHUD : hud_message_device = hud_message_device{}

    @editable
    Trigger : trigger_device = trigger_device{}

    #コイン枚数
    var CoinValue : int = 0

    #コイン増加量
    var AddCoinValue : int = 1

    #コイン増加倍率
    #var CoinMultiplier : int = 1

    OnBegin<override>()<suspends>:void=
        #コイン枚数をSetTextしloop処理でリアルタイムに更新
            GameHUD.SetText(SetCoinText())
            Trigger.TriggeredEvent.Subscribe(StartAddCoin)
    
    #コインの追加開始
    StartAddCoin(Agent : ?agent) : void =
        spawn{AddCoin(Agent)}

    #毎秒コイン枚数の増加処理
    AddCoin(Agent : ?agent)<suspends> : void =
        loop:
            set CoinValue += AddCoinValue
            GameHUD.SetText(SetCoinText())
            Sleep(1.0)       

    #コインの増加倍率の変更処理
    SetAddCoinValue(Value : int) : void =
        set AddCoinValue = Value 
            
    #CoiuValueをmessageへ変換         
    SetCoinText():message = 
        var Text : string= "Coin : {CoinValue}"
        CoinText : message = StringToMessage(Text)
        return CoinText

    #String→Messageへ変換するメソッド
    StringToMessage<localizes>(String : string) : message ="{String}"  
```
### level_up_deviceクラス
```
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

level_up_device := class(creative_device):
    #game_systemクラスを定義
    @editable
    GameSystemDevice : game_system = game_system{}

    #書き換えるコイン増加量を定義
    @editable
    AddMultiplier : int = 2

    #TriggerDevice：コイン書き換え処理をトリガー
    @editable
    Trigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void=
        #トリガーでコイン書き換え処理実行
        Trigger.TriggeredEvent.Subscribe(ChangeAddCoinValue)
    
    #コイン書き換え処理
    ChangeAddCoinValue(Agent : ?agent) : void =
        #game_systemクラスのメンバ関数SetAddcoinValueを呼び出し
         GameSystemDevice.SetAddCoinValue(AddMultiplier)
```