Systems
골프 템플릿 내의 시스템은 2개의 카테고리로 구분됩니다:
- 게임 플레이를 담당하는 시스템
- 플레이어 턴을 전용을 담당하고 있는 시스템.
Gameplay
Gameplay 시스템은 골프 템플릿에 대한 모든 게임 로직을 처리합니다.
최대 플레이어 수
최대 플레이어 수는 2이며 DSL에서 #pragma max_players 2
를 사용하여 고정됩니다. 또한 이 값은 배열 초기화에 재사용하기 위해 상수 #define MAX_PLAYERS 2
로 정의됩니다.
SetupSystem
SetupSystems
은 초기 게임 설정을 처리하며 다음을 다룹니다:
ISignalOnPlayerDataSet
시그널을 통해RuntimePlayer
수신- 플레이어의
RuntimePlayer
기반한 플레이어들을 초기화 - 플레이어의
TurnData
인스턴스 재설정 그리고 SpawnSystem
을 통해 볼 스포닝을 트리거 (관련된 시스템 참고).
SpawnSystem
SpawnSystem
은 SetupSystem
이 요청하면 골프공을 직접 스폰 하는 역할만 합니다. 이를 위해 먼저 GameConfig
에셋을 조회하고 필터를 사용하여 모든 활성 SpawnPoint
를 찾습니다. SpawnSystem
은 GameConfig
에 제공된 SpawnPointType
에 따라 볼을 생성하며, 그 이후에는 SpawnPoint
가 비활성화됩니다.
SpawnPoint
SpawnPoint
는 씬 프로토타입에 사용되는 컴포넌트로, 스폰 지점 위치를 정의합니다.
여기에는 SpawnPointType
에 대한 데이터와 엔티티가 생성되었는지 여부가 저장됩니다.
NearHole
:GameConfig
에셋에서NearHole
옵션을 선택하여 볼을 스폰 할 때 사용할SpawnPoint
를 정의합니다.Regular
: 일반 게임에서 볼 스포닝용.
C#
asset ConfigAssets;
asset GameConfig;
asset UserMap;
enum SpawnPointType { Regular, NearHole }
component SpawnPoint
{
SpawnPointType Type;
Boolean IsAvailable;
entity_ref Entity;
}
synced event GameplayEnded { }
PlaySystem
PlaySystem
은 공의 물리학을 처리하고 속도를 바탕으로 회전 상태를 판단합니다. 플레이어의 스트라이크 정보는 ISignalOnSkipCommandReceived
시그널을 통해 수신됩니다.
볼
볼 모델은 Actor
와 BallFields
2개의 컴포넌트를 사용합니다. 두 컴포넌트 모두 Ball.qtn
파일에 정의되어 있습니다.
액터
Actor
컴포넌트는 플레이어가 배치된 엔티티를 제어/소유하는 플레이어에 대한 player_ref
를 갖고 있습니다. Active
부울 값은 플레이어가 이 게임에서 현재 활성 상태인지 확인할 때 사용되며 저장된 파일에서 게임을 로드할 때 사용됩니다.
C#
component Actor
{
Boolean Active;
player_ref Player;
}
BallFields
BallFields
컴포넌트는 플레이어의 볼 상태를 간직하는 데 사용됩니다.
C#
component BallFields
{
asset_ref<BallSpec> Spec;
TurnData TurnStats;
FPVector3 LastPosition;
Boolean HasCompletedCourse;
Int32 EndOfMovementTimer;
Int32 Score;
}
TurnStats
: 특정 플레이어의 턴일 때 글로벌CurrentTurn
에서 통계를 누적하기 위한TurnData
인스턴스입니다. 이 필드는TurnSystem
에 의해 유지됩니다.LastPosition
: 총을 맞기 전 마지막 위치. 러프 필드에 착지할 때 공의 위치를 재설정하는 데 사용됩니다.HasCompletedCourse
: 플레이어가 현재 코스를 완료했는지 여부를 추적합니다.PlaySystem
을 통해 확인 및 업데이트됩니다.EnOfMovementTimer
: 볼이 멈출 때까지PlaySystem
에 의해 증가하는 타이머입니다.Score
: 플레이어의 턴이 종료되었을 때ScoreSystem
에 의해 업데이트됩니다.
OnBallShot
볼이 발사되었을 때 PlaySystem
에 의해서 트리거 되는 ISignalOnBallShot
시그널입니다.
입력
공을 칠 때 선수가 보내는 PlayCommand
외에도 일반 입력 구조체를 활용해 현재 조준 방향을 공유하고 바마크 위치를 다른 플레이어들과 공유합니다. 이것은 활동 중인 플레이어가 대기하는 플레이어 쪽을 조준하는 것을 재현할 수 있게 해줍니다.
input
{
FPVector3 Direction;
FP ForceBarMarkPos;
}
이 값들은 시각적 피드백만을 위한 것이며 PlayCommand
와 동일한 정보를 보유하고 있음에도 불구하고 게임 플레이 관련 로직에 사용되지 않습니다.
힘
볼에 가해지는 힘은 PlayCommand
를 통해 입력되는 플레이어 스트라이크에 의해 정의됩니다. PlaySystem
은 ISignalOnPlayCommandReceived
를 구현하여 PlayCommand
를 수신합니다. 시스템은 명령이 유효한지 확인하기 위해 다음 3단계로 명령을 처리합니다.
GameConfig
에셋에 정의된 값에 따라 타격 데이터(방향 및 힘)를 클램핑합니다.PhysicsBody3D
에 힘을 가하여 공을 칩니다. 그리고 마지막으로ISignalOnBallShot
시그널을 트리거 합니다.
충돌
PlaySystem
은 공에 힘을 가하는 것 외에도 코스의 구멍과 바깥쪽 러프 필드가 촉발한 충돌도 평가합니다.
볼이 러프 필드 정적 콜라이더(외측 다크 그린 필드)에서 멈추면, 그 위치는 스트라이크 전의 위치로 재설정됩니다.
공이 Hole
레이어에서 정적 콜라이더를 트리거하고 속도가 GameConfig
에셋에 정의된HitHoleVelocityThreshold
미만일 때 Threshold는 OnHitHole() 메소드를 호출하고 코스를 완료한 것으로 간주합니다.
PlayerTurn
글로벌 변수 CurrentTurn.Status
가 TurnStatus.Resolving
로 설정되어 있는 경우 공이 여전히 움직이고 있다는 것을 의미합니다. 이 시간 동안 PlaySystem
은 Update()
마다 볼의 움직임이 멈췄는지 확인합니다. 나머지 임곗값은 BallSpec
에셋에 설정된 값에 따라 결정됩니다.
공의 속도가 임곗값보다 낮으면 정지 상태를 고려합니다. 이때 BallFields
컴포넌트의 EndOfMovementTimer
필드가 증가하기 시작합니다. EndOfMovementWaitingInTicks
값에 도달하면 EndOfPlay()
메소드가 호출되고 ISignalOnTurnEnded
신호가 트리거 되어 현재 플레이어의 차례가 종료됩니다.
플레이가 끝나면 공의 PhysicsBody3D
컴포넌트가 비활성화되어 원치 않는 상호작용을 방지하고 해당 플레이어의 TurnStatus
는 비활성
상태입니다.
TurnSystem
TurnSystem
은 턴 기반 기능에 의해서 사용되는 턴 관련 로직을 처리합니다.
각 플레이어는 글로벌 CurrentTurn
데이터 인스턴스에서 턴을 집계할 수 있는 고유한 TurnData
인스턴스를 가지고 있습니다. 또한 시스템에서 활성 플레이어의 TurnStatus
를 업데이트합니다.
PlaySystem
에서 공을 치면 TurnSystem
은 CurrentTurn.Status
를 TurnStatus.Resolving
로 설정하고 플레이어의 BallFields
컴포넌트 TurnStats
필드에서 SetStatus()
를 호출합니다.
활성 플레이어로부터 유효한 SkipCommand
가 수신되면 TurnSystem
이 ISignalOnTurnEnded
신호를 트리거 하기만 하면 됩니다.
ISignalOnTurnEnded
시그널일 수신되면 TurnSystem
이 TurnType
을 확인합니다.
Countdown
타입이고 다음 턴에 적합한 공이 있으면 다음 공의 턴을 활성화합니다.Play
유형일 경우 현재 플레이어의 볼에AccumulateStats()
및SetStatus()
를 호출하여 비활성 상태로 만듭니다. 그런 다음 글로벌CurrentTurn
에서Reset()
을 호출합니다.
TurnData
턴 기반 SDK는 기본 Quantum SDK 용 애드온입니다. 이 기능은 턴 관련 데이터에 대한 로직을 저장하고 조작하는 데 도움이 됩니다.
대부분의 경우 턴 데이터 구조체 인스턴스에서 발생합니다. 이 인스턴스는 데이터 자산을 통해 구성 가능한 매개 변수를 가지며 각 플레이어/엔티티 및/또는 글로벌로 게임 흐름을 관리하는 데 사용할 수 있습니다.
변수
TurnData
구조체에는 데이터가 참조하는 플레이어 또는 엔티티에 쉽게 접근할 수 있도록 편의상 Player
와 Entity
에 대한 참조가 있습니다. 또한 턴의 구성 가능한 파라미터에 대한 정보를 전달하는 TurnConfig
에셋에 대한 참조를 유지합니다.
모든 턴에는 제안 하는 현재 TurnType
이 있지만 현재 턴 중에 허용/금지되는 상호 작용의 종류를 강제하지는 않습니다. 골프 템플릿에는 다음과 같은 두 가지 사전 정의된 유형이 있습니다.
Play
: 상호작용이 허용됩니다.Countdown
: 상호 작용이 금지되어 게임 플레이가 보류됩니다.
또한 모든 턴에는 TurnStatus
에 대한 추적 기능이 있습니다. 이 추적 기능은 해당 상태가 활성 상태인 동안 데이터를 조작하는 데 사용할 로직 경로를 정의하는 데 사용됩니다. 골프 템플릿은 다음과 같은 세 가지 사전 정의된 상태를 사용합니다.
Active
: 턴 타이머를 늘리고 플레이어 명령을 수락할 수 있습니다.Inactive
: 타이머가 중지되고 플레이어 명령이 수락되지 않습니다.Resolving
: 현재 플레이어가PlayCommand
를 수신하고 게임별 로직(예: 공 물리 시뮬레이션)이 실행된 후 임시 상태를 나타냅니다.
Number
는 플레이어의 턴 수를 추적하며, AccumulateStats()
를 호출할 때 1이 증가합니다.
이렇게 하면 글로벌 CurrentTurn
이 종료될 때 활성 플레이어의 TurnData
를 업데이트할 수 있습니다. 반대로 Ticks
변수는 프레임마다 증가하며 글로벌 CurrentTurn
는 Active
로 표시됩니다. 이를 통해 지금까지 플레이한 모든 턴 동안 각 플레이어가 얼마나 많은 틱을 활성화했는지 정확하게 알 수 있습니다.
C#
enum TurnType { Play, Countdown }
enum TurnStatus { Inactive, Active, Resolving }
struct TurnData
{
player_ref Player;
entity_ref Entity;
asset_ref<TurnConfig> ConfigRef;
TurnType Type;
TurnStatus Status;
Int32 Number;
Int32 Ticks;
}
CurrentTurn
은 글로벌 TurnData
인스턴스로 매번 새로운 턴의 시작 시점에 현재 플레이어의 턴을 추적하기 위해서 재설정됩니다. 턴이 종료될 때, 데이터는 이전에 존재하는 플레이어의 데이터 인스턴스에 집계/누적됩니다.
골프 템플릿은 TurnEndReasons
열거를 통해 그중 일부를 열거합니다. 턴이 종료되었다는 신호를 보내고 다른 게임별 로직 루틴을 트리거 하기 위해 변경 및 추가할 수 있습니다.
C#
enum TurnEndReason { Time, Skip, Play, Resolved }
global
{
TurnData CurrentTurn;
}
메소드
TurnData
인스턴스가 업데이트되고 상태가 Active
일 때 현재 턴에서 타이머를 사용하면 Ticks
이 1씩 증가합니다.
Ticks
값이 TurnConfig
에셋에 정의된 대로 이 턴의 최대 허용량에 도달하면 ISignalOnTurnEnded
신호가 TurnEndReason.Time
열거가 턴 엔드 이유로 전달됩니다.
C#
public void Update(Frame f)
{
var config = f.FindAsset<TurnConfig>(ConfigRef.Id);
if (config == null || !config.UsesTimer || Status != TurnStatus.Active)
{
return;
}
Ticks++;
if (Ticks >= config.TurnDurationInTicks)
{
f.Signals.OnTurnEnded(this, TurnEndReason.Time);
}
}
TurnData
인스턴스는 다른 인스턴스로부터 AccumulateStats()
를 할 수 있습니다. 골프 템플릿의 구현은 턴의 Number
를 1씩 증가시키고, 각 경우의 Ticks
을 누적하는 것으로 해석됩니다. 일반적으로 플레이어의 TurnData
인스턴스는 글로벌 CurrentTurn
이 종료될 때 통계가 누적됩니다.
C#
public void AccumulateStats(TurnData from)
{
Ticks += from.Ticks;
Number++;
}
TurnData
인스턴스는 2개의 메소드를 제공합니다:
SetType()
SetStatus()
Frame
을 제공할 필요는 없지만 원하는 경우 이벤트를 트리거 할 수 있습니다.
C#
public void SetType(TurnType newType, Frame f = null){
if (Type == newType){
return;
}
var previousType = Type;
Type = newType;
f?.Events.TurnTypeChanged(this, previousType);
}
public void SetStatus(TurnStatus newStatus, Frame f = null){
if (Status == newStatus){
return;
}
var previousStatus = Status;
Status = newStatus;
f?.Events.TurnStatusChanged(this, previousStatus);
if (Status == TurnStatus.Active){
f?.Events.TurnActivated(this);
}
}
플레이어의 TurnData
인스턴스는 게임 시작 시 Reset()
될 수 있으며, 모든 오버로드가 Ticks
값을 재설정하는 동시에 선택한 오버로드에 따라 다른 필드를 설정할 수 있는 기능도 제공합니다.
또한 턴이 끝날 때 글로벌 현재 턴을 재설정하여 다른 플레이어의 턴을 추적할 수 있습니다.
프레임이 제공된 경우(필수 사항은 아님) 해당 프레임에서 턴 타이머 재설정 이벤트가 발생합니다.
C#
public void ResetTicks(Frame f = null) {
ResetData(Type, Status, Entity, Player, ConfigRef, f);
}
public void Reset(TurnConfig config, TurnType type, TurnStatus status, Frame f = null){
ResetData(type, status, Entity, Player, config, f);
}
public void Reset(EntityRef entity, PlayerRef owner, Frame f = null){
ResetData(Type, Status, entity, owner, ConfigRef, f);
}
public void Reset(TurnConfig config, TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, Frame f = null){
ResetData(type, status, entity, owner, config, f);
}
ScoreSystem
ScoreSystem
은 게임 종료 스코어링을 처리합니다. 플레이(TurnType.Play
)의 일부로 게임이 종료되면 - 예, 플레이어가 공을 구멍에 넣음 - , 우승 플레이어의 점수는 다음과 같이 계산됩니다: 최대 허용 스트로크 수 + 1(GameConfig
의 MaxStrokes
필드에 정의됨)에서 공을 홀에 치는 데 걸린 스트로크 수를 뺀 값입니다.
아직 코스를 완료하지 않았다면 점수는 0점입니다.
GameEnd
GameEnd
시스템은 매 턴 끝에서 경기 종료 조건이 충족되었는지 확인합니다. 골프 템플릿은 다음 두 가지 종료 조건을 구현합니다.
- 모든 플레이어가 최대 허용 스트라이크 수를 기록
- 코스가 완료(즉, 공이 홀에 들어감).
이러한 조건 중 하나에 도달하면 전역 CurrentTurn
데이터가 재설정되고 GameplayEnded
이벤트가 발생합니다. GameplayEnded
이벤트는 유니티와 소통하고 시각적 피드백을 표시하는 데 사용됩니다.
턴 기반 시스템
턴 기반 시스템은 게임에 구애받지 않으며 모든 종류의 턴 기반 게임 플레이에 사용할 수 있습니다.
CommandSystem
CommandSystem
은 플레이어가 보내는 명령의 종류를 제어하고, 만약 이것이 유효하다면 그에 상응하는 신호/로직을 트리거 하는 역할을 합니다. 골프 템플릿에 포함된 두 가지 명령은 PlayCommand
와 SkipCommand
입니다.
현재 활성 플레이어가 보낸 명령만 허용되며 활성 플레이어는 글로벌 CurrentTurn
데이터 필드에서 참조됩니다.
PlayCommand
가 수신되면 글로벌 CurrentTurn
상태가 Resolving
으로 설정되어 게임별 로직을 실행해야 하는 동안 타이머가 정지됩니다.
C#
public override void Update(Frame f)
{
var currentTurn = f.Global->CurrentTurn;
if (currentTurn.Status != TurnStatus.Active)
{
return;
}
var currentPlayer = f.Global->CurrentTurn.Player;
switch (f.GetPlayerCommand(currentPlayer))
{
case PlayCommand playCommand:
if (currentTurn.Type != TurnType.Play)
{
return;
}
f.Signals.OnPlayCommandReceived(currentPlayer, playCommand.Data);
f.Events.PlayCommandReceived(currentPlayer, playCommand.Data);
break;
case SkipCommand skipCommand:
var config = f.FindAsset<TurnConfig>(currentTurn.ConfigRef.Id);
if (!config.IsSkippable)
{
return;
}
f.Signals.OnSkipCommandReceived(currentPlayer, skipCommand.Data);
f.Events.SkipCommandReceived(currentPlayer, skipCommand.Data);
break;
}
}
}
TurnTimerSystem
TurnTimerSystem
은 글로벌 CurrentTurn
에서 Update()
를 호출하는 역할을 합니다.
C#
public unsafe class TurnTimerSystem : SystemMainThread
{
public override void Update(Frame f)
{
f.Global->CurrentTurn.Update(f);
}
}
시그널
Quantum 시그널은 내부 시스템 커뮤니케이션용입니다.
턴 종료 신호를 트리거 하여 턴이 주어진 턴 종료 사유로 종료되었음을 알릴 수 있으며 골프 샘플의 다음 플레이어에게 턴을 전달하는 것과 같이 게임별 턴 제어 로직을 트리거 하는 데 사용해야 합니다.
플레이/스킵 명령 수신 신호는 해당 유효한 명령이 수신되면 Systems)에서 플레이어 명령 유효성에 대해 자세히 설명) 발생하며, 공을 치거나 골프 샘플 턴을 종료하는 등 원하는 게임별 로직을 트리거 하는 데 사용할 수 있습니다.
C#
signal OnTurnEnded (TurnData data, TurnEndReason reason);
signal OnPlayCommandReceived (PlayerRef player, PlayCommandData data);
signal OnSkipCommandReceived (PlayerRef player, SkipCommandData data);
이벤트
Quantum 이벤트는 시뮬레이션 내부에서 일어나는 일들을 렌더링 엔진에 전달하여 원하는 시청각 피드백으로 대응할 수 있도록 하는 것을 의미합니다.
일반/싱크/추상 Quantum 이벤트는 여기를 확인하세요.
- 턴 유형/상태가 턴 데이터 세트 (메소드 더 알아보기 )을 통해 변경되면 해당 턴 데이터 인스턴스 값과 이전 타입/상태에 대한 이벤트가 발생합니다.
- 턴 타입/상태 변화가 턴 데이터 설정 메소드 (메소드 더 알아보기)로 전달되면 이벤트가 턴 데이터 인스턴스 값과 이전 타입/상태가 전달되면서 발생합니다.
- 설정 상태 방법에서 상태를 활성으로 변경하여 **턴이 활성화되거나 턴이 특정 이유로 종료되면 이벤트가 발생하여 신호를 보내 두 번째 경우 이유를 전달합니다.
- 해당 명령 System에 유효한 플레이/스킵 명령이 수신되면 이벤트를 발생시켜 신호를 전송합니다.
C#
abstract event TurnEvent { TurnData Turn; }
synced event TurnTypeChanged : TurnEvent { TurnType PreviousType; }
synced event TurnStatusChanged : TurnEvent { TurnStatus PreviousStatus; }
synced event TurnEnded : TurnEvent { TurnEndReason Reason; }
synced event TurnTimerReset : TurnEvent { }
synced event TurnActivated : TurnEvent { }
abstract event CommandEvent { player_ref Player; }
event PlayCommandReceived : CommandEvent { PlayCommandData Data; }
event SkipCommandReceived : CommandEvent { SkipCommandData Data; }
Back to top