본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 3. · 89 Views
Phaser 게임 구조 이해하기
Phaser 게임 엔진의 핵심 구조를 처음부터 차근차근 배워봅니다. Game Config 설정부터 Scene 관리, 생명주기 함수까지 게임 개발의 기초를 탄탄하게 다질 수 있습니다.
목차
1. Game Config 설정
어느 날 김개발 씨는 회사에서 간단한 웹 게임 프로젝트를 맡게 되었습니다. "Phaser라는 엔진을 써보세요"라는 팀장님의 말씀에 공식 문서를 열어보았지만, 첫 번째 단계부터 막막했습니다.
도대체 Game Config란 무엇이고, 어떻게 설정해야 하는 걸까요?
Game Config는 Phaser 게임의 모든 기본 설정을 담고 있는 객체입니다. 마치 건물을 짓기 전에 작성하는 설계도면과 같습니다.
화면 크기, 물리 엔진 종류, 사용할 Scene 목록 등 게임의 뼈대가 되는 정보를 여기서 정의합니다.
다음 코드를 살펴봅시다.
// Phaser 게임의 설계도, Game Config 객체
const config = {
type: Phaser.AUTO, // 렌더러 자동 선택 (WebGL 또는 Canvas)
width: 800, // 게임 화면 가로 크기
height: 600, // 게임 화면 세로 크기
parent: 'game-container', // 게임이 들어갈 HTML 요소 ID
physics: {
default: 'arcade', // 기본 물리 엔진 설정
arcade: {
gravity: { y: 300 }, // 중력 설정
debug: false // 디버그 모드 끄기
}
},
scene: [MainScene, GameScene] // 사용할 Scene 목록
};
// 설정을 바탕으로 게임 인스턴스 생성
const game = new Phaser.Game(config);
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 처음으로 게임 개발 프로젝트를 맡게 되어 설레는 마음으로 Phaser 공식 문서를 펼쳤습니다.
그런데 첫 페이지부터 낯선 용어들이 쏟아져 나왔습니다. "Game Config가 뭐지?
왜 이렇게 설정할 게 많은 거야?" 마침 옆자리 박시니어 씨가 커피를 들고 다가왔습니다. "아, Phaser 시작하는구나?
Game Config부터 막히지?" 그렇다면 Game Config란 정확히 무엇일까요? 쉽게 비유하자면, Game Config는 마치 건축가가 작성하는 설계도면과 같습니다.
건물을 짓기 전에 층수, 방 개수, 화장실 위치 등을 미리 계획하듯이, 게임을 만들기 전에 화면 크기, 물리 엔진, 사용할 장면들을 미리 정의하는 것입니다. Game Config가 없다면 어떻게 될까요?
매번 게임을 실행할 때마다 "화면 크기 얼마로 할까?", "물리 엔진 뭘로 쓸까?"를 일일이 지정해야 할 것입니다. 코드가 여기저기 흩어지고, 설정을 변경할 때마다 여러 파일을 수정해야 하는 번거로움이 생깁니다.
바로 이런 문제를 해결하기 위해 Game Config가 존재합니다. 모든 설정을 한 곳에 모아두면 관리가 훨씬 쉬워집니다.
화면 크기를 바꾸고 싶으면 Config의 width와 height만 수정하면 됩니다. 물리 엔진을 교체하고 싶으면 physics 부분만 변경하면 됩니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 type: Phaser.AUTO는 렌더링 방식을 자동으로 선택하라는 의미입니다.
Phaser는 WebGL과 Canvas 두 가지 렌더링 방식을 지원하는데, AUTO로 설정하면 브라우저 환경에 따라 최적의 방식을 선택합니다. width와 height는 게임 화면의 크기를 픽셀 단위로 지정합니다.
800x600은 일반적인 게임 화면 크기입니다. parent는 게임 캔버스가 삽입될 HTML 요소의 ID입니다.
HTML에 해당 ID를 가진 div를 만들어두면 그 안에 게임 화면이 나타납니다. physics 객체는 물리 엔진 설정을 담고 있습니다.
Phaser는 arcade, matter, impact 등 여러 물리 엔진을 지원하는데, arcade가 가장 가볍고 배우기 쉽습니다. 마지막으로 scene 배열에는 게임에서 사용할 Scene들을 나열합니다.
Scene은 다음 장에서 자세히 다루겠습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 모바일 게임을 개발한다면 width와 height를 스마트폰 화면 비율에 맞게 조정할 것입니다. 또한 scale 속성을 추가하여 다양한 화면 크기에 대응하는 반응형 게임을 만들 수도 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 scene 배열을 비워두는 것입니다.
Scene이 없으면 게임은 아무것도 표시하지 않습니다. 반드시 최소 하나의 Scene은 등록해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 설계도부터 그려야 하는 거군요!" Game Config를 제대로 이해하면 게임의 기초 설정을 한눈에 파악하고 쉽게 관리할 수 있습니다. 여러분도 첫 번째 Phaser 프로젝트를 시작할 때 Config부터 꼼꼼히 작성해 보세요.
실전 팁
💡 - type은 특별한 이유가 없다면 Phaser.AUTO를 사용하세요
- 개발 중에는 physics.arcade.debug를 true로 설정하면 충돌 영역을 시각적으로 확인할 수 있습니다
- parent를 지정하지 않으면 body 태그 맨 아래에 게임 캔버스가 추가됩니다
2. Scene의 개념
김개발 씨가 Game Config를 설정하고 나니 또 다른 의문이 생겼습니다. "Scene이 뭐지?
왜 게임에 여러 개의 Scene이 필요한 거야?" 박시니어 씨가 웃으며 설명을 시작했습니다. "게임을 연극이라고 생각해봐.
Scene은 말 그대로 장면이야."
Scene은 게임의 독립적인 장면 또는 화면을 의미합니다. 마치 연극의 1막, 2막처럼 게임도 타이틀 화면, 게임 플레이 화면, 게임 오버 화면 등 여러 장면으로 구성됩니다.
각 Scene은 자신만의 리소스와 로직을 가지며, 서로 독립적으로 동작합니다.
다음 코드를 살펴봅시다.
// Scene은 Phaser.Scene을 상속받아 만듭니다
class MainMenuScene extends Phaser.Scene {
constructor() {
// Scene의 고유 키 설정
super({ key: 'MainMenu' });
}
preload() {
// 리소스 로드
this.load.image('logo', 'assets/logo.png');
}
create() {
// 게임 오브젝트 생성
this.add.image(400, 300, 'logo');
this.add.text(400, 450, '시작하려면 클릭하세요', {
fontSize: '24px',
fill: '#ffffff'
}).setOrigin(0.5);
}
}
김개발 씨는 Config 설정을 마치고 본격적으로 게임 코드를 작성하려 했습니다. 그런데 scene이라는 단어가 자꾸 눈에 들어왔습니다.
배열에도 있고, 클래스 이름에도 있고, 도대체 이게 뭘까요? 박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다.
"게임을 한 편의 연극이라고 생각해봐." 그렇다면 Scene이란 정확히 무엇일까요? 쉽게 비유하자면, Scene은 마치 연극의 막과 같습니다.
1막에서는 주인공이 등장하고, 2막에서는 갈등이 일어나고, 3막에서는 결말이 나옵니다. 각 막은 독립적인 무대 세트와 배우 배치를 가지면서도, 하나의 연극을 구성합니다.
게임도 마찬가지입니다. 타이틀 화면에서는 로고와 시작 버튼이 필요하고, 게임 플레이 화면에서는 캐릭터와 적, 점수판이 필요합니다.
게임 오버 화면에서는 최종 점수와 재시작 버튼이 필요합니다. Scene이 없던 시절에는 어땠을까요?
모든 게임 로직이 하나의 거대한 코드 덩어리에 섞여 있었습니다. "지금 타이틀 화면인지, 게임 중인지, 게임 오버인지"를 확인하는 조건문이 코드 곳곳에 흩어져 있었습니다.
새로운 화면을 추가할 때마다 기존 코드를 수정해야 했고, 버그가 생기기 쉬웠습니다. 바로 이런 문제를 해결하기 위해 Scene 개념이 등장했습니다.
각 화면을 독립적인 Scene으로 분리하면 코드가 깔끔해집니다. 타이틀 화면의 버그를 수정할 때 게임 플레이 코드를 건드릴 필요가 없습니다.
새로운 화면을 추가할 때도 기존 Scene에 영향을 주지 않습니다. 위의 코드를 살펴보겠습니다.
Phaser.Scene을 상속받아 새로운 Scene 클래스를 만듭니다. 생성자에서 **super({ key: 'MainMenu' })**를 호출하는데, 이 key가 Scene을 식별하는 고유한 이름이 됩니다.
나중에 다른 Scene으로 전환할 때 이 key를 사용합니다. preload 함수에서는 이미지, 오디오 등의 리소스를 로드합니다.
create 함수에서는 실제 게임 오브젝트들을 화면에 배치합니다. 이 두 함수는 다음 장에서 더 자세히 다루겠습니다.
실제 현업에서는 어떻게 활용할까요? 대부분의 게임은 최소 3~4개의 Scene으로 구성됩니다.
로딩 화면, 메인 메뉴, 게임 플레이, 게임 오버가 기본 구성입니다. 규모가 큰 게임은 설정 화면, 상점, 인벤토리 등 더 많은 Scene을 가집니다.
하지만 주의할 점도 있습니다. Scene을 너무 잘게 쪼개면 오히려 관리가 복잡해질 수 있습니다.
반대로 하나의 Scene에 너무 많은 기능을 넣으면 코드가 비대해집니다. 적절한 균형을 찾는 것이 중요합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. "그러니까 각 화면을 Scene이라는 단위로 분리해서 관리하는 거군요!" 박시니어 씨가 고개를 끄덕였습니다.
Scene 개념을 제대로 이해하면 게임의 구조를 체계적으로 설계할 수 있습니다. 여러분의 게임에 어떤 Scene들이 필요할지 먼저 설계해 보세요.
실전 팁
💡 - Scene의 key는 고유해야 합니다. 중복되면 예상치 못한 오류가 발생합니다
- 하나의 Scene에서 여러 역할을 담당하지 말고, 역할별로 Scene을 분리하세요
- Scene 클래스 파일은 별도의 파일로 분리하면 관리가 편합니다
3. preload create update 함수
김개발 씨가 Scene 클래스를 만들어보니 preload, create, update라는 함수가 계속 등장했습니다. "이 세 함수는 뭐가 다른 거예요?" 박시니어 씨가 손가락을 세 개 펴며 말했습니다.
"준비, 시작, 반복. 이 세 단계를 기억하면 돼."
preload, create, update는 Phaser Scene의 핵심 생명주기 함수입니다. preload에서 리소스를 미리 불러오고, create에서 게임 오브젝트를 생성하며, update에서 매 프레임마다 게임 로직을 실행합니다.
이 세 함수의 역할과 실행 순서를 이해하는 것이 Phaser 개발의 기본입니다.
다음 코드를 살펴봅시다.
class GameScene extends Phaser.Scene {
constructor() {
super({ key: 'Game' });
}
// 1단계: 리소스 로드 (이미지, 오디오 등)
preload() {
this.load.image('player', 'assets/player.png');
this.load.image('enemy', 'assets/enemy.png');
}
// 2단계: 게임 오브젝트 생성 (한 번만 실행)
create() {
this.player = this.physics.add.sprite(100, 300, 'player');
this.enemy = this.physics.add.sprite(600, 300, 'enemy');
this.score = 0;
}
// 3단계: 게임 로직 (매 프레임 실행, 초당 60회)
update() {
if (this.cursors.left.isDown) {
this.player.setVelocityX(-160);
}
}
}
김개발 씨는 Scene 클래스 안에 있는 세 개의 함수를 보며 고민에 빠졌습니다. preload, create, update.
이름만 봐서는 대충 감이 오는데, 정확히 언제 어떤 순서로 실행되는 걸까요? 박시니어 씨가 요리에 비유해서 설명하기 시작했습니다.
"요리를 한다고 생각해봐." 그렇다면 이 세 함수는 정확히 무슨 역할을 할까요? preload는 마치 요리 전 재료를 준비하는 단계와 같습니다.
냉장고에서 재료를 꺼내고, 필요한 도구를 챙기는 것처럼, preload에서는 이미지, 오디오, 데이터 파일 등 게임에 필요한 모든 리소스를 미리 불러옵니다. create는 실제로 요리를 시작하는 단계입니다.
준비한 재료로 음식을 만들기 시작하는 것처럼, create에서는 로드한 리소스를 사용해서 캐릭터, 배경, UI 등의 게임 오브젝트를 화면에 배치합니다. 이 함수는 Scene이 시작될 때 딱 한 번만 실행됩니다.
update는 요리 중 계속 상태를 확인하는 것과 같습니다. 불 세기를 조절하고, 간을 보고, 익었는지 확인하는 것처럼, update에서는 매 프레임마다 게임 상태를 확인하고 업데이트합니다.
초당 60회 정도 실행됩니다. 이 세 함수가 없다면 어떻게 될까요?
모든 코드가 뒤섞여서 혼란스러워질 것입니다. 리소스가 아직 로드되지 않았는데 사용하려 하면 오류가 발생합니다.
초기화 코드와 매 프레임 실행되는 코드가 구분되지 않으면 성능 문제도 생깁니다. 위의 코드를 단계별로 살펴보겠습니다.
preload 함수에서 this.load.image를 사용해 이미지 파일을 불러옵니다. 첫 번째 인자는 나중에 이 이미지를 참조할 때 사용할 키이고, 두 번째 인자는 실제 파일 경로입니다.
create 함수에서 this.physics.add.sprite를 사용해 물리 엔진이 적용된 스프라이트를 생성합니다. 위치 좌표와 함께 preload에서 지정한 이미지 키를 전달합니다.
this.player에 저장해두면 다른 함수에서도 접근할 수 있습니다. update 함수에서는 키보드 입력을 확인하고 플레이어를 움직입니다.
이 코드는 초당 60번 실행되므로, 키를 누르고 있는 동안 계속해서 속도가 적용됩니다. 실제 현업에서는 어떻게 활용할까요?
preload에서는 로딩 화면을 표시하면서 리소스를 불러옵니다. create에서는 게임의 초기 상태를 설정합니다.
update에서는 캐릭터 이동, 충돌 감지, 점수 계산 등 게임의 핵심 로직을 처리합니다. 하지만 주의할 점도 있습니다.
update 함수는 초당 60번 실행되므로, 여기에 무거운 연산을 넣으면 게임이 버벅거립니다. 가능한 한 가볍게 유지하고, 복잡한 계산은 필요할 때만 실행되도록 조건문으로 감싸세요.
다시 김개발 씨의 이야기로 돌아가 봅시다. "준비, 시작, 반복이군요!" 김개발 씨는 이 세 단어를 모니터 옆에 포스트잇으로 붙여두었습니다.
이 세 함수의 역할과 실행 순서를 제대로 이해하면 Phaser 게임 개발의 절반은 끝난 것입니다. 각 함수에 어떤 코드를 넣어야 할지 항상 고민하는 습관을 들이세요.
실전 팁
💡 - preload에서 로드하지 않은 리소스는 create에서 사용할 수 없습니다
- create에 넣어야 할 초기화 코드를 update에 넣으면 매 프레임 실행되어 성능 문제가 생깁니다
- update에서 복잡한 로직은 별도 함수로 분리하고 조건부로 호출하세요
4. 게임 생명주기 이해
김개발 씨는 preload, create, update를 이해했지만 아직 궁금한 점이 있었습니다. "그런데 이 함수들이 정확히 어떤 순서로, 언제 호출되는 거예요?" 박시니어 씨가 타임라인을 그리며 설명을 시작했습니다.
"Phaser의 생명주기를 알면 더 명확해질 거야."
게임 생명주기란 Phaser 게임이 시작되어 종료될 때까지 거치는 일련의 단계들을 말합니다. Scene의 생성부터 소멸까지 init, preload, create, update 순서로 함수가 호출되며, 각 단계마다 특정 작업을 수행합니다.
이 흐름을 이해하면 코드를 어디에 배치해야 할지 명확해집니다.
다음 코드를 살펴봅시다.
class GameScene extends Phaser.Scene {
// 1. 가장 먼저 실행 - Scene 초기 설정
init(data) {
console.log('init: Scene 초기화');
this.level = data.level || 1; // 전달받은 데이터 처리
}
// 2. 리소스 로드
preload() {
console.log('preload: 리소스 로드 중');
this.load.image('bg', 'assets/background.png');
}
// 3. 게임 오브젝트 생성
create() {
console.log('create: 오브젝트 생성');
this.add.image(400, 300, 'bg');
}
// 4. 매 프레임 반복
update(time, delta) {
// time: 게임 시작 후 경과 시간 (ms)
// delta: 이전 프레임과의 시간 차이 (ms)
this.physics.world.wrap(this.player);
}
}
김개발 씨는 게임을 실행해보면서 이상한 점을 발견했습니다. 분명히 코드를 제대로 작성한 것 같은데, 가끔 오브젝트가 undefined로 나오는 오류가 발생했습니다.
왜 그런 걸까요? 박시니어 씨가 화이트보드에 타임라인을 그렸습니다.
"생명주기를 제대로 이해하지 못해서 그래." 그렇다면 게임 생명주기란 정확히 무엇일까요? 쉽게 비유하자면, 생명주기는 마치 하루 일과표와 같습니다.
아침에 일어나서 세수하고, 아침 먹고, 출근하고, 일하고, 퇴근하는 것처럼 정해진 순서가 있습니다. 세수하기 전에 출근할 수 없고, 아침 먹기 전에 일할 수 없듯이, Phaser에서도 정해진 순서가 있습니다.
Phaser Scene의 생명주기는 init, preload, create, update 순서로 진행됩니다. init은 가장 먼저 실행되는 함수입니다.
Scene이 시작될 때 전달받은 데이터를 처리하거나 변수를 초기화할 때 사용합니다. 많은 개발자가 이 함수의 존재를 모르고 지나치지만, Scene 간 데이터 전달에 매우 유용합니다.
preload는 앞서 배웠듯이 리소스를 로드하는 단계입니다. 이 단계가 완료되기 전까지 create는 실행되지 않습니다.
따라서 대용량 리소스를 로드할 때 로딩 화면을 표시할 수 있습니다. create는 preload가 완료된 후 딱 한 번 실행됩니다.
게임 오브젝트를 생성하고 이벤트를 등록하는 등 초기 설정을 담당합니다. update는 create 이후부터 Scene이 종료될 때까지 매 프레임 반복 실행됩니다.
update 함수는 두 개의 인자를 받습니다. time은 게임 시작 후 경과한 총 시간이고, delta는 이전 프레임과의 시간 차이입니다.
왜 이 순서가 중요할까요? 만약 preload가 끝나기 전에 이미지를 사용하려 하면 오류가 발생합니다.
create에서 생성한 오브젝트를 init에서 사용하려 하면 undefined 오류가 납니다. 생명주기를 무시하면 이런 버그가 계속 발생합니다.
위의 코드에서 console.log를 넣어둔 것을 보세요. 실제로 실행해보면 init, preload, create 순서로 로그가 찍힙니다.
update는 그 후로 계속 반복됩니다. init 함수의 data 인자는 다른 Scene에서 전달한 데이터를 받습니다.
예를 들어 메인 메뉴에서 게임 Scene으로 전환할 때 선택한 난이도를 전달할 수 있습니다. update의 delta 값은 프레임 간 시간 차이를 알려줍니다.
컴퓨터 성능에 따라 프레임 속도가 달라질 수 있는데, delta를 활용하면 어떤 환경에서도 일정한 속도로 움직이는 게임을 만들 수 있습니다. 실제 현업에서는 이 생명주기를 철저히 지킵니다.
init에서 Scene 설정과 데이터 수신을 처리하고, preload에서 해당 Scene에 필요한 리소스만 로드합니다. create에서 모든 초기화를 완료하고, update에서는 순수하게 게임 로직만 처리합니다.
하지만 주의할 점도 있습니다. init과 preload에서 this.add나 this.physics를 사용하면 오류가 발생합니다.
아직 Scene이 완전히 준비되지 않았기 때문입니다. 게임 오브젝트 생성은 반드시 create 이후에 해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. "아, 그래서 순서가 꼬이면 undefined 오류가 났던 거군요!" 박시니어 씨가 미소 지었습니다.
"생명주기만 제대로 이해해도 버그의 절반은 예방할 수 있어." 게임 생명주기를 제대로 이해하면 코드를 어디에 배치해야 할지 명확해집니다. 여러분도 코드를 작성할 때 "이 코드는 어떤 생명주기 단계에 있어야 하지?"라고 스스로 질문하는 습관을 들이세요.
실전 팁
💡 - init 함수는 Scene 간 데이터 전달에 매우 유용합니다
- update의 delta 값을 활용하면 프레임 속도에 관계없이 일정한 게임 속도를 유지할 수 있습니다
- 게임 오브젝트 생성은 반드시 create 또는 그 이후에 해야 합니다
5. 여러 Scene 관리하기
김개발 씨의 게임이 점점 복잡해지고 있었습니다. 타이틀 화면, 게임 플레이, 일시정지, 게임 오버까지 벌써 네 개의 Scene이 필요했습니다.
"Scene이 많아지면 어떻게 관리해야 해요?" 박시니어 씨가 프로젝트 구조를 보여주며 말했습니다. "체계적으로 구성하면 전혀 어렵지 않아."
게임이 커질수록 여러 Scene을 체계적으로 관리하는 것이 중요해집니다. Game Config의 scene 배열에 모든 Scene을 등록하고, 각 Scene은 고유한 key로 식별합니다.
Scene을 파일별로 분리하고, SceneManager를 통해 Scene의 시작, 중지, 재시작 등을 제어할 수 있습니다.
다음 코드를 살펴봅시다.
// scenes/BootScene.js - 초기 로딩 담당
class BootScene extends Phaser.Scene {
constructor() { super({ key: 'Boot' }); }
preload() { this.load.image('loading', 'assets/loading.png'); }
create() { this.scene.start('Preloader'); }
}
// scenes/PreloaderScene.js - 전체 리소스 로딩
class PreloaderScene extends Phaser.Scene {
constructor() { super({ key: 'Preloader' }); }
preload() { /* 모든 리소스 로드 */ }
create() { this.scene.start('MainMenu'); }
}
// main.js - Scene 등록
const config = {
// ... 기타 설정
scene: [BootScene, PreloaderScene, MainMenuScene, GameScene, GameOverScene]
};
김개발 씨의 게임은 이제 제법 규모가 커졌습니다. 처음에는 하나의 파일에 모든 Scene을 작성했는데, 코드가 500줄을 넘어가면서 관리가 힘들어졌습니다.
박시니어 씨가 조언했습니다. "Scene별로 파일을 분리하고, 체계적인 구조를 만들어야 해." 그렇다면 여러 Scene을 어떻게 관리해야 할까요?
쉽게 비유하자면, Scene 관리는 마치 회사의 부서 관리와 같습니다. 영업부, 개발부, 인사부가 각자의 사무실에서 일하면서도 전체적으로는 하나의 회사로 운영되는 것처럼, 각 Scene은 독립적으로 동작하면서도 하나의 게임을 구성합니다.
일반적인 Phaser 게임은 다음과 같은 Scene 구조를 가집니다. BootScene은 가장 먼저 실행되는 Scene입니다.
로딩 화면에 필요한 최소한의 리소스만 로드합니다. 이 Scene은 매우 빠르게 실행되어야 하므로 가볍게 유지합니다.
PreloaderScene은 게임의 모든 리소스를 로드하는 Scene입니다. 여기서 프로그레스 바를 표시하면서 이미지, 오디오, 데이터 파일 등을 불러옵니다.
MainMenuScene은 타이틀 화면입니다. 게임 시작, 설정, 종료 등의 메뉴를 표시합니다.
GameScene은 실제 게임이 플레이되는 Scene입니다. 가장 복잡한 로직이 들어가는 곳입니다.
GameOverScene은 게임이 끝났을 때 표시되는 Scene입니다. 점수와 재시작 버튼을 보여줍니다.
Config의 scene 배열에 Scene을 등록하는 순서가 중요합니다. 배열의 첫 번째 Scene이 게임 시작 시 자동으로 실행됩니다.
따라서 BootScene을 맨 앞에 배치합니다. 나머지 Scene들은 순서에 상관없이 key로 접근할 수 있지만, 논리적인 순서대로 배치하면 코드를 읽기 쉬워집니다.
SceneManager는 Scene을 제어하는 도구입니다. 각 Scene 내부에서 this.scene으로 SceneManager에 접근할 수 있습니다.
this.scene.start로 다른 Scene을 시작하고, this.scene.stop으로 현재 Scene을 중지하고, this.scene.pause로 Scene을 일시정지할 수 있습니다. 파일 구조는 어떻게 가져가야 할까요?
보통 scenes 폴더를 만들어서 각 Scene을 별도 파일로 관리합니다. BootScene.js, PreloaderScene.js, MainMenuScene.js 식으로 파일을 분리하면 각 Scene의 코드를 독립적으로 관리할 수 있습니다.
실제 현업에서는 Scene을 더 세분화하기도 합니다. 예를 들어 게임 플레이 중에도 UI를 담당하는 Scene과 실제 게임 로직을 담당하는 Scene을 분리할 수 있습니다.
Phaser는 여러 Scene을 동시에 실행할 수 있으므로, 이런 구조가 가능합니다. 하지만 주의할 점도 있습니다.
Scene을 너무 많이 만들면 오히려 복잡해집니다. 작은 게임이라면 3~5개의 Scene으로 충분합니다.
Scene 간의 의존성이 복잡해지면 디버깅이 어려워지므로, 가능한 한 단순한 구조를 유지하세요. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 조언대로 파일을 분리하고 나니 코드가 훨씬 깔끔해졌습니다. "이제 어떤 Scene을 수정해야 할지 바로 찾을 수 있어요!" 여러 Scene을 체계적으로 관리하면 프로젝트가 커져도 유지보수가 쉬워집니다.
여러분도 처음부터 Scene 구조를 잘 설계해 보세요.
실전 팁
💡 - Scene 배열의 첫 번째 Scene이 자동으로 시작됩니다
- Scene 파일은 scenes 폴더에 모아서 관리하세요
- 동시에 여러 Scene을 실행할 수 있다는 점을 활용하면 UI와 게임 로직을 분리할 수 있습니다
6. Scene 전환 방법
김개발 씨는 이제 여러 Scene을 만들었습니다. 그런데 막상 Scene 사이를 이동하려니 어떤 메서드를 사용해야 할지 헷갈렸습니다.
"start, launch, switch, resume... 이게 다 뭐예요?" 박시니어 씨가 표를 그리며 차이점을 설명하기 시작했습니다.
Phaser에서 Scene 전환은 여러 방식으로 할 수 있습니다. start는 현재 Scene을 종료하고 새 Scene을 시작하고, launch는 현재 Scene을 유지한 채 새 Scene을 추가로 시작합니다.
switch와 pause/resume도 상황에 따라 유용하게 사용됩니다. 각 메서드의 차이를 이해하면 원하는 동작을 정확히 구현할 수 있습니다.
다음 코드를 살펴봅시다.
class GameScene extends Phaser.Scene {
create() {
// 게임 오버 시 - 현재 Scene 종료하고 GameOver Scene 시작
this.events.on('gameover', () => {
this.scene.start('GameOver', { score: this.score });
});
// 일시정지 시 - 현재 Scene 유지하고 Pause Scene 추가
this.input.keyboard.on('keydown-ESC', () => {
this.scene.pause();
this.scene.launch('PauseMenu');
});
}
}
class PauseMenuScene extends Phaser.Scene {
create() {
// 재개 버튼 클릭 시
this.resumeBtn.on('pointerdown', () => {
this.scene.stop(); // PauseMenu 종료
this.scene.resume('Game'); // Game Scene 재개
});
}
}
김개발 씨는 게임 오버 화면으로 전환하는 기능을 구현하고 있었습니다. this.scene.start를 사용했는데, 일시정지 메뉴는 어떻게 만들어야 할지 고민이었습니다.
일시정지할 때는 게임 화면이 뒤에 보여야 하는데... 박시니어 씨가 다가와 물었습니다.
"start랑 launch 차이 알아?" 그렇다면 Scene 전환 메서드들은 어떻게 다를까요? 쉽게 비유하자면, start는 TV 채널을 바꾸는 것과 같습니다.
1번 채널을 보다가 2번으로 바꾸면 1번은 사라지고 2번만 보입니다. 반면 launch는 화면 속 화면(PIP) 기능과 같습니다.
원래 보던 화면 위에 작은 창이 하나 더 뜨는 것입니다. **this.scene.start(key, data)**는 가장 많이 사용하는 메서드입니다.
현재 Scene을 완전히 종료하고 새로운 Scene을 시작합니다. 메인 메뉴에서 게임으로, 게임에서 게임 오버로 전환할 때 사용합니다.
두 번째 인자로 데이터를 전달할 수 있는데, 이 데이터는 새 Scene의 init 함수에서 받을 수 있습니다. **this.scene.launch(key, data)**는 현재 Scene을 유지한 채 새 Scene을 추가합니다.
일시정지 메뉴, 인벤토리 UI, 대화창 등 기존 화면 위에 새로운 화면을 띄울 때 사용합니다. 두 Scene이 동시에 실행되므로, 둘 다 update가 호출됩니다.
**this.scene.pause(key)**와 **this.scene.resume(key)**는 Scene의 실행을 제어합니다. pause를 호출하면 해당 Scene의 update가 멈춥니다.
화면은 그대로 보이지만 동작은 멈추는 것입니다. resume을 호출하면 다시 update가 실행됩니다.
일시정지 기능을 구현할 때 launch와 함께 사용합니다. **this.scene.stop(key)**는 Scene을 완전히 종료합니다.
launch로 띄운 Scene을 닫을 때 사용합니다. 일시정지 메뉴에서 "재개" 버튼을 누르면 일시정지 메뉴 Scene을 stop하고 게임 Scene을 resume합니다.
**this.scene.switch(key)**는 start와 비슷하지만 차이가 있습니다. switch는 현재 Scene을 sleep 상태로 만들고 새 Scene을 시작합니다.
나중에 다시 이전 Scene으로 돌아올 때 처음부터 다시 로드하지 않아도 됩니다. 하지만 메모리에 계속 남아있으므로 주의가 필요합니다.
위의 코드에서 일시정지 구현 패턴을 살펴보겠습니다. ESC 키를 누르면 먼저 현재 Scene을 pause합니다.
그리고 launch로 일시정지 메뉴를 띄웁니다. 일시정지 메뉴에서 재개 버튼을 누르면 자신을 stop하고 게임 Scene을 resume합니다.
이 패턴은 거의 모든 게임에서 동일하게 사용됩니다. 데이터 전달 방법도 알아두면 유용합니다.
start나 launch의 두 번째 인자로 객체를 전달하면, 새 Scene의 init 함수에서 이 데이터를 받을 수 있습니다. 게임 오버 시 점수를 전달하거나, 다음 스테이지 번호를 전달할 때 사용합니다.
실제 현업에서는 이런 전환 메서드들을 조합해서 복잡한 UI 흐름을 구현합니다. 예를 들어 상점 버튼을 누르면 게임을 pause하고 상점 Scene을 launch합니다.
상점에서 아이템을 구매하면 인벤토리 Scene도 함께 launch할 수 있습니다. 상점을 닫으면 모든 UI Scene을 stop하고 게임을 resume합니다.
하지만 주의할 점도 있습니다. 여러 Scene을 동시에 launch하면 이벤트 처리가 복잡해질 수 있습니다.
어떤 Scene의 클릭 이벤트가 먼저 처리될지 예측하기 어려워집니다. Scene의 깊이(depth)를 잘 관리하고, 필요 없는 Scene은 즉시 stop하세요.
다시 김개발 씨의 이야기로 돌아가 봅시다. "아, 그래서 일시정지할 때는 launch를 써야 하는 거군요!" 이제 김개발 씨는 상황에 맞는 Scene 전환 메서드를 선택할 수 있게 되었습니다.
Scene 전환 메서드들의 차이를 정확히 이해하면 원하는 UI 흐름을 자유자재로 구현할 수 있습니다. 여러분도 각 메서드를 직접 사용해보면서 차이점을 체감해 보세요.
실전 팁
💡 - 화면을 완전히 바꿀 때는 start, 위에 띄울 때는 launch를 사용하세요
- 일시정지 기능은 pause + launch + stop + resume 조합으로 구현합니다
- Scene 간 데이터 전달은 start/launch의 두 번째 인자를 활용하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
분산 추적 완벽 가이드
마이크로서비스 환경에서 요청의 전체 흐름을 추적하는 분산 추적 시스템의 핵심 개념을 배웁니다. Trace, Span, Trace ID 전파, 샘플링 전략까지 실무에 필요한 모든 것을 다룹니다.
CloudFront CDN 완벽 가이드
AWS CloudFront를 활용한 콘텐츠 배포 최적화 방법을 실무 관점에서 다룹니다. 배포 생성부터 캐시 설정, HTTPS 적용까지 단계별로 알아봅니다.