「タグ付きライト パズル」チュートリアルのこの最終ステップでは、パズルの 完全なスクリプト を示し、この例をさらに変更する アイデア を紹介します。
完全なスクリプト
次のコードは再利用可能なパズルの完全なスクリプトです。このパズルではプレイヤーがボタンを操作してライトのステートを切り替えて、適切なライトの組み合わせを見つける必要があります。
using { /Fortnite.com/Devices}
using { /Verse.org/Native}
using { /UnrealEngine.com/Temporary/Diagnostics}
using { /Verse.org/Simulation/Tags}
using { /Verse.org/Simulation}
# Verse.org/Simulation/Tags モジュールにある `tag` クラスから派生させて、新しいゲームプレイ タグを作成します。
puzzle_light := class(tag){}
log_tagged_lights_puzzle := class(log_channel){}
<#
このクラスのインスタンスを使って、
ボタンの仕掛け (button_device) の InteractedWithEvent をサブスクライブし、イベント ハンドラに含まれるイベントごとのプロパティ (この場合は Indices and PuzzleDevice) にアクセスします。
それぞれのボタンに独自のステートとイベント ハンドラがあると考えてください。
このクラスには <concrete> 指定子が付いていないため、各フィールドにデフォルト値を指定することは必須ではありません。
#>
button_event_handler := class():
# このボタンで制御するライトにアクセスするために使用される位置。
Indices : []int
# tagged_lights_puzzle that created this button_event_handler so we can call functions on it.
PuzzleDevice : tagged_lights_puzzle
OnButtonPressed(InPlayer : agent) : void =
# このボタンで制御している位置にあるライトのオン/オフを切り替えるように PuzzleDevice に指示します。
PuzzleDevice.ToggleLights(Indices)
tagged_lights_puzzle := class<concrete>(creative_device):
Logger : log = log{Channel := log_tagged_lights_puzzle}
# プレイヤーがパズルの操作に使用するボタン。
@editable
Buttons : []button_device = array{}
<#
ButtonsToLights の要素数は Buttons の要素数と一致させる必要があります。
インデックスのゼロから始まるそれぞれの配列では、Buttons[x] のボタンでどのライトを切り替えるかを定義します ('x' はボタンのインデックス)。
たとえば Buttons[2] では、array{0,1} で指定されているように、1 つ目と 2 つ目のライトのオン/オフを切り替えます。
#>
ButtonsToLights : [][]int = array{array{0, 3}, array{0, 1, 2}, array{0, 1}, array{1}}
<#
LightsState 論理配列は、すべてのライトのオン/オフのステートをトラックするのに使用されます。
また、ライトの初期ステートの設定にも使用されるため、その要素の数は、
puzzle_light タグが付いたライトの数と一致する必要があります。
#>
@editable
var LightsState : []logic = array{false, false, false, false}
<#
LightsState と SolvedLightsState が一致した場合にパズルが解かれます。
つまり、SolvedLightsState には LightsState と同じ数の要素が含まれるべきであることを意味します。
#>
@editable
SolvedLightsState : []logic = array{true, true, true, true}
@editable
# パズルが解かれると、この ItemSpawner が有効になります。
ItemSpawner:item_spawner_device = item_spawner_device{}
# ゲームプレイ タグを通じて見つかったライトを保持します。
var Lights : []customizable_light_device = array{}
# 各ボタンの InteractedWithEvent のハンドラを保持して、パズルが解かれたときにそのイベントをサブスクライブ解除できるようにします。
var ButtonSubscriptions : []cancelable = array{}
OnBegin<override>()<suspends> : void =
SetupPuzzleLights()
<#
それぞれのボタンとそのインデックスについて、それらに有効な LightsIndices 値が含まれていれば、
新しいボタン イベント ハンドラ (button_event_handler) を、それが使用するインデックスと、この tagged_light_puzzle (自身) への参照を使って作成します。
ハンドラの OnButtonPressed 関数を使って、ボタンの InteractedWithEvent をサブスクライブします。
これらのサブスクリプションを ButtonSubscriptions 配列に保存して後でキャンセルできるようにすることで、プレイヤーがすでに解いたパズルとやり取りできないようにします。
#>
set ButtonSubscriptions = for:
ButtonIndex -> Button : Buttons
LightIndices := ButtonsToLights[ButtonIndex]
do:
Button.InteractedWithEvent.Subscribe(button_event_handler{Indices := LightIndices, PuzzleDevice := Self}.OnButtonPressed)
SetupPuzzleLights() : void =
# ゲームプレイ タグを通じて取得した仕掛けが特定の順序になっていることは保証されないことに注意してください。
TaggedActors := GetCreativeObjectsWithTag(puzzle_light{})
<#
puzzle_light タグを含むそれぞれのアクタについて、当該の型にそれをキャストしようとすることで、カスタム可能なライトの仕掛け (customizable_light_device) であるかどうかをチェックします。
そうであれば、LightDevice を有効 (TurnOn())/無効 (TurnOff()) にするために、LightState の初期値を取得します。
タグ付けされたすべてのカスタマイズ可能なライトの仕掛け (customizable_light_device) を Lights 配列に保存します。
#>
set Lights = for:
ActorIndex -> TaggedActor : TaggedActors
LightDevice := customizable_light_device[TaggedActor]
ShouldLightBeOn := LightsState[ActorIndex]
do:
Logger.Print("Adding Light at index {ActorIndex} with State:{if (ShouldLightBeOn?) then "On" else "Off"}")
if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff()
LightDevice # 式の最後の文が、その結果の値です。`for` 式では、すべての値が配列で返されます。
ToggleLights(LightIndices : []int) : void =
<#
インデックスごとに、対応する LightState とライトの仕掛けへの参照を取得します。
その両方が有効な場合は、現在の LightState から IsLightOn を判定して (オンであればオフに、オフであればオンに)、
対応する LighsState[Index] を IsLightOn に設定します。
#>
for:
LightIndex : LightIndices
IsLightOn := LightsState[LightIndex]
Light := Lights[LightIndex]
do:
<#
Verse でブール値を反転させるには次のようにします。if (MyLogic?){false} else {true}
ここで、無効にした値を返す前に、ライトも無効 (TurnOff()) または有効 (TurnOn()) にします。
#>
Logger.Print("Turning light at index {LightIndex} {if (IsLightOn?) then "Off" else "On"}")
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
false
else:
Light.TurnOn()
true
if (set LightsState[LightIndex] = NewLightState): # Array indexing may fail, so it must be wrapped in a failure context.
Logger.Print("Updated the state for light at {LightIndex}")
# LightsState が変化している可能性があるため、パズルが解かれているかどうかをチェックします。
if (IsPuzzleSolved[]):
Logger.Print("Puzzle solved!")
ItemSpawner.Enable()
# プレイヤーがこれ以上ライトを切り替えることができないように、すべてのイベント ハンドラをサブスクライブ解除/キャンセルします。
for (ButtonSubscription : ButtonSubscriptions):
ButtonSubscription.Cancel()
<#
指定子 <decides> が付いている関数を使用できるのは、`if (IsPuzzleSolved[])` などの失敗コンテキスト内だけです。
() ではなく [] を使う関数呼び出しは、失敗する可能性のある関数呼び出しであることに留意してください。
#>
IsPuzzleSolved()<decides><transacts> : void =
<#
各 LightsState をイテレートして、それぞれの SolvedLightsState と一致するかどうかをチェックします。
`for` 内部ブロックが初めて失敗すると、その関数も失敗して、
その失敗が呼び出し元にバブルアップされます。
それ以外の場合は関数が成功します。
#>
for:
LightIndex -> IsLightOn : LightsState
IsLightOnInSolution := SolvedLightsState[LightIndex]
do:
IsLightOn = IsLightOnInSolution
応用編
このチュートリアルを完了することにより、Verse を使用して再利用できるパズルを作成する方法を習得しました。このパズルでは、プレイヤーがボタンを操作してライトのステートを切り替えて、適切なライトの組み合わせを見つける必要があります。
ここで学習したことを活かして以下を試してみてください。
- さらにタグを作成し、そのタグを使用して、決まった表示順序でライトを制御する。
- 異なる初期条件と解き方を使用し、ボタンとライトを増やして、同じセットアップでパズルを作成する。
- ユーザーが操作できる複数のタイプの仕掛けを使用して、多様な仕掛けを制御する。