JavaScript와 Canvas로 룰렛 게임 구현하기 – 완성된 모습
1. 초기 HTML 구조 작성하기
먼저, 기본적인 HTML 파일을 만들고, 룰렛을 그릴 canvas 요소와 룰렛을 돌리는 버튼을 배치합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas 룰렛</title>
</head>
<body>
<canvas id="canvas"></canvas>
<button id="spin">spin</button>
<script>
</script>
</body>
</html>
<canvas>는 룰렛을 그리는 공간입니다.
<button>은 룰렛을 회전시키는 버튼입니다.
<script>는 자바스크립트 코드를 이곳에 작성합니다.
2. Canvas를 이용해 룰렛 그리기
이제 script 부분에서 JavaScript를 이용해 룰렛 UI를 그리는 코드를 작성합니다.
2.1. Canvas 설정 및 초기화
먼저, script 부분에서 Canvas 요소를 가져오고, 2D 컨텍스트를 설정합니다.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 캔버스 크기 설정
canvas.width = 500;
canvas.height = 500;
getContext("2d"): 캔버스에서 그림을 그릴 수 있도록 설정합니다.
canvas.width, canvas.height: 캔버스 크기를 500×500으로 설정합니다.
2.2. 룰렛 원판그리기
원을 그리는 함수를 작성합니다.
function drawCircle() {
ctx.beginPath();
ctx.arc(250, 250, 240, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "#63FF84";
ctx.fill();
ctx.stroke();
}
drawCircle()
1. 새로운 경로를 시작 (beginPath())
2. 원의 중심(250, 250)과 반지름(240)으로 원을 그림 (arc())
3. 경로를 닫아 원을 완성 (closePath())
4. 연두색("#63FF84")으로 원을 채움 (fillStyle → fill())
5. 테두리를 그림 (stroke())
실행하면 초록색 원이 나타납니다.
2.3. 포인터 그리기
삼각형을 그리는 함수를 작성합니다.(포인터는 원판의 12시 부분에, 선택된 아이템을 가리키는 역할을 하는 삼각형을 의미합니다.)
function drawPointer() {
ctx.beginPath();
ctx.moveTo(250, 20);
ctx.lineTo(243, 6);
ctx.lineTo(257, 6);
ctx.closePath();
ctx.fillStyle = "#fff";
ctx.fill();
ctx.stroke();
}
drawPointer()
1. 새로운 경로를 시작 (beginPath())
2. 포인터(삼각형)의 세 꼭짓점을 지정
- moveTo(250, 20) → 포인터의 꼭짓점을 설정
- lineTo(243, 6), lineTo(257, 6) → 삼각형을 완성하는 두 점 추가
3. 경로를 닫아 삼각형을 완성 (closePath())
4. 흰색("#fff")으로 삼각형을 채움 (fillStyle → fill())
5. 테두리를 그림 (stroke())
실행하면 원 위쪽에 삼각형 모양의 포인터가 추가됩니다.
2.4. 룰렛 섹션 나누기
룰렛을 여러 조각으로 나누고, 각 조각에 색상과 텍스트를 추가합니다.
2.4.1. 데이터 배열 만들기
룰렛에 들어갈 항목과 색상을 배열로 정의합니다.
const testData = [
{ id: 0, text: "김치찌개", background: "#ab6c66" },
{ id: 1, text: "부대찌개", background: "#86bc85" },
{ id: 2, text: "된장찌개", background: "#a5adce" },
];
let segments = [...testData];
testData 배열 → 룰렛에 들어갈 메뉴와 색상을 저장
segments 배열 → 룰렛의 조각을 동적으로 조작할 때 사용
2.4.2. 룰렛 조각 그리기
segments 조각을 룰렛에 그립니다.
function drawSegments(angle) {
const addAngle = (Math.PI * 2) / segments.length;
ctx.clearRect(0, 0, 500, 500); // 이전 프레임 지우기
segments.forEach((segment, index) => {
ctx.beginPath();
ctx.moveTo(250, 250);
ctx.arc(250, 250, 240 - 1, angle, angle + addAngle);
ctx.closePath();
ctx.fillStyle = segment.background;
ctx.fill();
ctx.lineWidth = 1; // 선 두께 설정
ctx.stroke(); // 경로 외곽선 그리기
ctx.save(); // 현재 상태 저장
ctx.translate(250, 250);
ctx.rotate(angle + addAngle / 2);
ctx.textAlign = "center";
ctx.font = "bold 28px sans-serif";
ctx.fillStyle = "black";
ctx.fillText(segment.text, 240 / 2, 0);
ctx.restore();
angle += addAngle; // 다음 각도 시작 각도 업데이트
});
drawPointer();
}
drawSegments(0);
1. 각 조각의 크기(addAngle)를 계산하여 룰렛을 균등하게 나눔
2. 이전 프레임을 지움 (clearRect()) → 이전 프레임이 남아있으면 외곽선이 두꺼워짐
3. 각 조각을 순회하면서 원을 그림 (forEach())
- arc()를 이용해 원의 한 조각을 그림
- fill()로 조각을 색칠하고, stroke()로 테두리를 그림
4. 텍스트 배치 준비
- save()로 캔버스 상태 저장
- translate(250, 250)로 원점 이동
- rotate()를 사용해 조각 중앙에 맞춤
5. 텍스트 추가 후 상태 복원
- fillText()로 조각 중앙에 텍스트 표시
- restore()로 원래 캔버스 상태로 복원
6. 각도를 업데이트하여 다음 조각을 그림마지막으로 포인터(화살표)를 그림 (drawPointer())
2.5. 애니메이션 및 회전 로직
룰렛을 부드럽게 회전하도록 설정합니다.
2.5.1. 상태 변수 설정
let spinning = false; // 회전 상태
let selectedSegment = null // 랜덤 값을 저장
let rotationCount = 20; // 기본 회전수
let totalRotation = 0 // 계산된 전체 회전수에 대한 라디안 값
let startTimestamp = null // animate 함수가 실행된 시간
let duration = 5000 // 회전 시간
2.5.2. 감속 함수 (이징)
animate 함수의 progress 부분을 수정하면 됩니다. 저는 easings.net 에서 제공해주는 코드를 사용했습니다.
function easeInOutQuad(x) {
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
}
2.5.3. 회전 애니메이션
룰렛을 회전시키는 애니메이션 동작을 하는 함수를 작성합니다.
function animate(timestamp) {
if (!spinning) {
spinning = true;
startTimestamp = timestamp;
}
let elapsed = timestamp - startTimestamp; // 경과 시간 계산
let progress = easeInOutQuad(Math.min(elapsed / duration, 1)); // 이징함수 적용
let angle = totalRotation * progress; // 현재 회전 각도 계산
drawSegments(angle);
if (progress < 1) {
requestAnimationFrame(animate);
} else {
spinning = false;
}
}
1. timestamp → requestAnimationFrame()이 제공하는 현재 시간
2. 애니메이션 처음 실행 시 (spinning === false)
- startTimestamp에 timestamp 저장
- spinning = true;로 설정하여 중복 실행 방지
3. 경과 시간 계산 (elapsed = timestamp - startTimestamp;)
4. progress (진행 정도) 계산
- elapsed / duration으로 진행률 계산
- progress가 1이 되면 애니메이션 종료
2.5.4. 회전 시작 함수
룰렛의 회전을 시작하는 함수를 작성합니다.
function spin() {
if (spinning) return; // 이미 실행중이면, 실행되지 않게함
selectedSegment = Math.floor(Math.random() * segments.length); // 랜덤한 섹션 선택
const angle = (Math.PI * 2) / segments.length; // 한 조각의 크기를 계산
const randomAngle = Math.random() * -angle; // 선택된 섹션이 차지하는 범위에서 랜덤 값을 구합니다.
const correctionAngle = Math.PI * 1.5 - angle * selectedSegment; // 섹션의 시작각이 270도에 위치하게 합니다.
totalRotation = Math.PI * 2 * rotationCount + correctionAngle + randomAngle; // 전체 회전수
requestAnimationFrame(animate); // 애니메이션 시작
}
3. 이벤트 추가
버튼을 클릭하면 spin()을 호출하여 룰렛이 회전하도록 설정합니다.
document.getElementById("spin").addEventListener("click", spin);
잘 보고 가요! 저도 Canvas를 이용해서 재미있는 것들 좀 만들어보고 싶네요.
앗 ㅋㅋ 좋게 봐주셔서 감사합니다.
test 댓글