feat: 新增多项功能并添加新工具
- 私人剪贴板新增限时直通URL生成功能 - doc_cloud_keeper新增标题复制按钮 - 新增贪蛇出动游戏工具 - 新增OSS中转直链工具 - 添加批量抽帧测试脚本
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
/* 贪蛇出动 - 蛇实体:移动/转向/生长/路径采样 */
|
||||
(function () {
|
||||
'use strict';
|
||||
window.GS = window.GS || {};
|
||||
const U = GS.U;
|
||||
|
||||
class Snake {
|
||||
constructor(opts) {
|
||||
this.id = opts.id;
|
||||
this.name = opts.name;
|
||||
this.isPlayer = !!opts.isPlayer;
|
||||
this.color = opts.color;
|
||||
this.skin = opts.skin || 0; // 0 纯色 1 条纹
|
||||
this.angle = opts.angle != null ? opts.angle : U.rand(0, Math.PI * 2);
|
||||
this.targetAngle = this.angle;
|
||||
this.turnRate = this.isPlayer ? GS.CFG.turnRate : GS.CFG.botTurnRate;
|
||||
this.score = opts.score || GS.CFG.startScore;
|
||||
this.dead = false;
|
||||
this.boosting = false;
|
||||
this.shield = false; // 护盾:抵挡一次致命碰撞
|
||||
this.invincibleUntil = 0; // 无敌时间戳(火箭/复活保护)
|
||||
this.magnetUntil = 0; // 磁铁截止
|
||||
this.kills = 0;
|
||||
this.coins = 0; // 本局吃到的金币数
|
||||
this.lucky = 0; // 本局幸运星
|
||||
// 路径:密集头轨迹点
|
||||
this.path = [{ x: opts.x, y: opts.y }];
|
||||
this.pathLen = 0;
|
||||
this._segs = [];
|
||||
this._segsDirty = true;
|
||||
this.wobble = U.rand(0, Math.PI * 2); // 身体摆动相位
|
||||
}
|
||||
|
||||
get head() { return this.path[this.path.length - 1]; }
|
||||
|
||||
// 半径随分数增长
|
||||
get radius() { return U.clamp(7 + Math.sqrt(Math.max(0, this.score)) * 0.32, 7, 30); }
|
||||
|
||||
// 身体节数
|
||||
get segCount() { return Math.floor(10 + this.score / 6); }
|
||||
|
||||
get spacing() { return Math.max(5, this.radius * 0.5); }
|
||||
|
||||
get speed() { return this.boosting ? GS.CFG.boostSpeed : GS.CFG.baseSpeed; }
|
||||
|
||||
invincible(now) { return now < this.invincibleUntil; }
|
||||
|
||||
update(dt, now) {
|
||||
if (this.dead) return;
|
||||
// 转向限速
|
||||
const da = U.normAngle(this.targetAngle - this.angle);
|
||||
const maxTurn = this.turnRate * dt;
|
||||
this.angle += U.clamp(da, -maxTurn, maxTurn);
|
||||
|
||||
const sp = this.speed;
|
||||
const h = this.head;
|
||||
const nx = h.x + Math.cos(this.angle) * sp * dt;
|
||||
const ny = h.y + Math.sin(this.angle) * sp * dt;
|
||||
this.path.push({ x: nx, y: ny });
|
||||
this.pathLen += sp * dt;
|
||||
|
||||
// 裁剪路径:保留足够长度
|
||||
const need = this.segCount * this.spacing + this.radius * 4;
|
||||
while (this.path.length > 2 && this.pathLen > need) {
|
||||
const d = U.dist(this.path[0].x, this.path[0].y, this.path[1].x, this.path[1].y);
|
||||
if (this.pathLen - d < need) break;
|
||||
this.pathLen -= d;
|
||||
this.path.shift();
|
||||
}
|
||||
this._segsDirty = true;
|
||||
this.wobble += dt * (this.boosting ? 14 : 9);
|
||||
}
|
||||
|
||||
// 从头部沿路径每隔 spacing 采样一节(不修改原路径)
|
||||
segments() {
|
||||
if (!this._segsDirty && this._segs.length) return this._segs;
|
||||
const segs = [{ x: this.head.x, y: this.head.y }];
|
||||
const n = this.segCount;
|
||||
const spacing = this.spacing;
|
||||
let acc = 0; // 自上一个采样点起已走过但不足 spacing 的距离
|
||||
for (let i = this.path.length - 1; i > 0 && segs.length < n; i--) {
|
||||
let ax = this.path[i].x, ay = this.path[i].y;
|
||||
const b = this.path[i - 1];
|
||||
let d = U.dist(ax, ay, b.x, b.y);
|
||||
if (d < 1e-9) continue;
|
||||
while (acc + d >= spacing && segs.length < n) {
|
||||
const t = (spacing - acc) / d;
|
||||
const px = ax + (b.x - ax) * t;
|
||||
const py = ay + (b.y - ay) * t;
|
||||
segs.push({ x: px, y: py });
|
||||
d -= (spacing - acc);
|
||||
ax = px; ay = py;
|
||||
acc = 0;
|
||||
}
|
||||
acc += d;
|
||||
}
|
||||
this._segs = segs;
|
||||
this._segsDirty = false;
|
||||
return segs;
|
||||
}
|
||||
|
||||
grow(v) { this.score += v; }
|
||||
|
||||
// 死亡:返回尸体坐标(用于掉币)
|
||||
die() {
|
||||
this.dead = true;
|
||||
return this.segments().slice();
|
||||
}
|
||||
}
|
||||
|
||||
GS.Snake = Snake;
|
||||
})();
|
||||
Reference in New Issue
Block a user