blog heroalche.studio

【Verse】マルチプレイに対応したカスタムタイマーをつくろう! 

著者:

Yamamoto

投稿日 2023.12.18更新日 2023.12.18

こんにちは!
UEFNでは仕掛けを使えば簡単にタイマーを再現できます。しかし、タイマーの表示位置を変える場合はVerseを使う必要が出てきます。 そこで、今回はマルチプレイに対応したカウントアップ式のタイマーをVerseで作る方法を紹介したいと思います。

EpicのUEFNドキュメントではカウントダウン式のタイマーについて紹介されているので、こちらも参考にしてください。

カスタム仕様のカウントダウンタイマー


■サンプルコード

まず初めに、今回の実装の全体像を紹介します。

タイマー起動と終了に2つのミューテーターゾーンを使用しています。

スタートゾーンにプレイヤーが入る度、タイマーが0からカウントアップを開始し、ゴールゾーンに入ると一時停止した結果がHUDメッセージデバイスを通じて表示される仕様としています。

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Colors }
using { /Fortnite.com/Characters }

timer_manager_device := class(creative_device):
    StringToMessage<localizes>(value:string) : message = "{value}" #string ->messageへ変換
    @editable
    TimerStartZone :mutator_zone_device = mutator_zone_device{} #計測開始するミューテータゾーン
    @editable
    TimerStopZone : mutator_zone_device = mutator_zone_device{} #計測終了するミューテータゾーン
    @editable
    ResultTimeUIText : hud_message_device = hud_message_device{} #結果を表示するHUDメッセージデバイス
    @editable
    PlayerSpawners : []player_spawner_device= array{}  # UI表示対象のプレイヤがスポーンするスポナを登録

    var TimerControllers: [player]timer_controller = map{} # プレイヤごとにタイマーを保持するクラス

    OnBegin<override>()<suspends>:void= 
        for (Index := 0..PlayerSpawners.Length-1):
            if(PlayerSpawners[Index].SpawnedEvent.Subscribe(UIAtatchment)): 

        TimerStartZone.AgentEntersEvent.Subscribe(TimerStart) 
        TimerStopZone.AgentEntersEvent.Subscribe(TimerStop) 

    #タイマーをスタートさせる
    TimerStart(Agent:agent) : void =
        if(Player:=player[Agent],TimerController := TimerControllers[Player]):
            TimerController.ResetTimer()
            TimerController.StartCountUp(Agent)

    #タイマーをストップさせる
    TimerStop(Agent:agent):void =
        if(Player:=player[Agent],TimerController := TimerControllers[Player]):
            TimerController.CountStop()
            ResultTimeUIText.Show(Agent, StringToMessage(TimerController.CountTimer.TimerText))

  #UIの表示を行う
    UIAtatchment(Agent : agent):void= 
        if(Player:=player[Agent]):
            if(WClass := TimerControllers[Player]):
                return
            else :
                if(WClass:= set TimerControllers[Player] = timer_controller{}):
                        WClass.InitUI(Player

#UIの設定                        
timer_controller :=class :
    UI_Timer_Second_0 : text_block = text_block{DefaultTextColor := NamedColors.White} #01秒の位
    UI_Timer_Second_1 : text_block = text_block{DefaultTextColor := NamedColors.White} #10秒の位
    UI_Timer_Minute_0 : text_block = text_block{DefaultTextColor := NamedColors.White} #01分の位
    UI_Timer_Minute_1 : text_block = text_block{DefaultTextColor := NamedColors.White} #10分の位
    UI_Timer_Hour_0   : text_block = text_block{DefaultTextColor := NamedColors.White} #01時間の位
    UI_Timer_Hour_1   : text_block = text_block{DefaultTextColor := NamedColors.White} #10時間の位
    UI_Timer_Colon_0  : text_block = text_block{DefaultTextColor := NamedColors.White} #コロン分秒
    UI_Timer_Colon_1  : text_block = text_block{DefaultTextColor := NamedColors.White} #コロン時分

    var CountTimer :count_timer = count_timer{}  

    InitUI(Player : player): void =
        NewCanvas : canvas = canvas:
            Slots := array:
                canvas_slot:
                    Widget := UI_Timer_Second_0
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}  
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -610.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Second_1
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}  
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -630.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Colon_0           
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0},Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -650.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Minute_0
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}  
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -670.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Minute_1                     
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -690.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Colon_1           
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0},Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -710.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Hour_0
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -730.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Hour_1           
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0},Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -750.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true

        if (PlayerUI := GetPlayerUI[Player]):
            PlayerUI.AddWidget(NewCanvas)

    #タイマーの状況管理
    ResetTimer():void =
        set CountTimer.isReset = true
        set CountTimer.isActive = false
        set CountTimer = count_timer{}
        return

    StartCountUp(Agent:agent):void =
        spawn:
            CountTimer.RunCountUp(Agent,UI_Timer_Colon_0,UI_Timer_Colon_1,UI_Timer_Second_0,UI_Timer_Second_1,UI_Timer_Minute_0,UI_Timer_Minute_1,UI_Timer_Hour_0,UI_Timer_Hour_1)
        return

    CountStop():void =
        set CountTimer.isActive = false
        return

count_timer :=class :
    StringToMessage<localizes>(value:string) : message = "{value}"  #string ->messageへ変換
    var isActive : logic = true; # タイマーを更新する状態か
    var isReset : logic = false; # リセット対象のタイマーになっているか
    var Timer_Second_0 : int = 0 # 01秒の位
    var Timer_Second_1 : int = 0 # 10秒の位
    var Timer_Minute_0 : int = 0 # 01分の位
    var Timer_Minute_1 : int = 0 # 10分の位
    var Timer_Hour_0 : int = 0 # 01時間の位
    var Timer_Hour_1 : int = 0 # 10時間の位
    var TimerText :string = "00:00:00" # 結果表示用のひとまとめの文字列

    #タイマーの動きを設定
    UpdateTimerCount() : void =
        if(Timer_Second_0<9):
            set Timer_Second_0 +=1
        else:
            set Timer_Second_0 =0
            set Timer_Second_1 +=1

        if(Timer_Second_1>=6):
            set Timer_Second_1 = 0
            set Timer_Minute_0 +=1
        if(Timer_Minute_0>=10):
            set Timer_Minute_0 = 0
            set Timer_Minute_1 +=1
        if(Timer_Minute_1>=6):
            set Timer_Minute_1 = 0
            set Timer_Hour_0 +=1
        if(Timer_Hour_0>=10):
            set Timer_Hour_0 = 0
            set Timer_Hour_1 +=1

        set TimerText = MakeTimerText()

    MakeTimerText() : string = 
        return ToString(Timer_Hour_1)+ToString(Timer_Hour_0)+":"+ToString(Timer_Minute_1)+ToString(Timer_Minute_0)+":"+ToString(Timer_Second_1)+ToString(Timer_Second_0)

  #UIにテキストを設定する 
    RunCountUp(Agent:agent,UI_Timer_Colon_0:text_block,UI_Timer_Colon_1:text_block,UI_Timer_Second_0:text_block,UI_Timer_Second_1:text_block,UI_Timer_Minute_0:text_block,UI_Timer_Minute_1:text_block,UI_Timer_Hour_0:text_block,UI_Timer_Hour_1:text_block)<suspends> : void =
        set TimerText = MakeTimerText()
        UI_Timer_Hour_0.SetText(StringToMessage(ToString(Timer_Hour_0))) 
        UI_Timer_Hour_1.SetText(StringToMessage(ToString(Timer_Hour_1)))
        UI_Timer_Minute_0.SetText(StringToMessage(ToString(Timer_Minute_0)))
        UI_Timer_Minute_1.SetText(StringToMessage(ToString(Timer_Minute_1)))
        UI_Timer_Second_0.SetText(StringToMessage(ToString(Timer_Second_0)))
        UI_Timer_Second_1.SetText(StringToMessage(ToString(Timer_Second_1)))
        UI_Timer_Colon_0.SetText(StringToMessage(":"))
        UI_Timer_Colon_1.SetText(StringToMessage(":"))

        loop:
            Sleep(1.0) #Debugmode 
            if(isReset=true):
                return;

            if (isActive=true):
                UpdateTimerCount()   
                UI_Timer_Hour_0.SetText(StringToMessage(ToString(Timer_Hour_0))) 
                UI_Timer_Hour_1.SetText(StringToMessage(ToString(Timer_Hour_1)))
                UI_Timer_Minute_0.SetText(StringToMessage(ToString(Timer_Minute_0)))
                UI_Timer_Minute_1.SetText(StringToMessage(ToString(Timer_Minute_1)))
                UI_Timer_Second_0.SetText(StringToMessage(ToString(Timer_Second_0)))
                UI_Timer_Second_1.SetText(StringToMessage(ToString(Timer_Second_1)))
            else:
                return

1.インポートとデバイスの設定

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Colors }
using { /Fortnite.com/Characters }

timer_manager_device := class(creative_device):
    StringToMessage<localizes>(value:string) : message = "{value}" #string ->messageへ変換
    @editable
    TimerStartZone :mutator_zone_device = mutator_zone_device{} #計測開始するミューテータゾーン
    @editable
    TimerStopZone : mutator_zone_device = mutator_zone_device{} #計測終了するミューテータゾーン
    @editable
    ResultTimeUIText : hud_message_device = hud_message_device{} #結果を表示するHUDメッセージデバイス
    @editable
    PlayerSpawners : []player_spawner_device= array{}  # UI表示対象のプレイヤがスポーンするスポナを登録

まず、必要なモジュールを追加し、timer_manager_deviceクラスを作成していきます。11行目にあるStringToMessage<localizes>(value:string) : message = "{value}"はString型をmessage型に変換するものです。文字列のままでは画面に表示することができないため、変換を行います。

12行目以降からは、仕掛けを参照していきます。@editableをつけることで、UnlealEditor上で実際にどのデバイスをターゲットとするのかを設定することができます。@editableの下に記述しているもので仕掛けを参照しています。例えば、13行目のTimerStartZone :mutator_zone_device = mutator_zone_device{}では、ミューテータゾーン型のTimerStartZoneという変数を作成して、mutator_zone_device{}のインスタンスを生成し、このインスタンスを変数に代入しています。このようにして、今回はミューテータゾーンとHUDメッセージデバイス、プレイヤースポナーの仕掛けを参照しています。

2.ゲーム開始時の処理とタイマーのタイミング

    var TimerControllers: [player]timer_controller = map{} # プレイヤごとにタイマーを保持するクラス

    OnBegin<override>()<suspends>:void=
        for (Index := 0..PlayerSpawners.Length-1):
            if(PlayerSpawners[Index].SpawnedEvent.Subscribe(UIAtatchment) ): 

        TimerStartZone.AgentEntersEvent.Subscribe(TimerStart) 
        TimerStopZone.AgentEntersEvent.Subscribe(TimerStop) 

    #タイマーをスタートさせる
    TimerStart(Agent:agent) : void =
        if(Player:=player[Agent],TimerController := TimerControllers[Player]):
            TimerController.ResetTimer()
            TimerController.StartCountUp(Agent)

    #タイマーをストップさせる
    TimerStop(Agent:agent):void =
        if(Player:=player[Agent],TimerController := TimerControllers[Player]):
            TimerController.CountStop()
            ResultTimeUIText.Show(Agent, StringToMessage(TimerController.CountTimer.TimerText))

  #UIの表示を行う
    UIAtatchment(Agent : agent):void= 
        if(Player:=player[Agent]):
            if(WClass := TimerControllers[Player]):
                return
            else :
                if(WClass:= set TimerControllers[Player] = timer_controller{}):
                        WClass.InitUI(Player)

次にゲームが開始した時の処理を作成していきます。

3行目のOnBegin<override>()<suspends>:void=はゲーム開始時に全て実行する関数です。デバイス自体はそれぞれのプレイヤーがゲームに入った時でなく、最初のプレイヤーがインスタンスを立てた時に実行されます。
4,5行目ではスポーンした各プレイヤーにUIAtatchment関数を実行するというものです。UIAtatchment関数については23行目以降に書いています。

まず、1行目の var TimerControllers: [player]timer_controller = map{}で空のmapを作り、PlayerとタイマーUIの対応表を作っています。そのため、UIAtatchment関数内のif(WClass := TimerControllers[Player]):のif文はmapの中身が空なので、必ずfalseになり、elseブロックに入ります。ここでは、WClass:= set TimerControllers[Player] = timer_controller{})をmapに書き加え、新しいインスタンスを生成し、入ったプレイヤーに対しては以後もTimerControllerを使うという処理です。
つまり、2回目以降に同じプレイヤーが入った場合はif(WClass := TimerControllers[Player]):行がtrueとなり何もせず、別のプレイヤーが入った場合はまだmapに追加されていないためfalseとなり、先ほど同様elseブロックの処理を行うということです。
これらの記述を取り入れることでマルチプレイであっても、それぞれのプレイヤーを識別するでき、プレイヤーごとの処理が行えます。

7,8行目にはミューテータゾーンに入った際にTimerStart関数かTimerStop関数を実行する処理を書いています。11行目からのTimerStart関数ではmapにPlayerが追加されていればタイマーをリセットし、タイマーをスタートさせます。また、17行目からのTimerStop関数ではmapにPlayerが入っていれば、タイマーをストップし、HUDメッセージデバイスを通じて結果を表示します。 HUDメッセージデバイスの設定はUEFNのエディタにて行っています。Verseで表示させた際のHUDメッセージのフォントや位置についてはエディタで設定されたものが適用されますが、HUDの表示位置やレイヤがほかのWidgetと重複している場合には正しく表示されないことがあるので注意して下さい。

3.UIの作成

#UIの設定
timer_controller :=class :
    UI_Timer_Second_0 : text_block = text_block{DefaultTextColor := NamedColors.White} #01秒の位
    UI_Timer_Second_1 : text_block = text_block{DefaultTextColor := NamedColors.White} #10秒の位
    UI_Timer_Minute_0 : text_block = text_block{DefaultTextColor := NamedColors.White} #01分の位
    UI_Timer_Minute_1 : text_block = text_block{DefaultTextColor := NamedColors.White} #10分の位
    UI_Timer_Hour_0   : text_block = text_block{DefaultTextColor := NamedColors.White} #01時間の位
    UI_Timer_Hour_1   : text_block = text_block{DefaultTextColor := NamedColors.White} #10時間の位
    UI_Timer_Colon_0  : text_block = text_block{DefaultTextColor := NamedColors.White} #コロン分秒
    UI_Timer_Colon_1  : text_block = text_block{DefaultTextColor := NamedColors.White} #コロン時分

    var CountTimer :count_timer = count_timer{}  

    InitUI(Player : player): void =
        NewCanvas : canvas = canvas:
            Slots := array:
                canvas_slot:
                    Widget := UI_Timer_Second_0
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}  
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -610.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Second_1
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}  
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -630.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Colon_0           
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0},Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -650.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Minute_0
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}  
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -670.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Minute_1                     
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top :=950.0, Left := -690.00, Bottom := 0.0, Right := 0.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Colon_1           
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0},Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -710.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Hour_0
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0}, Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -730.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true
                canvas_slot:
                    Widget := UI_Timer_Hour_1           
                    Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.0},Maximum := vector2{X := 0.5, Y := 0.05}}
                    Alignment := vector2{X := 0.5, Y := 0.0}
                    Offsets := margin{Top := 950.00, Left := -750.0, Bottom := 0.0, Right := 00.0}
                    SizeToContent := true

        if (PlayerUI := GetPlayerUI[Player]):
            PlayerUI.AddWidget(NewCanvas)

    #タイマーの状況管理
    ResetTimer():void =
        set CountTimer.isReset = true
        set CountTimer.isActive = false
        set CountTimer = count_timer{}
        return

    StartCountUp(Agent:agent):void =
        spawn:
            CountTimer.RunCountUp(Agent,UI_Timer_Colon_0,UI_Timer_Colon_1,UI_Timer_Second_0,UI_Timer_Second_1,UI_Timer_Minute_0,UI_Timer_Minute_1,UI_Timer_Hour_0,UI_Timer_Hour_1)
        return

    CountStop():void =
        set CountTimer.isActive = false
        return

次にタイマーのUIを作成していきます。
3行目~10行目でそれぞれの桁(秒、分、時間)のテキストブロックを作成します。テキストブロックはテキストの内容や書式などを設定し、プレイヤーが見られるようにするものです。今回のテキストの色は白色にしました。

14行目以降では、先ほどのテキストブロックの位置を決めています。Anchorsはアンカーポイントを決めるものです。Alignmentでスロットの配置位置を自由に指定できます。Offsetsでスロットの上下左右の余白を決めることができます。

70行目以降ではタイマーの状況を管理する関数です。

4.タイマーの処理

count_timer :=class :
    StringToMessage<localizes>(value:string) : message = "{value}"  #string ->messageへ変換
    var isActive : logic = true; # タイマーを更新する状態か
    var isReset : logic = false; # リセット対象のタイマーになっているか
    var Timer_Second_0 : int = 0 # 01秒の位
    var Timer_Second_1 : int = 0 # 10秒の位
    var Timer_Minute_0 : int = 0 # 01分の位
    var Timer_Minute_1 : int = 0 # 10分の位
    var Timer_Hour_0 : int = 0 # 01時間の位
    var Timer_Hour_1 : int = 0 # 10時間の位
    var TimerText :string = "00:00:00" # 結果表示用のひとまとめの文字列

    #タイマーの動きを設定
    UpdateTimerCount() : void =
        if(Timer_Second_0<9):
            set Timer_Second_0 +=1
        else:
            set Timer_Second_0 =0
            set Timer_Second_1 +=1

        if(Timer_Second_1>=6):
            set Timer_Second_1 = 0
            set Timer_Minute_0 +=1
        if(Timer_Minute_0>=10):
            set Timer_Minute_0 = 0
            set Timer_Minute_1 +=1
        if(Timer_Minute_1>=6):
            set Timer_Minute_1 = 0
            set Timer_Hour_0 +=1
        if(Timer_Hour_0>=10):
            set Timer_Hour_0 = 0
            set Timer_Hour_1 +=1

        set TimerText = MakeTimerText()

    MakeTimerText() : string = 
        return ToString(Timer_Hour_1)+ToString(Timer_Hour_0)+":"+ToString(Timer_Minute_1)+ToString(Timer_Minute_0)+":"+ToString(Timer_Second_1)+ToString(Timer_Second_0)

#UIにテキストを設定する 
    RunCountUp(Agent:agent,UI_Timer_Colon_0:text_block,UI_Timer_Colon_1:text_block,UI_Timer_Second_0:text_block,UI_Timer_Second_1:text_block,UI_Timer_Minute_0:text_block,UI_Timer_Minute_1:text_block,UI_Timer_Hour_0:text_block,UI_Timer_Hour_1:text_block)<suspends> : void =
        set TimerText = MakeTimerText()
        UI_Timer_Hour_0.SetText(StringToMessage(ToString(Timer_Hour_0))) 
        UI_Timer_Hour_1.SetText(StringToMessage(ToString(Timer_Hour_1)))
        UI_Timer_Minute_0.SetText(StringToMessage(ToString(Timer_Minute_0)))
        UI_Timer_Minute_1.SetText(StringToMessage(ToString(Timer_Minute_1)))
        UI_Timer_Second_0.SetText(StringToMessage(ToString(Timer_Second_0)))
        UI_Timer_Second_1.SetText(StringToMessage(ToString(Timer_Second_1)))
        UI_Timer_Colon_0.SetText(StringToMessage(":"))
        UI_Timer_Colon_1.SetText(StringToMessage(":"))

        loop:
            Sleep(1.0) #Debugmode 
            if(isReset=true):
                return;

            if (isActive=true):
                UpdateTimerCount()   
                UI_Timer_Hour_0.SetText(StringToMessage(ToString(Timer_Hour_0))) 
                UI_Timer_Hour_1.SetText(StringToMessage(ToString(Timer_Hour_1)))
                UI_Timer_Minute_0.SetText(StringToMessage(ToString(Timer_Minute_0)))
                UI_Timer_Minute_1.SetText(StringToMessage(ToString(Timer_Minute_1)))
                UI_Timer_Second_0.SetText(StringToMessage(ToString(Timer_Second_0)))
                UI_Timer_Second_1.SetText(StringToMessage(ToString(Timer_Second_1)))
            else:
                return

最初にlogic型(bool型)でタイマーが動いていることを表すisActiveとリセットを表すisResetの変数と、タイマーの各桁(秒、分、時間)ごとの変数とタイマー全体の変数を用意します。

14行目から書かれているUpdateTimeCount関数はタイマーの更新処理です。例えば14行目の秒の処理であれば、1桁目が9以下であれば+1をしていき、もし9になった場合は0にリセットし、次の10桁+1をします。これを繰り返すことによりタイマーを再現しています。

set TimerText = MakeTimerText()でテキストを格納する変数を作り、タイマーの情報をString型で変数に格納していきます。

40行目からはString型のタイマー各桁をMessageを変更し、先ほど作った各桁のUIにテキストを設定します。もし、タイマーが動いている場合はUpdateTimeCount関数を実行してからUIにテキストを設定しています。
52行目にあるSleep(1.0)により、1秒待ってから処理が行わます。これにより1秒を再現することができ、正確に近いタイマーを作成することができます。