개발/게임 개발

로딩 없는 게임 만들기

잠수돌침대 2024. 3. 28. 18:30

이전 포스트모템을 통해 인턴으로 일할 때 배운 몇 가지 기술과 최적화 기법을 Grim Panthera에 적용하는 것에 대해 이야기했다.

오늘 소개하는 포스팅은 이와 관련된 것으로, 가벼운 게임부터 나중에 만들 모바일 게임까지 두루두루 사용할 수 있는 최적화 기법을 소개하고자 한다.

 

이 포스팅 역시 이전에 적어놓았던 원문을 한글로 번역한 버전이니 원문을 읽고 싶다면 아래의 링크를 참고하면 고맙겠다.

https://pesky-panthera.itch.io/grim-panthera-demo/devlog/702958/create-a-game-without-loading

 

Create a game without loading - Grim Panthera(Demo v1.0) by Pesky Panthera

Through my previous postmortem, I talked about applying some of the skills and optimization techniques I learned when I worked as an intern to Grim Panthera. Today's post is related to this, and I'd l...

pesky-panthera.itch.io

 


1. 씬(Scene) 전환 기술


아마 게임을 만드는 몇몇 개발자(또는 플레이어)들은 새로운 화면을 전환할 때 나오는 로딩 화면을 본 적이 있을 것이다. 이 현상 또한 내가 만들었던 'Press Axe' 게임에 적용한 기법인데, 가벼운 게임에 비해 불필요하게 긴 로딩 시간은 플레이어를 짜증나게 하기에 충분했다.

Unity 엔진에서는 장면 전환 시 화면이 잠깐 정지되고 버그가 발생한 것처럼 아무런 응답을 하지 않는다. 개발자들은 플레이어들에게 현상이 버그가 아님을 알리기 위해 로딩 화면을 별도로 구현하고 출력할 것을 적극 장려해야 한다.

하지만 씬의 전환이 빈번한 경우 플레이어의 게임 경험을 망가뜨릴 수 있고, 이것은 게임의 방식이 간단할수록 더욱 두드러지는 경향이 있다.

 

이 화면 좋아하는 사람?

 

이 부분에서 나는 로딩이 없는 게임에 대한 열망을 길러왔고, 이 열망을 Grim Panthera라는 게임에 적용할 수 있어서 정말 좋았다.

 

우리는 인디 게임 개발자로서 여러 간단한 게임을 만들 예정이기 때문에 이 자리를 빌려 Grim Panthera를 어떠한 방식으로 만들었는지 소개해보고자 한다.

 

2. 한 Scene으로 모든 것을 해결 - Object Pool 패턴

 

기본적으로 Scene의 전환이 의미하는 것은 DontDestroyOnLoad의 객체(ex : 싱글턴 패턴으로 사용되는 Game Manager)를 제외하고 모든 객체가 파괴됨과 동시에 다시 새롭게 생성됨을 의미한다.

 

이것은 많은 오버헤드를 발생시키는 주요한 이유 중 하나이며, 이러한 이유로 여러 객체를 자주 생성하거나 파괴하는 행위는 CPU에 많은 부하를 준다.

 

여기서 객체를 생성/파괴하는 것이 아니라 단순히 활성화/비활성화함으로 CPU에 대한 부담을 줄이고자 하는 Object Pool 패턴이 등장하게 된다.

 

풀장을 의미하는 여러 개의 'Pool'을 준비하고 그곳에 필요한 객체를 미리 넣어둔다. 그리고 필요할 때마다 꺼내거나 넣는 과정을 통해 오버헤드를 최소화시킬 수 있다.

 

그리고 가장 중요한 이 패턴의 장점은 따로 사용하기 위한 어려운 코드가 없다는 것이다!

 

Unity 엔진의 관점에서 이를 보면 Instantiate/Destroy가 아닌 SetActive(true/false)로 객체를 제어하여 코드를 작성하는 것을 의미한다. 그리고 이는 자신이 개발 중인 상황이나 조건에 따라 유연하게 코드를 작성하고 제어함으로 구현이 가능하다.

 

3. Grim Panthera에 사용된 방법

 

Grim Panthera를 제작할 때 하나의 Canvas 안에 객체를 넣었다 빼는 식으로 게임을 만들었다 소개한 바 있다. 여기에 사용된 객체는 동적으로 생성/파괴된 것이 아니라 미리 만들어 둔 Stage Pool에서 필요한 객체를 꺼내 사용하는 것으로 코드를 작성하였다.

 

 

 

나같은 경우에는 싱글턴으로 사용되는 GameManager의 역할을 확장하여 각 Pooling 객체를 제어하고 관리하는 공간으로도 활용하였다. 각 Stage는 GameManager 객체의 자식으로 할당되며 게임을 제어하는 방식에 따라 Stage를 전환할 때 Canvas 내의 객체가 해당 Pool에 SetActive(false) 형식으로 다시 들어가는 형태이다.

 

public void ReparentChildren(GameObject from, GameObject to, bool toActive) 
{      var children = new Transform[from.transform.childCount];
      for(int i = 0;i< from.transform.childCount; i++)
      {
          children[i] = from.transform.GetChild(i);
      }
      foreach(Transform child in children)
      {
         child.gameObject.SetActive(toActive);
          child.SetParent(to.transform);
      }
 }

 

위의 함수는 Canvas 내의 객체를 특정 Pool의 객체와 교환하는 함수이다. 마지막 파라미터로 받은 true/false 인자를 통해 SetActive 함수를 유동적으로 관리할 수 있게 하였다.

 

위 코드가 실행됨에 따라 코드를 통해 객체의 자식 요소들이 동적으로 변경됨으로 ChildCount 함수 등을 사용할 때 특히 주의를 요한다.

 

마지막으로 Object Pool 패턴 사용시 주의할 사항이 있다.

 

먼저 모든 객체가 순환된다는 가정하에 코드를 작성해야 한다. 이는 최초 생성되면 한 번만 실행되는 Start() 함수의 동작과 우리가 의도하려는 객체의 상황이 완전히 다르다는 것을 인지하고 있어야 함을 의미한다. 따라서 우리는 Start() 함수에 초기화 과정을 넣는 것이 아닌 OnEnable() 함수 또는 새로운 함수를 작성하여 활성화가 될 때마다 이를 실행해주어야 할 것이다.

 

두 번째, 객체가 활성화된 상태에서 컴포넌트나 값의 변경이 있을 경우 개발자가 다시 지정해주지 않는 이상 그 값은 초기화되지 않는다는 사실이다. 따라서 객체를 가져올 때마다 초기화 함수 또는 그 메서드를 포함하여 실행을 시켜야 한다.

 

4. 진짜 끝!

 

해당 패턴을 사용할 때의 장점과 이를 사용하는 방법에 대해 간략하게 살펴보았다. 하지만 장점만 있는 패턴은 존재하지 않는 법. 해당 패턴의 단점으로는 기존 방식보다 사전에 생성하는 객체가 많아 최초 리소스 소모가 훨씬 심하다는 단점을 가진다. 하지만 우리는 그 리소스가 부족할 정도로 쩌는 게임을 만드는 것이 아니기 때문에 기회가 된다면 다음 게임에 해당 패턴을 적용해볼 것으로 추천하고 싶다.

'개발 > 게임 개발' 카테고리의 다른 글

Grim Panthera Postmortem  (0) 2024.03.22
그래서 이제 뭐함?  (3) 2023.09.03
뭘 이렇게 많이 적으래? - 5  (0) 2023.07.24
그래서 무슨 게임을 만들까? - 4  (0) 2023.07.18
돌고 돌아 원점으로 - 3  (0) 2023.07.10