This document is about: QUANTUM 2
SWITCH TO

Systems

골프 템플릿 내의 시스템은 2개의 카테고리로 구분됩니다:

  • 게임 플레이를 담당하는 시스템
  • 플레이어 턴을 전용을 담당하고 있는 시스템.

Gameplay

Gameplay 시스템은 골프 템플릿에 대한 모든 게임 로직을 처리합니다.

최대 플레이어 수

최대 플레이어 수는 2이며 DSL에서 #pragma max_players 2를 사용하여 고정됩니다. 또한 이 값은 배열 초기화에 재사용하기 위해 상수 #define MAX_PLAYERS 2로 정의됩니다.

SetupSystem

SetupSystems은 초기 게임 설정을 처리하며 다음을 다룹니다:

  • ISignalOnPlayerDataSet 시그널을 통해 RuntimePlayer 수신
  • 플레이어의 RuntimePlayer 기반한 플레이어들을 초기화
  • 플레이어의 TurnData 인스턴스 재설정 그리고
  • SpawnSystem을 통해 볼 스포닝을 트리거 (관련된 시스템 참고).

SpawnSystem

SpawnSystemSetupSystem이 요청하면 골프공을 직접 스폰 하는 역할만 합니다. 이를 위해 먼저 GameConfig 에셋을 조회하고 필터를 사용하여 모든 활성 SpawnPoint를 찾습니다. SpawnSystemGameConfig에 제공된 SpawnPointType 에 따라 볼을 생성하며, 그 이후에는 SpawnPoint가 비활성화됩니다.

SpawnPoint

SpawnPoint는 씬 프로토타입에 사용되는 컴포넌트로, 스폰 지점 위치를 정의합니다.

spawnpoint prototype
spawnpoint prototype

여기에는 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 시그널을 통해 수신됩니다.

볼 모델은 ActorBallFields 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 외에도 일반 입력 구조체를 활용해 현재 조준 방향을 공유하고 바마크 위치를 다른 플레이어들과 공유합니다. 이것은 활동 중인 플레이어가 대기하는 플레이어 쪽을 조준하는 것을 재현할 수 있게 해줍니다.

Unknown

input 
{
    FPVector3 Direction;
    FP ForceBarMarkPos;
}

이 값들은 시각적 피드백만을 위한 것이며 PlayCommand와 동일한 정보를 보유하고 있음에도 불구하고 게임 플레이 관련 로직에 사용되지 않습니다.

볼에 가해지는 힘은 PlayCommand를 통해 입력되는 플레이어 스트라이크에 의해 정의됩니다. PlaySystemISignalOnPlayCommandReceived를 구현하여 PlayCommand를 수신합니다. 시스템은 명령이 유효한지 확인하기 위해 다음 3단계로 명령을 처리합니다.

  1. GameConfig 에셋에 정의된 값에 따라 타격 데이터(방향 및 힘)를 클램핑합니다.
  2. PhysicsBody3D에 힘을 가하여 공을 칩니다. 그리고 마지막으로
  3. ISignalOnBallShot 시그널을 트리거 합니다.

충돌

PlaySystem은 공에 힘을 가하는 것 외에도 코스의 구멍과 바깥쪽 러프 필드가 촉발한 충돌도 평가합니다.

볼이 러프 필드 정적 콜라이더(외측 다크 그린 필드)에서 멈추면, 그 위치는 스트라이크 전의 위치로 재설정됩니다.

golf header

공이 Hole 레이어에서 정적 콜라이더를 트리거하고 속도가 GameConfig 에셋에 정의된HitHoleVelocityThreshold 미만일 때 Threshold는 OnHitHole() 메소드를 호출하고 코스를 완료한 것으로 간주합니다.

PlayerTurn

글로벌 변수 CurrentTurn.StatusTurnStatus.Resolving로 설정되어 있는 경우 공이 여전히 움직이고 있다는 것을 의미합니다. 이 시간 동안 PlaySystemUpdate()마다 볼의 움직임이 멈췄는지 확인합니다. 나머지 임곗값은 BallSpec 에셋에 설정된 값에 따라 결정됩니다.

공의 속도가 임곗값보다 낮으면 정지 상태를 고려합니다. 이때 BallFields 컴포넌트의 EndOfMovementTimer 필드가 증가하기 시작합니다. EndOfMovementWaitingInTicks 값에 도달하면 EndOfPlay() 메소드가 호출되고 ISignalOnTurnEnded 신호가 트리거 되어 현재 플레이어의 차례가 종료됩니다.

플레이가 끝나면 공의 PhysicsBody3D 컴포넌트가 비활성화되어 원치 않는 상호작용을 방지하고 해당 플레이어의 TurnStatus비활성 상태입니다.

TurnSystem

TurnSystem은 턴 기반 기능에 의해서 사용되는 턴 관련 로직을 처리합니다.

각 플레이어는 글로벌 CurrentTurn 데이터 인스턴스에서 턴을 집계할 수 있는 고유한 TurnData 인스턴스를 가지고 있습니다. 또한 시스템에서 활성 플레이어의 TurnStatus 를 업데이트합니다.

PlaySystem에서 공을 치면 TurnSystemCurrentTurn.StatusTurnStatus.Resolving로 설정하고 플레이어의 BallFields 컴포넌트 TurnStats필드에서 SetStatus()를 호출합니다.

활성 플레이어로부터 유효한 SkipCommand가 수신되면 TurnSystemISignalOnTurnEnded 신호를 트리거 하기만 하면 됩니다.

ISignalOnTurnEnded 시그널일 수신되면 TurnSystemTurnType을 확인합니다.

  • Countdown 타입이고 다음 턴에 적합한 공이 있으면 다음 공의 턴을 활성화합니다.
  • Play 유형일 경우 현재 플레이어의 볼에 AccumulateStats()SetStatus()를 호출하여 비활성 상태로 만듭니다. 그런 다음 글로벌 CurrentTurn에서 Reset()을 호출합니다.

TurnData

턴 기반 SDK는 기본 Quantum SDK 용 애드온입니다. 이 기능은 턴 관련 데이터에 대한 로직을 저장하고 조작하는 데 도움이 됩니다.

대부분의 경우 턴 데이터 구조체 인스턴스에서 발생합니다. 이 인스턴스는 데이터 자산을 통해 구성 가능한 매개 변수를 가지며 각 플레이어/엔티티 및/또는 글로벌로 게임 흐름을 관리하는 데 사용할 수 있습니다.

변수

TurnData 구조체에는 데이터가 참조하는 플레이어 또는 엔티티에 쉽게 접근할 수 있도록 편의상 PlayerEntity에 대한 참조가 있습니다. 또한 턴의 구성 가능한 파라미터에 대한 정보를 전달하는 TurnConfig 에셋에 대한 참조를 유지합니다.

모든 턴에는 제안 하는 현재 TurnType이 있지만 현재 턴 중에 허용/금지되는 상호 작용의 종류를 강제하지는 않습니다. 골프 템플릿에는 다음과 같은 두 가지 사전 정의된 유형이 있습니다.

  • Play: 상호작용이 허용됩니다.
  • Countdown: 상호 작용이 금지되어 게임 플레이가 보류됩니다.

또한 모든 턴에는 TurnStatus에 대한 추적 기능이 있습니다. 이 추적 기능은 해당 상태가 활성 상태인 동안 데이터를 조작하는 데 사용할 로직 경로를 정의하는 데 사용됩니다. 골프 템플릿은 다음과 같은 세 가지 사전 정의된 상태를 사용합니다.

  • Active: 턴 타이머를 늘리고 플레이어 명령을 수락할 수 있습니다.
  • Inactive: 타이머가 중지되고 플레이어 명령이 수락되지 않습니다.
  • Resolving: 현재 플레이어가 PlayCommand를 수신하고 게임별 로직(예: 공 물리 시뮬레이션)이 실행된 후 임시 상태를 나타냅니다.

Number는 플레이어의 턴 수를 추적하며, AccumulateStats()를 호출할 때 1이 증가합니다.

이렇게 하면 글로벌 CurrentTurn이 종료될 때 활성 플레이어의 TurnData를 업데이트할 수 있습니다. 반대로 Ticks 변수는 프레임마다 증가하며 글로벌 CurrentTurnActive로 표시됩니다. 이를 통해 지금까지 플레이한 모든 턴 동안 각 플레이어가 얼마나 많은 틱을 활성화했는지 정확하게 알 수 있습니다.

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(GameConfigMaxStrokes 필드에 정의됨)에서 공을 홀에 치는 데 걸린 스트로크 수를 뺀 값입니다.

아직 코스를 완료하지 않았다면 점수는 0점입니다.

GameEnd

GameEnd 시스템은 매 턴 끝에서 경기 종료 조건이 충족되었는지 확인합니다. 골프 템플릿은 다음 두 가지 종료 조건을 구현합니다.

  • 모든 플레이어가 최대 허용 스트라이크 수를 기록
  • 코스가 완료(즉, 공이 홀에 들어감).

이러한 조건 중 하나에 도달하면 전역 CurrentTurn 데이터가 재설정되고 GameplayEnded 이벤트가 발생합니다. GameplayEnded 이벤트는 유니티와 소통하고 시각적 피드백을 표시하는 데 사용됩니다.

턴 기반 시스템

턴 기반 시스템은 게임에 구애받지 않으며 모든 종류의 턴 기반 게임 플레이에 사용할 수 있습니다.

CommandSystem

CommandSystem은 플레이어가 보내는 명령의 종류를 제어하고, 만약 이것이 유효하다면 그에 상응하는 신호/로직을 트리거 하는 역할을 합니다. 골프 템플릿에 포함된 두 가지 명령은 PlayCommandSkipCommand입니다. 현재 활성 플레이어가 보낸 명령만 허용되며 활성 플레이어는 글로벌 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