개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← Lion Survival

매니저 패턴 — PlayingScene을 얇게 유지하기

2026년 5월 20일
phaser3architecturemanager-pattern

게임 코드가 복잡해지는 이유가 있다.

모든 로직이 씬 클래스 안에 쌓이기 시작하면, 결국 수백 줄짜리 괴물 파일이 된다. PlayingScene이 그렇게 되지 않도록 유틸리티 함수들로 역할을 분리했다.

attackManager — 공격 이벤트 등록·수정·제거

공격 관련 모든 상태는 scene.m_attackEvents 객체에서 관리한다.

// 공격 추가
export function addAttackEvent(scene, attackType, damage, scale, repeatGap) {
  scene.m_attackEvents[attackType] = {
    event: scene.time.addEvent({
      delay: repeatGap,
      callback: () => doAttackOneSet(scene, attackType, damage, scale),
      loop: true,
    }),
    damage,
    scale,
    repeatGap,
  };
}

// 레벨업 때 스케일 변경
export function setAttackScale(scene, attackType, scale) {
  scene.m_attackEvents[attackType].scale = scale;
}

// 공격 제거
export function removeAttack(scene, attackType) {
  scene.m_attackEvents[attackType].event.remove();
  delete scene.m_attackEvents[attackType];
}

PlayingScene에서 addAttackEvent(this, "beam", 10, 1, 1000) 한 줄이면 빔 공격이 1초마다 발동된다. 내부 타이머 관리는 attackManager가 알아서 한다.

mobManager — 몬스터 스폰 관리

export function addMobEvent(scene, repeatGap, texture, animKey, initHp, dropRate) {
  const event = scene.time.addEvent({
    delay: repeatGap,
    callback: () => addMob(scene, texture, animKey, initHp, dropRate),
    loop: true,
  });
  scene.m_mobEvents.push(event);
}

export function removeOldestMobEvent(scene) {
  const oldest = scene.m_mobEvents.shift();
  oldest.remove();
}

레벨업할 때마다 약한 몬스터 스폰을 제거하고 강한 몬스터 스폰을 추가한다. scene.m_mobEvents는 배열이라 FIFO로 가장 오래된 이벤트를 제거한다.

플레이어 주변 랜덤 위치에 스폰

function getRandomPosition(x, y, distance) {
  const randomAngle = Math.random() * Math.PI * 2;
  const randomDistance = Math.random() * distance + distance;
  return [
    x + randomDistance * Math.cos(randomAngle),
    y + randomDistance * Math.sin(randomAngle),
  ];
}

화면 밖에서 등장하도록 플레이어 기준 distance 이상 떨어진 곳에 스폰한다. 갑자기 플레이어 위에 튀어나오면 안 되니까.

backgroundManager — 타일 배경 관리

export function setBackground(scene, bgKey) {
  if (scene.m_background) scene.m_background.destroy();

  scene.m_background = scene.add.tileSprite(
    0, 0,
    scene.scale.width * 4,
    scene.scale.height * 4,
    bgKey
  );
  scene.m_background.setDepth(-1);
}

tileSprite는 이미지를 반복 타일링해서 무한한 배경처럼 보이게 한다. PlayingScene.update에서 플레이어 위치에 맞게 tilePositionX/Y를 갱신하면 스크롤되는 배경이 완성된다.

레벨업 시 setBackground(this, "background2")로 배경을 교체한다. 게임 분위기가 달라져서 플레이어에게 진행감을 준다.

sceneManager — 씬 전환 처리

export function pause(scene, type) {
  scene.scene.pause("playGame");
  scene.scene.launch(type === "levelup" ? "levelupScene" : "pauseScene");
}

일시정지와 레벨업 화면 모두 동일한 패턴이다. 게임 씬을 pause하고 오버레이 씬을 launch한다. 오버레이 씬을 닫으면 게임이 다시 재개된다.

왜 이렇게 했는가

PlayingScene.create를 보면 매니저 함수 호출의 연속이다.

setBackground(this, "background1");
addMobEvent(this, 1000, "mob1", "mob1_anim", 10, 0.9);
addAttackEvent(this, "claw", 10, 2.3, 1500);
createTime(this);

각 기능의 구현 세부사항을 알 필요가 없다. 어떤 일을 하는지만 보인다.

유틸리티 함수 파일로 분리하면 세 가지가 좋아진다. 씬이 가볍게 유지되고, 테스트할 때 단위 분리가 쉬워지고, 나중에 코드를 다시 봤을 때 읽기가 편하다.

← 이전 글경험치와 레벨업 — 아이템과 UI
다음 글 →README와 데모 GIF — 게임을 소개하는 법