컴포넌트
소개
컴포넌트는 엔티티에 붙일 수 있고 부착된 구성요소를 기반으로 활성 엔티티의 하위 집합만 반복하여 필터링하는 데 사용할 수 있는 특수 구조입니다.
Quantum은 사용자 지정 컴포넌트 외에도 다음과 같은 몇 가지 사전 컴포넌트가 제공됩니다.
- Transform2D/Transform3D: 고정점(FP) 값을 사용하여 위치 및 회전합니다;
- PhysicsCollider, PhysicsBody, PhysicsCallbacks, PhysicsJoints(2D/3D): Quantum의 상태 비저장 물리 엔진에서 사용됩니다.
- PathFinderAgent, SteeringAgent, AlizionAgent, AlizionAgent와 같은 주요 구성 요소가 있습니다.OBstacle: 항법 기반 경로 찾기 및 이동입니다.
컴포넌트
DSL에서 기본적인 컴포넌트를 정의하는 기본 예제입니다:
C#
component Action
{
FP Cooldown;
FP Power;
}
구조체 대신 컴포넌트로 레이블을 지정(위와 같이) 하면 적절한 코드 구조(마커 인터페이스, id 속성 등)가 생성됩니다. 컴파일이 완료되면 컴포넌트 프로토타입과 함께 사용할 수 있도록 유니티 편집기에서도 사용할 수 있습니다. 편집기에서 사용자 지정 컴포넌트의 이름은 Entity Component ComponentName입니다.
컴포넌트에서 작업할 API는 Frame 클래스를 통해 제공됩니다.
컴포넌트 또는 포인터를 통해 컴포넌트에 대한 복사본 작업을 수행할 수 있습니다. 접근 타입을 구분하기 위해 복사본 작업을 위한 API는 Frame
을 통해 직접 접근할 수 있으며 포인터 액세스를 위한 API는 Frame.Unsafe
에서 사용할 수 있습니다 - 후자가 메모리를 수정합니다.
컴포넌트를 추가, 가져오기 및 설정하는 데 필요한 가장 기본적인 기능은 같은 이름의 함수입니다.
Add<T>
는 엔티티에 컴포넌트를 추가하는 데 사용됩니다. 각 엔티티는 특정 컴포넌트의 복사본을 하나만 가질 수 있습니다. 디버깅에 도움이 되도록 Add<T>
는 AddResult 열거형을 리턴합니다.
C#
public enum AddResult {
EntityDoesNotExist = 0, // The EntityRef passed in is invalid.
ComponentAlreadyExists = 1, // The Entity in question already has this component attached to it.
ComponentAdded = 2 // The component was successfully added to the entity.
}
엔티티에 컴포넌트가 있으면 Get<T>
를 사용하여 검색할 수 있습니다. 컴포넌트 값의 사본이 리턴됩니다. 복사 작업을 수행 중이므로 Set<T>
를 사용하여 수정된 값을 컴포넌트에 저장해야 합니다. Add 메소드와 마찬가지로 작업 결과를 확인하거나 응답하는 데 사용할 수 있는 SetResult 를 리턴합니다.
C#
public enum SetResult {
EntityDoesNotExist = 0, // The EntityRef passed in is invalid.
ComponentUpdated = 1, // The component values were successfully updated.
ComponentAdded = 2 // The Entity did not have a component of this type yet, so it was added with the new values.
}
예를 들어 상태 컴포넌트의 시작 값을 설정하려면 다음을 수행합니다:
C#
private void SetHealth(Frame f, EntityRef entity, FP value){
var health = f.Get<Health>(entity);
health.Value = value;
f.Set(entity, health);
}
다음 표는 이미 제시된 방법과 컴포넌트 및 컴포넌트 값을 조작하기 위해 제공된 다른 메소드를 요약한 것입니다.
메소드 | 리턴 | 추가 정보 |
---|---|---|
Add<T>(EntityRef entityRef) | AddResult enum, 위 참조. | 무효한 엔티티 참조 허용. |
Get<T>(EntityRef entityRef) | T 현재 값을 가진 T의 복사본. |
잘못된 컴포넌트 참조를 허용하지 않습니다. 컴포넌트 T가 엔티티에 없는 경우 예외를 발생시킵니다. |
Set<T>(EntityRef entityRef) | SetResult enum, 위 참조. | 잘못된 엔티티 참조 허용. |
Has<T>(EntityRef entityRef) | bool true = 엔티티가 존재하며 컴포넌트가 연결됨 false = 엔티티가 존재하지 않으며 컴포넌트가 연결되지 않음. |
잘못된 엔티티 참조 허용, 그리고 존재하지 않는 컴포넌트 허용. |
TryGet<T>(EntityRef entityRef, out T value) | bool true = 엔티티가 존재하고 컴포넌트가 연결됨. false = 엔티티가 존재하지 않거나 컴포넌트가 연결되지 않음. |
잘못된 엔티티 참조 허용. |
TryGetComponentSet(EntityRef entityRef, out ComponentSet componentSet) |
bool true = 엔티티가 존재하고 모든 컴포넌트의 컴포넌트들이 연결됨 false = 엔티티가 존재하지 않고, 또는 집합의 하나 이상의 컴포넌트가 연결되지 않음. |
잘못된 엔티티 참조 허용. |
Remove<T>(EntityRef entityRef) | 리턴값 없음. 엔티티가 존재하고 컴포넌트를 가지고 경우 컴포넌트를 제거합니다. 이외는 아무일도 하지 않습니다. |
잘못된 엔티티 참조 허용. |
컴포넌트에 대한 작업을 직접 용이하게 하고 Get/Set, Frame.Unsafe
을 사용할 경우 -작은- 오버헤드 발생하지 않도록 하기 위해 Get 및 TryGet의 안전하지 않은 버전을 제공합니다(아래 표 참조).
메소드 | 리턴 | 추가 정보 |
---|---|---|
GetPointer<T>(EntityRef entityRef) | T* | 잘못된 엔티티 참조 허용하지 않음. 컴포넌트 T가 엔티티에 없는 경우 예외를 발생시킵니다. |
TryGetPointer<T>(EntityRef entityRef out T* value) |
bool true = 엔티티가 존재하고 컴포넌트들이 엔티티에 연결됨 false = 엔티티가 존재하지 않거나, 컴포넌트들이 엔티티에 연결되지 않음 |
잘못된 엔티티 참조 허용. |
싱글톤 컴포넌트
싱글톤 컴포넌트는 특정 시간에 하나만 존재할 수 있는 특수 유형의 컴포넌트입니다. 전체 게임 상태의 임의 엔티티에는 특정 T 싱글톤 컴포넌트의 인스턴스가 하나만 있을 수 있습니다. 이 인스턴스는 ECS 데이터 버퍼의 핵심에 깊이 적용됩니다. 이것은 Quantum에 의해 엄격하게 시행됩니다.
사용자 정의 싱글톤 컴포넌트는 singleton component
를 사용하여 DSL에서 정의할 수 있습니다.
C#
singleton component MySingleton{
FP Foo;
}
싱글톤은 IComponentSingleton
이라는 인터페이스를 상속받으며, 그 자체가 IComponent
에서 상속받습니다. 따라서 일반 컴포넌트에서 기대할 수 있는 모든 일반적인 작업을 수행할 수 있습니다.
- 모든 엔티티에 부착할 수 있습니다.
- 모든 일반 안전 및 안전하지 않은 방법(예: Get, Set, TryGetPointer 등)을 사용하여 관리할 수 있습니다.
- 유니티 편집기를 통해 엔티티 프로토타입에 넣거나 엔티티의 코드에서 인스턴스화할 수 있습니다.
일반적인 컴포넌트 관련 방법 외에도 싱글톤 전용의 몇 가지 특별한 방법이 있습니다. 메소드는 일반 컴포넌트와 마찬가지로 값 유형을 반환하는지 포인터를 반환하는지 여부에 따라 Safe 및 Unsafe 로 구분됩니다.
메소드 | 리턴 | 추가정보 |
---|---|---|
API - Frame | ||
SetSingleton<T> (T component, EntityRef optionalAddTarget = default) |
void | 싱글톤이 존재하지 않는 경우 싱글톤을 설정. ------- EntityRef (선택), 어떤 엔티티를 추가할 지 지정합니다. 아무것도 주어지지 않았으면, 새로운 엔티티가 생성되고 싱글톤에 추가됩니다. |
GetSingleton<T>() | T | 싱글톤이 존재하지 않으면 예외를 발생시킵니다. 엔티티 참조는 필요하지 않으며, 자동적으로 찾게됩니다 |
TryGetSingleton<T>(out T component) | bool true = 싱글톤이 존재 false = 싱글톤이 존재하지 않음 |
싱글톤이 존재하지 않으면 예외를 발생시키지 않습니다. 엔티티 참조가 필요하지 않으며, 자동으로 찾습니다. |
GetOrAddSingleton<T>(EntityRef optionalAddTarget = default) | T | 싱글톤을 얻어 리턴합니다. 싱글톤이 없는 경우 SetSingleton에서와 같이 생성됩니다. ----- EntityRef (선택), 작성해야 하는 경우 엔티티를 추가할 엔티티를 지정합니다. 엔티티 Ref가 전달되지 않은 경우 싱글톤을 추가할 새 엔티티가 작성됩니다. |
GetSingletonEntityRef<T>() | EntityRef | 현재 싱글톤을 보유하고 있는 엔티티를 반환합니다. 싱글톤이 존재하지 않으면 예외를 발생시킵니다. |
TryGetSingletonEntityRef<T>(out EntityRef entityRef) | bool true = 싱글톤이 존재. false = 싱글톤이 미 존재 |
현재 싱글톤을 보유하고 있는 엔티티를 가져옵니다. 싱글이 없는 경우 예외를 던지지 않습니다. |
API - Frame.Unsafe | ||
Unsafe.GetSingletonPointer<T>() | T* | 싱글톤 포인터를 얻습니다. 존재하지 않으면 예외를 발생시킵니다 |
TryGetPointerSingleton<T>(out T* component) | bool true = 싱글톤이 존재. false = 싱글톤이 미 존재. |
싱글톤 포인터를 얻습니다. |
GetOrAddSingletonPointer<T>(EntityRef optionalAddTarget = default) | T* | 싱글톤을 얻거나 추가하여 리턴합니다. 싱글톤이 존재하지 않으면, 생성합니ㄷ. ----- EntityRef (선택), 작성해야 하는 경우 엔티티를 추가할 엔티티를 지정합니다. 엔티티 Ref가 전달되지 않은 경우 싱글톤을 추가할 새 엔티티가 작성됩니다. |
추가 기능
컴포넌트는 특수 구조체이므로 C# 파일에 partial 구조체 정의를 작성하여 사용자 지정 메소드로 확장할 수 있습니다.
예를 들어 Action 컴포넌트를 다음과 같이 확장할 수 있습니다.
C#
namespace Quantum
{
public partial struct Action
{
public void UpdateCooldown(FP deltaTime){
Cooldown -= deltaTime;
}
}
}
대응 콜백
대응 콜백에 두 가지의 컴포넌트가 있습니다:
ISignalOnAdd<T>
: 컴포넌트 유형 T가 엔티티에 추가되면 호출됩니다.ISignalOnRemove<T>
: 엔티티에서 엔티티 유형 T가 제거되면 호출됩니다.
이 기능은 사용자 정의 컴포넌트의 목록을 할당 및 할당하는 등 컴포넌트의 일부를 추가/제거할 때 특히 유용합니다.
이러한 신호를 수신하려면 시스템에서 해당 신호를 구현하기만 하면 됩니다.
컴포넌트 반복
단일 컴포넌트만 필요한 경우 ComponentIterator(안전) 및 ComponentBlockIterator(안전하지 않음)가 가장 적합합니다.
C#
foreach(var transform in frame.GetComponentIterator<Transform3D>()) {
transform.Component.Position += FPVector3.Forward * frame.DeltaTime;
// EntityRef
var e = transform.Entity;
// The safe version returns a copy of the component.
// Remember to set the new values on the entity!
frame.Set(e, transform);
}
컴포넌트 블록 반복기는 포인터를 통해 가장 빠르게 접근할 수 있도록 합니다. 아주 주의하여 처리.
C#
foreach(var t in frame.Unsafe.GetComponentBlockIterator<Transform3D>()) {
t->Position += FPVector3.Forward * frame.DeltaTime;
}
필터
필터는 컴포넌트 세트를 기준으로 엔티티를 필터링할 수 있을 뿐만 아니라 시스템에 필요한 컴포넌트만 캡처할 수 있는 편리한 방법입니다. 필터는 안전(Get/Set) 코드와 안전하지 않은(pointer) 코드에 모두 사용할 수 있습니다.
지네릭
필터를 생성하기 위해서는 간단히 프레임에서 제공되는 Filter
C#
var filtered = frame.Filter<Transform3D, PhysicsBody3D>();
지네릴 필터는 최대 8개의 컴포넌트를 포함할 수 있습니다.
without 및 any ComponentSet 필터를 생성하여 구체화해야 하는 경우입니다.
C#
var without = ComponentSet.Create<CharacterController3D>();
var any = ComponentSet.Create<NavMeshPathFinder, NavMeshSteeringAgent>();
var filtered = frame.Filter<Transform3D, PhysicsBody3D>(without, any);
_ComponentSet_은 최대 8개의 컴포넌트를 포함할 수 있습니다.
without 매개 변수로 전달된 ComponentSet 은 집합에 지정된 구성 요소 중 하나 이상을 포함하는 모든 엔티티를 제외합니다. any 집합은 엔티티에 하나 이상의 지정된 컴포넌트가 있는지 확인합니다. 엔티티에 지정된 구성 요소가 없으면 필터에서 제외됩니다.
필터를 통해 반복하는 것은 filter.Next()
와 함께 while loop를 사용하는 것만큼 간단합니다. 이렇게 하면 컴포넌트의 모든 사본과 컴포넌트가 부착된 엔티티의 EntityRef
가 채워집니다.
C#
while (filtered.Next(out var e, out var t, out var b)) {
t.Position += FPVector3.Forward * frame.DeltaTime;
frame.Set(e, t);
}
주의: 컴포넌트의 copies 를 반복하여 작업하고 있습니다. 따라서 새 데이터를 해당 엔티티에 다시 설정해야 합니다.
지네릭 필터는 컴포넌트 포인터로 작업할 수도 있습니다.
C#
while (filtered.UnsafeNext(out var e, out var t, out var b)) {
t->Position += FPVector3.Forward * frame.DeltaTime;
}
이 경우 컴포넌트의 데이터를 직접 수정합니다.
FilterStruct
일반 필터 외에도 FilterStruct 접근 방식을 사용할 수 있습니다.
이를 위해 먼저 수신하려는 각 컴포넌트 유형에 대해 public 속성을 가진 구조체를 정의해야 합니다.
C#
struct PlayerFilter
{
public EntityRef Entity;
public CharacterController3D* KCC;
public Health* Health;
public FP AccumulatedDamage;
}
ComponentSet 과 같이, FilterStruct 는 최대 8개의 컴포넌트 포인터까지 필터링할 수 있습니다.
주의: FilterStruct 로 사용되는 구조체는 EntityRef 필드가 필수 입니다!
FilterStruct 의 컴포넌트 타입 멤머들은 반드시 포인터이어야 합니다. 포인터인 것만 필터에 의해 채워질 것입니다. 컴포넌트 포인터뿐만 아니라 다른 변수도 정의할 수 있지만 이러한 변수는 필터에서 무시되고 사용자가 관리해야 합니다.
C#
var players = f.Unsafe.FilterStruct<PlayerFilter>();
var playerStruct = default(PlayerFilter);
while (players.Next(&playerStruct))
{
// Do stuff
}
Frame.Unsafe.FilterStruct<T>()
필터를 추가로 지정하기 위해 선택적 ComponentSets any 및 without 을 사용하는 오버로드 있습니다.
카운트에 대한 노트
필터는 얼마나 많은 엔티티가 터치되고 반복될지 미리 알 수 없습니다. 이는 Sparse-Set ECS에서 필터가 작동하는 방식 때문입니다.
- 필터는 제공된 컴포넌트 중에서 가장 적은 엔티티를 가진 컴포넌트를 찾습니다(교차를 확인하도록 설정된 컴포넌트).
- 세트를 통과하여 쿼리 된 다른 컴포넌트를 가지고 있지 않은 컴포넌트는 폐기됩니다.
정확한 숫자를 미리 알고 있으면 (O(n) 작업이기 때문에 필터를 한 번 통과해야 합니다.
컴포넌트 Getter
알려진 엔티티에서 특정 컴포넌트 집합을 가져오려면 필터 구조를 Frame.Unsafe.ComponentGetter
와 함께 사용하십시오. 주의: 안전하지 않은 컨텍스트에서만 사용할 수 있습니다!
C#
public unsafe class MySpecificEntitySystem : SystemMainThread
struct MyFilter {
public EntityRef Entity; // Mandatory member!
public Transform2D* Transform2D;
public PhysicsBody2D* Body;
}
public override void Update(Frame f) {
MyFilter result = default;
if (f.Unsafe.ComponentGetter<MyFilter>().TryGet(f, f.Global->MyEntity, &result)) {
// Do Stuff
}
}
이 작업을 자주 수행해야 하는 경우 아래와 같이 시스템에서 룩업 구조체를 캐시 할 수 있습니다(100% 안전).
C#
public unsafe class MySpecificEntitySystem : SystemMainThread
struct MyFilter {
public EntityRef Entity; // Mandatory member!
public Transform2D* Transform2D;
public PhysicsBody2D* Body;
}
ComponentGetter<MyFilter> _myFilterGetter;
public override void OnInit(Frame f) {
_myFilterGetter = f.Unsafe.ComponentGetter<MyFilter>();
}
public override void Update(Frame f) {
MyFilter result = default;
if (_myFilterGetter.TryGet(f, f.Global->MyEntity, &result)) {
// Do Stuff
}
}
필터링 전략
종종 컴포넌트가 많지만 그중 일부만 원하는 상황에 처하게 됩니다. 앞에서 Quantum에서 필터링에 사용할 수 있는 컴포넌트와 도구를 소개했습니다. 이 섹션에서는 이러한 컴포넌트를 활용하는 몇 가지 전략을 제시하겠습니다.
주의: 최고 접근 방식은 게임 및 시스템에 따라 달라집니다. 우리는 독특한 상황에 맞는 전략을 만들기 위한 출발점으로 아래의 전략을 택할 것을 추천합니다.
참고: 아래에 사용된 모든 용어는 다른 단어 개념들을 캡슐화하기 위해 내부적으로 만들어졌습니다.
마이크로-컴포넌트
많은 엔터티가 동일한 컴포넌트 유형을 사용하고 있을 수 있지만, 동일한 컴포넌트 구성을 사용하는 엔터티는 거의 없습니다. 이들의 구성을 더욱 전문화하는 한 가지 방법은 마이크로 컴포넌트 를 사용하는 것입니다. 마이크로 컴포넌트 는 특정 시스템이나 동작에 대한 데이터를 가진 고도로 전문화된 컴포넌트입니다. 고유성을 통해 필터를 생성하는 엔티티를 빠르게 식별할 수 있습니다.
플래그-컴포넌트
엔티티를 식별하는 일반적인 방법 중 하나는 플래그-컴포넌트 를 엔티티에 추가하는 것입니다. ECS에서 flags 의 개념은 se 단위로 존재하지 않으며 Quantum은 엔티티 타입을 지원하지 않습니다. 그러면 플래그-컴포넌트 는 정확히 무엇일까요? 데이터를 거의 또는 전혀 보유하지 않는 컴포넌트이며 엔티티를 식별하기 위한 독점적인 목적으로 만들어졌습니다.
예를 들어 팀 기반 게임에서 다음을 수행할 수 있습니다.
- TeamA 및 TeamB에 대한 열거가 있는 "Team" 컴포넌트입니다.
- "TeamA" 및 "TeamB" 컴포넌트로 구성됩니다.
옵션 1은 View에서 데이터를 폴링 하는 것이 주된 목적인 경우 유용하며, 옵션 2는 관련 시뮬레이션 시스템의 필터링 성능을 활용할 수 있습니다.
참고: 태그 지정 엔티티와 플래그 지정 엔티티가 서로 바꿔 사용되기 때문에 플래그 컴포넌트를 태그 컴포넌트라고도 합니다.
카운트
시뮬레이션에 현재 존재하는 컴포넌트 T의 양은 Frame.ComponentCount<T>()
를 사용하여 검색할 수 있습니다. 플래그 컴포넌트와 함께 사용하면 특정 유형의 단위를 빠르게 계산할 수 있습니다.
추가 / 제거
플래그 컴포넌트 또는 마이크로 컴포넌트만 엔티티에 임시로 부착해야 하는 경우 추가
및 제거
작업이 모두 O(1)이므로 적절한 옵션으로 남아 있습니다.
글로벌 목록
플래그 컴포넌트에 대한 대안으로 "덜" ECS와 같은 것이기는 하지만 FrameContext.User.cs
에 전역 목록을 유지하는 것이 있습니다. N개 팀을 추적해야 하는 경우 이 방법이 반드시 확장되지는 않지만 부분 집합이 제한된 세트에서는 편리합니다.
상태가 50% 미만인 모든 플레이어를 강조 표시하려면 글로벌 목록을 보류하고 다음을 수행할 수 있습니다.
- 시뮬레이션을 시작할 때 entity_ref를 목록에 추가/제거하는 시스템이 있어야 합니다.
- 이후의 모든 시스템에서 동일한 목록을 사용합니다.
주의: 이러한 유형의 조건만 산발적으로 식별하면 되는 경우 글로벌 목록을 유지하는 것보다 필요할 때 동적으로 계산하는 것이 좋습니다.
Back to top