내일배움캠프 23일차 TIL : 오브젝트 풀링 학습, 유니티 deltaTime 등 질답정리

woollim·2024년 10월 24일
2

내일배움캠프TIL

목록 보기
22/36
post-thumbnail

■ 학습 개요

오늘 계획

  • 유니티 숙련 다시 듣기 10강~17강
  • 유니티 꾸준실습 풀기 Q3(구현1~3)
  • 오브젝트 풀링 학습
  • 알고리즘 풀기

학습 회고

  • 숙련 강의를 들으며 궁금 했던 점들을 질문하고 왔다.
  • 어제 까지 붙잡고 있던 오브젝트 풀링이 드디어 이해가 됐다.
  • 게임 수학 특강을 들었는데, 기벡이 잘 기억이 안나서 다시 복습해야 한다.
  • 알고리즘 문제는 하루 한시간씩 꾸준히 투자하기로 했다. 아직 정렬, 탐색 등 알고리즘 개념이 부족하니 학습이 필요하다.


■ 질답 정리

Q1. fixedUpdate와 Update 차이

  • Update : 매 프레임마다 호출되며, 프레임 속도에 따라 호출 빈도가 변동될 수 있음. 즉, 게임 성능이 저하되면 호출 주기가 느려질 수 있음 주로 입력 처리나 코루틴 작업 등에 사용되며, 로직 부하가 큰 경우 호출 간격이 유동적일 수 있음
  • fixedUpdate : 고정된 시간 간격으로 호출됨. 물리 연산(예: 충돌 감지)을 처리하는 데 주로 사용됨. 호출 주기가 물리 연산 주기와 일치하지 않으면 충돌이 발생하지 않을 수 있기 때문에, 콜라이더를 충분히 두껍게 설정하여 충돌 가능성을 높이는 것이 중요함.
  • 참고링크

Q2. 유니티에서 쓰레딩 가능 여부

  • 유니티에서 쓰레딩은 여러 방식으로 가능함
    - 코루틴 : 유니티에서 제공하는 기능, 프레임 기반 작업 관리로, 메인 스레드를 점유하며 비동기적으로 작업을 처리함. (자세히보면 비동기적 동기!)
  • Job 시스템 : 유니티에서 제공하는 기능, 유니티의 가상 CPU 코어에서 작업을 처리함. 병렬 처리가 가능해 성능을 향상시킬 수 있음.
  • Task/UniTask (async/await) : C# 비동기 처리 방법을 활용하여 백그라운드 스레드에서 데이터만 처리할 수 있음. 이때, 데이터 전달을 위해 큐(Queue)를 사용하여 작업을 메인 스레드로 전달할 수 있음. 메인 스레드 디스패처는 큐를 통해 데이터를 넣고 빼며 처리하는 역할을 함.

Q3. Time.deltaTime이란?

  • Time.deltaTime은 프레임 간의 시간 차이를 나타내며, CPU 성능과 클럭 속도에 맞춰 1초를 나눔.
  • 예를 들어, 60프레임일 때는 한 프레임의 시간이 약 0.016초가 됨(1/60 = 0.016). 이는 물리 연산이나 애니메이션에서 프레임 속도에 상관없이 일정한 속도로 움직이게 하는 데 사용됨

Q4. 왜 캐릭터 매니저를 사용하나요?

  • 캐릭터 매니저는 게임 내 캐릭터의 데이터를 관리하기 위해 사용됨. 예를 들어, 캐릭터의 장비나 이름, ID 같은 게임의 유지 데이터를 저장하는 용도로 쓰임.
  • 플레이어는 공유 컨테이너로, 각 클래스에서 접근할 수 있음
  • 하지만 자주 메모리에서 접근하면 성능에 부정적 영향을 줄 수 있기 때문에, 메모리 사용을 줄이면서 효율적으로 관리하기 위해 캐릭터 매니저를 사용하는 것이 좋음


■ 오브젝트 풀링 정리

○ 주요 개념

  • 메모리 할당 최소화 : 오브젝트를 매번 생성하고 파괴하면 메모리 할당과 해제가 빈번하게 발생하여 성능이 저하 됨. 오브젝트 풀링은 이러한 문제를 해결하기 위해 미리 필요한 수의 오브젝트를 생성해둠

  • 재사용(Reusing) : 한 번 생성된 오브젝트는 풀 안에서 재사용됨. 새 오브젝트를 만들지 않고, 사용이 끝난 오브젝트를 다시 활용하므로 성능을 크게 향상시킴

  • 풀 관리 : 풀에서 오브젝트를 필요로 할 때는 사용 가능한 오브젝트를 꺼내 사용하고, 사용이 끝나면 다시 풀에 반환함. 이 과정을 통해 효율적인 메모리 관리를 유지할 수 있음

○ 장점

  • 성능 향상 : 매번 오브젝트를 새로 생성하는 것보다 빠르며, 특히 자주 반복적으로 생성되는 오브젝트에 효과적
  • 메모리 안정성 : 메모리 누수와 과도한 가비지 컬렉션을 방지함
  • 부하 감소 : CPU 부하를 줄여 애플리케이션의 성능을 최적화함

○ 단점

  • 초기 메모리 사용량 : 처음에 많은 오브젝트를 생성해야 하므로 초기 메모리 사용량이 높을 수 있음
  • 복잡한 관리 : 풀링된 오브젝트가 너무 많아지면 이를 효율적으로 관리하기 어려울 수 있음

○ 오브젝트 풀 자료구조

  • 유니티의 오브젝트 풀링에서 사용하는 자료구조는 특정한 요구 사항에 맞춰 선택할 수 있으며, 특정 자료구조로 고정되어 있지는 않음

  • 리스트(List) : 간단하고 직관적인 자료구조로, 오브젝트를 순차적으로 관리하기에 좋음. 삽입과 제거가 빠르지 않지만, 적은 수의 오브젝트를 관리할 때는 적합함

  • 큐(Queue) : 선입선출(FIFO) 방식으로 오브젝트를 재사용할 때 유용함. 오브젝트가 필요할 때 앞에서 꺼내고, 사용이 끝난 오브젝트는 다시 뒤로 넣는 방식

  • 스택(Stack) : 후입선출(LIFO) 방식으로 사용됨. 가장 최근에 사용한 오브젝트를 다시 사용하고 싶은 경우 적합

  • 딕셔너리(Dictionary) or 해시맵(HashMap) : 특정 키를 기준으로 오브젝트에 접근해야 할 때 유용함. 오브젝트 풀링에서 오브젝트의 종류나 상태에 따라 구분해야 할 때 유용할 수 있음


○ 일반적으로 사용하는 형태

  • Object Type (오브젝트 타입)

    • 풀링할 오브젝트의 타입을 지정
    • 예를 들어, 미사일, 적 캐릭터, 총알 등 특정 오브젝트의 유형을 미리 정의할 수 있음
  • Initial Pool Size (초기 풀 크기)

    • 풀에 미리 생성해둘 오브젝트의 수를 지정함
    • 이 값은 애플리케이션의 성능 요구에 따라 달라질 수 있음. 너무 적으면 새로운 오브젝트를 만들어야 할 수 있고, 너무 많으면 메모리 낭비가 발생할 수 있음
  • Max Pool Size (최대 풀 크기)

    • 풀링된 오브젝트의 최대 수를 지정함
    • 풀의 크기가 너무 커지는 것을 방지하기 위해 설정하는 값
    • 풀의 크기가 이 값을 초과하면 새로운 오브젝트가 생성되지 않고 기존 오브젝트를 재사용함
  • Auto Expand (자동 확장)

    • 초기 풀 크기가 부족할 경우, 자동으로 풀의 크기를 확장할지 여부를 설정함
    • 필요에 따라 오브젝트를 더 생성할 수 있도록 설정할 수 있음
  • Recycle Objects (오브젝트 재활용)

    • 사용된 오브젝트를 풀에 반환할 때 어떤 방식으로 재활용할지를 정의함
    • 일반적으로 오브젝트의 상태를 초기화한 후 풀에 다시 넣음
  • Factory Method (팩토리 메소드)

    • 오브젝트를 생성할 때 사용하는 메소드
    • 특정한 초기화가 필요한 경우 이 메소드를 통해 오브젝트를 생성하고 풀에 넣을 수 있음

○ IObjectPool 인터페이스

  • Unity에서 제공하는 오브젝트 풀링 인터페이스

  • 오브젝트 풀을 관리하는 데 필요한 기본적인 기능들을 정의한 인터페이스

  • 이를 통해 오브젝트 풀링 시스템을 구현하거나 커스터마이징할 수 있음

  • 주요 기능은 풀의 오브젝트를 요청하고 반환하는 과정에 대한 규칙을 설정하는 것

  • IObjectPool 인터페이스는 풀링 시스템을 표준화하여 다양한 종류의 오브젝트 풀링에 동일한 방식으로 접근할 수 있게 해줌

  • Get()

    • 풀에서 오브젝트를 요청할 때 사용하는 메서드
    • 오브젝트 풀에서 사용 가능한 오브젝트를 반환하며, 사용 중인 오브젝트가 없을 경우 새로운 오브젝트를 생성하거나, null을 반환할 수 있음
  • Release(T element)

    • 사용한 오브젝트를 다시 풀에 반환할 때 사용하는 메서드
    • 이 메서드는 오브젝트의 상태를 초기화하고, 다음에 다시 사용할 수 있도록 풀에 저장하는 역할을 함
  • Clear()

    • 풀 안에 있는 모든 오브젝트를 정리하는 메서드
    • 메모리를 해제하거나, 더 이상 필요하지 않은 오브젝트를 삭제할 때 사용
  • CountInactive

    • 풀 안에서 현재 사용하지 않는 오브젝트의 수를 반환
    • 이를 통해 풀의 상태를 모니터링할 수 있음


■ 꾸준실습 - 입력 오버라이드 수정

  • Space performed 이벤트에 OnSpacePressed 추가
public class InputRebinder : MonoBehaviour
{
    public InputActionAsset actionAsset;
    private InputAction spaceAction;
    private InputAction escapeAction;

    void Start()
    {
        // (완료) [구현사항 1] actionAsset에서 Space 액션을 찾고 활성화합니다.
        spaceAction = actionAsset.FindAction("Space");

        // Space 액션 활성화
        spaceAction.Enable();
        spaceAction.performed += OnSpacePressed;

    }

    // (완료) [구현사항 2] ContextMenu 어트리뷰트를 활용해서 인스펙터창에서 적용할 수 있도록 함

    void OnEnable()
    {
        // Esc 키에 대한 액션 생성
        escapeAction = new InputAction(binding: "<Keyboard>/escape");

        // Esc 키 입력 이벤트 등록
        escapeAction.performed += OnEscapePressed;

        // 액션 활성화
        escapeAction.Enable();
    }

    public void OnSpacePressed(InputAction.CallbackContext context)
    {
        if(context.performed)
        {
            Debug.Log("Check_Space");
        }
    }

    private void OnEscapePressed(InputAction.CallbackContext context)
    {
        // Esc 키 눌렀을 때 실행
        RebindSpaceToEscape();
    }

    public void RebindSpaceToEscape()
    {
        if (spaceAction == null)
            return;
        actionAsset.FindAction("Space").ApplyBindingOverride("<Keyboard>/escape");

        Debug.Log("Done!");
    }

    void OnDestroy()
    {
        // 액션을 비활성화합니다.
        spaceAction?.Disable();
        escapeAction?.Disable();
    }
}


■ 꾸준실습 - 오브젝트 풀링(List 활용)

○ [구현사항 1]

  • 최소 50개의 오브젝트 수 보장,
  • 부족할 경우 누적 300개까지 추가 생성,
  • 300개가 넘어갈 경우 임시로 생성 후 반환 시 파괴
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool_1 : MonoBehaviour
{
    // [구현사항 1]
    // 최소 50개의 오브젝트 수 보장,
    // 부족할 경우 누적 300개까지 추가 생성,
    // 300개가 넘어갈 경우 임시로 생성 후 반환 시 파괴
    private List<GameObject> pool;
    private const int minSize = 50;
    private const int maxSize = 300;
    [SerializeField] private GameObject bulletPrefab;

    void Awake()
    {
        pool = new List<GameObject>();
        for (int i = 0; i < minSize; i++)
        {
            pool.Add(CreateObject()); // 일단은 최소 50개 생성
        }
    }

    // 오브젝트 생성 함수
    private GameObject CreateObject()
    {
        GameObject newObject = Instantiate(bulletPrefab);
        newObject.SetActive(false); // 초기에는 비활성화
        return newObject;
    }

    // 오브젝트 가져오는 함수
    public GameObject GetObject()
    {
        foreach (var obj in pool)
        {// 풀 안에서
            if (!obj.activeInHierarchy) // 비활성화된 객체를 찾는다
            {
                obj.SetActive(true); // 활성화
                return obj; // 있으면 반환
            }
        }

        // 비활성화 객체 탐색 실패
        // 모든 객체가 활성화되어 있을 경우
        if (pool.Count < maxSize)
        {
            // 풀 안에 오브젝트가 최대 300개가 되기전까지는 추가
            GameObject newObject = CreateObject();
            pool.Add(newObject);
            newObject.SetActive(true);
            return newObject;
        }

        // 최대 크기를 초과할 경우, 임시로 생성 후 반환
        // 풀 안에는 추가하지 않음.
        GameObject tempObject = CreateObject();
        tempObject.SetActive(true);
        return tempObject;
    }

    // 오브젝트 반환 함수
    public void ReleaseObject(GameObject obj)
    {
        if (pool.Contains(obj))
        {// 풀안에 포함된 오브젝트면
            obj.SetActive(false); // 비활성화
        }
        else
        {
            // 풀 외의 것이면
            Destroy(obj); // 파괴
        }
    }
}

○ [구현사항 2]

  • 최소 50개의 오브젝트 수 보장,
  • 부족할 경우 누적 300개까지 추가 생성,
  • 300개가 넘어갈 경우 가장 오래전에 생성된 오브젝트를 반환 후 재사용
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

public class ObjectPool_2 : MonoBehaviour
{
    // [구현사항 2]
    // 최소 50개의 오브젝트 수 보장,
    // 부족할 경우 누적 300개까지 추가 생성,
    // 300개가 넘어갈 경우 가장 오래전에 생성된 오브젝트를 반환 후 재사용
    private List<GameObject> pool;
    private const int minSize = 50;
    private const int maxSize = 300;
    private int recentNum = 0;

    [SerializeField] private GameObject bulletPrefab;

    void Awake()
    {
        pool = new List<GameObject>();
        for (int i = 0; i < minSize; i++)
        {
            pool.Add(CreateObject());
        }
    }

    private GameObject CreateObject()
    {
        GameObject newObject = Instantiate(bulletPrefab);
        newObject.SetActive(false); // 초기에는 비활성화
        return newObject;
    }

    public GameObject GetObject()
    {
        foreach (var obj in pool)
        {
            if (!obj.activeInHierarchy) // 비활성화된 객체를 찾는다
            {
                obj.SetActive(true); // 활성화
                return obj;
            }
        }

        // 모든 객체가 활성화되어 있을 경우
        if (pool.Count < maxSize)
        {
            GameObject newObject = CreateObject();
            pool.Add(newObject);
            newObject.SetActive(true);
            return newObject;
        }

        // 300개가 넘어갈 경우
        // 가장 오래전에 생성된 오브젝트를 반환 후 재사용
        if(recentNum == maxSize)
            recentNum = 0;
        ReleaseObject(pool[recentNum]);
        pool[recentNum].SetActive(true);
        return pool[recentNum++];
    }

    public void ReleaseObject(GameObject obj)
    {
        if (pool.Contains(obj))
        {
            obj.SetActive(false); // 비활성화
        }
        else
        {
            Destroy(obj); // 풀에 포함되지 않은 객체는 파괴
        }
    }
}

○ [구현사항 3]

  • 오브젝트를 미리 생성하지 않고
  • 부족할 경우 누적 100개까지 추가 생성,
  • 100개가 넘어갈 경우 임시로 생성 후 반환 시 파괴
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

public class ObjectPool_3 : MonoBehaviour
{
    // [구현사항 3]
    // 오브젝트를 미리 생성하지 않고
    // 부족할 경우 누적 100개까지 추가 생성,
    // 100개가 넘어갈 경우 임시로 생성 후 반환 시 파괴
    private List<GameObject> pool;
    private const int maxSize = 100;

    [SerializeField] private GameObject bulletPrefab;

    void Awake()
    {
        pool = new List<GameObject>();
    }

    private GameObject CreateObject()
    {
        GameObject newObject = Instantiate(bulletPrefab);
        newObject.SetActive(false); // 초기에는 비활성화
        return newObject;
    }

    public GameObject GetObject()
    {
        foreach (var obj in pool)
        {
            if (!obj.activeInHierarchy) // 비활성화된 객체를 찾는다
            {
                obj.SetActive(true); // 활성화
                return obj;
            }
        }

        // 모든 객체가 활성화되어 있을 경우
        if (pool.Count < maxSize)
        {
            GameObject newObject = CreateObject();
            pool.Add(newObject);
            newObject.SetActive(true);
            return newObject;
        }

        GameObject tempObject = CreateObject();
        tempObject.SetActive(true);
        return tempObject;
    }

    public void ReleaseObject(GameObject obj)
    {
        if (pool.Contains(obj))
        {
            obj.SetActive(false); // 비활성화
        }
        else
        {
            Destroy(obj); // 풀에 포함되지 않은 객체는 파괴
        }
    }
}


■ 매일 알고리즘 풀이 4일차

○ 조건 문자열

#include <string>
#include <vector>

using namespace std;

int solution(string ineq, string eq, int n, int m)
{
    if (eq == "=")
    {
        if (ineq == ">")
            return  n >= m ? 1 : 0;
        else if (ineq == "<")
            return  n <= m ? 1 : 0;
    }
    else if (eq == "!")
    {
        if (ineq == ">")
            return  n > m ? 1 : 0;
        else if (ineq == "<")
            return  n < m ? 1 : 0;
    }
}

  • 다른 사람 풀이
    • 문자열을 합쳐서 조건식이 좀 더 간략해짐
#include <string>
#include <vector>

using namespace std;

int solution(string ineq, string eq, int n, int m) {
    string oper = ineq+eq;
    if(oper=="<=")
    {
        return n<=m;
    }
    else if(oper==">=")
    {
        return n>=m;
    }
    else if(oper=="<!")
    {
        return n<m;
    }
    else if(oper==">!")
    {
        return n>m;
    }
}

○ 문자열 돌리기

#include <iostream>
#include <string>

using namespace std;

int main(void) {
    string str;
    cin >> str;
    
    for(int i = 0; i < str.length(); i++)
        cout << str[i] <<"\n";
    return 0;
}

○ 문자열 반복해서 출력하기

#include <iostream>
#include <string>

using namespace std;

int main(void) {
    string str;
    int n;
    cin >> str >> n;

    for (int i = 0; i < n; i++)
        cout << str;
    return 0;
}

1개의 댓글

comment-user-thumbnail
2024년 10월 24일

감사합니다

답글 달기