diff --git a/cocoscreator_projects/YouleNexus/assets/scenes/LocalPlatformLogin.scene b/cocoscreator_projects/YouleNexus/assets/scenes/LocalPlatformLogin.scene new file mode 100644 index 0000000..2dd3d00 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scenes/LocalPlatformLogin.scene @@ -0,0 +1,1393 @@ +[ + { + "__type__": "cc.SceneAsset", + "_name": "LocalPlatformLogin", + "_objFlags": 0, + "__editorExtras__": {}, + "_native": "", + "scene": { + "__id__": 1 + } + }, + { + "__type__": "cc.Scene", + "_name": "LocalPlatformLogin", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": null, + "_children": [ + { + "__id__": 2 + } + ], + "_active": true, + "_components": [], + "_prefab": { + "__id__": 36 + }, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "autoReleaseAssets": false, + "_globals": { + "__id__": 37 + }, + "_id": "67e69aac-9982-4e78-a44c-1d9c42f016ae" + }, + { + "__type__": "cc.Node", + "_name": "Canvas", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [ + { + "__id__": 3 + }, + { + "__id__": 5 + }, + { + "__id__": 7 + }, + { + "__id__": 17 + }, + { + "__id__": 19 + }, + { + "__id__": 24 + }, + { + "__id__": 29 + } + ], + "_active": true, + "_components": [ + { + "__id__": 34 + }, + { + "__id__": 35 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 800, + "y": 360, + "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": "4fmPDGIcdPtKTxMtiB6nco" + }, + { + "__type__": "cc.Node", + "_name": "Camera", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 4 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 1000 + }, + "_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": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "56FfrqrBlPOJEcpxhwztIB" + }, + { + "__type__": "cc.Camera", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 3 + }, + "_enabled": true, + "__prefab": null, + "_projection": 0, + "_priority": 0, + "_fov": 45, + "_fovAxis": 0, + "_orthoHeight": 360, + "_near": 0.1, + "_far": 2000, + "_color": { + "__type__": "cc.Color", + "r": 20, + "g": 29, + "b": 45, + "a": 255 + }, + "_depth": 1, + "_stencil": 0, + "_clearFlags": 7, + "_rect": { + "__type__": "cc.Rect", + "x": 0, + "y": 0, + "width": 1, + "height": 1 + }, + "_aperture": 19, + "_shutter": 7, + "_iso": 0, + "_screenScale": 1, + "_visibility": 33554432, + "_targetTexture": null, + "_postProcess": null, + "_usePostProcess": false, + "_cameraType": -1, + "_trackingType": 0, + "_id": "5fJx8MoDNLDruFvKpvUqV3" + }, + { + "__type__": "cc.Node", + "_name": "Host", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 6 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "273kGrcqlGnqgl7sJetcLH" + }, + { + "__type__": "25970YMrRZG8YntQr+ux0rZ", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 5 + }, + "_enabled": true, + "__prefab": null, + "loadingPrefab": { + "__uuid__": "4db0114a-f751-4931-bfaf-4fef31430583", + "__expectedType__": "cc.Prefab" + }, + "loginPrefab": { + "__uuid__": "b209cefb-b381-4c7c-b765-94a5ab75a89c", + "__expectedType__": "cc.Prefab" + }, + "loadingRoot": { + "__id__": 7 + }, + "loginRoot": { + "__id__": 17 + }, + "resultRoot": { + "__id__": 19 + }, + "statusOverlay": { + "__id__": 24 + }, + "statusLabel": { + "__id__": 27 + }, + "resultLabel": { + "__id__": 22 + }, + "stopButton": { + "__id__": 29 + }, + "_id": "94INvS4pdAnIe3wFZxabB7" + }, + { + "__type__": "cc.Node", + "_name": "LoadingRoot", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [ + { + "__id__": 8 + } + ], + "_active": true, + "_components": [ + { + "__id__": 16 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": "40omEGvnFOKq2lXAwsqtP8" + }, + { + "__type__": "cc.Node", + "_objFlags": 0, + "_parent": { + "__id__": 7 + }, + "_prefab": { + "__id__": 9 + }, + "__editorExtras__": {} + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 8 + }, + "asset": { + "__uuid__": "4db0114a-f751-4931-bfaf-4fef31430583", + "__expectedType__": "cc.Prefab" + }, + "fileId": "RyJAROM7sGTy6Li8CWDPZA", + "instance": { + "__id__": 10 + }, + "targetOverrides": null, + "nestedPrefabInstanceRoots": null + }, + { + "__type__": "cc.PrefabInstance", + "fileId": "ff8m2dc1lLAqLAsOz8udcb", + "prefabRootNode": null, + "mountedChildren": [], + "mountedComponents": [], + "propertyOverrides": [ + { + "__id__": 11 + }, + { + "__id__": 13 + }, + { + "__id__": 14 + }, + { + "__id__": 15 + } + ], + "removedComponents": [] + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 12 + }, + "propertyPath": [ + "_name" + ], + "value": "Layer614_Loading" + }, + { + "__type__": "cc.TargetInfo", + "localID": [ + "RyJAROM7sGTy6Li8CWDPZA" + ] + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 12 + }, + "propertyPath": [ + "_lpos" + ], + "value": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + } + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 12 + }, + "propertyPath": [ + "_lrot" + ], + "value": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + } + }, + { + "__type__": "CCPropertyOverrideInfo", + "targetInfo": { + "__id__": 12 + }, + "propertyPath": [ + "_euler" + ], + "value": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + } + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 7 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 1600, + "height": 720 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "12ZBzo5G9KBoOxPTXRKk/Q" + }, + { + "__type__": "cc.Node", + "_name": "LoginRoot", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [], + "_active": false, + "_components": [ + { + "__id__": 18 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": "d6V5u3kY9ASbSWXectzlbS" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 17 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 1600, + "height": 720 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "40Uk4k/ONMnJXSfDjQDunZ" + }, + { + "__type__": "cc.Node", + "_name": "ResultRoot", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [ + { + "__id__": 20 + } + ], + "_active": false, + "_components": [ + { + "__id__": 23 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": "4e40f7pcFAipccXYiKt1LW" + }, + { + "__type__": "cc.Node", + "_name": "ResultLabel", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 19 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 21 + }, + { + "__id__": 22 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": "f9tsqXSTtDdqA0mcIgv5m8" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 20 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 1200, + "height": 380 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "44DJyd4DhIlI4pbBdfv/QM" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 20 + }, + "_enabled": true, + "__prefab": null, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 245, + "g": 246, + "b": 250, + "a": 255 + }, + "_string": "登录结果", + "_horizontalAlign": 1, + "_verticalAlign": 1, + "_actualFontSize": 0, + "_fontSize": 36, + "_fontFamily": "Arial", + "_lineHeight": 48, + "_overflow": 0, + "_enableWrapText": true, + "_font": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_isItalic": false, + "_isBold": false, + "_isUnderline": false, + "_underlineHeight": 2, + "_cacheMode": 0, + "_enableOutline": false, + "_outlineColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_outlineWidth": 2, + "_enableShadow": false, + "_shadowColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_shadowOffset": { + "__type__": "cc.Vec2", + "x": 2, + "y": 2 + }, + "_shadowBlur": 2, + "_id": "01Oedc9UVL64Gj9z6SWzgN" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 19 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 1600, + "height": 720 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "fcIBurW2VOWo3ZMkOtLxIu" + }, + { + "__type__": "cc.Node", + "_name": "StatusOverlay", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [ + { + "__id__": 25 + } + ], + "_active": true, + "_components": [ + { + "__id__": 28 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": "a6kdhP/8ZLMLeFOXf1x03K" + }, + { + "__type__": "cc.Node", + "_name": "StatusLabel", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 24 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 26 + }, + { + "__id__": 27 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": -265, + "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": "13eN54GNpEHIFD+205kDRk" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 25 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 363.8892470046937, + "height": 47.879999999999995 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "b2FWK2DwBNLL7XlUTSI/Nw" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 25 + }, + "_enabled": true, + "__prefab": null, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 245, + "g": 246, + "b": 250, + "a": 255 + }, + "_string": "正在加载资源和连接本地服务…", + "_horizontalAlign": 1, + "_verticalAlign": 1, + "_actualFontSize": 27.408333333333335, + "_fontSize": 26, + "_fontFamily": "Arial", + "_lineHeight": 38, + "_overflow": 0, + "_enableWrapText": true, + "_font": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_isItalic": false, + "_isBold": false, + "_isUnderline": false, + "_underlineHeight": 2, + "_cacheMode": 0, + "_enableOutline": false, + "_outlineColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_outlineWidth": 2, + "_enableShadow": false, + "_shadowColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_shadowOffset": { + "__type__": "cc.Vec2", + "x": 2, + "y": 2 + }, + "_shadowBlur": 2, + "_id": "51RskzXn5Ata2xeyOVlNCY" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 24 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 1600, + "height": 720 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "a2g6t0veZNVry/oIZkFXdJ" + }, + { + "__type__": "cc.Node", + "_name": "StopButton", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [ + { + "__id__": 30 + } + ], + "_active": true, + "_components": [ + { + "__id__": 33 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 665, + "y": 310, + "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": "00faTTaCpK+6dsBhx902Te" + }, + { + "__type__": "cc.Node", + "_name": "StopLabel", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 29 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 31 + }, + { + "__id__": 32 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "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": "e4Tfs0ZLVBiaGDxp3kpgsI" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 30 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 103.96835628705533, + "height": 47.879999999999995 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "d0s+NCQGxLt5QFk5h0CZVa" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 30 + }, + "_enabled": true, + "__prefab": null, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 245, + "g": 246, + "b": 250, + "a": 255 + }, + "_string": "停止验证", + "_horizontalAlign": 1, + "_verticalAlign": 1, + "_actualFontSize": 27.408333333333335, + "_fontSize": 26, + "_fontFamily": "Arial", + "_lineHeight": 38, + "_overflow": 0, + "_enableWrapText": true, + "_font": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_isItalic": false, + "_isBold": false, + "_isUnderline": false, + "_underlineHeight": 2, + "_cacheMode": 0, + "_enableOutline": false, + "_outlineColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_outlineWidth": 2, + "_enableShadow": false, + "_shadowColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_shadowOffset": { + "__type__": "cc.Vec2", + "x": 2, + "y": 2 + }, + "_shadowBlur": 2, + "_id": "eewB3DGj9MzpyN67ez8juQ" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 29 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 220, + "height": 64 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "52C95shHpLdqYKR29izOA+" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 2 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 1600, + "height": 720 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "ebjzNd6xRN6rCykuT0wir7" + }, + { + "__type__": "cc.Canvas", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 2 + }, + "_enabled": true, + "__prefab": null, + "_cameraComponent": { + "__id__": 4 + }, + "_alignCanvasWithScreen": true, + "_id": "793QSuOMFHY6zVWL61HGbM" + }, + { + "__type__": "cc.PrefabInfo", + "root": null, + "asset": null, + "fileId": "67e69aac-9982-4e78-a44c-1d9c42f016ae", + "instance": null, + "targetOverrides": null, + "nestedPrefabInstanceRoots": [ + { + "__id__": 8 + } + ] + }, + { + "__type__": "cc.SceneGlobals", + "ambient": { + "__id__": 38 + }, + "shadows": { + "__id__": 39 + }, + "_skybox": { + "__id__": 40 + }, + "fog": { + "__id__": 41 + }, + "octree": { + "__id__": 42 + }, + "skin": { + "__id__": 43 + }, + "lightProbeInfo": { + "__id__": 44 + }, + "postSettings": { + "__id__": 45 + }, + "bakedWithStationaryMainLight": false, + "bakedWithHighpLightmap": false + }, + { + "__type__": "cc.AmbientInfo", + "_skyColorHDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.5, + "z": 0.8, + "w": 0.520833125 + }, + "_skyColor": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.5, + "z": 0.8, + "w": 0.520833125 + }, + "_skyIllumHDR": 20000, + "_skyIllum": 20000, + "_groundAlbedoHDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.2, + "z": 0.2, + "w": 1 + }, + "_groundAlbedo": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.2, + "z": 0.2, + "w": 1 + }, + "_skyColorLDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.5, + "z": 0.8, + "w": 1 + }, + "_skyIllumLDR": 20000, + "_groundAlbedoLDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.2, + "z": 0.2, + "w": 1 + } + }, + { + "__type__": "cc.ShadowsInfo", + "_enabled": false, + "_type": 0, + "_normal": { + "__type__": "cc.Vec3", + "x": 0, + "y": 1, + "z": 0 + }, + "_distance": 0, + "_planeBias": 1, + "_shadowColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 76 + }, + "_maxReceived": 4, + "_size": { + "__type__": "cc.Vec2", + "x": 1024, + "y": 1024 + } + }, + { + "__type__": "cc.SkyboxInfo", + "_envLightingType": 0, + "_envmapHDR": null, + "_envmap": null, + "_envmapLDR": null, + "_diffuseMapHDR": null, + "_diffuseMapLDR": null, + "_enabled": false, + "_useHDR": true, + "_editableMaterial": null, + "_reflectionHDR": null, + "_reflectionLDR": null, + "_rotationAngle": 0 + }, + { + "__type__": "cc.FogInfo", + "_type": 0, + "_fogColor": { + "__type__": "cc.Color", + "r": 200, + "g": 200, + "b": 200, + "a": 255 + }, + "_enabled": false, + "_fogDensity": 0.3, + "_fogStart": 0.5, + "_fogEnd": 300, + "_fogAtten": 5, + "_fogTop": 1.5, + "_fogRange": 1.2, + "_accurate": false + }, + { + "__type__": "cc.OctreeInfo", + "_enabled": false, + "_minPos": { + "__type__": "cc.Vec3", + "x": -1024, + "y": -1024, + "z": -1024 + }, + "_maxPos": { + "__type__": "cc.Vec3", + "x": 1024, + "y": 1024, + "z": 1024 + }, + "_depth": 8 + }, + { + "__type__": "cc.SkinInfo", + "_enabled": true, + "_blurRadius": 0.01, + "_sssIntensity": 3 + }, + { + "__type__": "cc.LightProbeInfo", + "_giScale": 1, + "_giSamples": 1024, + "_bounces": 2, + "_reduceRinging": 0, + "_showProbe": true, + "_showWireframe": true, + "_showConvex": false, + "_data": null, + "_lightProbeSphereVolume": 1 + }, + { + "__type__": "cc.PostSettingsInfo", + "_toneMappingType": 0 + } +] \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/assets/scenes/LocalPlatformLogin.scene.meta b/cocoscreator_projects/YouleNexus/assets/scenes/LocalPlatformLogin.scene.meta new file mode 100644 index 0000000..a88364b --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scenes/LocalPlatformLogin.scene.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.1.50", + "importer": "scene", + "imported": true, + "uuid": "67e69aac-9982-4e78-a44c-1d9c42f016ae", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login.meta b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login.meta new file mode 100644 index 0000000..42f296d --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "6072a7f3-9095-4e71-9dbd-c952d89a64a7", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts new file mode 100644 index 0000000..2f76a7a --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts @@ -0,0 +1,207 @@ +import { _decorator, Component, Label, Node, Prefab, Sprite, UITransform, instantiate, isValid, sys } from 'cc'; +import { DEBUG } from 'cc/env'; +import { HttpConfigFetcher } from '../../framework/config/remote-config-fetcher.ts'; +import type { NativeHost } from '../../framework/config/sources/native-settings.ts'; +import type { RuntimeConfig } from '../../framework/config/runtime-config.ts'; +import type { PageModel } from '../../framework/presentation/ui-contracts.ts'; +import { createLocalLoginHost, type LocalLoginHost } from './local-login-host.ts'; +import type { LocalLoginEvidence } from './local-login-transport.ts'; +import { CocosLocalLoginView, createCocosFrameScheduler, requireNode } from './local-login-view.ts'; + +const { ccclass, property } = _decorator; +const MINIMUM_LOADING_MS = 500; + +/** Explicit DEBUG browser scene entry. All business state belongs to its host Runtime. */ +@ccclass('LocalPlatformLogin') +export class LocalPlatformLogin extends Component { + @property(Prefab) loadingPrefab: Prefab | null = null; + @property(Prefab) loginPrefab: Prefab | null = null; + @property(Node) loadingRoot: Node | null = null; + @property(Node) loginRoot: Node | null = null; + @property(Node) resultRoot: Node | null = null; + @property(Node) statusOverlay: Node | null = null; + @property(Label) statusLabel: Label | null = null; + @property(Label) resultLabel: Label | null = null; + @property(Node) stopButton: Node | null = null; + + private host: LocalLoginHost | null = null; + private view: CocosLocalLoginView | null = null; + private spinner: Node | null = null; + private loginInstance: Node | null = null; + private stopped = false; + private firstDrawAt: number | null = null; + private version: number | null = null; + private readonly pending = new Set<() => void>(); + private readonly evidence: (LocalLoginEvidence & { at: number })[] = []; + private pageModel: PageModel | null = null; + private readonly stopTouch = () => this.stop(); + + async start(): Promise { + // This guard precedes reading browser storage, preparing account or creating socket. + if (!DEBUG || sys.isNative) throw new Error('LocalPlatformLogin requires DEBUG browser preview'); + try { + const loadingRoot = this.required(this.loadingRoot, 'loadingRoot'); + const loginRoot = this.required(this.loginRoot, 'loginRoot'); + const stopButton = this.required(this.stopButton, 'stopButton'); + this.view = new CocosLocalLoginView({ loadingRoot, loginRoot, + resultRoot: this.required(this.resultRoot, 'resultRoot'), + statusOverlay: this.required(this.statusOverlay, 'statusOverlay'), + statusLabel: this.required(this.statusLabel, 'statusLabel'), + resultLabel: this.required(this.resultLabel, 'resultLabel') }); + this.required(this.loadingPrefab, 'loadingPrefab'); + this.required(this.loginPrefab, 'loginPrefab'); + this.spinner = requireNode(loadingRoot, 'Layer614_Loading/group-40/载入动画'); + this.validateSprites(loadingRoot); + if (this.spinner.getComponent(UITransform) === null || this.spinner.getComponent(Sprite) === null) { + throw new Error('Loading spinner requires UITransform and Sprite'); + } + stopButton.on(Node.EventType.TOUCH_END, this.stopTouch); + this.view.activate('loading'); + const firstDraw = this.nextDraw().then(() => { + this.firstDrawAt = performance.now(); + this.record({ kind: 'loading-first-draw', data: null }); + }); + // A configuration failure can cancel this frame before Runtime consumes its gate. + void firstDraw.catch(() => {}); + const view = this.view; + const browser = window as unknown as NativeHost & { returnCitySN?: unknown }; + this.host = createLocalLoginHost({ + configOptions: { mode: 'debug', hostKind: 'h5', search: window.location.search, + win: browser, fetcher: new HttpConfigFetcher() }, + storage: window.localStorage, random: Math.random, now: Date.now, + sampleHost: () => ({ location: null, + ...('returnCitySN' in browser ? { returnCitySN: browser.returnCitySN } : {}) }), + view: { + render: model => { this.pageModel = model; this.record({ kind: 'page-model', data: model }); view.render(model); }, + activate: page => { view.activate(page); this.record({ kind: 'page-active', data: page }); }, + showOverlay: model => view.showOverlay(model), + }, + scheduler: createCocosFrameScheduler(), + loadResources: () => this.loadResources(), + waitForMinimumDisplay: async () => { + await firstDraw; + const drawnAt = this.required(this.firstDrawAt, 'loading first draw'); + await this.delay(Math.max(0, MINIMUM_LOADING_MS - (performance.now() - drawnAt))); + this.record({ kind: 'minimum-display-ready', data: performance.now() - drawnAt }); + }, + createSocket: url => new WebSocket(url), record: event => this.record(event), + onBusy: busy => view.setBusy(busy), + onFailure: error => this.fail(error), + }); + await this.host.start(); + } catch (error) { this.fail(error); } + } + + private required(value: T | null, name: string): T { + if (value === null || value === undefined) throw new Error(`LocalPlatformLogin missing ${name}`); + return value; + } + + private record(event: LocalLoginEvidence): void { + if (event.kind === 'startup-config') this.version = (event.data as RuntimeConfig).identity.version; + this.evidence.push(Object.freeze({ ...event, at: performance.now() })); + } + + private validateSprites(root: Node): void { + for (const sprite of root.getComponentsInChildren(Sprite)) { + if (!sprite.node.activeInHierarchy) continue; + const frame = sprite.spriteFrame; + if (frame === null || !isValid(frame) || frame.texture === null || !isValid(frame.texture)) { + throw new Error(`Invalid SpriteFrame/texture dependency: ${sprite.node.name}`); + } + } + } + + private async loadResources(): Promise { + if (this.stopped) throw new Error('Local login resources cancelled'); + const prefab = this.required(this.loginPrefab, 'loginPrefab'); + const loading = this.required(this.loadingPrefab, 'loadingPrefab'); + if (!isValid(prefab) || !isValid(prefab.data) || !isValid(loading) || !isValid(loading.data)) { + throw new Error('Local login serialized Prefab dependency is invalid'); + } + const instance = instantiate(prefab); + this.loginInstance = instance; + instance.parent = this.required(this.loginRoot, 'loginRoot'); + const view = this.required(this.view, 'view'); + view.bindLogin(instance, () => { + const host = this.required(this.host, 'host'); + void host.login().catch(error => this.fail(error)); + }); + view.setVersion(this.required(this.version, 'resolved identity.version')); + // Validate active prefab content before the root is exposed as the login page. + for (const sprite of instance.getComponentsInChildren(Sprite)) { + if (!sprite.node.active) continue; + const frame = sprite.spriteFrame; + if (frame === null || !isValid(frame) || frame.texture === null || !isValid(frame.texture)) { + throw new Error(`Invalid login SpriteFrame/texture dependency: ${sprite.node.name}`); + } + } + await this.nextDraw(); + this.record({ kind: 'resources-ready', data: { login: prefab.uuid, loading: loading.uuid } }); + } + + private nextDraw(): Promise { + return new Promise((resolve, reject) => { + if (this.stopped) { reject(new Error('Local login frame cancelled')); return; } + const cancelFrame = createCocosFrameScheduler().request(() => { + this.pending.delete(cancel); resolve(); + }); + const cancel = () => { cancelFrame(); this.pending.delete(cancel); reject(new Error('Local login frame cancelled')); }; + this.pending.add(cancel); + }); + } + + private delay(milliseconds: number): Promise { + return new Promise((resolve, reject) => { + if (this.stopped) { reject(new Error('Local login timer cancelled')); return; } + const timer = window.setTimeout(() => { this.pending.delete(cancel); resolve(); }, milliseconds); + const cancel = () => { window.clearTimeout(timer); this.pending.delete(cancel); reject(new Error('Local login timer cancelled')); }; + this.pending.add(cancel); + }); + } + + update(deltaSeconds: number): void { + if (!this.stopped && this.spinner !== null && this.spinner.activeInHierarchy) this.spinner.angle -= deltaSeconds * 180; + } + + private release(): void { + this.stopped = true; + for (const cancel of [...this.pending]) cancel(); + this.stopButton?.off(Node.EventType.TOUCH_END, this.stopTouch); + this.view?.dispose(); + this.host?.stop(); + } + + private fail(error: unknown): void { + if (this.stopped) return; + this.record({ kind: 'component-failure', data: error }); + try { this.release(); } + catch (cleanup) { + this.record({ kind: 'cleanup-failure', data: cleanup }); + this.view?.showFailure({ error, cleanup }); + return; + } + this.view?.showFailure(error); + if (this.view === null && this.statusLabel !== null) this.statusLabel.string = `验证失败:${String(error)}`; + } + + stop(): void { + if (this.stopped) return; + this.view?.showStopped(); + try { this.release(); } + catch (error) { this.view?.showFailure(error); } + this.record({ kind: 'stopped', data: null }); + } + + onDestroy(): void { + this.stop(); + if (this.loginInstance !== null && isValid(this.loginInstance)) this.loginInstance.destroy(); + } + + /** Local memory only: callers must redact raw account/device IDs before sharing. */ + getDiagnostics() { + return Object.freeze({ events: Object.freeze([...this.evidence]), snapshot: this.host?.getSnapshot(), + pageModel: this.pageModel, stopped: this.stopped, pendingCount: this.pending.size, + firstDrawAt: this.firstDrawAt, spinnerAngle: this.spinner?.angle }); + } +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts.meta b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts.meta new file mode 100644 index 0000000..6737380 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "2597060c-ad16-46f1-89ed-42bfaec74ad9", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-game-entry.ts.meta b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-game-entry.ts.meta new file mode 100644 index 0000000..0f093bc --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-game-entry.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "aea065db-0dfa-4da8-a2cb-f1fe0165d910", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-host.ts.meta b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-host.ts.meta new file mode 100644 index 0000000..53c7288 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-host.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "2c6ce55f-b5a3-4503-bc65-ca12c5537c8f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-transport.ts.meta b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-transport.ts.meta new file mode 100644 index 0000000..dbeb6af --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-transport.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "2bbf94d8-6aa1-4b5f-a94c-c3f12205f46c", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-view.ts b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-view.ts new file mode 100644 index 0000000..85fba6c --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-view.ts @@ -0,0 +1,136 @@ +import { Director, Label, Node, Sprite, UITransform, director } from 'cc'; +import type { FrameScheduler, OverlayModel, PageId, PageModel, PlatformViewPort } from '../../framework/presentation/ui-contracts.ts'; + +interface ViewReferences { + loadingRoot: Node; loginRoot: Node; resultRoot: Node; + statusOverlay: Node; statusLabel: Label; resultLabel: Label; +} + +export function requireNode(root: Node, path: string): Node { + const node = root.getChildByPath(path); + if (node === null) throw new Error(`Local login binding missing node: ${path}`); + return node; +} + +function describeFailure(error: unknown): string { + if (error instanceof Error) { + const details = 'errors' in error ? (error as Error & { errors: unknown[] }).errors : []; + return [error.message, ...details.map(describeFailure)].join('\n'); + } + if (typeof error === 'string') return error; + return JSON.stringify(error, (_key, value: unknown) => value instanceof Error ? describeFailure(value) : value); +} + +/** Projection of Controller models; owns only Cocos bindings and input state. */ +export class CocosLocalLoginView implements PlatformViewPort { + private visitor: Node | null = null; + private version: Label | null = null; + private mask: Node | null = null; + private touch: (() => void) | null = null; + private canLogin = false; + private busy = false; + private terminal = false; + private page: PageId = 'loading'; + + constructor(private readonly refs: ViewReferences) { + for (const [name, value] of Object.entries(refs)) { + if (value === null || value === undefined) throw new Error(`Local login binding missing ${name}`); + } + } + + bindLogin(instance: Node, onLogin: () => void): void { + if (this.visitor !== null || this.terminal) throw new Error('Local login view cannot bind twice or after disposal'); + const visitor = requireNode(instance, 'group-2/游客登录'); + if (visitor.getComponent(Sprite) === null) throw new Error('游客登录 requires Sprite'); + if (visitor.getComponent(UITransform) === null) throw new Error('游客登录 requires UITransform'); + const version = requireNode(instance, 'group-2/版本显示').getComponent(Label); + if (version === null) throw new Error('版本显示 requires Label'); + const mask = requireNode(instance, 'group-2/登录中遮罩'); + const hidden = ['微信登录', '手机登录', 'qq登录', '快速登录', '隐藏快速登录-1', '隐藏快速登录-2', + '登录头像背景框', '登录中头像', '登录中昵称', '登录中动画', '登录中头像框'] + .map(name => requireNode(instance, `group-2/${name}`)); + hidden.forEach(node => { node.active = false; }); + this.visitor = visitor; this.version = version; this.mask = mask; + this.touch = () => { + if (!this.terminal && !this.busy && this.canLogin && this.page === 'login') onLogin(); + }; + visitor.on(Node.EventType.TOUCH_END, this.touch); + mask.active = this.busy; + } + + setVersion(version: number): void { + if (this.version === null) throw new Error('Login instance must be bound before version display'); + this.version.string = String(version); + } + + render(model: PageModel): void { + if (this.terminal) return; + switch (model.page) { + case 'loading': this.refs.statusLabel.string = '正在加载资源和连接本地服务…'; break; + case 'login': this.canLogin = model.canLogin; break; + case 'lobby': + this.refs.resultLabel.string = `登录成功\n玩家 ID:${model.self.playerid}\n昵称:${model.self.nickname}\n豆豆:${model.self.bean}`; + break; + case 'room': throw new Error('Local login view cannot present room'); + } + } + + activate(page: PageId): void { + if (this.terminal) return; + if (page === 'room') throw new Error('Local login view cannot activate room'); + this.page = page; + this.refs.loadingRoot.active = page === 'loading'; + this.refs.loginRoot.active = page === 'login'; + this.refs.resultRoot.active = page === 'lobby'; + this.refs.statusOverlay.active = page === 'loading' || this.busy; + } + + setBusy(busy: boolean): void { + if (this.terminal) return; + this.busy = busy; + if (this.mask !== null) this.mask.active = busy; + this.refs.statusOverlay.active = busy || this.page === 'loading'; + if (busy) this.refs.statusLabel.string = '正在登录,请稍候…'; + } + + showOverlay(model: OverlayModel): void { + if (this.terminal) return; + switch (model.kind) { + case 'none': this.refs.statusOverlay.active = this.busy || this.page === 'loading'; break; + case 'reconnect': + this.refs.statusOverlay.active = true; + this.refs.statusLabel.string = '连接中断,正在重连…'; break; + case 'denial': this.showFailure(model.outcome.data); break; + case 'kicked': this.showFailure(model.data); break; + case 'fatal': this.showFailure(model.error); break; + } + } + + showFailure(error: unknown): void { + this.dispose(); + this.refs.statusOverlay.active = true; + this.refs.statusLabel.string = `验证失败:${describeFailure(error)}`; + } + + showStopped(): void { + if (this.terminal) return; + this.dispose(); + this.refs.statusOverlay.active = true; + this.refs.statusLabel.string = '已停止本次验证'; + } + + dispose(): void { + this.terminal = true; this.canLogin = false; this.busy = false; + if (this.visitor !== null && this.touch !== null) this.visitor.off(Node.EventType.TOUCH_END, this.touch); + this.touch = null; + if (this.mask !== null) this.mask.active = false; + } +} + +export function createCocosFrameScheduler(): FrameScheduler { + return { request(callback) { + const onDraw = () => { director.off(Director.EVENT_AFTER_DRAW, onDraw); callback(); }; + director.on(Director.EVENT_AFTER_DRAW, onDraw); + return () => { director.off(Director.EVENT_AFTER_DRAW, onDraw); }; + } }; +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-view.ts.meta b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-view.ts.meta new file mode 100644 index 0000000..3eb9eab --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-view.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "5af2497c-15f2-4493-be9b-a6391d25b878", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-wire.ts.meta b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-wire.ts.meta new file mode 100644 index 0000000..5db90f4 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/local-login-wire.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "96a339e3-f954-4e96-b07f-9fe124d457f0", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/cocoscreator_projects/framework-tests/local-platform-login/view.test.ts b/cocoscreator_projects/framework-tests/local-platform-login/view.test.ts new file mode 100644 index 0000000..3b7320b --- /dev/null +++ b/cocoscreator_projects/framework-tests/local-platform-login/view.test.ts @@ -0,0 +1,145 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync, existsSync } from 'node:fs'; +import { runInNewContext } from 'node:vm'; +import ts from 'typescript'; +import { EventEmitter } from 'node:events'; + +class Sprite {} +class UITransform {} +class Label { string = ''; } +class Node extends EventEmitter { + static EventType = { TOUCH_END: 'touch-end' }; + active = true; + children: Node[] = []; + components = new Map(); + constructor(public name = '') { super(); } + getComponent(type: unknown) { return this.components.get(type) ?? null; } + getChildByPath(path: string): Node | null { + const [head, ...tail] = path.split('/'); + const child = this.children.find(node => node.name === head); + return child ? (tail.length ? child.getChildByPath(tail.join('/')) : child) : null; + } +} +const director = new EventEmitter(); +const source = new URL('../../YouleNexus/assets/scripts/local-platform-login/local-login-view.ts', import.meta.url); +function load() { + assert.ok(existsSync(source), 'Cocos local login view implementation must exist'); + const exports = {}; + runInNewContext(ts.transpileModule(readFileSync(source, 'utf8'), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, + }).outputText, { exports, require: (name: string) => { + assert.equal(name, 'cc'); + return { Node, Sprite, UITransform, Label, director, Director: { EVENT_AFTER_DRAW: 'draw' } }; + }, Error }); + return exports as any; +} +function fixture() { + const refs = { loadingRoot: new Node(), loginRoot: new Node(), resultRoot: new Node(), + statusOverlay: new Node(), statusLabel: new Label(), resultLabel: new Label() }; + const instance = new Node(); const group = new Node('group-2'); instance.children.push(group); + for (const name of ['游客登录', '版本显示', '微信登录', '手机登录', 'qq登录', '快速登录', + '隐藏快速登录-1', '隐藏快速登录-2', '登录中遮罩', '登录头像背景框', '登录中头像', + '登录中昵称', '登录中动画', '登录中头像框']) { + const node = new Node(name); group.children.push(node); + node.components.set(Sprite, new Sprite()); node.components.set(UITransform, new UITransform()); + node.components.set(Label, new Label()); + } + return { refs, instance, group, visitor: instance.getChildByPath('group-2/游客登录')! }; +} +test('required binding fails explicitly, including missing Sprite', () => { + const { CocosLocalLoginView } = load(); const f = fixture(); + const view = new CocosLocalLoginView(f.refs); + assert.throws(() => view.bindLogin(new Node(), () => {}), /游客登录/); + f.visitor.components.delete(Sprite); + assert.throws(() => view.bindLogin(f.instance, () => {}), /Sprite/); +}); +test('PageModel drives result labels and blocks room presentation', () => { + const { CocosLocalLoginView } = load(); const f = fixture(); const view = new CocosLocalLoginView(f.refs); + view.render({ page: 'lobby', self: { playerid: 19, nickname: '测试玩家', bean: 72, avatar: 'unused' } }); + view.activate('lobby'); + assert.equal(f.refs.resultLabel.string, '登录成功\n玩家 ID:19\n昵称:测试玩家\n豆豆:72'); + assert.equal(f.refs.resultRoot.active, true); assert.equal(f.refs.loginRoot.active, false); + assert.throws(() => view.activate('room'), /room/); +}); +test('busy mask and readiness gate touch, terminal disposal removes ownership', () => { + const { CocosLocalLoginView } = load(); const f = fixture(); const view = new CocosLocalLoginView(f.refs); + let clicks = 0; view.bindLogin(f.instance, () => clicks++); view.setVersion(123); + assert.equal((f.instance.getChildByPath('group-2/版本显示')!.getComponent(Label) as Label).string, '123'); + assert.equal(f.instance.getChildByPath('group-2/微信登录')!.active, false); + view.render({ page: 'login', canLogin: false }); view.activate('login'); f.visitor.emit('touch-end'); + assert.equal(clicks, 0); + view.render({ page: 'login', canLogin: true }); view.setBusy(true); f.visitor.emit('touch-end'); + assert.equal(clicks, 0); assert.equal(f.instance.getChildByPath('group-2/登录中遮罩')!.active, true); + view.setBusy(false); f.visitor.emit('touch-end'); assert.equal(clicks, 1); + view.showFailure({ state: 7, msg: '服务器拒绝' }); view.showOverlay({ kind: 'none' }); + assert.match(f.refs.statusLabel.string, /服务器拒绝/); + f.visitor.emit('touch-end'); assert.equal(clicks, 1); + view.dispose(); assert.equal(f.visitor.listenerCount('touch-end'), 0); +}); +test('frame request is one shot and real cancellation removes pending draw callback', () => { + const { createCocosFrameScheduler } = load(); const scheduler = createCocosFrameScheduler(); let calls = 0; + const cancel = scheduler.request(() => calls++); cancel(); director.emit('draw'); assert.equal(calls, 0); + scheduler.request(() => calls++); director.emit('draw'); director.emit('draw'); assert.equal(calls, 1); + assert.equal(director.listenerCount('draw'), 0); +}); + +test('failure display preserves nested server and cleanup errors', () => { + const { CocosLocalLoginView } = load(); const f = fixture(); + const view = new CocosLocalLoginView(f.refs); + view.showFailure({ error: { state: 8, msg: '版本拒绝' }, cleanup: new Error('socket close failed') }); + assert.match(f.refs.statusLabel.string, /版本拒绝/); + assert.match(f.refs.statusLabel.string, /socket close failed/); +}); + +test('component rejects non-debug/native before creating host or writing browser storage', async () => { + const path = new URL('../../YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts', import.meta.url); + assert.ok(existsSync(path), 'LocalPlatformLogin component must exist'); + for (const [debug, native] of [[false, false], [true, true]]) { + let hosts = 0; const exports: any = {}; + class Component {} + const decorator = (..._args: unknown[]) => (target: unknown) => target; + runInNewContext(ts.transpileModule(readFileSync(path, 'utf8'), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, experimentalDecorators: true }, + }).outputText, { exports, require: (name: string) => { + if (name === 'cc/env') return { DEBUG: debug }; + if (name === 'cc') return { Component, Node, Label, Prefab: class {}, sys: { isNative: native }, + _decorator: { ccclass: decorator, property: () => () => {} } }; + if (name.endsWith('local-login-host.ts')) return { createLocalLoginHost: () => { hosts++; } }; + if (name.endsWith('local-login-view.ts')) return load(); + if (name.endsWith('remote-config-fetcher.ts')) return { HttpConfigFetcher: class {} }; + throw new Error(`Unexpected dependency ${name}`); + }, Error }); + const component = new exports.LocalPlatformLogin(); + await assert.rejects(async () => component.start(), /DEBUG.*browser/); + assert.equal(hosts, 0); + } +}); + +test('component stop cancels pending draw and timer work and disables spinner updates', async () => { + const path = new URL('../../YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts', import.meta.url); + const exports: any = {}; const timers = new Map void>(); let timerId = 0; + runInNewContext(ts.transpileModule(readFileSync(path, 'utf8'), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, experimentalDecorators: true }, + }).outputText, { exports, require: (name: string) => { + if (name === 'cc/env') return { DEBUG: true }; + if (name === 'cc') return { Component: class {}, Node, Label, Prefab: class {}, sys: { isNative: false }, + _decorator: { ccclass: () => (target: unknown) => target, property: () => () => {} } }; + if (name.endsWith('local-login-host.ts')) return {}; + if (name.endsWith('local-login-view.ts')) return load(); + if (name.endsWith('remote-config-fetcher.ts')) return {}; + throw new Error(`Unexpected dependency ${name}`); + }, Error, performance: { now: () => 123 }, window: { + setTimeout: (callback: () => void) => { timers.set(++timerId, callback); return timerId; }, + clearTimeout: (id: number) => timers.delete(id), + } }); + const component = new exports.LocalPlatformLogin(); + const draw = component.nextDraw(); const timer = component.delay(900); + const drawRejection = assert.rejects(draw, /cancelled/); const timerRejection = assert.rejects(timer, /cancelled/); + component.spinner = { angle: 50, activeInHierarchy: true }; + component.update(0.5); assert.equal(component.spinner.angle, -40); + component.stop(); await Promise.all([drawRejection, timerRejection]); + component.update(0.5); assert.equal(component.spinner.angle, -40); + assert.equal(timers.size, 0); assert.equal(director.listenerCount('draw'), 0); + assert.equal(component.getDiagnostics().pendingCount, 0); +});