맵과 마우스 이동에 따른 1인칭 시점 렌더링까지 구현했으니 이제는 타겟을 생성해야 한다. 디자인부터 생각하다가 점수 측정 방식에 따라 디자인이 달라져야 한다는 생각에 도달했다.
게임 시스템을 구상하면서 점수 측정 기준을 단순히 맞춘 개수로 할지, 오래 버틴 시간을 기준으로 할 지 결정해야했다. 두 가지 다 사실 같은 기준이라고도 볼 수 있지만, 하나의 분명한 점수 기준이 필요했다.
점수 측정 기준을 정하기 전에 종료 기준도 정해야 한다는 사실을 깨달았다. 그리고 오래 걸리지 않고 정할 수 있었다. 게임성과 정확성을 충분히 판별할 수 있는 기준이라고 생각한다.
- 화면에 10개의 타겟이 남아있다면 게임 종료.
- 타겟 생성 속도는 점점 빨라짐- 타겟 적중 위치에 따른 차등 득점 방식으로 총합 점수를 계산.
- 바깥쪽: 1점
- 중간: 2점
- 중앙: 3점
타겟을 클릭할 때, 위치에 따라 점수를 차등 지급하도록 했다. 중앙에 가까울수록 고득점인 양궁이나 사격종목과 같은 방식이다. 타겟은 간단하게 3개의 동심원 형태로, Canvas를 사용해 그렸다.
export const drawCircle = (
ctx: CanvasRenderingContext2D,
x: number,
y: number,
radius: number,
fillStyle: string,
strokeStyle: string = '#333333',
lineWidth: number = Math.max(Math.floor(radius * 0.1), 1)
) => {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = fillStyle;
ctx.fill();
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.stroke();
};
// 바깥쪽 원 (1점)
drawCircle(
ctx,
screenX,
screenY,
size / 2,
isGameStarted ? CANVAS_COLORS.TARGET_OUTER : CANVAS_COLORS.TARGET_INNER
);

맵 중앙의 큰 직사각형, 즉 복도형 맵의 벽면이 타겟 생성 컨테이너다. 생성 영역이 너무 크면 현재 2d로 구현한 맵이기 때문에 실제 3d fps와의 역체감이 너무 크게 느껴질 것 같았다. 그래서 중앙의 좁은 영역을 타겟 컨테이너로 설정했다. 이 또한 Canvas로 그렸는데, 화면 비율에 반응형으로 지정하려면 직접 비율을 찾아서 위치를 설정해야 했기 때문에 피곤했다.
export const getDefaultConfig = (resolution: number): ContainerConfig => {
if (resolution === 16 / 9) {
return {
mapAspectRatio: 16 / 9, // 맵 비율
targetAreaRatio: 1, // 타겟 크기
verticalOffsetRatio: 0.246, // 수직 이동 비율
widthScaleRatio: 2.438, // 화면 너비 대비 크기 비율
heightScaleRatio: 4.124, // 화면 높이 대비 크기 비율
};
} else if (resolution === 16 / 10) {
return {
mapAspectRatio: 16 / 10,
targetAreaRatio: 1,
verticalOffsetRatio: 0.26,
widthScaleRatio: 2.326,
heightScaleRatio: 4.364,
};
} else {
return {
mapAspectRatio: 4 / 3,
targetAreaRatio: 1,
verticalOffsetRatio: 0.3,
widthScaleRatio: 2.33,
heightScaleRatio: 5.26,
};
}
};
게임 종료 기준을 정한 다음 타겟을 구현했다.
이제는 게임 시작 후 타겟 생성을 관리하는 로직이 필요하다.