1
已解决
file:///C:/Users/lx/Desktop/%E6%81%92%E6%98%9F%E6%A8%A1%E6%8B%9F%E5%99%A8v11.html
陆梓骞在2026-07-10 08:59:51追加了内容
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>引力模拟器 自定义直径+颜色弹窗版</title>
<**>
*{margin:0;padding:0;overflow:hidden;background:#000;}
canvas{display:block;}
.info{position:fixed;top:15px;left:15px;color:#fff;font-size:16px;font-family:微软雅黑;z-index:99;}
/* 自定义弹窗样式 */
.modalMask{
position:fixed;top:0;left:0;width:100vw;height:100vh;
background:rgba(0,0,0,0.7);z-index:9999;display:flex;
align-items:center;justify-content:center;
}
.modalBox{
background:#1a1a2e;padding:24px;border-radius:10px;
min-width:320px;color:#fff;font-family:微软雅黑;
}
.modalBox h3{margin:0 0 16px 0;text-align:center;}
.row{margin:12px 0;display:flex;align-items:center;gap:10px;}
.row label{width:90px;}
.row input{padding:6px;width:180px;font-size:14px;}
.btnWrap{margin-top:20px;display:flex;gap:12px;justify-content:center;}
.btn{padding:7px 22px;border:none;border-radius:6px;cursor:pointer;font-size:14px;}
.btnOk{background:#2196F3;color:#fff;}
.btnCancel{background:#666;color:#fff;}
</**>
</head>
<body>
<div class="info">左键空白=恒星(弹窗设置直径+颜色)|右键空白=黑洞(弹窗设置直径)|左键点天体选中|R开启/关闭遥控|方向键仅操控选中天体|Z加速 X减速|C清空</div>
<canvas id="c"></canvas>
<**>
const canvas = **.getElementById('c');
const ctx = canvas.getContext('2d');
canvas.width = innerWidth;
canvas.height = innerHeight;
const G = 120;
let speed = 1;
let stars = [];
let blackHoles = [];
let selectedTarget = null;
let isControlMode = false;
const controlForce = 0.8;
const keyState = {};
// 自定义弹窗函数:返回 {diameter,color} 或 null(取消)
function openCreateModal(isStar,defaultD,defaultColor){
return new Promise(resolve=>{
// 遮罩层
const mask = **.createElement('div');
mask.className = 'modalMask';
// 弹窗内容
let colorHtml = '';
if(isStar){
colorHtml = `
<div class="row">
<label>天体颜色</label>
<input type="color" id="colorInp" value="${defaultColor}">
</div>`;
}
mask.innerHTML = `
<div class="modalBox">
<h3>${isStar ? "创建恒星" : "创建黑洞"}</h3>
<div class="row">
<label>天体直径</label>
<input type="number" id="dInp" min="4" value="${defaultD}" placeholder="最小4">
</div>
${colorHtml}
<div class="btnWrap">
<button class="btn btnOk" id="okBtn">确定</button>
<button class="btn btnCancel" id="cancelBtn">取消</button>
</div>
</div>`;
**.body.appendChild(mask);
const dInp = mask.querySelector('#dInp');
const colorInp = mask.querySelector('#colorInp');
const okBtn = mask.querySelector('#okBtn');
const cancelBtn = mask.querySelector('#cancelBtn');
// 取消
cancelBtn.onclick = ()=>{
mask.remove();
resolve(null);
};
// 确定
okBtn.onclick = ()=>{
let d = parseFloat(dInp.value);
if(isNaN(d) || d <4) d = defaultD;
let c = defaultColor;
if(isStar) c = colorInp.value;
mask.remove();
resolve({diameter:d,color:c});
};
});
}
class Star{
constructor(x,y,diameter,colorHex){
this.x = x;
this.y = y;
this.vx = (Math.random()-0.5)*1.2;
this.vy = (Math.random()-0.5)*1.2;
this.diameter = diameter;
this.r = diameter / 2;
this.m = diameter * 2.2;
this.colorHex = colorHex;
this.trail = [];
this.alive = true;
}
absorbStar(m){
this.m += m;
this.diameter = this.m / 2.2;
this.r = this.diameter / 2;
}
absorbBlackHole(m){
this.m -= m * 0.6;
if(this.m < 8) this.m = 8;
this.diameter = this.m / 2.2;
this.r = this.diameter / 2;
}
update(){
let ax=0,ay=0;
for(let s of stars){
if(s === this || !s.alive) continue;
let dx = s.x - this.x;
let dy = s.y - this.y;
let d = Math.hypot(dx, dy);
d = Math.max(d, 12);
let f = G * this.m * s.m / (d*d);
ax += f * dx / d / this.m;
ay += f * dy / d / this.m;
}
for(let bh of blackHoles){
let dx = bh.x - this.x;
let dy = bh.y - this.y;
let d = Math.hypot(dx, dy);
d = Math.max(d, 12);
let f = G * this.m * bh.m / (d*d);
ax += f * dx / d / this.m;
ay += f * dy / d / this.m;
}
if(isControlMode && selectedTarget === this){
const keys = keyState;
if(keys.ArrowUp) ay -= controlForce;
if(keys.ArrowDown) ay += controlForce;
if(keys.ArrowLeft) ax -= controlForce;
if(keys.ArrowRight) ax += controlForce;
}
this.vx += ax * speed;
this.vy += ay * speed;
this.x += this.vx * speed;
this.y += this.vy * speed;
this.trail.push({x:this.x,y:this.y,alpha:1});
if(this.trail.length>180) this.trail.shift();
this.trail.forEach(p=>p.alpha *= 0.985);
for(let s of stars){
if(s === this || !s.alive) continue;
let dis = Math.hypot(this.x - s.x, this.y - s.y);
if(dis < this.r + s.r && this.m > s.m){
this.absorbStar(s.m);
s.alive = false;
}
}
for(let bh of blackHoles){
let dis = Math.hypot(this.x - bh.x, this.y - bh.y);
if(dis < this.r + bh.r){
if(this.m > bh.m){
this.absorbBlackHole(bh.m);
bh.m = 0;
}else{
bh.absorb(this.m);
this.alive = false;
break;
}
}
}
const bounceFactor = 0.98;
if(this.x < this.r){
this.x = this.r;
this.vx *= -bounceFactor;
}
if(this.x > canvas.width - this.r){
this.x = canvas.width - this.r;
this.vx *= -bounceFactor;
}
if(this.y < this.r){
this.y = this.r;
this.vy *= -bounceFactor;
}
if(this.y > canvas.height - this.r){
this.y = canvas.height - this.r;
this.vy *= -bounceFactor;
}
}
draw(){
for(let i=1;i<this.trail.length;i++){
const p1 = this.trail[i-1];
const p2 = this.trail[i];
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.strokeStyle=this.colorHex;
ctx.globalAlpha = p2.alpha * 0.4;
ctx.stroke();
ctx.globalAlpha = 1;
}
if(selectedTarget === this){
ctx.beginPath();
ctx.arc(this.x,this.y,this.r+6,0,Math.PI*2);
ctx.strokeStyle = isControlMode ? "#00ff00" : "#ffffff";
ctx.lineWidth=2;
ctx.stroke();
}
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,Math.PI*2);
ctx.fillStyle=this.colorHex;
ctx.fill();
}
}
class BlackHole{
constructor(x,y,diameter){
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.diameter = diameter;
this.r = diameter / 2;
this.m = diameter * 8;
this.baseD = diameter;
this.color = "#000000";
this.ringColor = "#9944ff";
}
absorb(num){
this.m += num;
this.diameter = this.baseD + this.m / 12;
this.r = this.diameter / 2;
}
update(){
let ax=0,ay=0;
for(let bh of blackHoles){
if(bh === this || bh.m <= 0) continue;
let dx = bh.x - this.x;
let dy = bh.y - this.y;
let d = Math.hypot(dx, dy);
d = Math.max(d, 20);
let f = G * this.m * bh.m / (d*d);
ax += f * dx / d / this.m;
ay += f * dy / d / this.m;
}
if(isControlMode && selectedTarget === this){
const keys = keyState;
if(keys.ArrowUp) ay -= controlForce;
if(keys.ArrowDown) ay += controlForce;
if(keys.ArrowLeft) ax -= controlForce;
if(keys.ArrowRight) ax += controlForce;
}
this.vx += ax * speed;
this.vy += ay * speed;
this.x += this.vx * speed;
this.y += this.vy * speed;
for(let bh of blackHoles){
if(bh === this || bh.m <= 0) continue;
let dis = Math.hypot(this.x - bh.x, this.y - bh.y);
if(dis < this.r + bh.r && this.m > bh.m){
this.absorb(bh.m);
bh.m = 0;
}
}
const bounceFactor = 0.98;
if(this.x < this.r){
this.x = this.r;
this.vx *= -bounceFactor;
}
if(this.x > canvas.width - this.r){
this.x = canvas.width - this.r;
this.vx *= -bounceFactor;
}
if(this.y < this.r){
this.y = this.r;
this.vy *= -bounceFactor;
}
if(this.y > canvas.height - this.r){
this.y = canvas.height - this.r;
this.vy *= -bounceFactor;
}
}
draw(){
if(selectedTarget === this){
ctx.beginPath();
ctx.arc(this.x,this.y,this.r+8,0,Math.PI*2);
ctx.strokeStyle = isControlMode ? "#00ff00" : "#ffffff";
ctx.lineWidth=2;
ctx.stroke();
}
ctx.beginPath();
ctx.arc(this.x,this.y,this.r+14,0,Math.PI*2);
ctx.fillStyle = this.ringColor;
ctx.globalAlpha=0.28;
ctx.fill();
ctx.globalAlpha=1;
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,Math.PI*2);
ctx.fillStyle=this.color;
ctx.fill();
}
}
**.addEventListener('keydown',e=>{
keyState[e.key] = true;
if(e.key.toLowerCase()==='c'){
stars = [];
blackHoles = [];
selectedTarget = null;
isControlMode = false;
}
if(e.key.toLowerCase()==='r'){
if(selectedTarget) isControlMode = !isControlMode;
}
// Z加速 X减速
if(e.key.toLowerCase()==='z') speed = Math.min(1.5, speed + 0.1);
if(e.key.toLowerCase()==='x') speed = Math.max(0.2, speed - 0.1);
});
**.addEventListener('keyup',e=>{
keyState[e.key] = false;
});
canvas.addEventListener('mousedown',async e=>{
const mx = e.clientX;
const my = e.clientY;
if(e.button === 0){
// 左键:判断是否点击天体
let hit = null;
for(let bh of blackHoles){
if(bh.m <= 0) continue;
let dis = Math.hypot(mx - bh.x, my - bh.y);
if(dis < bh.r + 10){
hit = bh;
break;
}
}
if(!hit){
for(let s of stars){
if(!s.alive) continue;
let dis = Math.hypot(mx - s.x, my - s.y);
if(dis < s.r + 10){
hit = s;
break;
}
}
}
if(hit){
selectedTarget = hit;
isControlMode = false;
}else{
// 空白创建恒星,弹窗直径+颜色
const res = await openCreateModal(true,30,"#ffdd66");
if(res){
stars.push(new Star(mx, my, res.diameter, res.color));
}
}
}else if(e.button === 2){
// 右键创建黑洞,仅直径
const res = await openCreateModal(false,36,"");
if(res){
blackHoles.push(new BlackHole(mx, my, res.diameter));
}
}
});
canvas.oncontextmenu = e=>e.preventDefault();
function loop(){
ctx.fillStyle='rgba(0,0,12,0.12)';
ctx.fillRect(0,0,canvas.width,canvas.height);
stars = stars.filter(s=>s.alive);
blackHoles = blackHoles.filter(b=>b.m>0);
blackHoles.forEach(bh=>{bh.update();bh.draw();});
stars.forEach(s=>{s.update();s.draw();});
requestAnimationFrame(loop);
}
loop();
window.onresize=()=>{
canvas.width=innerWidth;
canvas.height=innerHeight;
}
</**>
</body>
</html>
陆梓骞在2026-07-10 09:52:09追加了内容
打开教程:
桌面--》右键--》新建--》文本文档--》打开文本文档--》复制上面的代码--》粘贴到文本文档--》文件--》另存为--》保存名为 恒星模拟器v11.html --》
点击

改为所有文件
保存到桌面

点进去就可以使用了
0
0
0
0
0
0
0
0
