feat: 新增多项功能并添加新工具
- 私人剪贴板新增限时直通URL生成功能 - doc_cloud_keeper新增标题复制按钮 - 新增贪蛇出动游戏工具 - 新增OSS中转直链工具 - 添加批量抽帧测试脚本
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
/* 贪蛇出动 - 世界:金币生成/拾取/磁铁/边界/相机/死亡掉币 */
|
||||
(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;
|
||||
})();
|
||||
Reference in New Issue
Block a user