6dc79a2c12
- 私人剪贴板新增限时直通URL生成功能 - doc_cloud_keeper新增标题复制按钮 - 新增贪蛇出动游戏工具 - 新增OSS中转直链工具 - 添加批量抽帧测试脚本
113 lines
3.8 KiB
JavaScript
113 lines
3.8 KiB
JavaScript
/* 贪蛇出动 - 蛇实体:移动/转向/生长/路径采样 */
|
|
(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;
|
|
})();
|