마리오 웹 게임(4) - 캐릭터 충돌 처리
포스트
취소

마리오 웹 게임(4) - 캐릭터 충돌 처리

본 포스트는 JS(TS)로 캐릭터 충돌 처리에 대한 게시글입니다.

🤔 구상

기존에는 특정 y 좌표를 캐릭터의 위치로 고정하고 그 위치에서만 캐릭터가 움직이도록 구현했습니다.
물론 점프에서도 상승했다가 다시 원래 위치까지 하강하도록 구현했습니다.

하지만 원래 생각했던 게임은 캐릭터가 블록 위를 오르락내리락 할 수 있는 기능을 원했기에 기존 방식으로는 구현할 수 없다고 판단돼서 동작 방식을 바꾸기로 했습니다.

  • 수정한 방식
    1. 캐릭터는 항상 y 좌표에 일정 값을 더해줌으로써 중력과 같은 현상을 적용한다.
    2. 만약 캐릭터가 블록과 충돌한다면 충돌한 방향에 따라서 원래 위치로 되돌린다.

즉, 캐릭터는 항상 아래로 이동되지만, 블록에 가로막히면 움직이지 않는 것처럼 보여주는 방식입니다.
즉, 블록 위에 캐릭터가 올라가 있는 것처럼 보입니다.
( 실제 좌표는 아래로 내려갔다가 올라가지만, 렌더링 되는 시점이 올라간 시점이라 움직이지 않는 것처럼 보임 )

😮 구현

1. 중력

항상 requestAnimationFrame()의 마지막에서는 두 가지 처리합니다.

  1. 현재 좌표를 기록한다. ( 다음 실행을 기준으로 이전 좌표를 기록을 의미 )
  2. 캐릭터의 y위치를 증가한다. ( 캐릭터를 아래로 떨어뜨린다. )
    단, 떨어지는 시간에 비례해서 점점 빠르게 떨어지게 하고, 최대 속도를 정한다.

1번을 실행하는 이유는 다음 이동에서 캐릭터가 충돌을 발생하는 경우에는 이전 좌표로 되돌리기 위해서와 충돌의 방향을 알아내기 위해서입니다.
2번을 실행하는 이유는 중력과 같은 효과를 내기 위해서입니다. fall 1. 중력 gif

2. 충돌

각 메서드 코드 깃헙 링크 ( move(), stand(), crawl(), jump(), fall() 등의 메서드 )

일단 충돌의 구현에 관해 설명하기에 앞서 현재 구현 방식에 대해 적어보겠습니다.

  1. keyupkeydown을 이용해서 입력된 key를 캐릭터가 갖는다.
  2. move(), stand(), crawl(), jump(), fall() 등의 메서드를 이용해서 현재 캐릭터의 행동(key)에 의한 위치(position)과 이미지를 값만 바꾼다. ( 렌더링 X )
  3. draw() 메서드를 이용해서 이전 메서드들로 정했던 캐릭터의 좌표(position)와 이미지로 렌더링한다.

전체적으로 1, 2를 통해서 데이터상의 캐릭터의 좌표와 이미지를 변환시킵니다.
하지만 실제로는 3을 통해서 렌더링 됩니다.
이 점을 이용해서 1,2 와 3 사이에서 충돌 판정에 대한 메서드를 작성 및 실행해서 데이터상의 좌표를 변경시키도록 구현했습니다.

  • 충돌 판정 메서드
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Mario class 내부의 메서드

/**
 * 블록과 충돌 여부 판단 후 위치 조정
 * @param blocks 블록 배열
 * 
 * "Block"은 클래스 즉, "blocks"는 화면에 렌더링된 모든 블록들을 갖는 배열
 */
private isCollision(blocks: Block[]) {
  // 캐릭터의 상하좌우 좌표
  const { x, y } = this.position;
  const { w, h } = this.size;
  const cLeft = x;
  const cRight = x + w;
  const cTop = y;
  const cBottom = y + h;

  blocks.forEach((block) => {
    // 블록의 상하좌우 좌표
    const { x, y } = block.getPosition();
    const { w, h } = block.getSize();
    const bLeft = x;
    const bRight = x + w;
    const bTop = y;
    const bBottom = y + h;

    // 충돌인 경우 이전 좌표로 돌아가기 ( 즉, 가로막혀서 움직일 수 없는 효과를 보여줌 )
    if (
      bLeft <= cRight &&
      bRight >= cLeft &&
      bTop <= cBottom &&
      bBottom >= cTop
    ) {
      // "좌 -> 우" 충돌인 경우
      const isLeft = this.prevPos.x + this.size.w - bLeft;
      if (
        isLeft < 0 &&
        bTop < this.position.y + this.size.h - this.fallSpeed - 1
      ) {
        this.position.x -= 7 * this.speed;
        return;
      }
      // "우 -> 좌" 충돌인 경우
      const isRight = bRight - this.prevPos.x;
      if (
        isRight < 0 &&
        bTop < this.position.y + this.size.h - this.fallSpeed - 1
      ) {
        this.position.x += 7 * this.speed;
        return;
      }
      // // >>> "아래 -> 위" 충돌인 경우
      // const isBottom = bBottom - this.prevPos.y;
      // if (isBottom < 0) {
      //   this.position.y += 7 * this.speed;
      //   return;
      // }
      // "위 -> 아래" 충돌인 경우
      this.position.y = bTop - this.size.h;
    }
  });
}
  • 충돌 판정 규칙
    1. 블록의 좌측 좌표 < 캐릭터의 우측 좌표 ( 캐릭터가 블록보다 우측에 있는지 )
    2. 블록의 우측 좌표 > 캐릭터의 좌측 좌표 ( 캐릭터가 블록보다 좌측에 있는지 )
    3. 블록의 상 좌표 < 캐릭터의 하 좌표 ( 캐릭터가 블록보다 아래에 있는지 )
    4. 블록의 하 좌표 > 캐릭터의 상 좌표 ( 캐릭터가 블록보다 위에 있는지 )

1 && 2라면 같은 열에 존재, 3 && 4라면 같은 행에 존재
따라서 1 && 2 && 3 && 4라면 같은 행과 열에 존재 즉, 겹친다는 의미 === 충돌 발생

그리고 중력을 위해서 항상 아래로 내려가도록 y를 변화시켜주기 때문에 블록 위에 올라가 있다면 계속 충돌이 발생합니다.
그렇게 되면 충돌이 어느 방향에서 일어났는지 알아내기 어려워서 이전 좌표를 이용해서 충돌이 발생한 경우에 어떤 방향에서 어디로 발생한 것인지 알아냅니다.
그리고 그 방향에 맞게 이동을 리셋시켜주면 캐릭터가 블록을 통과할 수 없고 가로막히는 기능이 발생합니다.

blocked 2. 충돌 처리 gif

이제 충돌 처리에 대한 기능을 구현해서 굼바같은 적을 구현할 때는 방향에 의해 죽이거나 죽는 기능을 구현하겠습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

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

마리오 웹 게임(5) - 블록과 맵 구현