feat(ui): PlayerInfoView 视图薄壳(真机可挂载)
assets/scripts/views/PlayerInfoView.ts: - 自包含:不 import framework/store-binder - 内联 createViewBinder() 极简实现(语义与 framework 一致) - bindTo(session) 订阅 session.player.state → applyPlayer → Label.string - 替代旧 setHallRoomCard 等手工回调 关键发现(memory cocos-mcp-mount-script-blocker): - Cocos 3.8 只扫 assets/script(s)/ 作为脚本目录 - assets/framework/ 下的 .ts 挂脚本必失败(ctor 错误) - 视图薄壳必须放 assets/scripts/views/,framework/ 下的逻辑层通过 import 调用 mount_script 实证:assets/scripts/views/PlayerInfoView.ts 挂载成功 (MCP 返回 success + componentType UUID,无 ctor 错误)。 已知副作用:cocos_prefab create 后 prefab 没序列化 __scriptAsset 字段, import 报 ctor 错误。MCP create prefab 的 bug,需要后续用编辑器手动挂或写扩展。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "6839b1e3-eff7-4f46-a5f4-d2cf3e558cbf",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { _decorator, Component, Label, Sprite } from 'cc';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* PlayerInfoView: 响应式视图组件(自包含,便于 Cocos 编辑器挂载)。
|
||||
*
|
||||
* 设计取舍:不 import framework/store-binder,以避免 framework/ 的 .ts 在
|
||||
* cocos-mcp-server 的 mount_script 下出现 ctor 错误(framework/ 路径下
|
||||
* .ts 不被 Cocos 3.8 当 Component 子类编译;见 memory cocos-mcp-mount-script-blocker)。
|
||||
*
|
||||
* 实际订阅契约与 framework/ui/store-binder 一致:
|
||||
* - bindTo(binder) 接收外部 binder 实例
|
||||
* - 调用方持有 binder,内部 subscribe + 自动刷新 Label.string
|
||||
*
|
||||
* 后续如需让 framework/store-binder 与本组件复用同一份逻辑,可把 subscribe
|
||||
* 抽到一个 cc-agnostic 的 pure module;目前为解 ctor blocker 暂以自包含方式落地。
|
||||
*/
|
||||
|
||||
/** 内联极简 binder(语义与 framework/ui/store-binder 对齐) */
|
||||
interface ViewBinder {
|
||||
bind<T>(store: ViewStore<T>, slot: string, cb: (value: T) => void): void;
|
||||
unbindAll(): void;
|
||||
}
|
||||
|
||||
/** 极简 store 契约:暴露 subscribe 与 value */
|
||||
interface ViewStore<T> {
|
||||
subscribe(cb: (value: T, prev: T) => void): () => void;
|
||||
value: T;
|
||||
}
|
||||
|
||||
function createViewBinder(): ViewBinder {
|
||||
const unsubs: Array<() => void> = [];
|
||||
return {
|
||||
bind<T>(store: ViewStore<T>, _slot: string, cb: (value: T) => void) {
|
||||
// 首次同步:bind 时立即用当前 value 跑一次 callback
|
||||
try { cb(store.value); }
|
||||
catch (e) { console.error('[PlayerInfoView] initial callback error:', e); }
|
||||
const unsub = store.subscribe((value) => cb(value));
|
||||
unsubs.push(unsub);
|
||||
},
|
||||
unbindAll() {
|
||||
while (unsubs.length > 0) {
|
||||
const u = unsubs.pop();
|
||||
try { u?.(); } catch { /* 退订隔离 */ }
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ccclass('PlayerInfoView')
|
||||
export class PlayerInfoView extends Component {
|
||||
@property(Label)
|
||||
nicknameLabel: Label = null!;
|
||||
|
||||
@property(Label)
|
||||
beanLabel: Label = null!;
|
||||
|
||||
@property(Sprite)
|
||||
avatarSprite: Sprite = null!;
|
||||
|
||||
private binder: ViewBinder | null = null;
|
||||
private session: { player: { state: ViewStore<any> } } | null = null;
|
||||
|
||||
/**
|
||||
* 由场景初始化代码调用,把视图绑定到 session.player.state。
|
||||
* 调用方负责传入 session(典型用法:PlatformSession)。
|
||||
*
|
||||
* 兼容性:session.player.state 需有 subscribe/value 两个成员——framework
|
||||
* 的 ReadonlyReactive<T> 已具备。
|
||||
*/
|
||||
bindTo(session: { player: { state: ViewStore<any> } }): void {
|
||||
this.unbindIfNeeded();
|
||||
this.session = session;
|
||||
this.binder = createViewBinder();
|
||||
this.binder.bind(session.player.state, 'state', (p: any) => this.applyPlayer(p));
|
||||
}
|
||||
|
||||
onDestroy(): void { this.unbindIfNeeded(); }
|
||||
|
||||
/** 公共方法:外部可直接驱动 UI */
|
||||
applyPlayer(p: { nickname: string; bean: number; avatar: string }): void {
|
||||
if (this.nicknameLabel) this.nicknameLabel.string = p.nickname ?? '';
|
||||
if (this.beanLabel) this.beanLabel.string = String(p.bean ?? 0);
|
||||
// avatar 加载留给业务侧扩展
|
||||
}
|
||||
|
||||
private unbindIfNeeded(): void {
|
||||
this.binder?.unbindAll();
|
||||
this.binder = null;
|
||||
this.session = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user