Phaser Game - 7 맵 구성 기초

phaser

series

.

Phaser Game - 7 맵 구성 기초

맵을 간단히 구성해보자. 게임성을 위해 움직이는 발판을 생성하고 맵에 띄워보자.

캐릭터는 해당 발판을 타고 위로 올라갈 것이다.

또한 올라감에 따라 카메라가 캐릭터를 따라갈 수 있도록 해보자.

발판 배치하기

발판을 지상으로 부터 몇개 배치할 수 있도록 만들어보자.

먼저 맵은 위로 쭉 뻗은 형태를 가지게 될 것이고 위쪽 방향으로 발판이 배치될 것이다.

이를 통합 관리하기 위한 map.config를 만들어 보자.

해당 값들은 각각 gameConfigStage에서 발판을 생성할 때 활용할 것이다.

export const mapConfig = {
  width: MAP_W,
  height: MAP_H,
  scaffoldings: [
    {
      x: 200,
      y: MAP_H - 100,
      /** 이후 움직이는 발판 때 사용될 flag */
      movable: false,
    },
    {
      x: 400,
      y: MAP_H - 200,
      movable: true,
    },
    {
      x: 600,
      y: MAP_H - 300,
      movable: true,
    },
    {
      x: 400,
      y: MAP_H - 400,
      movable: false,
    },
    {
      x: 200,
      y: MAP_H - 500,
      movable: false,
    },
  ],
};

export const gameConfig: Phaser.Types.Core.GameConfig = {
  ...
  scale: {
    width: 800,
    height: 600,
  },
  ...
};

먼저 일괄적으로 발판을 관리할 pool을 만들어보자.

pool은 일괄적으로 발판을 관리하기 위해 발판을 모아두는 클래스로 관리 용이성을 위해 아래와 같이 작성한다.

export class ScaffoldingPool {
  private _scaffoldings: Scaffolding[] = [];
  private _scene: Phaser.Scene;

  constructor(scene: Phaser.Scene) {
    this._scene = scene;
  }

  add(scaffolding: Scaffolding) {
    this._scaffoldings.push(scaffolding);
    this._scene.add.existing(scaffolding);
    this._scene.physics.add.existing(scaffolding);
  }

  getAll(): Scaffolding[] {
    return this._scaffoldings;
  }

  clear() {
    this._scaffoldings.forEach((scaffolding) => scaffolding.destroy());
    this._scaffoldings = [];
  }

  static create(scene: Phaser.Scene): ScaffoldingPool {
    const pool = new ScaffoldingPool(scene);
    mapConfig.scaffoldings.forEach((scaffoldingConfig) => {
      const scaffolding = Scaffolding.create(
        scene,
        scaffoldingConfig.x,
        scaffoldingConfig.y,
      );
      pool.add(scaffolding);
    });

    return pool;
  }
}


export class Stage extends Scene {
  public scaffoldings!: ScaffoldingPool;

  create() {
    ...
    this.scaffoldings = ScaffoldingPool.create(this);
    ...
    this.physics.add.collider(this.player, this.scaffoldings.getAll());
  }

움직이는 발판 만들기

게임성을 위해 일부 발판이 특정 구간을 좌/우로 이동을 반복하게 만들어보자.

phaser엔 tween을 통해 오브젝트의 시간당 변화를 구현할 수 있다.

상세한 내용은 공식문서를 참고하고 여기선 간단한 좌/우 이동의 구현만 작업할 것이다.

특정 발판들만이 움직이도록 movable 값을 추가하고 이 값이 true인 경우에한 tween을 실행한다.

간단히 아래와 같이 작성할 수 있다.

export const mapConfig = {

}

export class Scaffolding extends Phaser.GameObjects.Container {
  private _movable = false;

  constructor(scene: Phaser.Scene, x: number, y: number, movable: boolean = false) {
    ...
    this._movable = movable;
    ...
  }

  static create(scene: Phaser.Scene, x: number, y: number, movable: boolean = false): Scaffolding {
    const scaffolding = new Scaffolding(scene, x, y, movable);
    ...
    if (scaffolding._movable) {
      scene.tweens.add({
        targets: scaffolding,
        x: {from: 50, to: MAP_W - scaffolding.width}, /** 좌/우로 움직일 범위*/
        duration: 5000,
        yoyo: true,
        repeat: -1,
      });
    }
  }

위와 같이 작성하면 50 ~ 맵끝에서 길이를 뺀 거리 만큼 5초동안 좌/우로 이동한다.

다만 위와 같이 작성하게 되면 최초 위치가 모두 50으로 강제 고정되어 발판이 모두 같은 x축 위치에서 좌우로 이동하게 된다.

따라서 아래와 같이 초기 이동과 이후 이동이 다를 수 있도록 onComplete를 활용하여 수정하자.

export class Scaffolding extends Phaser.GameObjects.Container {
  static create(scene: Phaser.Scene, x: number, y: number, movable: boolean = false): Scaffolding {
    ...
    if (scaffolding._movable) {
      scene.tweens.add({
        targets: scaffolding,
        x: 50,
        duration: ((scaffolding.x - 50) / (MAP_W - scaffolding.width)) * 5000,
        onComplete: () => {
          scene.tweens.add({
            targets: scaffolding,
            x: { from: 50, to: MAP_W - scaffolding.width },
            duration: 5000,
            yoyo: true,
            repeat: -1,
          });
        },
      });
    }
  }
}

발판과 캐릭터의 이동

위와같이 작성하고 움직이는 발판에 캐릭터가 올라가면 캐릭터와 발판이 같이 이동하지 않고 발판만 이동하여 캐릭터가 떨어지게 된다.

phaser는 오브젝트의 이동에따라 속도를 계산하는지 여부를 설정하는 setDirectControl을 제공한다. 해당 코드를 발판에 추가하자

export class Scaffolding extends Phaser.GameObjects.Container {
  constructor(scene: Phaser.Scene, x: number, y: number, movable: boolean = false) {
    ...
    const body = this.body as Phaser.Physics.Arcade.Body;
    body.setDirectControl();
    ...
  }
}