본문 바로가기

게임수학

게임 앱 환경

씬 그래프

게임엔진은 다양한 해석 가운데 하나의 그래픽스 관련 처리를 도맡아 하는 것이라 보는 사고방식이다. 몰입감을 자아내는 엔터테인먼트 측면에서 게임의 강점은 십중팔구 시각적 자극이며, 프로젝트에서 개발 비용이 가장 많이 할애되는 부분이 되기 쉽다. 원래 그래픽스 처리를 혼자서 도맡으려면, 어떤 장면에 어떤 물체가 있고 서로 어떤 관계성이 있는지 등, 렌더링 대상에 관한 일련의 정보 데이터베이스도 받아와야 한다. 그래서 일반적으로 게임엔진에는 에셋 관리를 위한 시스템이 있다. 에셋은 게임을 만드는 소재 일반을 가리키면 이미지, 3D 모델의 지오메트리 정보, 음악, 텍스트 등 온갖 데이터를 포함한다.
유니티에는 에셋 데이터베이스가 있으며, 파일시스템상에서 프로젝트 폴더 아래에 존재하는 정적 파일에 대해서는 파일별로 메타데이터가 기록된 meta 파일을 만든 다음 참조 정보를 모두에서 관리한다.

파일시스템 상의 에셋은 게임 실행 시 메모리에 로드되어 게임 씬에 배치된다. 플레이어가 게임이라고 인식하는 세계가 이 씬이다. 이때 빈 상자 대신 장면을 구성하는 캐릭터, 배경, 조명 등 모든 요소가 제작자의 의도대로 갖추어져 재현되지 않는다면 게임의 장면이라고 할 수 없다. 모든 것은 씬의 구조를 재현하기 위한 것이다. 

씬 내부에 어떠한 오브젝트가 있는지, 씬의 내용을 포함한 구조를 나타내는 데이터를 씬 그래프라고 한다. 씬 그래프라는 용어의 사용법은 다음 두 가지다.

  • 협의의 데이터 구조로서의 씬 그래프
  • 광의의 개념으로서의 씬 그래프

전자인 협의의 씬 그래프는 게임 씬 내의 오브젝트 군을 그룹으로 나누어, 렌더링이나 물리연산을 할 때 컴퓨터가 효율적으로 처리할 수 있게 하는 것이다. 데이터 구조로서는 팔진트리나 이진 공간 분할 트리가 주로 채용된다. 유니티의 3D 물리엔진인 엔비디아 PhysX와 2D 물리엔진인 Box2D는 공간 분할에 AABB트리를 사용한다. 협의의 씬 그래프는 어떤 처리를 적용할 범위를 확정하고, 처리가 불필요할 영역에 있는 오브젝트를 처리 대상에는 제외하는(렌더링으로 말하면 오클루전 컬링) 것이 주된 목적이다. 
한편, 더 높은 수준에서의 광의의 씬 그래프란 유니티의 하이라키처럼 친자관계가 있는 오브젝트군을 씬에 등록하여 연결하는 데이터베이스로 씬을 구성하는 오브젝트 계층의 레지스트리를 가리킨다. 에셋 데이터베이스도 씬 그래프에 연관된 에셋에 대한 참조 맵으로서 관리된다. 이런 측면에서 보면, 게임엔진이란 씬 그래프 관리자다. 다시 말해, 게임에 관련된 오브젝트의 씬 내에서의 배치와 관리를 일괄적으로 쉽게 진행하기 위한 도구다.

컴포넌트 지향

게임을 비롯한 일반적인 오브젝트 지향 소프트웨어 개발에서는 다음과 같은 원칙에 주목한다.

  • 오브젝트를 캡술화하여 은폐된 부분과 공개된 API를 구별한다.
  • 이미 있는 것을 재개발하지 않고, 가능한 한 재이용성을 의식해 개발한다.

유니티는 일반적인 게임엔진의 특징을 모두 가진다. 렌더링이나 물리연산을 지원하는 구조는 몰론이고, 에셋 데이터베이스나 씬 그래프로서 기능하는 하이라키도 있다. 하지만 유니티의 특징은 다른 곳에 있다. 수많은 게임엔진 중에서도 유니티는 게임엔진으로서 그 자체가 풍부한 기능을 갖춘 뛰어난 패키지라는 점을 더해, 쉽게 컴포넌트를 재이용할 수 있는 모던한 아키텍처를 채용하였다.

컴포넌트 지향의 목적은 관심의 분리다. 캡슐화나 모듈화로 문제를 나누고, 각 담장자가 자신이 담당하는 부분에만 집중하게 하여, 분업을 촉진한다. 유니티의 부품 에셋인 프리팹은 그 결과의 하나다. 

 

유니티는 씬에 존재하는 개개의 오브젝트에 MonoBehaviour를 기반으로 해서 상속할 클래스를 나타내는 C# 또는 자바스크립트로 기술된 스크립트 컴포넌트를 스택 형태로 덧붙여, 새로운 행동을 추가하는 컴포지션 구조를 채용한다. 이 점은 상속을 통해, 확장되는 오브젝트 지향 클래스 계층과는 위상을 달리한다.

상속은 is-a 관계로 표현된다. 예를 들어, 자동차는 기계다라는 문장은 기계라는 상위 개념의 구체화의 하나로 자동차가 있음을 뜻한다. 반면 컴포지션은 자동차는 바퀴를 가진다라는 자동차가 부품으로서 바퀴를 가진다는 has-a 관계를 나타낸다.

 

상속에는 개발을 진행함에 따라, 상속 계층이 깊어지기 쉽다는 단점이 있다. 상속 대신 컴포지션을 사용하면, 이러한 상속의 복잡성도 피할 수 있고, 캡슐화가 더욱 촉진되는 장점이 생긴다.

Monobehaviour 자체는 스크립트 컴포넌트 기반 클래스로서 유니티 컴포넌트 일반의 라이프 사이클 기능을 구현하고, 사용자 정의 컴포넌트에  다형성을 제공한다.

유니티의 각 컴포넌트끼리는 GetComponent API와 SendMessage API를 통해 추가된 GameObject 하위 구조나 씬 그래프를 구성하는 하이라키부터 대상 오브젝트를 탐색해 발견하고, 메시지를 보내 서로 통신한다. 컴포넌트 지향을 추진할 때는 설령 로컬 머신 내부에서 완결되는 시스템이라 해도 컴포넌트간 메시지 패싱의 통신 오버헤드가 성능 관점에서 문제될 수 있으므로, 메시지 라우팅이 최적화 되도록 설계되어야 한다.

게임 루프

게임의 몰입감을 높이기 위한 핵심은 실시간에 가까운 수준의 처리 속도다. 이간의 눈에 자연스럽게 보이려면 가능한 1초에 60회 렌더링 프레임을 갱신하고, 물리연산은 그것을 웃도는 비율로 갱신하는 것이 바람직하다. 그러려면 60분의 1초(16ms) 이내에 1프레임의 렌더링을 마쳐야 한다. fps란 초당 몇 프레임 그릴 수 있는지를 측정하는 방법으로, 다시 말해  60fps가 목표가 된다.

게임 루프는 이같은 갱신처리를 표현하기 위한 제어 구조로, 게임의 메인 스레드에서 while 루프로서 항상 실행되며 루프 1회에 1프레임을 그린다. 이 같은 구조는 게임 외 장면에서도 사용한다. 예를 들어 ios의 경우 CADisplayLink와 NSRunLoop 등 유저의 액션에 실시간으로 응답하는 종류의 GUI 애플리케이션이라면 반드시 갖추는 구조로 일반적으로 게임 이외의 일반 앱에서는 메인 이벤트 루프나 메시지 루프, 메시지 펌프라 불린다. 
유저 입력에 대한 응답성을 높이고자 최근에는 모바일 OS의 UI도 보통 60fps으로 렌더링한다. 하지만 모바일 디바이스에서 게임은 특히 부하가 높은 애플리케이션이므로, 배터리 소모를 줄이려면 게임 루프 처리로 CPU를 계속 점유하는 것은 피하고, 씬에 따라서는 30fps나 24fps와 같은 낮은 fps라도 허용범위라고 판단할 수 있는 때는 강제로 프레임레이트를 낮게 억제하는 것이 표준적인 방법이다.

Monobehaviour의 기능 중 하나는 게임 루프의 갱신 통지 이벤트를 Update/FixedUpdate 메서드로 구독하는 것이다. 이는 디자인 패턴에서 말하는 Observer 패턴에 해당한다. 모던한 GUI 애플리케이션 프레임워크라면 이벤트 루프는 일반적으로 프레임워크 쪽에 은폐되며, 루프 제어 구조가 그대로 유저에게 보이는 코드 상에 노출되는 일은 거의 없다. 이른바 제어의 반전이라는 불리는 패턴으로 구현되는 것이다. 다시 말해 애플리케이션 코드는 제어의 주체가 된다기보다는 수동적인 역할로 필요에 따라서는 애플리케이션에 제어를 전달하는 엔트리 포인트가 프레임워크 쪽에서 준비된다.

 

'게임수학' 카테고리의 다른 글

셰이더  (0) 2025.02.02
곡선  (0) 2025.02.02
행렬  (1) 2025.01.30
벡터  (0) 2025.01.30
좌표계  (0) 2025.01.28