마리오 웹 게임(2) - 배경과 캐릭터 구현
포스트
취소

마리오 웹 게임(2) - 배경과 캐릭터 구현

캐릭터의 이동을 구현하고 나서 캐릭터에 관한 변수를 어디에 선언하고 어떻게 관리하고 화면에 렌더링할지에 대한 고민을 시작했습니다.

초기 개발 과정

일단 캐릭터를 구현하는데 필요한 변수들을 전역적으로 선언해서 사용했습니다. 왜냐하면 캐릭터에 필요한 변수들을 여러 함수들 즉 이동, 화면 렌더링 등의 많은 함수에서 사용하기 때문입니다. 함수의 매개변수를 통해서 전달할 수 있겠지만 그러기에는 너무 많은 변수들을 선언하고 타입을 정해야 했기에 불편하다고 판단돼서 전역 변수를 이용했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(() => {
  // 전역 변수로 선언
  const pos = { x: 0, y: 0 };
  let speed = 5;
  let status = "small"

  // 각 함수에서 사용...
  const move = () => {
    // pos 사용...
    // speed 사용...
  };

  // 각 함수에서 사용...
  const startAnimation = () => {
    // status 사용...
    // speed 사용...

    requestAnimationFrame();
  };

  startAnimation();
})();

처음에는 위와 같은 형태로 캐릭터 관련 변수는 전역적으로 선언 / 사용했습니다. 하지만 기능을 추가로 구현하면서 캐릭터의 변수가 늘어나고 함수가 늘어나면서 코드의 가독성이 심하게 안좋고 작성하기도 불편하다고 느껴졌습니다. 그러다가 생각난 방법이 C++처럼 게임의 각 요소를 클래스로 만들어서 객체로 찍어내서 사용하면 변수/함수가 객체에 종속적이게 되면서 가독성도 좋아지고 사용하기도 더 편하지 않을까라는 생각을 했습니다.

클래스화 ( OOP 적용 )

1. 캐릭터 클래스 ( 마리오 )

실제로 사용한 코드의 일부분만 예시로 작성했습니다. ( GitHub 코드 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
type Position = { x: number; y: number };
type CharacterStatus = "small" | "long";

class Mario {
  private ctx: CanvasRenderingContext2D;
  private position: Position;
  private image: HTMLImageElement;
  private status: CharacterStatus;
  private speed: number;

  constructor(ctx: CanvasRenderingContext2D, position: Position) {
    const image = new Image();
    image.src = "./assets/images/mario.png";

    this.ctx = ctx;
    this.position = position;
    this.image = image;
    this.status = "small";
    this.speed = 5;
  }

  move() {
    // this.pos 사용...
    // this.speed 사용...
  }

  // getter / setter
  getSpeed() {
    return this.speed;
  }
  setSpeed(speed: number) {
    this.speed = speed;
  }
  // ... 나머지도 getter / setter 정의
}

위 처럼 클래스로 선언하고 나서는 변수와 함수들이 클래스에 종속적으로 들어가서 실제로 사용할 때도 접근 제한이 걸리기 때문에 각자의 값만 사용할 수 있게 되었습니다. 그리고 굼바(적)같은 경우에는 한 마리가 아니라 여러 마리가 생성될 것이기 때문에 클래스로 정의하고 객체로 찍어내기만 하면 각자의 독립적인 값을 갖고 움직일 수 있기 때문에 미래를 봐도 더 좋은 방법이라는 생각이 듭니다.

1
2
3
4
5
6
7
(() => {
  const mario = new Mario(ctx, { x: 100, y: 100 });
  
  // 필요한 부분에 아래와 같이 사용
  mario.move();
  mario.getSpeed();
})()

2. 배경 화면 클래스 ( feat. 싱글톤 )

캐릭터나 적의 경우에는 상황에 따라 여러 객체를 생성할 수도 있습니다. 하지만 배경의 경우에는 항상 하나의 객체만 생성하면 되므로 싱글톤을 적용했습니다.

static 변수를 통해서 모든 클래스가 공유하는 변수를 만들고 첫 생성이라면 그대로 생성하고 이미 만들어 졌다면 기존 객체를 넘겨주는 방식입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * 배경화면 클래스 ( + 싱글톤 )
 * @param ctx 배경화면을 그릴 "canvas"의 "context"
 * @param image 배경화면 이미지를 가진 이미지 객체
 */
export default class Background {
  static instance: Background;

  private ctx!: CanvasRenderingContext2D;
  private image!: HTMLImageElement;

  constructor(ctx: CanvasRenderingContext2D) {
    // 싱글톤으로 구현
    if (Background.instance) return Background.instance;

    const image = new Image();
    image.src = "./assets/images/background.jpg";

    this.ctx = ctx;
    this.image = image;

    // 이미지 로드 완료 시 그리기
    image.addEventListener("load", () => {
      ctx.drawImage(image, 0, 0, innerWidth, innerHeight);
    });

    // 싱글톤으로 구현
    Background.instance = this;
  }

  /**
   * 배경화면 그리기
   */
  draw() {
    this.ctx.clearRect(0, 0, innerWidth, innerHeight);
    this.ctx.drawImage(this.image, 0, 0, innerWidth, innerHeight);
  }
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

마리오 웹 게임(1) - 구상 및 좌/우 이동 구현

마리오 웹 게임(3) - 캐릭터 이동 및 점프 구현