真实物理小球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: #0a0a14;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
            color: #e0e0e0;
        }
        
        #gameCanvas {
            display: block;
            background-color: #0a0a14;
        }
        
        .controls {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 100;
        }
        
        .btn {
            background: linear-gradient(45deg, #2a2a3a, #1a1a2a);
            color: #e0e0e0;
            border: 2px solid #4a4a5a;
            border-radius: 12px;
            padding: 12px 20px;
            margin: 8px;
            cursor: pointer;
            font-size: 16px;
            font-weight: bold;
            transition: all 0.3s;
            box-shadow: 0 4px 12px rgba(0,0,0,0.4);
            backdrop-filter: blur(10px);
        }
        
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 16px rgba(0,0,0,0.5);
            border-color: #5a5a6a;
        }
        
        .random-btn {
            background: linear-gradient(45deg, #2e7d32, #1b5e20);
        }
        
        .clear-btn {
            background: linear-gradient(45deg, #c62828, #b71c1c);
        }
        
        .settings-btn {
            background: linear-gradient(45deg, #1565c0, #0d47a1);
        }
        
        .info {
            position: absolute;
            top: 20px;
            right: 20px;
            color: #a0a0b0;
            font-size: 14px;
            text-align: right;
            z-index: 100;
            background: rgba(15, 15, 25, 0.8);
            padding: 12px;
            border-radius: 10px;
            border: 1px solid #2a2a3a;
        }
        
        .instructions {
            position: absolute;
            bottom: 20px;
            left: 20px;
            color: #a0a0b0;
            font-size: 14px;
            z-index: 100;
            background: rgba(15, 15, 25, 0.8);
            padding: 15px;
            border-radius: 12px;
            border: 1px solid #2a2a3a;
            max-width: 300px;
            backdrop-filter: blur(5px);
        }
        
        .settings-panel {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(20, 20, 35, 0.95);
            border: 2px solid #3a3a4a;
            border-radius: 18px;
            padding: 30px;
            z-index: 200;
            box-shadow: 0 15px 40px rgba(0,0,0,0.6);
            min-width: 450px;
            backdrop-filter: blur(15px);
        }
        
        .settings-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 25px;
            padding-bottom: 15px;
            border-bottom: 2px solid #3a3a4a;
        }
        
        .settings-title {
            font-size: 26px;
            font-weight: bold;
            color: #64b5f6;
            text-shadow: 0 2px 4px rgba(0,0,0,0.3);
        }
        
        .close-btn {
            background: none;
            border: none;
            color: #ff6b6b;
            font-size: 28px;
            cursor: pointer;
            padding: 5px 10px;
            border-radius: 8px;
            transition: all 0.2s;
        }
        
        .close-btn:hover {
            background: rgba(255, 107, 107, 0.2);
            transform: scale(1.1);
        }
        
        .setting-group {
            margin-bottom: 22px;
        }
        
        .setting-label {
            display: block;
            margin-bottom: 10px;
            font-weight: 600;
            color: #c0c0d0;
            font-size: 15px;
        }
        
        .setting-input {
            width: 100%;
            padding: 12px;
            border-radius: 10px;
            border: 2px solid #3a3a4a;
            background: #1a1a2a;
            color: #e0e0e0;
            font-size: 15px;
            transition: all 0.3s;
        }
        
        .setting-input:focus {
            outline: none;
            border-color: #64b5f6;
            box-shadow: 0 0 10px rgba(100, 181, 246, 0.3);
        }
        
        .checkbox-group {
            display: flex;
            align-items: center;
            margin-bottom: 18px;
            padding: 10px;
            border-radius: 10px;
            background: rgba(30, 30, 45, 0.5);
            transition: background 0.3s;
        }
        
        .checkbox-group:hover {
            background: rgba(40, 40, 55, 0.7);
        }
        
        .checkbox-group input {
            margin-right: 12px;
            width: 20px;
            height: 20px;
            cursor: pointer;
        }
        
        .save-btn {
            width: 100%;
            padding: 16px;
            background: linear-gradient(45deg, #2196f3, #1976d2);
            border: none;
            border-radius: 12px;
            color: white;
            font-size: 18px;
            font-weight: bold;
            cursor: pointer;
            margin-top: 20px;
            transition: all 0.3s;
            box-shadow: 0 6px 20px rgba(33, 150, 243, 0.3);
        }
        
        .save-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(33, 150, 243, 0.4);
        }
        
        .overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            z-index: 150;
            display: none;
        }
        
        .color-preview {
            display: inline-block;
            width: 24px;
            height: 24px;
            border-radius: 50%;
            margin-left: 12px;
            border: 2px solid #4a4a5a;
            vertical-align: middle;
            box-shadow: 0 2px 6px rgba(0,0,0,0.3);
        }
        
        .value-display {
            float: right;
            color: #80b0ff;
            font-weight: bold;
            background: rgba(40, 40, 60, 0.6);
            padding: 2px 8px;
            border-radius: 6px;
            font-size: 14px;
        }
        
        .theme-selector {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
            margin-top: 10px;
        }
        
        .theme-option {
            padding: 12px;
            border-radius: 8px;
            cursor: pointer;
            text-align: center;
            font-size: 13px;
            font-weight: bold;
            transition: all 0.3s;
            border: 2px solid transparent;
        }
        
        .theme-option:hover {
            transform: translateY(-2px);
            border-color: #64b5f6;
        }
        
        .theme-option.active {
            border-color: #64b5f6;
            box-shadow: 0 0 15px rgba(100, 181, 246, 0.4);
        }
        
        .theme-green { background: linear-gradient(45deg, #2e7d32, #4caf50); }
        .theme-blue { background: linear-gradient(45deg, #1565c0, #2196f3); }
        .theme-purple { background: linear-gradient(45deg, #4527a0, #673ab7); }
        .theme-red { background: linear-gradient(45deg, #c62828, #f44336); }
        .theme-orange { background: linear-gradient(45deg, #ef6c00, #ff9800); }
        .theme-teal { background: linear-gradient(45deg, #00695c, #009688); }
    </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>
        <button class="btn settings-btn" id="settingsBtn">设置</button>
    </div>
    
    <div class="info">
        小球数量: <span id="ballCount">1</span><br>
        FPS: <span id="fpsCounter">60</span>
    </div>
    
    <div class="instructions">
        操作说明:<br>
        • 拖拽鼠标: 发射新球<br>
        • 随机发射按钮: 随机位置发射<br>
        • 清除所有按钮: 移除所有球<br>
        • 设置按钮: 打开设置面板
    </div>
    
    <div class="overlay" id="overlay"></div>
    
    <div class="settings-panel" id="settingsPanel" style="display: none;">
        <div class="settings-header">
            <div class="settings-title">游戏设置</div>
            <button class="close-btn" id="closeSettings">×</button>
        </div>
        
        <div class="setting-group">
            <label class="setting-label">重力强度 <span class="value-display" id="gravityValue">0.5</span></label>
            <input type="range" class="setting-input" id="gravity" min="0" max="2" step="0.05" value="0.5">
        </div>
        
        <div class="setting-group">
            <label class="setting-label">弹性系数 <span class="value-display" id="elasticityValue">0.85</span></label>
            <input type="range" class="setting-input" id="elasticity" min="0.1" max="0.95" step="0.05" value="0.85">
        </div>
        
        <div class="setting-group">
            <label class="setting-label">空气阻力 <span class="value-display" id="frictionValue">0.995</span></label>
            <input type="range" class="setting-input" id="friction" min="0.95" max="1" step="0.001" value="0.995">
        </div>
        
        <div class="setting-group">
            <label class="setting-label">小球半径 <span class="value-display" id="ballRadiusValue">20</span></label>
            <input type="range" class="setting-input" id="ballRadius" min="8" max="50" step="1" value="20">
        </div>
        
        <div class="checkbox-group">
            <input type="checkbox" id="showTrail" checked>
            <label class="setting-label">显示轨迹</label>
        </div>
        
        <div class="checkbox-group">
            <input type="checkbox" id="randomColors" checked>
            <label class="setting-label">随机颜色</label>
        </div>
        
        <div class="checkbox-group">
            <input type="checkbox" id="gradientColors" checked>
            <label class="setting-label">渐变颜色</label>
        </div>
        
        <div class="setting-group">
            <label class="setting-label">颜色主题</label>
            <div class="theme-selector">
                <div class="theme-option theme-green active" data-theme="green">自然绿</div>
                <div class="theme-option theme-blue" data-theme="blue">海洋蓝</div>
                <div class="theme-option theme-purple" data-theme="purple">神秘紫</div>
                <div class="theme-option theme-red" data-theme="red">热情红</div>
                <div class="theme-option theme-orange" data-theme="orange">活力橙</div>
                <div class="theme-option theme-teal" data-theme="teal">清新青</div>
            </div>
        </div>
        
        <div class="setting-group">
            <label class="setting-label">背景颜色</label>
            <input type="color" class="setting-input" id="backgroundColor" value="#0a0a14">
            <span class="color-preview" id="bgColorPreview" style="background-color: #0a0a14;"></span>
        </div>
        
        <div class="setting-group">
            <label class="setting-label">默认小球颜色</label>
            <input type="color" class="setting-input" id="defaultBallColor" value="#4caf50">
            <span class="color-preview" id="ballColorPreview" style="background-color: #4caf50;"></span>
        </div>
        
        <button class="save-btn" id="saveSettings">保存设置</button>
    </div>

    <script>
        class Ball {
            constructor(x, y, vx = 0, vy = 0, color = null) {
                this.x = x;
                this.y = y;
                this.vx = vx;
                this.vy = vy;
                this.radius = settings.ballRadius;
                this.elasticity = settings.elasticity;
                this.friction = settings.friction;
                this.gravity = settings.gravity;
                this.trail = [];
                this.maxTrail = 12;
                this.color = color || this.generateColor();
                this.highlightColor = this.generateHighlightColor(this.color);
                this.shadowColor = this.generateShadowColor(this.color);
            }
            
            generateColor() {
                if (!settings.randomColors) {
                    return settings.defaultBallColor;
                }
                
                // 根据当前主题生成颜色
                const themes = {
                    green: ['#2e7d32', '#388e3c', '#43a047', '#4caf50', '#66bb6a', '#81c784', '#a5d6a7'],
                    blue: ['#1565c0', '#1976d2', '#1e88e5', '#2196f3', '#42a5f5', '#64b5f6', '#90caf9'],
                    purple: ['#4527a0', '#512da8', '#5e35b1', '#673ab7', '#7e57c2', '#9575cd', '#b39ddb'],
                    red: ['#c62828', '#d32f2f', '#e53935', '#f44336', '#ef5350', '#ff7961', '#ffab91'],
                    orange: ['#ef6c00', '#f57c00', '#fb8c00', '#ff9800', '#ffa726', '#ffb74d', '#ffcc80'],
                    teal: ['#00695c', '#00796b', '#00897b', '#009688', '#26a69a', '#4db6ac', '#80cbc4']
                };
                
                const currentTheme = themes[settings.currentTheme] || themes.green;
                return currentTheme[Math.floor(Math.random() * currentTheme.length)];
            }
            
            generateHighlightColor(baseColor) {
                const r = parseInt(baseColor.substr(1, 2), 16);
                const g = parseInt(baseColor.substr(3, 2), 16);
                const b = parseInt(baseColor.substr(5, 2), 16);
                return `rgb(${Math.min(255, r + 80)}, ${Math.min(255, g + 80)}, ${Math.min(255, b + 80)})`;
            }
            
            generateShadowColor(baseColor) {
                const r = parseInt(baseColor.substr(1, 2), 16);
                const g = parseInt(baseColor.substr(3, 2), 16);
                const b = parseInt(baseColor.substr(5, 2), 16);
                return `rgb(${Math.max(0, r - 40)}, ${Math.max(0, g - 40)}, ${Math.max(0, b - 40)})`;
            }
            
            update() {
                // 应用重力
                this.vy += this.gravity;
                
                // 应用空气阻力
                this.vx *= this.friction;
                this.vy *= this.friction;
                
                // 更新位置
                this.x += this.vx;
                this.y += this.vy;
                
                // 添加轨迹点
                if (settings.showTrail) {
                    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) {
                if (settings.showTrail) {
                    // 绘制轨迹
                    for (let i = 0; i < this.trail.length; i++) {
                        const alpha = i / this.trail.length * 0.4;
                        const trailRadius = this.radius * (i / this.trail.length) * 0.6;
                        
                        const match = this.color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
                        if (match) {
                            const r = parseInt(match[1], 16);
                            const g = parseInt(match[2], 16);
                            const b = parseInt(match[3], 16);
                            const trailColor = `rgba(${r}, ${g}, ${b}, ${alpha})`;
                            
                            ctx.beginPath();
                            ctx.arc(this.trail[i].x, this.trail[i].y, trailRadius, 0, Math.PI * 2);
                            ctx.fillStyle = trailColor;
                            ctx.fill();
                        }
                    }
                }
                
                // 绘制主球体
                if (settings.gradientColors) {
                    const gradient = ctx.createRadialGradient(
                        this.x - this.radius/3, this.y - this.radius/3, 0,
                        this.x, this.y, this.radius
                    );
                    gradient.addColorStop(0, this.highlightColor);
                    gradient.addColorStop(0.6, this.color);
                    gradient.addColorStop(1, this.shadowColor);
                    
                    ctx.beginPath();
                    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                    ctx.fillStyle = gradient;
                    ctx.fill();
                } else {
                    // 简单填充(性能更好)
                    ctx.beginPath();
                    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                    ctx.fillStyle = this.color;
                    ctx.fill();
                }
                
                // 绘制球体边缘高光
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
                ctx.lineWidth = 1.5;
                ctx.stroke();
                
                // 绘制球体顶部高光点
                const highlightGradient = ctx.createRadialGradient(
                    this.x - this.radius/4, this.y - this.radius/4, 0,
                    this.x - this.radius/4, this.y - this.radius/4, this.radius/3
                );
                highlightGradient.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
                highlightGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
                
                ctx.beginPath();
                ctx.arc(this.x - this.radius/4, this.y - this.radius/4, this.radius/3, 0, Math.PI * 2);
                ctx.fillStyle = highlightGradient;
                ctx.fill();
            }
        }
        
        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.7)';
                    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.7)';
                    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 glowGradient = ctx.createRadialGradient(
                        this.startPos.x, this.startPos.y, 0,
                        this.startPos.x, this.startPos.y, 15
                    );
                    glowGradient.addColorStop(0, 'rgba(255, 255, 100, 0.4)');
                    glowGradient.addColorStop(1, 'rgba(255, 255, 100, 0)');
                    
                    ctx.beginPath();
                    ctx.arc(this.startPos.x, this.startPos.y, 15, 0, Math.PI * 2);
                    ctx.fillStyle = glowGradient;
                    ctx.fill();
                }
            }
        }
        
        // 设置配置
        const defaultSettings = {
            gravity: 0.5,
            elasticity: 0.85,
            friction: 0.995,
            ballRadius: 20,
            showTrail: true,
            randomColors: true,
            gradientColors: true,
            backgroundColor: '#0a0a14',
            defaultBallColor: '#4caf50',
            currentTheme: 'green'
        };
        
        let settings = {...defaultSettings};
        let balls = [];
        let lastTime = 0;
        let frameCount = 0;
        let fps = 60;
        
        // 从localStorage加载设置
        function loadSettings() {
            const savedSettings = localStorage.getItem('ballSimulatorSettings');
            if (savedSettings) {
                settings = {...defaultSettings, ...JSON.parse(savedSettings)};
            }
        }
        
        // 保存设置到localStorage
        function saveSettings() {
            localStorage.setItem('ballSimulatorSettings', JSON.stringify(settings));
        }
        
        // 初始化设置
        loadSettings();
        
        // DOM元素
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const ballCountElement = document.getElementById('ballCount');
        const fpsCounterElement = document.getElementById('fpsCounter');
        const randomBtn = document.getElementById('randomBtn');
        const clearBtn = document.getElementById('clearBtn');
        const settingsBtn = document.getElementById('settingsBtn');
        const settingsPanel = document.getElementById('settingsPanel');
        const overlay = document.getElementById('overlay');
        const closeSettings = document.getElementById('closeSettings');
        const saveSettingsBtn = document.getElementById('saveSettings');
        
        // 设置面板元素
        const gravityInput = document.getElementById('gravity');
        const elasticityInput = document.getElementById('elasticity');
        const frictionInput = document.getElementById('friction');
        const ballRadiusInput = document.getElementById('ballRadius');
        const showTrailCheckbox = document.getElementById('showTrail');
        const randomColorsCheckbox = document.getElementById('randomColors');
        const gradientColorsCheckbox = document.getElementById('gradientColors');
        const backgroundColorInput = document.getElementById('backgroundColor');
        const defaultBallColorInput = document.getElementById('defaultBallColor');
        const gravityValue = document.getElementById('gravityValue');
        const elasticityValue = document.getElementById('elasticityValue');
        const frictionValue = document.getElementById('frictionValue');
        const ballRadiusValue = document.getElementById('ballRadiusValue');
        const bgColorPreview = document.getElementById('bgColorPreview');
        const ballColorPreview = document.getElementById('ballColorPreview');
        const themeOptions = document.querySelectorAll('.theme-option');
        
        // 设置画布大小
        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        }
        
        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);
        
        // 创建初始小球
        balls = [new Ball(canvas.width/2, canvas.height/2, 
                         (Math.random() - 0.5) * 8, (Math.random() - 0.5) * 8)];
        
        const launcher = new Launcher();
        
        // 绘制网格背景
        function drawGrid() {
            ctx.strokeStyle = 'rgba(40, 40, 60, 0.2)';
            ctx.lineWidth = 1;
            
            // 绘制垂直线
            for (let x = 0; x < canvas.width; x += 60) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, canvas.height);
                ctx.stroke();
            }
            
            // 绘制水平线
            for (let y = 0; y < canvas.height; y += 60) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(canvas.width, y);
                ctx.stroke();
            }
        }
        
        // FPS计算
        function updateFPS(timestamp) {
            frameCount++;
            if (timestamp >= lastTime + 1000) {
                fps = frameCount;
                frameCount = 0;
                lastTime = timestamp;
                fpsCounterElement.textContent = fps;
            }
        }
        
        // 性能优化:限制小球数量
        function optimizeBalls() {
            if (balls.length > 100) {
                // 移除最老的小球
                balls.splice(0, balls.length - 80);
            }
        }
        
        // 游戏循环
        function gameLoop(timestamp) {
            updateFPS(timestamp);
            optimizeBalls();
            
            // 清空画布
            ctx.fillStyle = settings.backgroundColor;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // 绘制网格
            drawGrid();
            
            // 更新和绘制所有小球
            for (let i = 0; i < balls.length; 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 color = settings.randomColors ? null : settings.defaultBallColor;
            const newBall = new Ball(
                Math.random() * (canvas.width - 200) + 100,
                Math.random() * (canvas.height - 200) + 100,
                (Math.random() - 0.5) * 12,
                (Math.random() - 0.5) * 12,
                color
            );
            balls.push(newBall);
        });
        
        clearBtn.addEventListener('click', () => {
            balls = [];
        });
        
        // 设置面板事件
        settingsBtn.addEventListener('click', () => {
            // 更新设置面板的值
            gravityInput.value = settings.gravity;
            elasticityInput.value = settings.elasticity;
            frictionInput.value = settings.friction;
            ballRadiusInput.value = settings.ballRadius;
            showTrailCheckbox.checked = settings.showTrail;
            randomColorsCheckbox.checked = settings.randomColors;
            gradientColorsCheckbox.checked = settings.gradientColors;
            backgroundColorInput.value = settings.backgroundColor;
            defaultBallColorInput.value = settings.defaultBallColor;
            
            gravityValue.textContent = settings.gravity;
            elasticityValue.textContent = settings.elasticity;
            frictionValue.textContent = settings.friction;
            ballRadiusValue.textContent = settings.ballRadius;
            bgColorPreview.style.backgroundColor = settings.backgroundColor;
            ballColorPreview.style.backgroundColor = settings.defaultBallColor;
            
            // 更新主题选择
            themeOptions.forEach(option => {
                option.classList.remove('active');
                if (option.dataset.theme === settings.currentTheme) {
                    option.classList.add('active');
                }
            });
            
            settingsPanel.style.display = 'block';
            overlay.style.display = 'block';
        });
        
        closeSettings.addEventListener('click', () => {
            settingsPanel.style.display = 'none';
            overlay.style.display = 'none';
        });
        
        overlay.addEventListener('click', () => {
            settingsPanel.style.display = 'none';
            overlay.style.display = 'none';
        });
        
        // 设置面板实时更新显示
        gravityInput.addEventListener('input', () => {
            gravityValue.textContent = parseFloat(gravityInput.value).toFixed(2);
        });
        
        elasticityInput.addEventListener('input', () => {
            elasticityValue.textContent = parseFloat(elasticityInput.value).toFixed(2);
        });
        
        frictionInput.addEventListener('input', () => {
            frictionValue.textContent = parseFloat(frictionInput.value).toFixed(3);
        });
        
        ballRadiusInput.addEventListener('input', () => {
            ballRadiusValue.textContent = ballRadiusInput.value;
        });
        
        backgroundColorInput.addEventListener('input', () => {
            bgColorPreview.style.backgroundColor = backgroundColorInput.value;
        });
        
        defaultBallColorInput.addEventListener('input', () => {
            ballColorPreview.style.backgroundColor = defaultBallColorInput.value;
        });
        
        // 主题选择
        themeOptions.forEach(option => {
            option.addEventListener('click', () => {
                themeOptions.forEach(opt => opt.classList.remove('active'));
                option.classList.add('active');
            });
        });
        
        // 保存设置
        saveSettingsBtn.addEventListener('click', () => {
            const activeTheme = document.querySelector('.theme-option.active');
            
            settings = {
                gravity: parseFloat(gravityInput.value),
                elasticity: parseFloat(elasticityInput.value),
                friction: parseFloat(frictionInput.value),
                ballRadius: parseInt(ballRadiusInput.value),
                showTrail: showTrailCheckbox.checked,
                randomColors: randomColorsCheckbox.checked,
                gradientColors: gradientColorsCheckbox.checked,
                backgroundColor: backgroundColorInput.value,
                defaultBallColor: defaultBallColorInput.value,
                currentTheme: activeTheme ? activeTheme.dataset.theme : 'green'
            };
            
            saveSettings();
            
            // 更新所有球的物理参数
            balls.forEach(ball => {
                ball.gravity = settings.gravity;
                ball.elasticity = settings.elasticity;
                ball.friction = settings.friction;
                ball.radius = settings.ballRadius;
            });
            
            settingsPanel.style.display = 'none';
            overlay.style.display = 'none';
        });
        
        // 启动游戏循环
        gameLoop();
    </script>
</body>
</html>

        
编辑器加载中
预览
控制台