From 5cb73a6d82323c6c540ee848d7ebc74524b2c2c9 Mon Sep 17 00:00:00 2001 From: lili Date: Mon, 29 Jun 2026 10:14:09 -0700 Subject: [PATCH] =?UTF-8?q?feat(cheap-gen):=20=E9=98=B6=E6=AE=B5=E4=B8=80B?= =?UTF-8?q?=20#4=20gamefeel=20=E5=8A=A0=20CoyoteTimer/ComboWindow=20?= =?UTF-8?q?=E5=B7=A5=E5=8E=82=E6=96=B9=E6=B3=95(=E5=85=8D=20new+=E4=BC=A0?= =?UTF-8?q?=20time)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 附A #4:gamefeel 的 CoyoteTimer/ComboWindow 是 class、须 new 且要传受控 TimeSource——AI 拿不到 受控 time、做不出来。加工厂方法 gamefeel.createCoyoteTimer(opts)/createComboWindow(opts): 用 init 时捕获的受控时间源自动注入,AI 直接调、免 new + 免找 time。全链路同改: - gamefeel/impl.js:init 捕获 ctx.time,加两个工厂方法(new CoyoteTimer/ComboWindow(time,opts)) - gamefeel/api.d.ts:GamefeelPlugin 接口加两个工厂声明 - skill line115:列工厂方法(免 new + 自动注入受控 time);内置 inputBuffer 改 gamefeel.inputBuffer 验证:gamefeel 18/18 + 确定性(init 后工厂可调、返带 isAvailable/hit 的实例)。prompt 不提 gamefeel 故不同改。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .agents/skills/littlejs-game-dev.md | 2 +- game-runtime/src/plugins/gamefeel/api.d.ts | 4 ++++ game-runtime/src/plugins/gamefeel/impl.js | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.agents/skills/littlejs-game-dev.md b/.agents/skills/littlejs-game-dev.md index aaedf8d9..aaf0bbea 100644 --- a/.agents/skills/littlejs-game-dev.md +++ b/.agents/skills/littlejs-game-dev.md @@ -112,7 +112,7 @@ export function createGame({ plugins, bundle, viewport }) { |---|---|---| | **collision** | 碰撞查询(纯数学) | `circleVsCircle/circleVsAabb/aabbVsAabb/satVsPolygon`→`{hit,depth,nx,ny}`;`raycast*`;`SpatialHash`。**⚠️ Aabb=中心+半宽半高 `{x,y,hw,hh}`(非 {x,y,w,h});Circle `{x,y,r}`** | | **physics-lite** | 街机运动原语(非刚体) | `Projectile{step,reset}`/`integrateProjectile(state,dt,opts)`;`springStep`/`criticalDamping`;`moveWithConstraint`;`clampSpeed`/`applyFriction`。**⚠️ ProjectileState=`{px,py,vx,vy}`(位置是 px/py 非 x/y);重力经 opts.gravity={x,y}`** | -| **gamefeel** | 手感 | `easing`(13 曲线 in/out/inOut);`InputBuffer{consume,peek}`;`CoyoteTimer{setGrounded,isAvailable,consume}`;`ComboWindow{hit,getCount}` | +| **gamefeel** | 手感 | `easing`(13 曲线 in/out/inOut);内置 `gamefeel.inputBuffer{consume,peek}`;**工厂(免 new + 自动注入受控 time,#4)**:`gamefeel.createCoyoteTimer(opts)→{setGrounded,isAvailable,consume}`、`gamefeel.createComboWindow(opts)→{hit,getCount}` | | **particles-juice** | 粒子 + 打击感 | `spawnEmitter(preset\|cfg,x,y)`(或 `burst(x,y)` 直发 burst 预设,#6/#10 母语别名);`hitStop(dur)`/`getTimeScale`;`shakeScreen`/`getShakeOffset`;`flashScreen`/`pulseScale`;`PRESETS`(burst/trail/drift)。**⚠️ `render(g)` 必须每帧手调(非自动);`step(dt)` 默认 autoStep 自驱(经 onFrame),要手调 step 须工厂传 `autoStep:false` 防双步进** | | **palette-post** | 调色 + 后处理 | `rgbToHsl/hslToRgb/applyPaletteMap/shiftHsl`;`setPost/renderPost`(vignette/dither/scanline)。**色板由你注入,插件不内置成品色** | | **audio-music** | 程序化音频 | `loadSong/play/stop/loop`;`setIntensity`/`setIntensityLayering`;`playSfx('blip'\|'thud'\|'chime')` | diff --git a/game-runtime/src/plugins/gamefeel/api.d.ts b/game-runtime/src/plugins/gamefeel/api.d.ts index 827785b8..1e46439d 100644 --- a/game-runtime/src/plugins/gamefeel/api.d.ts +++ b/game-runtime/src/plugins/gamefeel/api.d.ts @@ -244,6 +244,10 @@ export interface GamefeelPlugin extends Plugin { * 永久走内置(引擎门面刻意不暴此二者)。 */ readonly easing: EasingLib; + /** #4:用插件 init 时捕获的受控时间源造一个 CoyoteTimer(免自己 new + 找 time)。init 后可用。 */ + createCoyoteTimer(opts?: CoyoteTimerOptions): CoyoteTimer; + /** #4:用受控时间源造一个 ComboWindow(免自己 new + 找 time)。init 后可用。 */ + createComboWindow(opts?: ComboWindowOptions): ComboWindow; } /** diff --git a/game-runtime/src/plugins/gamefeel/impl.js b/game-runtime/src/plugins/gamefeel/impl.js index b4b6e04a..145fde9a 100644 --- a/game-runtime/src/plugins/gamefeel/impl.js +++ b/game-runtime/src/plugins/gamefeel/impl.js @@ -456,6 +456,8 @@ export function createGamefeelPlugin(opts) { /** @type {EngineMath|null} */ let engMath = null; let disposed = false; + /** @type {TimeSource|null} init 时捕获的受控时间源,供 #4 工厂方法 createCoyoteTimer/createComboWindow 注入。 */ + let time = null; /** @type {GamefeelPlugin} */ const plugin = { @@ -471,6 +473,22 @@ export function createGamefeelPlugin(opts) { get easing() { return makeEasingFacade(engMath); }, + /** + * #4 阶段一B:用受控时间源造一个 CoyoteTimer(AI 直觉 gamefeel.createCoyoteTimer(opts),免自己 new + 找 time)。 + * @param {CoyoteTimerOptions} [opts] + * @returns {CoyoteTimer} + */ + createCoyoteTimer(opts) { + return new CoyoteTimer(time, opts); + }, + /** + * #4 阶段一B:用受控时间源造一个 ComboWindow(AI 直觉 gamefeel.createComboWindow(opts))。 + * @param {ComboWindowOptions} [opts] + * @returns {ComboWindow} + */ + createComboWindow(opts) { + return new ComboWindow(time, opts); + }, /** * 初始化:经受控面 getInput()+time 接一个内置 InputBuffer(演示受控面正确接线), * 并缓存引擎 math 门面句柄(供 easing getter 门面优先取引擎 Ease)。 @@ -479,6 +497,7 @@ export function createGamefeelPlugin(opts) { init(ctx) { // 经受控面拿输入订阅与时间源(禁直接 addEventListener / Date.now)。 const input = ctx.getInput(); + time = ctx.time; // #4:缓存受控时间源,供 createCoyoteTimer/createComboWindow 工厂注入。 inputBuffer = new InputBuffer(input, ctx.time, { windowMs: options.inputBufferMs, types: options.inputTypes,