개발단에 가입하여 베르의 게임 개발 유튜브를 후원해주세요! 

 

베르의 게임 개발 유튜브

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다! 게임 개발에 도움이 되는 강좌들을 올리는 채널입니다! [투네이션 후원] https://toon.at/donate/637735212761460238 [유니티 어필리에이트

www.youtube.com

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다!

이번에는 유니티에서 공식 지원하는 오브젝트 풀링을 알아봅시다.

 

[패키지 다운로드]

https://github.com/WERGIA/WerGameDevChan/blob/main/Unity/unity-object-pool.unitypackage

 

사용 엔진 버전 : 2021.3

 

타임라인

0:00 인트로

0:10 오브젝트 생성과 파괴 방식의 문제

2:26 오브젝트 풀링의 개념

3:02 유니티 오브젝트 풀링

6:14 아웃트로

스크립트

인트로

안녕하세요. 여러분들과 함께 게임 개발을 공부하는 베르입니다.

이번에는 유니티 2021부터 정식 기능으로 포함된 오브젝트 풀링에 대해서 알아보도록 하겠습니다.

오브젝트 생성과 파괴 방식의 문제

오브젝트 풀링이 무엇인지는 이전에 만든 오브젝트 풀링 영상에서 이야기했었지만 그래도 한 번 더 간단하게 설명해보도록 하겠습니다.

먼저 오브젝트 풀링을 사용하지 않고 오브젝트를 생성하거나 파괴하면 두 가지 문제가 발생할 수 있습니다.

첫 번째 문제는 가비지 컬렉팅으로 인한 프레임 드랍입니다.

프로그래밍에서 오브젝트를 생성하거나 파괴하는 작업은 꽤나 무거운 작업으로 분류됩니다.

오브젝트 생성은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고, 오브젝트 파괴는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있습니다.

가비지 컬렉팅이란 유니티 스크립팅의 기반이 되는 C# 프로그래밍에서 제공되는 기능입니다.

우리가 Destroy 함수로 게임 오브젝트를 파괴한다고 선언하면 곧바로 메모리에서 사라지는 것이 아닙니다.

그 게임 오브젝트는 게임에서 보이지 않게 되지만 가비지 콜렉터라고 부르는 것이 그것을 수거해서 파괴하기 전까지 메모리에 남아있습니다.

그래서 오브젝트를 파괴한다고 선언하면 일정 사이클이 지난 이후에 가비지 컬렉터가 메모리를 뒤져서 파괴 선언된 오브젝트를 수거해서 파괴합니다.

이렇게 파괴 선언된 오브젝트가 적으면 수거해서 파괴하는 시간이 짧지만, 많은 양의 오브젝트가 쌓여있다면 당연히 수거하고 파괴하는 데 더 많은 시간이 걸리게 됩니다.

거기에 컴퓨터는 이렇게 파괴한 오브젝트를 수거해서 정리하는 작업만 진행하고 있는게 아닙니다.

게임이 동작하는 로직도 처리하고 있고 게임 화면을 보여주기 위해서 렌더링 작업까지 진행하고 있습니다.

그렇기 때문에 파괴된 오브젝트가 많다면 많은 양의 메모리를 정리하기 위해서 게임의 프레임이 일시적으로 떨어지는 문제가 발생합니다.

두 번째 문제는 메모리 파편화입니다.

프로그램에서는 처음부터 정해진 메모리의 양도 있지만, 동작하는 도중에 필요한 양에 따라 새로 할당받는 경우도 있습니다.

그런데 이 예시 이미지처럼 64바이트의 A, B, C, D를 할당받은 상태에서 A와 C를 할당 해제하면 3번과 같이 빈 공간이 생겨납니다.

이 때 128바이트의 오브젝트를 생성하려고 하면 A와 C 자리에는 공간이 부족에서 D의 뒤에 있는 공간에 할당해야 합니다.

이 할당 이 후에 128바이트 짜리 오브젝트를 하나 더 생성하려고 하면, 메모리 빈 공간만 따졌을 때는 128바이트의 공간이 존재하지만, 연결된 크기가 128바이트인 공간이 없어서 오브젝트 생성에 실패합니다.

한마디로 메모리는 남아 있는데 메모리가 부족한 역설적인 상황이 발생하는 것입니다.

이런 문제들을 해결하기 위해서 고안된 개념이 바로 오브젝트 풀링입니다.

오브젝트 풀링의 개념

오브젝트 풀링의 개념은 생각보다 간단합니다.

먼저 오브젝트를 담아둘 오브젝트 풀을 만들고 여기에 빌려줄 오브젝트를 생성해서 담아둔 뒤 외부에서 이 오브젝트가 필요하면 오브젝트 풀에서 빌려가는 갑니다.

그리고 빌려온 오브젝트를 모두 사용하고 나면 다시 오브젝트 풀에 돌려주는 겁니다.

그런데 만약 외부에서 오브젝트 풀 안에 있는 모든 오브젝트를 다 빌려가서 더 빌려줄 수 없는 상황이 되면 그때서야 새로운 오브젝트를 생성해서 꺼내줍니다.

그리고 이렇게 새로 만든 오브젝트 역시 사용이 끝나서 돌려 받으면 파괴하지 않고 오브젝트 풀이 보관하고 있으면 됩니다.

이게 바로 오브젝트 풀링의 개념입니다.

유니티 오브젝트 풀링

유니티 오브젝트 풀링을 사용하는 방법을 알아보기 전에 영상 하단의 링크에서 자료를 다운로드 받아서 프로젝트에 임포트합니다.

패키지를 임포트한 다음에 Pooling Test Scene을 열고 플레이시켜보면 클릭하는 동안에 많은 수의 총알 오브젝트가 생성되는 모습을 볼 수 있습니다.

총알을 발사하는 역할의 Shooter 클래스를 보면 지금은 오브젝트 풀을 사용하지 않고 총알을 생성하고 있습니다.

이제 이 총알 생성과 파괴 부분을 유니티 오브젝트 풀로 대체해보겠습니다.

유니티 오브젝트 풀을 사용하기 위해서는 UnityEngine.Pool 네임스페이스가 필요합니다.

그리고 오브젝트 풀을 적용하기 위해서는 풀링의 대상이 될 오브젝트의 클래스와 풀링된 오브젝트를 관리할 클래스, 두 군데에 작업을 해줘야합니다.

먼저 풀링의 대상이 될 Bullet 클래스로 이동합니다.

그리고 클래스의 상단에 UnityEngine.Pool 네임스페이스를 선언해줍니다.

IObjectPool<Bullet> 타입으로 이 총알 오브젝트를 관리 중인 풀을 캐싱할 _ManagedPool 멤버 변수를 선언합니다.

그리고 SetManagedPool 함수를 만들고 매개변수로 받은 풀을 _ManagedPool에 저장하도록 만들어줍니다.

그 다음에는 DestroyBullet 함수를 만들고 호출되면 총알 오브젝트를 풀에 반환하도록 코드를 작성합니다.

그리고 Shoot 함수에서 총알을 5초 후에 파괴하는 부분을 5초 후에 DestroyBullet 함수를 호출하도록 수정합니다.

그 다음에는 Shooter 클래스로 이동해서 상단에 UnityEngine.Pool 네임스페이스를 선언해줍니다.

그리고 멤버 변수로 총알 오브젝트들을 관리할 IObjectPool<Bullet> 타입의 _Pool 변수를 선언해줍니다.

그리고 Awake 함수를 만들어서 _Pool 멤버 변수에 Object Pool을 생성해서 넣어줍니다.

여기서 생성자의 매개 변수에 넣어줄 것이 좀 많습니다.

먼저 총알 오브젝트를 생성할 때 호출될 함수인 CreateBullet 함수를 만들고 가지고 있는 프리팹으로 총알 오브젝트를 생성한 다음 생성된 bullet 오브젝트에 자신이 등록되어야할 풀을 알려주고 반환합니다.

그리고 풀에서 오브젝트를 빌려올 때 사용될 OnGetBullet 함수와 오브젝트를 풀에 돌려줄 때 사용될 OnReleaseBullet 함수를 선언하고 코드를 작성합니다.

마지막으로 풀에서 오브젝트를 파괴할 때 사용될 OnDestroyBullet 함수 역시 작성해줍니다.

그리고 이렇게 만든 함수들을 풀의 생성자 매개변수에 넣어주고, 생성가능한 오브젝트 최대 갯수 역시 넣어줍니다.

오브젝트 풀과 관련된 코드를 모두 작성한 다음에는 Update 함수로 가서 프리팹을 인스턴스화해서 총알 오브젝트를 생성하는 부분의 코드를 오브젝트 풀을 사용하도록 코드를 수정합니다.

코드를 모두 작성한 다음에는 저장하고 에디터로 돌아갑니다.

그리고 게임을 플레이해서 테스트해보면 처음에는 총알 오브젝트가 없지만 총알을 발사할 때 생겨납니다.

그리고 총알이 사라질 때가 되면 풀에 설정해놓은 최대 갯수만 남아서 비활성화되고 나머지는 완전히 파괴되는 모습을 볼 수 있습니다.

다시 한 번 총알을 발사해보면 비활성화된 총알들이 재활용되고 모자란 총알들만 생성되는 모습을 볼 수 있습니다.

아웃트로

이번 영상에서는 2021 버전부터 정식 지원되는 유니티 오브젝트 풀링에 대해서 알아보았습니다.

이 강좌는 시청자 여러분들의 시청과 후원으로 제작되었습니다.

이상 베르의 게임 개발 유튜브였습니다. 감사합니다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

[참고자료]

https://luv-n-interest.tistory.com/1368

https://docs.unity3d.com/ScriptReference/Pool.ObjectPool_1.html

https://workinprogress.kr/wiki/programming/do-not-feed-the-gc/

반응형

Programming 

오브젝트 풀링 기법

 

작성 기준 버전 :: 2019.2

 

프로그래밍에서 오브젝트를 생성하거나 파괴하는 작업은 꽤나 무거운 작업으로 분류된다. 오브젝트 생성은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화하는 과정으로, 오브젝트 파괴는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있다.

 

이러한 문제를 해결하기 위해서 사용되는 기법이 바로 오브젝트 풀링(Object pooling)이다.

 

오브젝트 풀링의 개념

 

오브젝트 풀링의 개념은 아주 간단하다. 

 

 

먼저 풀링을 할 오브젝트를 담을 오브젝트 풀을 구성한다.

 

 

그 다음 풀링할 오브젝트를 생성해서 오브젝트 풀에서 그 오브젝트들을 관리하게 만든다.

 

 

외부에서 해당 오브젝트가 필요하면 오브젝트 풀에서 꺼내간다.

 

 

오브젝트 풀에서 꺼낸 오브젝트의 사용이 끝나면 오브젝트를 풀에 돌려준다.

 

 

오브젝트 풀에서 오브젝트를 가져오려고 할 때, 모든 오브젝트가 이미 사용중이라면 새로운 오브젝트를 생성해서 꺼내준다.

 

이것이 바로 오브젝트 풀의 개념이다. 게임에 필요한 오브젝트를 미리 생성해서 필요할 때마다 꺼내쓰고 사용이 끝나면 오브젝트 풀에 돌려주는 것이다. 이렇게 함으로써, 오브젝트가 필요할 때마다 생성하고 다 쓰면 파괴하는 것이 아니라, 게임이 시작될 때, 필요한 만큼의 오브젝트만 생성하고, 모자라면 추가로 생성하고, 게임이 끝나면 파괴하는 방식으로 오브젝트의 생성/파괴 횟수를 줄일 수 있게 된다.

 

오브젝트 풀링 기법은 특히 메모리가 부족하고 CPU의 성능이 낮은 디바이스의 사양이 낮았던 예전에 주로 사용되었던 기법이지만, 현재에 와서도 게임의 최적화를 위해서 많이 사용되며, 모바일 게임이 많이 제작되면서 PC에 비해서 모자란 디바이스 성능으로 인해서 재조명 받는 기법이다.

 

보통 자주 생성되었다가 파괴되어야 하는 총알이나, 캐릭터가 뛸 때 발생하는 먼지 이펙트 같은 곳에 많이 사용되는 기법이다.

 

 

 

유니티 엔진에서의 구현

 

기본 세팅

 

기본적인 세팅은 다음과 같다.

 

 

씬 한 가운데 캐릭터의 역할을 할 초록색 박스를 배치했다.

 

public class Shooter : MonoBehaviour
{
    [SerializeField]
    private GameObject bulletPrefab;

    private Camera mainCam;

    void Start()
    {
        mainCam = Camera.main;
    }

    void Update()
    {
        if(Input.GetMouseButton(0))
        {
            RaycastHit hitResult;
            if(Physics.Raycast(mainCam.ScreenPointToRay(Input.mousePosition), out hitResult))
            {
                var bullet = Instantiate(bulletPrefab).GetComponent<Bullet>();
                var direction = new Vector3(hitResult.point.x, transform.position.y, hitResult.point.z) - transform.position;
                bullet.transform.position = direction.normalized;
                bullet.Shoot(direction.normalized);
            }
        }
    }
}

 

그리고 박스 게임 오브젝트에는 마우스 클릭 방향으로 총알 게임 오브젝트를 생성해서 발사하는 Shooter 컴포넌트가 부착되어 있다.

 

 

public class Bullet : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Destroy(gameObject, 5f);
    }

    void Update()
    {
        transform.Translate(direction);   
    }
}

 

그리고 지정된 방향으로 5초 간 날아가다 자동으로 소멸하는 총알 프리팹 역시 만들어두었다.

 

 

이것을 테스트 해보면 마우스를 클릭하면 엄청난 양의 총알 오브젝트가 새로 생성되고 시간이 지나면 모두 파괴되는 것을 볼 수 있다.

 

 

이 상황을 프로파일러를 통해서 확인해보면 총알을 생성하는 Instantiate와 파괴하는 Destroy가 어느 정도 성능을 잡아먹고 있는 것을 확인할 수 있다.

 

오브젝트 풀 구현

 

public class ObjectPool : MonoBehaviour
{
    public static ObjectPool Instance;

    [SerializeField]
    private GameObject poolingObjectPrefab;

    Queue<Bullet> poolingObjectQueue = new Queue<Bullet>();

    private void Awake()
    {
        Instance = this;

        Initialize(10);
    }

    private void Initialize(int initCount)
    {
        for(int i = 0; i < initCount; i++)
        {
            poolingObjectQueue.Enqueue(CreateNewObject());
        }
    }

    private Bullet CreateNewObject()
    {
        var newObj = Instantiate(poolingObjectPrefab).GetComponent<Bullet>();
        newObj.gameObject.SetActive(false);
        newObj.transform.SetParent(transform);
        return newObj;
    }

    public static Bullet GetObject()
    {
        if(Instance.poolingObjectQueue.Count > 0)
        {
            var obj = Instance.poolingObjectQueue.Dequeue();
            obj.transform.SetParent(null);
            obj.gameObject.SetActive(true);
            return obj;
        }
        else
        {
            var newObj = Instance.CreateNewObject();
            newObj.gameObject.SetActive(true);
            newObj.transform.SetParent(null);
            return newObj;
        }
    }

    public static void ReturnObject(Bullet obj)
    {
        obj.gameObject.SetActive(false);
        obj.transform.SetParent(Instance.transform);
        Instance.poolingObjectQueue.Enqueue(obj);
    }
}

 

오브젝트 풀링 기법을 구현하는 기초적인 코드의 전체는 위의 코드와 같다. 

 

public static ObjectPool Instance;

private void Awake()
{
    Instance = this;

    Initialize(10);
}

 

이 코드를 하나씩 살펴보자. 우선 오브젝트 풀의 경우 오브젝트를 생성하는 어떤 코드에서든 접근이 가능해야하는 경우가 많기 때문에 싱글톤 패턴으로 구현되는 경우가 많다. 

 

[SerializeField]
private GameObject poolingObjectPrefab;

 

그리고 생성해야할 오브젝트의 프리팹을 가지고 있다. 프리팹을 가지고 있어야 하는 이유는 처음 생성해둔 오브젝트를 모두 꺼내주고 나서, 꺼내준 오브젝트를 미처 돌려받기 전에, 오브젝트를 요청받았을 때, 새로운 오브젝트를 생성해서 꺼내주기 위한 것이다.

 

Queue<Bullet> poolingObjectQueue = new Queue<Bullet>();

 

큐(Queue)를 이용해서 생성된 총알 오브젝트를 순서대로 관리한다. 만약 오브젝트 풀에서 오브젝트를 꺼내주는 순서와 돌려받는 순서를 제대로 관리하지 못하면, 하나의 오브젝트를 여러 곳에 빌려주는 등의 문제가 발생할 수 있다.

 

private void Awake()
{
    Instance = this;

    Initialize(10);
}

 

게임 오브젝트가 생성되면 제일 먼저 실행되는 Awake() 함수에서는 싱글톤 패턴에서 사용되는 오브젝트 풀 인스턴스에 자신을 할당하는 일과 오브젝트 풀을 초기화하는 Initialize() 함수를 호출한다. 

 

private void Initialize(int initCount)
{
    for(int i = 0; i < initCount; i++)
    {
        poolingObjectQueue.Enqueue(CreateNewObject());
    }
}

 

Initialze() 함수에서는 매개변수로 받은 초기화 갯수 값에 따라서 CreateNewObject() 함수에서 만들어진 새로운 총알 오브젝트를 pooling Object Queue에 넣어준다. 이렇게 게임이 시작하기 전에 사용될 게임 오브젝트를 미리 적절한 갯수를 만들어 줌으로써 게임 플레이 도중에 발생할 과부하를 게임 준비 과정인 로딩으로 돌릴 수 있다. 모두 잘 알겠지만, 플레이어는 아주 조금 더 긴 로딩은 당장 용납하지만, 플레이 도중에 발생하는 렉은 용납하지 못한다.

 

private Bullet CreateNewObject()
{
    var newObj = Instantiate(poolingObjectPrefab).GetComponent<Bullet>();
    newObj.gameObject.SetActive(false);
    newObj.transform.SetParent(transform);
    return newObj;
}

 

CreateNewObject() 함수는 poolingObjectPrefab으로부터 새 게임 오브젝트를 만든 뒤 비활성화해서 반환하는 역할을 한다. 이렇게 곧바로 비활성화하는 이유는 아직 오브젝트 풀이 꺼내주지 않은 오브젝트는 비활성화된 것으로써 플레이어의 눈에 띄지 않아야하기 때문이다.

 

public static Bullet GetObject()
{
    if(Instance.poolingObjectQueue.Count > 0)
    {
        var obj = Instance.poolingObjectQueue.Dequeue();
        obj.transform.SetParent(null);
        obj.gameObject.SetActive(true);
        return obj;
    }
    else
    {
        var newObj = Instance.CreateNewObject();
        newObj.gameObject.SetActive(true);
        newObj.transform.SetParent(null);
        return newObj;
    }
}

 

GetObject() 함수는 오브젝트 풀이 가지고 있는 게임 오브젝트를 요청한 자에게 꺼내주는 역할을 한다. 단, 모든 오브젝트를 꺼내서 빌려줘서 poolingObjectQueue에 빌려줄 오브젝트가 없는 상태라면 CreateNewObject() 함수를 호출해서 새로운 오브젝트를 생성해서 빌려준다.

 

public static void ReturnObject(Bullet obj)
{
    obj.gameObject.SetActive(false);
    obj.transform.SetParent(Instance.transform);
    Instance.poolingObjectQueue.Enqueue(obj);
}

 

ReturnObject() 함수는 빌려준 오브젝트를 돌려받는 함수이다. 돌려받은 오브젝트를 비활성화한 뒤 정리하는 일을 처리한다.

 

무엇이든지 빌려줬으면 돌려받아야 한다. 이 원칙은 프로그래밍에서도 매우 중요한 원칙으로 사용한 오브젝트를 제대로 돌려받지 않으면 GetObject() 함수를 호출할 때, 계속해서 새로운 오브젝트를 생성하고 사라지지 않은 오브젝트들이 씬에 쌓이게 된다. 이것이 바로 메모리 누수이다.

 

 

코드 작성이 끝나면 씬에 새 게임 오브젝트를 배치하고 오브젝트 풀 컴포넌트를 부착하고 pooling Object Prefab 프로퍼티에 풀링해야할 게임 오브젝트의 프리팹을 할당해주면 된다.

 

 

 

그 다음에는 총알 오브젝트를 생성하는 부분과 파괴하는 부분을 수정해야 한다.

 

public class Shooter : MonoBehaviour
{
    void Update()
    {
        if(Input.GetMouseButton(0))
        {
            RaycastHit hitResult;
            if(Physics.Raycast(mainCam.ScreenPointToRay(Input.mousePosition), out hitResult))
            {
                var bullet = ObjectPool.GetObject(); // 수정
                var direction = new Vector3(hitResult.point.x, transform.position.y, hitResult.point.z) - transform.position;
                bullet.transform.position = direction.normalized;
                bullet.Shoot(direction.normalized);
            }
        }
    }
}

 

우선 생성하는 부분은 Shooter 클래스의 Update() 함수에 Instanitate()로 총알을 생성하던 부분을 ObjectPool.GetObject()로 대체하면 된다.

 

public class Bullet : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Invoke("DestroyBullet", 5f);
    }

    public void DestroyBullet()
    {
        ObjectPool.ReturnObject(this);
    }

    void Update()
    {
        transform.Translate(direction);   
    }
}

 

그 다음 파괴하는 부분은 Bullet 클래스에서 DestoryBullet() 함수에서 ObjectPool.ReturnObject()를 호출해서 오브젝트 풀에 돌려주도록 구현하고 Shoot() 함수에서 Destory() 함수를 호출하던 부분을 Invoke()로 5초 뒤에 DestoryBullet() 함수를 호출하도록 수정한다.

 

 

 

플레이를 해서 테스트 해보면 총알이 일정 갯수만큼 생성되고 나면 더 이상 생성되지 않으며, 시간이 지나면 소멸되어야 하는 총알 오브젝트들이 비활성화되서 오브젝트 풀로 반환되는 것을 확인할 수 있다.

 

 

프로파일러를 통해서 확인해보면 최대 생성량에 도달한 이후에는 Instatiate와 Destroy로 인한 작업이 발생하지 않는 것을 볼 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

애니메이터 컨트롤러 동작 변경 문제

 

이전까지 게임을 제작을 시작할 때, 5.3.5 버전의 유니티를 사용하다가, 변경된 파티클 시스템을 사용하기 위해 유니티를 5.6.0으로 업데이트를 진행했었다. 엔진을 업데이트하면서 변경점으로 인해서 프로젝트에 어느 정도 문제가 발생할 것이라고 생각은 하고 있었지만, 예상외로 별다른 문제없이 유니티 업데이트가 진행되었었다.

 

문제는 애니메이션 쪽에서 발생했다. 제작 중인 게임은 최적화를 위해서 오브젝트 풀링(Object pooling) 기법을 사용하고 있었는데, 이 기법을 간단하게 설명하자면 유니티 상에서 게임 오브젝트를 생성(Instantiate)하고 삭제(Destory)하는 과정에서 발생하는 부하를 줄이기 위해, 게임에 필요한 게임 오브젝트를 미리 생성해서 컨테이너에 저장해두고, 필요하면 게임 오브젝트의 활성화해서 사용하고 죽음이나 파괴 등으로 필요없어진 게임 오브젝트의 비활성화 시키는 방법으로 비활성화 시킨 오브젝트라도 같은 종류의 오브젝트가 필요하면 다시 활성화 시켜서 재활용할 수 있다. 이렇게 하면 빠르게 작동해야하는 게임 도중에 무거운 Instantiate() 함수의 호출이나 이후에 가비지 컬렉터(Gabage Collector) 호출로 이어지는 Destory() 함수의 호출을 최소화할 수 있다.

 

이 오브젝트 풀링 기법과 유니티가 버전업되면서 변경된 애니메이터 컨트롤러의 동작이 만나면서 문제가 발생했다. 이전에 사용하던 5.3.5의 애니메이터 컨트롤러의 경우에는 동작중이던 게임오브젝트를 비활성화 시키고 난 이후에 해당 오브젝트를 재사용하기 위해서 다시 활성화 시키면 애니메이터 컨트롤러의 상태가 초기로 자동으로 돌아갔다.

 

 

그렇기 때문에 애니메이터를 위의 그림과 같이 구성해두면 캐릭터가 사망하면서 Die 트리거가 작동해서 Die 애니메이션이 작동한 이후에 죽은 캐릭터의 게임 오브젝트를 비활성화 시키고, 나중에 이 오브젝트를 재사용하기 위해 다시 활성화 시키면 애니메이터 컨트롤러의 상태는 자동으로 Idle 상태로 돌아오는 방식으로 동작했었다.

 

하지만 유니티를 5.6.0으로 업데이트한 이후에는 게임 오브젝트를 비활성화 시켰다가 다시 활성화 시키면 애니메이터 컨트롤러의 상태가 처음으로 돌아오지 않고 비활성화 시키던 당시의 상태를 유지하는 것으로 애니메이터 컨트롤러의 동작이 변경되었다(5.3.5와 5.6.0 사이의 어느 버전에서 동작이 변경되었을 것이다. 정확한 릴리즈 노트를 찾아보지는 않아서 변경된 정확한 버전은 알지 못한다).

 

그렇기 때문에 죽은 캐릭터의 게임 오브젝트를 재활용하려고 게임 오브젝트를 다시 활성화 시키면 Die 애니메이션이 끝난 상태로 나타나는 버그가 발생하게 되었다.

 

이러한 문제해결하기 위해서는 애니메이터 컨트롤러에서 상태 노드들의 연결을 약간 변경해 주어야 했다.

 

 

애니메이션이 종료된 이후에 제일 처음의 상태로 돌아가야 한다면 위의 그림처럼 그 상태는 반드시 Exit 노드로 연결되어야 한다. 애니메이션의 상태를 Exit 노드와 연결해 주면 그 애니메이션이 끝난 이후에 Exit 노드로 이동한 후 다시 Entry로 돌아가서 제일 처음 애니메이션을 다시 출력한다. 하지만 Die 같은 애니메이션의 경우 아무런 제약 없이 Exit 노드와 연결해주면 캐릭터가 죽어서 넘어진 다음에 다시 벌떡 일어나는 불상사가 발생할 수도 있다. 그런 종류의 불상사를 막아야 하는 경우에는 Exit 노드로 가는 트랜지션(Transition)에 하나의 트리거를 추가해 준 다음, 다시 돌아갈 상황이 되었을 때, 스크립트에서 트리거를 호출해서 애니메이션 상태를 처음으로 돌릴 수 있다.

 

결론적으로 최신 버전의 유니티에서는 애니메이터 컨트롤러가 끊임없이 흘러가게 하기 위해서는 마지막 애니메이션을 절대로 고립된 상태로 두지말고 Exit 노드에 연결해 주어야 한다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

+ Recent posts