This document is about: QUANTUM 1
SWITCH TO

에셋 연결하기

데이터 에셋 클래스

Quantum 에셋은 런타임 중에 불변의 데이터 컨테이너로 작동하는 일반적인 C# 클래스입니다.
이러한 에셋을 Quantum에서 어떻게 설계, 구현 및 사용해야하는지를 몇가지의 규칙으로 정의합니다.

다음은 간단한 결정성 속성을 가진 에셋 클래스(문자 스펙 용)의 최소한의 정의입니다:

C#

namespace Quantum 
{
  partial class CharacterSpec 
  {
    public FP Speed;
  public FP MaxHealth;
  }
}

에셋 클래스 정의는 partial 이어야 하며 Quantum 네임스페이스에 포함되어야 합니다.

에셋 클래스의 인스턴스를 만들고 데이터베이스에 로드(Unity에서 편집)하는 방법은 이 장의 뒷부분에서 다룰 예정입니다.

에셋 연결과 사용하기

에셋 클래스로 Quantum에게 알려주기 위한 것 입니다(기본 AssetObject 클래스를 상속 받아 인스턴스를 포함하도록 데이터베이스를 준비함으로써 내부 메타 데이터 추가):

C#

// this goes into a DSL file
asset CharacterSpec;

에셋 인스턴스들은 참조로써 전달되어야하는 변경불가능한 객체들입니다.
일반 C# 객체 참조는 메모리 정렬 ECS 구조체에 포함될 수 없기 때문에 asset_ref 특수 유형을 DSL 내부에서 게임 상태(엔티티, 컴포넌트 또는 기타 임시 데이터 구조체에서) 내부에 속성을 선언하는 데 사용할 수 있습니다:

C#

entity Character[8]
{
  use Transform2D;
  use DynamicBody;
  fields
  {
    // reference to an immutable instance of CharacterSpec (from the Quantum asset database)
    asset_ref<CharacterSpec> Spec;
    FP Health;
  }
}

캐릭터 엔티티 생성시에 에셋 참조를 지정하기위한 하나의 옵션은 에셋 데이터베이스에서 인스턴스를 직접 얻어 속성으로 설정하는 것입니다.

C#

var c = f.CreateCharacter();
c->CharacterSpec = DB.FindAsset<CharacterSpec>("mage");

에셋의 기본 사용은 런타임에 데이터를 읽고 이를 시스템 내부의 모든 계산에 적용하는 것입니다.
다음 예제는 할당된 CharacterSpecSpeed 값을 사용하여 해당 캐릭터의 속도(물리 엔진)를 계산합니다:

C#

// consider c a Character* (from an iterator, for example)
c->DynamicBody.Velocity = FPVector2.Right * c->CharacterSpec.Speed;

결정론에 대한 참고

위의 코드는 런타임 중에 원하는 캐릭터의 속도를 계산하기 위해 Speed 속성을 읽지만 값(속도)은 변경되지 않습니다.

Update 내에서 런타임에 게임 상태 에셋 참조를 전환하는 것은 완전히 안전하고 유효합니다 (asset_ref는 롤백이 가능한 유형이므로 게임 상태의 일부가 될 수 있습니다).
그러나 데이터 자산의 속성 values을 변경하는 것은 결정적이지 않습니다 (자산의 내부 데이터가 게임 상태의 일부로 간주되지 않으므로 절대로 롤백되지 않습니다).

다음 코드는 런타임 동안 안전(전환 참조)및 안전하지 않은(내부 데이터 변경) 예제를 보여줍니다.

C#

// c is a Character*

// this is VALID and SAFE, as the CharacterSpec asset ref is part of the game state
c->CharacterSpec = DB.FindAsset<CharacterSpec>("anotherCharacterSpec");

// this is NOR valid NEITHER deterministic, as the internal data from an asset is NOT part of the transient game state:
// DON'T do this:
c->CharacterSpec.Speed = 10;

에셋 상속

데이터 자산에서 상속을 사용할 수 있으므로 개발자는 훨씬 더 융통성있게 개발할 수 있습니다 (특히 다형성 메소드와 함께 사용하는 경우).

상속을 위한 기본 단계는 추상 기본 에셋 클래스를 만드는 것입니다 (CharacterSpec 예제를 계속 사용할 것 입니다).

C#

namespace Quantum 
{
  partial abstract class CharacterSpec 
  {
    public FP Speed;
  public FP MaxHealth;
  }
}

CharacterSpec의 구체적인 하위 클래스는 자체적으로 사용자 정의 데이터 속성을 추가 할 수 있으며 Serializable 유형으로 표기되어야 합니다.

C#

namespace Quantum 
{
  [Serializable]
  public partial class MageSpec : CharacterSpec 
  {
    public FP HealthRegenerationFactor;
  }

  [Serializable]
  public partial class WarriorSpec : CharacterSpec 
  {
    public FP Armour;
  }
}

중요한 사항: CharacterSpec 만을 DSL의 자산으로 표시해야하며 asset_refs도 기본 클래스를 가리킬 것입니다.(상속의 유연성을 완전하게 사용하는 것을 보려면 다형성에 대한 다음 섹션 참조하십시오).

데이터-기반 다형성

구체적인 CharacterSpec 클래스를 직접 평가 (if 문 또는 switch 문에서)하는 게임 플레이 로직을 사용하면 매우 나쁜 디자인이 되므로 다형성 메소드와 결합할 때 에셋 상속이 더 적합합니다.

데이터 에셋에 논리를 추가한다는 것은 quantum.state 프로젝트에 논리를 구현한다는 것을 의미하며 이 논리는 여전히 다음 제약 사항들을 고려해야합니다.

  • 일시적인 게임 상태 데이터에서 작동합니다: 즉, 데이터 에셋의 논리 메소드가 일시적인 데이터를 매개변수(엔티티 포인터 또는 Frame 객체 자체)로 받아야 함을 의미합니다.
  • 저작물 자체에 대한 데이터는 읽거나 수정하지 마십시오. 에셋은 여전히 immutable 읽기 전용 인스턴스로 처리되어야합니다.

다음 예제는 기본 클래스에 가상 메소드를 추가하고 하위 클래스 중 하나에 사용자 정의 구현을 추가한 것입니다. (이 문서의 맨 위에 Character 엔티티에 대해 정의된 Health 필드 사용).

C#

namespace Quantum 
{
  partial unsafe abstract class CharacterSpec 
  {
    public FP Speed;
  public FP MaxHealth;
  public virtual void Update(Frame f, Character* c)
  {
    if (c->Health < 0)
    f.DestroyCharacter(c);
  }
  }

  [Serializable]
  public partial unsafe class MageSpec : CharacterSpec 
  {
    public FP HealthRegenerationFactor;
  // reads data from own instance and uses it to update transient health of Character pointer passed as param
  public virtual void Update(Frame f, Character* c)
  {
      c->Health += HealthRegenerationFactor * f.DeltaTime;
    base.Update(f, c);
  }
  }
}

Character에 할당 된 구체적인 에셋에 이 유연한 메소드 구현을 독립적으로 사용하기위해, 이것은 모든 시스템에서 실행할 수 있습니다:

C#

// All currently active Character entities
var all = f.GetAllCharacters();

// looping through the iterator
while (all.Next())
{
  // retrieving the Character pointer 
  var c = all.Current;
  // Updating Health using data-driven polymorphism (behavior depends on the data asset type and instance assigned to character
  c->CharacterSpec.Update(c, f);
}

유니티에서 에셋 편집하기

자산 데이터베이스 (Quantum 시뮬레이션 시스템 내부의 DB 클래스)는 에셋 인스턴스로 초기화해야합니다.
기본 메커니즘은 Scriptable Objects의 생성된 미러 계층 구조 형태로 부트스트랩 Unity 프로젝트에 포함되어 있습니다.

새로운 자산 클래스가 생성 될 때마다 Unity에서 Quantum/Generate Asset Linking Scripts를 클릭하면 Unity 미러 에셋 컨테이너가 생성됩니다.

사용 가능한 에셋 유형 목록은 프로젝트 탭의 컨텍스트 메뉴에서 사용할 수 있습니다 (Assets/Resources/DB 폴더를 마우스 오른쪽 단추로 클릭하고 컨텍스트 메뉴를 Create/Quantum/Assets/... 으로 이동하십시오).

생성된 ScriptableObjects 에셋은 오브젝트 인스펙터에서 직접 편집 할 수 있습니다.

Editing a data asset
유니티에서 데이터 에셋의 속성 편집.

고유한 GUID는 수동으로 삽입하거나 Quantum/Refresh Database를 클릭하여 생성 할 수 있습니다.

Resources/DB 폴더 (Unity 부트 스트랩 프로젝트에 있음)내에 생성된 ScriptableObject를 가진 에셋 데이터 클래스의 모든 인스턴스는 기본적으로 데이터베이스에 자동으로 로드됩니다 (기타 고급 옵션은 사용자 정의 에셋 로더 섹션를 참조하십시오)

유니티에서 에셋을 드래그하고 드롭하기

시뮬레이션 시스템 내부에서 에셋 인스턴스를 추가하고 DB 클래스에서 검색까지는 할 수 있습니다.
훨씬 더 흥미로운 접근법은 에셋 인스턴스가 서로 데이터베이스 참조를 가리킬 수 있도록 하는 것입니다 (유니티에서 이러한 참조를 드래그 앤 드롭 할 수 있습니다).

하나의 공통적인 사용은 특정 플레이어가 선택한 CharacterSpec 자산에 대한 링크를 포함하도록 RuntimePlayer 사전 빌드된 클래스를 사용하는 것입니다. (기술적으로는 절차적으로 할당 할 수 있는 GUID 문자열로 끝나지만 이 예제에서는 유니티 에디터에서 에셋 인스턴스를 슬롯으로 드래그 앤 드롭하는 빠른 방법을 찾고있을 것입니다 - 특별히 프로토 타이핑중에는 공통점이 있습니다).

일시적인 게임 상태가 아닌 것에 대해서는 asset_ref를 사용하는 대신에 생성된 필요한 기능을 제공하는(타이프 안정성 포함) CharacterSpecLink 클래스를 사용하는 것이 가장 좋습니다.

C#

// this is added to the RuntimPlayer.User.cs file
namespace Quantum 
{
  partial class RuntimePlayer 
  {
  public CharacterSpecLink CharacterSpec;
  }
}

이것은 유니티 인스펙터 (Bootstrap 게임 플레이 장면에서 QuantumDebugRunner를 들여다보고 있습니다 - 트레이닝 비디오 및 주제에 관한 장을 참조하십시오)에서 이와 같이 나타납니다:

Drag & Drop Asset
에셋 링크 속성들은 Quantum scriptable 객체용 타입-세이프로써 표시됩니다.

이 접근법으로, 모든 하드 코딩된 DB lookup을 시뮬레이션 시스템 내부에서 제거할 수 있습니다:

C#

public override void OnInit(Frame f)
{
  for (Int32 i = 0; i < f.RuntimeConfig.Players.Length; ++i)
  {
    var c = f.CreateCharacter();
    c->CharacterSpec = f.RuntimeConfig.Players[i].CharacterSpec; // notice CharacterSpecLink casts to CharacterSpec asset ref
  }
}

유니티 데이터와 같이 에셋 확장하기

사용자정의 에셋 로더

기본 Quantum 에셋 로더는 Resources/DB 폴더 내부에서 Unity의 ScriptableObject 하위 클래스 AssetBase 인스턴스를 찾고 에셋 DB 클래스(시뮬레이션 시스템 API에서 사용 가능)로 에셋을 로드하고 유니티 미러 에셋을 UnityDB 클래스 (이전 섹션에서 언급 한 유니티 데이터 익스텐션에 사용됨)에 추가합니다.

이 디폴트 로더에 대한 기본 구현은 Assets/Quantum/UnityDB.cs 에 있습니다(LoadAll 함수를 찾아보십시오).

구현되는 게임에 따라 흥미로운 두 가지 옵션이 있습니다.

  • 외부 CMS 시스템으로부터 에셋 로드하기
  • 절차적으로 자산 생성하기

CMS로 부터 로드하기

데이터 자산을 제공하는 외부 CMS에 대한 사례는 해당 게임이 릴리즈된 이후에 많은 밸런스 조정이 필요할 때 큰 의미가 있습니다.

이렇게하면 캐릭터 사양, 맵, NPC 사양 등과 같은 모든 데이터 기반의 균형 잡힌 시트가 게임 빌드 자체와 독립적으로 업데이트 될 수 있습니다.
게임 클라이언트는 항상 온라인 게임을 시작하거나 참가하기 전에 게임 데이터를 최신 버전으로 업그레이드하기 위해 CMS 서비스에 연결할 것 입니다.

모든 사용자 정의 시리얼라이저 (바이너리, JSON 등)를 사용하여 데이터를 다시 Quantum Asset 객체로 변환 할 수 있으며, 이를 샘플 구현과 마찬가지로 데이터베이스에 등록 할 수 있습니다.

절차적인 생성

마찬가지로, 애셋은 매치가 시작되기 전에 절차적으로 생성 될 수 있습니다 (데이터베이스의 전체 또는 일부가 이 방법으로 생성 될 수 있습니다).

중요한 사항은 다음과 같습니다:

  • 절차적 생성은 일반적으로 각 클라이언트에서 독립적으로 실행되므로 생성된 데이터가 모든 클라이언트에서 같도록 유의해야 합니다.(공유 시드를 사용하고 결정적 알고리즘을 사용하면 좋은 절충안이 됩니다).
  • 시뮬레이션 시스템 내부에서 데이터 자산을 절차 적으로 생성해야하는 경우 (온라인 매치), DB 클래스의 실시간 추가 자산은 "검증 된 프레임 상태 공개" 고급 구성 옵션과 함께 수행해야합니다 (결정론적 구성에 관한 챕터에 설명되어 있습니다);

맵 에셋 베이킹 파이프라인

Quantum에서 사용자 지정 데이터를 생성하는 또 다른 유연한 진입점은 맵 베이킹 파이프라인입니다.
Map 자산은 Quantum 시뮬레이션에 필요하며 Navmeshes 및 정적 충돌기와 같은 기본 정보를 포함하지만 사용자 정의 에셋 슬롯 (모든 커스텀 데이터 자산의 인스턴스가 될 수 있음)도 포함합니다.

이 사용자 정의 자산은 초기화 또는 런타임 중에 사용되는 모든 정적 데이터 (예: 위치, 스폰된 유형 등의 스폰 지점 데이터의 배열)를 저장하는 데 사용할 수 있습니다.

유니티 에디터에서 bake 버튼이 호출 될 때마다 호출되는 커스텀 코드를 할당하려면, MapDataBakerCallback 을 확장하는 클래스는 필수로 OnBake (MapData data) 메소드를 오버라이드해야합니다:

C#

public class MyCustomDataBaker: MapDataBakerCallback
{
  public void OnBake(MapData data)
  {
    // any custom code to live-load data from scene to be baked into a custom asset

  // generated custom asset can then be assigned to data.Asset.Settings.UserAsset
  }
}
Back to top