3일 코딩 챌린지: 게임 프로토타입 만들기 - 2

정지현·2025년 3월 4일
0

1. AI와 함께하는 3일 게임개발 챌린지 - day 2

  1. 농장 게임 맵 세팅 - 맵 이동은 일단 빠져도 좋을 것 같음.
  2. 플레이어의 움직임 기능 및 애니메이션
  3. 농장과 플레이어의 상호작용 기능

1과 2의 뼈대는 1일차에 완성되었다고 생각한다.

  1. 상호작용 부분은 이제 실제 농사 기능을 구현하는데 필수적인 로직이 될 것이다.

2. 맵의 타일과 상호작용을 하려면... 어떻게 하지?

1일차에 타일의 스프라이트가 추가된 지도를 다시 한 번 보자.

tilemap-objects

지금 지도 타일의 형식은 흙, 나무, 돌, 풀, 물로 되어 있다.

가장 간단하게 생각해서 플레이어를 해당 타일에 위치시키고, 어떠한 키를 통해 해당 타일에 오브젝트가 있다면 수집하는 것으로 판정해서 그림을 제거하고 인벤토리를 업데이트하는 방식이면 괜찮지 않을까?

먼저 해당 타일에 오브젝트가 있는지를 알아야 할 것 같다.

프롬프트 1: 캐릭터가 해당 타일과 상호작용을 하려면 먼저 해당 캐릭터가 어떤 타일에 있는지부터 먼저 알아야 할 것 같아. 그걸 디버거로 표시하는게 좋을듯?

  private drawDebugInfo(): void {
    const { x, y } = this.player.getPlayable().position;
    const ctx = this.debugCanvas.getContext("2d")!;
    ctx.clearRect(0, 0, 400, 50);
    ctx.font = "14px Arial";
    ctx.fillStyle = "black";
    const tileCoord = _whatTile(x, y);

    ctx.fillText(
      `X: ${Math.floor(x)}, Y: ${Math.floor(y)}, whatTile ${
        MapObject[this.map.getMap()[tileCoord.col][tileCoord.row]]
      }, tree: ${this.player.tree}`,
      15,
      25
    );
  }

해결책은 생각보다 간단했다. 캔버스를 하나 더 두던가, 아니면 하나의 캔버스를 쪼개서 디버그 정보 패널을 다는 것.

캔버스 클래스는 이미 만들어 두었으니, 충분히 캔버스 인스턴스를 두 개로 쪼개서 표시하는 것이 앞으로 훨씬 좋을 것 같다고 생각했다.

map-debugger

디버깅 캔버스를 따로 둔 것은 앞으로도 아주 유용할 거라고 생각한다. FPS라든지, NPC의 정보라든지 추가적인 정보를 표기하는데 유용할 것 같다.

프롬프트 2: 좋아. 캐릭터가 어떤 타일에 있는지를 알게 되었으니 이제 타일에 맞는 상호작용을 실행시켜주면 될 것 같아. 그냥 엔터 키를 눌렀을때 상호작용을 하는 걸로 할까?

  private gatherResources() {
    const currentTile = _whatTile(
      this.player.getPlayable().position.x,
      this.player.getPlayable().position.y
    );

    const mapData = this.map.getMap();
    const tileValue = mapData[currentTile.col][currentTile.row];

    // Create a mapping for gatherable tiles:
    const gatherableTiles: {
      [key: number]: { resource: string; newTile: number };
    } = {
      [MapObject.STONE]: { resource: "stone", newTile: MapObject.DIRT },
      [MapObject.TREE]: { resource: "wood", newTile: MapObject.DIRT },
    };

    const action = gatherableTiles[tileValue];
    if (action) {
      // Update the tile and add the corresponding resource to the player.
      const newMap = this.map.getMap();
      newMap[currentTile.col][currentTile.row] = action.newTile;
      this.player.setResource(action.resource, 1);
      this.map.setMap(newMap);
    }
  }

해당 펑션이 추가되어 현재 타일이 어떤 타일인지 확인한 뒤 돌과 나무라면 플레이어의 이벤토리에서 1 늘리고, 기존 타일을 흙 타일로 바꾸도록 수정이 되었다.

interactions

여기까지 만들어놓고 보니... 생각보다 빠르게 목표를 달성했다는 느낌이 든다. 물론 중간에 한글로 쓰던 프롬프트를 영어로 바꾸긴 했다. 그렇다고 해도 여전히 빠른 속도이다.

남은 시간과 3일차는 앞으로의 추가 개발을 위해 하나의 거대한 main.ts 내부의 클래스들을 나누는 것이 될 것 같다.

3. 어떻게 나눌 것인지?

사실 클린 스트럭쳐를 구현해보면 좋았겠지만, 내 자신의 이해도 부족으로 AI의 적절한 답을 얻을 수 없었다. 대신 직접 클래스를 기능별로 나누되 조언을 구하는 방향으로 진행하기로 하였다.

farmingV1-clean/src
  ├── assetLoader.ts    // 스프라이트 및 리소스 로드
  ├── camera.ts         // 화면 이동 및 카메라 기능
  ├── canvas.ts         // 캔버스 생성 및 관리
  ├── constants.ts      // 게임 내에서 사용하는 상수값 관리
  ├── gameEvent.ts      // 게임 내 이벤트 및 상호작용 관리
  ├── index.d.ts        // 타입 선언 파일 (TypeScript 전용)
  ├── main.ts           // 게임의 진입점 (엔트리 포인트)
  ├── player.ts         // 플레이어 클래스 및 애니메이션 로직
  ├── tileMap.ts        // 타일맵 데이터 및 렌더링
  ├── utils.ts          // 범용적인 유틸리티 함수 모음

작업을 통해 다음과 같이 클래스를 나눌 수 있었다. 이 과정에서는

  1. SRP를 고민하자.
  • camera는 맵의 이동 기능, canvas는 main.ts에서 사용할 캔버스 인스턴스를, assetLoader는 main.ts에서 사용할 이미지를 로딩하고 로딩 스테이트를 전달하는 방식으로 최대한 하나의 책임을 하나의 파일에서 처리할 수 있도록 했다.
  1. 파일 하나 당 코드의 길이
  • 전에 어디선가 보았을 때 한 파일의 길이가 300줄정도가 읽기 편하다고 한다.
  1. 재사용성
  • 역시 앞으로 재사용될 수 있는 내부적인 로직은 utils.ts로 따로 빼주었다.

3일차에서는 해당 나눠진 코드 베이스를 기반으로 추가적인 개선이 있을 것 같다.

4. 2일차 소감

  • 1일차와 마찬가지로 설정한 목표에 대해 빠르게 진행되었음. 다만 중간에 AI가 잘못된 코드를 주는 경우가 있어 프롬프트를 영어로 바꾸어 진행하였음.

  • 하지만 1일차와 다르게 조금씩 세세한 부분에서 에러가 있어 직접 조정한 부분이 있었음. 계속해서 이런 현상에 대해 주의할 필요가 있음.

references:
https://medium.com/better-programming/how-to-implement-a-typescript-web-app-with-clean-architecture-27c7eb745ab4
https://iamschulz.com/writing-a-game-in-typescript/#conclusion

profile
Can an old dog learn new tricks?

0개의 댓글