Skip to content

Commit 4e75b2a

Browse files
wehosHongzhi Wenclaude
authored
feat(soccer-demo): 足球小游戏 demo + 心情/难度/气泡 API (Project-N-E-K-O#927)
* fix(autostart-prompt): 接收 shell 端 autostart CHANGED 事件,托盘切换后立即失效本地缓存 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(soccer-demo): 足球小游戏 demo,VRM/L2D 双角色 + 心情/难度/气泡 API - 新页面 /soccer_demo:玩家 VRM sister、AI L2D mao_pro,鼠标走位、蓄力射门、圆形碰撞、球门计分 - AI 状态机(attack/defend/clear)+ 球预判 + 反应延时 + 踢球技能(瞄门角、随机扰动) - 4 档难度循环(max/lv2/lv3/lv4,前摇 + 冷却 + 速度 + 是否允许进攻),每进球自动切档 - 6 种心情(calm/happy/angry/relaxed/sad/surprised)叠乘速度/冷却/冲量/散射 + startle/zoneout 特殊行为,默认 20s 随机轮换 - 气泡系统 + 事件流:window.SoccerDemo 暴露 setMood/setDifficulty/setAvatar/say/onSpeak/onEvent/setBubbleRenderer 等接口,供 neko 本体集成 LLM 和美术素材 - 球死角脱困:时间窗位移检测 + 幽灵穿模 + 强制撤退,避免 AI 把球顶墙 - static/vrm-core.js: init 加 options.embed 参数,跳过全屏容器样式覆写,支持 200x300 嵌入模式 - main_routers/pages_router.py: 加 /soccer_demo 路由 * fix(soccer-demo): 接 review 反馈(CodeRabbit / Codex / code-quality) - setAiAvatar: 去掉 50ms setTimeout race,改用 currentModel 就绪轮询(20s 超时),避免 rAF throttle 环境下外部调用者被挂死 - setPlayerAvatar: 换模型时调用 VRMUtils.deepDispose 释放旧 VRM 的 GPU 资源(geometry/material/texture),防止多次切换泄漏 - aiRetreatSec 1.0→1.8,与 unstickCd 对齐,消除撤退提前解锁的 0.8s 空窗 - 删除未使用的 tryKick(玩家已改蓄力系统)、zoneoutSec 变量、speechTick 里未用的 H - 清理 kickBall / checkGoal / aiMoodTick 里几处 typeof X === 'function' 恒真守卫(同作用域函数声明,被静态分析点出恒为 true) * fix(soccer-demo): setAiAvatar 用完整路径比对,避免同名不同目录的 L2D 模型误判就绪 --------- Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 95ba77c commit 4e75b2a

3 files changed

Lines changed: 1646 additions & 10 deletions

File tree

main_routers/pages_router.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ async def live2d_parameter_editor(request: Request):
5858
})
5959

6060

61+
@router.get("/soccer_demo", response_class=HTMLResponse)
62+
async def soccer_demo(request: Request):
63+
"""Soccer MVP demo (VRM + L2D avatars)"""
64+
templates = get_templates()
65+
return templates.TemplateResponse("templates/soccer_demo.html", {
66+
"request": request,
67+
**_vrm_defaults_ctx(),
68+
})
69+
70+
6171
@router.get("/live2d_emotion_manager", response_class=HTMLResponse)
6272
async def live2d_emotion_manager(request: Request):
6373
"""Live2D情感映射管理器页面"""

static/vrm-core.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,10 @@ class VRMCore {
362362
}
363363
}
364364

365-
async init(canvasId, containerId, lightingConfig = null) {
365+
async init(canvasId, containerId, lightingConfig = null, options = {}) {
366366
this._ensureThreeReady();
367367
const THREE = window.THREE;
368+
const embed = options && options.embed === true;
368369

369370
this.manager.container = document.getElementById(containerId);
370371
this.manager.canvas = document.getElementById(canvasId);
@@ -383,15 +384,17 @@ class VRMCore {
383384
throw new Error(errorMsg);
384385
}
385386

386-
this.manager.container.style.display = 'block';
387-
this.manager.container.style.visibility = 'visible';
388-
this.manager.container.style.opacity = '1';
389-
this.manager.container.style.width = '100%';
390-
this.manager.container.style.height = '100%';
391-
this.manager.container.style.position = 'fixed';
392-
this.manager.container.style.top = '0';
393-
this.manager.container.style.left = '0';
394-
this.manager.container.style.setProperty('pointer-events', 'auto', 'important');
387+
if (!embed) {
388+
this.manager.container.style.display = 'block';
389+
this.manager.container.style.visibility = 'visible';
390+
this.manager.container.style.opacity = '1';
391+
this.manager.container.style.width = '100%';
392+
this.manager.container.style.height = '100%';
393+
this.manager.container.style.position = 'fixed';
394+
this.manager.container.style.top = '0';
395+
this.manager.container.style.left = '0';
396+
this.manager.container.style.setProperty('pointer-events', 'auto', 'important');
397+
}
395398

396399
this.manager.clock = new THREE.Clock();
397400
this.manager.scene = new THREE.Scene();

0 commit comments

Comments
 (0)