2023.01.29 - [유니티/DOTS] - DOTS 1.0 - 기본 (Component,System,Aspect,Job)
Webp사용
이번에는 ECS에서 Entity를 생성해보고, 기존 MonoBehvaiour코드에서 Entity의 데이터에 접근하도록 하는 걸 해보자.
ECS Instantiate
우선 Spawner 역할을 할 컴포넌트를 만든다. 간단한게 prefab과 몇 개를 만들지를 지정해준다.
public struct PlayerSpawnerComponent : IComponentData
{
public Entity Prefab;
public int SpawnAmount;
}
public class PlayerSpawnerAuthoring : MonoBehaviour
{
public GameObject prefab;
public int spawnAmount;
}
public class PlayerSpawnerBaker : Baker<PlayerSpawnerAuthoring>
{
public override void Bake(PlayerSpawnerAuthoring authoring)
{
AddComponent(new PlayerSpawnerComponent()
{
Prefab = GetEntity(authoring.prefab),
SpawnAmount = authoring.spawnAmount,
});
}
}
그 다음에 지금 생성된 엔티티들 중 Player인 엔티티들을 파악할 필요가 있는데, 이를 위해서 빈 컴포넌트인 PlayerTag를 추가해줄 수 있다.
public struct PlayerTag : IComponentData
{
}
public class PlayerTagAuthoring : MonoBehaviour
{
}
public class PlayerTagBaker : Baker<PlayerTagAuthoring>
{
public override void Bake(PlayerTagAuthoring authoring)
{
AddComponent(new PlayerTag());
}
}
이제 만든 Prefab에 PlayerTagAuthoring을 추가해주면 끝난다. PlayerTag를 Query하는 것 만으로 Player 엔티티만 모아올 수 있다.
이제 SystemBase를 상속한 시스템에서 prefab을 Instantiate해주면 된다.
그런데 주의할 점이 있는 게, 엔티티 리스트를 얻어오고 이걸 시스템 내에서 사용하게 될 탠데, 시스템 내에서 엔티티 리스트에 엔티티를 추가하거나 제거한다면? 컬렉션 반복 중에 컬렉션 목록을 수정하는 것과 똑같은 오류가 발생할 것이다.
따라서 이런 건 다른 시스템들이 수행되기 전이나 후에 실행되어야 한다.
시스템 목록을 보면 Begin Simulation Entity Command Buffer System이 있는데 이 시스템은 다른 시스템이 돌아가기 전에 호출되는 시스템이다.
여기서 커맨드 버퍼란 엔티티 리스트의 수정을 위해 추가나 제거 명령을 모아두는 버퍼이다.
아무튼 이 시스템을 이용해서 생성 시스템을 만든다.
public partial class PlayerSpawnSystem : SystemBase
{
protected override void OnUpdate()
{
var query = EntityManager.CreateEntityQuery(typeof(PlayerTag));
var playerSpawnerComponent = SystemAPI.GetSingleton<PlayerSpawnerComponent>();
var spawnAmount = playerSpawnerComponent.SpawnAmount;
if (query.CalculateEntityCount() >= spawnAmount) return;
var randomComponent = SystemAPI.GetSingletonRW<RandomComponent>();
var entityCommandBuffer = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>()
.CreateCommandBuffer(World.Unmanaged);
var newEntity = entityCommandBuffer.Instantiate(playerSpawnerComponent.Prefab);
entityCommandBuffer.SetComponent(newEntity,
new Speed(){Value = randomComponent.ValueRW.Random.NextFloat(1f,5f)});
}
}
그럼 끝.
MonoBehaviour에서 Entity 읽기
마우스 클릭으로 캡슐 오브젝트를 클릭하면 아래와 같이 선택되었다는 느낌을 주는 오브젝트를 추가하고 싶다고 하자.
대충 동그란 띠 모양의 오브젝트가 있는 프리팹을 만들고
public class SelectedObjectVisual : MonoBehaviour
{
public GameObject Circle;
public float Distance;
private Entity _targetEntity;
private Transform _transform; //Transform 캐싱
private void Awake()
{
_transform = GetComponent<Transform>();
}
private void LateUpdate()
{
//엔티티 위치를 얻어 업데이트 해준다.
}
private Entity SelectEntity(Vector3 mousePosition)
{
//마우스 클릭 위치로 엔티티를 선택한다.
}
}
위와 같은 스크립트를 일단 추가해주자.
circle은 실제 초록 동그라미 오브젝트로, 선택한 개체가 없을 경우 on/off해주기 위해 참조를 받는다.
distance는 선택 기준이 되는 거리
targetEntity는 현재 오브젝트가 따라다닐 entity를 말한다.
우선 Entity를 얻어오려면 EntityManager를 알아야 하는데 ECS에서는 World라는 개념이 있어서 World마다 EntityManager가 존재할 수 있다. 우리가 원하는 건 DefaultGameObjectInjectionWorld라는 곳에 있다.
아무튼 다음과 같이 마우스 클릭 지점으로부터 거리가 Distance 이내인 가장 가까운 엔티티를 선택하는 메서드를 구현한다.
private Entity SelectEntity(Vector3 mousePosition)
{
if(MainCamera.Instance == null) return Entity.Null;
var mainCam = MainCamera.Instance.Camera;
var ray = mainCam.ScreenPointToRay(mousePosition);
if (!Physics.Raycast(ray, out RaycastHit hit, 50f, LayerMask.GetMask("Ground"))) return Entity.Null;
var planePos = hit.point;
var query = World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntityQuery(typeof(PlayerTag));
Entity closestEntity = Entity.Null;
var minSqrDist = distance * distance;
foreach (var entity in query.ToEntityArray(Unity.Collections.Allocator.Temp))
{
var entityPos = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<LocalTransform>(entity).Position;
var dist = (planePos - (Vector3)entityPos).sqrMagnitude;
if (dist < minSqrDist)
{
minSqrDist = dist;
closestEntity = entity;
}
}
return closestEntity;
}
그리고 LateUpdate에 위치를 업데이트 해준다.
private void LateUpdate()
{
if (Input.GetMouseButtonUp(0))
{
var mousePosition = Input.mousePosition;
_targetEntity = SelectEntity(mousePosition);
}
if (_targetEntity == Entity.Null)
{
cursor.SetActive(false);
return;
}
cursor.SetActive(true);
Vector3 targetPosition = World.DefaultGameObjectInjectionWorld.EntityManager
.GetComponentData<LocalTransform>(_targetEntity).Position;
_transform.position = targetPosition;
}
여태껏 구현한 내용을 실행해주면 다음과 같다.
'게임엔진 > DOTS' 카테고리의 다른 글
Unity.Physics에 가속도와 힘 구현해보기 - 1 (1) | 2023.02.18 |
---|---|
Physics 1.0 써보기 (1) | 2023.02.10 |
DOTS 1.0 - 기본 (Component,System,Aspect,Job) (0) | 2023.01.29 |
DOTS 1.0 나온 기념 세팅법 (2) | 2023.01.01 |
일단 시작3 - Instantiate & Destory (0) | 2022.03.23 |