用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/parsoFish/forge --skill canvas-game命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | canvas-game |
| category | frontend |
| description | HTML5 Canvas game development patterns — game loop, rendering, input, state management, and Vite/Vitest tooling. |
// Fixed timestep with variable rendering
const TICK_RATE = 1000 / 60; // 60 updates/sec
let lastTime = 0;
let accumulator = 0;
function gameLoop(timestamp: number): void {
const delta = timestamp - lastTime;
lastTime = timestamp;
accumulator += delta;
// Fixed-step updates (deterministic)
while (accumulator >= TICK_RATE) {
update(TICK_RATE / 1000);
accumulator -= TICK_RATE;
}
// Variable-rate rendering with interpolation
const alpha = accumulator / TICK_RATE;
render(alpha);
requestAnimationFrame(gameLoop);
}
save()/restore() around transform blocksMath.round() for positions to avoid sub-pixel blurfunction setupHiDPI(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): void {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
}
const keys = new Set<string>();
canvas.addEventListener('keydown', (e) => keys.add(e.key));
canvas.addEventListener('keyup', (e) => keys.delete(e.key));
// In update:
if (keys.has('ArrowRight')) player.moveRight(dt);
// Mock canvas context for unit tests
const mockCtx = {
fillRect: vi.fn(),
clearRect: vi.fn(),
save: vi.fn(),
restore: vi.fn(),
// ... other methods as needed
} as unknown as CanvasRenderingContext2D;
// Test game logic without canvas
describe('GameState', () => {
it('advances simulation by one tick', () => {
const state = createInitialState();
const next = tick(state, 1/60);
expect(next.frame).toBe(state.frame + 1);
});
});
// vite.config.ts
export default defineConfig({
build: {
target: 'es2022',
rollupOptions: {
output: {
// Single chunk for game — avoid loading latency
manualChunks: undefined,
},
},
},
// Asset handling for sprites/audio
assetsInclude: ['**/*.png', '**/*.wav', '**/*.ogg'],
});
// Can't inspect canvas DOM — use screenshot comparison
test('game renders initial state', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('canvas');
await expect(page).toHaveScreenshot('initial-state.png', {
maxDiffPixelRatio: 0.01,
});
});
// Or test via exposed game API
test('pressing S starts simulation', async ({ page }) => {
await page.goto('/');
await page.keyboard.press('s');
const isRunning = await page.evaluate(() => window.__gameState?.running);
expect(isRunning).toBe(true);
});