diff --git a/cocoscreator_projects/YouleNexus/assets/scenes/Main.scene b/cocoscreator_projects/YouleNexus/assets/scenes/Main.scene index eed307e..011bde8 100644 --- a/cocoscreator_projects/YouleNexus/assets/scenes/Main.scene +++ b/cocoscreator_projects/YouleNexus/assets/scenes/Main.scene @@ -26,7 +26,7 @@ "_active": true, "_components": [], "_prefab": { - "__id__": 25 + "__id__": 38 }, "_lpos": { "__type__": "cc.Vec3", @@ -57,7 +57,7 @@ }, "autoReleaseAssets": false, "_globals": { - "__id__": 26 + "__id__": 39 }, "_id": "39e47c22-7aa0-4b0b-98a5-c5006cf3766b" }, @@ -163,15 +163,21 @@ "_children": [ { "__id__": 5 + }, + { + "__id__": 23 + }, + { + "__id__": 33 } ], "_active": true, "_components": [ { - "__id__": 23 + "__id__": 36 }, { - "__id__": 24 + "__id__": 37 } ], "_prefab": null, @@ -475,6 +481,273 @@ ], "value": null }, + { + "__type__": "cc.Node", + "_name": "PlayerInfoView_Runtime", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 4 + }, + "_children": [ + { + "__id__": 24 + } + ], + "_active": true, + "_components": [ + { + "__id__": 32 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": -100, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 33554432, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "fcIV9QMDtLGpt3/3eCUF9w" + }, + { + "__type__": "cc.Node", + "_objFlags": 0, + "_parent": { + "__id__": 23 + }, + "_prefab": { + "__id__": 25 + }, + "__editorExtras__": {} + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 24 + }, + "asset": { + "__uuid__": "9dd8661c-f764-4e33-b5be-47e02a7d6f41", + "__expectedType__": "cc.Prefab" + }, + "fileId": "dflMFKQ9RAybmIkPF8a3Q5", + "instance": { + "__id__": 26 + }, + "targetOverrides": null, + "nestedPrefabInstanceRoots": null + }, + { + "__type__": "cc.PrefabInstance", + "fileId": "068gqTjUZHz4hmwVlaPi9D", + "prefabRootNode": null, + "mountedChildren": [], + "mountedComponents": [], + "propertyOverrides": [ + { + "__id__": 27 + }, + { + "__id__": 29 + }, + { + "__id__": 30 + }, + { + "__id__": 31 + } + ], + "removedComponents": [] + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 28 + }, + "propertyPath": [ + "_name" + ], + "value": "PlayerInfoView" + }, + { + "__type__": "cc.TargetInfo", + "localID": [ + "dflMFKQ9RAybmIkPF8a3Q5" + ] + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 28 + }, + "propertyPath": [ + "_lpos" + ], + "value": { + "__type__": "cc.Vec3", + "x": 0, + "y": 100, + "z": 0 + } + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 28 + }, + "propertyPath": [ + "_lrot" + ], + "value": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + } + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 28 + }, + "propertyPath": [ + "_euler" + ], + "value": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + } + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 23 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 100, + "height": 100 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "feGGV1vlZBXYEb9+YMs0YO" + }, + { + "__type__": "cc.Node", + "_name": "SceneStart_Node", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 4 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 34 + }, + { + "__id__": 35 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 600, + "y": -300, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 33554432, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "6eeU3KOvxGQbS6UwtxOvAd" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 33 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 100, + "height": 100 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "b4q29azdFN5o73K3kHuaVX" + }, + { + "__type__": "c9dc3Tde6BMC7Mls6KOasWP", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 33 + }, + "_enabled": true, + "__prefab": null, + "playerInfoView": { + "__id__": 24 + }, + "_id": "c1JK5vephNPJufMYxFCDrz" + }, { "__type__": "cc.UITransform", "_name": "", @@ -523,34 +796,37 @@ "nestedPrefabInstanceRoots": [ { "__id__": 5 + }, + { + "__id__": 24 } ] }, { "__type__": "cc.SceneGlobals", "ambient": { - "__id__": 27 + "__id__": 40 }, "shadows": { - "__id__": 28 + "__id__": 41 }, "_skybox": { - "__id__": 29 + "__id__": 42 }, "fog": { - "__id__": 30 + "__id__": 43 }, "octree": { - "__id__": 31 + "__id__": 44 }, "skin": { - "__id__": 32 + "__id__": 45 }, "lightProbeInfo": { - "__id__": 33 + "__id__": 46 }, "postSettings": { - "__id__": 34 + "__id__": 47 }, "bakedWithStationaryMainLight": false, "bakedWithHighpLightmap": false diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/SceneStart.ts b/cocoscreator_projects/YouleNexus/assets/scripts/SceneStart.ts new file mode 100644 index 0000000..85b453d --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/SceneStart.ts @@ -0,0 +1,69 @@ +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); + } +} \ No newline at end of file