Unity - 오브젝트(Object), 컴포넌트(Component), 오브젝트 풀링(Object Pooling)

2025. 10. 14. 15:39·Unity

Object라는 건 프로그래밍 외에도 다양한 곳에서 다양한 의미로 사용된다.

어째 현실에서도 Object 데이터 타입이랑 똑같다...

 

이번 글에서는 유니티에서 Objec와 관련된 내용들을 정리해 봤다.

 


 

Scene

게임의 한 단계 / 장면을 구성하는 모든 리소스와 상태를 담는 .unity 파일이다.

독립적인 세계(레벨) 단위로 메뉴, 플레이, 엔딩 등 씬을 분리해 개발, 테스트, 로딩을 최적화할 수 있다.

씬 간의 전환은 SceneManager.LoadScene() 함수를 통해 이루어진다.

using UnityEngine.SceneManagement;

// 싱글 로드
SceneManager.LoadScene("Game");

// 애드티브 로드(멀티 씬)
SceneManager.LoadScene("UI", LoadSceneMode.Additive);

// 언로드
SceneManager.UnloadSceneAsync("UI");

로딩 화면 별도씬 + 애드티브 로드로 컨텐츠 스트리밍 할 것을 권장한다고 한다.

 

공통 시스템 (Audio, Save, network)은 Persistent 씬으로 독립하고 DontDestroyOnLoad로 유지하는 식으로 관리할 수 있다.

 

 

GameObject

빈 껍데기와 계층구조.

Transform(위치, 회전, 스케일)만 담는 컨테이너로 실제 동작은 Component가 담당한다.

부모-자식 구조를 통해 로컬 트랜스폼으로 묶어 관리한다.

 

Component

실제 기능을 보유한 모듈
Transform, Renerer, Colider, MonoBehaviour 등 소스코드로 이루어져있기에 수정도 가능하다.

한 클래스가 하나의 역할에 집중하도록 모듈화하는 것을 원칙으로 한다.

[SerializeField] 를 통해 private 변수도 에디터 인스펙터에서 의존성을 주입할 수 있다.

 

 

Prefab

씬의 GameObject 구성을 에셋으로 저장한 템플릿, 재사용과 일관성 유지를 위해 사용하는 프리셋으로 한번 만들어두면 복사 / 재사용이 가능하다.

원본을 수정하면 연결된 인스턴스들도 같이 업데이트 된다.

런타임 중에 Instaitiate(prefab)로 동적 스폰이 가능하다.

 

 


 

 

Instantiate / Destroy 비용 구조

GameObject는 런타임 중에 Instantiate(), Destroy()를 통해 동적으로 생성하거나 파괴할 수 있다.

Instantiate : 메모리 할당 + 컴포넌트 초기화 + Awake / OnEnable 호출

Destory : 즉시 파괴가 아닌 스케줄링 -> GC 압력 증가, 프레임 경합 지점에서 스파이크 발생 가능.

그렇기 때문에 빈번하게 다량의 오브젝트를 생성-삭제 할 경우, 오브젝트 풀링 사용을 권장한다.

 

 

 

Object Pooling

게임에서 반복적으로 사용되는 오브젝트를 미리 생성해 두고, 필요할 때 활성화/비활성화 하며 재사용하는 기법이다.

생성, 파괴시 프레임 드랍과 GC 부담을 줄일 수 있어 많이 사용되는 최적화 기법이다.

 

간단하게 총알을 자동으로 계속 발사하는 오브젝트 풀링을 구현해보자.

using UnityEngine;

public class Bullet : MonoBehaviour
{
    [SerializeField] private float speed = 20f;

    void Update()
    {
        // Z축(정면)으로 계속 이동
        transform.Translate(Vector3.forward * speed * Time.deltaTime);
    }
    
    void OnCollisionEnter(Collision collision)
    {
        Destroy(gameObject);
    }
}

 

이렇게 구성하면 단순히 총알을 Destroy 하게 된다.

하지만

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BulletSpawner : MonoBehaviour
{
    [Header("Bullet Settings")]
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private Transform firePoint;
    [SerializeField] private float bulletSpeed = 20f;
    [SerializeField] private float fireInterval = 0.2f;
    [SerializeField] private int prewarmCount = 30;

    private readonly Queue<GameObject> _pool = new();
    private float _timer;

    void Start()
    {
        // 초기 풀 생성
        for (int i = 0; i < prewarmCount; i++)
        {
            var bullet = Instantiate(bulletPrefab);
            bullet.SetActive(false);
            _pool.Enqueue(bullet);
        }
    }

    void Update()
    {
        _timer += Time.deltaTime;
        if (_timer >= fireInterval)
        {
            _timer = 0f;
            Fire();
        }
    }

    private void Fire()
    {
        if (!bulletPrefab || !firePoint) return;

        // 풀에서 가져오기
        GameObject bullet = _pool.Count > 0 ? _pool.Dequeue() : Instantiate(bulletPrefab);

        bullet.transform.SetPositionAndRotation(firePoint.position, firePoint.rotation);
        bullet.SetActive(true);

        if (bullet.TryGetComponent<Rigidbody>(out var rb))
        {
            rb.linearVelocity = firePoint.forward * bulletSpeed;
        }

        //일정 시간 후 반환
        StartCoroutine(ReturnToPool(bullet, 3f));
    }

    private IEnumerator ReturnToPool(GameObject bullet, float delay)
    {
        yield return new WaitForSeconds(delay);
        bullet.SetActive(false);
        _pool.Enqueue(bullet);
    }
}

 

BulletSpawner를 만들고, Queue를 이용해 pool을 만들어 준다.

그리고 기존의 총알은 Destroy가 아닌 SetActive를 이용해서

 

void OnCollisionEnter(Collision collision)
{
    gameObject.SetActive(false);
}

 

비활성화로 바꿔준다.

실제로 적용해보면

 

 

 

Bullet의 Clone들이 활성, 비활성화 되면서 재활용되는 것을 볼 수 있다.

 

 

 

주의점

Instaintiate()로 생성한 오브젝트는 메모리 힙에 새로 할당되므로, 생성 직후 참조를 유지하지 않으면 GC가 회수할 수 없다.

Destroy() 호출 시 즉시 메모리가 해제되는 것이 아니라, 프레임이 끝난 후에 GC가 수거한다. 따라서 생성 / 파괴가 빈번한 경우 성능 저하가 발생할 수 있다.

필요하다면 DestroyImmediate()를 사용할 수 있으나, 런타임 중 남용하면 예상치 못한 문제를 발생시킬 수 있어 에디터 환경에서만 사용이 권장된다.

'Unity' 카테고리의 다른 글

Unity - EventSystem (이벤트 시스템)  (0) 2025.10.20
Unity - Coroutine, yield, Invoke, IEnumerator  (0) 2025.10.17
Unity - Rigidbody, Collider - 물리, 충돌 처리  (0) 2025.10.16
Unity - Transform, 위치 이동  (0) 2025.10.15
Unity - 생명주기 (Life Cycle)  (1) 2025.10.13
'Unity' 카테고리의 다른 글
  • Unity - Coroutine, yield, Invoke, IEnumerator
  • Unity - Rigidbody, Collider - 물리, 충돌 처리
  • Unity - Transform, 위치 이동
  • Unity - 생명주기 (Life Cycle)
ellug
ellug
Web / App / Game Dev
  • ellug
    dev.ellug
    ellug
  • 전체
    오늘
    어제
    • 분류 전체보기 (95)
      • Javascript (React, Next) (1)
      • C# (66)
      • Graphics (1)
      • Unity (25)
      • 프로그래머스 (0)
        • Lv.0 (0)
        • Lv.1 (0)
        • Lv.2 (0)
        • Lv.3 (0)
      • AI Agent (0)
      • StableDiffusion (0)
      • ETC (1)
      • 잡담 (0)
      • 유머 (0)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    interface
    console
    Design Pattern
    Queue
    list
    delegate
    Abstract
    unity
    struct
    stack
    콘솔
    클래스
    firebase
    큐
    유니티
    최적화
    스택
    function
    터미널
    알고리즘
    네트워크
    inputsystem
    Photon
    추상 클래스
    useState
    C#
    Network
    게임
    class
    자료구조
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
ellug
Unity - 오브젝트(Object), 컴포넌트(Component), 오브젝트 풀링(Object Pooling)
상단으로

티스토리툴바