웹 브라우저에서 가볍게 즐길 수 있는 미니골프 게임을 직접 구현하면 프론트엔드 이벤트 처리와 기본 물리 연산 원리를 자연스럽게 익힐 수 있습니다. 별도의 게임 엔진이나 외부 라이브러리 설치 없이 HTML 캔버스와 자바스크립트 수학 함수만으로 완성도 높은 미니게임을 만들 수 있습니다.
개발 환경 및 사전 준비
이 프로젝트는 텍스트 에디터와 웹 브라우저만 있으면 누구나 바로 시작할 수 있습니다. 별도의 백엔드 서버나 빌드 도구가 필요하지 않으며, 세 개의 파일을 같은 디렉토리에 생성하여 서로 연결하는 방식으로 구성합니다.
- 프로젝트 구성 파일 목록입니다. 각 파일은 독립된 역할을 담당하며 브라우저에서 통합 실행됩니다.
- index.html: 게임 캔버스와 기본 레이아웃을 정의하는 마크업 파일
- style.css: 게임 화면을 정렬하고 시각적 요소를 꾸미는 스타일시트 파일
- app.js: 공의 움직임, 마우스 드래그 조작, 홀컵 충돌을 처리하는 자바스크립트 파일
HTML 마크업 구조 설계
관련 글: 순수 웹 기술 활용
게임 화면의 뼈대가 되는 마크업은 단순하게 유지합니다. HTML5의 canvas 요소가 게임 필드 역할을 수행하며, 현재 점수나 타수를 표시할 수 있는 정보 영역을 함께 배치합니다.
index.htmlHTML
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>미니골프 게임</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="game-container">
<h1>간단한 미니골프 게임</h1>
<div class="score-board">타수: <span id="stroke-count">0</span></div>
<canvas id="golfCanvas" width="600" height="400"></canvas>
</div>
<script src="app.js"></script>
</body>
</html>
CSS 스타일 적용
캔버스가 화면 중앙에 깔끔하게 정렬되도록 레이아웃을 설정합니다. 초록색 잔디 느낌을 주는 배경색과 직관적인 버튼 배치를 통해 시각적인 몰입감을 높여줍니다.
style.cssCSS
body {
margin: 0;
padding: 0;
background-color: #1a1a1a;
color: #ffffff;
font-family: 'Malgun Gothic', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.game-container {
text-align: center;
}
canvas {
background-color: #2e8b57;
border: 4px solid #ffffff;
border-radius: 8px;
box-shadow: 0 8px 16px rgba(0,0,0,0.4);
}
자바스크립트 물리 엔진 구현
마우스 드래그 거리와 방향을 계산해 골프공에 속도 벡터를 부여합니다. 매 프레임마다 마찰력에 의해 공의 속도가 줄어들며, 벽이나 장애물에 부딪히면 튕겨 나가는 간단한 반사 법칙을 적용합니다.
app.jsJAVASCRIPT
const canvas = document.getElementById('golfCanvas');
const ctx = canvas.getContext('2d');
const strokeSpan = document.getElementById('stroke-count');
let strokes = 0;
let isAiming = false;
let startX = 0;
let startY = 0;
let ball = {
x: 100,
y: 200,
radius: 10,
vx: 0,
vy: 0,
friction: 0.98,
isMoving: false
};
const hole = {
x: 500,
y: 200,
radius: 15
};
canvas.addEventListener('mousedown', (e) => {
if (ball.isMoving) return;
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const dist = Math.hypot(mouseX - ball.x, mouseY - ball.y);
if (dist < ball.radius * 2) {
isAiming = true;
startX = ball.x;
startY = ball.y;
}
});
canvas.addEventListener('mouseup', (e) => {
if (!isAiming) return;
isAiming = false;
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
ball.vx = (startX - mouseX) * 0.15;
ball.vy = (startY - mouseY) * 0.15;
ball.isMoving = true;
strokes++;
strokeSpan.textContent = strokes;
});
function update() {
if (ball.isMoving) {
ball.x += ball.vx;
ball.y += ball.vy;
ball.vx *= ball.friction;
ball.vy *= ball.friction;
if (Math.hypot(ball.vx, ball.vy) < 0.2) {
ball.vx = 0;
ball.vy = 0;
ball.isMoving = false;
}
if (ball.x - ball.radius < 0 || ball.x + ball.radius > canvas.width) {
ball.vx *= -1;
}
if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) {
ball.vy *= -1;
}
const distToHole = Math.hypot(ball.x - hole.x, ball.y - hole.y);
if (distToHole < hole.radius) {
alert('홀인원 성공! 총 타수: ' + strokes);
resetGame();
}
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(hole.x, hole.y, hole.radius, 0, Math.PI * 2);
ctx.fillStyle = '#111111';
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.closePath();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
function resetGame() {
ball.x = 100;
ball.y = 200;
ball.vx = 0;
ball.vy = 0;
ball.isMoving = false;
strokes = 0;
strokeSpan.textContent = strokes;
}
loop();
자주 발생하는 오류 및 해결 방법
관련 글: 미니골프게임 구현 방법
캔버스 좌표 계산 시 브라우저 스크롤이나 CSS 크기 조정에 따라 마우스 위치가 어긋나는 현상이 종종 발생합니다. getBoundingClientRect () 메서드를 활용해 캔버스 내부 상대 좌표를 정확히 산출해야 오작동을 막을 수 있습니다.
공이 멈추지 않고 계속 움직여요.
공이 멈추지 않고 계속 미끄러지는 현상은 마찰력 계수가 너무 낮거나 0에 가까울 때 발생합니다. friction 값을 0.95에서 0.98 사이로 설정하면 자연스럽게 감속합니다.
마우스 드래그 방향과 반대로 공이 날아갑니다.
마우스 드래그 방향과 반대로 공이 발사되는 것은 새총 원리와 동일한 정상적인 동작입니다. 당기는 반대 방향으로 힘 벡터가 작용하도록 코드가 작성되었기 때문입니다.
완성 코드 예제
마무리
HTML, CSS, 자바스크립트의 기본 조합만으로도 충분히 재미있는 미니골프게임을 브라우저 상에서 구현할 수 있습니다.
기본적인 충돌 감지와 벡터 연산을 응용하여 장애물이나 경사로를 추가해 나만의 게임으로 확장해 보세요.
