게임을 만들기 전에 해야 할 일이 하나 있습니다. 프레임워크를 켜는 것입니다.
Unity 로 게임을 처음 만들 때는 빈 GameObject 에 스크립트를 붙이고 Start() 에 코드를 쓰면 바로 무언가가 움직였습니다. 프레임워크를 쓰면 순서가 조금 달라집니다. 엔티티를 띄우는 창구, 이벤트를 나르는 통로, 게임의 흐름을 쥐는 진행자가 먼저 켜져 있어야 하고, 우리 코드는 그 위에서 "지금 네 차례다"라는 호출을 받아 실행됩니다.
이 장에서는 게임을 한 줄도 만들지 않습니다. 대신 Play 버튼을 눌렀을 때 콘솔에 아래 한 줄이 찍히는 상태까지 갑니다.
ProcedureMain: Enter
Plain Text
복사
별것 아닌 것 같지만, 이 한 줄이 찍힌다는 것은 프레임워크의 스무 개 모듈이 전부 켜졌고, 그 프레임워크가 우리가 쓴 클래스를 찾아내 호출했다는 뜻입니다. 이후의 모든 장은 이 한 줄 뒤에 코드를 이어 붙이는 일입니다.
이 한 줄에서 출발해 10장까지 따라 오면 아래 게임이 됩니다. 캐릭터를 고르고, 몰려오는 적을 무기 넷으로 막고, 동료를 불러 함께 싸웁니다. 영상은 완성된 예제를 에디터에서 실제로 플레이한 것입니다.
이 책에서 만드는 게임 — 캐릭터 선택(3장), 적의 추적(5장), 무기 넷(6장), 동료(8장)
이 장에서 하는 일
1.
프레임워크를 프로젝트에 넣는다
2.
프레임워크가 무엇으로 이루어져 있는지 씬에서 직접 본다
3.
안 하면 아무 에러 없이 실패하는 설정 세 가지를 해 둔다
4.
첫 프로시저를 만들어 프레임워크가 우리 코드를 부르게 한다
1.1 무엇을 준비하나
준비물 | 설명 |
Unity 6 (6000.0 이상) | URP 3D 템플릿으로 만든 프로젝트. 이 책의 화면은 6000.6 기준입니다 |
프레임워크 | GameFramework-Unity-0.1.0.unitypackage — 코어 DLL, Unity Game Framework, 프레임워크 프리팹, 시작 예제가 한 파일에 들어 있습니다. (코어 DLL 만 든 GameFramework-Core, 이 책의 완성본인 GameFramework-Example 도 함께 배포됩니다) |
아트 에셋 | 예제 프로젝트의 Assets/Art (캐릭터·적·이펙트·사운드). Unity 의 공개 샘플 Zombie Toys 에서 온 것입니다 |
씬 | 예제의 Assets/Scenes/MainScene.unity — 장난감 방 배경과 조명, 카메라가 놓여 있습니다 |
이 책은 완성된 예제 프로젝트를 옆에 열어 두고, 게임 코드가 들어가는 Assets/GameMain 폴더를 처음부터 한 파일씩 만들어 가는 방식으로 진행합니다. 아트와 씬은 예제의 것을 그대로 쓰고, 우리가 직접 쓰는 것은 C# 코드와 프레임워크 설정입니다.
1.2 프레임워크 넣기
프레임워크는 .unitypackage 파일 하나로 되어 있습니다. Unity 에서 Assets → Import Package → Custom Package… 로 GameFramework-Unity-0.1.0.unitypackage 를 고르고 Import 를 누르면 끝입니다. 파일들이 프로젝트의 Assets 아래에 풀립니다.
프레임워크는 세 층으로 되어 있고, 풀린 폴더가 그 층을 그대로 보여 줍니다.
내 프로젝트/
├─ Assets/
│ ├─ Plugins/
│ │ └─ GameFramework.dll ① Game Framework — 엔진과 무관한 순수 C# 코어 (어셈블리: GameFramework)
│ ├─ Scripts/ ② Unity Game Framework — 코어를 Unity 에 잇는 계층 (컴포넌트, EntityLogic, UIFormLogic)
│ │ ├─ Editor/ 전용 인스펙터와 상단 메뉴
│ │ └─ Starter/ 시작 예제 — 넣자마자 돌려 볼 수 있는 씬과 절차
│ ├─ Prefabs/
│ │ └─ GameFramework.prefab 프레임워크를 켜는 프리팹
│ └─ GameMain/ ③ 우리 게임 — 이 책에서 쓰는 코드 (아직 없다)
├─ Packages/
└─ ProjectSettings/
Plain Text
복사
① 코어는 Unity 를 전혀 모르는 순수 C# 라이브러리입니다. Unity 프로젝트 바깥의 별도 C# 프로젝트로 만들어 DLL 로 빌드한 것이 Assets/Plugins 에 들어 있습니다. ② Unity Game Framework 는 그 코어를 MonoBehaviour·프리팹·Addressables 에 연결하는 소스 코드입니다. ③ 우리 게임은 그 둘을 써서 만듭니다.
예제 프로젝트의 Assets 폴더. ① Plugins — 코어 DLL ② Scripts — Unity Game Framework ③ GameMain — 우리 게임 ④ Prefabs — 프레임워크 프리팹
그림은 완성된 예제 프로젝트의 Assets 폴더입니다. 위의 트리에서 본 세 층이 ①②③ 으로, 프레임워크를 켜는 프리팹이 ④ 로 보입니다. 나머지 폴더는 아트(Art, Audio, Fonts)와 씬, 설정 파일, 그리고 원본 샘플에서 넘어온 것들입니다. 새 프로젝트에 프레임워크 패키지만 넣었다면 ①②④ 만 생기고, ③ 은 이 책을 따라가며 직접 만듭니다.
Import 가 끝나고 상단 메뉴에 Game Framework 가 생겼으면 제대로 들어간 것입니다. Assets/Scripts/Editor 폴더에서 나오는 메뉴이고, 아래에서 볼 전용 인스펙터도 여기서 나옵니다.
프레임워크가 실제로 도는지도 바로 볼 수 있습니다. Assets/Scripts/Starter/GameFrameworkStarter.unity 를 열고 Play 하면 콘솔에 아래 줄이 찍힙니다.
Game Framework is running (ProcedureStarter).
Plain Text
복사
이 씬과 ProcedureStarter 는 프레임워크에 들어 있는 시작 예제입니다. 프레임워크 프리팹은 처음에 이 절차로 시작하도록 되어 있어서, 아무 씬에 프리팹을 놓아도 일단 돌아갑니다. 1.7 절에서 이것을 우리 절차로 바꿉니다.
코어의 소스가 궁금하거나 고쳐 보고 싶다면 메뉴 Game Framework → Download Core Source 를 누릅니다. 확인 창에서 Download 를 누르면 코어의 저장소가 프로젝트의 External/GameFramework 폴더에 내려받아지고, 그 솔루션을 빌드하면 새 GameFramework.dll 이 Assets/Plugins 로 복사됩니다. 이 책을 따라 하는 데는 필요 없습니다.
프레임워크는 Unity 패키지 Addressables 와 SharpZipLib 을 필요로 합니다. Import 할 때 "이 패키지는 Package Manager 의존성이 있다"는 창이 뜨면 Install 을 눌러 함께 설치합니다. 건너뛰었다면 Package Manager 에서 com.unity.addressables 와 com.unity.sharp-zip-lib 을 직접 넣습니다.
여기서 알아 둘 것 — 코어만 다른 어셈블리다
코어는 DLL 이라 우리 코드와 다른 어셈블리(GameFramework)입니다. 반면 Assets/Scripts 의 Unity Game Framework 는 소스 그대로 들어 있어서, 우리가 Assets/GameMain 에 쓸 코드와 같은 어셈블리(Assembly-CSharp)로 함께 컴파일됩니다.
지금은 "그렇구나" 하고 넘어가도 됩니다. 다만 이 사실 때문에 프레임워크의 메서드를 오버라이드할 때 쓰는 키워드가 정해집니다. 부모 클래스가 코어의 것이냐, Unity Game Framework 의 것이냐에 따라 한 단어가 달라집니다. 이 장의 마지막에서 실제로 만나게 됩니다.
1.3 프레임워크는 어떻게 생겼나
프레임워크를 "켠다"는 것은 구체적으로 프리팹 하나를 씬에 올려 두는 것입니다. 예제의 Assets/Prefabs/GameFramework.prefab 을 열어 보면 이렇게 생겼습니다.
MainScene 의 Hierarchy. GameFramework 오브젝트 아래에 모듈별 자식이 스무 개 달려 있다
루트 오브젝트 하나에 자식이 스무 개 달려 있고, 자식 하나가 모듈 하나입니다. 이름만 읽어도 무슨 일을 하는지 짐작이 갑니다. Entity 는 캐릭터와 적을 띄우고, Event 는 알림을 나르고, Sound 는 소리를 냅니다.
스무 개가 많아 보이지만 이 책에서 직접 다루는 것은 일곱 개뿐입니다.
자식 오브젝트 | 하는 일 | 처음 쓰는 장 |
Procedure | 게임의 큰 흐름(플레이 중 → 게임오버 → 다시 시작)을 쥔다 | 이 장 |
Entity | 캐릭터·적·총알·이펙트를 띄우고 치운다 | 3장 |
Event | "누가 죽었다", "점수가 올랐다" 같은 알림을 전달한다 | 3장 |
FSM | 캐릭터의 상태(살아 있음 / 죽는 중)를 나눈다 | 4장 |
UI | 체력 바, 일시정지 메뉴 같은 화면을 띄운다 | 7장 |
Sound | 효과음을 낸다 | 8장 |
Resource | 프리팹과 소리 파일을 불러온다. 직접 부르지는 않지만 위의 모듈들이 전부 이것을 거친다 | 3장 |
나머지(Config, Data Table, Network, Download …)는 붙어만 있고 아무 일도 하지 않습니다. 지워도 되지만, 나중에 필요해졌을 때 다시 찾아 붙이는 것보다 그냥 두는 편이 편합니다.
루트에 붙은 BaseComponent
루트 오브젝트를 선택하면 인스펙터에 Base 라는 컴포넌트가 보입니다.
루트의 Base 컴포넌트. ① 프레임워크의 동작을 갈아 끼우는 헬퍼 드롭다운 ② 목표 프레임
이 컴포넌트가 프레임워크의 심장입니다. 매 프레임 Update 에서 모든 모듈을 한 번씩 돌립니다. 이 오브젝트가 씬에 없으면 이벤트도, 엔티티도, 아무것도 움직이지 않습니다.
프레임워크의 컴포넌트들은 Assets/Scripts/Editor 폴더에 든 전용 인스펙터로 그려집니다. 헬퍼는 드롭다운으로 고르고, Play 중에는 지금 상태(엔티티 수, 풀에 든 개수 등)가 함께 표시됩니다.
이 책의 에디터 그림은 Unity 6000.6 에서 예제 프로젝트를 열고 찍은 것입니다. 빨간 상자와 ①② 번호는 본문과 그림 설명에서 가리키는 곳입니다.
지금 손댈 값은 없습니다. 눈에 익혀 둘 것은 둘입니다.
•
Frame Rate — 게임의 목표 프레임(그림의 ②). 예제는 30 으로 두었습니다
•
Log Helper 등 … Helper 로 끝나는 드롭다운들(그림의 ①) — 프레임워크의 동작을 갈아 끼우는 자리입니다. 지금은 기본값 그대로 둡니다
씬에 올리기
새 프로젝트라면 이 프리팹을 MainScene 의 Hierarchy 로 끌어다 놓습니다. 위치는 상관없습니다. 예제의 MainScene 에는 이미 올라가 있습니다.
프레임워크 오브젝트는 게임 내내 하나만 있어야 합니다. 씬을 여러 개 쓰는 게임이라면 첫 씬에만 두고, 다른 씬은 그 위에 추가로 불러오는 방식을 씁니다. 이 책의 게임은 씬 하나로 끝까지 가므로 신경 쓸 일이 없습니다.
1.4 모듈에 값 채우기
자식 오브젝트 몇 개에는 미리 적어 둬야 하는 값이 있습니다. 지금 전부 채워 두면 뒤의 장에서 다시 돌아올 필요가 없습니다.
Entity — 엔티티 그룹
Entity 자식을 선택합니다.
① Hierarchy 의 Entity 자식 ② Entity Groups 에 등록된 여섯 개의 그룹(Player 부터 Weapon 까지)
Entity Groups 에 그룹을 여섯 개 만듭니다. 그룹은 "비슷한 것끼리 묶는 서랍"이라고 생각하면 됩니다. 적은 적끼리, 총알은 총알끼리 묶어 두면 프레임워크가 서랍별로 따로 재활용합니다.
Name | Instance Capacity | 들어갈 것 |
Player | 4 | 선택 화면의 두 캐릭터, 조작하는 플레이어 |
Enemy | 64 | 적 다섯 종류 |
Projectile | 64 | 날아가는 탄 |
Effect | 32 | 맞았을 때 터지는 이펙트 |
Ally | 2 | 소환하는 아군 |
Weapon | 8 | 플레이어가 드는 무기 넷 (6장) |
나머지 세 칸(Instance Auto Release Interval, Instance Expire Time, Instance Priority)은 60 / 60 / 0 으로 둡니다. 뜻은 5장에서 적이 대량으로 나올 때 설명합니다.
Name 은 나중에 코드에서 그대로 문자열로 씁니다. "Enemy" 라고 적었으면 코드에서도 "Enemy" 입니다. 대소문자 하나만 달라도 "그런 그룹은 없다"는 오류가 나므로 표 그대로 입력합니다.
UI 와 Sound
같은 방식으로 두 군데를 더 채웁니다.
자식 오브젝트 | 항목 | 값 |
UI | UI Groups | 그룹 2개 — Default(Depth 0), Popup(Depth 1) |
Sound | Sound Groups | 그룹 2개 — Music(Agent Helper Count 1), Soundeffects(Agent Helper Count 16) |
Soundeffects 의 16 은 "효과음이 동시에 열여섯 개까지 겹쳐 날 수 있다"는 뜻입니다. 한 번 울리고 끝나는 소리뿐 아니라 냉기 분사음처럼 계속 이어지는 소리도 이 자리를 하나씩 차지합니다(8장).
UI 그룹의 Depth 는 그룹끼리의 앞뒤 순서입니다. 숫자가 큰 그룹의 화면이 위에 그려집니다. 체력 바 같은 상시 화면은 Default 에, 일시정지 메뉴처럼 그 위를 덮어야 하는 화면은 Popup 에 엽니다. 이 숫자가 실제 그리기 순서로 바뀌는 과정은 7장에서 봅니다.
① UI 자식 ② UI Groups — Default 의 Depth 0, Popup 의 Depth 1
① Sound 자식 ② Sound Groups — Music(Agent Helper Count 1), Soundeffects(16)
1.5 안 하면 조용히 실패하는 설정 세 가지
여기가 이 장에서 가장 중요한 부분입니다. 아래 셋은 빠뜨려도 에러가 나지 않습니다. 빨간 글씨 하나 없이 화면에 아무것도 안 나오고, 원인을 짐작할 단서도 없습니다. 프레임워크를 처음 쓰는 사람이 가장 오래 헤매는 곳입니다.
① 로그 켜기
프레임워크는 Debug.Log 대신 자체 로그 함수 Log.Info, Log.Warning, Log.Error 를 씁니다. 그런데 이 함수들은 특정 심볼이 정의돼 있을 때만 컴파일됩니다. 심볼이 없으면 호출 자체가 빌드에서 사라집니다. 출시 빌드에서 로그 비용을 0 으로 만들기 위한 장치인데, 개발 중에 이걸 모르면 "프레임워크가 아무 말도 안 한다"가 됩니다.
Edit → Project Settings → Player → Other Settings → Scripting Define Symbols 에 아래 두 개를 넣고 Apply 를 누릅니다.
ENABLE_LOG
ENABLE_DEBUG_AND_ABOVE_LOG
Plain Text
복사
이 설정은 플랫폼마다 따로 저장됩니다. 지금은 PC(Standalone) 탭에만 넣으면 되고, 나중에 Android 로 빌드한다면 그 탭에도 넣어야 합니다. 손으로 넣는 대신 상단 메뉴 Game Framework → Log Scripting Define Symbols → Enable All Logs 를 눌러도 됩니다. PC·모바일·웹 등 다섯 플랫폼에 ENABLE_LOG 를 한 번에 넣어 줍니다(ENABLE_LOG 하나만 있어도 모든 로그가 켜집니다).
② Addressables 의 Play Mode Script
이 프레임워크는 프리팹을 Addressables 로 불러옵니다. Addressables 는 "에디터에서 플레이할 때 에셋을 어디서 읽을지"를 고르는 설정이 있는데, 이것이 맞지 않으면 캐릭터가 하나도 뜨지 않습니다.
1.
Window → Asset Management → Addressables → Groups 를 엽니다.
2.
처음이라면 Create Addressables Settings 버튼을 누릅니다.
3.
창 위쪽의 Play Mode Script 를 눌러 Use Asset Database (fastest) 를 고릅니다. (설정이 이미 만들어진 프로젝트라면 메뉴 Game Framework → Addressables → Play Mode - Use Asset Database 로 같은 일을 할 수 있습니다. 이미 그 모드라면 메뉴가 비활성화돼 있습니다.)
이 값은 프로젝트의 Library 폴더에 저장됩니다. Library 는 git 에 올리지 않는 폴더라서, 프로젝트를 내려받은 사람마다 한 번씩 직접 설정해야 합니다. 팀원이 "받았는데 아무것도 안 떠요"라고 하면 십중팔구 이것입니다.
③ 레이어 번호
Edit → Project Settings → Tags and Layers 의 Layers 에 아래 이름을 정확히 그 번호 칸에 넣습니다.
번호 | 이름 | 쓰임 |
8 | Floor | 바닥. 마우스가 가리키는 지점을 찾을 때 쓴다 (4장) |
9 | Shootable | 적. 무기가 맞힐 수 있는 대상 (5·6장) |
10 | Blocking | 시야를 막는 구조물 |
13 | FrostFX | 냉기 무기의 이펙트 (6장) |
14 | Environment | 배경 소품 |
번호가 중요한 이유는 아트 에셋의 프리팹들이 이미 그 번호의 레이어로 저장돼 있기 때문입니다. 이름만 맞추고 번호가 다르면 총알이 적을 통과합니다.
1.6 첫 프로시저 만들기
설정이 끝났으니 이제 프레임워크가 부를 우리 코드를 만듭니다.
프레임워크는 게임의 진행을 프로시저(Procedure) 라는 단위로 나눕니다. "로딩 중", "메뉴", "플레이 중", "게임오버" 같은 큰 단계 하나하나가 프로시저이고, 한 번에 하나만 실행됩니다. 이 책의 게임은 두 개를 씁니다. ProcedureMain(한 판을 플레이하는 중)과 ProcedureGameOver(죽은 뒤 다시 시작하기 전). 자세한 이야기는 다음 장에서 하고, 지금은 첫 번째 것을 껍데기만 만듭니다.
폴더와 파일
Assets 아래에 GameMain 폴더를 만들고, 그 안에 Procdure 폴더를 만듭니다.
폴더 이름이 Procedure 가 아니라 Procdure 인 것은 예제 프로젝트의 오타가 그대로 남은 것입니다. 폴더 이름은 동작에 영향이 없으니 올바른 철자로 만들어도 됩니다. 이 책은 예제와 경로를 맞추기 위해 Procdure 로 적습니다.
첫 번째 파일은 우리 게임의 프로시저들이 공통으로 상속할 기반 클래스입니다.
// Assets/GameMain/Procdure/ProcedureBase.cs
namespace ToyBoxNightmare
{
public abstract class ProcedureBase : GameFramework.Procedure.ProcedureBase
{
/// <summary>
/// 일부 특수한 프로시저(예: 게임 UI 리소스가 아직 로드되지 않은 상태의 프로시저)에서는,
/// 네이티브 다이얼로그를 사용해서 메시지를 보여줄 수 있다.
/// </summary>
public abstract bool UseNativeDialog
{
get;
}
}
}
C#
복사
프레임워크에도 ProcedureBase 가 있는데, 그것을 상속한 우리 게임 전용 ProcedureBase 를 한 겹 더 두는 것입니다. 지금은 속성 하나뿐이라 쓸모없어 보이지만, 프로시저가 늘어났을 때 "모든 프로시저가 공통으로 가져야 할 것"을 넣을 자리가 됩니다. 프레임워크의 클래스를 직접 상속하지 않고 내 것을 한 겹 끼우는 것은 이 책에서 계속 반복되는 습관입니다.
두 번째 파일이 실제 프로시저입니다.
// Assets/GameMain/Procdure/ProcedureMain.cs
using UnityGameFramework.Runtime;
using ProcedureOwner = GameFramework.Fsm.IFsm<GameFramework.Procedure.IProcedureManager>;
namespace ToyBoxNightmare
{
public class ProcedureMain : ProcedureBase
{
public override bool UseNativeDialog => false;
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
Log.Info("ProcedureMain: Enter");
}
}
}
C#
복사
코드 읽기
짧은 코드지만 프레임워크를 쓰는 방식이 거의 다 들어 있습니다. 한 줄씩 보겠습니다.
using ProcedureOwner = … — 프로시저의 메서드들은 IFsm<IProcedureManager> 라는 긴 타입을 인자로 받습니다. 매번 쓰기 번거로우니 파일 맨 위에서 ProcedureOwner 라는 별명을 붙였습니다. 프로시저 파일마다 이 한 줄을 그대로 복사해 넣으면 됩니다.
namespace ToyBoxNightmare — 이 이름을 기억해 두세요. 바로 다음 단계의 인스펙터 목록에 이 이름이 붙어서 나옵니다.
protected override void OnEnter(…) — 프레임워크가 "이제 이 프로시저를 시작한다"고 할 때 한 번 부르는 메서드입니다. MonoBehaviour 의 Start 와 비슷한 자리입니다. 프로시저에는 이 밖에도 매 프레임 불리는 OnUpdate, 끝날 때 불리는 OnLeave 가 있습니다.
여기서 앞서 미뤄 둔 이야기가 나옵니다. 접근 제한자가 protected override 입니다. ProcedureBase 는 코어(GameFramework.dll)의 클래스이고, 거기에 OnEnter 는 protected internal virtual 로 선언돼 있습니다. 그런데 코어는 우리 코드와 다른 어셈블리라서 internal 을 따라 쓸 수 없습니다. protected internal override 라고 쓰면 이런 컴파일 에러가 납니다.
error CS0507: 'ProcedureMain.OnEnter(…)': cannot change access modifiers when overriding
'protected' inherited member
Plain Text
복사
3장부터 만들 엔티티 로직(EntityLogic)과 7장의 UI 폼(UIFormLogic)은 사정이 반대입니다. 그 둘은 Assets/Scripts 에 소스로 들어 있어 우리 코드와 같은 어셈블리이므로, 선언 그대로 protected internal override 라고 써야 합니다(protected override 라고 쓰면 같은 CS0507 이 납니다). 표로 기억합니다.
부모 클래스가 있는 곳 | 해당하는 것 | 오버라이드 |
코어 (GameFramework.dll) | 프로시저 ProcedureBase, 상태 FsmState<T> | protected override |
Unity Game Framework (Assets/Scripts) | 엔티티 로직 EntityLogic, UI 폼 UIFormLogic | protected internal override |
틀려도 컴파일러가 바로 CS0507 로 알려 주니 겁낼 것은 없습니다.
base.OnEnter(procedureOwner); — 부모의 것을 먼저 부릅니다. 프레임워크의 훅을 오버라이드할 때는 습관처럼 첫 줄에 둡니다.
Log.Info(…) — 프레임워크의 로그 함수입니다. 1.5 절에서 심볼을 넣지 않았다면 이 줄은 컴파일에서 통째로 사라집니다.
1.7 프레임워크에 프로시저 알려 주기
클래스를 만들었다고 프레임워크가 저절로 알아채지는 않습니다. "이런 프로시저가 있고, 이것부터 시작해라"를 알려 줘야 합니다.
Hierarchy 에서 GameFramework → Procedure 를 선택합니다.
① Hierarchy 의 Procedure 자식 ② 사용할 프로시저 목록과 시작 프로시저 (그림은 완성된 예제의 모습. 시작 예제가 들어가기 전에 찍어 ProcedureStarter 줄은 없다)
항목 | 지금 넣을 값 |
Available Procedures | ToyBoxNightmare.ProcedureMain 에 체크, ProcedureStarter 는 체크 해제 |
Entrance Procedure | 드롭다운에서 ToyBoxNightmare.ProcedureMain 선택 |
목록에는 프로젝트 안의 프로시저 클래스가 자동으로 나옵니다. 지금은 프레임워크의 시작 예제 UnityGameFramework.Runtime.ProcedureStarter(처음부터 체크돼 있다)와 방금 만든 ProcedureMain 둘입니다. ProcedureStarter 의 체크를 풀고 ProcedureMain 에 체크합니다. 그림의 ProcedureGameOver 는 9장에서 만들면 나타납니다.
이렇게 고친 값은 씬에 놓인 프리팹 인스턴스에 적힙니다(인스펙터에서 바꾼 항목이 굵게 보이고 왼쪽에 파란 줄이 생깁니다). 씬을 저장합니다. 이 값을 프리팹에 Apply 하지 않습니다. 프리팹은 어느 씬, 어느 프로젝트에서나 쓰는 공통 설정이고, "이 게임은 ProcedureMain 으로 시작한다"는 이 게임의 씬에 속한 정보이기 때문입니다. 예제 프로젝트도 이 설정을 MainScene 에 두고 있습니다.
목록에 보이는 것은 네임스페이스를 포함한 전체 이름입니다. 체크하면 이 문자열이 저장되고, 프레임워크는 실행할 때 이 문자열로 클래스를 찾아 직접 인스턴스를 만듭니다. 우리가 new ProcedureMain() 을 하거나 GameObject 에 붙이는 일은 없습니다.
1.8 실행해 보기
MainScene 이 열려 있는지 확인하고 Play 를 누릅니다.
첫 실행 화면. 배경만 보이고, 왼쪽 위에 프레임워크의 디버거 아이콘이 떠 있다
화면에는 장난감 방 바닥만 보입니다. 게임을 아직 만들지 않았으니 당연합니다. 대신 두 군데를 확인합니다.
화면 왼쪽 위의 작은 상자 — DEBUGGER 와 FPS 숫자가 적혀 있습니다. 프레임워크의 Debugger 모듈이 그린 것입니다. 이것이 보인다면 프레임워크가 켜졌다는 뜻입니다. 클릭하면 로그와 메모리 상태를 볼 수 있는 창이 열립니다.
Console 창 — 아래와 같은 줄들이 차례로 찍혀 있어야 합니다.
Game Framework Version: 2021.05.31
Game Version: 0.1.0 (0)
Unity Version: 6000.6.0f1
During this run, Game Framework will use editor resource files, which you should validate first.
Addressables resource manager initialized.
Strict checking is enabled for the Reference Pool. It will drastically affect the performance.
ProcedureMain: Enter
Plain Text
복사
앞의 여섯 줄은 프레임워크가 켜지면서 스스로 남긴 것이고, 마지막 줄이 우리 코드가 찍은 것입니다. 이 줄이 보이면 이 장은 성공입니다.
여섯 번째 줄의 "Strict checking … drastically affect the performance" 는 경고처럼 읽히지만 걱정할 것이 아닙니다. 개발 중에 실수를 잡아 주는 검사가 켜져 있다는 안내이고, 3장에서 이 검사가 실제로 무엇을 잡아 주는지 보게 됩니다.
1.9 잘 안 될 때
증상 | 원인과 해결 |
콘솔에 아무것도 안 찍힌다. 프레임워크 버전 줄조차 없다 | 1.5 ① 의 심볼을 넣지 않았습니다. 넣은 뒤 Apply 를 눌렀는지, 지금 빌드 대상 플랫폼의 탭에 넣었는지 확인합니다 |
앞의 여섯 줄은 나오는데 ProcedureMain: Enter 만 없고, 대신 Can not find procedure type '…' 이 찍힌다 | 저장된 이름의 클래스가 없습니다. 클래스나 네임스페이스 이름을 바꾼 뒤라면 Procedure 인스펙터를 다시 열어 체크를 새로 합니다 |
Entrance procedure is invalid. | Entrance Procedure 가 비어 있거나, 체크를 푼 프로시저를 가리키고 있습니다. 드롭다운에서 다시 고릅니다 |
컴파일 에러 CS0507 | 오버라이드 키워드가 부모와 맞지 않습니다. 프로시저·상태는 protected override, 엔티티 로직·UI 폼은 protected internal override 입니다 (1.6 절의 표) |
왼쪽 위에 디버거 상자가 없다 | 씬에 GameFramework 프리팹이 올라가 있지 않거나, 다른 씬을 열고 Play 했습니다 |
Game Framework is running (ProcedureStarter). 만 찍히고 ProcedureMain: Enter 가 없다 | 진입 절차가 아직 시작 예제입니다. 1.7 절대로 ProcedureStarter 의 체크를 풀고 Entrance Procedure 를 ProcedureMain 으로 고른 뒤 씬을 저장합니다 |
이 장에서 만든 것
파일 | 내용 |
Assets/GameMain/Procdure/ProcedureBase.cs | 우리 게임 프로시저의 공통 기반 |
Assets/GameMain/Procdure/ProcedureMain.cs | 한 판을 진행하는 프로시저. 지금은 로그 한 줄만 찍는다 |
그리고 프레임워크 프리팹에 엔티티 그룹 6개, UI 그룹 2개, 사운드 그룹 2개, 프로시저 1개를 등록했습니다.
정리
•
프레임워크는 프리팹 하나로 켜지고, 자식 오브젝트 하나가 모듈 하나다
•
프레임워크는 우리 클래스를 이름 문자열로 찾아 직접 만들고 호출한다. 프로시저 이름은 인스펙터가 대신 적어 주지만, 그룹 이름처럼 직접 적는 문자열은 코드와 정확히 같아야 한다
•
프레임워크는 세 층이다 — 코어 DLL, Unity Game Framework, 우리 게임. 코어의 클래스(프로시저, 상태)를 오버라이드할 때는 protected override, Unity Game Framework 의 클래스(엔티티 로직, UI 폼)는 protected internal override
•
로그 심볼, Play Mode Script, 레이어 번호 — 셋은 빠뜨려도 에러가 나지 않으니 문제가 생기면 먼저 의심한다
직접 해 보기
1.
ProcedureMain 에 OnUpdate 를 오버라이드해서 3초마다 Log.Info 를 찍어 보세요. 시그니처는 protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds) 입니다. 두 번째 인자 elapseSeconds 는 지난 프레임부터 흐른 시간입니다.
2.
Procedure 인스펙터에서 ProcedureMain 의 체크를 풀고 Play 해 보세요. 어떤 메시지가 나오는지 한 번 봐 두면, 나중에 같은 실수를 했을 때 바로 알아봅니다. 확인한 뒤 다시 체크하고 Entrance Procedure 도 다시 고릅니다.















