🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Phaser 플레이어 캐릭터 구현 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 5. · 10 Views

Phaser 플레이어 캐릭터 구현 완벽 가이드

Phaser 게임 엔진으로 플레이어 캐릭터를 구현하는 방법을 단계별로 알아봅니다. 스프라이트 로드부터 이동, 점프, 애니메이션, 상태 머신, 더블 점프까지 게임 개발의 핵심을 다룹니다.


목차

  1. 플레이어_스프라이트_로드
  2. 좌우_이동_구현
  3. 점프_구현
  4. 애니메이션_연결
  5. 상태_머신_패턴
  6. 더블_점프_구현

1. 플레이어 스프라이트 로드

게임 개발을 시작한 김개발 씨는 첫 번째 관문에서 막혔습니다. 화면에 플레이어 캐릭터를 어떻게 띄워야 할까요?

선배 박시니어 씨가 다가와 말했습니다. "먼저 스프라이트를 로드하는 법부터 배워봐요."

스프라이트란 게임에서 사용되는 2D 이미지를 말합니다. 마치 만화책의 한 컷처럼, 캐릭터의 모습을 담은 이미지 파일입니다.

Phaser에서는 preload 함수에서 이미지를 미리 불러오고, create 함수에서 화면에 배치합니다.

다음 코드를 살펴봅시다.

class GameScene extends Phaser.Scene {
  preload() {
    // 스프라이트시트 로드: 키, 경로, 프레임 크기 설정
    this.load.spritesheet('player', 'assets/player.png', {
      frameWidth: 32,
      frameHeight: 48
    });
  }

  create() {
    // 물리 엔진이 적용된 스프라이트 생성
    this.player = this.physics.add.sprite(100, 300, 'player');
    // 중력 설정으로 캐릭터가 아래로 떨어지게
    this.player.setGravityY(800);
    // 월드 경계 충돌 활성화
    this.player.setCollideWorldBounds(true);
  }
}

김개발 씨는 입사 첫 주에 회사의 신규 게임 프로젝트에 투입되었습니다. 팀장님이 말했습니다.

"자네가 플레이어 캐릭터 움직임을 담당하게 될 거야." 설렘 반 걱정 반으로 모니터 앞에 앉았지만, 도대체 어디서부터 시작해야 할지 막막했습니다. 점심시간에 선배 박시니어 씨에게 조심스럽게 물었습니다.

"선배님, 게임에서 캐릭터 이미지는 어떻게 화면에 띄우나요?" 박시니어 씨가 웃으며 대답했습니다. "그건 스프라이트라는 개념부터 알아야 해.

스프라이트는 게임에서 움직이는 모든 2D 그래픽을 말하는 거야." 스프라이트를 쉽게 이해하려면 플립북 애니메이션을 떠올려 보면 됩니다. 어린 시절 공책 귀퉁이에 조금씩 다른 그림을 그리고 빠르게 넘기면 움직이는 것처럼 보였던 경험이 있으실 겁니다.

게임의 스프라이트도 마찬가지입니다. 여러 장의 이미지를 빠르게 교체하면서 캐릭터가 걷거나 뛰는 것처럼 보이게 만드는 것입니다.

Phaser에서 스프라이트를 다루는 과정은 크게 두 단계로 나뉩니다. 첫 번째는 preload 단계입니다.

이 단계에서는 게임에 필요한 모든 이미지 파일을 메모리에 미리 불러옵니다. 마치 요리를 시작하기 전에 재료를 미리 손질해 두는 것과 같습니다.

두 번째는 create 단계입니다. 이미 준비된 이미지를 실제로 게임 화면에 배치합니다.

여기서 중요한 점은 단순히 이미지만 배치하는 것이 아니라, 물리 엔진을 적용한다는 것입니다. 물리 엔진이 적용되면 캐릭터에 중력이 작용하고, 다른 물체와 충돌할 수 있게 됩니다.

코드를 살펴보면, this.load.spritesheet 함수가 눈에 들어옵니다. 여기서 첫 번째 인자 'player'는 나중에 이 이미지를 참조할 때 사용할 키 이름입니다.

두 번째 인자는 실제 이미지 파일의 경로입니다. 세 번째 인자인 객체에서는 각 프레임의 크기를 지정합니다.

스프라이트시트란 여러 애니메이션 프레임이 한 장의 이미지에 격자 형태로 배치된 파일입니다. 가로 32픽셀, 세로 48픽셀 크기로 잘라서 각각의 프레임으로 사용하겠다는 의미입니다.

create 함수에서 this.physics.add.sprite를 호출하면 물리 엔진이 적용된 스프라이트가 생성됩니다. 좌표 (100, 300)에 'player' 키로 등록된 이미지가 배치됩니다.

**setGravityY(800)**은 아래 방향으로 800의 힘으로 중력을 적용하겠다는 뜻입니다. 마지막으로 **setCollideWorldBounds(true)**는 캐릭터가 게임 화면 밖으로 나가지 않도록 경계를 설정합니다.

이 설정이 없으면 캐릭터가 화면 아래로 끝없이 떨어질 것입니다. 김개발 씨가 코드를 따라 치고 실행해 보았습니다.

드디어 화면에 캐릭터가 나타났습니다! 중력에 의해 아래로 떨어지다가 화면 바닥에서 멈추는 모습을 보며 뿌듯함을 느꼈습니다.

실전 팁

💡 - 스프라이트시트 이미지는 각 프레임 크기가 동일해야 합니다

  • preload에서 로드한 키 이름과 create에서 사용하는 키 이름이 일치해야 합니다

2. 좌우 이동 구현

화면에 캐릭터를 띄우는 데 성공한 김개발 씨는 다음 과제를 받았습니다. "이제 키보드로 캐릭터를 움직여 봐요." 가만히 서 있는 캐릭터를 보며 생각했습니다.

어떻게 하면 좌우로 걸어 다니게 할 수 있을까요?

Phaser에서 키보드 입력을 받으려면 커서 키 객체를 생성해야 합니다. update 함수에서 매 프레임마다 키 입력을 확인하고, 플레이어의 **속도(velocity)**를 조절하여 움직임을 구현합니다.

왼쪽 키를 누르면 음수, 오른쪽 키를 누르면 양수 속도를 설정합니다.

다음 코드를 살펴봅시다.

class GameScene extends Phaser.Scene {
  create() {
    this.player = this.physics.add.sprite(100, 300, 'player');
    // 커서 키(화살표 키) 입력 객체 생성
    this.cursors = this.input.keyboard.createCursorKeys();
  }

  update() {
    // 왼쪽 화살표 키를 누르고 있으면
    if (this.cursors.left.isDown) {
      this.player.setVelocityX(-160);
    }
    // 오른쪽 화살표 키를 누르고 있으면
    else if (this.cursors.right.isDown) {
      this.player.setVelocityX(160);
    }
    // 아무 키도 누르지 않으면 정지
    else {
      this.player.setVelocityX(0);
    }
  }
}

박시니어 씨가 김개발 씨 옆에 앉으며 말했습니다. "게임에서 가장 중요한 건 뭘까요?" 김개발 씨가 잠시 생각하다 대답했습니다.

"음... 재미요?" 박시니어 씨가 고개를 끄덕였습니다.

"맞아요. 그리고 재미의 첫걸음은 바로 조작감이에요." 플레이어가 캐릭터를 자유롭게 움직일 수 있어야 게임에 몰입할 수 있습니다.

아무리 그래픽이 화려해도 캐릭터가 내 뜻대로 움직이지 않으면 답답할 뿐입니다. Phaser에서 키보드 입력을 처리하는 방법은 생각보다 간단합니다.

createCursorKeys 함수를 호출하면 화살표 키와 스페이스바 입력을 한 번에 관리할 수 있는 객체가 생성됩니다. 마치 게임 컨트롤러의 방향 패드처럼 말이죠.

여기서 중요한 개념이 바로 update 함수입니다. Phaser는 초당 60번 정도 화면을 다시 그립니다.

이것을 프레임이라고 부릅니다. update 함수는 매 프레임마다 자동으로 호출되어, 게임 상태를 갱신하는 역할을 합니다.

따라서 update 함수 안에서 "지금 왼쪽 키가 눌려 있는가?"를 계속 확인해야 합니다. isDown 속성이 true라면 해당 키가 현재 눌린 상태라는 뜻입니다.

캐릭터를 움직이게 하려면 **속도(velocity)**를 설정합니다. X축 속도가 양수면 오른쪽으로, 음수면 왼쪽으로 움직입니다.

160이라는 숫자는 초당 160픽셀의 속도를 의미합니다. 이 값을 조절하면 캐릭터의 이동 속도가 달라집니다.

한 가지 주의할 점이 있습니다. 아무 키도 누르지 않았을 때 속도를 0으로 설정하는 else 문을 빠뜨리면 안 됩니다.

이 부분이 없으면 키에서 손을 떼도 캐릭터가 계속 미끄러지듯 움직입니다. 관성이 적용된 것처럼 보이지만, 의도한 것이 아니라면 어색한 조작감을 만들어냅니다.

또 하나 신경 써야 할 부분은 else if 구조입니다. 왼쪽과 오른쪽 키를 동시에 누르면 어떻게 될까요?

else if를 사용했기 때문에 왼쪽 키가 우선 처리되고 오른쪽 키는 무시됩니다. 만약 동시 입력 시 캐릭터가 멈추게 하고 싶다면 별도의 로직이 필요합니다.

김개발 씨가 코드를 완성하고 테스트해 보았습니다. 화살표 키를 누르니 캐릭터가 좌우로 움직입니다!

하지만 뭔가 이상합니다. 캐릭터가 오른쪽을 향해 왼쪽으로 뒷걸음질하고 있었습니다.

박시니어 씨가 힌트를 주었습니다. "flipX 속성을 사용해서 캐릭터가 바라보는 방향을 바꿔줘야 해요." 왼쪽으로 갈 때 this.player.flipX = true, 오른쪽으로 갈 때 this.player.flipX = false를 추가하면 해결됩니다.

실전 팁

💡 - 속도 값을 조절하여 게임에 맞는 이동 속도를 찾으세요

  • flipX 속성으로 캐릭터가 바라보는 방향을 자연스럽게 전환하세요

3. 점프 구현

좌우로 걸어 다니는 캐릭터를 보며 김개발 씨는 한 가지 아쉬움을 느꼈습니다. "점프도 하고 싶은데..." 플랫포머 게임의 꽃은 역시 점프입니다.

장애물을 뛰어넘고, 높은 곳에 올라가는 짜릿한 경험은 점프 없이는 불가능합니다.

점프는 Y축 속도를 음수로 설정하여 구현합니다. 단, 무한 점프를 방지하기 위해 바닥에 닿아 있을 때만 점프할 수 있도록 조건을 추가해야 합니다.

Phaser의 물리 엔진은 body.onFloor() 메서드로 바닥 접촉 여부를 알려줍니다.

다음 코드를 살펴봅시다.

update() {
  // 좌우 이동 코드 (생략)

  // 점프 구현: 위쪽 화살표 + 바닥에 닿아 있을 때만
  if (this.cursors.up.isDown && this.player.body.onFloor()) {
    // Y축 음수 속도 = 위로 이동
    this.player.setVelocityY(-400);
  }
}

create() {
  this.player = this.physics.add.sprite(100, 300, 'player');
  // 바닥 생성
  this.ground = this.physics.add.staticGroup();
  this.ground.create(400, 568, 'ground');
  // 플레이어와 바닥의 충돌 설정
  this.physics.add.collider(this.player, this.ground);
}

김개발 씨는 어린 시절 즐겨하던 슈퍼 마리오를 떠올렸습니다. 점프 한 번으로 굼바를 밟고, 벽돌을 부수고, 깃발에 올라타는 그 쾌감!

이제 자신의 게임에도 점프를 넣을 차례입니다. 물리적으로 점프란 무엇일까요?

땅을 박차고 위로 올라간 뒤, 중력에 의해 다시 떨어지는 움직임입니다. Phaser에서는 이것을 Y축 속도로 표현합니다.

화면 좌표계에서 Y축은 아래쪽이 양수입니다. 따라서 위로 올라가려면 음수 속도를 설정해야 합니다.

**setVelocityY(-400)**은 초기에 위로 400의 속도로 올라가라는 의미입니다. 이후 중력이 계속 작용하면서 속도가 점점 줄어들고, 결국 음수에서 양수로 바뀌면서 떨어지게 됩니다.

하지만 여기서 중요한 문제가 있습니다. 점프 키를 계속 누르면 어떻게 될까요?

조건 없이 구현하면 공중에서도 무한으로 점프할 수 있습니다. 하늘 높이 날아가 버리는 캐릭터를 보며 김개발 씨는 당황했습니다.

이 문제를 해결하는 열쇠가 바로 body.onFloor() 메서드입니다. 이 메서드는 캐릭터가 어떤 물체 위에 서 있을 때 true를 반환합니다.

점프 조건에 이것을 추가하면 "바닥에 닿아 있을 때만 점프 가능"이라는 자연스러운 규칙이 만들어집니다. 그런데 onFloor가 제대로 작동하려면 충돌 설정이 필수입니다.

플레이어와 바닥 사이에 충돌이 없으면 캐릭터는 바닥을 뚫고 떨어질 것이고, onFloor도 항상 false를 반환할 것입니다. staticGroup은 움직이지 않는 정적 물체를 그룹으로 관리하는 기능입니다.

바닥, 벽, 플랫폼 같은 배경 요소에 적합합니다. 정적 물체는 다른 물체와 충돌해도 밀리지 않습니다.

this.physics.add.collider는 두 물체 사이의 충돌을 활성화합니다. 이 한 줄로 플레이어가 바닥에 착지하고, 바닥 위를 걸어 다닐 수 있게 됩니다.

점프 높이와 느낌을 조절하고 싶다면 두 가지 값을 바꿔보세요. setVelocityY의 절댓값을 높이면 더 높이 뜁니다.

setGravityY 값을 높이면 더 빨리 떨어집니다. 이 두 값의 균형이 게임의 점프 감각을 결정합니다.

김개발 씨가 여러 값을 실험해 본 결과, 중력 800에 점프 속도 -400 정도가 자연스러운 느낌을 준다는 것을 알게 되었습니다. 물론 이것은 게임 장르와 스타일에 따라 달라질 수 있습니다.

실전 팁

💡 - 점프 높이는 초기 속도와 중력의 비율로 결정됩니다

  • 점프감을 테스트할 때는 여러 값을 직접 시도해보는 것이 좋습니다

4. 애니메이션 연결

키보드로 움직이는 캐릭터를 보며 김개발 씨는 뭔가 허전함을 느꼈습니다. 캐릭터가 움직이긴 하는데, 마치 미끄러지듯 이동하고 있었습니다.

다리도 움직이지 않고 팔도 흔들지 않는 로봇 같은 모습이었습니다.

애니메이션은 스프라이트시트의 여러 프레임을 순차적으로 재생하여 움직이는 것처럼 보이게 합니다. Phaser에서는 anims.create로 애니메이션을 정의하고, play 메서드로 재생합니다.

현재 상태에 따라 적절한 애니메이션을 선택해야 자연스러운 움직임이 만들어집니다.

다음 코드를 살펴봅시다.

create() {
  // 걷기 애니메이션 생성
  this.anims.create({
    key: 'walk',
    frames: this.anims.generateFrameNumbers('player', { start: 0, end: 7 }),
    frameRate: 10,
    repeat: -1  // -1은 무한 반복
  });

  // 대기 애니메이션
  this.anims.create({
    key: 'idle',
    frames: [{ key: 'player', frame: 0 }],
    frameRate: 1
  });

  // 점프 애니메이션
  this.anims.create({
    key: 'jump',
    frames: [{ key: 'player', frame: 8 }],
    frameRate: 1
  });
}

update() {
  if (!this.player.body.onFloor()) {
    this.player.anims.play('jump', true);
  } else if (this.cursors.left.isDown || this.cursors.right.isDown) {
    this.player.anims.play('walk', true);
  } else {
    this.player.anims.play('idle', true);
  }
}

박시니어 씨가 김개발 씨의 화면을 보더니 말했습니다. "캐릭터가 움직이긴 하는데, 뭔가 생동감이 없네요." 김개발 씨도 동의했습니다.

"맞아요. 다리라도 움직여야 걸어가는 것 같은데요." 애니메이션의 원리는 옛날 만화 영화와 같습니다.

조금씩 다른 그림을 빠르게 보여주면 눈의 착시로 인해 움직이는 것처럼 보입니다. 초당 24프레임 이상이면 부드러운 움직임으로 인식됩니다.

게임에서 애니메이션은 보통 스프라이트시트 형태로 준비됩니다. 한 장의 이미지 안에 걷기 1, 걷기 2, 걷기 3...

이런 식으로 연속된 동작이 나열되어 있습니다. 이것을 순서대로 보여주면 걸어가는 애니메이션이 완성됩니다.

anims.create 함수로 애니메이션을 정의합니다. key는 이 애니메이션을 호출할 때 사용할 이름입니다.

frames는 어떤 프레임들을 사용할지 지정합니다. generateFrameNumbers를 사용하면 시작 프레임부터 끝 프레임까지 자동으로 배열을 만들어줍니다.

frameRate는 초당 몇 장의 프레임을 보여줄지 결정합니다. 10으로 설정하면 초당 10장, 즉 0.1초마다 다음 프레임으로 넘어갑니다.

값이 높을수록 빠른 동작, 낮을수록 느린 동작이 됩니다. repeat: -1은 애니메이션을 무한 반복하겠다는 의미입니다.

걷기 애니메이션은 계속 걷는 동안 반복되어야 하니까요. 반면 점프나 공격처럼 한 번만 재생될 애니메이션은 이 옵션을 생략합니다.

update 함수에서 애니메이션을 선택하는 로직을 살펴봅시다. 우선순위가 중요합니다.

공중에 떠 있으면 무조건 점프 애니메이션을 보여줍니다. 바닥에 있고 이동 키가 눌려 있으면 걷기 애니메이션입니다.

둘 다 아니면 가만히 서 있는 대기 애니메이션입니다. play 함수의 두 번째 인자 true는 중요한 역할을 합니다.

이것은 "이미 같은 애니메이션이 재생 중이면 처음부터 다시 시작하지 마라"는 의미입니다. 이 옵션이 없으면 매 프레임마다 애니메이션이 0번 프레임부터 다시 시작해서 덜덜 떨리는 것처럼 보입니다.

김개발 씨가 애니메이션을 적용하고 실행해 보았습니다. 드디어 캐릭터가 발을 구르며 걸어갑니다!

점프할 때는 팔을 벌리고, 가만히 있을 때는 숨을 쉬는 것처럼 미세하게 움직입니다. 게임이 한층 살아있는 느낌이 들었습니다.

실전 팁

💡 - 애니메이션 전환 시 play의 두 번째 인자를 true로 설정하여 끊김을 방지하세요

  • frameRate를 조절하여 캐릭터의 움직임 속도감을 맞추세요

5. 상태 머신 패턴

프로젝트가 진행되면서 캐릭터에 새로운 동작들이 추가되었습니다. 달리기, 웅크리기, 사다리 타기, 벽 타기...

코드가 점점 복잡해지더니 어느 순간 버그가 터지기 시작했습니다. 웅크리면서 점프하는 이상한 동작이 나오기도 했습니다.

**상태 머신(State Machine)**은 캐릭터가 가질 수 있는 상태를 명확히 정의하고, 각 상태에서 허용된 전환만 가능하게 만드는 패턴입니다. 마치 신호등처럼 빨강에서 바로 초록으로 갈 수 없고 반드시 노랑을 거쳐야 하는 규칙을 만드는 것입니다.

다음 코드를 살펴봅시다.

class Player {
  constructor(scene) {
    this.scene = scene;
    this.sprite = scene.physics.add.sprite(100, 300, 'player');
    // 가능한 상태 정의
    this.states = { IDLE: 'idle', WALK: 'walk', JUMP: 'jump', FALL: 'fall' };
    this.currentState = this.states.IDLE;
  }

  setState(newState) {
    // 현재 상태에서 전환 가능한지 검사
    if (this.currentState === this.states.JUMP && newState === this.states.WALK) {
      return; // 점프 중에는 걷기로 전환 불가
    }
    this.currentState = newState;
    this.sprite.anims.play(newState, true);
  }

  update(cursors) {
    // 상태에 따른 동작 처리
    switch (this.currentState) {
      case this.states.IDLE:
      case this.states.WALK:
        if (cursors.up.isDown && this.sprite.body.onFloor()) {
          this.sprite.setVelocityY(-400);
          this.setState(this.states.JUMP);
        }
        break;
    }
  }
}

김개발 씨의 코드는 어느새 스파게티처럼 엉켜 있었습니다. if문 안에 if문, 그 안에 또 if문...

조건이 20개가 넘어가자 어디서 뭐가 잘못됐는지 찾기도 힘들었습니다. 박시니어 씨가 코드 리뷰를 하다가 한숨을 쉬었습니다.

"김개발 씨, 이건 상태 머신 패턴을 적용해야 해요. 안 그러면 동작이 추가될 때마다 버그가 기하급수적으로 늘어날 거예요." 상태 머신이란 무엇일까요?

쉽게 말해 "캐릭터가 지금 무엇을 하고 있는지"를 명확하게 정의하는 것입니다. 마치 사람의 상태처럼요.

지금 걷고 있는지, 뛰고 있는지, 앉아 있는지, 자고 있는지를 구분하는 것입니다. 상태 머신의 핵심은 두 가지입니다.

첫째, 가능한 상태를 미리 정의합니다. IDLE, WALK, JUMP, FALL 같은 상태를 열거합니다.

둘째, 상태 간 전환 규칙을 정합니다. 걷는 중에는 점프할 수 있지만, 점프 중에는 걷기로 바로 전환할 수 없다는 식입니다.

이것이 왜 중요할까요? 상태가 명확하면 "지금 점프 중인가?" 같은 질문에 간단히 답할 수 있습니다.

currentState === JUMP만 확인하면 됩니다. 복잡한 조건 조합이 필요 없어집니다.

코드를 살펴보면, setState 함수가 전환을 담당합니다. 여기서 전환이 허용되는지 검사한 뒤 상태를 변경합니다.

상태가 바뀌면 해당 상태의 애니메이션도 자동으로 재생됩니다. update 함수는 현재 상태에 따라 다르게 동작합니다.

switch문으로 상태를 분기하면, IDLE 상태일 때의 로직과 JUMP 상태일 때의 로직을 깔끔하게 분리할 수 있습니다. 실무에서는 상태 머신을 더 정교하게 구현합니다.

각 상태를 별도의 클래스로 만들고, enter(상태 진입 시), execute(상태 유지 중), exit(상태 종료 시) 메서드를 정의하기도 합니다. 김개발 씨가 상태 머신을 적용하자 놀라운 일이 벌어졌습니다.

웅크리면서 점프하는 버그가 사라졌습니다! 코드도 훨씬 읽기 쉬워졌습니다.

새로운 동작을 추가할 때도 어디를 수정해야 하는지 명확해졌습니다. 박시니어 씨가 웃으며 말했습니다.

"이게 바로 디자인 패턴의 힘이에요. 처음엔 오히려 복잡해 보여도, 프로젝트가 커질수록 진가를 발휘하죠."

실전 팁

💡 - 상태 전환 규칙을 도표로 그려보면 설계가 명확해집니다

  • 각 상태에서 허용되는 입력과 전환을 문서화해두세요

6. 더블 점프 구현

기획팀에서 새로운 요청이 들어왔습니다. "캐릭터가 공중에서 한 번 더 점프할 수 있으면 좋겠어요." 더블 점프는 많은 플랫포머 게임에서 볼 수 있는 기능입니다.

하지만 기존의 "바닥에 닿아 있을 때만 점프" 로직으로는 구현할 수 없습니다.

더블 점프를 구현하려면 점프 횟수를 추적해야 합니다. 바닥에서 점프하면 첫 번째 점프, 공중에서 한 번 더 점프하면 두 번째 점프입니다.

바닥에 착지하면 점프 횟수를 초기화합니다. 최대 점프 횟수를 변수로 관리하면 트리플 점프도 쉽게 확장할 수 있습니다.

다음 코드를 살펴봅시다.

class Player {
  constructor(scene) {
    this.sprite = scene.physics.add.sprite(100, 300, 'player');
    this.maxJumps = 2;      // 최대 점프 횟수
    this.jumpCount = 0;      // 현재 점프 횟수
    this.jumpPressed = false; // 키 중복 입력 방지
  }

  update(cursors) {
    // 바닥에 착지하면 점프 횟수 초기화
    if (this.sprite.body.onFloor()) {
      this.jumpCount = 0;
    }

    // 점프 키를 새로 눌렀고, 아직 점프 가능 횟수가 남았다면
    if (cursors.up.isDown && !this.jumpPressed && this.jumpCount < this.maxJumps) {
      this.sprite.setVelocityY(-400);
      this.jumpCount++;
      this.jumpPressed = true;
    }

    // 점프 키를 떼면 다시 점프 가능
    if (cursors.up.isUp) {
      this.jumpPressed = false;
    }
  }
}

김개발 씨는 처음에 간단하게 생각했습니다. "그냥 onFloor 조건을 빼면 되는 거 아닌가요?" 하지만 그렇게 하면 무한 점프가 되어버립니다.

점프 키를 연타하면 하늘 끝까지 올라갈 수 있게 됩니다. 더블 점프의 핵심은 횟수 제한입니다.

최대 2번까지만 점프할 수 있고, 바닥에 착지하면 카운트가 초기화됩니다. 이렇게 하면 땅에서 한 번, 공중에서 한 번, 총 두 번 점프할 수 있습니다.

하지만 여기서 또 다른 문제가 있습니다. update 함수는 매 프레임 호출됩니다.

점프 키를 누르고 있으면 한 프레임 안에 점프 두 번이 연속으로 실행될 수 있습니다. 60분의 1초 사이에 두 번 점프해버리면 더블 점프가 아니라 그냥 높은 점프가 되어버립니다.

이 문제를 해결하는 것이 jumpPressed 플래그입니다. 점프 키를 누르면 플래그를 true로 설정하고, 키를 떼면 다시 false로 만듭니다.

플래그가 true인 동안에는 점프 로직이 실행되지 않습니다. 이런 기법을 **엣지 검출(Edge Detection)**이라고 부릅니다.

키가 "눌린 상태"가 아니라 "눌리는 순간"만 감지하는 것입니다. 마치 벨을 누르면 한 번만 울리는 것처럼, 점프도 키를 누르는 순간 한 번만 실행됩니다.

코드에서 조건문의 순서도 중요합니다. onFloor 체크가 가장 먼저 나오는 이유는, 착지하는 순간 바로 점프 카운트를 초기화해야 하기 때문입니다.

착지하자마자 다시 점프할 수 있어야 자연스러운 조작감이 만들어집니다. 더블 점프를 트리플 점프로 확장하고 싶다면 maxJumps 값만 3으로 바꾸면 됩니다.

이처럼 숫자를 변수로 분리해두면 나중에 아이템을 먹으면 점프 횟수가 늘어나는 기능도 쉽게 추가할 수 있습니다. 게임에 따라 더블 점프의 연출을 다르게 할 수 있습니다.

두 번째 점프에서 다른 애니메이션을 재생하거나, 점프 높이를 다르게 설정하기도 합니다. jumpCount 값을 확인하면 몇 번째 점프인지 알 수 있으므로 이런 분기 처리가 가능합니다.

김개발 씨가 더블 점프를 구현하고 테스트해 보았습니다. 공중에서 한 번 더 점프하니 더 높은 발판에 올라갈 수 있게 되었습니다!

기획팀도 만족스러워했습니다. "이제 레벨 디자인의 폭이 훨씬 넓어졌어요!" 박시니어 씨가 마지막으로 조언했습니다.

"여기서 더 나아가면 벽 점프, 대시, 활강 같은 동작도 추가할 수 있어요. 상태 머신과 조합하면 복잡한 액션 게임도 만들 수 있죠." 김개발 씨의 눈이 반짝였습니다.

실전 팁

💡 - 점프 입력은 엣지 검출로 처리하여 한 번 누름에 한 번만 점프하게 하세요

  • maxJumps를 변수로 분리하면 아이템이나 스킬로 점프 횟수를 늘리기 쉬워집니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Phaser#GameDev#Sprite#Animation#StateMachine#Game

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.