🤖

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

⚠️

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

이미지 로딩 중...

게임 개발 적 캐릭터 구현 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 5. · 12 Views

게임 개발 적 캐릭터 구현 완벽 가이드

Phaser 게임 엔진을 사용하여 적 캐릭터를 구현하는 방법을 다룹니다. 스프라이트 로드부터 순찰 패턴, 추적 AI, 충돌 처리, 처치 방법, 다양한 적 유형까지 게임 개발의 핵심을 배워봅니다.


목차

  1. 적 스프라이트 로드
  2. 순찰 패턴 구현
  3. 플레이어 추적 AI
  4. 적과 충돌 처리
  5. 적 처치 방법
  6. 다양한 적 유형

1. 적 스프라이트 로드

김개발 씨는 첫 플랫포머 게임을 만들고 있습니다. 플레이어 캐릭터는 잘 움직이는데, 이제 적 캐릭터를 추가해야 할 차례입니다.

"적 이미지는 어떻게 불러오고, 화면에 배치하지?" 선배 박시니어 씨에게 물어보기로 했습니다.

스프라이트 로드란 게임에서 사용할 이미지 리소스를 메모리에 불러오는 과정입니다. 마치 영화 촬영 전에 배우와 소품을 섭외하는 것과 같습니다.

Phaser에서는 preload 함수에서 이미지를 로드하고, create 함수에서 실제로 화면에 배치합니다.

다음 코드를 살펴봅시다.

class GameScene extends Phaser.Scene {
  preload() {
    // 적 스프라이트시트 로드 (32x32 크기의 프레임)
    this.load.spritesheet('enemy', 'assets/enemy.png', {
      frameWidth: 32,
      frameHeight: 32
    });
  }

  create() {
    // 적 캐릭터 생성 및 물리 엔진 적용
    this.enemy = this.physics.add.sprite(400, 300, 'enemy');
    this.enemy.setCollideWorldBounds(true);

    // 걷기 애니메이션 생성
    this.anims.create({
      key: 'enemy_walk',
      frames: this.anims.generateFrameNumbers('enemy', { start: 0, end: 3 }),
      frameRate: 8,
      repeat: -1
    });
  }
}

김개발 씨는 입사 3개월 차 주니어 개발자입니다. 회사에서 작은 웹 게임 프로젝트를 맡게 되었는데, 플레이어 캐릭터까지는 어찌저찌 만들었습니다.

그런데 적 캐릭터를 추가하려니 막막했습니다. 선배 개발자 박시니어 씨가 다가와 말했습니다.

"적 캐릭터도 플레이어랑 크게 다르지 않아요. 일단 스프라이트부터 로드해 봅시다." 그렇다면 스프라이트란 정확히 무엇일까요?

쉽게 비유하자면, 스프라이트는 마치 종이 인형과 같습니다. 게임 화면에 등장하는 캐릭터, 아이템, 배경 요소들은 모두 스프라이트입니다.

특히 스프라이트시트는 여러 장의 그림을 한 장에 모아놓은 것으로, 애니메이션을 만들 때 유용합니다. Phaser 게임 엔진에서 리소스를 불러오는 과정은 크게 두 단계로 나뉩니다.

첫 번째는 preload 단계로, 게임에 필요한 모든 이미지와 사운드를 미리 불러옵니다. 두 번째는 create 단계로, 불러온 리소스를 실제 게임 오브젝트로 만듭니다.

위 코드를 한 줄씩 살펴보겠습니다. 먼저 this.load.spritesheet 메서드로 적 이미지를 불러옵니다.

첫 번째 인자는 나중에 이 이미지를 참조할 때 사용할 이름이고, 두 번째는 파일 경로입니다. frameWidth와 frameHeight는 스프라이트시트에서 각 프레임의 크기를 지정합니다.

create 함수에서는 this.physics.add.sprite로 물리 엔진이 적용된 스프라이트를 생성합니다. 좌표 400, 300에 enemy라는 이름의 스프라이트를 배치하는 것입니다.

setCollideWorldBounds(true)는 적이 화면 밖으로 나가지 않도록 설정합니다. 애니메이션 생성도 중요합니다.

this.anims.create로 enemy_walk라는 애니메이션을 만들고, generateFrameNumbers로 0번부터 3번 프레임을 사용하도록 지정합니다. frameRate는 초당 프레임 수이고, repeat: -1은 무한 반복을 의미합니다.

실제 현업에서는 적 캐릭터마다 다른 스프라이트시트를 사용하고, 상태에 따라 다른 애니메이션을 재생합니다. 걷기, 공격, 피격, 사망 등 다양한 애니메이션을 미리 정의해두면 게임이 훨씬 생동감 있어집니다.

주의할 점도 있습니다. preload에서 로드하지 않은 리소스를 create에서 사용하면 에러가 발생합니다.

또한 스프라이트시트의 프레임 크기를 잘못 지정하면 이미지가 깨져 보일 수 있으니 정확한 픽셀 값을 입력해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 preload와 create가 분리되어 있군요!"

실전 팁

💡 - 스프라이트시트는 개별 이미지보다 로딩 속도가 빠르고 메모리 효율이 좋습니다

  • 애니메이션 키 이름은 일관된 규칙으로 정하세요 (예: enemy_walk, enemy_attack)

2. 순찰 패턴 구현

적 캐릭터가 화면에 나타났지만 가만히 서 있기만 합니다. 김개발 씨가 "적이 움직여야 게임 같은데..."라고 중얼거리자, 박시니어 씨가 웃으며 말했습니다.

"순찰 패턴을 만들어 봅시다. 적이 일정 구간을 왔다 갔다 하게요."

순찰 패턴은 적 캐릭터가 정해진 경로를 반복해서 이동하는 행동입니다. 마치 경비원이 건물 주변을 정해진 루트로 도는 것과 같습니다.

가장 기본적인 패턴은 좌우로 왔다 갔다 하는 것이며, 이를 통해 게임에 긴장감을 더할 수 있습니다.

다음 코드를 살펴봅시다.

class Enemy extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y) {
    super(scene, x, y, 'enemy');
    scene.add.existing(this);
    scene.physics.add.existing(this);

    // 순찰 설정
    this.patrolSpeed = 100;
    this.patrolDistance = 150;
    this.startX = x;
    this.direction = 1; // 1: 오른쪽, -1: 왼쪽
  }

  update() {
    // 순찰 범위를 벗어나면 방향 전환
    if (this.x > this.startX + this.patrolDistance) {
      this.direction = -1;
      this.flipX = true;
    } else if (this.x < this.startX - this.patrolDistance) {
      this.direction = 1;
      this.flipX = false;
    }
    this.setVelocityX(this.patrolSpeed * this.direction);
  }
}

적 캐릭터가 화면에 나타났습니다. 그런데 가만히 서 있기만 하니 뭔가 어색합니다.

김개발 씨는 적이 좌우로 움직이게 만들고 싶었습니다. 박시니어 씨가 설명을 시작했습니다.

"순찰 패턴은 게임 AI의 가장 기본이에요. 적이 일정 구간을 반복해서 움직이면 플레이어는 타이밍을 계산해서 지나가야 하죠." 순찰 패턴을 비유하자면, 마치 놀이공원의 퍼레이드 차량과 같습니다.

정해진 경로를 따라 일정한 속도로 움직이고, 끝에 도달하면 방향을 바꿔 되돌아옵니다. 적 캐릭터도 마찬가지로 시작 지점을 기준으로 좌우로 왔다 갔다 합니다.

위 코드에서는 Enemy 클래스를 만들어 적 캐릭터를 객체지향적으로 관리합니다. Phaser.Physics.Arcade.Sprite를 상속받아 물리 엔진의 기능을 그대로 사용할 수 있습니다.

constructor에서 중요한 변수들을 초기화합니다. patrolSpeed는 이동 속도, patrolDistance는 시작 지점에서 얼마나 멀리까지 갈 수 있는지를 정합니다.

startX는 처음 위치를 저장해두어 순찰 범위의 기준점으로 삼습니다. update 함수는 매 프레임마다 호출됩니다.

여기서 현재 위치가 순찰 범위를 벗어났는지 확인합니다. 오른쪽 끝에 도달하면 direction을 -1로 바꿔 왼쪽으로 이동하게 하고, 왼쪽 끝에 도달하면 1로 바꿔 오른쪽으로 이동하게 합니다.

flipX 속성은 스프라이트를 좌우 반전시킵니다. 적이 왼쪽으로 갈 때는 flipX를 true로 설정해 왼쪽을 바라보게 만듭니다.

이런 작은 디테일이 게임의 완성도를 높입니다. setVelocityX로 실제 이동 속도를 적용합니다.

patrolSpeed에 direction을 곱하면 방향에 따라 양수 또는 음수 속도가 됩니다. 실무에서는 더 복잡한 순찰 패턴도 사용합니다.

여러 지점을 순서대로 방문하거나, 가끔 멈춰서 주변을 살피는 등의 행동을 추가하면 적이 더 똑똑해 보입니다. 주의할 점은 update 함수가 씬의 update에서 호출되어야 한다는 것입니다.

Phaser는 스프라이트의 update를 자동으로 호출하지 않으므로, 씬에서 직접 enemy.update()를 호출해야 합니다.

실전 팁

💡 - 여러 순찰 지점이 필요하면 배열에 좌표를 저장하고 순서대로 방문하게 구현하세요

  • 순찰 중 잠시 멈추는 대기 시간을 추가하면 더 자연스러워집니다

3. 플레이어 추적 AI

순찰하던 적이 플레이어를 발견하면 어떻게 될까요? 당연히 쫓아와야 합니다.

김개발 씨가 "적이 나를 쫓아오게 하고 싶어요"라고 하자, 박시니어 씨가 말했습니다. "감지 범위를 만들고, 범위 안에 플레이어가 들어오면 추적 모드로 전환하면 돼요."

추적 AI는 적이 플레이어를 발견하고 쫓아가는 행동을 구현한 것입니다. 마치 경비견이 침입자를 발견하면 짖으며 쫓아가는 것과 같습니다.

일정 거리 안에 플레이어가 들어오면 순찰을 멈추고 추적 모드로 전환됩니다.

다음 코드를 살펴봅시다.

class Enemy extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y) {
    super(scene, x, y, 'enemy');
    this.scene = scene;
    this.detectRange = 200;  // 감지 범위
    this.chaseSpeed = 150;   // 추적 속도
    this.isChasing = false;
  }

  update(player) {
    const distance = Phaser.Math.Distance.Between(
      this.x, this.y, player.x, player.y
    );

    if (distance < this.detectRange) {
      // 추적 모드
      this.isChasing = true;
      const direction = player.x > this.x ? 1 : -1;
      this.setVelocityX(this.chaseSpeed * direction);
      this.flipX = direction === -1;
    } else if (this.isChasing && distance > this.detectRange * 1.5) {
      // 추적 해제 (순찰로 복귀)
      this.isChasing = false;
    }
  }
}

게임에서 적이 그저 왔다 갔다만 하면 긴장감이 없습니다. 플레이어를 발견하면 쫓아오는 적이 있어야 진정한 도전 요소가 됩니다.

박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "적 주변에 보이지 않는 원을 그려봐요.

이게 감지 범위예요. 플레이어가 이 원 안에 들어오면 적이 플레이어를 발견한 거죠." 추적 AI를 비유하자면, 마치 슈퍼마켓의 자동문 센서와 같습니다.

센서 범위 안에 사람이 들어오면 문이 열리듯이, 적의 감지 범위 안에 플레이어가 들어오면 추적이 시작됩니다. 코드에서 가장 핵심은 Phaser.Math.Distance.Between 함수입니다.

이 함수는 두 점 사이의 거리를 계산해줍니다. 적의 위치와 플레이어의 위치를 넣으면 둘 사이의 거리가 반환됩니다.

distance가 detectRange보다 작으면 플레이어가 감지 범위 안에 있다는 뜻입니다. 이때 isChasing을 true로 설정하고 추적 모드로 전환합니다.

플레이어가 오른쪽에 있으면 오른쪽으로, 왼쪽에 있으면 왼쪽으로 이동합니다. 여기서 재미있는 점은 추적 해제 조건입니다.

단순히 detectRange를 벗어나면 바로 해제하는 게 아니라, detectRange의 1.5배를 벗어나야 해제됩니다. 이렇게 하면 경계선 근처에서 추적과 순찰이 반복되는 어색한 상황을 방지할 수 있습니다.

이를 히스테리시스라고 부릅니다. chaseSpeed를 patrolSpeed보다 높게 설정하면 적이 플레이어를 발견했을 때 더 빠르게 쫓아옵니다.

이런 속도 차이가 게임의 긴장감을 높여줍니다. 실무에서는 더 정교한 추적 알고리즘을 사용하기도 합니다.

장애물을 피해가는 길찾기 알고리즘이나, 플레이어의 마지막 위치를 기억하는 기능 등을 추가할 수 있습니다. 주의할 점은 update 함수에 player 객체를 전달해야 한다는 것입니다.

씬에서 enemy.update(this.player)처럼 호출해야 플레이어 위치를 알 수 있습니다.

실전 팁

💡 - 감지 범위를 시각적으로 보여주면 디버깅이 쉬워집니다 (개발 중에만 사용)

  • 적마다 다른 감지 범위와 추적 속도를 설정하면 다양성이 생깁니다

4. 적과 충돌 처리

적이 플레이어를 쫓아옵니다. 그런데 부딪히면 어떻게 될까요?

김개발 씨가 테스트해보니 적과 플레이어가 그냥 겹쳐버립니다. "충돌 처리를 해야겠네요." 박시니어 씨가 고개를 끄덕였습니다.

충돌 처리는 두 게임 오브젝트가 부딪혔을 때 어떤 일이 일어날지 정의하는 것입니다. 마치 교통사고 발생 시 보험 처리 절차가 정해져 있듯이, 게임에서도 충돌 시 데미지 처리, 넉백 효과 등을 구현해야 합니다.

다음 코드를 살펴봅시다.

class GameScene extends Phaser.Scene {
  create() {
    this.player = new Player(this, 100, 300);
    this.player.health = 100;

    this.enemy = new Enemy(this, 400, 300);

    // 충돌 감지 설정
    this.physics.add.overlap(
      this.player,
      this.enemy,
      this.handlePlayerEnemyCollision,
      null,
      this
    );
  }

  handlePlayerEnemyCollision(player, enemy) {
    // 무적 시간 체크
    if (player.isInvincible) return;

    // 데미지 처리
    player.health -= 20;
    player.isInvincible = true;

    // 넉백 효과
    const knockbackDir = player.x > enemy.x ? 1 : -1;
    player.setVelocityX(300 * knockbackDir);

    // 무적 시간 해제 (1초 후)
    this.time.delayedCall(1000, () => {
      player.isInvincible = false;
    });
  }
}

플레이어와 적이 부딪혔을 때 아무 일도 일어나지 않으면 게임이 성립하지 않습니다. 충돌 처리는 게임의 핵심 메카닉 중 하나입니다.

박시니어 씨가 말했습니다. "Phaser에서 충돌 처리는 크게 두 가지가 있어요.

collide는 물리적으로 부딪혀서 밀려나고, overlap은 겹침만 감지해요. 데미지 처리에는 overlap이 더 적합해요." 충돌 처리를 비유하자면, 마치 알람 시스템과 같습니다.

센서가 침입을 감지하면 미리 정해진 대응 절차가 실행되듯이, 두 오브젝트가 겹치면 콜백 함수가 호출됩니다. 코드에서 this.physics.add.overlap을 살펴봅시다.

첫 번째와 두 번째 인자는 충돌을 감지할 두 오브젝트입니다. 세 번째 인자는 충돌 시 호출될 콜백 함수입니다.

네 번째는 프로세스 콜백(필터링 용도)인데 여기서는 null입니다. 다섯 번째는 콜백 함수 내에서 this가 가리킬 대상입니다.

handlePlayerEnemyCollision 함수가 실제 충돌 처리를 담당합니다. 가장 먼저 무적 시간을 체크합니다.

무적 상태면 바로 리턴하여 데미지를 받지 않습니다. 무적 시간이 없으면 적과 부딪힐 때마다 연속으로 데미지를 받아 순식간에 죽게 됩니다.

player.health에서 20을 빼서 데미지를 적용하고, isInvincible을 true로 설정합니다. 그 다음 넉백 효과를 적용합니다.

플레이어가 적보다 오른쪽에 있으면 오른쪽으로, 왼쪽에 있으면 왼쪽으로 밀려나게 합니다. this.time.delayedCall은 일정 시간 후에 함수를 실행하는 타이머입니다.

1000밀리초(1초) 후에 무적 상태를 해제합니다. 이 시간 동안 플레이어는 깜빡이는 효과를 넣어 무적 상태임을 시각적으로 표현하기도 합니다.

실무에서는 데미지 타입, 방어력, 크리티컬 히트 등 더 복잡한 공식을 적용합니다. 또한 피격 시 사운드와 화면 흔들림 효과를 추가하면 타격감이 좋아집니다.

실전 팁

💡 - 무적 시간 동안 플레이어가 깜빡이는 효과를 추가하면 시각적 피드백이 좋아집니다

  • 적의 공격력을 적 클래스의 속성으로 만들면 다양한 적을 쉽게 구현할 수 있습니다

5. 적 처치 방법

플레이어가 적에게 데미지를 받기만 하면 안 됩니다. 반격할 수 있어야 합니다.

김개발 씨가 "슈퍼마리오처럼 머리 위에서 밟으면 적이 죽게 하고 싶어요"라고 하자, 박시니어 씨가 "좋은 선택이에요. 충돌 시 위치 관계를 확인하면 돼요"라고 답했습니다.

적 처치는 플레이어가 적을 물리치는 방법을 구현한 것입니다. 마치 가위바위보에서 이기는 조건이 정해져 있듯이, 어떤 상황에서 적이 죽는지 규칙을 정해야 합니다.

대표적인 방법으로 머리 밟기, 총알 발사, 근접 공격 등이 있습니다.

다음 코드를 살펴봅시다.

handlePlayerEnemyCollision(player, enemy) {
  // 플레이어가 위에서 떨어지며 적을 밟았는지 확인
  const playerBottom = player.body.bottom;
  const enemyTop = enemy.body.top;
  const isStomping = player.body.velocity.y > 0 &&
                     playerBottom <= enemyTop + 10;

  if (isStomping) {
    // 적 처치
    enemy.health -= 50;

    if (enemy.health <= 0) {
      this.defeatEnemy(enemy);
    }

    // 플레이어 점프 (밟은 반동)
    player.setVelocityY(-300);
  } else {
    // 플레이어가 데미지를 받음
    this.damagePlayer(player, enemy);
  }
}

defeatEnemy(enemy) {
  // 처치 효과
  this.tweens.add({
    targets: enemy,
    alpha: 0,
    scaleY: 0.1,
    duration: 300,
    onComplete: () => enemy.destroy()
  });
}

적을 처치하는 방법은 게임의 재미를 결정하는 중요한 요소입니다. 너무 쉬우면 긴장감이 없고, 너무 어려우면 답답합니다.

박시니어 씨가 말했습니다. "슈퍼마리오의 밟기 공격은 단순하지만 정말 잘 만든 시스템이에요.

위에서 밟으면 내가 이기고, 옆에서 부딪히면 내가 지고. 명확하죠?" 머리 밟기 공격을 구현하려면 충돌 순간의 위치 관계를 분석해야 합니다.

핵심은 두 가지 조건입니다. 첫째, 플레이어가 아래로 떨어지고 있어야 합니다(velocity.y가 양수).

둘째, 플레이어의 바닥이 적의 머리 근처에 있어야 합니다. 코드에서 player.body.bottom은 플레이어 충돌 박스의 바닥 y좌표이고, enemy.body.top은 적 충돌 박스의 상단 y좌표입니다.

playerBottom이 enemyTop + 10 이하라면 플레이어가 적의 머리 위에 있다고 판단합니다. 여기서 10픽셀의 여유를 둔 것은 판정을 조금 느슨하게 해서 플레이어가 성공했다고 느끼게 하기 위함입니다.

조건을 만족하면 적의 health를 감소시킵니다. 체력이 0 이하가 되면 defeatEnemy 함수를 호출하여 적을 처치합니다.

동시에 플레이어는 setVelocityY(-300)으로 위로 튀어오릅니다. 이 반동 점프 덕분에 연속 밟기가 가능해집니다.

defeatEnemy 함수에서는 Tween을 사용해 처치 애니메이션을 만듭니다. 적의 투명도(alpha)를 0으로, 세로 크기(scaleY)를 0.1로 줄이면서 납작해지며 사라지는 효과를 줍니다.

300밀리초 동안 애니메이션이 진행되고, 완료되면 destroy()로 적 오브젝트를 완전히 제거합니다. 밟기가 아닌 경우에는 damagePlayer 함수를 호출하여 플레이어가 데미지를 받게 합니다.

이렇게 같은 충돌이라도 상황에 따라 다른 결과가 나오도록 구현합니다.

실전 팁

💡 - 처치 시 점수나 아이템을 드롭하면 성취감이 높아집니다

  • 밟기 판정을 너무 엄격하게 하면 답답하니 약간의 여유를 두세요

6. 다양한 적 유형

기본 적 캐릭터는 완성되었습니다. 하지만 같은 적만 계속 나오면 금방 지루해집니다.

김개발 씨가 "날아다니는 적이나 총 쏘는 적도 만들고 싶어요"라고 하자, 박시니어 씨가 "클래스 상속을 활용하면 쉽게 만들 수 있어요"라고 알려줬습니다.

다양한 적 유형은 게임의 다양성과 난이도 곡선을 만들어줍니다. 마치 레스토랑의 메뉴가 다양해야 손님이 질리지 않듯이, 게임도 여러 종류의 적이 있어야 플레이어가 지루해하지 않습니다.

클래스 상속을 활용하면 기본 적을 확장하여 새로운 유형을 쉽게 만들 수 있습니다.

다음 코드를 살펴봅시다.

// 기본 적 클래스
class Enemy extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y, texture) {
    super(scene, x, y, texture);
    this.health = 50;
    this.damage = 20;
    this.speed = 100;
  }
}

// 날아다니는 적
class FlyingEnemy extends Enemy {
  constructor(scene, x, y) {
    super(scene, x, y, 'flying_enemy');
    this.body.setAllowGravity(false);
    this.floatAmplitude = 50;
    this.floatSpeed = 2;
    this.startY = y;
  }

  update(time) {
    // 위아래로 둥둥 떠다니는 움직임
    this.y = this.startY + Math.sin(time * 0.001 * this.floatSpeed) * this.floatAmplitude;
    super.update();
  }
}

// 원거리 공격 적
class ShooterEnemy extends Enemy {
  constructor(scene, x, y) {
    super(scene, x, y, 'shooter_enemy');
    this.shootCooldown = 2000;
    this.lastShot = 0;
  }

  update(time, player) {
    if (time > this.lastShot + this.shootCooldown) {
      this.shoot(player);
      this.lastShot = time;
    }
  }

  shoot(player) {
    const bullet = this.scene.enemyBullets.get(this.x, this.y);
    if (bullet) {
      const angle = Phaser.Math.Angle.Between(this.x, this.y, player.x, player.y);
      bullet.fire(angle);
    }
  }
}

게임의 재미는 다양성에서 옵니다. 처음부터 끝까지 같은 적만 나온다면 플레이어는 금방 패턴을 익히고 지루해할 것입니다.

박시니어 씨가 설명했습니다. "객체지향 프로그래밍의 상속을 활용하면 좋아요.

기본 적의 공통 기능을 부모 클래스에 넣고, 각 유형의 특수 기능만 자식 클래스에서 구현하는 거죠." 이것을 비유하자면 마치 자동차와 같습니다. 모든 자동차는 바퀴와 엔진이 있고 운전할 수 있습니다.

하지만 스포츠카는 빠르고, 트럭은 짐을 많이 실을 수 있고, 버스는 사람을 많이 태울 수 있습니다. 기본 기능은 같지만 특화된 기능이 다른 것입니다.

코드에서 기본 Enemy 클래스는 health, damage, speed 같은 공통 속성을 가집니다. 모든 적은 체력이 있고, 데미지를 주고, 움직입니다.

FlyingEnemy는 Enemy를 상속받아 날아다니는 적을 구현합니다. 핵심은 this.body.setAllowGravity(false)입니다.

이렇게 하면 중력의 영향을 받지 않아 공중에 떠 있을 수 있습니다. update에서 Math.sin을 사용해 위아래로 둥둥 떠다니는 움직임을 만듭니다.

sin 함수의 주기적인 특성을 활용한 것입니다. ShooterEnemy는 일정 주기로 총알을 발사하는 원거리 공격 적입니다.

shootCooldown은 발사 간격이고, lastShot은 마지막 발사 시간입니다. 현재 시간이 lastShot + shootCooldown보다 크면 쿨다운이 끝난 것이므로 발사할 수 있습니다.

shoot 함수에서는 오브젝트 풀에서 총알을 가져옵니다. enemyBullets.get은 비활성화된 총알을 재사용하는 방식으로, 매번 새 객체를 생성하는 것보다 성능이 좋습니다.

Phaser.Math.Angle.Between으로 플레이어를 향한 각도를 계산하고, 그 방향으로 총알을 발사합니다. 실무에서는 적 유형을 더 세분화합니다.

패턴을 예측하기 어려운 적, 특정 조건에서만 약점이 드러나는 적, 다른 적을 소환하는 적 등 다양한 유형을 만들어 게임에 깊이를 더합니다. 새로운 적을 추가할 때마다 기본 Enemy 클래스의 코드를 수정할 필요가 없다는 것이 상속의 큰 장점입니다.

새로운 자식 클래스만 만들면 됩니다.

실전 팁

💡 - 각 적 유형의 시각적 차이를 명확히 하여 플레이어가 즉시 구분할 수 있게 하세요

  • 스테이지가 진행될수록 새로운 적 유형을 점진적으로 소개하면 학습 곡선이 완만해집니다

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

#Phaser#GameDev#Enemy#AI#Collision#Sprite#Game,JavaScript,Phaser,Project

댓글 (0)

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