최근 개발자로써 슬럼프가 왔다. 무엇에도 집중하기 힘들고 개발 자체를 손놓고 있곤 하는데, 무언가 신선한 것으로 다시 흥미를 일으켜 보고 싶다.
시도해볼 한 가지는 game jams 에서 확인할 수 있는 게임 개발 챌린지이다. 일주일-3일 정도로 시간을 나눠 놓고 거기에 주제에 맞는 게임을 개발하여 제출하는 것인데, 자기자신에게 엄격한 데드라인을 설정하고 무언가 한 가지에 집중하는 것이 꽤 효과있을거라고 생각하고 당장 해보기로 했다.
주제는 게임 잼이니만큼 어떠한 게임의 프로토타입을 3일만에 개발해보는 것으로 정했다.
먼저 무엇을 만들어 볼것이냐인데, 요즘 스타듀밸리라는 게임의 원조인 어릴 적 재밋게 한 기억이 있는 하베스트 문이라는 농장 경영 게임을 모작하는 것으로 시작하는 것이 좋겠다고 생각했다.
간단하게 스타듀 밸리라는 게임에 영향을 준 농사짓는 게임이라고 생각하면 되겠다.
게임 개발에 완전히 초보자이니만큼 먼저 자바스크립트 게임의 기본 요소부터 알고 가야할 필요가 있었다.
GPT의 답변은 이렇다:
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();
};
이런 형태가 되고 코드의 실행 순서는
해당 코드를 통해 그려진 것은 다음과 같았다. nested 된 배열의 숫자 값에 따라 다른 색을 입혀 다른 타일이라는 것을 정의하고, 캐릭터는 파란색으로 표시해 주었다.
내 개인적인 생각으로는 AI를 가장 잘 활용하는 방법은 항상 내가 전혀 모르는 어떤 분야에 대해서 질문하는 것이었는데, 여기서도 아주 잘해주는 것 같아서 첫 단추를 잘 끼운 기분이 들었다. 이미 예전 유튜브에서나 봤던 1970년대 유튜브로나 접했던 아타리라는 게임기 속의 게임과 너무 흡사해 보인다.
목표를 보니 초보자의 입장에서 좀 압도되는 듯 하다. 하지만 AI와 함께 충분히 해볼 수 있지 않을까?
3일 동안 할 수 있는 것부터 먼저 진행해보는 것이 좋겠다.
정도라면 충분히 첫 프로토타입으로 괜찮을 거라고 생각한다.
원작의 농장 지도는 이렇게 생겼다. 무엇보다 플레이어가 상호작용할 수 있는 나무, 잡초, 돌, 건물과 같은 오브젝트들이 보인다. 해당 타일을 확장해서 시도할 수 있지 않을까?
해당 프롬프트로 업데이트 된 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())
);
}
해당 지도에 실제 그래피컬 에셋을 적용하면 괜찮을 것 같다.
ai가 opengameart라는 사이트를 추천해주었고 여기서 몇 가지 에셋들을 받을 수 있었다.
해당 사이트에서 몇 가지 무료 타일 이미지와 캐릭터 이미지를 찾을 수 있었다.
캐릭터의 스프라이트와 기본적인 바닥 타일 이미지를 추가할 수 있었다.
여기서 타일 이미지와 다른 것은 여러 프레임이 담겨진 스프라이트라는 것인데, 이것을 어떻게 애니메이션으로 구현할 수 있을까?
최대 프레임 개수를 지정하고 루프에서 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
);
}
}
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;
...
}
여기서 약간의 딜레이를 예상했지만, 내가 제시한 방향이 맞는 것 같다.
생성된 코드에서는
훌륭하게 작동한다. 이틑날에 추가적으로 개발할 수 있을 듯 하다.
https://github.com/jihyeonjeong11/games/tree/main/farmingV1-clean