import { _decorator, Component, Node } from 'cc'; const { ccclass, property } = _decorator; /** * SceneStart: 端到端联调脚本(真机验证响应式链路)。 * * 工作流: * 1. onLoad/start 时构造一个 mock playerState(自包含 signal) * 2. 找到场景里的 PlayerInfoView 节点(通过 property) * 3. 调用 view.bindTo(mockSession),bind 会立即触发一次 applyPlayer(首次同步) * 4. 3 秒后改变 playerState.value,验证 view 自动更新 Label.string * * 这模拟完整的「login 事件 → PlatformSession 填 Store → view 自动刷新」链路, * 不依赖真 WS,但能让 cocos 编辑器运行时验证响应式模式正确。 */ @ccclass('SceneStart') export class SceneStart extends Component { @property(Node) playerInfoView: Node = null!; start(): void { if (!this.playerInfoView) { console.error('[SceneStart] playerInfoView 未设置'); return; } // mock session:playerState 是自包含 signal(模拟 ReadonlyReactive) const playerState: any = { value: { nickname: '初始玩家', bean: 0, avatar: '' }, subscribe(cb: (v: any, p: any) => void) { const subs: Array<(v: any, p: any) => void> = ((playerState as any).__subs ??= []); subs.push(cb); return () => { const i = subs.indexOf(cb); if (i >= 0) subs.splice(i, 1); }; }, }; // 触发 setter 时调用所有订阅 Object.defineProperty(playerState, 'value', { get() { return (playerState as any).__value; }, set(v: any) { const prev = (playerState as any).__value; (playerState as any).__value = v; for (const fn of ((playerState as any).__subs ?? [])) { try { fn(v, prev); } catch (e) { console.error(e); } } }, }); (playerState as any).__value = { nickname: '初始玩家', bean: 0, avatar: '' }; const mockSession: any = { player: { state: playerState } }; const view: any = this.playerInfoView.getComponent('PlayerInfoView'); if (!view || typeof view.bindTo !== 'function') { console.error('[SceneStart] PlayerInfoView 组件未挂或无 bindTo'); return; } view.bindTo(mockSession); console.log('[SceneStart] bindTo 已触发,nickname 应为「初始玩家」'); this.scheduleOnce(() => { playerState.value = { nickname: '更新后玩家', bean: 100, avatar: '' }; console.log('[SceneStart] 改变 playerState,nickname 应自动变为「更新后玩家」'); }, 3); } }