본문 바로가기
TIL

[멋쟁이사자처럼 부트캠프 TIL 회고] 유니티 게임개발 3기 14일차 자료구조(Queue), 애니메이션 리타겟팅, 오브젝트 풀링

by HYttt 2024. 12. 6.

Queue

  • '선입선출' (First In First Out, FIFO) 원칙을 따르는 선형 자료구조. 가장 먼저 들어온 데이터가 가장 먼저 나가는 구조이다.
public class Node<T>
{
    public T data{get; private set;}
    public Node<T> next{get; set;}

    public Node(T data)
    {
        this.data = data;
        next = null;
    }
}
public class QueueEx<T>
{
    private Node<T> front;
    private Node<T> rear;
    private int size;
    
    public QueueEx()
    {
        front = null;
        rear = null;
        size = 0;
    }
    public void Enqueue(T data)
    {
        Node<T> newNode = new Node<T>(data);
        if (IsEmpty())
        {
            front = newNode;
            rear = newNode;
        }
        else
        {
            rear.next = newNode;
            rear = newNode;
        }
        size++;
    }
    public T Dequeue()
    {
        if (IsEmpty())
        {
            throw new InvalidOperationException("Queue is empty");
        }
        T data = front.data;
        front = front.next;
        size--;
        if (IsEmpty())
        {
            rear = null;
        }

        return data;
    }
    public T Peek()
    {
        if (IsEmpty())
        {
            throw new InvalidOperationException("큐가 비어있습니다.");
        }
        return front.data;
    }
    public bool IsEmpty()
    {
        return size == 0;
    }

    public int Size()
    {
        return size;
    }
}

 

Priority Queue

  • 큐의 각 요소가 우선순위를 가지고 있어 우선순위가 높은 요소가 먼저 처리되며 Heap을 이용해 구현한다.

Heap

  • 완전이진트리 형태의 자료구조. 여러 개의 값 중 최댓값 또는 최솟값을 찾아내는 연산이 빠르다.
  • 완전이진트리 형태로 이루어져 있고, 부모노드와 서브트리간 대소 관계가 성립된다. (반정렬 상태)
  • MaxHeap : 부모 노드의 값이 자식 노드의 값 보다 크거나 같음
  • MinHeap :  부모 노드의 값이 자식 노드의 값 보다 작거나 같음
public class PriorityQueue<T> where T : IComparable<T>
{
    private List<T> heap = new List<T>();

    public void Enqueue(T item)
    {
        heap.Add(item);
        int currentIndex = heap.Count - 1;
        HeapifyUp(currentIndex);
    }

    public T Dequeue()
    {
        if (heap.Count == 0)
        {
            throw new InvalidOperationException("Queue is empty");
        }
        T root = heap[0];
        int lastIndex = heap.Count - 1;
        heap[0] = heap[lastIndex];
        heap.RemoveAt(lastIndex);
        
        if(heap.Count > 0)
            HeapifyDown(0);
        
        return root;
    }

    private void HeapifyUp(int index)
    {
        while (index > 0)
        {
            int parentIndex = (index - 1) / 2;
            
            if (heap[index].CompareTo(heap[parentIndex]) >= 0)
                break;
            Swap(index, parentIndex);
            index = parentIndex;
        }
    }

    private void HeapifyDown(int index)
    {
        int lastIndex = heap.Count - 1;
        while (true)
        {
            int smallest = index;
            int leftChild = index * 2 + 1;
            int rightChild = index * 2 + 2;
            
            if(leftChild <= lastIndex && heap[leftChild].CompareTo(heap[smallest]) < 0)
                smallest = leftChild;
            if(rightChild <= lastIndex && heap[rightChild].CompareTo(heap[smallest]) < 0)
                smallest = rightChild;
            if(smallest == index)
                break;
            Swap(index, smallest);
            index = smallest;
        }
    }

    private void Swap(int i, int j)
    { 
        T temp = heap[i];
        heap[i] = heap[j];
        heap[j] = temp;
    }

    public int Count => heap.Count;
    public bool IsEmpty => heap.Count == 0;
}

 

Max Heap을 이용한 오브젝트 풀링

  • 유니티에서 오브젝트를 생성하기 위해서는 Instantiate를, 삭제할 때는 Destroy를 사용한다.
  • Instantiate(오브젝트 생성)은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고, Destroy(오브젝트 파괴)는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있어서 총알과 같이 자주 생성되고 삭제되는 오브젝트들이 있을 경우 치명적일 수 있다.
  • 오브젝트 풀링은 자주 사용하는 오브젝트를 미리 생성해 놓고 이걸 사용할 때마다 새로 생성 삭제 하는 것 이 아닌 사용할 때는 오브젝트풀한테 빌려서 사용하고 삭제할 때는 오브젝트풀한테 돌려줌으로써 단순하게 오브젝트를 활성화 비활성화 만 하도록 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Collections.Generic;

public class ObjectPool : MonoBehaviour
{
    public GameObject prefab;
    public int poolSize = 10;
    public int createCount;

    private Queue<GameObject> objectPool = new Queue<GameObject>();
    public List<GameObject> objects = new List<GameObject>();

    void Start()
    {
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            objectPool.Enqueue(obj);
        }
    }

    public GameObject GetPooledObject()
    {
        if (objectPool.Count > 0)
        {
            GameObject obj = objectPool.Dequeue();
            obj.SetActive(true);
            return obj;
        }
        else
        {
            for (int i = 0; i < poolSize; i++)
            {
                GameObject obj = Instantiate(prefab);
                obj.SetActive(false);
                objectPool.Enqueue(obj);
            }

            return objectPool.Dequeue();
        }
        return null;
    }

    public void ReturnToPool(GameObject obj)
    {
        obj.SetActive(false);
        objectPool.Enqueue(obj);
    }
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            for (int i = 0; i < createCount; i++)
            {
                float x = Random.Range(-100, 100);
                float y = Random.Range(-100, 100);
                float z = Random.Range(-100, 100);

                var go = GetPooledObject();
                go.transform.position = new Vector3(x, y, z);
                objects.Add(go);
            }
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            for (var i = 0; i < objects.Count; i++)
            {
                ReturnToPool(objects[i]);
            }
            
            objects.Clear();
        }
    }
}

애니메이션 리타겟팅

  • 동일한 애니메이션 세트를 다양한 캐릭터 모델에 비교적 편리하게 적용할 수 있게 해준다.
  • 리타게팅은 모델의 골격 구조 사이에 연관성을 제공하기 때문에 아바타가 설정된 휴머노이드 모델에만 적용 가능하다.

(1) 리타켓팅 할 곳의 Rig > Animation Type을 Humanoid로 변경 후 적용

(2) AnimatorController 생성 후 적용할 애니메이션을 복제하여 넣기

(3) AnimatorController 연결