마리오 웹 게임(9) - v 1.0.0 마무리
포스트
취소

마리오 웹 게임(9) - v 1.0.0 마무리

본 포스트는 JS(TS)로 만든 웹 게임에 대한 마무리 포스팅입니다. ( 웹 사이트 링크 )

📌 시작 목적

최근에 <canvas>에 관해 공부하기도 했고, 웹에 대한 흥미가 떨어져서 “웹으로 게임을 만들어보면 어떨까?” 라는 생각으로 웹 게임을 만들기 시작했습니다.
그리고 게임을 만든다면 마리오를 이용한 게임을 만들어보고 싶다는 생각을 이전에 한 적이 있어서 마리오를 주제로 선택했습니다.

📌 구현에 필요한 데이터

캔버스를 이용해서 화면을 렌더링할 것이기 때문에 각종 이미지 스트라이트들이 필요했습니다.
인터넷에서 찾아봐도 원하는 이미지 스프라이트가 없어서 게임에서 모션을 하나하나 캡쳐해서 포토샵을 이용해서 누끼를 따서 이미지 스프라이트를 제작했습니다.

현재는 마리오 모션들, 굼바 모션들, 3가지 블록들, 코인의 스프라이트가 존재합니다.

📌 구현 방식

1. 프레임워크를 사용하지 않은 이유

React를 사용하면 더 편하긴 하겠지만 앞으로 만들 프로젝트들은 거의 React를 사용할거로 생각해서 이번에는 기본에 충실하고 프레임워크를 안 썼을 때의 불편함을 느껴보고자 해서 TS만 사용했습니다.

2. 객체 지향 방식을 사용한 이유

초기에는 딱히 어떤 방법으로 코드를 작성하자고 생각하고 시작하진 않아서 쉽게 사용할 수 있는 함수형으로 코드를 작성했습니다.

코드를 작성하다 보니 몇 가지 문제가 발생했습니다.

  1. 전역적으로 사용해야 할 변수들이 발생
  2. 변수들을 그룹화해서 사용하면 편해지는 경우 발생 ( C의 구조체처럼 )

1번은 임시로 전역변수로 사용하다가 나중에 설명할 매니저 클래스를 이용해서 해결했습니다.

2번은 초기에는 하나의 객체에 묶어서 사용했으나 사용하면서 중복되는 코드, 불확실한 타입 같은 문제가 생겨서 몇 가지의 큰 객체로 정해서 클래스로 선언했습니다.
또한 클래스는 각자의 필드와 메서드를 가지고, 중복되는 필드나 메서드가 적당히 발생하면 상위 클래스를 만들어서 상속하도록 구현했습니다.
ex) 마리오와 굼바 모두 위치 필드와, 렌더링 메서드를 가짐 -> 마리오와 굼바의 공통된 값을 갖는 상위 클래스인 캐릭터를 가상 클래스로 생성 후 상속

mario-class-diagram 현재 구현한 클래스 다이어그램

위 다이어그램을 설명하자면 “캐릭터 -> 플레이어, 적”, “플레이어 -> 마리오”, “적 -> 굼바”와 같은 구조로 코드를 구성했습니다.
그리고 각자의 공통된 필드는 상위 클래스에서 선언해서 하위에서 사용하도록 만들었고, 메서드는 동작이 일치하면 상위에서 선언하고 자식마다 다르다면 가상 메서드로 제작해서 하위 클래스에서 직접 작성하도록 구현했습니다.

또한 각 매니저를 만들어서 게임의 흐름을 쉽게 제어하도록 구현했습니다.
예를 들면 마리오와 굼바의 충돌에 대한 처리를 해야하는 경우에 마리오, 굼바 모두 독립적인 객체이므로 각자가 서로의 좌표를 알고 있어야 충돌에 처리가 가능했습니다.
하지만 생각해보면 마리오가 모든 적들의 좌표를 알고 있는 것도 이상하고 인수로 받는다고 하더라도 마리오에서 굼바의 좌표를 받을지, 굼바에서 마리오의 좌표를 받을지 애매하기 때문에 충돌 관련 코드를 처리하기 위한 매니저를 따로 만들었습니다.
충돌 매니저에서 마리오, 굼바, 블록, 맵과의 충돌 처리를 수행하도록 했습니다.
다른 매니저들도 마찬가지로 게임의 흐름을 위해 사용했습니다.

3. 싱글톤

굼바(적)같은 객체는 여러 개가 만들어져야 합니다.
하지만 매니저 클래스 같은 경우에는 두 개 이상의 인스터스를 만들 필요가 없습니다.
어차피 클래스 개인이 갖는 변수를 사용하지 않기 때문에 호출할 때 인수로 값을 넘겨주는 방식을 주로 사용합니다. ( 매니저를 만든 이유 )
따라서 하나의 인스턴스롤 돌려서 사용하면 되기 때문에 싱글톤으로 선언해서 사용했습니다.

  • 싱글톤 생성 방법
    1. 정적 변수로 본인의 인스턴스를 갖는 필드를 생성합니다.
    2. ( 생성자에서 ) 인스턴스가 이미 만들어졌다면 기존 인스턴스를 반환합니다.
    3. ( 생성자에서 ) 인스턴스가 만들어지지 않았다면 정적 필드에 인스턴스를 넣습니다. ( 이 경우는 굳이 return하지 않아도 자동으로 반환됨, 물론 명시적으로 작성해도 됨 )
1
2
3
4
5
6
7
8
9
10
export default class MapManager {
  private static instance: MapManager;

  constructor(type: MapType) {
    // 싱글톤으로 구현
    if (MapManager.instance) return MapManager.instance;

    MapManager.instance = this;
  }
}

4. 추상 클래스

앞서 공통된 부분을 합쳐서 상위 클래스로 만들었다고 했습니다.
그 상위 클래스는 사실 존재하지는 않습니다.
즉, 객체로 만들면 안된다는 의미입니다.
따라서 추상(abstract) 클래스로 제작했습니다.

저의 코드에서는 CharacterPlayer는 추상 클래스고, Mario는 일반 클래스입니다.

이해를 위한 예시: 사람은 동물중에 하나지만 동물이라는 대상은 존재하지 않음
사람과 강아지 등의 생물의 공통점을 모아서 정의를 내린 단어지 명확한 대상이 존재하지 않음

🔎 요약

  1. 중복되는 부분과 그룹화해야하는 영역은 클래스로 생성 ( Player, Mario )
  2. 클래스끼리 중복되는 부분이 있다면 합쳐서 공통된 상위 클래스로 생성 ( Character )
  3. 각 클래스에서 처리하기 애매한 부분은 매니저 클래스를 따로 만들어서 처리 ( 게임, 충돌 등 )
    3-1. 단, 하나만 존재해도 된다면 싱글톤으로 제작, 인스턴스를 만들 필요가 없다면 추상 클래스로 제작

📌 게임 진행 흐름

게임의 진행은 크게 3가지로 나눠집니다.

  1. 게임 시작 UI
    게임 시작 UI에서는 맵의 종류와 타입을 선택할 수 있습니다.
    맵의 종류는 직선맵과 언덕맵이 있고, 맵의 타입에는 지상, 지하, 눈맵이 있습니다.
    start 게임 시작 Gif

  2. 게임 플레이
    게임 플레이에서는 게임 시작 UI에서 고른 맵의 종류와 타입이 적용된 맵이 렌더링됩니다.
    걷기, 달리기, 점프(상승, 하강), 엎드리기 등의 모션이 있습니다.
    기본적으로 적을 죽이고, 코인을 먹고, 스테이지를 이동하면서 점수가 올라갑니다.
    ( 점수는 스테이지에 비례해서 증가폭이 결정됨 )
    그리고 적에게 죽거나, 바닥에 떨어지면 게임이 종료되며 게임 종료 UI로 이동됩니다.
    play 게임 플레이 Gif

  3. 게임 종료 UI
    게임 종료 UI에서는 최종 점수를 볼 수 있고, 다시 게임하기인 시작 UI로 이동하는 버튼이 존재합니다.
    게임 종료 즉시 기존에 존재하던 마리오, 적, 블록등의 모든 정보는 사라지고 점수만 남게 됩니다.
    아마도 가비지 컬렉터에 의해서 자동적으로 메모리에서 제거될 것입니다.
    end 게임 종료 Gif

📌 구현한 기능들

  1. 게임 시작 UI에서 고른 맵의 종류와 타입에 맞는 화면 렌더링
  2. 마리오 모션 ( 걷기, 달리기, 점프( 상승, 하강 ), 엎드리기 )
  3. 적의 모션 ( 걷기, 죽기 )
  4. 충돌처리
    • 캐릭터와 맵 ( 캐릭터 이동 불가… 벽에 가로막혀서 해당 방향으로 움직임 불가능, 적의 경우 이동 방향을 변경 )
    • 캐릭터와 블록 ( 캐릭터 이동 불가… 벽에 가로막혀서 해당 방향으로 움직임 불가능, 적의 경우 이동 방향을 변경 )
    • 플레이어와 적 ( 충돌 방향에 따라서 플레이어나 적이 죽음… 적이 죽을 경우 점수 )
    • 적과 적 ( 이동 금지 및 이동 방향 변경 )
  5. 이동 가속 ( 1초 이상 같은 방향으로 이동하는 경우 가속, 가속 상태에서 점프 시 점프력++ )
  6. 중력 ( 위로 상승 시 점점 느리게, 아래로 하강 시 점점 빠르게 움직이도록 구현 )
  7. 적을 밟는 순간 점프키 누르고 있는 경우 더 높이 점프
  8. 엎드려 점프 가능, 엎드려 움직임 불가능
  9. 코인 먹기 ( 코인과 충돌 )
  10. 다음 스테이지로 이동 ( 문에서 ↑ )

📌 해결하지 못한 버그

  1. 굼바들이 부딪힐 때 통과하는 버그
  2. 떨어지는 중에 벽에 붙어서 이동키를 누르는 경우 벽에 붙음

📌 추가할만한 기능들

  1. 마리오 커지기 ( 버섯 )
  2. 게임 클리어 요소 ( 현재는 게임 클리어 조건이 없음 )
  3. 타이머
  4. 거북이 ( + 등껍질 )
  5. 맵 이동 ( 문 )
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

React 스크롤 방향 찾기

React-Router-Dom V6의 replace