이전에 프리뷰버전으로 나왔던 Unity ECS를 봤을 때는 공부해야 할 것도 많고 써먹기도 힘들겠다 싶어서 미뤄두었는데, Unity 2022.2 테크 스트림(https://youtu.be/46qRfiAGCHs) 돌려보다가 관련 이야기가 나왔고 최신 버전이 1.0.0-pre.15 인걸로 봐서는 아직 프리뷰 버전이지만 정식 서비스에 가까워져 온 게 아닌가 싶어서 지금이라도 공부해볼까 합니다.
개념적인 부분은 다른 블로그에서 잘 쓰여있어 제가 참고한 링크만 남겨두겠습니다.
[Unity] 새로운 컴포넌트 시스템 ECS와 Entity
유니티가 새롭게 개발하고 있는 컴포넌트 시스템인 ECS가 기존의 컴포넌트 시스템과 무슨 차이가 있는지 알아보자.
velog.io
https://gyungmun.blogspot.com/2019/01/unity-ecs-ecs-jobs-system.html
[Unity-ECS] 엔티티 구성 요소 ECS 및 Jobs System
MetaMoni Framework
gyungmun.blogspot.com
https://github.com/KorStrix/Unity_Study_ECS
GitHub - KorStrix/Unity_Study_ECS: 유니티 ECS 공부 목적용 저장소입니다.
유니티 ECS 공부 목적용 저장소입니다. Contribute to KorStrix/Unity_Study_ECS development by creating an account on GitHub.
github.com
1. 설정
ref: https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/getting-started-installation.html
1.1 유니티 설치
https://unity.com/kr/releases/2022-2
Unity 2022.2 테크 스트림 | Unity
최신 릴리스인 Unity 2022.2 테크 스트림에서 최신 기능 및 개선된 플랫폼 최적화를 발견하세요.
unity.com
Entities 1.0 버전을 사용하기 위해서는 Unity 2022.2.0b8 이상이 필요합니다.
아마도 2022.2 버전부터 Entities를 위한 기능들이 추가되어서 그렇지 않을까 생각됩니다.
1.2 프로젝트 생성
프로젝트 생성 시 URP 혹은 HDRP 템플릿을 선택하시거나 아니면 나중에 패키지 설치 시에 추가해 주세요.
튜토리얼 중에 출력되지 않으면 Entities Graphic와 URP 혹은 HDRP를 설치하라는 노트가 있었는데, getting started에서 왜 소개를 안 해주는지 모르겠네요.
1.3 패키지 설치
Package Manager에서 Add package by name... 으로 설치합니다.
아래의 패키지명을 입력해주시면 됩니다.
1.4 도메인 리로드 설정
- Edit > Project Settings > Editor
- Play Mode Options 활성화
- Reload Domain, Reload Scene 비활성화
최적의 환경을 만들기 위해서는 비활성화하라고 나와있어서 진행했는데, 정확히 어떤 이유인지는 모르겠습니다.
일단 하라고 해서 진행하긴 했습니다.
2. 튜토리얼 따라 하기
ref: https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/ecs-workflow.html
Spawn Example을 통해서 이해해보려 했는데 적혀있는 설명을 완벽히 이해하기 힘들어서 우선은 그대로 따라 해 봤습니다.
2.1 Sub Scene 추가
씬에서 우클릭해서 New Sub Scene - Empty Scene 메뉴로 생성해 주었습니다.
Sub Scene이 만들어지면 해당 Scene을 저장해 줍니다.
2.2 Component Data 추가
using Unity.Entities;
using Unity.Mathematics;
public struct Spawner : IComponentData
{
public Entity Prefab;
public float3 SpawnPosition;
public float NextSpawnTime;
public float SpawnRate;
}
위 코드 그대로 만들어주었습니다.
IComponentData를 상속받은 것으로 보아 사용되는 데이터 덩어리 같습니다.
2.3 Authoring, Baker
using UnityEngine;
using Unity.Entities;
class SpawnerAuthoring : MonoBehaviour
{
public GameObject Prefab;
public float SpawnRate;
}
class SpawnerBaker : Baker<SpawnerAuthoring>
{
public override void Bake(SpawnerAuthoring authoring)
{
AddComponent(new Spawner
{
// By default, each authoring GameObject turns into an Entity.
// Given a GameObject (or authoring component), GetEntity looks up the resulting Entity.
Prefab = GetEntity(authoring.Prefab),
SpawnPosition = authoring.transform.position,
NextSpawnTime = 0.0f,
SpawnRate = authoring.SpawnRate
});
}
}
Authoring은 이름이 왜 Authoring인지는 모르겠지만 MonoBehaviour를 상속받아 Serialized Field를 Scene에 저장하기 위함이 아닌가 싶습니다. 실제로 Inspector View에서 보여지는 부분 같아요.
Baker는 위에서 만든 Authoring Behaviour에 Serialized Field를 불러와서 Component Data로 만들어주는 역할을 합니다.
새로 생성한 씬에서 새로운 GameObject를 만들어주고 Authoring 스크립트를 추가합니다.
그리고 스폰될 Prefab을 등록해주고 Spawn Rate를 조절해 줍니다.
2.4 Entities Hierarchy Window
상단 메뉴 Window - Entities - Hierarchy 메뉴를 통해 열 수 있습니다.
Entities Hierarchy를 열고 플레이를 하면 위 캡쳐처럼 나오게 됩니다.
저희가 추가한 GameObject가 나오고 아래는 곧 추가할 System들로 보입니다.
2.4 System 추가
using Unity.Entities;
using Unity.Transforms;
using Unity.Burst;
[BurstCompile]
public partial struct SpawnerSystem : ISystem
{
public void OnCreate(ref SystemState state) { }
public void OnDestroy(ref SystemState state) { }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// Queries for all Spawner components. Uses RefRW because this system wants
// to read from and write to the component. If the system only needed read-only
// access, it would use RefRO instead.
foreach (RefRW<Spawner> spawner in SystemAPI.Query<RefRW<Spawner>>())
{
ProcessSpawner(ref state, spawner);
}
}
private void ProcessSpawner(ref SystemState state, RefRW<Spawner> spawner)
{
// If the next spawn time has passed.
if (spawner.ValueRO.NextSpawnTime < SystemAPI.Time.ElapsedTime)
{
// Spawns a new entity and positions it at the spawner.
Entity newEntity = state.EntityManager.Instantiate(spawner.ValueRO.Prefab);
state.EntityManager.SetComponentData(newEntity, LocalTransform.FromPosition(spawner.ValueRO.SpawnPosition));
// Resets the next spawn time.
spawner.ValueRW.NextSpawnTime = (float)SystemAPI.Time.ElapsedTime + spawner.ValueRO.SpawnRate;
}
}
}
Authoring에서 설정해주고 Component Data로 저장된 SpawnRate값 주기로 Prefab를 Instantiate하는 코드입니다.
여기서는 정말 Entities에서만 쓰는 생소한 클래스, 함수들이 나와서 너무 당황스러운데, 이래서 진입장벽이 높은 게 아닐까 싶어요.
위 코드를 추가하고 SampleScene에서 실행해보면 무언가 생성되고 있는 것을 볼 수 있습니다.
2.5 System 최적화
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
using Unity.Burst;
[BurstCompile]
public partial struct OptimizedSpawnerSystem : ISystem
{
public void OnCreate(ref SystemState state) { }
public void OnDestroy(ref SystemState state) { }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
EntityCommandBuffer.ParallelWriter ecb = GetEntityCommandBuffer(ref state);
new ProcessSpawnerJob
{
ElapsedTime = SystemAPI.Time.ElapsedTime,
Ecb = ecb
}.ScheduleParallel();
}
private EntityCommandBuffer.ParallelWriter GetEntityCommandBuffer(ref SystemState state)
{
var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
return ecb.AsParallelWriter();
}
}
[BurstCompile]
public partial struct ProcessSpawnerJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter Ecb;
public double ElapsedTime;
private void Execute([ChunkIndexInQuery] int chunkIndex, ref Spawner spawner)
{
// If the next spawn time has passed.
if (spawner.NextSpawnTime < ElapsedTime)
{
// Spawns a new entity and positions it at the spawner.
Entity newEntity = Ecb.Instantiate(chunkIndex, spawner.Prefab);
Ecb.SetComponent(chunkIndex, newEntity, LocalTransform.FromPosition(spawner.SpawnPosition));
// Resets the next spawn time.
spawner.NextSpawnTime = (float)ElapsedTime + spawner.SpawnRate;
}
}
}
매뉴얼에서 예제를 하나 더 소개해주고 있는데, JobSystem을 이용하여 Instantiate 하는 코드입니다.
이전 System코드를 지우고 이 코드로 바꿔주면 실행됩니다.
위 코드와 마찬가지로 처음 보는 클래스와 함수들이 많아 공부해야 할 게 너무 많네요.
동작은 이전 코드와 동일한데 매뉴얼에서는 Profiler를 보면 Worker스레드에서 동작한다는데 아직은 비용이 적은 작업을 하고 있어서 찾을 수가 없네요.
Profiler를 통해 확인이 가능하다는 것만 기억해 둡시다.
마무리.
MonoBehaviour가 있으면 Baker클래스를 통해 Component Data로 bake 해서 System 클래스로 실행되는 것으로 보입니다.
초기 버전과 큰 차이는 기존 Component시스템을 버리지 않고 혼용하기로 한 것인데, 그 예로 위 프로젝트에서 SampleScene에서는 기존 Component시스템으로 동작하고 SubScene컴포넌트로 불러들이는 SubScene은 Bake 되어서 사용되는 것으로 보입니다.
대충 큰 틀에서 실행되는 구조는 이해한 것 같습니다.
다음에는 이런저런 코드를 추가해서 Unity의 기본 컴포넌트들부터 접근해보도록 하겠습니다. 예로 들면 재일 많이 사용되는 Transform부터요.
'Study Log' 카테고리의 다른 글
Unity ECS Study Log - 6 (0) | 2023.01.15 |
---|---|
Unity ECS Study Log - 5 (0) | 2023.01.14 |
Unity ECS Study Log - 4 (0) | 2023.01.09 |
Unity ECS Study Log - 3 (1) | 2023.01.07 |
Unity ECS Study Log - 2 (0) | 2023.01.01 |