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

정지현·2025년 3월 4일
0

1. 최근 개발이 재미없다.

최근 개발자로써 슬럼프가 왔다. 무엇에도 집중하기 힘들고 개발 자체를 손놓고 있곤 하는데, 무언가 신선한 것으로 다시 흥미를 일으켜 보고 싶다.

시도해볼 한 가지는 game jams 에서 확인할 수 있는 게임 개발 챌린지이다. 일주일-3일 정도로 시간을 나눠 놓고 거기에 주제에 맞는 게임을 개발하여 제출하는 것인데, 자기자신에게 엄격한 데드라인을 설정하고 무언가 한 가지에 집중하는 것이 꽤 효과있을거라고 생각하고 당장 해보기로 했다.

주제는 게임 잼이니만큼 어떠한 게임의 프로토타입을 3일만에 개발해보는 것으로 정했다.

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

먼저 무엇을 만들어 볼것이냐인데, 요즘 스타듀밸리라는 게임의 원조인 어릴 적 재밋게 한 기억이 있는 하베스트 문이라는 농장 경영 게임을 모작하는 것으로 시작하는 것이 좋겠다고 생각했다.

하베스트문 1

하베스트문2

간단하게 스타듀 밸리라는 게임에 영향을 준 농사짓는 게임이라고 생각하면 되겠다.

게임 개발에 완전히 초보자이니만큼 먼저 자바스크립트 게임의 기본 요소부터 알고 가야할 필요가 있었다.

프롬프트 1: 자바스크립트 게임의 기본 구성 요소에 대해 알려줘

GPT의 답변은 이렇다:

  1. 게임 루프. 모든 게임에서 공통적으로 사용되는 방식. RAF 훅을 사용해 프레임 만큼 업데이트하는 방식으로 진행됨.
  2. HTML5 캔버스. 게임의 모든 것들을 그리는 엘리먼트.
  3. 자바스크립트 지식. 루프와 애니메이션 및 비즈니스 로직을 구현하는데 필요.
class Game {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private player: Player;
  private tileMap: TileMap;

  constructor() {
    this.canvas = document.createElement("canvas");
    this.canvas.width = 800;
    this.canvas.height = 600;
    document.body.appendChild(this.canvas);

    this.ctx = this.canvas.getContext("2d")!;
    this.player = new Player(5, 5);
    this.tileMap = new TileMap();

    this.init();
  }

    private init() {
    window.addEventListener("keydown", (e) => this.player.handleInput(e));
    requestAnimationFrame(() => this.gameLoop());
  }

...

  private gameLoop() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.tileMap.render(this.ctx);
    this.player.render(this.ctx);
    requestAnimationFrame(() => this.gameLoop());
  }
}

class Player {
  private x: number;
  private y: number;
  private size: number = 32;
...
}

class TileMap {
  private tiles: number[][] = [
    [1, 1, 1, 1, 1, 1, 1, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 1, 1, 1, 1, 1]
  ];
  private tileSize: number = 32;
...
  render(ctx: CanvasRenderingContext2D) {
    for (let row = 0; row < this.tiles.length; row++) {
      for (let col = 0; col < this.tiles[row].length; col++) {
        ctx.fillStyle = this.tiles[row][col] === 1 ? "#654321" : "#228B22";
        ctx.fillRect(col * this.tileSize, row * this.tileSize, this.tileSize, this.tileSize);
      }
    }
  }
}

window.onload = () => {
  new Game();
};

이런 형태가 되고 코드의 실행 순서는

  1. 게임 클래스가 실행되고 캔버스를 그리고 플레이어와 맵 오브젝트를 가져온다.
  2. RAF훅에 게임 루프를 걸어서 실행한다.
  3. 게임 루프에서는 맵과 플레이어를 드로우한다.

canvas

해당 코드를 통해 그려진 것은 다음과 같았다. nested 된 배열의 숫자 값에 따라 다른 색을 입혀 다른 타일이라는 것을 정의하고, 캐릭터는 파란색으로 표시해 주었다.

내 개인적인 생각으로는 AI를 가장 잘 활용하는 방법은 항상 내가 전혀 모르는 어떤 분야에 대해서 질문하는 것이었는데, 여기서도 아주 잘해주는 것 같아서 첫 단추를 잘 끼운 기분이 들었다. 이미 예전 유튜브에서나 봤던 1970년대 유튜브로나 접했던 아타리라는 게임기 속의 게임과 너무 흡사해 보인다.

3. 기본 코드베이스가 생겼으니 계획을 계속 진행할 수 있겠다.

프롬프트 2: 하베스트문 슈퍼 패미콤 버전의 필수 기능에 대해 알려줘. 앞으로의 개발 플랜을 잡는데 필요해.

  1. 시간, 날짜 관리
  • 밤/낮 사이클
  • 계절과 한달 간격의 달력.
  1. 작물과 농장 관리
  • 작물 라이프 사이클
  • 작물 기반 경제 시스템
  1. 동물 사육
  • 가축 종류 및 돌봄 기능
  • 가축 부산물 및 경제 시스템에 추가
  1. 자원 및 경제 관리
  • 돈, 씨앗, 도구와 같은 자원 관리 시스템
  1. NPC 및 이벤트 시스템
  • NPC 추가 및 NPC의 평일, 주말 스케쥴 시스템 추가
  • 축제와 같은 특별 이벤트 추가
  1. 목표 기능 추가
  • 간단한 목표 달성을 통한 장기 목표 달성이 가능하도록 하는 시스템 추가(매일매일 낚시를 통해 돈을 벌어 집 업그레이드를 구매한다던가..)
  • 난이도 설정

목표를 보니 초보자의 입장에서 좀 압도되는 듯 하다. 하지만 AI와 함께 충분히 해볼 수 있지 않을까?

3일 동안 할 수 있는 것부터 먼저 진행해보는 것이 좋겠다.

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

정도라면 충분히 첫 프로토타입으로 괜찮을 거라고 생각한다.

harvestmoonfarm

원작의 농장 지도는 이렇게 생겼다. 무엇보다 플레이어가 상호작용할 수 있는 나무, 잡초, 돌, 건물과 같은 오브젝트들이 보인다. 해당 타일을 확장해서 시도할 수 있지 않을까?

4. 지도 확장하기

프롬프트 3: 일단 맵을 좀 키워 보자, 100, 100 사이즈로 하는 건 어때? 그리고 타일도 좀 다양화시켜 보자. 흙, 풀, 나무, 물, 돌이 있다고 생각해봐. 아직 맵이 확정된건 아니니 그냥 랜덤생성하는 게 좋을 것 같아.

해당 프롬프트로 업데이트 된 tileMap.ts 클래스는 다음과 같았다.

// tileMap.ts
  private generateTile() {
    const rand = Math.random();
    if (rand < 0.2) return MapObject.STONE;
    if (rand < 0.5) return MapObject.GRASS;
    if (rand < 0.7) return MapObject.DIRT;
    if (rand < 0.9) return MapObject.TREE;
    return MapObject.WATER;
  }

  private generateRandomMap(maxX, maxY) {
    return Array.from({ length: maxY }, () =>
      Array.from({ length: maxX }, () => this.generateTile())
    );
  }

tilemap-expanded

해당 지도에 실제 그래피컬 에셋을 적용하면 괜찮을 것 같다.

프롬프트 4: 오픈 게임 에셋이 좀 필요할 것 같아. 어떤 사이트가 좋을까?

ai가 opengameart라는 사이트를 추천해주었고 여기서 몇 가지 에셋들을 받을 수 있었다.

해당 사이트에서 몇 가지 무료 타일 이미지와 캐릭터 이미지를 찾을 수 있었다.

tilemap-img

캐릭터의 스프라이트와 기본적인 바닥 타일 이미지를 추가할 수 있었다.

slavic-woman-idle

slavic-woman-walk

여기서 타일 이미지와 다른 것은 여러 프레임이 담겨진 스프라이트라는 것인데, 이것을 어떻게 애니메이션으로 구현할 수 있을까?

5. 캐릭터 추가하기

프롬프트 5: 좋아. 이제 캐릭터 움직임 및 애니메이션으로 넘어가자. 가만히 있는 애니메이션을 추가하려면 어떻게 하는게 좋을까? 게임 루프가 계속 콜되니까 거기서 스프라이트를 바꿔서(자르는 위치를 바꿔서) 계속해서 표시하면 되는걸까?

최대 프레임 개수를 지정하고 루프에서 update를 실행해 프레임 카운트를 업데이트해서 이미지를 자르는 x값을 계속해서 바꾸어주는 것이었다.


export const Player {
...

  public update(deltaTime: number, isMoving: boolean) {
    const frameCount = isMoving ? this.walkingFrameCount : this.frameCount;
    // 0.1초마다 프레임 변경
    this.lastFrameTime += deltaTime;
    if (this.lastFrameTime >= this.frameDuration) {
      this.frameIndex = (this.frameIndex + 1) % frameCount;
      this.lastFrameTime = 0;
    }

    this.isMoving = false;
  }

  public draw(
    ctx: CanvasRenderingContext2D,
    assetLoader: AssetLoader,
    camera: Camera,
    image
  ) {
    ctx.drawImage(
      image,
      22 * this.frameIndex,
      0,
      24,
      32,
      this.playable.position.x - camera.x,
      this.playable.position.y - camera.y,
      PLAYER_SIZE,
      PLAYER_SIZE
    );
  }
}

idle-animation

프롬프트 6: 좋아. 그렇다면 움직이는 애니메이션은 어때? 가만히 있는 상태와 구분하는 것이 좋지 않아? 어떻게 player의 상태를 업데이트 할까?

export class Player {
  position: Position;
  speed: number;
  direction: Direction;
  public isMoving: boolean = false;

  private frameIndex: number = 0;
  private frameCount: number = 9; // 총 9 프레임
  private walkingFrameCount: number = 5;
  private frameWidth: number = 24;
  private frameHeight: number = 32;
  private frameDuration: number = 100; // 0.1초마다 프레임 변경
  private lastFrameTime: number = 0;
  ...
}

여기서 약간의 딜레이를 예상했지만, 내가 제시한 방향이 맞는 것 같다.

생성된 코드에서는

  1. 키보드 입력을 감지해 움직이는 키라면 isMoving을 true로 한다.
  2. update는 isMoving 상태에 따라서 위 9프레임인지, 혹은 움직이는 스프라이트인 5프레임을 사용할 것인지를 결정한다.
  3. 그리는 부분은 위와 동일하다.

walking

훌륭하게 작동한다. 이틑날에 추가적으로 개발할 수 있을 듯 하다.

6. 1일차 소감

  • 항상 생각하는 AI의 가장 큰 장점은 처음 해보는 것을 접할때 정말 좋은 보일러플레이트를 제공해주는 것이다. 실제로 1일차 개발은 아주 빠르게 진행된 것 같다.
  • 하지만 여전히 AI가 잘못 코드를 생성하는 일을 겪을 수 있겠다. 경험상, AI가 잘 나가다가 한번 틀리는 그 지점에서 개발자가 배경 지식이 없다면 크게 시간이 지체될 수 있던 것으로 기억한다.

references:

https://opengameart.org/

https://github.com/jihyeonjeong11/games/tree/main/farmingV1-clean

profile
Can an old dog learn new tricks?

0개의 댓글