고전 우주 슈팅 게임 최적화 작업

개요

고전 우주 슈팅 게임 제작 하고 시간이 많이 흐른 후 프로젝트를 다시 열어 보았습니다. 처음 출시 당시에는 보이지 않았던 아쉬운 부분이 여기저기 많았습니다. 출시를 자체를 우선하다보니 그렇게 된 것 같았습니다. 새로운 게임을 만드는 것이 더 재미있을 것 같았습니다. 하지만 새 게임에서도 이전과 비슷한 실수를 할 가능성이 높을 것 같았습니다. 나중을 위해서 이미 만들어진 게임을 최적화 하는 작업도 의미가 있겠다는 생각이 들었습니다.

최적화 대상

프로젝트를 열고 일단 C# 스크립트를 전체적으로 점검해 보았습니다. Update(), FixedUpdate() 등 짧은 시간에 많이 호출되는 메소드 안에 필요없는 연산이 많았습니다. 거기에 반복적으로 게임오브젝트를 참조하는 코드도 많았습니다.

다음으로 유니티 에디터에서 게임 오브젝트들을 살펴 보았습니다. 필요없는 컴포넌트가 붙어있는 것이 있었습니다. 나중에 지운다고 생각하고 아예 잊어버렸던 것 같았습니다. 그리고 의욕이 앞서서 모바일에 대한 최적화 부분을 고려하지 않은 부분도 보였습니다.

1차로 C# 스크립트 부분을 최적화 하고 다음으로 게임 오브젝트 부분을 최적화 하기로 계획하고 실행에 들어갔습니다.

C# 스크립트 최적화

1. Update() 와 같은 메소드 내의 반복 연산 제거

Update() 와 같이 짧은 시간 많이 호출되는 메소드에서 연산되는 부분을 최소화 합니다. 다음의 예는 적의 y 좌표를 비교하는 부분입니다.

void Update()
{
    if (enemyPosition.y <= System.Math.Abs(Camera.main.ViewportToWorldPoint(Vector3.zero).y))
    {
        ...
    }
    ...
}

System.Math.Abs(Camera.main.ViewportToWorldPoint(Vector3.zero).y) 이 부분은 한번만 가져와서 변수에 넣고 사용하도록 변경했습니다. 가장 기본적인 사항인데 눈에 보이는 기능에만 집중하다보니 비효율적으로 되어 있었습니다. 개발 중 계속해서 더 효율적으로 할 수 있는 방법은 없는지 계속 고민하는 자세가 필요할 것 같습니다.

2. 게임 오브젝트 생성 및 파괴 부분 오브젝트 풀링으로 전환

슈팅게임의 특성 상 많은 수의 탄환과 적이 등장합니다. 게임 오브젝트를 생성할 때 Instantiate 메소드를 사용하고 파괴할 때 Destroy 메소드를 사용했습니다. 이런 반복적인 생성/파괴가 많은 비용이 드는 작업입니다. 유니티 2021 버전부터 자체적으로 제공하는 IObjectPool 을 이용해서 오브젝트 풀링을 사용하는 방식으로 변경했습니다. 자세한 내용은 유니티(Unity) IObjectPool 을 사용한 오브젝트 풀링(Object Pooling) 를 참조하시면 됩니다.

3. 게임 오브젝트 및 참조 객체 캐시

게임 오브젝트를 제어하기 위해서 스크립트 내에서 다음과 같이 참조를 하게 됩니다. 적 보스를 맞혔을 때 효과 애니메이션을 표시하시 위해서 Animator 를 참조하는 코드 입니다.

private void OnTriggerEnter2D(Collider2D collision)
{
    var enemyBossBlinkAnimator = gameObject.GetComponent<Animator>();
    ...
}

이것을 클래스 로컬 변수로 정의하고 Start() 메소드에서 참조하도록 변경했습니다.

private Animator enemyBossAnimator;
void Start()
{
    ...
    enemyBossAnimator = gameObject.GetComponent<Animator>();
    ...
}

4. 게임 오브젝트 태그 비교 부분 변경

게임 오브젝트의 비교 판단을 위해서 gameObject.tag 를 사용한 부분은 gameObject.CompareTag 메소드를 이용한 방식으로 변경했습니다. gameObjec.tag 가 호출되면 문자열을 위한 메모리 할당이 발생해서 비효율적입니다.

//if (collision.gameObject.tag == "Bullet")
if (collision.gameObject.CompareTag("Bullet"))
{
    ...
}

게임 오브젝트

1. Collider 변경

충돌을 위해서 게임 오브젝트에 Collider를 부착했고 의욕이 앞서서 정밀한 판정을 해야겠다는 생각으로 복잡한 Polygon Collider 2D 를 사용했습니다.

너무 많은 Point 가 정의되어 있는 Collider

이것을 최대한 간략한 형태로 변경했습니다. 원형 모양의 게임 오브젝트에는 Circle Collider 2D를 적용하고 Polygon Collider 사용은 최소화하고 꼭 사용해야 하는 경우 점의 개수를 줄여 적용했습니다.

2. Animation Controller 변경

여러가지 모양의 적이나 적 보스의 애니메이션 부분은 보여지는 부분만 다르고 내부적으로 동작하는 것은 동일한 경우가 많습니다. 각 애니메이션 별로 Animation Controller 가 따로 존재하게 되면 관리가 매우 어렵게 됩니다. 뭔가 상태가 추가 되거나 변경이 되어야 하는 경우 모두 따로 변경해 주어야 해서 많은 시간이 소요됩니다.

각각 Animator Controller 가 있던 것을 Animator Override Controller 를 이용해서 정리했습니다. 자세한 사항은 유니티(Unity) Animator Override Controller 이용해서 애니메이션 변경하기 를 참조하시면 됩니다.

3. 사용하지 않는 Asset 정리

개발 초기에는 Asset을 간략하게 만들기도 하고 임시로 사용하는 경우가 많습니다. 진행이 되면서 다른 것으로 대치되거나 하는데 추가만 했지 삭제하는 작업을 하지는 않았습니다. 나중에 보니 어떤 Asset 이 사용되지 않는지 구분하기 어려웠습니다. 어디에서 사용되었는지 알려주는 기능이 있기는 했지만 결국 직접 확인해서 삭제해야 했습니다.

시간이 흐르다 보니 사용하지 않은 Asset 이라고 생각해서 지웠는데 나타나지 않았서 부랴부랴 다시 복원한 경우도 있었습니다. Asset 에 변화가 있는 경우 사용하지 않으면 바로 삭제하는 것이 가장 좋다는 것을 이 경험으로 알게되었습니다.

그리고 Asset 을 관리하기 편하게 미리 정리해 두는 것도 필요하다는 생각이 들었습니다. 파일이 많아지고 정리가 되어 있지 않으니 참조하기가 아주 어려웠습니다.

이미 출시된 게임을 정리하고 최적화 했던 경험을 정리해 보았습니다. 모든 것을 다루지 못했고 개발하시는 게임과 맞지 않는 부분이 있을 수 있지만 이 글을 참조하셔서 시행착오를 최소화 하시는데 도움이 되었으면 합니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Time limit is exhausted. Please reload the CAPTCHA.