태그 보관물: unity

유니티(Unity) IObjectPool 을 사용한 오브젝트 풀링(Object Pooling)

개요

게임내에서 많은 오브젝트를 생성하고 파괴하는 과정은 많은 비용을 소모합니다. 비용을 최소화하기 위해 오브젝트 풀링(Object Pooling)을 사용합니다. 미리 오브젝트를 만들어 놓고 필요할 때마다 꺼내서 사용하는 개념으로 생각하면 됩니다.

오브젝트 풀링을 직접 구현할 수도 있고 애셋스토어에 있는 것을 사용해도 됩니다. 그러나 유니티에서 자체적으로 지원합니다. 2021 버전부터 지원합니다.

사용

가장 먼저 생각해 볼 수 있는 것이 플레이어나 적의 탄환입니다. 기본적으로는 탄환 오브젝트를 생성되고 파괴되는 방식으로 구현할 수 있습니다. 이렇게 하지 않고 IObjectPool 을 이용한 방법으로 구현해 보겠습니다.

게임 오브젝트(플레이어 탄환)를 하나 만들고 다음 스크립트를 생성한 후 연결해 줍니다.

public class PlayerBullet : MonoBehaviour
{
    private IObjectPool<PlayerBullet> playerBulletPool;

    public void SetPool(IObjectPool<PlayerBullet> objectPool)
    {
        playerBulletPool = objectPool;
    }

    private void OnBecameInvisible()
    {
        playerBulletPool?.Release(this);
    }
}

플레이어 게임 오브젝트와 연결된 스크립트에서 탄환을 발사하는 코드가 있다고 가정하고 다음과 같이 구현합니다.

public PlayerBullet playerBulletPrefabForPool; //PlayerBullet 클래스가 연결된 게임오브젝트
private IObjectPool<PlayerBullet> playerBulletPool;

private void Awake()
{
    playerBulletPool = new ObjectPool<PlayerBullet>(CreateBullet, OnGetBullet, OnReleaseBullet, OnDestroyBullet, maxSize: 10);
}

private PlayerBullet CreateBullet()
{
    PlayerBullet playerBullet = Instantiate(playerBulletPrefabForPool, playerTransform.position, Quaternion.identity);
    playerBullet.SetPool(playerBulletPool);
    return playerBullet;
}

private void OnGetBullet(PlayerBullet playerBullet)
{
    playerBullet.gameObject.SetActive(true);
    playerBullet.transform.position = playerTransform.position;
}

private void OnReleaseBullet(PlayerBullet playerBullet)
{
    playerBullet.gameObject.SetActive(false);
}

private void OnDestroyBullet(PlayerBullet playerBullet)
{
    Destroy(playerBullet.gameObject);
} 

1행은 PlayerBullet 클래스와 연결된 게임 오브젝트를 정의합니다. 이것을 유니티 에디터에서 연결해 줍니다.

6행은 실제 오브젝트 풀을 생성하고 필요한 메소드들을 정의합니다. 최대 숫자는 10으로 설정했습니다. 화면에 최대로 나타날 수 있는 개수를 고려하여 적절하게 조정합니다. 나머지 부분은 지정한 메소드의 이름으로 각각을 정의합니다.

탄환을 발사하는 부분에서는 다음과 같이 구현합니다. 탄환 게임 오브젝트를 Instantiate 로 생성하지 않고 오브젝트 풀에서 가져오도록 한 것이 가장 큰 차이점 입니다.

var playerBullet = playerBulletPool?.Get(); //오브젝트 풀에서 가져옴
//add your code
//GameObject bullet = Instantiate(playerBullet... //바로 생성

유니티 에디터에서 실행해서 보면 다음과 같이 활성화된 탄환(bullet-circle)과 비활성화된 탄환을 구별할 수 있습니다.

활성/비활성 플레이어 탄환

이렇게 오브젝트 풀링을 사용하면 탄환을 계속 생성/파괴하지 않고 효율적으로 구현할 수 있습니다. 메모리를 더 사용하게 되지만 생성/파괴되는 비용을 크게 낮출 수 있습니다.

유니티(Unity) C# 스크립트를 이용한 게임 관련 변수 관리

개요

게임 내 여러 요소를 제어하기 위해 많은 변수가 필요합니다. 우주 슈팅 게임을 예로 들어본다면 플레이어, 적, 보스 등등 각 객체에 고유한 특성을 정의하는 값이 존재합니다. 이런 변수 값을 체계적으로 잘 정리해 두어야 테스트 및 난이도 조절할 때 쉽게 변경할 수 있습니다.

변수값을 관리하는 방법이 여러가지(DB, File…)가 있습니다. 이번 글에서는 C# 스크립트를 이용해 보도록 하겠습니다.

변수 관리 스크립트

각종 변수 정보를 담을 C# 스크립트를 생성합니다(Create > C# Script). 아래 코드와 같은 형태로 내용을 입력합니다.

public class GameControlData : MonoBehaviour
{
    public static GameControlData GAME_CONTROL_DATA;

    void Awake()
    {
        if (GAME_CONTROL_DATA == null) 
        {
            DontDestroyOnLoad(gameObject);
            GAME_CONTROL_DATA = this;
        } 
    }
}

Awake 메소드에 DontDestoryOnLoad 를 이용해서 다른 Scene 에서도 사용할 수 있도록 해 줍니다. 그리고 정적변수로 정의한 GAME_CONTROL_DATA 가 비어 있는 경우 this 로 대입해 줍니다.

GameObject 생성 및 스크립트 적용

빈 게임 오브젝트를 하나 만들고 Scene에 배치합니다. 여기에 위에서 생성한 스크립트를 붙여줍니다. 그러면 다음과 같은 상태가 됩니다.

빈 오브젝트를 생성하고 스크립트를 부착한 상태
빈 오브젝트를 생성하고 스크립트를 부착한 상태

필요한 변수들을 다음 코드와 같이 스크립트에 추가하면 다음과 같이 에디터에서 변수가 나타나는 것을 확인할 수 있습니다.

public static GameControlData GAME_CONTROL_DATA;

public float playerSpeed;
public float enemySpeed;
public int itemType;

void Awake()
{
    if (GAME_CONTROL_DATA == null)
    {
        DontDestroyOnLoad(gameObject);
        GAME_CONTROL_DATA = this;
    }
}
변수 추가 후 에디터에서 변경된 상태
변수 추가 후 에디터에서 변경된 상태

이제 에디터에서 변수의 값을 변경할 수 있습니다. 이 경우 에디터에서 지정된 값이 적용됩니다. 조정을 통해 변수의 값을 확정한 후 원래 스크립트의 값을 변경하면 됩니다.

다른 스크립트에서 참조

다른 스크립트에서는 다음 코드와 같은 형식으로 참조합니다.

using static GameBaseData;

GAME_CONTROL_DATA.playerSpeed = 1.0f;

using static 지시문을 이용해서 축약된 형태로 변수를 참조할 수 있도록 합니다.

유형별 변수 정리

변수가 많아지면 각 분류별로 정리된 것이 관리하기 편합니다. 다음과 같이 변수를 유형별로 나누어 에디터에서 구분되어 나타나도록 할 수 있습니다.

[Header("Player Setting")]
public float playerSpeed;
public float playerPower;

[Header("Enemy Setting")]
public float enemySpeed;

[Header("Item Setting")]
public int itemType;
[SerializeField]
private int itemPower;
변수를 분류한 후 에디터의 상태
변수를 분류한 후 에디터의 상태

변수가 private 라도 [SerializeField]로 지정하면 에디터에 나타나 쉽게 변경할 수 있습니다.

이번글에서는 간단한 게임의 변수 항목을 게임 오브젝트에 C# 스크립트를 붙여 손쉽게 관리하는 방법을 알아보았습니다.