ゲーム ループは、入力 (通常はプレイヤーによるコントローラーやマウスとのインタラクトへの応答)、ゲーム ステートの更新、ゲーム ステートに及ぼした影響をプレイヤーに示す出力 (プレイヤーがボタンを押すとライトが灯るなど) を行うために繰り返し実行 (ループ) されるコードです。 通常、このループは、ゲームが完了ステート (プレイヤーが目標を達成したときなど) または失敗ステート (プレイヤーがゴールに到達する前に時間切れになるなど) に到達した際に終了します。
「タイムトライアル:ピザ配送」チュートリアルのこのステップを完了すると、ゲーム ループを作成する方法、ゲームの完了と失敗のステートを定義する方法を習得できます。
以下は、タイムトライアル:ピザ配送のゲーム ループの疑似コードです。
loop:
race:
loop:
SelectNextPickupZone
WaitForPlayerToCompletePickupZone
block:
WaitForFirstPickup
SelectNextDeliveryZone
WaitForPlayerToCompleteDeliveryZone
このループは、カウントダウン タイマーが終了するか、ゲームで予期しないエラーが発生した場合に終了します。
コア ゲーム ループを作成する
次の手順に従って、「game_coordinator_device.verse」ファイルを更新します。
private指定子とsuspends指定子を持つPickupDeliveryLoop()という名前の新しいメソッドを作成します。 以前にOnBegin()内に作成したループを、この新しいメソッドに移します。VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]):タグ配列の長さからピックアップ レベルの最大数を決定します。ピックアップ レベルがピックアップ レベルの最大数を超えない限り、プレイヤーがピックアップ ゾーンを完了するたびに
PickupLevelが増加します。VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 var PickupLevel : int = 0配送ゾーンは、プレイヤーが最初のピックアップを完了した後に有効になるようにする必要がありますが、配送ゾーンに行く前でも必要に応じてアイテムをピックアップできるようにする必要があります。 これを実現するには、ピックアップ ゾーン コードと配送ゾーン コードを同時に発生させる必要があります。 この例では、次の理由で race 並列処理式を使用しています。
プレイヤーが配送を終了したときに、配送ブロックによってピックアップ ゾーン ループがキャンセルされるようにする必要がある。
ピックアップ ループに問題がある場合に、ピックアップ ゾーンのループによって配送ブロックがキャンセルされるようにする必要がある。
また、ゾーンの無効化を多少変更する必要もあります。 ループまたは配送ブロックがキャンセルされたときに、スクリプトがゾーンの完了を待機している場合は、
DeactivateZone()を呼び出さないようにする必要があります。ゾーンの無効化処理は実行されないため、ゾーンが有効のまま維持されて、バグが発生します。
これを修正するには defer 式を使用します。
deferは、deferが現れるスコープが終了するまで、defer に含まれている式の実行を遅らせます。deferは、通常のスコープの終了 (関数の終了)、早期終了 (リターンやブレークなど)、キャンセルされた同時タスク/非同期式 (raceなど) といったプログラム制御がスコープ外に転送されたときに実行されます。 これは、何が起こっても最後に実行される操作をキューに入れるようなものです。DeactivateZoneの各呼び出しをdeferでラップし、それぞれのZoneCompletedEvent.Await()の前に移動します。VersePickupDeliveryLoop<private>()<suspends>; : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 var PickupLevel : int = 0 race: loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone()前の例では、ピックアップ ゾーンが有効になった際に配送ゾーンも同時に有効になりますが、配送ゾーンの有効化は、最初のピックアップが完了するまで遅らせる必要があります。 これを実現するには、イベントを追加して配送ゾーンを待機させ、そのイベント発生後に有効にします。
VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 FirstPickupZoneCompletedEvent := event(){}このピックアップ ゾーン/配送ゾーンの race 式をゲームが終了するまでループさせて、プレイヤーがアイテムのピックアップと配送を続けられるようにします。
VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 FirstPickupZoneCompletedEvent := event(){}「game_coordinator_device.verse」ファイルは次のようになります。
Verseusing { /Verse.org/Simulation } using { /Fortnite.com/Devices } using { /Fortnite.com/Vehicles } using { /Fortnite.com/Characters } using { /Fortnite.com/Playspaces } using { /Verse.org/Random } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/SpatialMath } using { /UnrealEngine.com/Temporary/Curves } using { /Verse.org/Simulation/Tags }
Verse ファイルを保存し、コードをコンパイルして、レベルをプレイテストします。
レベルをプレイテストすると、アイテム スポナーの仕掛けの一つがゲームの開始時に有効になります。その後、プレイヤーはアイテムをピックアップします。 プレイヤーが最初のアイテムをピックアップした後に、そのアイテム スポナーの仕掛けが無効になり、キャプチャー エリアの仕掛けが有効になります。 これは、手動でゲームを終了するまで続きます。
ゲーム ループの完了ステートと失敗ステートを定義する
コア ゲーム ループを作成できたので、ゲーム ループの完了ステートと失敗ステートを定義します。 このゲームは次の場合に終了します。
カウントダウンが終了したとき、または
ゲーム ループに問題があったとき。
ゲームの完了ステートと失敗ステートを設定するには、次の手順に従います。
game_coordinator_device内に、countdown_timer クラスのインスタンスをprivate指定子付きで作成します。game_coordinator_device<public> := class(creative_device): @editable EndGame<public> : end_game_device = end_game_device{} var CountdownTimer<private> : countdown_timer = countdown_timer{}Versegame_coordinator_device<public> := class(creative_device): @editable EndGame<public> : end_game_device = end_game_device{} var CountdownTimer<private> : countdown_timer = countdown_timer{}countdown_timerのコンストラクタにはプレイヤー参照が必要であるため、任意指定のプレイヤー変数を追加して、このシングル プレイヤー ゲームのプレイヤーへの参照を格納し、そのプレイヤー参照を取得する関数をFindPlayer()という名前で作成します。OnBegin()内で、ゾーンを設定する前にFindPlayer()を呼び出します。Versegame_coordinator_device<public> := class(creative_device): @editable EndGame<public> : end_game_device = end_game_device{} var CountdownTimer<private> : countdown_timer = countdown_timer{} var MaybePlayer<private> : ?player = false OnBegin<override>()<suspends> : void = FindPlayer() SetupZones() FindPlayer<private>() : void = # Since this is a single player experience, the first player (at index 0) # should be the only one available. if (FirstPlayer := GetPlayspace().GetPlayers()[0]): set MaybePlayer = option{FirstPlayer} Logger.Print("Player found") else: # Log an error if we can't find a player. # This shouldn't happen because at least one player is always present. Logger.Print("Can't find valid player", ?Level := log_level.Error)カウントダウン タイマーが終了するのを待機し、エンド ゲームの仕掛けをアクティブ化する関数を
HandleCountdownEnd()という名前で作成します。VerseHandleCountdownEnd<private>(InPlayer : agent)<suspends> : void = CountdownTimer.CountdownEndedEvent.Await() EndGame.Activate(InPlayer)StartGame()という名前の関数を作成し、OnBegin()内でSetupZones()の後にこの関数を呼び出します。 この関数では以下のことを行います。カウントダウン タイマーを初期化する。
Versegame_coordinator_device<public> := class(creative_device): # How long the countdown timer will start counting down from. @editable InitialCountdownTime<public> : float = 30.0 @editable EndGame<public> : end_game_device = end_game_device{} OnBegin<override>()<suspends> : void = FindPlayer() SetupZones() StartGame() StartGame<private>()<suspends> : void = Logger.Print("Trying to start the game...") <# We construct a new countdown_timer that'll countdown from InitialCountdownTime once started. The countdown_timer requires a player to show their UI to. We should have a valid player by now. #> if (ValidPlayer := MaybePlayer?): Logger.Print("Valid player, starting game...") set CountdownTimer = MakeCountdownTimer(InitialCountdownTime, ValidPlayer) CountdownTimer.StartCountdown() else: Logger.Print("Can't find valid player. Aborting game start", ?Level := log_level.Error)HandleCountdownEnd(ValidPlayer)とPickupDeliveryLoop()の両方を呼び出すrace式を使用して、次のようになるようにします。カウントダウン終了時にゲーム ループを停止する、または
ゲーム ループが停止したら、カウントダウンをキャンセルする。
•
VerseStartGame<private>()<suspends> : void = Logger.Print("Trying to start the game...") <# We construct a new countdown_timer that'll countdown from InitialCountdownTime once started. The countdown_timer requires a player to show their UI to. We should have a valid player by now. #> if (ValidPlayer := MaybePlayer?): Logger.Print("Valid player, starting game...") set CountdownTimer = MakeCountdownTimer(InitialCountdownTime, ValidPlayer) CountdownTimer.StartCountdown() # We wait for the countdown to end. # At the same time, we also run the Pickup and Delivery game loop that constitutes the core gameplay. race: HandleCountdownEnd(ValidPlayer) PickupDeliveryLoop() else: Logger.Print("Can't find valid player. Aborting game start", ?Level := log_level.Error)
「game_coordinate_device.verse」ファイルは次のようになります。
Verseusing { /Verse.org/Simulation } using { /Fortnite.com/Devices } using { /Fortnite.com/Vehicles } using { /Fortnite.com/Characters } using { /Fortnite.com/Playspaces } using { /Verse.org/Random } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/SpatialMath } using { /UnrealEngine.com/Temporary/Curves } using { /Verse.org/Simulation/Tags }Verse ファイルを保存し、コードをコンパイルして、レベルをプレイテストします。
レベルのプレイテスト時には前のセクションと同じように機能しますが、カウントダウンが終了したとき、またはゲーム ループに問題があるときにゲームを終了するタイマーが追加されています。