- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试 - 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本 - 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试 - 产品文档、UI 设计规范、各玩法 GDD / HANDOFF - 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
36 lines
1.7 KiB
TypeScript
36 lines
1.7 KiB
TypeScript
import type { Ball, Point } from './types';
|
|
import { canAppend } from './rules';
|
|
|
|
/** 同一控制器接 PointerEvent 或 Cocos EventTouch;取消不等于松手。 */
|
|
export class Gesture {
|
|
chain: number[] = [];
|
|
active = false;
|
|
private previous: Point | null = null;
|
|
constructor(private readonly balls: () => Ball[], private readonly append = canAppend) {}
|
|
down(p: Point): void {
|
|
this.cancel(); this.previous = p;
|
|
const b = this.hit(p);
|
|
if (b) { this.active = true; this.chain = [b.id]; }
|
|
}
|
|
move(p: Point): void {
|
|
if (!this.active) return;
|
|
const from = this.previous || p;
|
|
// 分段采样避免快速扫过小球时跳号;一次事件最多 120 次。
|
|
const steps = Math.min(120, Math.max(1, Math.ceil(Math.hypot(p.x - from.x, p.y - from.y) / 6)));
|
|
for (let i = 1; i <= steps; i++) {
|
|
const b = this.hit({ x: from.x + (p.x - from.x) * i / steps, y: from.y + (p.y - from.y) * i / steps });
|
|
if (!b || b.id === this.chain[this.chain.length - 1]) continue;
|
|
if (this.chain.length > 1 && b.id === this.chain[this.chain.length - 2]) { this.chain.pop(); continue; }
|
|
const balls = this.balls();
|
|
if (this.append(this.chain.map(id => balls.find(b => b.id === id)!), b)) this.chain.push(b.id);
|
|
}
|
|
this.previous = p;
|
|
}
|
|
up(): number[] { const path = [...this.chain]; this.cancel(); return path; }
|
|
cancel(): void { this.chain = []; this.active = false; this.previous = null; }
|
|
private hit(p: Point): Ball | undefined {
|
|
return this.balls().filter(b => Math.hypot(b.x - p.x, b.y - p.y) <= b.r + 4)
|
|
.sort((a, b) => Math.hypot(a.x - p.x, a.y - p.y) / a.r - Math.hypot(b.x - p.x, b.y - p.y) / b.r)[0];
|
|
}
|
|
}
|