[아빠표 코딩] 6살 딸을 위해 만든 웹게임 ‘슈퍼 아이템 토끼의 모험’ (플레이 & 소스코드 공개)

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


아이가 쉽게 할 수 있으면서도, 재미있는 요소(특히 방귀 소리…😅)를 넣어달라는 요청을 적극 반영하여 자바스크립트로 구현해 보았습니다. 설치할 필요 없이 웹 브라우저에서 바로 즐길 수 있습니다.

🎮 게임 플레이하기

아래 게임 화면을 클릭하고 시작해 보세요!

슈퍼 아이템 토끼의 모험
아이템 설명
🍠 고구마: 왕방귀 발사!
⚡ 번개: 슈웅~ 도망가기
🍄 버섯: 거대해지기(무적+공격)
💊 알약: 에너지 회복!
아이템 보유
🏅 순위 기록

    ⭐ 점수: 0 | 🏆 최고: 0

    점프: 스페이스/클릭 · 아이템 사용: 우클릭/CTRL

    효과!

    앗! 잡혔다! 😭

    🥕 슈퍼 토끼의 모험 🍄

    🎮 조작: 스페이스/클릭 점프 · 우클릭/CTRL 아이템 사용

    게임 종료!

    이번 점수: 0

    최고 점수: 0

    이름
    난이도
    점수

    🕹️ 조작 방법 & 꿀팁

    이 게임은 PC와 모바일 모두에서 가능하지만, PC에서 키보드와 마우스를 사용할 때 가장 재미있습니다.

    • 점프: 스페이스바(Space) 또는 마우스 왼쪽 클릭
    • 아이템 사용: Ctrl 키 또는 마우스 오른쪽 클릭

    🥕 아이템 도감

    게임 도중에 하늘에서 떨어지는 아이템을 먹으면 인벤토리에 저장됩니다. 위급할 때 사용해 보세요!

    1. 🍠 고구마: 뿡~! 강력한 왕방귀를 뀌어서 뒤따라오는 곰 아저씨를 멀리 날려버립니다.
    2. ⚡ 번개: 슈웅~ 토끼가 순식간에 앞으로 도망갑니다.
    3. 🍄 버섯: 거대 토끼로 변신! 구름 장애물을 부수고 곰을 공격할 수 있는 무적 상태가 됩니다.
    4. 💊 알약: 깎인 하트(에너지)를 하나 회복합니다.

    💻 개발 비하인드 & 소스코드

    이 게임은 HTML5 CanvasJavaScript만으로 구현되었습니다.

    • 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, '&lt;').replace(/>/g, '&gt;');
                    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>

    모두 즐거운 시간 되시길 바랍니다! 😊

    Leave a Comment

    Smart Dog