Web网页开发——水果忍者

发布于:2025-02-27 ⋅ 阅读:(28) ⋅ 点赞:(0)

1.介绍

复刻经典小游戏——水果忍者

2.预览

在这里插入图片描述

3.代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fruit Ninja Web Demo</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            background-color: #333;
            font-family: Arial, sans-serif;
            touch-action: none;
        }
        #gameCanvas {
            background-color: #87CEEB;
            display: block;
            margin: 0 auto;
            max-width: 100%;
            max-height: 100vh;
        }
        #gameContainer {
            position: relative;
            width: 100%;
            height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }
        #startScreen, #gameOverScreen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            z-index: 10;
        }
        #gameOverScreen {
            display: none;
        }
        button {
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 5px;
        }
        .scoreBoard {
            position: absolute;
            top: 10px;
            left: 10px;
            color: white;
            font-size: 24px;
            z-index: 5;
            text-shadow: 2px 2px 4px black;
        }
        .comboText {
            position: absolute;
            color: yellow;
            font-size: 36px;
            font-weight: bold;
            text-shadow: 2px 2px 4px black;
            opacity: 1;
            transition: opacity 1s, transform 1s;
            z-index: 5;
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <div id="startScreen">
            <h1>Fruit Ninja Web Demo</h1>
            <p>Slice fruits with your mouse or finger to score points!</p>
            <p>Avoid bombs or you'll lose!</p>
            <button id="startButton">Start Game</button>
        </div>
        
        <canvas id="gameCanvas"></canvas>
        
        <div id="gameOverScreen">
            <h1>Game Over</h1>
            <p>Your Score: <span id="finalScore">0</span></p>
            <button id="restartButton">Play Again</button>
        </div>
        
        <div class="scoreBoard">Score: <span id="scoreDisplay">0</span></div>
    </div>

    <script>
        // Game variables
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const startScreen = document.getElementById('startScreen');
        const gameOverScreen = document.getElementById('gameOverScreen');
        const startButton = document.getElementById('startButton');
        const restartButton = document.getElementById('restartButton');
        const scoreDisplay = document.getElementById('scoreDisplay');
        const finalScore = document.getElementById('finalScore');
        
        let score = 0;
        let gameActive = false;
        let gameObjects = [];
        let sliceTrail = [];
        let sliceActive = false;
        let lastTimestamp = 0;
        let spawnTimer = 0;
        let comboCount = 0;
        let comboTimer = 0;
        
        // Resize canvas
        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        }
        
        // Initialize game
        function initGame() {
            resizeCanvas();
            score = 0;
            gameObjects = [];
            sliceTrail = [];
            sliceActive = false;
            scoreDisplay.textContent = score;
            gameActive = true;
            lastTimestamp = 0;
            spawnTimer = 0;
            
            // Start animation
            requestAnimationFrame(gameLoop);
        }
        
        // Game objects classes
        class GameObject {
            constructor(x, y, type) {
                this.x = x;
                this.y = y;
                this.type = type;
                this.sliced = false;
                this.velocityX = Math.random() * 8 - 4;
                this.velocityY = -15 - Math.random() * 5;
                this.gravity = 0.5;
                this.rotation = 0;
                this.rotationSpeed = Math.random() * 0.1 - 0.05;
                this.size = 40 + Math.random() * 20;
                
                if (this.type === 'banana') {
                    this.color = '#FFD700';
                } else if (this.type === 'bomb') {
                    this.color = '#333';
                    this.size = 30 + Math.random() * 10;
                } else if (this.type === 'pomegranate') {
                    this.color = '#FF4500';
                    this.size = 50 + Math.random() * 10;
                } else if (this.type === 'watermelon') {
                    this.color = '#32CD32';
                    this.size = 60 + Math.random() * 10;
                } else {
                    // Default apple
                    this.color = '#FF0000';
                }
                
                this.slicedColor1 = this.color;
                this.slicedColor2 = this.type === 'watermelon' ? '#FF6347' : this.color;
                
                // For slice animation
                this.sliceAngle = 0;
                this.slicePart1 = { x: 0, y: 0, vx: 0, vy: 0, rotation: 0 };
                this.slicePart2 = { x: 0, y: 0, vx: 0, vy: 0, rotation: 0 };
            }
            
            update() {
                if (this.sliced) {
                    // Update sliced parts
                    this.slicePart1.x += this.slicePart1.vx;
                    this.slicePart1.y += this.slicePart1.vy;
                    this.slicePart1.vy += this.gravity;
                    this.slicePart1.rotation += 0.05;
                    
                    this.slicePart2.x += this.slicePart2.vx;
                    this.slicePart2.y += this.slicePart2.vy;
                    this.slicePart2.vy += this.gravity;
                    this.slicePart2.rotation += 0.05;
                    
                    return this.slicePart1.y < canvas.height && this.slicePart2.y < canvas.height;
                } else {
                    // Update normal object
                    this.x += this.velocityX;
                    this.y += this.velocityY;
                    this.velocityY += this.gravity;
                    this.rotation += this.rotationSpeed;
                    
                    return this.y < canvas.height + 100;
                }
            }
            
            draw() {
                ctx.save();
                
                if (this.sliced) {
                    // Draw first slice part
                    ctx.save();
                    ctx.translate(this.slicePart1.x, this.slicePart1.y);
                    ctx.rotate(this.slicePart1.rotation);
                    
                    ctx.beginPath();
                    ctx.arc(0, 0, this.size / 2, 0, Math.PI, false);
                    ctx.fillStyle = this.slicedColor1;
                    ctx.fill();
                    
                    if (this.type === 'watermelon') {
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2 - 5, 0, Math.PI, false);
                        ctx.fillStyle = this.slicedColor2;
                        ctx.fill();
                    }
                    ctx.restore();
                    
                    // Draw second slice part
                    ctx.save();
                    ctx.translate(this.slicePart2.x, this.slicePart2.y);
                    ctx.rotate(this.slicePart2.rotation);
                    
                    ctx.beginPath();
                    ctx.arc(0, 0, this.size / 2, Math.PI, 2 * Math.PI, false);
                    ctx.fillStyle = this.slicedColor1;
                    ctx.fill();
                    
                    if (this.type === 'watermelon') {
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2 - 5, Math.PI, 2 * Math.PI, false);
                        ctx.fillStyle = this.slicedColor2;
                        ctx.fill();
                    }
                    ctx.restore();
                } else {
                    // Draw normal object
                    ctx.translate(this.x, this.y);
                    ctx.rotate(this.rotation);
                    
                    if (this.type === 'bomb') {
                        // Draw bomb
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2, 0, 2 * Math.PI);
                        ctx.fillStyle = this.color;
                        ctx.fill();
                        
                        // Draw fuse
                        ctx.beginPath();
                        ctx.moveTo(0, -this.size / 2);
                        ctx.quadraticCurveTo(10, -this.size / 2 - 15, 20, -this.size / 2 - 10);
                        ctx.lineWidth = 3;
                        ctx.strokeStyle = '#8B4513';
                        ctx.stroke();
                    } else if (this.type === 'banana') {
                        // Draw banana
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2, 0.3 * Math.PI, 1.7 * Math.PI);
                        ctx.lineWidth = this.size / 2;
                        ctx.strokeStyle = this.color;
                        ctx.stroke();
                    } else if (this.type === 'watermelon') {
                        // Draw watermelon
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2, 0, 2 * Math.PI);
                        ctx.fillStyle = '#32CD32';
                        ctx.fill();
                        
                        // Inner part
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2 - 5, 0, 2 * Math.PI);
                        ctx.fillStyle = '#FF6347';
                        ctx.fill();
                        
                        // Seeds
                        ctx.fillStyle = 'black';
                        for (let i = 0; i < 8; i++) {
                            const angle = i * (Math.PI / 4);
                            const distance = this.size / 4;
                            ctx.beginPath();
                            ctx.ellipse(
                                Math.cos(angle) * distance,
                                Math.sin(angle) * distance,
                                3, 5, angle, 0, 2 * Math.PI
                            );
                            ctx.fill();
                        }
                    } else if (this.type === 'pomegranate') {
                        // Draw pomegranate
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2, 0, 2 * Math.PI);
                        ctx.fillStyle = this.color;
                        ctx.fill();
                        
                        // Crown
                        ctx.beginPath();
                        ctx.moveTo(-10, -this.size / 2);
                        ctx.lineTo(10, -this.size / 2);
                        ctx.lineTo(0, -this.size / 2 - 10);
                        ctx.closePath();
                        ctx.fillStyle = '#8B4513';
                        ctx.fill();
                    } else {
                        // Draw apple
                        ctx.beginPath();
                        ctx.arc(0, 0, this.size / 2, 0, 2 * Math.PI);
                        ctx.fillStyle = this.color;
                        ctx.fill();
                        
                        // Stem
                        ctx.beginPath();
                        ctx.moveTo(0, -this.size / 2);
                        ctx.lineTo(0, -this.size / 2 - 7);
                        ctx.lineWidth = 3;
                        ctx.strokeStyle = '#8B4513';
                        ctx.stroke();
                    }
                }
                
                ctx.restore();
            }
            
            checkSlice(slicePath) {
                if (this.sliced) return false;
                
                // Check if the slice path intersects with the object
                for (let i = 1; i < slicePath.length; i++) {
                    const x1 = slicePath[i-1].x;
                    const y1 = slicePath[i-1].y;
                    const x2 = slicePath[i].x;
                    const y2 = slicePath[i].y;
                    
                    // Calculate distance from line segment to center of object
                    const distance = distToSegment(this.x, this.y, x1, y1, x2, y2);
                    
                    if (distance < this.size / 2) {
                        // Calculate slice angle
                        this.sliceAngle = Math.atan2(y2 - y1, x2 - x1);
                        
                        // Set the slice parts
                        this.slicePart1.x = this.x;
                        this.slicePart1.y = this.y;
                        this.slicePart1.vx = this.velocityX - 1 + Math.random() * 2;
                        this.slicePart1.vy = this.velocityY - 2;
                        
                        this.slicePart2.x = this.x;
                        this.slicePart2.y = this.y;
                        this.slicePart2.vx = this.velocityX + 1 + Math.random() * 2;
                        this.slicePart2.vy = this.velocityY - 2;
                        
                        this.sliced = true;
                        
                        // Handle special fruits
                        if (this.type === 'bomb') {
                            return 'bomb';
                        } else if (this.type === 'banana') {
                            return 'banana';
                        } else if (this.type === 'pomegranate') {
                            return 'pomegranate';
                        } else {
                            return 'fruit';
                        }
                    }
                }
                
                return false;
            }
        }
        
        // Helper function to calculate distance from point to line segment
        function sqr(x) { return x * x; }
        function dist2(v, w) { return sqr(v.x - w.x) + sqr(v.y - w.y); }
        
        function distToSegmentSquared(p, v, w) {
            const l2 = dist2(v, w);
            if (l2 === 0) return dist2(p, v);
            
            let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
            t = Math.max(0, Math.min(1, t));
            
            return dist2(p, {
                x: v.x + t * (w.x - v.x),
                y: v.y + t * (w.y - v.y)
            });
        }
        
        function distToSegment(px, py, x1, y1, x2, y2) {
            return Math.sqrt(distToSegmentSquared(
                {x: px, y: py}, 
                {x: x1, y: y1}, 
                {x: x2, y: y2}
            ));
        }
        
        // Spawn new game objects
        function spawnObjects() {
            const types = ['apple', 'watermelon', 'banana', 'pomegranate'];
            const x = Math.random() * canvas.width;
            const y = canvas.height + 20;
            
            // 20% chance to spawn a bomb
            if (Math.random() < 0.2) {
                gameObjects.push(new GameObject(x, y, 'bomb'));
            } else {
                const type = types[Math.floor(Math.random() * types.length)];
                gameObjects.push(new GameObject(x, y, type));
            }
        }
        
        // Show combo text animation
        function showComboText(count) {
            const comboText = document.createElement('div');
            comboText.className = 'comboText';
            comboText.textContent = `COMBO x${count}!`;
            comboText.style.left = `${canvas.width / 2 - 100}px`;
            comboText.style.top = `${canvas.height / 2 - 50}px`;
            
            document.getElementById('gameContainer').appendChild(comboText);
            
            setTimeout(() => {
                comboText.style.opacity = '0';
                comboText.style.transform = 'translateY(-50px)';
                
                setTimeout(() => {
                    comboText.remove();
                }, 1000);
            }, 10);
        }
        
        // Spawn small fruits (for pomegranate effect)
        function spawnSmallFruits(x, y) {
            const count = 5 + Math.floor(Math.random() * 5);
            
            for (let i = 0; i < count; i++) {
                const fruit = new GameObject(x, y, 'apple');
                fruit.size = 15 + Math.random() * 10;
                fruit.velocityX = Math.random() * 10 - 5;
                fruit.velocityY = -10 - Math.random() * 5;
                gameObjects.push(fruit);
            }
        }
        
        // Game loop
        function gameLoop(timestamp) {
            if (!gameActive) return;
            
            // Calculate delta time
            const deltaTime = timestamp - lastTimestamp;
            lastTimestamp = timestamp;
            
            // Clear canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // Spawn objects
            spawnTimer += deltaTime;
            if (spawnTimer > 1000) {
                spawnObjects();
                spawnTimer = 0;
            }
            
            // Draw slice trail
            if (sliceActive && sliceTrail.length > 1) {
                ctx.beginPath();
                ctx.moveTo(sliceTrail[0].x, sliceTrail[0].y);
                
                for (let i = 1; i < sliceTrail.length; i++) {
                    ctx.lineTo(sliceTrail[i].x, sliceTrail[i].y);
                }
                
                ctx.strokeStyle = 'white';
                ctx.lineWidth = 5;
                ctx.lineCap = 'round';
                ctx.lineJoin = 'round';
                ctx.stroke();
                
                // Trail glow effect
                ctx.shadowColor = '#FFF';
                ctx.shadowBlur = 15;
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
                ctx.lineWidth = 15;
                ctx.stroke();
                ctx.shadowBlur = 0;
            }
            
            // Update and draw game objects
            let fruitSlicedThisFrame = 0;
            
            for (let i = gameObjects.length - 1; i >= 0; i--) {
                const obj = gameObjects[i];
                
                // Check for slicing
                if (sliceActive && sliceTrail.length > 3 && !obj.sliced) {
                    const sliceResult = obj.checkSlice(sliceTrail);
                    
                    if (sliceResult) {
                        if (sliceResult === 'bomb') {
                            // Game over if bomb is sliced
                            gameActive = false;
                            finalScore.textContent = score;
                            gameOverScreen.style.display = 'flex';
                            return;
                        } else {
                            fruitSlicedThisFrame++;
                            
                            if (sliceResult === 'fruit') {
                                score += 10;
                            } else if (sliceResult === 'banana') {
                                // Banana gives double points for a short time
                                score += 20;
                            } else if (sliceResult === 'pomegranate') {
                                score += 30;
                                // Spawn small fruits
                                spawnSmallFruits(obj.x, obj.y);
                            }
                            
                            scoreDisplay.textContent = score;
                        }
                    }
                }
                
                const isVisible = obj.update();
                
                if (isVisible) {
                    obj.draw();
                } else {
                    gameObjects.splice(i, 1);
                }
            }
            
            // Handle combo
            if (fruitSlicedThisFrame > 1) {
                comboCount = fruitSlicedThisFrame;
                comboTimer = 0;
                
                // Add combo bonus
                const comboBonus = comboCount * 5;
                score += comboBonus;
                scoreDisplay.textContent = score;
                
                // Show combo text
                showComboText(comboCount);
            } else if (fruitSlicedThisFrame === 1) {
                comboTimer += deltaTime;
                
                if (comboTimer > 500) {
                    comboCount = 0;
                }
            }
            
            // Reduce slice trail gradually
            if (sliceTrail.length > 0 && !sliceActive) {
                sliceTrail.shift();
            }
            
            // Continue animation
            requestAnimationFrame(gameLoop);
        }
        
        // Event listeners for mouse/touch
        canvas.addEventListener('mousedown', (e) => {
            sliceActive = true;
            sliceTrail = [];
            sliceTrail.push({
                x: e.clientX,
                y: e.clientY
            });
        });
        
        canvas.addEventListener('mousemove', (e) => {
            if (sliceActive) {
                sliceTrail.push({
                    x: e.clientX,
                    y: e.clientY
                });
                
                // Limit trail length
                if (sliceTrail.length > 20) {
                    sliceTrail.shift();
                }
            }
        });
        
        canvas.addEventListener('mouseup', () => {
            sliceActive = false;
        });
        
        canvas.addEventListener('mouseleave', () => {
            sliceActive = false;
        });
        
        // Touch events
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            sliceActive = true;
            sliceTrail = [];
            sliceTrail.push({
                x: e.touches[0].clientX,
                y: e.touches[0].clientY
            });
        });
        
        canvas.addEventListener('touchmove', (e) => {
            e.preventDefault();
            if (sliceActive) {
                sliceTrail.push({
                    x: e.touches[0].clientX,
                    y: e.touches[0].clientY
                });
                
                // Limit trail length
                if (sliceTrail.length > 20) {
                    sliceTrail.shift();
                }
            }
        });
        
        canvas.addEventListener('touchend', (e) => {
            e.preventDefault();
            sliceActive = false;
        });
        
        // Button event listeners
        startButton.addEventListener('click', () => {
            startScreen.style.display = 'none';
            initGame();
        });
        
        restartButton.addEventListener('click', () => {
            gameOverScreen.style.display = 'none';
            initGame();
        });
        
        // Handle window resize
        window.addEventListener('resize', resizeCanvas);
        
        // Initial setup
        resizeCanvas();
    </script>
</body>
</html>