最後のステップでは、ゲーム マネージャーを通じて全ての要素をまとめます。 ゲーム マネージャーは、プレイヤーへのゲームプレイ オブジェクトの割り当てとゲーム ループのコントロールを行います。 具体的に、ゲーム マネージャーは:
ボードやミニボードなどのゲームプレイ オブジェクトをプレイヤーに割り当てます。
移動したときの動作など、ゲーム ループのロジックを制御します。
プレイヤーが勝利してゲームプレイが終了したタイミングを判断します。
移動タイプの定義
プレイヤーのターン中、プレイヤーはゲーム ボード上の座標を選択します。 座標が選択されると、次の2種類の移動タイプがあります。
アタック:指定された位置にあるポーンの破壊を試みます。
公開:指定された場所から特定の半径内にある全てのポーンを公開します。
DataTypes モジュールで、移動タイプを定義する次の列挙型を追加します。
using{/Verse.org/Simulation}
using{/Verse.org/Random}
using{/UnrealEngine.com/Temporary/SpatialMath}
DataTypes<public> := module:
...
move_type<public> := enum<open>:
Attack
この列挙型はオープンであるため、将来いつでも移動型を追加できます
ゲーム マネージャーの作成
次に、game_manager.verse という名前の新しい Verse ファイルを作成し、game_manager という名前の新しいクリエイティブの仕掛けを追加します。 これは、ゲームワールドに存在し、ゲームの流れをコントロールする単一のオブジェクトになります。
プレイヤーごとのオブジェクトの定義
各プレイヤーには、ゲーム ボードやミニボードだけでなく、自分がどのタイルをアタックしたか、移動イベント、選択した座標が変更されたイベントなど、いくつかのオブジェクトが関連付けられています。 per_player_objects という名前の新しいクラスを定義します。
using { /Verse.org/Simulation }
per_player_objects<public> := class:
@editable
Board<public>:board
@editable
Miniboard<public>:miniboard
var AttackedTiles<public>:[]tile_coordinate = array{}
MoveEvent<public>:event(tuple(tile_coordinate, move_type)) = event(tuple(tile_coordinate, move_type)){}
CoordinateChangeEvent<public>:event(tile_coordinate) = event(tile_coordinate){}ゲーム マネージャーの定義
ゲーム マネージャー クラスでは、プレイヤーをプレイヤー オブジェクトに関連付ける必要があります。 Verse でプレイヤーをオブジェクトに関連付けるために最適な方法は、weak_map を使用することです。 ゲーム マネージャー クラスに次のフィールドを追加します。
using { /Verse.org/Simulation }
per_player_objects<public> := class:
@editable
Board<public>:board
@editable
Miniboard<public>:miniboard
var AttackedTiles<public>:[]tile_coordinate = array{}
MoveEvent<public>:event(tuple(tile_coordinate, move_type)) = event(tuple(tile_coordinate, move_type)){}
CoordinateChangeEvent<public>:event(tile_coordinate) = event(tile_coordinate){}
プレイヤーオブジェクトの割り当て
プレイヤーが参加するときに、PerPlayerObjects から PerPlayerManagement オブジェクトへ各プレイヤーにプレイヤー オブジェクトを割り当てます。 まず、ゲーム内の全てのプレイヤーを取得し、各プレイヤーにプレイヤー オブジェクトを割り当てます。 十分な数のプレイヤー オブジェクトがない場合は、エラーを処理します。 ゲーム マネージャーに次の AssignPlayerObjects 関数を追加します。
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
...
game_manager := class(creative_device):
AssignPlayerObjects():void =
for (Index -> Player : GetPlayspace().GetPlayers()):
if:
勝利条件の定義
次は、プレイヤーが勝利条件を満たしたタイミングを判断します。 ボード上で見つけて破壊するポーンがなくなると、プレイヤーは勝利します。 これは、ボード内のポーン配列の長さを直接クエリすることで実行されます。 ゲーム マネージャーに次の WinConditionMet 関数を追加します。
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
...
game_manager := class(creative_device):
WinConditionMet(Player:player)<decides><transacts>:void =
# Player wins if no pawns remain
Print("Pawns remaining: {PerPlayerManagement[Player].Board.Pawns.Length}")
入力プレイヤーに対して見つけて破棄するポーンが残っていない場合にのみ、この関数が成功します。
アタック移動時
各プレイヤーにゲーム オブジェクトを割り当て、プレイヤーが勝利したかどうかを判断する方法を把握できたので、次は攻撃移動中の動作を定義します。 プレイヤーが相手のタイルを1つアタックすると、次のステップが実行されます。
攻撃座標にポーンがあるかどうかを特定します。
[Yes] の場合:
プレイヤーのボードからポーンを削除します。
対戦相手のミニボードにヒットマーカーを set します。
指定していない場合:
対戦相手のミニボードにミス マーカーを set します。
「OnAttack」という名前の関数を、次の定義を使用して game_manager クラスに追加します。
OnAttack(Instigator:player, Recipient:player, TileCoordinate:tile_coordinate):void =
if:
InstigatorObjects := PerPlayerManagement[Instigator]
RecipientObjects := PerPlayerManagement[Recipient]
then:
# Determine if the attack is a hit
var MarkerType:marker_type = marker_type.Miss
Print("Attack coordinate: Left: {TileCoordinate.Left}, Forward: {TileCoordinate.Forward}")
公開移動時
もう 1 つの移動型は公開移動です これには、アタック動作を処理するための追加設定が必要となります。 最初に、3 つの新しい関数を UtilityFunctions モジュールに追加します。
operator'-':2 つのtile_coordinateオブジェクトの減算バイナリ演算を定義します。Abs:tile_coordinateのコンポーネントごとの絶対値を取得します。ManhattanDistance:2 つのtile_coordinateオブジェクト間の Manhattan または Taxicab の距離を取得します。
UtilityFunctions<public> := module:
using{DataTypes}
...
Abs(TileCoordinate:tile_coordinate)<transacts>:tile_coordinate =
tile_coordinate:
Left := Abs(TileCoordinate.Left)
Forward := Abs(TileCoordinate.Forward)
Manhattan Distance は、タイル グリッド上を基本方位に沿って移動することによって、2 つの tile_coordinate オブジェクト間の距離を計算します。 詳細は、https://en.wikipedia.org/wiki/Taxicab_geometry をご覧ください。
ユーティリティが定義されたので、OnReveal 関数の動作を定義します。 プレイヤーが敵に対する tile_coordinate のある半径内にポーンを公開することを選択した場合、次のステップが実行されます。
ManhattanDistanceに応じて、入力座標から設定されたRevealDistance内にあるプレイヤーのボード上の全てのポーンを見つけます。その距離内にある全てのポーンに対して、公開エフェクトを再生します。
「OnReveal」という名前の関数を次の定義で game_manager クラスに追加します。
OnReveal(Instigator:player, Recipient:player, TileCoordinate:tile_coordinate):void =
if:
InstigatorObjects := PerPlayerManagement[Instigator]
RecipientObjects := PerPlayerManagement[Recipient]
then:
for:
Pawn : InstigatorObjects.Board.Pawns
PawnTileCoordinate := InstigatorObjects.Board.GetTileCoordinate[Pawn]
ManhattanDistance(PawnTileCoordinate, TileCoordinate) < RevealDistance
do:
プレイヤーのターン
次に、プレイヤーのターンがどのように見えるかを構成します。 プレイヤーのターンが来たときに、通知できる 2 つの異なるイベント (MoveEvent または CoordinateChangeEvent) のいずれかを待機します。 これらのイベントのいずれかが通知されるたびに、他のイベントを放棄し、これらのイベントを競合状態内に並べます。 座標の変更が通知されると、同じプレイヤーは移動型を選択するまでプレイを続けます。 そのため、アタックまたは非表示が選択されたときにのみ、次のプレイヤーに移ります。
次の定義を使用して game_manager クラスに関数 OnTurn を追加します。
OnTurn(Player:player, Opponent:player)<suspends>:void =
if (PlayerObjects := PerPlayerManagement[Player]):
loop:
var Continue:logic = false
race:
block:
# Listens for a call to PerPlayerManager[Player].CoordinateChangeEvent.Signal(:tile_coordinate)
TileCoordinate := PlayerObjects.CoordinateChangeEvent.Await()
block:
# Listens for a call to PerPlayerManager[Player].MoveEvent.Signal(:tile_coordinate,:move_type)
ゲーム ループを定義する
プレイヤーのターンが定義されたので、プライマリ ゲーム ループを構築できます。 ゲーム ループ内では、次のステップが実行されます。
全てのプレイヤーを取得します。
1 人のプレイヤーに自分のターンを割り当て、もう 1 人のプレイヤーに最初のプレイヤーが移動するのを待機させます。
プレイヤーが勝利するまでループし、それぞれが交互にターンします。
これを行うには、次の GameLoop 関数を game_manager クラスに追加します。
GameLoop()<suspends>:void =
Players := GetPlayspace().GetPlayers()
if :
Players.Length = 2
var TurnPlayer:player = Players[0]
var OtherPlayer:player = Players[1]
then:
loop:
OnTurn(TurnPlayer, OtherPlayer)
if (WinConditionMet[TurnPlayer]):
ゲームプレイの開始
最後に、各プレイヤーにプレイヤー オブジェクトを割り当てて、ゲーム ループを開始します。 これを自動的に行うには、AssignPlayerObjects および GameLoop の呼び出しを OnBegin 関数に追加します。
# Runs when the device is started in a running game
OnBegin<override>()<suspends>:void=
AssignPlayerObjects()
GameLoop()概要
要約すると、このページでは、次の手順を実行しました。
ゲームの動きを定義します。
ゲーム ループを構築します。
勝利条件が満たされるタイミングを決定します。
しかし、このエクスペリエンスを独自のものにするために実行できることはまだたくさんあります。たとえば、
ユーザー インターフェースを設計して実装。
プレイヤーがいつどのように動くのかをゲーム マネージャーに通知する方法を設定。
ゲームのワールドと設定をデザイン。
攻撃や特定のエフェクトの作成。
音楽デザインと装飾の追加。
これらのコア ゲームプレイ クラスを自由にビルドし、再構築し、その一部を使用して、独自のものを作成して構いません。
ファイル
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { DataTypes }
using { UtilityFunctions }
per_player_objects<public> := class:
@editable
Board<public>:board
@editable