Quantum 소개
개요
Photon Quantum은 Unity로 제작된 온라인 멀티 플레이어 게임용 고성능 결정론적 ECS (Entity Component System) 프레임 워크입니다.
액션 RPG, 스포츠 게임, 전투 게임등과 같이 지연 시간에 민감한 온라인 게임을 위해 디자인되었습니다.
한 가지 중요한 개념은 Quantum은 안정적인 기존 Photon 제품 및 인프라 기반위에 구축된 최첨단 예측/롤백 시뮬레이션 API를 구현한다는 것입니다.
이 사항을 그림으로 살펴보면 다음과 같습니다:
Lockstep이 없는 결정론
결정론적 시스템에서 게임 클라이언트는 모든 클라이언트에서 로컬로 실행되는 시뮬레이션으로 플레이어 입력만으로 교환합니다.
과거에는 게임 클라이언트가 시뮬레이션의 각 틱/프레임을 업데이트하기 전에 다른 모든 플레이어의 대기하는 lockstep 방식을 사용했습니다.
그러나 Quantum에서는 게임 클라이언트가 입력 예측을 사용하여 로컬에서 시뮬레이션을 자유롭게 진행할 수 있으며 발전된 롤백 시스템은 게임 상태를 복원하고 잘못된 판단을 다시 시뮬레이션합니다.
Quantum은 입력 대기 시간 및 시계 동기화를 관리하기 위해 게임에 무관심한 신뢰할 수 있는 서버 구성 요소 (photon 서버 플러그인)를 사용하기 때문에 클라이언트는 시뮬레이션을 롤백 및 확인하기 위해 가장 느린 클라이언트까지 기다릴 필요가 없습니다.
다음은 Quantum 게임의 기본 빌딩 블록들입니다:
- Quantum 서버 플러그인: 게임 클라이언트 간의 입력 시간 및 전송을 관리하며, 클럭 동기화 소스 역할을 합니다.
즉, 고객이 호스트한 백-엔드 시스템(매치메이킹, 플레이어 서비스 등)과 통합 할 수 있습니다. - 게임 클라이언트 시뮬레이터: Quantum 서버와 통신하고, 로컬 시뮬레이션을 실행하며, 모든 입력 예측 및 롤백을 수행합니다.
- 사용자 지정 게임 플레이 코드: Quantum ECS를 사용하여 별도의 순수 C# 시뮬레이션 (Unity에서 분리됨)으로 고객이 개발합니다.
고성능 코드를 구성하는 방법에 대한 프레임 워크를 제공하는 것 외에도 Quantum의 API는 결정론적 3D vector math, 2D 물리 엔진 , navmesh 패스파인더, 애니메이터 등과 같이 어떤 게임에서도 재사용할 수 있는 다양한 범위의 사전 구축 컴포넌트(데이터)와 시스템(로직)을 제공하고 있습니다.
이전 스타일의 코딩
Quantum의 고성능의 핵심은 unsafe C# 코드 (C-스타일 포인터, 메모리 정렬 데이터 구조 및 직접 할당/런타임시 가비지 콜렉션 없음)입니다.
포인터 기반 C#의 사용이 (성능을 위해) 사용하지만, 사용자 정의 DSL과 자동 코드 생성을 스마트하게 사용함으로써 대부분의 복잡성은 개발자가 알지 못합니다.
코드 생성
Quantum에서 모든 게임 플레이 데이터 (게임 상태)는 메모리 정렬 C# 구조체로써 단일 블록으로 구성됩니다.
개발자는 모든 데이터 구조를 정의하기 위해 성능에 초점을 두는 것 보다 게임 개념에 집중할 수 있도록 사용자 지정 DSL (도메인 특정 언어)을 사용합니다:
// components define reusable game state data groups
component Resources
{
Int32 mana;
FP health;
}
// entities are component containers (custom and pre-built ones)
entity character[32]
{
use Resources;
use Transform2D;
}
위 코드는 최대 32문자까지 게임 상태 데이터 구조를 생성하며, 각 구조에는 트랜스폼 및 리소스 컴포넌트 모두 포함됩니다.
자동 생성된 API를 통해 엔티티를 읽거나 수정하거나 생성하거나 삭제하는 포괄적인 기능으로 게임 상태를 취득하고 수정할 수 있습니다:
C#
// Auto-generated function to create a new character, returning a pointer (Frame f is the game state container passed at every init or update function)
var c = f.CreateCharacter();
var position = c->Transform2D.Position;
// if you want to update all characters...
var all = f.GetAllCharacters();
while (all.Next())
{
Character* c = all.Current;
}
// destroying an entity is also straightforward...
f.DestroyCaracter(c);
상태가 없는 시스템
Quantum의 DSL은 엔티티, 컴포넌트 및 보조 구조체 (구조체, 열거형, 유니온, 비트세트, 컬렉션 등)와 같은 개념으로 게임 상태 데이터 정의를 다루지만 이 게임 상태를 갱신 할 사용자 지정 게임 로직을 구성 할 수 있는 방법이 필요합니다.
Quantum의 클라이언트 시뮬레이션 루프에 의해 모든 틱 업데이트가 실행되는 상태를 저장하지 않는 로직인 Systems를 구현하여 사용자 정의 로직을 작성합니다.
C#
public unsafe class LogicSystem : SystemBase
{
public override void Update(Frame f)
{
// your game logic here (f is a reference for the generated game state container).
}
}
시스템 API 게임 루프 호출 순서, 시스템 상호 통신(물리 엔진 충돌 콜백과 같은 사용자 정의 및 사전 구축된 것 모두), 이벤트 및 기타 여러 가지 확장 후크.
이벤트
시뮬레이션이 Unity의 API를 직접 참조하지 않고 순수 C# 언어로 구현되었지만, 게임 플레이 코드가 렌더링 엔진과 통신 할 수 있게 하는 두 가지 중요한 기능이 있습니다: 이벤트와 에셋 연결 시스템.
이벤트는 게임 코드가 시뮬레이션을 하는 동안 중요한 일이 발생했다는 것을 렌더링 엔진에 알리는 한 가지 방법입니다.
한 가지 좋은 예는 어떤 상황으로 인하여 캐릭터가 피해를 입었을 때입니다.
이전 섹션의 상태를 기반으로한 피해로 인하여 캐릭터 엔티티의 리소스 컴포넌트로부터 체력값을 감소시킨다는 것을 상상해보십시오.
유니티 렌더링 스크립트에서 유일하게 눈에 띄는 데이터는 새로운 체력 값 그 자체 일 뿐이며 피해를 일으킨 원인을 알 수 없고 이전의 체력 값을 알 수도 없습니다.
DSL 파일내의 이벤트 정의:
C#
event Damage
{
entity_ref<Character> Character;
FP Amount;
}
게임 플레이 코드에서는 간단한 API 호출로써 이벤트를 발생시킵니다(생성됨):
C#
public void ApplyDamage(Frame f, character* c, FP amount)
{
f.Events.Damage(amount, c->EntityRef);
}
Quantum의 even 프로세서는 틱 업데이트가 완료된 후 생성된 모든 이벤트를 처리하고 서버 확인 입력, 이벤트 반복 등을 필요로 하는 이벤트를 처리합니다.
시뮬레이션 코드에서 발생한 이벤트는 Unity 스크립트에서 생성된 콜백으로부터 런타임에서 소비될 수 있습니다:
C#
public void OnDamage(DamageEvent dmg)
{
var target = QuantumGame.Frame.GetCharacter(dmg.Character);
// instantiate and show floating damage number above the target character, etc
}
에셋 링킹
Unity는 유연한 편집기와 원활한 에셋 파이프라인으로 유명합니다.
에셋 링킹 시스템을 통해 게임 디자이너 및 레벨 디자이너는 Unity Editor에서 데이터 기반 시뮬레이션 개체를 생성 및 편집하고 시뮬레이션에 투입 할 수 있습니다.
이것은 최종 밸런싱 터치를 게임 플레이에 추가하기위한 프로토 타입 모두에 필수요소 입니다.
개발자는 C# 시뮬레이션 프로젝트에서 원하는 속성을 보여주는 데이터 기반 클래스를 생성합니다:
C#
public partial class CharacterClass
{
public Int32 MaxMana;
public FP MaxHealth;
}
유니티 레벨 디자이너는 필요에 따라 이 에셋의 인스턴스를 여러 개 만들 수 있습니다. 각 인스턴스에는 유일한 GUID가 자동으로 할당됩니다.
그런 다음 프로그래머는 시뮬레이션 내부에서 이러한 에셋의 데이터를 직접 사용할 수 있습니다:
C#
var data = DB.FindAsset<CharacterClass>("character_class_id");
var mana = data.MaxMana;
상태 정의 DSL의 엔티티와 컴포넌트에게 이러한 에셋을 직접적으로 할당하는 것이 가능합니다:
entity Character[32]
{
use Resources;
use Transform2D;
fields
{
asset_ref<CharacterClass> CharacterData;
}
}
결정론적 라이브러리
Quantum에서 시뮬레이션은 동일한 입력값을 사용하는 경우 모든 클라이언트에서 동일한 결과를 계산해야합니다.
이것은 결정론적이어야한다는 것을 의미합니다. float 또는 double 변수를 사용하지 않으며, 벡터, 물리 엔진 등과 같은 Unity API의 어떤 것도 사용하지 않습니다.
게임 개발자가 이러한 스타일의 게임 플레이를 구현할 수 있도록 Quantum에는 클래스/구조체/함수와 DSL로 엔티티를 정의 할 때 직접 사용할 수 있는 유연한 확장 가능한 결정론적 라이브러리가 번들로 제공됩니다.
다음의 모듈들을 사용할 수 있습니다:
- 결정론적 수학 라이브러리: floats/doubles, FPVector2, FPVector3, FPMatrix, FPQuaternion, FPRandom, FPBounds2 및 안전한 캐스팅를 포함한 모든 추가 수학 유틸리티와 기본 유형의 파서를 대체하는 FP(고정 소수점) 유형 (Q48.16).
수학 라이브러리는 성능을 기본 목표로 구현되므로 인라인, 룩업 테이블(lookup table) 및 빠른 연산자를 가능한 한 많이 사용합니다. - 2D 물리 엔진: 정적 및 동적 객체 (원형, 상자 및 폴리건 콜라이더)를 지원하는 고성능 rigidbody 2D 물리 엔진.
다이나믹 바디는 사전 구축된 컴포넌트로써 엔티티 내부에서 직접 사용하며 충돌 및 트리거(물리 엔진 시스템에서 생성되는 신호)에 대한 콜백이 있습니다. - NavMesh + A* PathFinder: 기존 Unity navmesh의 내보내기 도구 또는 메쉬를 직접 조작하는 편집기를 모두 포함합니다. 길 찾기 외에 가시 거리 검사를 수행 할 수도 있습니다.
- 결정론적 애니메이션 컨트롤러: 메카닉 에셋의 데이터를 결정적인 형식으로 내보내고, 결정론적인 시뮬레이션에서 애니메이션 상태 머신을 실행하고, 메카닉 애니메이터를 제어하여 동기화 상태를 유지합니다.
루트 모션 등의 지원이 포함되어 있습니다.
학습해야할 것이 많으므로 이 설명서의 매뉴얼 섹션을 살펴보시기 바랍니다.
Back to top