[업데이트 예정]Unity 토이 프로젝트 찍먹 - 생성 그리고 파괴

Changhoony·2022년 11월 18일
0

Unity_Toy_Project

목록 보기
3/4

* 목표

* 아주 기본적인 최적화 방법 중 하나인 오브젝트 풀링을 사용할 수 있다.
* ECS에서 생성과 파괴의 방법을 다룰 수 있다.

* 방법

* 기존 방법 : Queue나 Stack 중 하나 그리고 List를 활용한다.
* ECS 방법 : EntityCommandBuffer 또는 EntityManager를 활용한다.

* 내용

1. 기존 방법

1.1 준비

// 생성 오브젝트 프리팹
public GameObject prefab;
// 최대 개수
public int createdCount; 
// 자동 관리가 시작되는 개수.
public int managedCount;
// 꺼내기 전, 오브젝트를 담아둔 큐 -> 새로 꺼낼 때 활용.
public Queue<GameObject> prefabQueue;
// 꺼낸 후, 오브젝트를 관리하는 리스트 -> 다시 넣을 때 활용.
public List<GameObject> prefabList;

1.2 생성

private void Start()
{
	CreatePool();
}

private void CreatePool()
{
    prefabQueue = new Queue<GameObject>();
    prefabList = new List<GameObject>();
    
	for(int i = 0; i < createdCount; i ++)
    {
       // 생성
   	   var item = Instantiate(prefab);
       
       // 필요한 설정
       ...
       item.name = $"Recycle_{i}";
       item.transform.localscale = Vector3.one;
       ...
       
       // 끄고 큐에 넣으면 준비 끝
       item.SetActive(false);
       prefabQueue.Enqueue(item);
    }
}

1.3 대여 및 반납

/// 대여

public GameObject Dequeue()
{
	// 자동 관리 시점이 되면 동작 한다.
	if(prefabList.Count >= managedCount)
    {
       // 가장 먼저 만들었던 오래된 녀석을 가져온다.
       var readyMadeItem = prefabList.First();
       // 해당 오브젝트를 끄고 넣는다.
       readyMadeItem.SetActive(false);
       prefabQueue.Enqueue(readyMadeItem);
    }
    
    // 새로 쓸 오브젝트를, 꺼낸 후 리스트에 집어넣는다. ( * 여기서 켜지 않는다. )
	var item = prefabQueue.Dequeue();
   prefabList.Add(item);
   reutrun item;
}

--------------- 이하 외부 스크립트 ------------------

public class Gun : MonoBehaviour
{
	private void Update()
    {
   	    if(Input.GetKeyDown(KeyCode.Space)
       	{
            // 필요할 때 꺼내서
            var item = PoolManager.Instance.Dequeue();   
            // 꺼낸 오브젝트의 트랜스폼 값을 초기화 해준 뒤
            item.transform.localscale = Vector3.one;
            item.transform.localPosition = Vector3.zero;
            ...
            // 여기서 오브젝트를 활성화 한다.
            item.SetActive(true);
        }
    }
}
/// 반납

public void Enqueue(GameObject go)
{
   // 끄고 큐에 먼저 집어넣는다.
	go.SetActive(false);
	prefabQueue.Enqueue(go);
   
   // 마지막에 리스트에서 삭제한다.
	prefabList.Remove(go);
}

----------------- 이하 외부 스크립트 ------------------
 public class Bullet : MonoBehaviour
{
	[SerializedField] private float lifeTime;
   [SerializedField] private float maxLifeTime;
   [SerializedField] private float bulletSpeed;
   
   private void OnEnable()
   {
   	  lifeTime = maxLifeTime;
   }
	
   // 움직이는 부분
   private void Update()
   {
   
     // 반납 부분
      if(lifeTime <= 0)
        PoolManager.Instance.Enqueue(gameObject);
        
      lifeTime -= Time.deltaTime;         
      transform.Translate(transform.foward * Time.deltaTime * bulletSpeed);
    }
}

2. ECS 방법

2.1 준비

// 1. 스폰 대상 태그, 태그는 비워 두어도 됨.
public struct BallTag : IComponentData { } 

------ 이하 별도의 하나의 스크립트 (예시 기준 : BallTagAuthoring.cs) 에 쓰기 -------

public class BallTagAuthoring : MonoBehaviour { }
public class BallTagBaker : Bake<BallTagAuthoring>
{
	AddComponent(new BallTag());
}
// 2. 스폰 정보
public struct SpawnerProperties : IComponentData
{
	public Entity Prefab;
    public int CreatedCount;
}

------------------- 이하 별도의 하나의 스크립트(예시 기준 : SpawnerPropertiesAuthoring.cs)에 쓰기 -----------------------------
public class SpawnerPropertiesAuthoring : MonoBehaviour
{
	public GameObject prefab;
   public int createdCount;
}
public class SpawnerPropertiesBaker : Baker<SpawnerPropertiesAuthoring>
{
	public override void Bake(SpawnerPropertiesAuthoring authoring)
   {
   		AddComponent(new SpanwerProperties()
        {
        		Prefab = GetEntity(authoring.prefab),
               CreatedCount = authoring.createdCount
        });
   }
}

2.2 생성

/// 반드시 partial struct 두개를 모두 써야 함. 
[BurstCompile]
public partial struct SpanwerSystem : ISystem
{
   public void OnCreate(ref SystemState state)
   {
       
   }

   public void OnDestroy(ref SystemState state)
   {
       
   }
   
   [BurstCompile]
   public void OnUpdate(ref SystemState state)
   {
   	   // state.EntitiyManaer.CreateQuery를 사용할 경우 에러 발생 !
       // 생성할 볼 들을 관리할 수 있는 쿼리 작성    
       var query = SystemAPI.QueryBuilder().WithAll<BallTag>().Build();
       
       // 생성 버퍼, EntitiyManager.Instantiate와의 차이는 메인스레드에서 작동 함.
       var ecb = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged);
       
       var spawnData = SystemAPI.GetSingleton<BallSpawnProperties>();
       if (query.CalculateEntityCount() < spawnData.MaxCount)
       {
           var entitiy = ecb.Instantiate(spawnData.Prefab);
           
           // 기존 오브젝트에 설정된 SpeedAuthoring이 있고, 값이 설정 되었다 하더라도 덮어 쓸 수 있음.
           // ecb.SetComponent(entity, new Speed(){ Value = 5 }); 
       }
   }
}

2.3 사용 및 반납

/// 사용 
/// 다양한 사용 방법이 있지만, 그냥 적당히 움직이다가 사라지는 방식을 적음.
/// 이동 시스템이 작성 되었다고 가정함, 이전 포스팅을 기준으로 작성하였기 때문에 여기서는 별도로 작성하지 않겠음.

/// 이하 이동 관련 Aspect class 
public readonly partial struct BallAspect : IAspect
{
	// 파괴하기 위해 작성
   private readonly Entity _entity;
   // 볼의 속도, 활동 시간 등을 적어 둔 종합 프로퍼티들
   private readonly RefRW<BallProperties> _properties;
   
   // 외부에서 접근하기 위한 엔티티 프로퍼티
   public Entity Entitiy => _entitiy;
   
   // 실제 움직이는 로직
	public void Move(float deltaTime)
    { 
    	.. 구현되어 있음.. 
    }
     
    // 활동 시간 업데이트 로직      
    public float UpdateLifeTime(float deltaTime)
    {
       return _properties.ValueRW.lifeTime -= deltaTime;
    }
}

/// 이하 이동 관련 ISystem

public partial struct BallMovingSystem : ISystem
{
   public void OnCreate(ref SystemState state)
   {
       
   }

   public void OnDestroy(ref SystemState state)
   {
       
   }
   
   [BurstCompile]
   public void OnUpdate(ref SystemState state)
   {
      // 시간 변화량
      var deltaTime = SystemAPI.Time.deltaTime;
       
      // EntityCommandBuffer 설정 - 엔티티 파괴용.
      var ecb = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged);
      
      // 볼이 움직임 JobSystem 구현 부분
      var ballMovingHandle = new BallMovingJob
      {
         deltaTime = deltaTime,
         ecb = ecb
           
      }.Schedule(state.Dependency);
   }
}

public struct BallMovingJob : IJobEntity
{
   public float deltaTime;
   public EntityCommandBuffer ecb;
   public void Excute(BallAspect ball)
   {
   		// 움직이는 로직 실행
        if(ball.Entity != Entity.Null)
   	       ball.Move(delta);

		// 활동시간 체크, 시간 만료되면 파괴
        if(ball.UpdateLifeTime(detaTime) <= 0)
           ecb.DestroyEntity(ball.Entity);
   }
}

결과 ▼

위의 스크립트와 다른 내용입니다. 하지만 파란색 총알이 3초뒤에 사라지는 부분은 똑같습니다.

profile
Unity 개발

0개의 댓글