리얼 개발

[게임 디자인 패턴] Part 1. 도입 본문

디자인 패턴

[게임 디자인 패턴] Part 1. 도입

econo-my 2024. 7. 2. 17:41

이 카테고리는 "로버트 나이스트롬""게임 프로그래밍 패턴"을 보고 개인적으로 필요하다고 생각되는 내용을 요약한 글입니다.

 

패턴의 개념이란?

패턴은 오랫동안 정형화되고 축적된 경험을 추상화한 지식 단위이다. 여러 사람이 공유하는 개념이기 때문에 패턴 이름만으로도 많은 정보를 한 번에 정확하게 전달할 수 있다.

해당 책의 요지

“GoF의 디자인 패턴”에 나오는 패턴들 중 저자가 실제 게임을 제작하며 썼던 패턴들을 소개해주고 있으며 딱딱한 ‘직원 정보’, ‘은행 계좌’가 아닌 ‘몬스터’, ‘업적’ 같은 것들을 예제 삼아 패턴을 설명하고 있다. 또한 이 책 어디에도 3D 그래픽스에 필요한 선형대수나 게임 물리에 필요한 미적분 내용은 나오지 않는다. AI의 탐색 트리에서 알파-베타 가지치키를 하는 방법이나, 오디오가 방 안에서 들리는 것처럼 반향 효과를 시뮬레이션하는 방법도 알려주지 않는다.

대신 이 책은 그런 요소들 사이에 들어가는 코드에 대해 알아볼 것이다. ‘어떻게 코딩할 것인가’ 보다는 ‘어떻게 구조를 잡을 것인가’ 를 다룬다.

목차

  • 도입
    • 구조, 성능, 게임
  • 디자인 패턴 다시 보기
    • 명령 패턴
    • 경량 패턴
    • 관찰자 패턴
    • 프로토타입 패턴
    • 싱글턴 패턴
    • 상태 패턴
  • 순서 패턴
    • 이중 버퍼
    • 게임 루프
    • 업데이트 메서드
  • 행동 패턴
    • 바이트코드
    • 하위 클래스 샌드박스
    • 타입 객체
  • 디커플링 패턴
    • 컴포넌트
    • 이벤트 큐
    • 서비스 중개자
  • 최적화 패턴
    • 데이터 지역성
    • 더티 플래그
    • 객체 풀
    • 공간 분할

 

Part 1. 도입

좋은 소프트웨어 구조란?

좋은 소프트웨어 구조란 뭔가를 고쳐야 할 때 그럴 줄 알았다는 듯이 코드가 준비되어 있는 걸 의미한다. 즉, 코드를 거의 건드리지 않고도 적당한 함수 몇 개만 호출하면 원하는 작업을 할 수 있어야 한다.

말은 좋지만 현실적으로는 쉽지 않다.

하나씩 나눠 생각해보자. 먼저 ‘구조는 변경과 관련이 있다’는 점이다. 얼마나 쉽게 변경할 수 있느냐가 코드 설계를 평가하는 척도가 된다. 변화가 없는 코드는 출발선을 결코 떠나지 않는 달리기 선수와 다를바 없다.

 

디커플링은 어떻게 도움이 되는가?

반드시 그런 건 아니지만, 소프트웨어 구조의 많은 부분이 ‘코드 파악’ 단계와 관련이 있다. 머릿속에 코드를 로딩하는 게 굉장히 느리기 때문에, 한 번에 이해해야 하는 코드 양을 최대한 줄이기 위해 여러 방법이 나왔다. ‘디커플링’은 다양하게 정의할 수 있겠지만, 책은 양쪽 코드 중에서 한쪽이 없으면 코드를 이해할 수 없을 때 둘이 커플링되어 있다고 본다. 두 코드를 디커플링하면, 각각을 따로 이해할 수 있게 된다. 작업에 관련된 코드가 두 코드 중에서 하나에만 연관되어 있다면, 한쪽 코드만 머리에 집어 넣으면 된다.

작업에 들어가기 전에 알아야 할 지식의 양을 줄이는 것. 이것이 저자가 생각하는 소프트웨어 구조의 핵심 목표다.

디커플링의 다른 정의는 ‘어느 한 코드를 변경했을 때 다른 코드를 변경하지 않아도 된다’ 이다.

 

성능과 속도

소프트웨어 구조와 추상화가 게임 성능을 저하시킨다는 비판도 있다. 코드를 유연하게 만드는 많은 패턴이 가상 함수, 인터페이스, 포인터, 메시지 같은 메커니즘에 의존하는데, 다들 어느 정도 런타임 비용을 요구한다. 소프트웨어 구조 개발은 대부분 가정에 기반한다. 관찰자, 이벤트 큐를 사용하는 이유는 당장은 서로 통신하는 부분이 둘 밖에 없더라도 나중에 셋, 넷으로 쉽게 늘리고 싶기 때문이다. 최적화 기법은 구체적인 제한을 선호한다. 적이 256개 이하일거라고 확신한다면? ID를 1바이트로 압축할 수 있다.

(*) 재미있는 반례가 C++ 템플릿이다. 템플릿 메타프로그래밍은 런타임 낭비 없는 인터페이스 추상화 방법을 제공한다.

 

균형 잡기

게임 개발에는 다음과 같은 목표가 있다.

  1. 프로젝트 개발 기간 동안 코드를 쉽게 이해할 수 있도록 구조를 깔끔하게 만들 수 있다.
  2. 실행 성능을 최적화하고 싶다.
  3. 지금 개발 중인 기능을 최대한 빠르게 구현하고 싶다.

이들 목표는 서로 어느 정도 상반된다. 좋은 구조는 장기적으로 생산성을 높여주지만, 구조를 좋게 유지하려면 코드를 변경할 때마다 노력을 더 들여야 한다.

이들 목표가 모두 일종의 ‘속도’와 관련이 있다는 점이 재밌다. 각각 장기적인 개발 속도, 게임 실행 속도, 단기적인 개발 속도에 해당한다.

 

최대한 빠르게 구현한 결과물이 최고의 실행 속도를 내는 경우는 드물다. 최적화에는 엄청난 개발 기간이 소요된다. 최적화하고 나면 코드가 고착되는 경향이 있다. 극도로 최적화된 코드는 유연하지 않고 고치기가 매우 어렵다. 셋 다 각각 장단점이 있기 때문에 트레이드오프 외에는 명쾌한 정답이 없다.

“정답은 없다. 오답에 대한 다양한 취향이 있을 뿐이다.” 

 

내 생각에는 이 말이 컴퓨터 과학을 관통하는 말인 것 같다. 운영체제의 스케쥴링, 알고리즘들의 속도와 메모리의 상관 관계를 생각해보면 항상 정답은 없고 어딘가 안 좋은것들 중 최선을 고르는 느낌이기 때문이다.

 

단순함

이런 제약을 완화할 방법이 혹시라도 하나 있다면 단순함이 아닐까 싶다. 저자는 코드를 최대한 간결하게, 문제를 직접 해결하는 방향으로 짜려고 노력한다. 이런 코드는 읽어보면 의도를 바로 알 수 있고, 그 외의 다른 해결 방법은 떠오르지 않는 법이다. 자료구조와 알고리즘을 순서대로 먼저 잡아놓고 여기서부터 다른 방법을 찾아나간다. 코드를 단순하게 유지하면 전체 코드를 줄일 수 있다. 이러면 코드를 고칠 때 먼저 머리에 담아야 할 코드 양이 줄어든다.

 

문제들은 대부분 우아하지 않고, 수많은 유스케이스로 뒤덮여 있다. 조건이 A일 때는 B를 해야 하지만, 조건이 C일 때는 D를 해야 하는 식이다. 즉, 수많은 상황을 지원해야 한다.가장 간단한 해결책은 모든 유스케이스를 하나하나 코드로 옮기는 것이다. 생각나는 대로 조건문을 마구 늘어놓는 것인데, 초보 개발자가 흔히 이렇게 한다.

 

이런 코드는 전혀 우아하지 않고, 예상과 조금만 다른 입력이 들어와도 전혀 동작하지 않는다. 우아한 해결책이란 일반적인 , 그러니까 적은 로직으로도 많은 유스케이스를 정확하게 처리할 수 있는 코드를 뜻한다.

 

마치며

  • 추상화와 디커플링을 잘 활용하면 코드를 점차 쉽고 빠르게 만들 수 있다. 하지만, 지금 고민 중인 코드에 유연함이 필요하다는 확신이 없다면 추상화와 디커플링을 적용하느라고 시간 낭비하지 말자.
  • 개발 내내 성능을 고민하고, 최적화에 맞게 설계해야 한다. 하지만 가정을 코드에 박아 넣어야 하는 저수준의 핵심 최적화는 가능하면 늦게 하라.
  • 게임 기획 내용을 확인해볼 수 있도록 빠르게 개발하되, 너무 서두르느라 코드를 엉망으로 만들지 말자. 결국 그 코드로 작업해야 하는 건 우리다.
  • 나중에 버릴 코드를 잘 만들겠다고 시간 낭비하지 말자. 록 스타들이 호텔 방을 어지르는 이유는 다음날 계산하고 나가면 그만이라는 것을 알기 때문이다.
  • 무엇보다, 뭔가 재미있는 걸 만들고 싶다면 먼저 만드는 데에서 재미를 느껴보라.