タグ付きライト パズル 用の Verse コードの作成を開始する前に、実行したい内容を実現する最適な方法を考えてみましょう。このセクションでは、パズルのメカニックを作成するアプローチ方法を示します。このステップの最後に、このパズルを作成するための アルゴリズム を表す 疑似コード があります。次のステップ では、Verse と UEFN でこのアルゴリズムを実装する方法を示します。
目標、要件、およびコンストレイントを特定する
最初のステップでは、目標、要件およびコンストレイントを特定します。 要件は、多様な目標を小さく分割することで明らかになることがよくあります。
| 目標 |
|
| 要件 |
|
| コンストレイント |
|
問題を分割する
実現したい内容と作業する内容を理解したので、簡単に答えを出せる小さな問題に元の問題を分割します。 次のような質問を考えると、大きな問題を分割することができます。
- プレイヤーはパズルにどのようにインタラクションできるか?
- ライトを見つけるのにゲームプレイ タグをどのように使用するか?
- エディタで変更できる初期条件と解決法をどのように定義するか?
- Verse 構造体に格納されたゲーム ステートとインゲームのビジュアルをどのように一致させるか?
- ライトの特定セットをプレイヤーのインタラクションでどのように更新できるか?
- パズルを解いた後に、プレイヤーのインタラクションをどのように無効化するか?
次に、これら小さな問題にある依存関係の可能性を特定します。 この場合、問題は独立しているように見えますが、次の点を考慮する価値があります。
- 質問 1、5 および 6 はゆるやかに関連しています。
- 質問 1 と 6 に対して、「プレイヤーがどのようにパズルとインタラクションするか」では、「パズルを解いたときに、インタラクションをどのように無効化するか」を決定できません。
- 質問 1 と 5 に対して、単一のインタラクションで、複数のライトを一度に切り替えます。これは、データ構造をインタラクションからライトへのマッピングを使用するように設定します。
- 質問 2 は重要な設計の考慮事項です。ゲームプレイ タグ API の仕組みが、コードでライトをコントロールする方法に影響を及ぼす可能性があります。これは質問 4 と 5 に予期しない結果をもたらします。インゲームのライト ステートを変更する必要があり、これを実行する共通の方法を見つける必要があるためです。
- 質問 3 と 4 は、おそらく開始、現在、解決の各ステートに対するベースのデータ構造に対する単一の解決法でカバーできます。
可能性がある解決法を考案する
元の問題を小さな問題に分割したので、小さな問題に関連する質問の答えを出すことに注力します。
1.プレイヤーはパズルにどのようにインタラクションできるか?
この質問には複数の解決法があります。一般に、プレイヤーがインタラクションでき、インタラクションを検出するために Verse が使用できる、どのような仕掛けも使用できます。クリエイティブ ツールセットでは、これらの要件を満たす多くの仕掛けがあります。たとえば、トリガーの仕掛け、ボタンの仕掛け、さらに カラー チェンジ タイルの仕掛け および パーセプション トリガーの仕掛け があります。
この例ではボタンの仕掛けとその InteractedWithEvent を使用します。これはボタンが有効である限り、プレイヤーがボタンにインタラクトするたびに、ディスパッチされます。イベントの詳細については、「仕掛けのインタラクションをコード化する」を参照してください。
2. ライトを見つけるのにゲームプレイ タグをどのように使用するか?
ゲームプレイ タグがあると、Verse コードで定義したカスタム タグに割り当てられたアクタのグループを取得できます。
GetCreativeObjectsWithTag() 関数を使用して、使用するカスタム タグを割り当てたすべてのアクタのリストを取得します。この関数の結果は、creative_object_interface を実装するすべてのオブジェクトの配列です。customizable_light_device はカスタマイズ可能なライトの仕掛けの Verse 表現で、creative_object_interface を実装するクラスです。
GetCreativeObjectsWithTag() から返される仕掛けのリストの順番は決まっていません。そして 関数呼び出し はすべての仕掛けを返すのに時間がかかることがあります。特にレベルに多数の仕掛けがある場合は時間がかかるため、すばやくアクセスできるようにライトを格納しておくことをお勧めします。これは キャッシング と呼ばれ、多くの場合、パフォーマンスを向上させます。ライトは同じタイプのコレクションであるため、配列 を使用して、まとめて格納できます。
つまり以下の項目を実行できます。
puzzle_lightという名前で新しいタグを作成します。puzzle_lightタグで、パズルのすべてのライトをマークします。GetCreativeObjectsWithTag(puzzle_light)を呼び出して、puzzle_lightタグを含むすべてのアクタを取得します。- 関数呼び出しからの結果が
customizable_light_deviceであるかどうかを決定します。 - 配列の
customizable_light_deviceオブジェクトのリストを保存し、後からアクセスできるようにします。
3. エディタで変更できる初期条件と解決法をどのように定義するか?
ライトに設定するのは 2 つのステート、オン または オフ だけです。Verse の logic 型を使用すると、ライトのオン/オフ ステートを表現できます。logic 型の値は、true または false だけであるためです。複数のライトがあるため、配列を使用して、すべての logic 値を格納でき、ライト ステートに対する配列の位置 (インデックス) を関連するライトのインデックスに一致させます。
この logic 値の配列を使用して、パズル ライトの初期ステートを定義し、ゲーム中にライトの現在のステートを入れることもできます。この配列をエディタに @editable 属性で公開できます。ゲームの開始時にライトはオンにもオフにもでき、配列に格納されたステートにビジュアルを一致させることができます。
パズルの解決状態は、ライトの現在のステートを格納するために使用するタイプに一致する必要があります。つまり 2 つを比較することでパズルが解かれたかどうかをチェックできます。これは、2 つの編集可能な logic 配列があり、一方はライトの現在の状態を表し、もう一方は、パズルの解決状態を表すということです。つまり、パズル ライトの初期ステートとパズルの解決状態をエディタから編集でき、結果として、異なるコンフィギュレーションでパズルを再利用できます。
4. Verse 構造体に格納されたゲーム ステートとインゲームのビジュアルをどのように一致させるか?
関数 TurnOn() と TurnOff() を使用して、インゲームで customizable_light_device をオンまたはオフに切り替えられます。ロジック配列で表現されたとおり、ライトの現在のステートを更新するときは常に、TurnOn() と TurnOff() を呼び出して、インゲームのビジュアルとゲーム ステートを一致させます。
5. ライトの特定セットをプレイヤーのインタラクションでどのように更新できるか?
最初の質問から、ボタンの仕掛けを使用して、プレイヤーがパズルとインタラクションすることを決定しました。イベント ハンドラをボタンの InteractedWithEvent に サブスクライブ できます。これによりプレイヤーがボタンの仕掛けとインタラクションしたときにライトを変更します。プレイヤーが使用するボタンが複数あるため、再び配列を使用して、それらをまとめて保持することができます。
ボタン イベントそれぞれを、切り替えるライトのセットにマップする方法を特定する必要があります。
customizable_light_device 配列のライトの順番が、ライトのステートを表すロジック配列と同じ順番であるため、ボタンとその影響を受けるライトのインデックスのマッピングを作成できます。 このマッピングは配列で表現できます。そこで要素の順番は、ボタンの順番に一致し、要素はインデックスの配列です。
配列を編集可能にできます。つまりエディタでボタンとライトのマッピングを変更でき、コード自体を変更しないでパズルを再利用できます。
6.パズルを解いた後に、プレイヤーのインタラクションをどのように無効化するか?
InteractedWithEvent を通じて検出される、ボタンの仕掛けを使用してパズルとプレイヤーがインタラクションすることがわかっています。
パズルが解かれたとき、プレイヤーがパズルを変更できなくなるように、パズルの仕掛けがプレイヤーからの入力を受け取るのを停止するにはどのような方法があるでしょうか。
これを実行するには次の 3 種類の方法があります。
- パズルが解かれたときに、インゲームのボタンを無効にします。
- パズルが解かれたときに、変更された
tagged_lights_puzzleにlogicフィールドを追加します。ゲーム ステートが更新されるといつも、このlogicフィールドをまずチェックし、パズルがまだ解かれていないことを確認します。 - パズルが解かれたときに、ボタン
InteractedWithEventからサブスクライブを解除して、イベント ハンドラが呼び出されないようにします。
シンプルで効率的な解決法であるため、3 番目のオプションが最適です。 条件コード実行をチェックするために新しいフィールドを作成する必要がありません。 仕掛けイベントからサブスクライブを解除する考え方は、他の状況でも再利用できます。 一般にお勧めするのは、対象について通知を受けたいときにイベントをサブスクライブし、必要がなくなったときにサブスクライブを解除します。 サブスクライブの解除に対する実装の詳細は、このチュートリアルの後の部分で説明します。
解決法と計画を疑似コードで結合する
小さな分割した問題の解決法が得られたので、それらをまとめて元の問題を解決します。 解決法を構築するためにアルゴリズムを疑似コードで形式化します。
ゲームを開始したときに何が起こるか?各ライトがセットアップされます。ボタンの InteractedWithEvent にサブスクライブし、puzzle_light タグがあるすべての仕掛けを見つけて、キャッシュに保存します。開始時の LightState に基づいて、インゲームのライトのオン/オフも切り替えます。
OnBegin:
Result of GetCreativeObjectsWithTag(puzzle_light) is stored in the variable FoundDevices
for each Device in FoundDevices:
if Device is a Customizable Light Device:
Store the Light
if ShouldLightBeOn?:
Turn on Light
else:
Turn off Light
for each Button:
Subscribe to the Button InteractedWithEvent using the handler OnButtonInteractedWith
OnButtonInteractedWith の疑似コード バージョンはこのような感じで、InteractedButtonIndex は、プレイヤーがインタラクトするボタンに一致する button_device 配列のインデックスを表します。チュートリアルの後半で、イベント ハンドラ内でこの情報を受け取る方法を説明します。
OnButtonInteractedWith:
Get lights associated with the button interacted with using the ButtonsToLights array and store in the variable Lights
# ライトのオン/オフを切り替える
for each Light in Lights:
if IsLightOn?:
Set the Light game state to off
Turn off Light
else:
Set the Light game state to on
Turn on Light
if IsPuzzleSolved():
Enable Item Spawner
for each Button:
Unsubscribe from the Button InteractedWithEvent
IsPuzzleSolved の疑似コードでは、ライトの現在のステートが解決状態に一致するかどうかをチェックします。現在のステートが解決状態に一致しない場合、チェックは失敗し、上の疑似コードの if IsPuzzleSolved ブロックが実行されません。現在のステートが解決状態に一致する場合、チェックは成功し、if IsPuzzleSolved ブロックが実行されます。
IsPuzzleSolved:
for each Light:
if IsLightOn is not equal to IsLightOnInSolution
fail and return
succeed (成功)
以上でアルゴリズムは完成です。
次のステップ
このチュートリアルの 次のステップ で、このアルゴリズムを Verse プログラミング言語に変換し、プロジェクトをプレイテストして、各ステップが実行されることを確認します。