6dc79a2c12
- 私人剪贴板新增限时直通URL生成功能 - doc_cloud_keeper新增标题复制按钮 - 新增贪蛇出动游戏工具 - 新增OSS中转直链工具 - 添加批量抽帧测试脚本
147 lines
4.7 KiB
JavaScript
147 lines
4.7 KiB
JavaScript
/* 贪蛇出动 - 世界:金币生成/拾取/磁铁/边界/相机/死亡掉币 */
|
|
(function () {
|
|
'use strict';
|
|
window.GS = window.GS || {};
|
|
const U = GS.U;
|
|
|
|
class World {
|
|
constructor() {
|
|
this.size = GS.CFG.worldSize;
|
|
this.coins = []; // {x,y,r,value,kind:0普通 1星 2大, phase}
|
|
this.grid = new U.Grid(220);
|
|
this.cam = { x: this.size / 2, y: this.size / 2, scale: 1 };
|
|
this.seed();
|
|
}
|
|
|
|
seed() {
|
|
this.coins.length = 0;
|
|
for (let i = 0; i < GS.CFG.coinCount; i++) this.coins.push(this._makeCoin());
|
|
this._rebuild();
|
|
}
|
|
|
|
_makeCoin(x, y, kind) {
|
|
const k = kind != null ? kind : (Math.random() < 0.08 ? 1 : 0);
|
|
const base = {
|
|
0: { r: 7, value: 2 },
|
|
1: { r: 12, value: 10 }, // 星币:+幸运星
|
|
2: { r: 14, value: 20 } // 大金币:尸体掉落
|
|
}[k];
|
|
return {
|
|
x: x != null ? x : U.rand(60, this.size - 60),
|
|
y: y != null ? y : U.rand(60, this.size - 60),
|
|
r: base.r, value: base.value, kind: k,
|
|
phase: U.rand(0, Math.PI * 2),
|
|
vx: 0, vy: 0
|
|
};
|
|
}
|
|
|
|
_rebuild() {
|
|
this.grid.clear();
|
|
for (const c of this.coins) this.grid.insert(c);
|
|
}
|
|
|
|
// 尸体掉币:沿身体间隔掉落
|
|
dropFromSegments(segs) {
|
|
for (let i = 0; i < segs.length; i += 2) {
|
|
const s = segs[i];
|
|
this.coins.push(this._makeCoin(
|
|
U.clamp(s.x + U.rand(-14, 14), 40, this.size - 40),
|
|
U.clamp(s.y + U.rand(-14, 14), 40, this.size - 40), 2));
|
|
}
|
|
this._rebuild();
|
|
}
|
|
|
|
// 外部新增单枚金币(加速掉落等),并重建网格索引
|
|
addCoin(x, y, kind) {
|
|
const c = this._makeCoin(
|
|
U.clamp(x, 40, this.size - 40),
|
|
U.clamp(y, 40, this.size - 40), kind || 0);
|
|
c.r = kind === 2 ? 14 : (kind === 1 ? 12 : 5);
|
|
c.value = kind === 2 ? 20 : (kind === 1 ? 10 : 1);
|
|
this.coins.push(c);
|
|
this.grid.insert(c);
|
|
return c;
|
|
}
|
|
|
|
// 宝藏屋奖励:玩家附近撒一大簇金币
|
|
treasureBurst(x, y) {
|
|
for (let i = 0; i < 70; i++) {
|
|
const a = U.rand(0, Math.PI * 2), d = U.rand(30, 320);
|
|
this.coins.push(this._makeCoin(
|
|
U.clamp(x + Math.cos(a) * d, 40, this.size - 40),
|
|
U.clamp(y + Math.sin(a) * d, 40, this.size - 40),
|
|
Math.random() < 0.25 ? 1 : (Math.random() < 0.4 ? 2 : 0)));
|
|
}
|
|
this._rebuild();
|
|
}
|
|
|
|
update(dt, snakes, now) {
|
|
let moved = false;
|
|
for (const s of snakes) {
|
|
if (s.dead) continue;
|
|
const h = s.head;
|
|
const magnet = now < s.magnetUntil;
|
|
const eatR = s.radius + 16;
|
|
const pullR = magnet ? 260 : eatR;
|
|
const near = this.grid.query(h.x, h.y, Math.max(pullR, 60));
|
|
for (const c of near) {
|
|
if (c._eaten) continue;
|
|
const d2 = U.dist2(h.x, h.y, c.x, c.y);
|
|
if (magnet && d2 < pullR * pullR) {
|
|
const d = Math.sqrt(d2) || 1;
|
|
const pull = 420 * dt;
|
|
c.x += (h.x - c.x) / d * pull;
|
|
c.y += (h.y - c.y) / d * pull;
|
|
moved = true;
|
|
}
|
|
if (d2 < (eatR + c.r) * (eatR + c.r)) {
|
|
c._eaten = true;
|
|
s.grow(c.value);
|
|
s.coins += c.value;
|
|
if (c.kind === 1) s.lucky += 1;
|
|
}
|
|
}
|
|
}
|
|
if (this.coins.some(c => c._eaten)) {
|
|
this.coins = this.coins.filter(c => !c._eaten);
|
|
// 补充普通金币保持总量
|
|
while (this.coins.length < GS.CFG.coinCount) this.coins.push(this._makeCoin());
|
|
moved = true;
|
|
}
|
|
if (moved) this._rebuild();
|
|
}
|
|
|
|
// 相机跟随
|
|
updateCam(target, viewW, viewH, dt) {
|
|
const k = 1 - Math.pow(0.001, dt); // 平滑跟随
|
|
this.cam.x += (target.x - this.cam.x) * k;
|
|
this.cam.y += (target.y - this.cam.y) * k;
|
|
const base = Math.min(viewW, viewH) / 720;
|
|
this.cam.scale = U.clamp(base, 0.7, 1.6);
|
|
}
|
|
|
|
inBounds(x, y, margin) {
|
|
return x > margin && y > margin && x < this.size - margin && y < this.size - margin;
|
|
}
|
|
|
|
// 碰撞判定:返回 {type:'body', snake} 或 {type:'wall'} 或 null
|
|
collide(s, snakes) {
|
|
const h = s.head;
|
|
if (!this.inBounds(h.x, h.y, s.radius * 0.5)) return { type: 'wall' };
|
|
for (const o of snakes) {
|
|
if (o === s || o.dead) continue;
|
|
const segs = o.segments();
|
|
const rr = (s.radius * 0.8 + o.radius * 0.9);
|
|
// 跳过对方头部附近(允许并排)
|
|
for (let i = 2; i < segs.length; i++) {
|
|
const g = segs[i];
|
|
if (U.dist2(h.x, h.y, g.x, g.y) < rr * rr) return { type: 'body', snake: o };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
GS.World = World;
|
|
})();
|