Quantum DSL (게임 상태)
Quantum은 자체 DSL로 모든 게임 상태 데이터를 선언해 주어야 합니다(도메인 명시 언어).
이 정의들은 ".qtn" 확장자인 텍스트 파일에 작성됩니다(정의는 필요한만큼 분리될 수 있으며, Quantum 컴파일러가 머지하여 하나의 게임 상태로 생성시켜줍니다).
DSL의 목적은 개발자로부터 Quantum의 예측/롤백 모델에 의해 부과된 복잡한 메모리 요건을 추상화하는 것입니다.
DSL-정의된 게임 상태는 커스텀 Quantum 컴파일러가 해석하여 일반 C#으로 변환해줍니다(메모리 제한을 두고)
이 변환에는 작업을 단순화하고 개발자를 고성능 포인터 기반 접근 방식에서 보호하기 위해 언매니지드 C# 포인터의 광범위한 사용과 코드 생성 기능이 포함됩니다.
엔티티
엔티티는 임시 게임 개념(동적으로 생성/파괴되어야 하는 인스턴스)으로 사용해야 하며, 일반적으로 컴포넌트의 집합을 포함하지만 속성으로써 선언된 타입(컴포넌트가 아닌 DSL 상에 있는 특수 타입)들도 포함할 수 있습니다.
엔티티에 대해 최소한으로 정의해 제공해야 할것은 이름과 최대 인스턴스 수(quantum은 모든 게임 상태 데이터를 사전 할당합니다)입니다.
C#
entity Character[19]
{
}
이것은 Character 라고하는 엔티티에 대한 C# 구조체를 생성하며 게임 상태내에 19개의 "인스턴스"를 사전할당합니다. "field" 스코프로 엔티티에 직접 속성을 추가하는 것이 가능합니다.
C#
entity MyEntity[19]
{
fields
{
FP Health;
FP Mana;
}
}
생성된 "MyEntity" 구조체 인스턴스 포인터는 다음과 같이 필드에 직접 접근할 수 있습니다(생성된 상태 API에 대한 상세내용은 다음 장인 시스템을 참고하십시오).
C#
var mana = myEntity->Mana;
myEntity->Health = 10;
컴포넌트
컴포넌트는 특수한 재사용 데이터 컨테이너/그룹으로 엔티티에 붙일 수 있습니다. 다음은 컴포넌트의 기본 정의입니다:
C#
component Weapon
{
FP Cooldown;
FP Power;
}
"use" 키워드는 엔티티에 컴포넌트를 붙입니다.
C#
entity MyEntity[19]
{
use Weapon;
fields
{
FP Health;
FP Mana;
}
}
생성된 엔티티 구조체에서, 컴포턴트 이름/유형은 속성 이름으로도 사용됩니다:
C#
myEntity->Weapon.Power = 4;
Quantum에는 몇 개의 사전 구축된 컴포넌트들이 있습니다:
- Transform2D - 위치(FPVector2)와 라디언으로 회전(FP)이 포함되어 있습니다;
- DynamicBody - 현재 속도, 매스, 쉐이프, 머터리얼 데이터와 기타 물리 엔진 자료(엔티티의 물리를 사용할 때 필요한 위치와 회전은 Transform2D로 부터 가져옵니다);
- Prefab - 런타임의 Unity Prefab에 엔티티를 연결하는 데 도움이 되는 단순화된 구성요소 (Unity에서 엔티티 렌더링용);
- Animator - Quantum에서 결정론적 애니메이션 시스템의 시뮬레이션 부분 (Unity의 메카님과 통합됩니다);
다음은 이 섹션에서 주제로 다루어진 조합된 DSL입니다:
C#
component Weapon
{
FP Cooldown;
FP Power;
}
entity MyEntity[19]
{
use Weapon;
use Transform2D;
use DynamicBody;
fields
{
FP Health;
FP Mana;
}
}
구조체
Quantum DSL은 또한 메모리 정렬을 관리할 필요 없이 일반 구조체를 정의할 수 있으며, 정렬되지 않은 데이터의 정의로부터 보호합니다.
C#
struct Resources
{
FP Health;
FP Mana;
}
이렇게 하면 "Resources" 구조체를 DSL의 다른 모든 부분에서 유형으로 사용할 수 있습니다. 예를들어, 이전에 엔티티 정의에 사용된 루즈 필드를 교체하는 데 사용할 수 있습니다.
C#
entity MyEntity[19]
{
use Weapon;
use Transform2D;
use DynamicBody;
fields
{
Resources Resources;
}
}
컴포넌트 vs. 구조체
중요한 것은 컴포넌트가 일반 구조체 대신에 왜 그리고 언제 사용하는가입니다(컴포넌트는 결국 구조체입니다).
컴포넌트는 특수 유형으로 변환되는 생성된 메타-데이터를 포함합니다.
- 엔티티 메타 데이터에는 런타임의 정의에서 구성요소의 존재 여부를 신속하게 필터링하는 데이터가 포함되어 있습니다.
- 컴포넌트 유형의 활성 인스턴스에 대해 게임 상태를 이동할 수 있습니다. (이것은 고성능 엔티티 필터의 가장 단순한 것이며 다음장 Systems API에서 다룹니다);
다음 세션에서 분명하게 드러나겠지만, 컴포넌트는 다른 구조체처럼 다른 구조체 없이 포인터(이 장의 뒷부분에 있는 시그널에서) 또는 값 유형으로 전달될 수 있습니다(다른 기능이나 이벤트에 대해서는 나중에 참조). 그러나 위에서 설명한 기능은 컴포넌트 전용입니다.
Unions, Enums와 Bitsets
C와 유사한 유니온과 열거형도 만들어질 수 있습니다. 아래 예제는 열거형을 사용하여 일부 상호 배타적인 데이터 유형/값을 조합에 겹쳐서 저장할 데이터를 지정하는 방법을 보여줍니다.
C#
enum DataType
{
typeA, typeB
}
struct DataA
{
FPVector2 Something;
FP Anything;
}
struct DataB
{
FPVector3 SomethingElse;
Int32 AnythingElse;
}
union Data
{
DataA A;
DataA B;
}
struct DataContainer
{
DataType Type;
Data Data;
}
Bitsets는 원하는 목적을 위해 고정된 크기의 메모리 블록을 선언하는 데 사용될 수 있습니다(예를들어 픽셀 퍼펙트 메카닉용 전쟁 안개, 그리드 같은 구조등).
C#
struct FOWData
{
bitset[256] Map;
}
입력
Quantum에서는 클라이언트간에 교환되는 런타임 입력도 DSL에서 선언됩니다. 이 예에서는 간단한 이동 벡터를 게임의 입력으로 정의합니다.
C#
input
{
FPVector2 Movement;
}
버튼과 같은 입력에 대해서, Quantum은 안전하게 상태 변화를 계산할 수 있으므로(다음장인 systems에서 다루게 될 것입니다) 특수한 타입 "button"이 사용되어야 합니다
C#
input
{
FPVector2 Movement;
button Fire;
}
사전 정의된 입력 구조체가 필요하며, 동일한 게임 플레이 세션에서 사용할 상호 배타적인 입력 유형과 데이터를 정의하기 위해 결합을 사용할 수 있습니다. 이 예에서는 동일한 입력 정의에 일반적인 이동/발사 또는 "물건 구입" 명령 데이터가 포함될 수 있는 방법을 보여 줍니다.
C#
enum InputType
{
BuyCommand, MovementAndFire
}
struct BuyData
{
Int32 ItemID;
Int32 UnitID;
Boolean ApplyImmediately;
}
struct MovementAndFireData
{
FPVector2 Movement;
button Fire;
}
union InputData
{
BuyData BuyCommand;
MovementAndFireData MovementAndFire;
}
input
{
InputType Type;
InputData Data;
}
입력이 어떻게 (Systems)에 의해서 처리되고 Systems로 부터 Quantum으로 어떻게 주입되는지는 다음 장을 참고하십시오(Bootstrap Unity 프로젝트)
이벤트
이벤트는 시뮬레이션에서 발생하는 것을 렌더링 엔진으로 전달하는 세분화된 솔루션입니다(게임 상태의 일부를 수정/업데이트하는 곳에서는 사용해서는 안 됨). "event" 키워드를 사용하여 이름 및 데이터를 정의합니다:
C#
event TriggerSound
{
FPVector2 Position;
FP Volume;
}
상속은 이벤트에 사용할 수 있습니다(선택적으로 일부 이벤트는 abstract로 표시될 수 있으므로, 직접 트리거되는 것을 차단하여 구체적인 하위 클래스만 허용합니다).
C#
abstract event TriggerSound
{
FPVector2 Position;
Int32 SoundID; // this would be better handled with unity-side asset linking extensions, but this is out of the scope of this chapter
}
event TriggerShot : TriggerSound
{
FP Power;
}
특정 이벤트가 서버에서 입력 데이터가 확인될 때만 (Unity로) 전송되도록 하려면(유도 허위 양성 롤백 방지) "synced" 키워드를 사용하십시오.
C#
synced event TriggerDeathSound : TriggerSound
{
}
원치 않는 중복 디스패치를 방지하기 위해 Quantum은 (이벤트 데이터를 소스로 사용하여) 모든 이벤트 유형에 대한 해시코드 함수를 자동으로 생성합니다. 특정 상황에서 개발자는 (이벤트 인스턴스를 고유하게 만드는) 주요-후보 데이터가 무엇인지 엄격하게 제어 할 수 있습니다. 한 가지 예를 들면 위치에서 약간의 롤백으로 인한 변경으로 인해 동일한 이벤트의 두 인스턴스가 실제로 다른 두 개의 이벤트로 잘못 해석되는 경우입니다. 이를 방지하기 위해 해시 함수는 "nothashed" 키워드를 사용하여 일부 이벤트 데이터를 무시하도록 할 수 있습니다:
C#
abstract event TriggerSound
{
nothashed FPVector2 Position;
Int32 SoundID;
}
다음 장을 참조하여 Quantum (Systems)에서 이벤트가 트리거되고 Unity 콜백(Bootstrap Unity 프로젝트)에 어떻게 전송되는지 알아보십시오.
시그널
시그널은 분리된 시스템간 통신 API(게시자/가입자 API 또는 관찰자 패턴과 같은 비트)로 사용되는 함수 서명입니다. 다음과 같이 간단하게 시그널을 정의합니다.
C#
signal OnDamage(FP Damage);
이것은 다음의 인터페이스를 생성할 것 입니다(모든 시스템에서 구현될 수 있습니다):
C#
public interface ISignalOnDamage
{
public void OnDamage(Frame f, FP Damage);
}
시그널은 Quantum DSL에서 포인터의 직접 선언을 허용하는 유일한 개념이기 때문에 참조로 데이터를 전달하면 구체적인 구현에서 원본 데이터를 직접 수정할 수 있습니다.
C#
signal OnDamageBefore(FP* Damage);
특수 유형
Quantum은 복잡한 개념(조합 포인터 오프셋, 플레이어 인덱스 등)을 추상화하거나 언매니지드 코드로 일반적인 실수로부터 보호하는 데 사용되는 몇 가지 특수 유형을 가집니다. 다음 특수 유형을 다른 데이터 유형(컴포넌트, 엔터티 필드, 이벤트 등)내에서 사용할 수 있습니다.
- player_ref - 런타임 플레이어 인덱스를 나타냅니다(Int32간에 캐스트됩니다). 엔티티에 붙혀진 경우 플레이어가 제어하는 엔티티 인스턴스를 표시할 수 있습니다(Quantum의 플레이어 인덱스 기반 입력과 결합);
- entity_ref
- (일반) Quantum 내의 각 프레임/틱 데이터가 분리된 메모리 영역/블록(Quantum은 빠른 롤백을 위해 이러한 버퍼를 저장합니다), 포인터들은 프레임간(Unity 상에 게임 상태로도 되지 않음)에 캐시될 수 없습니다. 엔티티 ref는 엔티티유형, 인덱스와 버전을 추상화합니다(또한 개발자가 이전 ref를 사용하여 손상되거나 재사용된 엔티티 슬롯에 실수로 사용되지 않는 데이터에 액세스하지 못하도록 보호); - asset_ref
- Quantum 에셋 데이터베이스에서 데이터 에셋 인스턴스에 대한 롤백 가능 참조 (데이터 에셋 장을 참고하십시오) - array
[size] - 데이터 콜렉션을 나타내는 고정된 크기의 "배열". 일반 C# 배열은 힙 할당 개체 참조(속성등을 가진)일 수 있으며, 이는 Quantum의 메모리 요구 사항을 위반하므로 특수 배열 유형은 게임 내에서 되돌릴 수 있는 데이터 수집을 위해 포인터 기반으로 간단한 API를 생성합니다;
에셋에 대한 참고
에셋은 개발자가 인덱스된 데이터베이스 내에서 변하지 않는 인스턴스로 끝나는 데이터 기반 컨테이너(일반 클래스, 상속, 다형성 방법 등)를 정의할 수 있는 Quantum의 특별한 기능입니다. "asset" 키워드는 (기존) 클래스를 게임 상태 내에서 참조가 할당될 수 있는 데이터 에셋으로 할당하는 데 사용됩니다(기능 및 제한에 대한 자세한 내용은 데이터 자산 장을 참고하십시오):
C#
asset CharacterData; // the CharacterData class is partially defined in a normal C# file by the developer
다음 구조체는 위의 타입의 유효한 예제를 나타냅니다(이전에 정의된 유형도 일부 참조되고 있습니다):
C#
struct SpecialData
{
player_ref Player;
entity_ref<Character> Character;
entity_ref AnyEntity;
asset_ref<CharacterData> CharacterData;
array<FP>[10] TenNumbers;
array<entity_ref<Character>>[4] FourCharacterRefs;
}
사용가능한 타입과 Imports
Quantum의 DSL 파서는 사전 임포트된 크로스-플랫폼 결정론적 유형이 있으며 게임 상태 정의를 하는데 사용할 수 있습니다:
- Boolean
- Char
- Byte
- SByte
- UInt16
- Int16
- UInt32
- Int32
- UInt64
- Int64
- FP
- FPVector2
- FPVector3
- FPMatrix
- FPQuaternion
- PlayerRef - DSL 내에서의 player_ref
- EntityRef - DSL 내에서의entity_ref
- LayerMask
- NullableFP - DSL 내에서 FP 사용
- NullableFPVector2 - DSL 내에서 FPVector2 사용?
- NullableFPVector3 - DSL 내에서 FPVector3 사용?
컴파일러 옵션
다음의 컴파일러 옵션은 현재 Quantum의 DSL 파일 내부에서 사용할 수 있는 것 입니다(향후 더 많은 옵션이 추가될 예정입니다):
C#
// numeric constants
#define MY_NUMBER 10
// pre defining max number of players (default is 8)
#pragma max_players 16
Back to top