Skip to main content
Summary

완성 이후에 보이는 선택지

여기까지 왔다면 Phaser로 2D 게임을 만드는 기본 흐름은 모두 경험한 셈입니다. Scene을 만들고, 에셋을 불러오고, 입력과 물리, 타일맵, UI, 사운드, 저장, 배포까지 다뤘습니다. 이제 남은 질문은 "무엇을 더 붙일 수 있는가"입니다.

고급 주제는 어려운 API를 많이 외우는 단계가 아닙니다. 프로젝트의 방향을 정하는 단계에 가깝습니다. 혼자 즐기는 웹 게임으로 둘 것인지, 실시간 멀티플레이어를 붙일 것인지, 광고와 랭킹을 넣을 것인지, 모바일 앱으로 감쌀 것인지 선택해야 합니다.

중요한 기준은 하나입니다. 게임의 핵심 재미가 먼저 완성되어 있어야 합니다. 멀티플레이어, 광고, 앱 패키징은 재미를 대신 만들어 주지 않습니다. 이미 재미있는 작은 게임을 더 멀리 보내는 도구입니다. 핵심이 흔들리는 상태에서 확장 기능부터 붙이면, 기술은 늘어나지만 정작 게임은 끝나지 않을 수 있습니다.

Phaser를 확장하는 고급 주제AI 생성 이미지

실시간 멀티플레이어의 기본 구조

Phaser 자체는 화면을 그리고 입력을 처리하는 클라이언트 쪽 게임 엔진입니다. 두 명 이상의 플레이어가 같은 게임 세계에 들어오려면 서버가 필요합니다. 가장 단순한 구조는 다음과 같습니다.

플레이어 A 브라우저 ─┐
                    ├─ WebSocket 서버 ─ 게임 방 상태
플레이어 B 브라우저 ─┘

WebSocket은 브라우저와 서버가 연결을 유지한 채 양방향으로 메시지를 주고받는 방식입니다. HTTP 요청처럼 매번 새로 묻고 답하는 흐름보다 실시간 게임에 잘 맞습니다.

클라이언트가 서버에 보내는 메시지는 보통 "내 위치는 x=120이다"가 아니라 "오른쪽 키를 누르고 있다", "공격 버튼을 눌렀다" 같은 입력이어야 합니다. 서버는 이 입력을 받아 실제 위치, 충돌, 피해량, 점수를 계산한 뒤 모든 클라이언트에 결과 상태를 보냅니다. 경쟁 요소가 있는 게임에서는 서버가 최종 상태를 판단하는 구조가 치트 방지에 유리합니다.1

아주 단순화하면 클라이언트 코드는 이런 모양이 됩니다.

const socket = new WebSocket('wss://example.com/game');

socket.addEventListener('open', () => {
  socket.send(JSON.stringify({ type: 'join', roomId: 'room-1' }));
});

function sendInput(input) {
  if (socket.readyState !== WebSocket.OPEN) return;

  socket.send(JSON.stringify({
    type: 'input',
    left: input.left,
    right: input.right,
    jump: input.jump
  }));
}

socket.addEventListener('message', (event) => {
  const message = JSON.parse(event.data);

  if (message.type === 'state') {
    applyServerState(message.players);
  }
});

Phaser Scene은 여전히 입력을 읽고 화면을 그립니다. 다만 최종 게임 상태는 서버에서 온 데이터를 기준으로 맞춥니다. 즉, Phaser가 "보여 주고 조작하는 역할"을 맡고, 서버가 "중요한 게임 판정을 맡는 역할"을 나눠 갖는다고 생각하면 이해하기 쉽습니다.

동기화에서 생기는 문제

멀티플레이어를 붙이면 게임은 갑자기 어려워집니다. 이유는 네트워크 때문입니다. 내 컴퓨터에서 오른쪽 키를 누른 순간 서버에 메시지가 도착하고, 서버가 계산한 뒤 다시 내 화면에 돌아오기까지 시간이 걸립니다. 이것을 지연 시간, 즉 latency라고 부릅니다.

지연 시간을 그대로 받아들이면 캐릭터가 늦게 움직입니다. 그래서 실시간 게임에서는 보통 로컬 플레이어를 먼저 움직입니다. 사용자가 키를 누르면 내 화면에서는 즉시 이동시키고, 나중에 서버 상태가 도착하면 차이를 보정합니다. 이를 클라이언트 예측이라고 부릅니다.

반대로 다른 플레이어는 내 입력으로 직접 움직일 수 없습니다. 서버에서 받은 위치를 그대로 매번 적용하면 캐릭터가 순간이동하듯 끊겨 보일 수 있습니다. 이때는 최근 서버 상태 몇 개를 저장해 두고 그 사이를 부드럽게 이어 그립니다. 이를 보간, interpolation이라고 합니다.

처음부터 완벽한 네트워크 코드를 만들 필요는 없습니다. 다음 순서로 확장하는 것이 안전합니다.

  1. 같은 방에 접속한 플레이어 목록을 보여 준다.

  2. 각 플레이어의 위치를 서버에서 받아 화면에 표시한다.

  3. 로컬 플레이어 입력을 서버에 보낸다.

  4. 서버가 위치와 충돌을 계산해 상태를 방송한다.

  5. 끊김이 보이면 예측과 보간을 추가한다.

  6. 재접속, 방 나가기, 비정상 종료를 처리한다.

처음에는 WebSocket을 직접 써서 구조를 이해해도 좋습니다. 프로젝트가 커지면 Socket.IO나 Colyseus처럼 방, 재접속, 상태 동기화를 돕는 라이브러리를 검토할 수 있습니다. 단, 라이브러리를 쓰더라도 "클라이언트는 입력을 보내고, 서버는 중요한 상태를 판단한다"는 원칙은 유지해야 합니다.

플러그인을 고르는 법

Phaser 생태계에는 UI, 입력, 경로 찾기, 광고, 캡처, 특수 효과 같은 다양한 플러그인이 있습니다.2 플러그인은 시간을 아껴 주지만, 무조건 좋은 선택은 아닙니다. 작은 기능 하나를 위해 오래 관리되지 않은 플러그인을 추가하면 나중에 Phaser 버전 업데이트나 빌드 과정에서 문제가 생길 수 있습니다.

플러그인을 고를 때는 다음을 확인하세요.

  • 현재 사용하는 Phaser 버전을 지원하는가?

  • 최근 업데이트가 너무 오래되지 않았는가?

  • 예제 코드가 실제로 실행되는가?

  • 라이선스가 프로젝트 배포 방식과 맞는가?

  • 기능이 너무 크지 않은가?

  • 문제가 생겼을 때 직접 대체 구현을 할 수 있는가?

특히 UI 플러그인은 편리하지만 게임 전체 구조에 깊게 들어오는 경우가 많습니다. 버튼 몇 개라면 직접 만드는 편이 나을 수 있습니다. 반대로 복잡한 텍스트 입력, 스크롤 목록, 대화창 시스템처럼 직접 만들기 번거로운 기능은 플러그인을 검토할 가치가 있습니다.

좋은 플러그인은 프로젝트의 시간을 줄여 주지만, 나쁜 플러그인은 프로젝트의 발목을 잡습니다. 그래서 "지금 당장 편한가?"뿐 아니라 "나중에 이 기능을 빼거나 바꿀 수 있는가?"까지 함께 생각하는 편이 좋습니다.

간단한 Scene 플러그인 만들기

플러그인은 꼭 거대한 기능일 필요가 없습니다. 프로젝트 안에서 반복되는 작은 연출도 플러그인으로 묶을 수 있습니다. 예를 들어 점수 텍스트나 버튼을 짧게 튀게 하는 효과를 여러 Scene에서 반복한다고 해 봅시다.

class JuicePlugin extends Phaser.Plugins.ScenePlugin {
  constructor(scene, pluginManager) {
    super(scene, pluginManager);
  }

  pop(target) {
    this.scene.tweens.add({
      targets: target,
      scaleX: target.scaleX * 1.2,
      scaleY: target.scaleY * 1.2,
      duration: 80,
      yoyo: true,
      ease: 'Sine.Out'
    });
  }

  flash(target, color = 0xffffff) {
    target.setTint(color);
    this.scene.time.delayedCall(80, () => {
      target.clearTint();
    });
  }
}

게임 설정에 Scene 플러그인으로 등록합니다.

const config = {
  type: Phaser.AUTO,
  width: 960,
  height: 540,
  scene: [PlayScene],
  plugins: {
    scene: [
      { key: 'JuicePlugin', plugin: JuicePlugin, mapping: 'juice' }
    ]
  }
};

이제 Scene 안에서 다음처럼 사용할 수 있습니다.

this.juice.pop(this.scoreText);
this.juice.flash(this.player, 0xff5555);

이런 작은 플러그인은 프로젝트의 표현 규칙을 한곳에 모읍니다. 코인 획득, 피격, 버튼 클릭이 모두 비슷한 강도로 반응해야 한다면 JuicePlugin 같은 도구가 유용합니다. 핵심은 "여러 곳에서 반복되는 기능인가?"입니다. 한 Scene에서 한 번만 쓰는 코드는 굳이 플러그인으로 만들 필요가 없습니다.

광고, 랭킹, 업적, 분석 도구 붙이기

웹 게임을 서비스로 운영하려면 외부 서비스와 연결하게 됩니다. 대표적으로 광고, 랭킹, 업적, 분석 도구가 있습니다. 이 기능들은 Phaser 코드와 직접 섞지 않는 편이 좋습니다. 대신 중간에 얇은 래퍼를 둡니다.

export const GameServices = {
  submitScore(score) {
    if (!window.leaderboardApi) return;
    window.leaderboardApi.submit(score);
  },

  unlockAchievement(id) {
    if (!window.achievementApi) return;
    window.achievementApi.unlock(id);
  },

  track(eventName, data = {}) {
    if (!window.analyticsApi) return;
    window.analyticsApi.track(eventName, data);
  }
};

Scene에서는 특정 서비스 이름을 직접 부르지 않습니다.

GameServices.track('level_clear', {
  level: this.currentLevel,
  time: this.elapsedTime
});

GameServices.submitScore(this.score);

이렇게 하면 나중에 분석 도구를 바꾸거나 랭킹 서버를 직접 만들 때 Scene 코드를 크게 고치지 않아도 됩니다. 실제 서비스 코드에서는 광고 로딩이나 점수 전송이 비동기로 동작하는 경우가 많으므로, 실패했을 때의 처리도 함께 준비해야 합니다.

외부 서비스를 붙일 때 주의할 점도 있습니다. 광고는 로딩 실패를 전제로 처리해야 합니다. 보상형 광고라면 "광고를 끝까지 봤다"는 결과를 신뢰할 수 있는 방식으로 확인해야 합니다. 랭킹은 사용자가 보낸 점수를 그대로 믿으면 안 됩니다. 중요한 게임에서는 서버에서 점수를 검증해야 합니다. 분석 도구는 너무 많은 이벤트를 보내지 말고, 개인정보와 관련된 값은 신중하게 다뤄야 합니다.

가장 좋은 방식은 게임이 외부 서비스 없이도 정상적으로 끝까지 플레이되게 만드는 것입니다. 광고가 안 떠도 게임은 멈추면 안 됩니다. 랭킹 서버가 잠시 느려도 결과 화면은 보여야 합니다.

데스크톱과 모바일 앱으로 포장하기

Phaser 게임은 기본적으로 웹 게임입니다. 하지만 같은 코드를 데스크톱 앱이나 모바일 앱 형태로 배포할 수도 있습니다.

Electron은 JavaScript, HTML, CSS로 데스크톱 애플리케이션을 만들 수 있게 해 주는 프레임워크입니다. 내부에 Chromium과 Node.js를 포함해 Windows, macOS, Linux용 앱을 만들 수 있습니다.3 Phaser 게임을 Electron에 넣으면 웹 게임을 데스크톱 창 안에서 실행하는 형태가 됩니다.

Electron을 검토할 만한 경우는 다음과 같습니다.

  • 오프라인 실행이 중요하다.

  • 데스크톱 파일 시스템 접근이 필요하다.

  • Steam 같은 PC 플랫폼 배포를 고려한다.

  • 웹 브라우저 주소창 없이 앱처럼 실행하고 싶다.

모바일 앱은 Capacitor를 검토할 수 있습니다. Capacitor 문서는 WebGL과 canvas 기반 게임을 모바일 기기에서 실행할 수 있는 대상으로 설명하며, Phaser를 웹 기반 게임 엔진의 예로 언급합니다.4 구조는 웹 앱을 iOS나 Android 프로젝트 안에 넣고, 필요한 경우 네이티브 기능을 플러그인으로 연결하는 방식에 가깝습니다.

다만 앱 포장은 마법이 아닙니다. 모바일 브라우저에서 무거운 게임은 앱으로 감싸도 여전히 무겁습니다. 터치 영역, 화면 회전, 사운드 잠금 해제, 저장소 권한, 앱 일시정지와 재개 처리를 다시 확인해야 합니다. 앱으로 만들기 전에 모바일 웹에서 먼저 충분히 테스트하는 편이 안전합니다.

계속 학습하는 루틴

마지막으로 중요한 것은 학습 루틴입니다. Phaser는 기능이 많기 때문에 모든 API를 한 번에 외울 수 없습니다. 대신 문제를 기준으로 찾아보는 습관을 들이면 됩니다.

추천하는 루틴은 단순합니다.

  1. 만들고 싶은 작은 기능을 하나 정한다.

  2. Phaser 예제에서 비슷한 코드를 찾는다.

  3. 예제를 그대로 실행해 본다.

  4. 숫자와 조건을 바꿔 동작 원리를 확인한다.

  5. 내 프로젝트 구조에 맞게 옮긴다.

  6. 반복되는 부분은 함수, 클래스, 플러그인으로 정리한다.

Phaser의 공식 Learn 페이지는 시작 가이드, 첫 게임 만들기, 5000개 이상의 코드 예제, Sandbox, 튜토리얼과 개발 로그를 학습 자료로 제공합니다.5 특히 예제는 API를 이해하는 가장 빠른 자료입니다. 문서를 읽기 전에 예제를 실행해 보고, 그다음 필요한 메서드의 의미를 확인하면 덜 지칩니다.

커뮤니티 자료를 볼 때는 날짜를 확인하세요. Phaser는 오래된 자료도 많습니다. Phaser 2 또는 Phaser CE 예제를 Phaser 3 프로젝트에 그대로 붙이면 맞지 않는 부분이 생길 수 있습니다. 코드가 실행되지 않을 때는 먼저 버전을 확인하는 습관이 필요합니다.

다음 게임을 정하는 기준

이 책의 목표는 Phaser의 모든 기능을 나열하는 것이 아니었습니다. 작은 게임을 실제로 완성할 수 있는 흐름을 익히는 것이 목표였습니다. 이제 다음 프로젝트를 고를 때는 너무 큰 게임보다 "끝낼 수 있는 게임"을 선택하세요.

좋은 다음 목표는 이런 형태입니다.

  • 3개의 스테이지가 있는 짧은 플랫폼 게임

  • 2분 동안 버티는 슈팅 게임

  • 20레벨짜리 퍼즐 게임

  • 랭킹 하나가 붙은 점수 경쟁 게임

  • 모바일 터치 조작을 제대로 다듬은 캐주얼 게임

완성한 뒤 하나씩 확장하면 됩니다. 먼저 재미를 확인하고, 그다음 저장을 붙이고, 그다음 랭킹을 붙이고, 그다음 광고나 앱 배포를 검토하세요. 순서가 바뀌면 기술은 많아지지만 게임은 끝나지 않을 수 있습니다.

Phaser는 브라우저 안에서 빠르게 실험하고, 배포하고, 고칠 수 있는 좋은 도구입니다. 이제 필요한 것은 더 많은 API가 아니라 작은 완성작을 계속 만드는 습관입니다. 완성한 게임 하나는 읽기만 한 튜토리얼 열 개보다 더 많은 것을 알려 줍니다.

참고

1Phaser Multiplayer Tutorial: Add Real-Time Networking to Your Game

2Phaser Plugins

3Electron Introduction

4Games | Capacitor Documentation

5Learn Phaser - Tutorials, Examples & Documentation | Phaser

책 목록

Phaser로 시작하는 웹 2D 게임 개발

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기