跳动的小球edit icon

作者:
邓朝元
Fork(复制)
下载
嵌入
BUG反馈
index.html
现在支持上传本地图片了!
index.html
            
            <!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>真实弹跳小球模拟器</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            background-color: #14141e;
            font-family: Arial, sans-serif;
            overflow: hidden;
        }
        
        #gameCanvas {
            display: block;
            background-color: #14141e;
        }
        
        .controls {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 100;
        }
        
        .btn {
            background: linear-gradient(45deg, #3c3c4e, #2a2a3a);
            color: #f0f0f0;
            border: 2px solid #ffffff;
            border-radius: 10px;
            padding: 12px 20px;
            margin: 8px;
            cursor: pointer;
            font-size: 16px;
            font-weight: bold;
            transition: all 0.3s;
            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
        }
        
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0,0,0,0.4);
        }
        
        .random-btn {
            background: linear-gradient(45deg, #3c8c50, #2a6a3a);
        }
        
        .clear-btn {
            background: linear-gradient(45deg, #dc3c3c, #a02a2a);
        }
        
        .info {
            position: absolute;
            top: 20px;
            right: 20px;
            color: #9696a0;
            font-size: 14px;
            text-align: right;
            z-index: 100;
        }
        
        .instructions {
            position: absolute;
            bottom: 20px;
            left: 20px;
            color: #9696a0;
            font-size: 14px;
            z-index: 100;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas"></canvas>
    
    <div class="controls">
        <button class="btn random-btn" id="randomBtn">随机发射</button>
        <button class="btn clear-btn" id="clearBtn">清除所有</button>
    </div>
    
    <div class="info">
        小球数量: <span id="ballCount">1</span><br>
        按住鼠标拖拽发射新球
    </div>
    
    <div class="instructions">
        操作说明:<br>
        • 拖拽鼠标: 发射新球<br>
        • 随机发射按钮: 随机位置发射<br>
        • 清除所有按钮: 移除所有球
    </div>

    <script>
        class Ball {
            constructor(x, y, vx = 0, vy = 0) {
                this.x = x;
                this.y = y;
                this.vx = vx;
                this.vy = vy;
                this.radius = 20;
                this.elasticity = 0.85;  // 弹性系数
                this.friction = 0.995;   // 空气阻力
                this.gravity = 0.5;      // 重力
                this.trail = [];         // 轨迹点
                this.maxTrail = 10;      // 最大轨迹点数
            }
            
            update() {
                // 应用重力
                this.vy += this.gravity;
                
                // 应用空气阻力
                this.vx *= this.friction;
                this.vy *= this.friction;
                
                // 更新位置
                this.x += this.vx;
                this.y += this.vy;
                
                // 添加轨迹点
                this.trail.push({x: this.x, y: this.y});
                if (this.trail.length > this.maxTrail) {
                    this.trail.shift();
                }
                
                // 边界碰撞检测
                if (this.x - this.radius <= 0) {
                    this.x = this.radius;
                    this.vx = -this.vx * this.elasticity;
                } else if (this.x + this.radius >= canvas.width) {
                    this.x = canvas.width - this.radius;
                    this.vx = -this.vx * this.elasticity;
                }
                
                if (this.y - this.radius <= 0) {
                    this.y = this.radius;
                    this.vy = -this.vy * this.elasticity;
                } else if (this.y + this.radius >= canvas.height) {
                    this.y = canvas.height - this.radius;
                    this.vy = -this.vy * this.elasticity;
                }
            }
            
            draw(ctx) {
                // 绘制轨迹
                for (let i = 0; i < this.trail.length; i++) {
                    const alpha = i / this.trail.length;
                    const trailRadius = this.radius * alpha * 0.5;
                    const trailColor = `rgba(0, ${Math.floor(180 * alpha)}, ${Math.floor(80 * alpha)}, ${alpha * 0.6})`;
                    
                    ctx.beginPath();
                    ctx.arc(this.trail[i].x, this.trail[i].y, trailRadius, 0, Math.PI * 2);
                    ctx.fillStyle = trailColor;
                    ctx.fill();
                }
                
                // 绘制主球体
                const gradient = ctx.createRadialGradient(
                    this.x - this.radius/3, this.y - this.radius/3, 0,
                    this.x, this.y, this.radius
                );
                gradient.addColorStop(0, '#64ffba');  // 高光
                gradient.addColorStop(0.7, '#00b450'); // 主色
                gradient.addColorStop(1, '#006432');   // 暗部
                
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                ctx.fillStyle = gradient;
                ctx.fill();
                
                // 绘制球体边缘高光
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                ctx.strokeStyle = 'rgba(100, 255, 186, 0.3)';
                ctx.lineWidth = 2;
                ctx.stroke();
            }
        }
        
        class Launcher {
            constructor() {
                this.startPos = null;
                this.endPos = null;
                this.active = false;
                this.power = 0;
            }
            
            start(pos) {
                this.startPos = pos;
                this.active = true;
            }
            
            update(pos) {
                if (this.active) {
                    this.endPos = pos;
                    // 计算力度
                    const dx = this.startPos.x - this.endPos.x;
                    const dy = this.startPos.y - this.endPos.y;
                    this.power = Math.min(Math.sqrt(dx*dx + dy*dy) / 10, 20);
                }
            }
            
            end() {
                if (this.active && this.startPos && this.endPos) {
                    // 计算发射速度
                    const dx = (this.startPos.x - this.endPos.x) / 5;
                    const dy = (this.startPos.y - this.endPos.y) / 5;
                    const ball = new Ball(this.startPos.x, this.startPos.y, dx, dy);
                    this.active = false;
                    this.startPos = null;
                    this.endPos = null;
                    return ball;
                }
                this.active = false;
                this.startPos = null;
                this.endPos = null;
                return null;
            }
            
            draw(ctx) {
                if (this.active && this.startPos && this.endPos) {
                    // 绘制瞄准线
                    ctx.beginPath();
                    ctx.moveTo(this.startPos.x, this.startPos.y);
                    ctx.lineTo(this.endPos.x, this.endPos.y);
                    ctx.strokeStyle = 'rgba(255, 100, 100, 0.8)';
                    ctx.lineWidth = 3;
                    ctx.stroke();
                    
                    // 绘制力度指示器
                    const angle = Math.atan2(this.endPos.y - this.startPos.y, this.endPos.x - this.startPos.x);
                    const endIndicatorX = this.startPos.x + Math.cos(angle) * this.power * 5;
                    const endIndicatorY = this.startPos.y + Math.sin(angle) * this.power * 5;
                    
                    ctx.beginPath();
                    ctx.moveTo(this.startPos.x, this.startPos.y);
                    ctx.lineTo(endIndicatorX, endIndicatorY);
                    ctx.strokeStyle = 'rgba(100, 255, 100, 0.8)';
                    ctx.lineWidth = 2;
                    ctx.stroke();
                    
                    // 绘制起始点
                    ctx.beginPath();
                    ctx.arc(this.startPos.x, this.startPos.y, 8, 0, Math.PI * 2);
                    ctx.fillStyle = 'rgba(255, 255, 100, 0.8)';
                    ctx.fill();
                }
            }
        }
        
        // 初始化
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const ballCountElement = document.getElementById('ballCount');
        const randomBtn = document.getElementById('randomBtn');
        const clearBtn = document.getElementById('clearBtn');
        
        // 设置画布大小
        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        }
        
        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);
        
        // 创建初始小球
        let balls = [new Ball(canvas.width/2, canvas.height/2, 
                             (Math.random() - 0.5) * 10, (Math.random() - 0.5) * 10)];
        
        const launcher = new Launcher();
        
        // 绘制网格背景
        function drawGrid() {
            ctx.strokeStyle = 'rgba(60, 60, 70, 0.3)';
            ctx.lineWidth = 1;
            
            // 垂直线
            for (let x = 0; x < canvas.width; x += 50) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, canvas.height);
                ctx.stroke();
            }
            
            // 水平线
            for (let y = 0; y < canvas.height; y += 50) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(canvas.width, y);
                ctx.stroke();
            }
        }
        
        // 游戏循环
        function gameLoop() {
            // 清空画布
            ctx.fillStyle = '#14141e';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // 绘制网格
            drawGrid();
            
            // 更新和绘制所有小球
            for (let i = balls.length - 1; i >= 0; i--) {
                balls[i].update();
                balls[i].draw(ctx);
            }
            
            // 绘制发射器
            launcher.draw(ctx);
            
            // 更新球数显示
            ballCountElement.textContent = balls.length;
            
            requestAnimationFrame(gameLoop);
        }
        
        // 事件监听
        canvas.addEventListener('mousedown', (e) => {
            const rect = canvas.getBoundingClientRect();
            const mousePos = {
                x: e.clientX - rect.left,
                y: e.clientY - rect.top
            };
            
            // 检查是否点击了按钮
            const buttons = document.querySelectorAll('.btn');
            let clickedButton = false;
            buttons.forEach(btn => {
                const btnRect = btn.getBoundingClientRect();
                if (e.clientX >= btnRect.left && e.clientX <= btnRect.right &&
                    e.clientY >= btnRect.top && e.clientY <= btnRect.bottom) {
                    clickedButton = true;
                }
            });
            
            if (!clickedButton) {
                launcher.start(mousePos);
            }
        });
        
        canvas.addEventListener('mousemove', (e) => {
            const rect = canvas.getBoundingClientRect();
            const mousePos = {
                x: e.clientX - rect.left,
                y: e.clientY - rect.top
            };
            launcher.update(mousePos);
        });
        
        canvas.addEventListener('mouseup', (e) => {
            const newBall = launcher.end();
            if (newBall) {
                balls.push(newBall);
            }
        });
        
        // 按钮事件
        randomBtn.addEventListener('click', () => {
            const newBall = new Ball(
                Math.random() * (canvas.width - 200) + 100,
                Math.random() * (canvas.height - 200) + 100,
                (Math.random() - 0.5) * 16,
                (Math.random() - 0.5) * 16
            );
            balls.push(newBall);
        });
        
        clearBtn.addEventListener('click', () => {
            balls = [];
        });
        
        // 启动游戏循环
        gameLoop();
    </script>
</body>
</html>
        
编辑器加载中
预览
控制台