안녕하세요!
오늘은 제가 6살 딸 아이를 위해 직접 만든 웹 게임 **<슈퍼 아이템 토끼의 모험>**을 소개해 드리려고 합니다.

아이가 쉽게 할 수 있으면서도, 재미있는 요소(특히 방귀 소리…😅)를 넣어달라는 요청을 적극 반영하여 자바스크립트로 구현해 보았습니다. 설치할 필요 없이 웹 브라우저에서 바로 즐길 수 있습니다.
🎮 게임 플레이하기
아래 게임 화면을 클릭하고 시작해 보세요!
아이템 설명
🍠 고구마: 왕방귀 발사!
⚡ 번개: 슈웅~ 도망가기
🍄 버섯: 거대해지기(무적+공격)
💊 알약: 에너지 회복!
아이템 보유
🏅 순위 기록
⭐ 점수: 0 | 🏆 최고: 0
점프: 스페이스/클릭 · 아이템 사용: 우클릭/CTRL
효과!
앗! 잡혔다! 😭
🥕 슈퍼 토끼의 모험 🍄
🎮 조작: 스페이스/클릭 점프 · 우클릭/CTRL 아이템 사용
게임 종료!
이번 점수: 0
최고 점수: 0
이름
난이도
점수
🕹️ 조작 방법 & 꿀팁
이 게임은 PC와 모바일 모두에서 가능하지만, PC에서 키보드와 마우스를 사용할 때 가장 재미있습니다.
- 점프: 스페이스바(Space) 또는 마우스 왼쪽 클릭
- 아이템 사용:
Ctrl키 또는 마우스 오른쪽 클릭
🥕 아이템 도감
게임 도중에 하늘에서 떨어지는 아이템을 먹으면 인벤토리에 저장됩니다. 위급할 때 사용해 보세요!
- 🍠 고구마: 뿡~! 강력한 왕방귀를 뀌어서 뒤따라오는 곰 아저씨를 멀리 날려버립니다.
- ⚡ 번개: 슈웅~ 토끼가 순식간에 앞으로 도망갑니다.
- 🍄 버섯: 거대 토끼로 변신! 구름 장애물을 부수고 곰을 공격할 수 있는 무적 상태가 됩니다.
- 💊 알약: 깎인 하트(에너지)를 하나 회복합니다.
💻 개발 비하인드 & 소스코드
이 게임은 HTML5 Canvas와 JavaScript만으로 구현되었습니다.
- Web Audio API를 사용해 별도의 mp3 파일 없이 코드로 효과음을 만들었습니다.
- LocalStorage를 활용해 브라우저에 최고 점수와 랭킹이 저장되도록 했습니다.
혹시 아이를 위해 비슷한 게임을 만들어보고 싶거나, 코딩 공부용으로 필요하신 분들을 위해 전체 소스코드를 공유합니다. 이 코드를 복사해서 .html 파일로 저장하면 바로 실행됩니다.
📜 전체 소스코드 보기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>슈퍼 아이템 토끼의 모험</title>
<style>
.game-wrapper {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
width: 100%;
height: 600px;
background-color: #f0f8ff;
font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif;
overflow: hidden;
position: relative;
}
.side-panel {
width: 230px;
height: 560px;
background: rgba(255,255,255,0.9);
border-radius: 16px;
box-shadow: 0 6px 14px rgba(0,0,0,0.08);
padding: 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.panel-title {
font-weight: bold;
color: #ff6f61;
font-size: 16px;
}
.panel-box {
background: #fff;
border-radius: 12px;
padding: 10px;
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
font-size: 14px;
color: #333;
}
.game-area {
position: relative;
}
#gameCanvas {
border: 4px solid #ffb7b2;
border-radius: 15px;
background: linear-gradient(to bottom, #87CEEB, #E0F7FA);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
display: block;
}
.ui-layer {
position: absolute;
top: 20px;
width: 100%;
text-align: center;
pointer-events: none;
z-index: 5;
}
#score-board {
color: #FFD700;
text-shadow: 2px 2px 0px #000;
font-size: 26px;
font-weight: bold;
margin: 0 0 8px 0;
}
#lightning-meter {
width: 220px;
height: 14px;
margin: 0 auto;
border-radius: 20px;
background: rgba(0,0,0,0.15);
overflow: hidden;
box-shadow: inset 0 0 4px rgba(0,0,0,0.2);
}
#lightning-fill {
height: 100%;
width: 100%;
background: linear-gradient(90deg, #ffe066, #ff8c00);
transition: width 0.1s linear, background 0.2s ease;
}
#controls-hint {
margin: 6px 0 0 0;
font-size: 14px;
color: #444;
text-shadow: 1px 1px 0px #fff;
}
#inventory {
margin: 8px auto 0 auto;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.slot {
width: 36px;
height: 36px;
border-radius: 10px;
background: rgba(255,255,255,0.8);
border: 2px solid #ffb7b2;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
box-shadow: 0 3px 6px rgba(0,0,0,0.08);
}
#energy-bar {
margin: 6px auto 0 auto;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.heart {
font-size: 24px;
}
#effect-text {
color: #fff;
font-size: 40px;
font-weight: bold;
text-shadow: 2px 2px 0px #ff6f61;
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
display: none;
z-index: 20;
}
@keyframes popUp {
0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0; }
50% { transform: translate(-50%, -50%) scale(1.2); opacity: 1; }
100% { transform: translate(-50%, -50%) scale(1.0); opacity: 1; }
}
#message {
color: #ff4444;
text-shadow: 1px 1px 0px #fff;
font-size: 35px;
font-weight: bold;
display: none;
margin-top: 20px;
background-color: rgba(255,255,255,0.8);
padding: 10px;
border-radius: 10px;
}
#start-screen {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255, 255, 255, 0.95);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
}
#start-btn {
padding: 15px 30px;
font-size: 24px;
background-color: #ff6f61;
color: white;
border: none;
border-radius: 50px;
cursor: pointer;
box-shadow: 0 5px #d45a4e;
font-weight: bold;
margin-top: 20px;
}
.item-desc {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 20px;
text-align: left;
font-size: 16px;
color: #333;
background: #fff;
padding: 20px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.controls-box {
margin-top: 16px;
font-size: 15px;
color: #444;
background: #fff;
padding: 10px 14px;
border-radius: 12px;
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
}
#leaderboard ol {
margin: 6px 0 0 18px;
padding: 0;
max-height: 240px;
overflow-y: auto;
}
.end-form {
margin-top: 10px;
background: #fff;
padding: 10px 14px;
border-radius: 12px;
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
display: grid;
gap: 8px;
min-width: 240px;
}
.end-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #333;
}
.end-input, .end-select {
flex: 1;
border: 2px solid #ffb7b2;
border-radius: 10px;
padding: 6px 10px;
font-size: 14px;
outline: none;
}
#save-score-btn {
padding: 8px 16px;
font-size: 16px;
border-radius: 20px;
border: none;
background: #ff6f61;
color: white;
cursor: pointer;
font-weight: bold;
}
#end-screen {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255, 255, 255, 0.95);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 12;
}
#restart-btn {
padding: 12px 26px;
font-size: 22px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 50px;
cursor: pointer;
box-shadow: 0 5px #3e8e41;
font-weight: bold;
margin-top: 15px;
}
.difficulty-box {
margin-top: 16px;
display: flex;
gap: 10px;
justify-content: center;
}
.diff-btn {
padding: 8px 18px;
font-size: 16px;
border: 2px solid #ffb7b2;
border-radius: 20px;
background: white;
cursor: pointer;
transition: all 0.2s;
font-weight: bold;
}
.diff-btn.active {
background: #ff6f61;
color: white;
border-color: #ff6f61;
}
.diff-btn:hover {
transform: scale(1.05);
}
</style>
</head>
<body>
<div class="game-wrapper" id="game-wrapper">
<div class="side-panel">
<div class="panel-box">
<div class="panel-title">아이템 설명</div>
<div>🍠 고구마: 왕방귀 발사!</div>
<div>⚡ 번개: 슈웅~ 도망가기</div>
<div>🍄 버섯: 거대해지기(무적+공격)</div>
<div>💊 알약: 에너지 회복!</div>
</div>
<div class="panel-box">
<div class="panel-title">아이템 보유</div>
<div id="inventory"></div>
</div>
<div class="panel-box" id="leaderboard">
<div class="panel-title">🏅 순위 기록</div>
<ol id="leaderboard-list"></ol>
</div>
</div>
<div class="game-area">
<canvas id="gameCanvas" width="400" height="600"></canvas>
<div class="ui-layer">
<p id="score-board">⭐ 점수: <span id="score">0</span> | 🏆 최고: <span id="best-score">0</span></p>
<div id="lightning-meter"><div id="lightning-fill"></div></div>
<p id="controls-hint">점프: 스페이스/클릭 · 아이템 사용: 우클릭/CTRL</p>
<div id="energy-bar"></div>
<div id="effect-text">효과!</div>
<p id="message">앗! 잡혔다! 😭</p>
</div>
<div id="start-screen">
<h1 style="color:#ff6f61; margin:0; font-size: 28px;">🥕 슈퍼 토끼의 모험 🍄</h1>
<div class="controls-box">🎮 조작: 스페이스/클릭 점프 · 우클릭/CTRL 아이템 사용</div>
<div class="difficulty-box">
<button class="diff-btn" data-level="easy">쉬움 😊</button>
<button class="diff-btn active" data-level="normal">보통 😐</button>
<button class="diff-btn" data-level="hard">어려움 😰</button>
</div>
<button id="start-btn">▶ 게임 시작하기</button>
</div>
<div id="end-screen">
<h2 style="color:#ff6f61; margin:0 0 10px 0;">게임 종료!</h2>
<p style="font-size:20px; margin:0 0 6px 0;">이번 점수: <b id="end-score">0</b></p>
<p style="font-size:18px; margin:0;">최고 점수: <b id="end-best">0</b></p>
<div class="end-form">
<div class="end-row">
<span>이름</span>
<input id="end-name" class="end-input" type="text" maxlength="10" placeholder="이름 입력" />
</div>
<div class="end-row">
<span>난이도</span>
<select id="end-difficulty" class="end-select">
<option value="easy">쉬움</option>
<option value="normal">보통</option>
<option value="hard">어려움</option>
</select>
</div>
<div class="end-row">
<span>점수</span>
<input id="end-score-input" class="end-input" type="number" min="0" />
</div>
<button id="save-score-btn">기록 올리기</button>
</div>
<button id="restart-btn">다시 하기</button>
</div>
</div>
</div>
<script>
window.addEventListener('load', function() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreBoard = document.getElementById('score-board');
const messageDiv = document.getElementById('message');
const startScreen = document.getElementById('start-screen');
const startBtn = document.getElementById('start-btn');
const effectText = document.getElementById('effect-text');
const scoreValue = document.getElementById('score');
const bestScoreValue = document.getElementById('best-score');
const lightningFill = document.getElementById('lightning-fill');
const inventoryEl = document.getElementById('inventory');
const energyBar = document.getElementById('energy-bar');
const endScreen = document.getElementById('end-screen');
const endScore = document.getElementById('end-score');
const endBest = document.getElementById('end-best');
const endNameInput = document.getElementById('end-name');
const endDifficultySelect = document.getElementById('end-difficulty');
const endScoreInput = document.getElementById('end-score-input');
const saveScoreBtn = document.getElementById('save-score-btn');
const leaderboardList = document.getElementById('leaderboard-list');
const restartBtn = document.getElementById('restart-btn');
// --- 사운드 ---
let audioCtx = null;
function initAudio() {
try {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) audioCtx = new AudioContext();
} catch (e) {}
}
function playTone(type) {
if (!audioCtx) return;
try {
if (audioCtx.state === 'suspended') audioCtx.resume();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain); gain.connect(audioCtx.destination);
const now = audioCtx.currentTime;
if (type === 'jump') {
osc.frequency.setValueAtTime(600, now); osc.frequency.linearRampToValueAtTime(800, now+0.1);
gain.gain.setValueAtTime(0.1, now); gain.gain.linearRampToValueAtTime(0, now+0.1);
osc.start(now); osc.stop(now+0.1);
} else if (type === 'item') {
osc.type = 'sine';
osc.frequency.setValueAtTime(500, now); osc.frequency.linearRampToValueAtTime(1000, now+0.2);
gain.gain.setValueAtTime(0.1, now); gain.gain.linearRampToValueAtTime(0, now+0.2);
osc.start(now); osc.stop(now+0.2);
} else if (type === 'fart') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(150, now); osc.frequency.exponentialRampToValueAtTime(50, now+0.5);
gain.gain.setValueAtTime(0.3, now); gain.gain.linearRampToValueAtTime(0, now+0.5);
osc.start(now); osc.stop(now+0.5);
} else if (type === 'hit') {
osc.type = 'square'; osc.frequency.setValueAtTime(150, now);
gain.gain.setValueAtTime(0.2, now); gain.gain.linearRampToValueAtTime(0, now+0.3);
osc.start(now); osc.stop(now+0.3);
}
} catch (e) {}
}
// --- 게임 변수 ---
let gameState = 'ready';
let score = 0;
let bestScore = Number(localStorage.getItem('rabbit_best') || 0);
let frame = 0;
let gameSpeed = 3;
let difficulty = 'normal';
let LIGHTNING_MAX = 600;
let lightningTimer = LIGHTNING_MAX;
const INVENTORY_MAX = 3;
let inventory = [];
let MAX_ENERGY = 3;
let energy = MAX_ENERGY;
let chaseSpeedBase = 0.12;
let chaseSpeedMultiplier = 0.5;
// 아이템 효과 타이머
let bigTimer = 0;
let smallTimer = 0;
const bunny = {
x: 100, y: 300, baseSize: 35, size: 35, velocity: 0,
draw() {
let emoji = '🐰';
if(gameState === 'caught') emoji = '😭';
else if(bigTimer > 0) emoji = '💪🐰';
else if(smallTimer > 0) emoji = '👶';
ctx.font = this.size + 'px sans-serif';
ctx.fillText(emoji, this.x, this.y + this.size);
},
update() {
this.velocity += 0.4;
this.y += this.velocity;
if(bigTimer > 0) { this.size = 70; bigTimer--; }
else if(smallTimer > 0) { this.size = 20; smallTimer--; }
else { this.size = this.baseSize; }
if(this.y > canvas.height - 40) { this.y = canvas.height - 40; this.velocity = 0; }
if(this.y < 0) { this.y = 0; this.velocity = 0; }
if(this.x > 100) this.x -= 0.5;
if(this.x < 100) this.x += 0.5;
},
jump() { this.velocity = -7; playTone('jump'); }
};
const hunter = {
x: 20, y: 300, reactionTime: 0,
draw() {
ctx.font = '45px sans-serif';
ctx.fillText('🐻', this.x, this.y + 45);
if(this.reactionTime > 0) {
ctx.font = '20px sans-serif'; ctx.fillStyle = 'red';
ctx.fillText('으악!', this.x+10, this.y-10);
this.reactionTime--;
}
},
update() {
this.y += (bunny.y - this.y) * 0.05;
const chaseBoost = chaseSpeedBase + (1 - (lightningTimer / LIGHTNING_MAX)) * chaseSpeedMultiplier;
this.x += chaseBoost;
if(this.x > bunny.x - 10) this.x = bunny.x - 10;
}
};
// 아이템 클래스
let items = [];
class Item {
constructor() {
this.x = canvas.width;
this.y = Math.random() * (canvas.height - 100) + 50;
let rand = Math.random();
if(rand < 0.5) { this.type = 'star'; this.emoji = '⭐'; }
else if(rand < 0.65) { this.type = 'fart'; this.emoji = '🍠'; }
else if(rand < 0.8) { this.type = 'speed'; this.emoji = '⚡'; }
else if(rand < 0.9) { this.type = 'big'; this.emoji = '🍄'; }
else { this.type = 'energy'; this.emoji = '💊'; }
}
update() { this.x -= gameSpeed; }
draw() { ctx.font = '30px sans-serif'; ctx.fillText(this.emoji, this.x, this.y + 30); }
}
let clouds = [];
let farts = [];
function checkCollision(obj, item) {
let dx = obj.x - item.x;
let dy = obj.y - item.y;
let dist = 35;
if(bigTimer > 0) dist = 60;
if(smallTimer > 0) dist = 20;
return Math.sqrt(dx*dx + dy*dy) < dist;
}
function showEffect(text) {
effectText.innerText = text;
effectText.style.display = 'block';
effectText.style.animation = 'none';
effectText.offsetHeight;
effectText.style.animation = 'popUp 1s ease-out';
setTimeout(() => { effectText.style.display = 'none'; }, 1000);
}
function animate() {
if (gameState !== 'playing') return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
frame++;
if(frame % 20 === 0) {
score++;
updateScore();
}
lightningTimer--;
updateLightningMeter();
if(lightningTimer <= 0) {
lightningTimer = 0; // 0 이하로 내려가지 않도록
}
// 바닥
ctx.fillStyle = '#90EE90'; ctx.fillRect(0, canvas.height - 20, canvas.width, 20);
// [변경] 자동 방귀 삭제됨
// 방귀 업데이트 (고구마 먹었을 때만 생성됨)
for(let i=0; i<farts.length; i++){
farts[i].x -= 2; farts[i].life--;
ctx.font = '60px sans-serif'; ctx.fillText('💨', farts[i].x, farts[i].y + 25);
// 곰이 방귀 맞음
if(Math.abs(farts[i].x - hunter.x) < 50 && Math.abs(farts[i].y - hunter.y) < 50) {
hunter.reactionTime = 40;
hunter.x -= 100; // 곰을 멀리 밀어냄
playTone('hit');
farts.splice(i, 1); i--;
} else if(farts[i].life <= 0) { farts.splice(i, 1); i--; }
}
// 캐릭터
hunter.update(); hunter.draw();
bunny.update(); bunny.draw();
// 아이템
if (frame % 70 === 0) items.push(new Item());
for(let i=0; i<items.length; i++){
items[i].update(); items[i].draw();
if(checkCollision(bunny, items[i])) {
let type = items[i].type;
playTone('item');
if(type === 'star') {
score += 5; updateScore();
}
else if(type === 'fart') { // 고구마: 왕방귀!
storeItem(type, "고구마 저장!");
}
else if(type === 'speed') { // 번개
storeItem(type, "번개 저장!");
}
else if(type === 'big') { // 버섯
storeItem(type, "버섯 저장!");
}
else if(type === 'energy') { // 알약
storeItem(type, "알약 저장!");
}
items.splice(i, 1); i--;
} else if(items[i].x < -50) { items.splice(i, 1); i--; }
}
// 구름
if (frame % 140 === 0) clouds.push({x: canvas.width, y: Math.random()*(canvas.height-200)+50});
for(let i=0; i<clouds.length; i++){
clouds[i].x -= gameSpeed;
ctx.font = '40px sans-serif'; ctx.fillText('☁️', clouds[i].x, clouds[i].y + 30);
if(checkCollision(bunny, clouds[i])) {
if(bigTimer > 0) { // 무적
playTone('hit');
clouds.splice(i, 1); i--;
score += 2; updateScore();
} else {
bunny.x -= 20; if(bunny.x < 50) bunny.x = 50;
playTone('hit');
clouds.splice(i, 1); i--;
}
} else if(clouds[i].x < -50) { clouds.splice(i, 1); i--; }
}
// 잡힘 체크
let shouldContinue = true;
if(Math.abs(bunny.x - hunter.x) < (bigTimer>0?50:30) && Math.abs(bunny.y - hunter.y) < (bigTimer>0?50:30)) {
if(bigTimer > 0) {
// 버섯 먹었을 때 곰 공격
showEffect("곰 공격! 🐻💥");
hunter.x -= 150;
if(hunter.x < -120) hunter.x = -120;
bunny.x += 80;
if(bunny.x > 250) bunny.x = 250;
score += 10;
updateScore();
playTone('hit');
} else {
// 에너지 감소
energy--;
renderEnergy();
if(energy <= 0) {
gameOver('앗! 잡혔다! 😭');
shouldContinue = false;
} else {
showEffect("앗! 에너지 -1");
hunter.x -= 100;
bunny.x += 50;
playTone('hit');
}
}
}
if(shouldContinue) {
requestAnimationFrame(animate);
}
}
function storeItem(type, text) {
if(inventory.length < INVENTORY_MAX) {
inventory.push(type);
renderInventory();
showEffect(text);
} else {
showEffect("가득! 바로 사용!");
useItem(type);
}
}
function consumeNextItem() {
if(gameState !== 'playing') return;
if(inventory.length === 0) {
showEffect("아이템 없음");
return;
}
const next = inventory.shift();
renderInventory();
useItem(next);
}
function useItem(type) {
if(type === 'fart') {
showEffect("왕방귀 발사!!");
farts.push({x: bunny.x, y: bunny.y, life: 100});
playTone('fart');
} else if(type === 'speed') {
showEffect("슈우웅~!!");
lightningTimer = LIGHTNING_MAX;
bunny.x += 150; if(bunny.x > 300) bunny.x = 300;
} else if(type === 'big') {
showEffect("거대 토끼!!");
bigTimer = 300; smallTimer = 0;
} else if(type === 'energy') {
if(energy < MAX_ENERGY) {
energy++;
renderEnergy();
showEffect("에너지 회복! ❤️");
} else {
showEffect("에너지 가득!");
}
}
}
function renderInventory() {
const icons = inventory.map(type => {
if(type === 'fart') return '🍠';
if(type === 'speed') return '⚡';
if(type === 'big') return '🍄';
if(type === 'energy') return '💊';
return '❓';
});
let html = '';
for(let i=0; i<INVENTORY_MAX; i++) {
html += `<div class="slot">${icons[i] || ''}</div>`;
}
inventoryEl.innerHTML = html;
}
function renderEnergy() {
let html = '';
for(let i=0; i<MAX_ENERGY; i++) {
if(i < energy) html += '<span class="heart">❤️</span>';
else html += '<span class="heart">🖤</span>';
}
energyBar.innerHTML = html;
}
function updateScore() {
scoreValue.innerText = score;
if(score > bestScore) {
bestScore = score;
localStorage.setItem('rabbit_best', bestScore);
bestScoreValue.innerText = bestScore;
}
}
function updateLightningMeter() {
const ratio = Math.max(0, Math.min(1, lightningTimer / LIGHTNING_MAX));
lightningFill.style.width = (ratio * 100).toFixed(1) + '%';
if(ratio < 0.3) lightningFill.style.background = 'linear-gradient(90deg, #ff4d4d, #ff9900)';
else lightningFill.style.background = 'linear-gradient(90deg, #ffe066, #ff8c00)';
}
function gameOver(reason) {
gameState = 'caught';
messageDiv.innerText = reason;
messageDiv.style.display = 'block';
endScore.innerText = score;
endBest.innerText = bestScore;
endNameInput.value = '';
endDifficultySelect.value = difficulty;
endScoreInput.value = score;
saveScoreBtn.disabled = false;
saveScoreBtn.innerText = '기록 올리기';
renderLeaderboard();
endScreen.style.display = 'flex';
}
function resetGame() {
gameState = 'playing';
score = 0; updateScore();
messageDiv.style.display = 'none';
endScreen.style.display = 'none';
bunny.x = 100; bunny.y = 300; bunny.velocity = 0;
hunter.y = 300; hunter.x = -120;
items = []; clouds = []; farts = []; frame = 0;
bigTimer = 0; smallTimer = 0;
lightningTimer = LIGHTNING_MAX;
inventory = []; renderInventory();
energy = MAX_ENERGY; renderEnergy();
updateLightningMeter();
animate();
}
function saveScoreFromForm() {
if(saveScoreBtn.disabled) return;
const name = (endNameInput.value || '').trim() || '익명';
const diff = endDifficultySelect.value || 'normal';
const rawScore = parseInt(endScoreInput.value, 10);
const safeScore = Number.isFinite(rawScore) ? Math.max(0, rawScore) : score;
const entry = { name, difficulty: diff, score: safeScore, time: Date.now() };
const saved = JSON.parse(localStorage.getItem('rabbit_scores') || '[]');
saved.push(entry);
saved.sort((a, b) => b.score - a.score);
const trimmed = saved.slice(0, 20);
localStorage.setItem('rabbit_scores', JSON.stringify(trimmed));
renderLeaderboard();
saveScoreBtn.disabled = true;
saveScoreBtn.innerText = '기록 완료';
}
function renderLeaderboard() {
const saved = JSON.parse(localStorage.getItem('rabbit_scores') || '[]');
leaderboardList.innerHTML = saved.map(item => {
const safeName = item.name.replace(/</g, '<').replace(/>/g, '>');
const diffLabel = item.difficulty === 'easy' ? '쉬움' : item.difficulty === 'hard' ? '어려움' : '보통';
return `<li>${safeName} (${diffLabel}) - ${item.score}</li>`;
}).join('');
}
// 난이도 선택
document.querySelectorAll('.diff-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.diff-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
difficulty = this.getAttribute('data-level');
applyDifficulty();
});
});
function applyDifficulty() {
if(difficulty === 'easy') {
LIGHTNING_MAX = 900;
MAX_ENERGY = 5;
chaseSpeedBase = 0.08;
chaseSpeedMultiplier = 0.3;
} else if(difficulty === 'normal') {
LIGHTNING_MAX = 600;
MAX_ENERGY = 3;
chaseSpeedBase = 0.12;
chaseSpeedMultiplier = 0.5;
} else { // hard
LIGHTNING_MAX = 450;
MAX_ENERGY = 2;
chaseSpeedBase = 0.15;
chaseSpeedMultiplier = 0.7;
}
}
startBtn.onclick = function() {
initAudio();
applyDifficulty();
startScreen.style.display = 'none';
resetGame();
};
saveScoreBtn.onclick = function() {
saveScoreFromForm();
};
restartBtn.onclick = function() {
resetGame();
};
function doJump() { if(gameState === 'playing') bunny.jump(); }
window.onkeydown = function(e) {
if(e.code === 'Space') doJump();
if((e.code === 'ControlLeft' || e.code === 'ControlRight') && !e.repeat) consumeNextItem();
};
canvas.onmousedown = doJump;
canvas.ontouchstart = function(e) { e.preventDefault(); doJump(); };
canvas.oncontextmenu = function(e) { e.preventDefault(); consumeNextItem(); };
bestScoreValue.innerText = bestScore;
updateLightningMeter();
renderInventory();
renderEnergy();
renderLeaderboard();
bunny.draw(); hunter.draw();
});
</script>
</body>
</html>
</details>
모두 즐거운 시간 되시길 바랍니다! 😊