Step 4 - 대포알
다른 엔터티의 엔터티를 참조하는 프리팹을 만드는 중입니다.
- "Scripts/Components" 폴더에 "CannonBall.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
-
using Unity.Entities; using Unity.Mathematics; // 대포알에 대한 동일한 접근 방식으로, 우리는 엔티티를 식별하기 위한 구성요소를 만들고 있습니다. // 그러나 이번에는 속도 필드라는 데이터가 포함되어 있기 때문에 태그 컴퍼넌트(비어 있는 컴퍼넌트)가 아닙니다. // 그것은 즉시 사용되지는 않겠지만, 우리가 모션을 구현할 때 관련성이 있을 것입니다. struct CannonBall : IComponentData { public float3 Speed; }
- "Scripts/Authoring" 폴더에 "CannonBallAuthoring.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
-
using Unity.Entities; using Unity.Rendering; class CannonBallAuthoring : UnityEngine.MonoBehaviour { } class CannonBallBaker : Baker<CannonBallAuthoring> { public override void Bake(CannonBallAuthoring authoring) { // 기본적으로 컴퍼넌트들은 0으로 초기화됩니다. // 따라서 이 경우 대포알의 스피드 필드는 float3.zero가 됩니다. AddComponent<CannonBall>(); } }
- Hierarchy 창에서 "SampleScene"을 마우스 오른쪽 버튼으로 클릭하고 GameObject > 3D Object > Sphere를 선택한 후 새로운 GameObject의 이름을 "CannonBall"로 지정합니다. Position을 (0,0,0), Rotation을 (0,0,0), Scale을 (0.2,0.2,0.2)로 설정합니다.
- "CannonBallAuthoring" 컴퍼넌트를 "CannonBall" GameObject에 추가합니다.
- "CannonBall" 게임 오브젝트에서 "Sphere Collider" 컴퍼넌트를 제거합니다.
- "CannonBall" GameObject를 Project 창의 "Prefabs" 폴더로 드래그 앤 드롭합니다.
- "SampleScene"에서 "CannonBall" GameObject(현재는 프리팹 인스턴스)를 삭제합니다.
- 다음과 같이 "Scripts/Components" 폴더에 있는 "Turret.cs" 파일의 내용을 수정합니다.
-
using Unity.Entities; struct Turret : IComponentData { + // 이 엔티티는 대포알이 생성되어야 하는 대포의 노즐을 참조할 것입니다. + public Entity CannonBallSpawn; + // 이 엔티티는 대포알이 발사될 때마다 생성될 프리팹을 참조할 것입니다. + public Entity CannonBallPrefab; }
- "Scripts/Authoring" 폴더에 있는 "TurretAuthoring.cs" 파일의 내용을 다음과 같이 수정합니다.
-
using Unity.Entities; class TurretAuthoring : UnityEngine.MonoBehaviour { + public UnityEngine.GameObject CannonBallPrefab; + public UnityEngine.Transform CannonBallSpawn; } class TurretBaker : Baker<TurretAuthoring> { public override void Bake(TurretAuthoring authoring) { - AddComponent<Turret>(); + AddComponent(new Turret + { + // 기본적으로 각 GameObject 작성은 엔티티로 바뀝니다. + // GameObject(또는 컴퍼넌트 작성)가 지정되면 GetEntity는 결과 엔티티를 검색합니다. + CannonBallPrefab = GetEntity(authoring.CannonBallPrefab), + CannonBallSpawn = GetEntity(authoring.CannonBallSpawn) + }); } }
- "Turret" GameObject를 선택하고, "Turret Authoring" 컴퍼넌트 요소의 새 필드 "CannonBallPrefab"과 "CannonBallSpawn"을 각각 Project 폴더의 "CannonBall" 프리팹(드래그 & 드롭)과 "SpawnPoint" GameObject(Hierarchy 창에서 드래그 & 드롭)로 설정합니다.
- "Scripts/Aspects" 폴더에 "TurretAspect.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
-
using Unity.Entities; using Unity.Mathematics; using Unity.Rendering; // Turret component에 직접 접근하는 것이 아니라 외관(Aspect)을 만들고 있습니다. // 외관(Aspect)을 사용하면 구성 요소에 액세스하기 위한 사용자 지정 API를 제공할 수 있습니다. readonly partial struct TurretAspect : IAspect { // 이 참조는 Turret 구성 요소에 대한 읽기 전용 액세스를 제공합니다. // 읽기 전용 참조에서 ValueRW(ValueRO 대신)를 사용하려고 하면 오류가 발생합니다. readonly RefRO<Turret> m_Turret; // 다음 속성들의 ValueRo값의 사용에 유의하십시오. public Entity CannonBallSpawn => m_Turret.ValueRO.CannonBallSpawn; public Entity CannonBallPrefab => m_Turret.ValueRO.CannonBallPrefab; }
- 📝 NOTE
다음 단계에서는 타입이 지정된 구성 요소에 임의로 접근할 수 있는 ComponentDataFromEntity<T>를 사용합니다. 이 기능에 대한 자세한 내용은 API Documentation를 참조하십시오. -
using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Rendering; using Unity.Transforms; [BurstCompile] partial struct TurretShootingSystem : ISystem { // ComponentLookup은 컴퍼넌트(엔티티 검색)에 대한 임의 접근을 제공합니다. // 우리는 이것을 사용하여 스폰 포인트(대포 노즐)의 월드 공간 좌표와 방향을 추출할 것입니다. ComponentLookup<LocalToWorldTransform> m_LocalToWorldTransformFromEntity; [BurstCompile] public void OnCreate(ref SystemState state) { // ComponentLookup 구조체는 한 번 초기화해야 합니다. // 매개 변수는 검색들을 읽기 전용으로 할지 또는 쓰기를 허용할지 여부를 지정합니다. m_LocalToWorldTransformFromEntity = state.GetComponentLookup<LocalToWorldTransform>(true); } [BurstCompile] public void OnDestroy(ref SystemState state) { } [BurstCompile] public void OnUpdate(ref SystemState state) { // ComponentLookup 구조체들은 모든 프레임마다 업데이트 되어야 합니다. m_LocalToWorldTransformFromEntity.Update(ref state); // 인스턴스화에 필요한 구조 변경을 지연시키기 위해 EntityCommandBuffer를 제작합니다. var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>(); var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged); // Job의 인스턴스를 만드는 중입니다. // 그것을 통과하면서 ComponentLookup은 스폰 포인트의 월드 트랜스폼을 요구합니다. // 또한 Job이 쓸 수 있는 엔티티 명령 버퍼도 있습니다. var turretShootJob = new TurretShoot { LocalToWorldTransformFromEntity = m_LocalToWorldTransformFromEntity, ECB = ecb }; // 싱글 스레드에서 실행을 예약하고 메인 스레드를 차단하지 않습니다. turretShootJob.Schedule(); } } [BurstCompile] partial struct TurretShoot : IJobEntity { [ReadOnly] public ComponentLookup<LocalToWorldTransform> LocalToWorldTransformFromEntity; public EntityCommandBuffer ECB; // TurretAspects 매개변수는 읽기 전용으로 선언하는 "in"입니다. // 이 경우 "ref"(읽기-쓰기)로 만들면 차이가 없지만 // 잠재적으로 경합 상태(Race conditions)가 안전 시스템(Safety System)을 건드는 상황에 직면하게 됩니다. // 그래서 일반적으로 가능한 모든 곳에서 "in"을 사용하는 것이 좋은 원칙입니다. void Execute(in TurretAspect turret) { var instance = ECB.Instantiate(turret.CannonBallPrefab); var spawnLocalToWorld = LocalToWorldTransformFromEntity[turret.CannonBallSpawn]; var cannonBallTransform = UniformScaleTransform.FromPosition(spawnLocalToWorld.Value.Position); // 새 인스턴스의 트랜스폼을 덮어쓰려고 합니다. // 비율을 명시적으로 복사하지 않으면 1로 재설정되고 크기가 너무 큰 캐논 볼을 갖게 됩니다. cannonBallTransform.Scale = LocalToWorldTransformFromEntity[turret.CannonBallPrefab].Value.Scale; ECB.SetComponent(instance, new LocalToWorldTransform { Value = cannonBallTransform }); ECB.SetComponent(instance, new CannonBall { Speed = spawnLocalToWorld.Value.Forward() * 20.0f }); } }
- 플레이 모드로 들어가면 탱크 뒤에 대포알의 흔적이 남아 있는 것을 볼 수 있습니다.
- 참고