마지막 단계는 게임 관리 장치(game manager)를 사용해 모든 것을 하나로 합치는 것입니다. 게임 관리 장치는 플레이어에 대한 게임플레이 오브젝트 할당과 게임 루프 플로우를 제어합니다. 게임 관리 장치의 구체적인 기능은 다음과 같습니다.
플레이어에게 보드와 미니보드 같은 게임플레이 오브젝트를 할당합니다.
행동 발생 시 일어나는 일 등 게임 루프의 로직을 제어합니다.
플레이어의 승리 시점과 게임플레이 종료 시점을 결정합니다.
행동 유형 정의하기
플레이어의 턴에서, 플레이어는 게임보드상의 좌표를 선택합니다. 좌표를 선택한 후에는 두 가지 행동 유형이 존재합니다.
공격: 특정 위치의 폰을 파괴하려 시도합니다.
공개: 특정 위치의 반경 내 모든 폰을 드러냅니다.
DataTypes 모듈에서 행동 유형을 정의하는 다음 enum을 추가합니다.
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:
승리 조건 정의하기
다음으로 해야 할 일은 플레이어가 승리 조건을 충족하는 시점을 결정하는 것입니다. 플레이어는 보드에 찾아 파괴할 폰이 더 없을 경우 승리합니다. 이는 보드의 Pawns 배열 길이를 직접적으로 쿼리함으로써 이루어집니다. 게임 관리 장치에 다음 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}")
이 함수는 입력 플레이어가 찾아내거나 없앨 폰이 더 남지 않은 경우에만 성공합니다.
공격 행동 시
이제 각 플레이어에게 게임 오브젝트를 할당하는 방법과 플레이어의 승리 여부 판단 방법을 알게 되었으니, 다음 단계는 공격 행동 중 일어나는 일을 정의하는 것입니다. 플레이어가 상대의 타일 중 하나를 공격하면 다음 단계가 발생합니다.
보드 위의 폰이 공격 좌표에 있는지 확인합니다.
있는 경우:
플레이어의 보드에서 폰을 제거합니다.
상대의 미니보드에 적중 마커를 설정합니다.
없는 경우:
상대의 미니보드에 빗나감 마커를 설정합니다.
다음 정의와 함께 game_manager 클래스에 이름이 OnAttack인 함수를 추가합니다.
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}")
공개 행동 시
다른 행동 유형은 공개 행동입니다. 여기에는 공격 행동에 비해 몇 가지 추가적인 설정이 필요합니다. 먼저 UtilityFunctions 모듈에 세 가지 신규 함수를 추가해야 합니다.
operator'-': 두 개의tile_coordinate오브젝트에 대해 뺄셈 이항 연산자를 정의합니다.Abs:tile_coordinate의 컴포넌트 측면 절대값을 구합니다.ManhattanDistance: 두tile_coordinate오브젝트 간의 맨해튼 거리(택시 거리라고도 함)를 구합니다.
UtilityFunctions<public> := module:
using{DataTypes}
...
Abs(TileCoordinate:tile_coordinate)<transacts>:tile_coordinate =
tile_coordinate:
Left := Abs(TileCoordinate.Left)
Forward := Abs(TileCoordinate.Forward)
맨해튼 거리는 타일 그리드에서 기본 방향을 따라 탐색하며 두 tile_coordinate 오브젝트 사이의 거리를 계산합니다. 자세한 정보는 https://en.wikipedia.org/wiki/Taxicab_geometry를 참고하세요.
이제 유틸리티 정의가 완료되었으니, OnReveal 함수의 비헤이비어를 정의합니다. 플레이어가 적의 tile_coordinate로부터 특정 반경 내 적을 드러내기로 하는 경우, 다음 단계가 발생합니다.
ManhattanDistance에 따라 입력 좌표로부터 설정된RevealDistance내에 있는 플레이어 보드상의 모든 폰을 찾습니다.해당 거리 내에 있는 모든 폰마다 공개 이펙트를 재생합니다.
다음 정의와 함께 game_manager 클래스에 이름이 OnReveal인 함수를 추가합니다.
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:
플레이어 턴
다음으로 플레이어의 턴이 어떻게 보일지 구성합니다. 플레이어의 턴이 되면, 신호를 받을 수 있는 두 개의 이벤트 중 하나를 기다립니다. MoveEvent 또는 CoordinateChangeEvent입니다. 이 이벤트 중 하나라도 신호를 받으면 다른 이벤트를 버리고 race 조건 내부에 두 이벤트를 나란히 배치합니다. 좌표 변경 신호를 받으면 같은 플레이어는 행동 유형을 선택할 때까지 계속 플레이해야 합니다. 그러므로, 공격 또는 공개가 선택될 때만 다음 플레이어에게 차례가 넘어갑니다.
다음 정의와 함께 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)
게임 루프 정의하기
이제 플레이어 턴이 정의되었으니, 드디어 기본 게임 루프를 생성할 수 있습니다. 게임 루프 내에서는 다음 단계가 발생합니다.
모든 플레이어를 모읍니다.
한 플레이어에게 턴을 할당하고, 다른 플레이어는 첫 번째 플레이어가 행동하기까지 기다리도록 합니다.
플레이어가 턴을 번갈아 가며 플레이하다가 한 명이 승리할 때까지 반복합니다.
이를 위해 game_manager 클래스에 다음 GameLoop 함수를 추가합니다.
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에 호출을 추가하고 OnBegin 함수에 GameLoop를 추가해 자동으로 수행할 수 있습니다.
# Runs when the device is started in a running game
OnBegin<override>()<suspends>:void=
AssignPlayerObjects()
GameLoop()요약
요약해 보면, 이 페이지에서는 다음 단계를 살펴보았습니다.
게임 행동을 정의합니다.
게임 루프를 생성합니다.
승리 조건이 언제 충족되는지 결정합니다.
경험을 완전히 나만의 것으로 만들기 위해 할 수 있는 일이 아직 많이 있습니다. 예를 들면 다음과 같습니다.
유저 인터페이스를 디자인하고 구현합니다.
플레이어 행동이 게임 관리 장치에 신호를 전달하는 시점과 방법을 연결합니다.
게임 세계 및 배경을 디자인합니다.
공격 및 공개의 이펙트를 만듭니다.
음악 디자인과 배경을 추가합니다.
자유롭게 핵심 게임플레이 클래스를 기반으로 제작하고, 재생성하고, 일부를 사용하여 완전히 나만의 것으로 만들어 보세요.
Files
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