ECS – ISystem, SystemBase


ISystem과 SystemBase 비교

https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/systems-comparison.html

시스템을 생성하기 위하여 ISystem 또는 SystemBase를 사용할 수 있다.

ISystem은 비관리형 메모리에 접근할 수 있도록 해주고, SystemBase는 관리형 데이터를 저장하는 데 유용하다.

두 시스템 타입 모두 Entities 패키지와 작업 시스템(job system)과 함께 사용할 수 있다.

ISystem은 Burst와 호환되며, SystemBase보다 빠르고 값 기반 표현을 사용한다.

일반적으로 성능상의 이점을 얻기 위해 SystemBase보다 ISystem을 사용하는 것이 좋다.

그러나 SystemBase는 가비지 컬렉션 할당이나 SourceGen 컴파일 시간 증가를 감수하면서도 편리한 기능을 제공한다. (편의성 및 생산성을 위하여)

Feature (특징)ISystem compatibility (호환성)SystemBase compatibility (호환성)
Burst compile OnCreateOnUpdate, and OnDestroyYesNo
Unmanaged memory allocated (관리되지 않는 메모리 할당됨)YesNo
GC allocated (GC 할당)NoYes
Can store managed data directly in system type
(관리되는 데이터를 시스템 유형에 직접 저장할 수 있음)
NoYes
Idiomatic foreachYesYes
Entities.ForEachNoYes
Job.WithCodeNoYes
IJobEntityYesYes
IJobChunkYesYes
Supports inheritance (상속 지원)NoYes
https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/systems-comparison.html
https://www.youtube.com/watch?v=LbTkEQA_7IA

만약 Spline 경로를 따라 특정 엔티티를 이동시키는 시스템을 작성한다면

  1. 이 시스템은 모든 Spline을 따라가는 엔티티를 대상으로 작동하며, 이러한 엔티티는 FollowingSplineTag 컴포넌트로 식별됩니다.
    이 컴포넌트는 엔티티 쿼리에 포함되지만, 시스템은 이 컴포넌트를 읽거나 쓸 필요가 없습니다.
  2. 시스템은 Spline을 따라가는 엔티티의 SplineFollower 컴포넌트에 읽기 전용으로 접근해야 합니다.
    이 컴포넌트에는 따라갈 Spline 엔티티를 참조하는 Entity와 스플라인을 따라 이동할 거리를 나타내는 float 값이 포함되어 있습니다.
  3. Spline 엔티티에는 SplinePointsBuffer라는 동적 버퍼가 포함되어 있습니다.
    Spline을 따라가는 엔티티가 임의의 Entity 핸들을 사용하여 따라갈 Spline 엔티티를 참조하기 때문에, 시스템은 이러한 버퍼에 읽기 전용 랜덤 액세스가 필요합니다.
  4. Spline 엔티티에는 SplineLength 컴포넌트도 포함되어 있으며, 시스템은 Spline 위치 계산을 수행하기 위해 이 컴포넌트에 읽기 전용 랜덤 액세스가 필요합니다.
  5. 마지막으로, 시스템은 Spline을 따라가는 엔티티의 LocalTransform 컴포넌트에 읽기-쓰기 접근이 필요하며, 이를 통해 위치와 회전을 업데이트합니다.

다음은 Spline 계산을 수행하는 도우미 메서드의 스텁(stub)을 선언한 예시입니다:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Spline을 따르는 엔티티를 식별하기 위한 태그 컴포넌트
// 데이터는 포함하지 않고, 단순히 해당 엔티티가 Spline 팔로워임을 나타냄
// 단순 태그 역할로, 시스템이 해당 엔티티를 필터링할 때 사
public struct FollowingSplineTag : IComponentData { }
// Spline 팔로워 엔티티에 포함될 컴포넌트
// - Spline: 따라갈 스플라인 엔티티를 참조
// - Distance: Spline에서의 현재 거리
public struct SplineFollower : IComponentData
{
public Entity Spline; // 따라갈 Spline 엔티티
public float Distance; // Spline 상에서의 현재 위치(거리)
}
// Spline 점 데이터를 저장하기 위한 동적 버퍼 요소
// - SplinePoint: 스플라인 상의 점 좌표(float3)
public struct SplinePointsBuffer : IBufferElementData
{
public float3 SplinePoint; // Spline의 특정 점
}
// Spline의 전체 길이를 저장하기 위한 컴포넌트
// - Value: Spline의 총 길이
public struct SplineLength : IComponentData
{
public float Value; // Spline 전체 길이 값
}
// Spline 계산과 관련된 헬퍼 클래스
public struct SplineHelper
{
// Spline을 따라 이동하는 위치와 회전을 계산하여 LocalTransform을 반환
// - pointsBuf: Spline 점 데이터를 담고 있는 동적 버퍼
// - length: Spline의 총 길이
// - distance: Spline 상에서의 현재 거리
public static LocalTransform FollowSpline(DynamicBuffer<SplinePointsBuffer> pointsBuf, float length, float distance)
{
// Spline 계산을 수행하고 새 LocalTransform을 반환
// 계산 로직은 이 부분에 구현해야 함
}
}
// Spline을 따르는 엔티티를 식별하기 위한 태그 컴포넌트 // 데이터는 포함하지 않고, 단순히 해당 엔티티가 Spline 팔로워임을 나타냄 // 단순 태그 역할로, 시스템이 해당 엔티티를 필터링할 때 사 public struct FollowingSplineTag : IComponentData { } // Spline 팔로워 엔티티에 포함될 컴포넌트 // - Spline: 따라갈 스플라인 엔티티를 참조 // - Distance: Spline에서의 현재 거리 public struct SplineFollower : IComponentData { public Entity Spline; // 따라갈 Spline 엔티티 public float Distance; // Spline 상에서의 현재 위치(거리) } // Spline 점 데이터를 저장하기 위한 동적 버퍼 요소 // - SplinePoint: 스플라인 상의 점 좌표(float3) public struct SplinePointsBuffer : IBufferElementData { public float3 SplinePoint; // Spline의 특정 점 } // Spline의 전체 길이를 저장하기 위한 컴포넌트 // - Value: Spline의 총 길이 public struct SplineLength : IComponentData { public float Value; // Spline 전체 길이 값 } // Spline 계산과 관련된 헬퍼 클래스 public struct SplineHelper { // Spline을 따라 이동하는 위치와 회전을 계산하여 LocalTransform을 반환 // - pointsBuf: Spline 점 데이터를 담고 있는 동적 버퍼 // - length: Spline의 총 길이 // - distance: Spline 상에서의 현재 거리 public static LocalTransform FollowSpline(DynamicBuffer<SplinePointsBuffer> pointsBuf, float length, float distance) { // Spline 계산을 수행하고 새 LocalTransform을 반환 // 계산 로직은 이 부분에 구현해야 함 } }
// Spline을 따르는 엔티티를 식별하기 위한 태그 컴포넌트
// 데이터는 포함하지 않고, 단순히 해당 엔티티가 Spline 팔로워임을 나타냄
// 단순 태그 역할로, 시스템이 해당 엔티티를 필터링할 때 사
public struct FollowingSplineTag : IComponentData { }

// Spline 팔로워 엔티티에 포함될 컴포넌트
// - Spline: 따라갈 스플라인 엔티티를 참조
// - Distance: Spline에서의 현재 거리
public struct SplineFollower : IComponentData
{
   public Entity Spline; // 따라갈 Spline 엔티티
   public float Distance; // Spline 상에서의 현재 위치(거리)
}

// Spline 점 데이터를 저장하기 위한 동적 버퍼 요소
// - SplinePoint: 스플라인 상의 점 좌표(float3)
public struct SplinePointsBuffer : IBufferElementData
{
   public float3 SplinePoint; // Spline의 특정 점
}

// Spline의 전체 길이를 저장하기 위한 컴포넌트
// - Value: Spline의 총 길이
public struct SplineLength : IComponentData
{
   public float Value; // Spline 전체 길이 값
}

// Spline 계산과 관련된 헬퍼 클래스
public struct SplineHelper
{
   // Spline을 따라 이동하는 위치와 회전을 계산하여 LocalTransform을 반환
   // - pointsBuf: Spline 점 데이터를 담고 있는 동적 버퍼
   // - length: Spline의 총 길이
   // - distance: Spline 상에서의 현재 거리
   public static LocalTransform FollowSpline(DynamicBuffer<SplinePointsBuffer> pointsBuf, float length, float distance)
   {
       // Spline 계산을 수행하고 새 LocalTransform을 반환
       // 계산 로직은 이 부분에 구현해야 함
   }
}

foreach를 이용한 ISystem 시스템 내에서의 사용

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Spline 길이 컴포넌트 조회를 위한 읽기 전용 LookUp 생성
var lengthLookup = SystemAPI.GetComponentLookup<SplineLength>(true);
// Spline 점 버퍼 조회를 위한 읽기 전용 LookUp 생성
var pointsBufferLookup = SystemAPI.GetBufferLookup<SplinePointsBuffer>(true);
// FollowingSplineTag를 가진 모든 엔티티를 조회하여 처리
foreach (var (transform, follower) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<SplineFollower>>()
.WithAll<FollowingSplineTag>()) // 태그 기반 필터링
{
// Spline의 전체 길이를 가져옴
var splineLength = lengthLookup[follower.ValueRO.Spline].Value;
// Spline 점 데이터를 포함하는 동적 버퍼를 가져옴
var pointsBuf = pointsBufferLookup[follower.ValueRO.Spline];
// Spline 계산 로직을 통해 엔티티의 위치와 회전을 업데이트
transform.ValueRW = SplineHelper.FollowSpline(pointsBuf, splineLength, follower.ValueRO.Distance);
}
// Spline 길이 컴포넌트 조회를 위한 읽기 전용 LookUp 생성 var lengthLookup = SystemAPI.GetComponentLookup<SplineLength>(true); // Spline 점 버퍼 조회를 위한 읽기 전용 LookUp 생성 var pointsBufferLookup = SystemAPI.GetBufferLookup<SplinePointsBuffer>(true); // FollowingSplineTag를 가진 모든 엔티티를 조회하여 처리 foreach (var (transform, follower) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<SplineFollower>>() .WithAll<FollowingSplineTag>()) // 태그 기반 필터링 { // Spline의 전체 길이를 가져옴 var splineLength = lengthLookup[follower.ValueRO.Spline].Value; // Spline 점 데이터를 포함하는 동적 버퍼를 가져옴 var pointsBuf = pointsBufferLookup[follower.ValueRO.Spline]; // Spline 계산 로직을 통해 엔티티의 위치와 회전을 업데이트 transform.ValueRW = SplineHelper.FollowSpline(pointsBuf, splineLength, follower.ValueRO.Distance); }
// Spline 길이 컴포넌트 조회를 위한 읽기 전용 LookUp 생성
var lengthLookup = SystemAPI.GetComponentLookup<SplineLength>(true);

// Spline 점 버퍼 조회를 위한 읽기 전용 LookUp 생성
var pointsBufferLookup = SystemAPI.GetBufferLookup<SplinePointsBuffer>(true);

// FollowingSplineTag를 가진 모든 엔티티를 조회하여 처리
foreach (var (transform, follower) in
        SystemAPI.Query<RefRW<LocalTransform>, RefRO<SplineFollower>>()
        .WithAll<FollowingSplineTag>()) // 태그 기반 필터링
{
   // Spline의 전체 길이를 가져옴
   var splineLength = lengthLookup[follower.ValueRO.Spline].Value;

   // Spline 점 데이터를 포함하는 동적 버퍼를 가져옴
   var pointsBuf = pointsBufferLookup[follower.ValueRO.Spline];

   // Spline 계산 로직을 통해 엔티티의 위치와 회전을 업데이트
   transform.ValueRW = SplineHelper.FollowSpline(pointsBuf, splineLength, follower.ValueRO.Distance);
}

IJobEntity를 이용한 멀티스레드로 접근

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// 멀티스레드로 스플라인 처리를 수행하기 위한 Job 선언
[BurstCompile]
[WithAll(typeof(FollowingSplineTag))] // 특정 태그를 가진 엔티티만 처리
public partial struct FollowSplineJob : IJobEntity
{
// 스플라인 길이를 조회하기 위한 읽기 전용 LookUp
[ReadOnly] public ComponentLookup<SplineLength> LengthLookup;
// 스플라인 점 데이터를 조회하기 위한 읽기 전용 BufferLookUp
[ReadOnly] public BufferLookup<SplinePointsBuffer> PointsBufferLookup;
// 각 엔티티를 처리하는 Execute 메서드
public void Execute(ref LocalTransform transform, in SplineFollower follower)
{
// 스플라인의 전체 길이를 조회
var splineLength = LengthLookup[follower.Spline].Value;
// 스플라인 점 데이터를 조회
var pointsBuf = PointsBufferLookup[follower.Spline];
// 계산된 새 LocalTransform을 할당
transform = SplineHelper.FollowSpline(pointsBuf, splineLength, follower.Distance);
}
}
// 멀티스레드로 스플라인 처리를 수행하기 위한 Job 선언 [BurstCompile] [WithAll(typeof(FollowingSplineTag))] // 특정 태그를 가진 엔티티만 처리 public partial struct FollowSplineJob : IJobEntity { // 스플라인 길이를 조회하기 위한 읽기 전용 LookUp [ReadOnly] public ComponentLookup<SplineLength> LengthLookup; // 스플라인 점 데이터를 조회하기 위한 읽기 전용 BufferLookUp [ReadOnly] public BufferLookup<SplinePointsBuffer> PointsBufferLookup; // 각 엔티티를 처리하는 Execute 메서드 public void Execute(ref LocalTransform transform, in SplineFollower follower) { // 스플라인의 전체 길이를 조회 var splineLength = LengthLookup[follower.Spline].Value; // 스플라인 점 데이터를 조회 var pointsBuf = PointsBufferLookup[follower.Spline]; // 계산된 새 LocalTransform을 할당 transform = SplineHelper.FollowSpline(pointsBuf, splineLength, follower.Distance); } }
// 멀티스레드로 스플라인 처리를 수행하기 위한 Job 선언
[BurstCompile]
[WithAll(typeof(FollowingSplineTag))] // 특정 태그를 가진 엔티티만 처리
public partial struct FollowSplineJob : IJobEntity
{
   // 스플라인 길이를 조회하기 위한 읽기 전용 LookUp
   [ReadOnly] public ComponentLookup<SplineLength> LengthLookup;

   // 스플라인 점 데이터를 조회하기 위한 읽기 전용 BufferLookUp
   [ReadOnly] public BufferLookup<SplinePointsBuffer> PointsBufferLookup;

   // 각 엔티티를 처리하는 Execute 메서드
   public void Execute(ref LocalTransform transform, in SplineFollower follower)
   {
       // 스플라인의 전체 길이를 조회
       var splineLength = LengthLookup[follower.Spline].Value;

       // 스플라인 점 데이터를 조회
       var pointsBuf = PointsBufferLookup[follower.Spline];

       // 계산된 새 LocalTransform을 할당
       transform = SplineHelper.FollowSpline(pointsBuf, splineLength, follower.Distance);
   }
}

IJobEntity

특정 엔티티의 쿼리를 자동으로 생성하며, 멀티스레드에서 병렬 처리 가능.

Execute 메서드에서 각 엔티티를 처리하는 로직을 정의.


ISystem 개요

비관리 시스템(Unmanaged System)을 생성하려면 ISystem 인터페이스를 구현해야 합니다.

추상 매서드(abstract methods) 구현 – 필수

ISystem 인터페이스를 구현할 때 다음과 같은 abstract 메서드를 반드시 구현해야 하며, abstract 매서드는 Burst 컴파일이 가능합니다:

MethodDescription
OnCreateSystem event callback to initialize the system and its data before usage.
시스템과 데이터를 사용하기 전에 초기화하기 위한 시스템 이벤트 콜백.
OnUpdateSystem event callback to add the work that your system must perform every frame.
매 프레임마다 시스템이 수행해야 할 작업을 추가하는 시스템 이벤트 콜백.
OnDestroySystem event callback to clean up resources before destruction.
시스템이 파괴되기 전에 리소스를 정리하기 위한 시스템 이벤트 콜백.

ISystem 시스템은 SystemBase와 달리 기본 클래스를 상속받지 않습니다.

대신, 각 OnCreate, OnUpdate, OnDestroy 메서드에서 ref SystemState 인수를 사용하여 다음과 같은 데이터를 액세스할 수 있습니다:

  • World
  • WorldUnmanaged
  • EntityManager와 같은 컨텍스트 월드 데이터 및 API.

간단한 사용 예제 코드

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using Unity.Burst;
using Unity.Entities;
public struct ExampleComponent : IComponentData
{
public float Value;
}
[BurstCompile]
public partial struct ExampleSystem : ISystem
{
// OnCreate: 시스템이 생성될 때 호출됩니다.
[BurstCompile]
public void OnCreate(ref SystemState state)
{
// ExampleComponent를 가진 엔티티가 업데이트 대상이 되도록 설정
state.RequireForUpdate<ExampleComponent>();
// 초기화 작업: 엔티티 추가 예시
var entityManager = state.EntityManager;
var entity = entityManager.CreateEntity();
entityManager.AddComponentData(entity, new ExampleComponent { Value = 0f });
UnityEngine.Debug.Log("System Created and ExampleComponent initialized.");
}
// OnUpdate: 매 프레임마다 호출됩니다.
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// 모든 ExampleComponent를 가진 엔티티를 대상으로 작업 수행
foreach (var example in SystemAPI.Query<RefRW<ExampleComponent>>())
{
// Value 값을 증가
example.ValueRW.Value += 1f * SystemAPI.Time.DeltaTime;
}
UnityEngine.Debug.Log("System Updated: Values incremented.");
}
// OnDestroy: 시스템이 파괴될 때 호출됩니다.
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
UnityEngine.Debug.Log("System Destroyed: Cleanup complete.");
}
}
using Unity.Burst; using Unity.Entities; public struct ExampleComponent : IComponentData { public float Value; } [BurstCompile] public partial struct ExampleSystem : ISystem { // OnCreate: 시스템이 생성될 때 호출됩니다. [BurstCompile] public void OnCreate(ref SystemState state) { // ExampleComponent를 가진 엔티티가 업데이트 대상이 되도록 설정 state.RequireForUpdate<ExampleComponent>(); // 초기화 작업: 엔티티 추가 예시 var entityManager = state.EntityManager; var entity = entityManager.CreateEntity(); entityManager.AddComponentData(entity, new ExampleComponent { Value = 0f }); UnityEngine.Debug.Log("System Created and ExampleComponent initialized."); } // OnUpdate: 매 프레임마다 호출됩니다. [BurstCompile] public void OnUpdate(ref SystemState state) { // 모든 ExampleComponent를 가진 엔티티를 대상으로 작업 수행 foreach (var example in SystemAPI.Query<RefRW<ExampleComponent>>()) { // Value 값을 증가 example.ValueRW.Value += 1f * SystemAPI.Time.DeltaTime; } UnityEngine.Debug.Log("System Updated: Values incremented."); } // OnDestroy: 시스템이 파괴될 때 호출됩니다. [BurstCompile] public void OnDestroy(ref SystemState state) { UnityEngine.Debug.Log("System Destroyed: Cleanup complete."); } }
using Unity.Burst;
using Unity.Entities;

public struct ExampleComponent : IComponentData
{
    public float Value;
}

[BurstCompile]
public partial struct ExampleSystem : ISystem
{
    // OnCreate: 시스템이 생성될 때 호출됩니다.
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        // ExampleComponent를 가진 엔티티가 업데이트 대상이 되도록 설정
        state.RequireForUpdate<ExampleComponent>();

        // 초기화 작업: 엔티티 추가 예시
        var entityManager = state.EntityManager;
        var entity = entityManager.CreateEntity();
        entityManager.AddComponentData(entity, new ExampleComponent { Value = 0f });

        UnityEngine.Debug.Log("System Created and ExampleComponent initialized.");
    }

    // OnUpdate: 매 프레임마다 호출됩니다.
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // 모든 ExampleComponent를 가진 엔티티를 대상으로 작업 수행
        foreach (var example in SystemAPI.Query<RefRW<ExampleComponent>>())
        {
            // Value 값을 증가
            example.ValueRW.Value += 1f * SystemAPI.Time.DeltaTime;
        }

        UnityEngine.Debug.Log("System Updated: Values incremented.");
    }

    // OnDestroy: 시스템이 파괴될 때 호출됩니다.
    [BurstCompile]
    public void OnDestroy(ref SystemState state)
    {
        UnityEngine.Debug.Log("System Destroyed: Cleanup complete.");
    }
}

ISystemStartStop 구현 – 선택적

ISystemStartStop 인터페이스를 구현하면 아래의 추가적인 콜백 메서드들을 사용할 수 있습니다.

MethodDescription
OnStartRunningSystem event callback before the first call to OnUpdate, and when a system resumes after it’s stopped or disabled.
OnUpdate가 처음 호출되기 전에, 그리고 시스템이 중단되거나 비활성화되었다가 다시 활성화되었을 때 호출됨.
OnStopRunningSystem event callback when a system is disabled or doesn’t match any of the system’s required components for update.
시스템이 비활성화되거나 업데이트에 필요한 컴포넌트를 더 이상 매칭하지 않을 때 호출됨.

작업(Job) 스케줄링

모든 시스템 이벤트는 메인 스레드에서 실행됩니다.

따라서 OnUpdate 메서드를 사용하여 대부분의 작업을 수행할 작업(Job)을 스케줄링하는 것이 가장 좋은 방법입니다.

작업을 스케줄링하려면 다음 중 하나를 사용할 수 있습니다:

IJobEntity: 개별 엔티티에 초점을 맞추며, 간단한 구조로 특정 컴포넌트를 처리할 때 적합합니다.

IJobChunk: 데이터 청크 단위로 처리하여 메모리 접근을 최적화하며, 대량의 데이터를 처리할 때 더 효율적입니다.

IJobEntity를 사용한 예제

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
// 컴포넌트 정의
public struct ExampleComponent : IComponentData
{
public float Value;
}
[BurstCompile]
public partial struct ExampleSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<ExampleComponent>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// IJobEntity를 정의
[BurstCompile]
public partial struct IncrementJob : IJobEntity
{
public float DeltaTime;
public void Execute(ref ExampleComponent example)
{
// 값 증가
example.Value += DeltaTime;
}
}
// 작업 스케줄링
new IncrementJob
{
DeltaTime = SystemAPI.Time.DeltaTime
}.ScheduleParallel();
}
[BurstCompile]
public void OnDestroy(ref SystemState state) { }
}
using Unity.Burst; using Unity.Entities; using Unity.Jobs; // 컴포넌트 정의 public struct ExampleComponent : IComponentData { public float Value; } [BurstCompile] public partial struct ExampleSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<ExampleComponent>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { // IJobEntity를 정의 [BurstCompile] public partial struct IncrementJob : IJobEntity { public float DeltaTime; public void Execute(ref ExampleComponent example) { // 값 증가 example.Value += DeltaTime; } } // 작업 스케줄링 new IncrementJob { DeltaTime = SystemAPI.Time.DeltaTime }.ScheduleParallel(); } [BurstCompile] public void OnDestroy(ref SystemState state) { } }
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;

// 컴포넌트 정의
public struct ExampleComponent : IComponentData
{
    public float Value;
}

[BurstCompile]
public partial struct ExampleSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<ExampleComponent>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // IJobEntity를 정의
        [BurstCompile]
        public partial struct IncrementJob : IJobEntity
        {
            public float DeltaTime;

            public void Execute(ref ExampleComponent example)
            {
                // 값 증가
                example.Value += DeltaTime;
            }
        }

        // 작업 스케줄링
        new IncrementJob
        {
            DeltaTime = SystemAPI.Time.DeltaTime
        }.ScheduleParallel();
    }

    [BurstCompile]
    public void OnDestroy(ref SystemState state) { }
}

IJobChunk: 아키타입 청크 단위로 데이터를 반복 처리.

IJobChunk를 사용한 예제

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
[BurstCompile]
public partial struct ChunkExampleSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<ExampleComponent>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// IJobChunk 정의
[BurstCompile]
public struct IncrementChunkJob : IJobChunk
{
public float DeltaTime;
// ExampleComponent 데이터 접근
public ComponentTypeHandle<ExampleComponent> ExampleTypeHandle;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var examples = chunk.GetNativeArray(ExampleTypeHandle);
for (int i = 0; i < chunk.Count; i++)
{
examples[i] = new ExampleComponent
{
Value = examples[i].Value + DeltaTime
};
}
}
}
// 작업 스케줄링
var job = new IncrementChunkJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
ExampleTypeHandle = state.GetComponentTypeHandle<ExampleComponent>()
};
state.Dependency = job.ScheduleParallel(state.Dependency);
}
[BurstCompile]
public void OnDestroy(ref SystemState state) { }
}
using Unity.Burst; using Unity.Entities; using Unity.Jobs; using Unity.Collections; [BurstCompile] public partial struct ChunkExampleSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<ExampleComponent>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { // IJobChunk 정의 [BurstCompile] public struct IncrementChunkJob : IJobChunk { public float DeltaTime; // ExampleComponent 데이터 접근 public ComponentTypeHandle<ExampleComponent> ExampleTypeHandle; public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) { var examples = chunk.GetNativeArray(ExampleTypeHandle); for (int i = 0; i < chunk.Count; i++) { examples[i] = new ExampleComponent { Value = examples[i].Value + DeltaTime }; } } } // 작업 스케줄링 var job = new IncrementChunkJob { DeltaTime = SystemAPI.Time.DeltaTime, ExampleTypeHandle = state.GetComponentTypeHandle<ExampleComponent>() }; state.Dependency = job.ScheduleParallel(state.Dependency); } [BurstCompile] public void OnDestroy(ref SystemState state) { } }
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;

[BurstCompile]
public partial struct ChunkExampleSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<ExampleComponent>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // IJobChunk 정의
        [BurstCompile]
        public struct IncrementChunkJob : IJobChunk
        {
            public float DeltaTime;

            // ExampleComponent 데이터 접근
            public ComponentTypeHandle<ExampleComponent> ExampleTypeHandle;

            public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
            {
                var examples = chunk.GetNativeArray(ExampleTypeHandle);

                for (int i = 0; i < chunk.Count; i++)
                {
                    examples[i] = new ExampleComponent
                    {
                        Value = examples[i].Value + DeltaTime
                    };
                }
            }
        }

        // 작업 스케줄링
        var job = new IncrementChunkJob
        {
            DeltaTime = SystemAPI.Time.DeltaTime,
            ExampleTypeHandle = state.GetComponentTypeHandle<ExampleComponent>()
        };

        state.Dependency = job.ScheduleParallel(state.Dependency);
    }

    [BurstCompile]
    public void OnDestroy(ref SystemState state) { }
}

콜백 메서드의 호출 순서

Unity는 시스템 생성 프로세스 중 여러 시점에서 다음 콜백 메서드를 호출하며, 이를 통해 시스템이 매 프레임 수행할 작업을 스케줄링할 수 있습니다

매 프레임 수행할 작업을 스케줄링할 수 있습니다:

https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/systems-systembase.html
메서드설명
OnCreateECS가 시스템을 생성할 때 호출됨.
OnStartRunningOnUpdate가 처음 호출되기 전에, 또는 시스템이 중단되거나 비활성화되었다가 다시 활성화되었을 때 호출됨.
OnUpdate시스템에 작업이 있는 동안 매 프레임마다 호출됨. 시스템의 실행 조건에 대한 자세한 내용은 ShouldRunSystem 참조.
OnStopRunningOnDestroy 호출 전, 또는 시스템이 중단될 때(예: 시스템의 RequireForUpdate와 매칭되는 엔티티가 없거나, Enabled 속성이 false인 경우) 호출됨.
OnDestroyECS가 시스템을 파괴할 때 호출됨.

추가 자료

Access data introduction


SystemBase 개요

관리형 시스템을 생성하려면 추상 클래스 SystemBase를 구현해야 합니다.

OnUpdate 시스템 이벤트 콜백을 사용하여 매 프레임마다 시스템이 수행해야 할 작업을 추가해야 합니다.

ComponentSystemBase 네임스페이스의 다른 콜백 메서드는 선택 사항입니다.

모든 시스템 이벤트는 메인 스레드에서 실행됩니다.

따라서, 대부분의 작업은 OnUpdate 메서드에서 작업을 예약하는 것이 모범 사례입니다.

시스템에서 작업을 예약하려면 다음 메커니즘 중 하나를 사용할 수 있습니다:

Entities.ForEach: 컴포넌트 데이터를 반복 처리합니다.

Job.WithCode: 람다 표현식을 단일 백그라운드 작업으로 실행합니다.

IJobEntity: 여러 시스템에서 컴포넌트 데이터를 반복 처리합니다.

IJobChunk: 아키타입 청크별 데이터를 반복 처리합니다.

아래 예제는 Entities.ForEach를 사용하여 한 컴포넌트의 값을 기반으로 다른 컴포넌트를 업데이트하는 시스템을 구현하는 방법을 보여줍니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Position 구조체: ECS(Entity Component System)에서 엔티티의 위치를 저장하는 데이터 컴포넌트
public struct Position : IComponentData
{
public float3 Value; // 3D 공간에서 위치 값을 나타내는 float3 타입
}
// Velocity 구조체: ECS에서 엔티티의 속도를 저장하는 데이터 컴포넌트
public struct Velocity : IComponentData
{
public float3 Value; // 3D 공간에서 속도를 나타내는 float3 타입
}
// ECSSystem 클래스: SystemBase를 상속받아 엔티티의 Position 값을 업데이트하는 시스템을 정의
[RequireMatchingQueriesForUpdate] // 모든 쿼리 조건이 매칭되는 경우에만 시스템이 업데이트되도록 설정
public partial class ECSSystem : SystemBase
{
// OnUpdate: 매 프레임마다 실행되는 시스템 메서드
protected override void OnUpdate()
{
// DeltaTime: 두 프레임 사이의 시간 간격을 로컬 변수에 저장
float dT = SystemAPI.Time.DeltaTime;
// Entities API를 사용하여 Position과 Velocity 컴포넌트를 가진 엔티티를 대상으로 작업 실행
Entities
.WithName("Update_Displacement") // 작업에 이름 부여 (디버깅 또는 프로파일링 용도)
.ForEach( // ForEach를 사용하여 컴포넌트 데이터를 반복 처리
(ref Position position, in Velocity velocity) =>
{
// 현재 Position 값에 Velocity * DeltaTime을 더해 위치를 업데이트
position = new Position()
{
Value = position.Value + velocity.Value * dT
};
}
)
.ScheduleParallel(); // 작업을 병렬로 예약하여 여러 스레드에서 실행
}
}
// Position 구조체: ECS(Entity Component System)에서 엔티티의 위치를 저장하는 데이터 컴포넌트 public struct Position : IComponentData { public float3 Value; // 3D 공간에서 위치 값을 나타내는 float3 타입 } // Velocity 구조체: ECS에서 엔티티의 속도를 저장하는 데이터 컴포넌트 public struct Velocity : IComponentData { public float3 Value; // 3D 공간에서 속도를 나타내는 float3 타입 } // ECSSystem 클래스: SystemBase를 상속받아 엔티티의 Position 값을 업데이트하는 시스템을 정의 [RequireMatchingQueriesForUpdate] // 모든 쿼리 조건이 매칭되는 경우에만 시스템이 업데이트되도록 설정 public partial class ECSSystem : SystemBase { // OnUpdate: 매 프레임마다 실행되는 시스템 메서드 protected override void OnUpdate() { // DeltaTime: 두 프레임 사이의 시간 간격을 로컬 변수에 저장 float dT = SystemAPI.Time.DeltaTime; // Entities API를 사용하여 Position과 Velocity 컴포넌트를 가진 엔티티를 대상으로 작업 실행 Entities .WithName("Update_Displacement") // 작업에 이름 부여 (디버깅 또는 프로파일링 용도) .ForEach( // ForEach를 사용하여 컴포넌트 데이터를 반복 처리 (ref Position position, in Velocity velocity) => { // 현재 Position 값에 Velocity * DeltaTime을 더해 위치를 업데이트 position = new Position() { Value = position.Value + velocity.Value * dT }; } ) .ScheduleParallel(); // 작업을 병렬로 예약하여 여러 스레드에서 실행 } }
// Position 구조체: ECS(Entity Component System)에서 엔티티의 위치를 저장하는 데이터 컴포넌트
public struct Position : IComponentData
{
    public float3 Value; // 3D 공간에서 위치 값을 나타내는 float3 타입
}

// Velocity 구조체: ECS에서 엔티티의 속도를 저장하는 데이터 컴포넌트
public struct Velocity : IComponentData
{
    public float3 Value; // 3D 공간에서 속도를 나타내는 float3 타입
}

// ECSSystem 클래스: SystemBase를 상속받아 엔티티의 Position 값을 업데이트하는 시스템을 정의
[RequireMatchingQueriesForUpdate] // 모든 쿼리 조건이 매칭되는 경우에만 시스템이 업데이트되도록 설정
public partial class ECSSystem : SystemBase
{
    // OnUpdate: 매 프레임마다 실행되는 시스템 메서드
    protected override void OnUpdate()
    {
        // DeltaTime: 두 프레임 사이의 시간 간격을 로컬 변수에 저장
        float dT = SystemAPI.Time.DeltaTime;

        // Entities API를 사용하여 Position과 Velocity 컴포넌트를 가진 엔티티를 대상으로 작업 실행
        Entities
            .WithName("Update_Displacement") // 작업에 이름 부여 (디버깅 또는 프로파일링 용도)
            .ForEach( // ForEach를 사용하여 컴포넌트 데이터를 반복 처리
                (ref Position position, in Velocity velocity) =>
                {
                    // 현재 Position 값에 Velocity * DeltaTime을 더해 위치를 업데이트
                    position = new Position()
                    {
                        Value = position.Value + velocity.Value * dT
                    };
                }
            )
            .ScheduleParallel(); // 작업을 병렬로 예약하여 여러 스레드에서 실행
    }
}

콜백 메서드의 호출 순서

Unity의 SystemBase에서는 시스템이 생성되고 업데이트되는 동안 특정 시점에서 호출되는 여러 콜백 메서드가 있습니다.

이 메서드들은 매 프레임마다 시스템이 수행해야 할 작업을 예약하는 데 사용됩니다.

메서드설명
OnCreateECS가 시스템을 생성할 때 호출됨.
OnStartRunningOnUpdate가 처음 호출되기 전에, 또는 시스템이 중단되거나 비활성화되었다가 다시 활성화되었을 때 호출됨.
OnUpdate시스템에 작업이 있는 동안 매 프레임마다 호출됨. 시스템의 실행 조건에 대한 자세한 내용은 ShouldRunSystem 참조.
OnStopRunningOnDestroy 전에 호출됩니다. 또한, 시스템이 중지될 때 호출됩니다. 시스템이 중지되는 경우는 다음과 같습니다
엔티티가 시스템의 RequireForUpdate 조건과 일치하지 않을 때 / 시스템의 Enabled 속성이 false로 설정될 때
참고: RequireForUpdate가 지정되지 않은 경우, 시스템은 비활성화되거나 삭제되지 않는 한 계속 실행됩니다.
OnDestroyECS가 시스템을 파괴할 때 호출됨.
https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/systems-systembase.html

부모 시스템 그룹의 OnUpdate 메서드는 해당 그룹에 속한 모든 시스템의 OnUpdate 메서드를 트리거합니다.

시스템 업데이트 순서에 대한 자세한 내용은 시스템 업데이트 순서를 참조하세요.

“ECS – ISystem, SystemBase”에 대한 1개의 생각

  1. 핑백: ECS - ISystem Study (1) - 어제와 내일의 나 그 사이의 이야기

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다