<!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>
index.html
index.html