laya-snake / src /web /js /snakeMotion.js
deepondas's picture
Claude Opus 5.5
Add obstacles, multiple foods and a real-time 3D (Three.js) view
38cd66a
Raw History Blame Contribute Delete
4.36 kB
export const GLIDE_MS = 160;
// The previous snake and when the latest move started, for visual interpolation only.
export const motion = { prev: null, at: 0 };
// Recent food pickups, for the 2D sparkle.
export const effects = [];
// Remembers the previous snake so frames can glide between cells, and marks eaten food.
export function trackMotion(old, s) {
const now = performance.now();
while (effects.length && now - effects[0].at > 1000) effects.shift();
if (!old || !old.snake.length || !s.snake.length || old.round !== s.round) {
motion.prev = null;
return;
}
const [ox, oy] = old.snake[0], [nx, ny] = s.snake[0];
if (ox === nx && oy === ny) return;
if (Math.abs(ox - nx) + Math.abs(oy - ny) === 1) { motion.prev = old.snake; motion.at = now; }
else motion.prev = null;
if (s.score > old.score) effects.push({ x: nx + 0.5, y: ny + 0.5, at: now });
}
// Allocation-free polyline through the snake in cell units, head first, with arc lengths.
export class Centerline {
constructor() {
this.n = 0; this.total = 0; this.cap = 0;
this.x = this.y = this.d = null;
this.px = new Float32Array(64); this.py = new Float32Array(64);
}
// Grows the buffers to hold at least `n` samples.
ensure(n) {
if (n <= this.cap) return;
this.cap = Math.max(n, this.cap * 2, 256);
this.x = new Float32Array(this.cap); this.y = new Float32Array(this.cap); this.d = new Float32Array(this.cap);
}
// Appends one sample.
push(x, y) { this.x[this.n] = x; this.y[this.n] = y; this.n++; }
// Writes the point `dist` cells from the head into `out` (clamped to the body).
pointAt(dist, out) {
const n = this.n;
if (n === 1 || dist <= 0) { out.x = this.x[0]; out.y = this.y[0]; return out; }
if (dist >= this.total) { out.x = this.x[n - 1]; out.y = this.y[n - 1]; return out; }
let lo = 0, hi = n - 1;
while (hi - lo > 1) { const m = (lo + hi) >> 1; if (this.d[m] <= dist) lo = m; else hi = m; }
const span = this.d[hi] - this.d[lo], t = span > 1e-9 ? (dist - this.d[lo]) / span : 0;
out.x = this.x[lo] + (this.x[hi] - this.x[lo]) * t;
out.y = this.y[lo] + (this.y[hi] - this.y[lo]) * t;
return out;
}
}
// Fills `cl` for this frame: cell centres, head and tail gliding between the last two states, with
// every corner replaced by a quadratic curve between the midpoints of its two edges. The curve
// stays inside the corner cell, so it never cuts across a cell the snake is not in.
export function buildCenterline(state, now, cl) {
const cells = state.snake, n = cells.length;
cl.n = 0; cl.total = 0;
if (!n) return cl;
if (cl.px.length < n + 1) { cl.px = new Float32Array((n + 1) * 2); cl.py = new Float32Array((n + 1) * 2); }
const px = cl.px, py = cl.py;
for (let i = 0; i < n; i++) { px[i] = cells[i][0] + 0.5; py[i] = cells[i][1] + 0.5; }
let m = n;
const e = motion.prev ? Math.min(1, Math.max(0, (now - motion.at) / GLIDE_MS)) : 1;
const ease = e * (2 - e);
if (n > 1 && ease < 1) {
px[0] = px[1] + (px[0] - px[1]) * ease; py[0] = py[1] + (py[0] - py[1]) * ease;
const oldTail = motion.prev[motion.prev.length - 1], newTail = cells[n - 1];
if (oldTail[0] !== newTail[0] || oldTail[1] !== newTail[1]) {
const ox = oldTail[0] + 0.5, oy = oldTail[1] + 0.5;
px[m] = ox + (px[n - 1] - ox) * ease; py[m] = oy + (py[n - 1] - oy) * ease; m++;
}
}
cl.ensure(m * 8 + 16);
if (m === 1) { cl.push(px[0], py[0]); cl.d[0] = 0; return cl; }
const fx = (px[0] + px[1]) / 2, fy = (py[0] + py[1]) / 2;
for (let k = 0; k < 4; k++) cl.push(px[0] + (fx - px[0]) * k / 4, py[0] + (fy - py[0]) * k / 4);
for (let i = 1; i < m - 1; i++) {
const sx = (px[i - 1] + px[i]) / 2, sy = (py[i - 1] + py[i]) / 2;
const ex = (px[i] + px[i + 1]) / 2, ey = (py[i] + py[i + 1]) / 2;
for (let k = 0; k < 8; k++) {
const t = k / 8, u = 1 - t;
cl.push(u * u * sx + 2 * u * t * px[i] + t * t * ex, u * u * sy + 2 * u * t * py[i] + t * t * ey);
}
}
const lx = px[m - 1], ly = py[m - 1], mx = (px[m - 2] + lx) / 2, my = (py[m - 2] + ly) / 2;
for (let k = 0; k <= 4; k++) cl.push(mx + (lx - mx) * k / 4, my + (ly - my) * k / 4);
let len = 0;
cl.d[0] = 0;
for (let i = 1; i < cl.n; i++) { len += Math.hypot(cl.x[i] - cl.x[i - 1], cl.y[i] - cl.y[i - 1]); cl.d[i] = len; }
cl.total = len;
return cl;
}