From 613f2a4d13664cd5c7f41a4a0b5fd0b93f3a9c33 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 3 Jun 2026 08:08:27 +0800 Subject: [PATCH] feat: Enhance scene transition management and HUD scaffolding - Added RequestTransition method to ISceneService for direct scene transition requests without needing Inspector SO references. - Updated DoorTransition and RoomTransition to utilize the new RequestTransition method via ServiceLocator. - Introduced SceneFadeController to manage scene fade effects during transitions, with event channel integration for fade requests. - Created HUDScaffoldWizard to automate HUD Canvas setup, including various UI elements and event channel bindings. - Updated assembly definitions to include necessary dependencies for new UI components. - Added Streaming assets for budget configuration to optimize scene loading and memory management. --- Assets/_Game/Data/Streaming.meta | 8 + .../Streaming/STR_BudgetConfig_Default.asset | 24 + .../STR_BudgetConfig_Default.asset.meta | 8 + Assets/_Game/Scenes/Persistent.unity | 516 +++++++++++++++++- Assets/_Game/Scenes/Testings/TestRoomA.unity | 265 +++++++-- Assets/_Game/Scripts/Core/SceneService.cs | 9 + .../Scripts/Editor/BaseGames.Editor.asmdef | 3 +- .../Editor/Scene/SceneScaffoldTools.cs | 11 + .../Scripts/Editor/UI/HUDScaffoldWizard.cs | 402 ++++++++++++++ .../Editor/UI/HUDScaffoldWizard.cs.meta | 11 + Assets/_Game/Scripts/UI/BaseGames.UI.asmdef | 3 +- .../_Game/Scripts/UI/SceneFadeController.cs | 44 ++ .../Scripts/UI/SceneFadeController.cs.meta | 11 + Assets/_Game/Scripts/World/DoorTransition.cs | 17 +- Assets/_Game/Scripts/World/RoomTransition.cs | 17 +- 15 files changed, 1285 insertions(+), 64 deletions(-) create mode 100644 Assets/_Game/Data/Streaming.meta create mode 100644 Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset create mode 100644 Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset.meta create mode 100644 Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs create mode 100644 Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs.meta create mode 100644 Assets/_Game/Scripts/UI/SceneFadeController.cs create mode 100644 Assets/_Game/Scripts/UI/SceneFadeController.cs.meta diff --git a/Assets/_Game/Data/Streaming.meta b/Assets/_Game/Data/Streaming.meta new file mode 100644 index 0000000..22e5fb7 --- /dev/null +++ b/Assets/_Game/Data/Streaming.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 712983ef837dc2745b7c72eb346da52f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset b/Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset new file mode 100644 index 0000000..8d87153 --- /dev/null +++ b/Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset @@ -0,0 +1,24 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 65e349713a81a3846a654ea9f428ef99, type: 3} + m_Name: STR_BudgetConfig_Default + m_EditorClassIdentifier: + MaxDormantRooms: 6 + MaxMemoryMB: 300 + MaxConcurrentLoads: 2 + PreloadLookaheadHops: 2 + CoolingDuration: 6 + LifecycleActivatePerFrame: 8 + AtmosphericFadeOutDuration: 0.25 + AtmosphericFadeInDuration: 0.3 + RegionNameDisplayDuration: 1.2 + SeamlessWaitTimeout: 10 diff --git a/Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset.meta b/Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset.meta new file mode 100644 index 0000000..021943e --- /dev/null +++ b/Assets/_Game/Data/Streaming/STR_BudgetConfig_Default.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cd50a6ced54f54a43bda5a4d39b7865e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scenes/Persistent.unity b/Assets/_Game/Scenes/Persistent.unity index f2f12d3..117f930 100644 --- a/Assets/_Game/Scenes/Persistent.unity +++ b/Assets/_Game/Scenes/Persistent.unity @@ -281,6 +281,76 @@ AudioSource: m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 +--- !u!1 &157732602 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 157732603} + - component: {fileID: 157732605} + - component: {fileID: 157732604} + m_Layer: 0 + m_Name: SYS_RoomStreamingManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &157732603 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157732602} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 162844601} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &157732604 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157732602} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a6db86de2f8c90548ba7e185b9cd5df6, type: 3} + m_Name: + m_EditorClassIdentifier: + _streamingManagerRef: {fileID: 157732605} + _onFadeOutRequest: {fileID: 11400000, guid: a17901d6793dcf2409e2672ffb383208, type: 2} + _onFadeInRequest: {fileID: 11400000, guid: f8d520fe699782b4184ff72ce5200c25, type: 2} + _onRegionNameDisplay: {fileID: 0} + _onSceneWorldStateRestored: {fileID: 0} + _mapDatabase: {fileID: 0} + _budget: {fileID: 11400000, guid: cd50a6ced54f54a43bda5a4d39b7865e, type: 2} +--- !u!114 &157732605 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157732602} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0c17eea5df8bd684599801eb7dd7c41f, type: 3} + m_Name: + m_EditorClassIdentifier: + _mapDatabase: {fileID: 0} + _budget: {fileID: 11400000, guid: cd50a6ced54f54a43bda5a4d39b7865e, type: 2} + _onRoomEntered: {fileID: 11400000, guid: 133c65f23b6631846ab619edb0e44708, type: 2} + _onRoomPreloaded: {fileID: 0} + _onRoomActivated: {fileID: 0} + _unitsPerGrid: 16 --- !u!1 &162844600 GameObject: m_ObjectHideFlags: 0 @@ -322,6 +392,8 @@ Transform: - {fileID: 225262501} - {fileID: 737017261} - {fileID: 1208346588} + - {fileID: 1151871939} + - {fileID: 157732603} m_Father: {fileID: 1218230064} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &225262500 @@ -432,8 +504,8 @@ MonoBehaviour: _sceneService: {fileID: 1321234353} _eventChannelRegistry: {fileID: 750267177} _saveManager: {fileID: 737017262} - _checkpointService: {fileID: 1308486565} - _primaryListener: {fileID: 0} + _checkpointService: {fileID: 1151871940} + _primaryListener: {fileID: 464414703} --- !u!1 &307714525 GameObject: m_ObjectHideFlags: 0 @@ -554,6 +626,176 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 307714525} m_CullTransparentMesh: 1 +--- !u!1 &315072829 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 315072830} + - component: {fileID: 315072836} + - component: {fileID: 315072835} + - component: {fileID: 315072834} + - component: {fileID: 315072833} + - component: {fileID: 315072832} + - component: {fileID: 315072831} + m_Layer: 0 + m_Name: VCamA + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &315072830 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 315072829} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 537419888} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &315072831 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 315072829} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 886251e9a18ece04ea8e61686c173e1b, type: 3} + m_Name: + m_EditorClassIdentifier: + CameraDistance: 10 + DeadZoneDepth: 0 + Composition: + ScreenPosition: {x: 0, y: -0.15} + DeadZone: + Enabled: 1 + Size: {x: 0.15, y: 0.05} + HardLimits: + Enabled: 0 + Size: {x: 0.8, y: 0.8} + Offset: {x: 0, y: 0} + CenterOnActivate: 1 + TargetOffset: {x: 0, y: 0, z: 0} + Damping: {x: 0.5, y: 0, z: 0} + Lookahead: + Enabled: 1 + Time: 0.28 + Smoothing: 5 + IgnoreY: 1 +--- !u!114 &315072832 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 315072829} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a12cbb2380ff137459b7ba80d492733f, type: 3} + m_Name: + m_EditorClassIdentifier: + _restScale: 0.25 + _speedAtFullLookahead: 12 + _speedSmoothing: 5 +--- !u!114 &315072833 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 315072829} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cb5a7225ab133e74b81d1f0ae22ccc77, type: 3} + m_Name: + m_EditorClassIdentifier: + _dampingDown: 0.08 + _dampingUp: 0.65 +--- !u!114 &315072834 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 315072829} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7e2e7849ca8d76f438c4b2899c9fb421, type: 3} + m_Name: + m_EditorClassIdentifier: + LockX: 0 + LockY: 0 + LockedX: 0 + LockedY: 0 +--- !u!114 &315072835 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 315072829} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4d75924d76b05344aa410607bc57db98, type: 3} + m_Name: + m_EditorClassIdentifier: + BoundingVolume: {fileID: 0} + SlowingDistance: 0 +--- !u!114 &315072836 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 315072829} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f9dfa5b682dcd46bda6128250e975f58, type: 3} + m_Name: + m_EditorClassIdentifier: + Priority: + Enabled: 0 + m_Value: 0 + OutputChannel: 1 + StandbyUpdate: 2 + m_StreamingVersion: 20241001 + m_LegacyPriority: 0 + Target: + TrackingTarget: {fileID: 0} + LookAtTarget: {fileID: 0} + CustomLookAtTarget: 0 + Lens: + FieldOfView: 40 + OrthographicSize: 10 + NearClipPlane: 0.1 + FarClipPlane: 5000 + Dutch: 0 + ModeOverride: 0 + PhysicalProperties: + GateFit: 2 + SensorSize: {x: 21.946, y: 16.002} + LensShift: {x: 0, y: 0} + FocusDistance: 10 + Iso: 200 + ShutterSpeed: 0.005 + Aperture: 16 + BladeCount: 5 + Curvature: {x: 2, y: 11} + BarrelClipping: 0.25 + Anamorphism: 0 + BlendHint: 0 --- !u!1 &320356782 GameObject: m_ObjectHideFlags: 0 @@ -971,7 +1213,7 @@ Camera: height: 1 near clip plane: 0.1 far clip plane: 5000 - field of view: 10 + field of view: 40 orthographic: 0 orthographic size: 10 m_Depth: 0 @@ -998,7 +1240,7 @@ Transform: m_GameObject: {fileID: 464414701} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -18.954266, y: 5.5, z: -64} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -1080,6 +1322,8 @@ Transform: - {fileID: 464414705} - {fileID: 1838586610} - {fileID: 1341315320} + - {fileID: 315072830} + - {fileID: 1852813069} m_Father: {fileID: 1218230064} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &545025334 @@ -1255,6 +1499,54 @@ MonoBehaviour: _onMapOpen: {fileID: 11400000, guid: b972e8c7aec9da34d80381e643d49cb2, type: 2} _onCharmPanelOpen: {fileID: 0} _onSpellSelectOpen: {fileID: 0} +--- !u!1 &712830947 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 712830948} + - component: {fileID: 712830949} + m_Layer: 0 + m_Name: SYS_SceneFade + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &712830948 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 712830947} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1810445798} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &712830949 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 712830947} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5e4fa87eedbe2f24db81bdf66714343f, type: 3} + m_Name: + m_EditorClassIdentifier: + _fadeOut: {fileID: 0} + _fadeIn: {fileID: 0} + _onFadeOutRequest: {fileID: 11400000, guid: a17901d6793dcf2409e2672ffb383208, type: 2} + _onFadeInRequest: {fileID: 11400000, guid: f8d520fe699782b4184ff72ce5200c25, type: 2} --- !u!1 &737017260 GameObject: m_ObjectHideFlags: 0 @@ -1728,6 +2020,51 @@ MonoBehaviour: _onLoadingStarted: {fileID: 11400000, guid: 78de6bbfc7741ad47ad568ba0b65fde0, type: 2} _onLoadingComplete: {fileID: 11400000, guid: 1e4580d266c4bd44c8b25a70d26608d3, type: 2} _onLoadingProgressUpdated: {fileID: 11400000, guid: 2d366d52ee6a8214c8cd94d14e9ccd15, type: 2} +--- !u!1 &1151871938 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1151871939} + - component: {fileID: 1151871940} + m_Layer: 0 + m_Name: CheckpointService + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1151871939 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1151871938} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 162844601} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1151871940 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1151871938} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 486e785d32d1c4c468a4eb0fd4cf1822, type: 3} + m_Name: + m_EditorClassIdentifier: + _onSceneLoaded: {fileID: 11400000, guid: ce0e5d2372599c74aabdac63c1620289, type: 2} --- !u!1 &1189402268 GameObject: m_ObjectHideFlags: 0 @@ -2958,6 +3295,7 @@ Transform: - {fileID: 1758098875} - {fileID: 1710356906} - {fileID: 622211655} + - {fileID: 712830948} m_Father: {fileID: 1218230064} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1838586609 @@ -3065,6 +3403,176 @@ MonoBehaviour: _lensConfig: {fileID: 11400000, guid: 12fec951ce5cc3d499b00e38b5dfa14a, type: 2} _onPlayerSpawned: {fileID: 11400000, guid: 7e2c7e614f6627b449a244ab44443adf, type: 2} _showDebugOverlay: 0 +--- !u!1 &1852813068 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1852813069} + - component: {fileID: 1852813075} + - component: {fileID: 1852813074} + - component: {fileID: 1852813073} + - component: {fileID: 1852813072} + - component: {fileID: 1852813071} + - component: {fileID: 1852813070} + m_Layer: 0 + m_Name: VCamB + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1852813069 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1852813068} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 537419888} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1852813070 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1852813068} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 886251e9a18ece04ea8e61686c173e1b, type: 3} + m_Name: + m_EditorClassIdentifier: + CameraDistance: 10 + DeadZoneDepth: 0 + Composition: + ScreenPosition: {x: 0, y: -0.15} + DeadZone: + Enabled: 1 + Size: {x: 0.15, y: 0.05} + HardLimits: + Enabled: 0 + Size: {x: 0.8, y: 0.8} + Offset: {x: 0, y: 0} + CenterOnActivate: 1 + TargetOffset: {x: 0, y: 0, z: 0} + Damping: {x: 0.5, y: 0, z: 0} + Lookahead: + Enabled: 1 + Time: 0.28 + Smoothing: 5 + IgnoreY: 1 +--- !u!114 &1852813071 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1852813068} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a12cbb2380ff137459b7ba80d492733f, type: 3} + m_Name: + m_EditorClassIdentifier: + _restScale: 0.25 + _speedAtFullLookahead: 12 + _speedSmoothing: 5 +--- !u!114 &1852813072 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1852813068} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cb5a7225ab133e74b81d1f0ae22ccc77, type: 3} + m_Name: + m_EditorClassIdentifier: + _dampingDown: 0.08 + _dampingUp: 0.65 +--- !u!114 &1852813073 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1852813068} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7e2e7849ca8d76f438c4b2899c9fb421, type: 3} + m_Name: + m_EditorClassIdentifier: + LockX: 0 + LockY: 0 + LockedX: 0 + LockedY: 0 +--- !u!114 &1852813074 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1852813068} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4d75924d76b05344aa410607bc57db98, type: 3} + m_Name: + m_EditorClassIdentifier: + BoundingVolume: {fileID: 0} + SlowingDistance: 0 +--- !u!114 &1852813075 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1852813068} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f9dfa5b682dcd46bda6128250e975f58, type: 3} + m_Name: + m_EditorClassIdentifier: + Priority: + Enabled: 0 + m_Value: 0 + OutputChannel: 1 + StandbyUpdate: 2 + m_StreamingVersion: 20241001 + m_LegacyPriority: 0 + Target: + TrackingTarget: {fileID: 0} + LookAtTarget: {fileID: 0} + CustomLookAtTarget: 0 + Lens: + FieldOfView: 40 + OrthographicSize: 10 + NearClipPlane: 0.1 + FarClipPlane: 5000 + Dutch: 0 + ModeOverride: 0 + PhysicalProperties: + GateFit: 2 + SensorSize: {x: 21.946, y: 16.002} + LensShift: {x: 0, y: 0} + FocusDistance: 10 + Iso: 200 + ShutterSpeed: 0.005 + Aperture: 16 + BladeCount: 5 + Curvature: {x: 2, y: 11} + BarrelClipping: 0.25 + Anamorphism: 0 + BlendHint: 0 --- !u!1 &1859511082 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/_Game/Scenes/Testings/TestRoomA.unity b/Assets/_Game/Scenes/Testings/TestRoomA.unity index e62a772..79a8d3c 100644 --- a/Assets/_Game/Scenes/Testings/TestRoomA.unity +++ b/Assets/_Game/Scenes/Testings/TestRoomA.unity @@ -5627,6 +5627,102 @@ MonoBehaviour: _noiseFrequency: 1 _dedicatedCamera: {fileID: 840207430} _dedicatedPriority: 20 +--- !u!1 &318931261 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 318931262} + - component: {fileID: 318931264} + - component: {fileID: 318931263} + m_Layer: 25 + m_Name: '[Shape_Polygon]' + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &318931262 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 318931261} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.291, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 831117707} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &318931263 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 318931261} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e11b931e351246344aec20aa35489592, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!60 &318931264 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 318931261} + m_Enabled: 1 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 1 + m_UsedByEffector: 0 + m_UsedByComposite: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0, y: 0} + oldSize: {x: 0, y: 0} + newSize: {x: 0, y: 0} + adaptiveTilingThreshold: 0 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: 0, y: 0.3} + - {x: 0.6047108, y: 0.3348503} + - {x: 0.7406552, y: 0.12676334} + - {x: 0.7771857, y: -0.2715292} + - {x: -0.79761577, y: -0.28302938} + - {x: -0.78284264, y: -0.07071018} + m_UseDelaunayMesh: 0 --- !u!1 &320063820 GameObject: m_ObjectHideFlags: 0 @@ -12912,7 +13008,7 @@ Transform: m_GameObject: {fileID: 785719612} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 0, y: 0.51, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -12992,10 +13088,10 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: 652617267 + m_SortingLayer: -6 m_SortingOrder: 0 - m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, type: 3} + m_Sprite: {fileID: 1805018772, guid: 97a250d50f6857443aa7d823bbe36304, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} m_FlipX: 0 m_FlipY: 0 @@ -13798,9 +13894,9 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 831117707} - - component: {fileID: 831117706} - component: {fileID: 831117705} - component: {fileID: 831117704} + - component: {fileID: 831117708} m_Layer: 25 m_Name: ContactDamageZone m_TagString: Untagged @@ -13838,9 +13934,25 @@ MonoBehaviour: _id: _rivalHitBoxMask: serializedVersion: 2 - m_Bits: 0 ---- !u!58 &831117706 -CircleCollider2D: + m_Bits: 512 +--- !u!4 &831117707 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 831117703} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 318931262} + m_Father: {fileID: 1864792379} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!70 &831117708 +CapsuleCollider2D: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -13871,24 +13983,9 @@ CircleCollider2D: m_IsTrigger: 1 m_UsedByEffector: 0 m_UsedByComposite: 0 - m_Offset: {x: 0, y: 0} - serializedVersion: 2 - m_Radius: 0.4 ---- !u!4 &831117707 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 831117703} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 1864792379} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_Offset: {x: 0.40729484, y: 0.32778132} + m_Size: {x: 0.7284849, y: 0.65556294} + m_Direction: 1 --- !u!1 &837279661 GameObject: m_ObjectHideFlags: 0 @@ -210626,8 +210723,8 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 1758953875} - - component: {fileID: 1758953874} - component: {fileID: 1758953873} + - component: {fileID: 1758953876} m_Layer: 27 m_Name: HurtBox m_TagString: Untagged @@ -210649,8 +210746,23 @@ MonoBehaviour: m_EditorClassIdentifier: _onDamageDealt: {fileID: 0} _onHitConfirmed: {fileID: 11400000, guid: a67d56f5124e0db4f98f326c74be8091, type: 2} ---- !u!70 &1758953874 -CapsuleCollider2D: +--- !u!4 &1758953875 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1758953872} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1864792379} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!61 &1758953876 +BoxCollider2D: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -210681,24 +210793,19 @@ CapsuleCollider2D: m_IsTrigger: 1 m_UsedByEffector: 0 m_UsedByComposite: 0 - m_Offset: {x: 0, y: 0} - m_Size: {x: 0.55, y: 0.75} - m_Direction: 0 ---- !u!4 &1758953875 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1758953872} + m_Offset: {x: -0.01498282, y: 0.4} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0, y: 0} + oldSize: {x: 0, y: 0} + newSize: {x: 0, y: 0} + adaptiveTilingThreshold: 0 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 1864792379} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_Size: {x: 1.5648925, y: 0.8} + m_EdgeRadius: 0 --- !u!1 &1766894770 GameObject: m_ObjectHideFlags: 0 @@ -212034,6 +212141,7 @@ GameObject: - component: {fileID: 1864792372} - component: {fileID: 1864792371} - component: {fileID: 1864792370} + - component: {fileID: 1864792380} m_Layer: 13 m_Name: ENM_CaoZhi m_TagString: Untagged @@ -212282,7 +212390,7 @@ BoxCollider2D: m_IsTrigger: 0 m_UsedByEffector: 0 m_UsedByComposite: 0 - m_Offset: {x: 0, y: 0} + m_Offset: {x: -0.01498282, y: 0.4} m_SpriteTilingProperty: border: {x: 0, y: 0, z: 0, w: 0} pivot: {x: 0, y: 0} @@ -212293,7 +212401,7 @@ BoxCollider2D: adaptiveTiling: 0 m_AutoTiling: 0 serializedVersion: 2 - m_Size: {x: 0.6, y: 0.8} + m_Size: {x: 1.5648925, y: 0.8} m_EdgeRadius: 0 --- !u!50 &1864792378 Rigidbody2D: @@ -212341,6 +212449,67 @@ Transform: - {fileID: 1650269713} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1864792380 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1864792369} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cdaa3305fa954c45a80c9662aa6f425, type: 3} + m_Name: + m_EditorClassIdentifier: + m_GraphName: Behavior Tree + m_Index: 0 + m_Data: + m_TaskData: + - m_ObjectType: Opsive.BehaviorDesigner.Runtime.Tasks.Actions.StackedAction + m_ValueHashes: + m_LongValueHashes: 30b77c28f48d1b2966cb3cf98c4bd96480f6501333363a66622a4da7e5588b5847627e5c3afba34af262c60c48f0bccad0ed2caea3f34ac163eb2caea3f34ac1a8724a1098fa102319cb31fdb78bb19fc8fad5b29007ef3a396f2e031315f24fe4a06bb554569d8946fff10c773614c9 + m_ValuePositions: 000000000200000004000000060000000a0000003100000035000000390000003d0000004100000045000000460000004a0000004a000000 + m_Values: 0000ffffffff010000004261736547616d65732e456e656d6965732e41492e42445f506174726f6c576179706f696e7473020000000000000001000000000000009a99993e000000000000000000 + m_UnityObjects: + - {fileID: 5705765} + - {fileID: 1398696825} + m_Version: 3.4 + m_EventTaskData: + - m_ObjectType: Opsive.BehaviorDesigner.Runtime.Tasks.Events.Start + m_ValueHashes: + m_LongValueHashes: 59405171878141b1 + m_ValuePositions: 00000000 + m_Values: 0000 + m_UnityObjects: [] + m_Version: 3.4 + m_SharedVariableData: [] + m_DisabledEventNodesData: [] + m_DisabledLogicNodesData: [] + m_UniqueID: 206339031 + m_LogicNodePropertiesData: + - m_ObjectType: Opsive.GraphDesigner.Runtime.LogicNodeProperties + m_ValueHashes: + m_LongValueHashes: bc124df8ef5e104cf36ca30dee0de9958dd19827f48d1b29bd8814239a1bb7642f6406e2580d1e294f869cc9196b0c27aea3bc2e18d5b803a034c7c2b541f015557e981535906112a98ff48b9e8a66b37f542abda0a249c23a7f01b1f88d1a7b + m_ValuePositions: 00000000240000002c000000300000003000000030000000400000004100000045000000450000004700000049000000 + m_Values: 35363062633134302d633833632d343334312d393338392d3063396165323031616638660000a54300008c4200000043000000000000000000000000000000000000000000ffffffff00 + m_UnityObjects: [] + m_Version: 3.4 + m_EventNodePropertiesData: + - m_ObjectType: Opsive.GraphDesigner.Runtime.NodeProperties + m_ValueHashes: + m_LongValueHashes: bc124df8ef5e104cf36ca30dee0de9958dd19827f48d1b29bd8814239a1bb7642f6406e2580d1e294f869cc9196b0c27aea3bc2e18d5b803 + m_ValuePositions: 00000000240000002c00000030000000300000003000000040000000 + m_Values: 31383566623665622d343438392d346266612d616463652d3434353131393731373638310000a543000000000000dc420000000000000000000000000000000000 + m_UnityObjects: [] + m_Version: 3.4 + m_GroupPropertiesData: [] + m_SharedVariableGroupsData: [] + m_StartWhenEnabled: 1 + m_PauseWhenDisabled: 0 + m_UpdateMode: 0 + m_EvaluationType: 0 + m_MaxEvaluationCount: 1 + m_Subtree: {fileID: 0} --- !u!1 &1865796628 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/_Game/Scripts/Core/SceneService.cs b/Assets/_Game/Scripts/Core/SceneService.cs index adff7c8..aeedf4b 100644 --- a/Assets/_Game/Scripts/Core/SceneService.cs +++ b/Assets/_Game/Scripts/Core/SceneService.cs @@ -10,6 +10,12 @@ namespace BaseGames.Core /// public interface ISceneService { + /// + /// 直接触发场景过渡请求(无需 Inspector SO 引用)。 + /// 游戏场景物体(RoomTransition、DoorTransition 等)通过 ServiceLocator 调用此方法, + /// 替代手动拖拽 SceneLoadRequestEventChannelSO。 + /// + void RequestTransition(SceneLoadRequest request); /// 异步加载指定场景(Additive)。 IEnumerator LoadSceneCoroutine(SceneLoadRequest request); /// 卸载当前房间场景(保留 Persistent)。 @@ -69,6 +75,9 @@ namespace BaseGames.Core private void OnDisable() => _subscriptions.Clear(); + /// + public void RequestTransition(SceneLoadRequest request) => HandleSceneLoadRequest(request); + private void HandleSceneLoadRequest(SceneLoadRequest request) { // Seamless / AtmosphericFade 由 ITransitionDirector 处理(需要预加载支持) diff --git a/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef b/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef index 876c852..603f3f4 100644 --- a/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef +++ b/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef @@ -35,7 +35,8 @@ "BaseGames.EventChain", "BaseGames.VFX", "BaseGames.Localization", - "Unity.InputSystem" + "Unity.InputSystem", + "Unity.TextMeshPro" ], "includePlatforms": [ "Editor" diff --git a/Assets/_Game/Scripts/Editor/Scene/SceneScaffoldTools.cs b/Assets/_Game/Scripts/Editor/Scene/SceneScaffoldTools.cs index f100eae..279216e 100644 --- a/Assets/_Game/Scripts/Editor/Scene/SceneScaffoldTools.cs +++ b/Assets/_Game/Scripts/Editor/Scene/SceneScaffoldTools.cs @@ -168,8 +168,19 @@ namespace BaseGames.Editor AssignAsset(loadingMgr, "_onLoadingProgressUpdated", report, false, "EVT_LoadingProgressUpdated"); loadingCanvasGo.SetActive(false); + // ── SceneFadeController(场景切换黑幕)─────────────────────────── + // 纯逻辑节点:监听淡出/淡入事件后调用 SceneFeedback.Play()。 + // 实际 UI 效果完全由 SceneFeedback 内部的 MMF_Player 负责。 + GameObject fadeCtrGo = GetOrCreateChild(ui.transform, "SYS_SceneFade").gameObject; + SceneFadeController fadeCtr = GetOrAddComponent(fadeCtrGo); + AssignAsset(fadeCtr, "_onFadeOutRequest", report, false, "EVT_FadeOutRequest"); + AssignAsset(fadeCtr, "_onFadeInRequest", report, false, "EVT_FadeInRequest"); + report.Add("Canvas_Splash:请将工作室 Logo CanvasGroup 赋给 _studioLogoGroup,游戏标题 CanvasGroup 赋给 _gameTitleGroup。"); report.Add("Canvas_Loading:请为 LoadingScreenManager 绑定 _progressBar(Slider)和 _loadingPanel(GameObject)。"); + report.Add("SYS_SceneFade:请创建两个带 MMF_Player 的 SceneFeedback(淡出/淡入)," + + "配置完毕后分别拖入 SceneFadeController._fadeOut / _fadeIn。" + + "MMF_Player 总时长应 ≤ SceneService._sceneFadeDuration(默认 0.4 s)。"); EnsureAudioSources(audioManagerGo, audioManager, report); diff --git a/Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs b/Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs new file mode 100644 index 0000000..ebd35ff --- /dev/null +++ b/Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs @@ -0,0 +1,402 @@ +using System.Collections.Generic; +using BaseGames.UI; +using BaseGames.UI.HUD; +using TMPro; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.UI; + +namespace BaseGames.Editor.UI +{ + /// + /// HUD 脚手架工具(架构 10_UIModule §HUD)。 + /// 在当前活动场景中生成完整的 HUD Canvas 层级结构并自动绑定已存在的事件频道资产。 + /// 执行路径:BaseGames ▸ Scene ▸ Setup ▸ Scaffold HUD Canvas + /// + public static class HUDScaffoldWizard + { + [MenuItem("BaseGames/Scene/Setup/Scaffold HUD Canvas", priority = 203)] + public static void ScaffoldHUDCanvas() + { + var report = new List(); + Undo.SetCurrentGroupName("Scaffold HUD Canvas"); + int undoGroup = Undo.GetCurrentGroup(); + + // ── Canvas ──────────────────────────────────────────────────────── + GameObject canvasGo = GetOrCreateRootCanvas("HUD Canvas", 0); + + // ── HUDRoot ─────────────────────────────────────────────────────── + GameObject hudRootGo = GetOrCreateChild(canvasGo.transform, "HUDRoot").gameObject; + HUDController hudCtrl = GetOrAddComponent(hudRootGo); + + // 若场景中已存在 UIManager,自动将 _hudRoot 指向本 HUDRoot + UIManager uiManager = Object.FindFirstObjectByType(); + if (uiManager != null) + AssignRef(uiManager, "_hudRoot", hudRootGo); + + // ── HP 区域 ─────────────────────────────────────────────────────── + GameObject hpContainerGo = GetOrCreateChild(hudRootGo.transform, "HP_Container").gameObject; + var hpLayout = GetOrAddComponent(hpContainerGo); + hpLayout.childForceExpandWidth = false; + hpLayout.childForceExpandHeight = false; + hpLayout.spacing = 4f; + + // ── 魄元(Soul)量条 ────────────────────────────────────────────── + GameObject soulGaugeGo = GetOrCreateChild(hudRootGo.transform, "Gauge_Soul").gameObject; + Image soulFill = GetOrAddComponent(soulGaugeGo); + soulFill.type = Image.Type.Filled; + soulFill.fillMethod = Image.FillMethod.Horizontal; + soulFill.fillAmount = 1f; + + // ── 灵力(Spirit)量条 ──────────────────────────────────────────── + GameObject spiritGaugeGo = GetOrCreateChild(hudRootGo.transform, "Gauge_Spirit").gameObject; + Image spiritFill = GetOrAddComponent(spiritGaugeGo); + spiritFill.type = Image.Type.Filled; + spiritFill.fillMethod = Image.FillMethod.Horizontal; + spiritFill.fillAmount = 1f; + + // ── 灵铢(LingZhu)文本 ─────────────────────────────────────────── + GameObject lingZhuGo = GetOrCreateChild(hudRootGo.transform, "Text_LingZhu").gameObject; + TMP_Text lingZhuText = GetOrAddComponent(lingZhuGo); + lingZhuText.text = "0"; + + // ── 回春图标(Spring Charges)───────────────────────────────────── + GameObject springContainerGo = GetOrCreateChild(hudRootGo.transform, "Spring_Container").gameObject; + var springLayout = GetOrAddComponent(springContainerGo); + springLayout.childForceExpandWidth = false; + springLayout.childForceExpandHeight = false; + springLayout.spacing = 4f; + + // ── 形态图标(Form Icons × 4)──────────────────────────────────── + const int kFormIconCount = 4; + GameObject formIconsRoot = GetOrCreateChild(hudRootGo.transform, "FormIcons").gameObject; + Image[] formImages = new Image[kFormIconCount]; + for (int i = 0; i < kFormIconCount; i++) + { + GameObject iconGo = GetOrCreateChild(formIconsRoot.transform, $"FormIcon_{i}").gameObject; + formImages[i] = GetOrAddComponent(iconGo); + } + + // ── 交互提示(InteractPrompt)───────────────────────────────────── + GameObject interactPromptGo = GetOrCreateChild(hudRootGo.transform, "InteractPrompt").gameObject; + InteractPromptWidget interactWidget = GetOrAddComponent(interactPromptGo); + + GameObject promptRootGo = GetOrCreateChild(interactPromptGo.transform, "Root").gameObject; + GameObject promptKeyIconGo = GetOrCreateChild(promptRootGo.transform, "KeyIcon").gameObject; + Image keyIcon = GetOrAddComponent(promptKeyIconGo); + GameObject promptLabelGo = GetOrCreateChild(promptRootGo.transform, "LabelText").gameObject; + TMP_Text promptLabel = GetOrAddComponent(promptLabelGo); + promptLabel.text = "互动"; + promptRootGo.SetActive(false); + + AssignRef(interactWidget, "_keyIcon", keyIcon); + AssignRef(interactWidget, "_labelText", promptLabel); + AssignRef(interactWidget, "_root", promptRootGo); + + // ── 法术槽(SpellSlot)─────────────────────────────────────────── + GameObject spellSlotGo = GetOrCreateChild(hudRootGo.transform, "SpellSlot").gameObject; + SpellSlotWidget spellWidget = GetOrAddComponent(spellSlotGo); + + GameObject spellIconGo = GetOrCreateChild(spellSlotGo.transform, "IconImage").gameObject; + Image spellIcon = GetOrAddComponent(spellIconGo); + GameObject spellCdGo = GetOrCreateChild(spellSlotGo.transform, "CooldownFill").gameObject; + Image spellCd = GetOrAddComponent(spellCdGo); + spellCd.type = Image.Type.Filled; + spellCd.fillMethod = Image.FillMethod.Radial360; + spellCd.fillAmount = 0f; + GameObject spellEmptyGo = GetOrCreateChild(spellSlotGo.transform, "EmptySlot").gameObject; + + AssignRef(spellWidget, "_iconImage", spellIcon); + AssignRef(spellWidget, "_cooldownFill", spellCd); + AssignRef(spellWidget, "_emptySlot", spellEmptyGo); + + // ── 工具栏(ToolHUD × 2 slots)────────────────────────────────── + GameObject toolHUDGo = GetOrCreateChild(hudRootGo.transform, "ToolHUD").gameObject; + ToolHUD toolHUD = GetOrAddComponent(toolHUDGo); + const int kToolSlotCount = 2; + var toolSlots = new ToolSlotUI[kToolSlotCount]; + + for (int i = 0; i < kToolSlotCount; i++) + { + GameObject slotGo = GetOrCreateChild(toolHUDGo.transform, $"ToolSlot_{i}").gameObject; + ToolSlotUI slotUI = GetOrAddComponent(slotGo); + + GameObject iconGo = GetOrCreateChild(slotGo.transform, "Icon").gameObject; + Image slotIcon = GetOrAddComponent(iconGo); + GameObject usesGo = GetOrCreateChild(slotGo.transform, "UsesText").gameObject; + TMP_Text usesText = GetOrAddComponent(usesGo); + usesText.text = "0"; + GameObject cdGo = GetOrCreateChild(slotGo.transform, "CooldownMask").gameObject; + Image cdMask = GetOrAddComponent(cdGo); + cdMask.type = Image.Type.Filled; + cdMask.fillMethod = Image.FillMethod.Horizontal; + cdMask.fillAmount = 0f; + + AssignRef(slotUI, "_icon", slotIcon); + AssignRef(slotUI, "_usesText", usesText); + AssignRef(slotUI, "_cooldownMask", cdMask); + toolSlots[i] = slotUI; + } + + AssignArrayRefs(toolHUD, "_slots", toolSlots, report); + + // ── Boss 血条(BossHPBar)──────────────────────────────────────── + GameObject bossBarGo = GetOrCreateChild(hudRootGo.transform, "BossHPBar").gameObject; + bossBarGo.SetActive(false); + BossHPBar bossHPBar = GetOrAddComponent(bossBarGo); + + GameObject bossNameGo = GetOrCreateChild(bossBarGo.transform, "BossName").gameObject; + TMP_Text bossName = GetOrAddComponent(bossNameGo); + bossName.text = "Boss 名称"; + GameObject bossHPFillGo = GetOrCreateChild(bossBarGo.transform, "HPFill").gameObject; + Image bossHPFill = GetOrAddComponent(bossHPFillGo); + bossHPFill.type = Image.Type.Filled; + bossHPFill.fillMethod = Image.FillMethod.Horizontal; + bossHPFill.fillAmount = 1f; + GameObject phaseRootGo = GetOrCreateChild(bossBarGo.transform, "PhaseMarkersRoot").gameObject; + + AssignRef(bossHPBar, "_bossNameText", bossName); + AssignRef(bossHPBar, "_hpFill", bossHPFill); + AssignRef(bossHPBar, "_phaseMarkersRoot", phaseRootGo.transform); + + // ── 状态效果 HUD(StatusEffectHUD)────────────────────────────── + GameObject statusHUDGo = GetOrCreateChild(hudRootGo.transform, "StatusEffectHUD").gameObject; + StatusEffectHUD statusHUD = GetOrAddComponent(statusHUDGo); + + GameObject statusTemplateGo = GetOrCreateChild(statusHUDGo.transform, "SlotTemplate").gameObject; + statusTemplateGo.SetActive(false); + GetOrAddComponent(statusTemplateGo); + GameObject statusContainerGo = GetOrCreateChild(statusHUDGo.transform, "Container").gameObject; + var statusLayout = GetOrAddComponent(statusContainerGo); + statusLayout.childForceExpandWidth = false; + statusLayout.spacing = 4f; + + AssignRef(statusHUD, "_slotTemplate", statusTemplateGo); + AssignRef(statusHUD, "_container", statusContainerGo.transform); + + // ── 任务追踪(QuestTracker)────────────────────────────────────── + GameObject questTrackerGo = GetOrCreateChild(hudRootGo.transform, "QuestTracker").gameObject; + QuestTrackerWidget questWidget = GetOrAddComponent(questTrackerGo); + + GameObject questTitleGo = GetOrCreateChild(questTrackerGo.transform, "QuestTitle").gameObject; + TMP_Text questTitle = GetOrAddComponent(questTitleGo); + questTitle.text = "任务名称"; + GameObject objTemplateGo = GetOrCreateChild(questTrackerGo.transform, "ObjectiveRowTemplate").gameObject; + objTemplateGo.SetActive(false); + GetOrAddComponent(objTemplateGo); + GameObject objContainerGo = GetOrCreateChild(questTrackerGo.transform, "ObjectiveContainer").gameObject; + var objLayout = GetOrAddComponent(objContainerGo); + objLayout.childForceExpandWidth = false; + objLayout.spacing = 2f; + + AssignRef(questWidget, "_questTitleText", questTitle); + AssignRef(questWidget, "_objectiveRowTemplate", objTemplateGo); + AssignRef(questWidget, "_objectiveContainer", objContainerGo.transform); + + // ── HUDController 子组件引用 ────────────────────────────────────── + AssignRef(hudCtrl, "_hpContainer", hpContainerGo.transform); + AssignRef(hudCtrl, "_soulGaugeFill", soulFill); + AssignRef(hudCtrl, "_spiritGaugeFill", spiritFill); + AssignRef(hudCtrl, "_lingZhuText", lingZhuText); + AssignRef(hudCtrl, "_springContainer", springContainerGo.transform); + AssignRef(hudCtrl, "_interactPromptWidget", interactWidget); + AssignArrayRefs(hudCtrl, "_formIcons", formImages, report); + + // ── 事件频道 ────────────────────────────────────────────────────── + AssignAsset(hudCtrl, "_onHPChanged", report, true, "EVT_HPChanged", "EVT_PlayerHPChanged"); + AssignAsset(hudCtrl, "_onMaxHPChanged", report, false, "EVT_MaxHPChanged", "EVT_PlayerMaxHPChanged"); + AssignAsset(hudCtrl, "_onSoulPowerChanged", report, false, "EVT_SoulPowerChanged", "EVT_MagicPowerChanged"); + AssignAsset(hudCtrl, "_onSpiritPowerChanged", report, false, "EVT_SpiritPowerChanged"); + AssignAsset(hudCtrl, "_onLingZhuChanged", report, false, "EVT_LingZhuChanged", "EVT_CurrencyChanged"); + AssignAsset(hudCtrl, "_onSpringChargesChanged", report, false, "EVT_SpringChargesChanged", "EVT_SpringCharges"); + AssignAsset(hudCtrl, "_onFormChanged", report, false, "EVT_FormChanged"); + + AssignAsset(interactWidget, "_onShowPrompt", report, false, "EVT_ShowInteractPrompt", "EVT_InteractPromptShow"); + AssignAsset(interactWidget, "_onHidePrompt", report, false, "EVT_HideInteractPrompt", "EVT_InteractPromptHide"); + + AssignAsset(bossHPBar, "_onBossFightToggled", report, false, "EVT_BossFightToggled", "EVT_BossFightStarted"); + AssignAsset(bossHPBar, "_onBossHPChanged", report, false, "EVT_BossHPChanged"); + AssignAsset(bossHPBar, "_onBossHPMaxSet", report, false, "EVT_BossHPMaxSet"); + AssignAsset(bossHPBar, "_onBossNameSet", report, false, "EVT_BossNameSet"); + AssignAsset(bossHPBar, "_onBossPhaseThreshold", report, false, "EVT_BossPhaseThreshold"); + + AssignAsset(statusHUD, "_onStatusEffectApplied", report, false, "EVT_StatusEffectApplied"); + AssignAsset(statusHUD, "_onStatusEffectExpired", report, false, "EVT_StatusEffectExpired"); + + AssignAsset(questWidget, "_onQuestStarted", report, false, "EVT_QuestStarted"); + AssignAsset(questWidget, "_onQuestCompleted", report, false, "EVT_QuestCompleted"); + AssignAsset(questWidget, "_onQuestFailed", report, false, "EVT_QuestFailed"); + AssignAsset(questWidget, "_onQuestAbandoned", report, false, "EVT_QuestAbandoned"); + AssignAsset(questWidget, "_onQuestReadyToComplete", report, false, "EVT_QuestReadyToComplete"); + AssignAsset(questWidget, "_onObjectiveBatchUpdated", report, false, "EVT_QuestObjectiveBatchUpdated"); + + AssignAsset(toolHUD, "_onToolUsed", report, false, "EVT_ToolUsed"); + + // ── 手工步骤说明 ────────────────────────────────────────────────── + report.Add("HUDController._hpCellPrefab:请将 HP 格子 Prefab 赋给该字段。"); + report.Add("HUDController._springIconPrefab:请将回春图标 Prefab 赋给该字段。"); + report.Add("BossHPBar._phaseMarkerPrefab:请将阶段标记点 Prefab 赋给该字段。"); + report.Add("StatusEffectHUD._slotConfigs:请在 Inspector 中配置各状态效果的图标映射。"); + + Undo.CollapseUndoOperations(undoGroup); + MarkDirtyAndLog("HUD Canvas 脚手架", canvasGo, report); + } + + // ───────────────────────────────────────────────────────────────────── + // Private helpers + // ───────────────────────────────────────────────────────────────────── + + /// + /// 在活动场景中查找或创建 HUD Canvas。 + /// 查找顺序: + /// 1. 遍历场景根节点中的直接同名节点 + /// 2. 遍历各根节点下的 [UI]/UIRoot/{name} 或 UIRoot/{name} 子路径 + /// 若均不存在则新建 Canvas,并优先挂载至 UIRoot(兼容 ScaffoldPersistentScene 层级); + /// 若场景中不存在 UIRoot,则回退到场景根层并发出警告。 + /// + private static GameObject GetOrCreateRootCanvas(string name, int sortOrder) + { + Scene scene = SceneManager.GetActiveScene(); + + // ── Phase 1: 搜索已存在的 Canvas ───────────────────────────────── + foreach (GameObject root in scene.GetRootGameObjects()) + { + if (root.name == name) + return root; + + // 兼容 ScaffoldPersistentScene 层级:[Persistent] ▸ [UI] ▸ UIRoot ▸ HUD Canvas + foreach (string path in new[] { $"[UI]/UIRoot/{name}", $"UIRoot/{name}", name }) + { + Transform found = root.transform.Find(path); + if (found != null) return found.gameObject; + } + } + + // ── Phase 2: 定位 UIRoot(Canvas 的正确父节点)─────────────────── + Transform uiRoot = null; + foreach (GameObject root in scene.GetRootGameObjects()) + { + uiRoot = root.transform.Find("[UI]/UIRoot") + ?? root.transform.Find("UIRoot"); + if (uiRoot != null) break; + } + + if (uiRoot == null) + Debug.LogWarning( + "[HUDScaffold] 未找到 UIRoot(期望路径:[Persistent]/[UI]/UIRoot)。" + + "将在场景根层创建 HUD Canvas。建议先执行 BaseGames/Scene/Setup/Scaffold Persistent Scene。"); + + // ── Phase 3: 创建 Canvas 并挂载至正确父节点 ────────────────────── + GameObject canvasGo = new GameObject(name); + Undo.RegisterCreatedObjectUndo(canvasGo, $"Create {name}"); + + if (uiRoot != null) + canvasGo.transform.SetParent(uiRoot, false); + + Canvas canvas = canvasGo.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.sortingOrder = sortOrder; + + CanvasScaler scaler = canvasGo.AddComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1920, 1080); + + canvasGo.AddComponent(); + return canvasGo; + } + + private static Transform GetOrCreateChild(Transform parent, string name) + { + Transform child = parent.Find(name); + if (child != null) return child; + + GameObject go = new GameObject(name); + Undo.RegisterCreatedObjectUndo(go, $"Create {name}"); + go.transform.SetParent(parent, false); + return go.transform; + } + + private static T GetOrAddComponent(GameObject go) where T : Component + { + T c = go.GetComponent(); + return c != null ? c : Undo.AddComponent(go); + } + + private static void AssignRef(Object target, string propertyName, Object value) + { + var so = new SerializedObject(target); + var prop = so.FindProperty(propertyName); + if (prop == null) + { + Debug.LogWarning($"[HUDScaffold] 未找到属性 {target.GetType().Name}.{propertyName}", target); + return; + } + prop.objectReferenceValue = value; + so.ApplyModifiedPropertiesWithoutUndo(); + } + + private static void AssignArrayRefs(Object target, string propertyName, T[] values, List report) + where T : Object + { + var so = new SerializedObject(target); + var prop = so.FindProperty(propertyName); + if (prop == null || !prop.isArray) + { + report.Add($"{target.GetType().Name}.{propertyName} 不是可写数组字段。"); + return; + } + prop.arraySize = values.Length; + for (int i = 0; i < values.Length; i++) + prop.GetArrayElementAtIndex(i).objectReferenceValue = values[i]; + so.ApplyModifiedPropertiesWithoutUndo(); + } + + private static void AssignAsset(Object target, string propertyName, List report, + bool required, params string[] candidates) + { + Object asset = FindFirstAsset(candidates); + if (asset == null) + { + if (required) + report.Add($"未找到 {target.GetType().Name}.{propertyName} 所需资产: {string.Join(" / ", candidates)}"); + return; + } + AssignRef(target, propertyName, asset); + } + + private static Object FindFirstAsset(params string[] candidates) + { + foreach (string candidate in candidates) + { + if (string.IsNullOrWhiteSpace(candidate)) continue; + string[] guids = AssetDatabase.FindAssets(candidate); + foreach (string guid in guids) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + Object asset = AssetDatabase.LoadMainAssetAtPath(path); + if (asset != null && asset.name == candidate) + return asset; + } + } + return null; + } + + private static void MarkDirtyAndLog(string scaffoldName, GameObject root, List report) + { + EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene()); + Selection.activeGameObject = root; + + if (report.Count == 0) + { + Debug.Log($"[HUDScaffold] {scaffoldName} 完成。", root); + return; + } + + Debug.LogWarning( + $"[HUDScaffold] {scaffoldName} 完成,以下 {report.Count} 项需手动确认:\n- {string.Join("\n- ", report)}", + root); + } + } +} diff --git a/Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs.meta b/Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs.meta new file mode 100644 index 0000000..b6812a4 --- /dev/null +++ b/Assets/_Game/Scripts/Editor/UI/HUDScaffoldWizard.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74e18b6a781df434c86d28bb3bf54101 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/UI/BaseGames.UI.asmdef b/Assets/_Game/Scripts/UI/BaseGames.UI.asmdef index 997bc7a..b0fa3b6 100644 --- a/Assets/_Game/Scripts/UI/BaseGames.UI.asmdef +++ b/Assets/_Game/Scripts/UI/BaseGames.UI.asmdef @@ -22,7 +22,8 @@ "BaseGames.Quest", "BaseGames.Skills", "Unity.Addressables", - "Unity.ResourceManager" + "Unity.ResourceManager", + "BaseGames.Feedback" ], "autoReferenced": true, "overrideReferences": false, diff --git a/Assets/_Game/Scripts/UI/SceneFadeController.cs b/Assets/_Game/Scripts/UI/SceneFadeController.cs new file mode 100644 index 0000000..8dfe953 --- /dev/null +++ b/Assets/_Game/Scripts/UI/SceneFadeController.cs @@ -0,0 +1,44 @@ +using UnityEngine; +using BaseGames.Core.Events; +using BaseGames.Feedback; + +namespace BaseGames.UI +{ + /// + /// 场景切换黑屏控制器(Persistent 场景)。 + /// 监听 EVT_FadeOutRequest / EVT_FadeInRequest,调用对应 SceneFeedback.Play()。 + /// 实际视觉效果(黑幕、淡入淡出动画等)完全由 SceneFeedback 内部的 MMF_Player 配置决定。 + /// + /// 挂载位置:Persistent 场景 → [UI] → SYS_SceneFade + /// + /// 配置步骤: + /// 1. 创建两个带 MMF_Player 的 SceneFeedback(淡出 / 淡入),在 MMF_Player 中配置所需效果 + /// 2. 将淡出 SceneFeedback 拖入 _fadeOut,淡入 SceneFeedback 拖入 _fadeIn + /// 3. 将 EVT_FadeOutRequest / EVT_FadeInRequest 拖入对应频道槽 + /// 4. MMF_Player 总时长应 ≤ SceneService._sceneFadeDuration(默认 0.4 s) + /// + [AddComponentMenu("BaseGames/UI/Scene Fade Controller")] + public class SceneFadeController : MonoBehaviour + { + [Header("Feedback")] + [SerializeField] private SceneFeedback _fadeOut; + [SerializeField] private SceneFeedback _fadeIn; + + [Header("事件频道")] + [SerializeField] private VoidEventChannelSO _onFadeOutRequest; + [SerializeField] private VoidEventChannelSO _onFadeInRequest; + + private readonly CompositeDisposable _subs = new(); + + private void OnEnable() + { + _onFadeOutRequest?.Subscribe(OnFadeOut).AddTo(_subs); + _onFadeInRequest?.Subscribe(OnFadeIn).AddTo(_subs); + } + + private void OnDisable() => _subs.Clear(); + + private void OnFadeOut() => _fadeOut?.Play(); + private void OnFadeIn() => _fadeIn?.Play(); + } +} diff --git a/Assets/_Game/Scripts/UI/SceneFadeController.cs.meta b/Assets/_Game/Scripts/UI/SceneFadeController.cs.meta new file mode 100644 index 0000000..b79a0d4 --- /dev/null +++ b/Assets/_Game/Scripts/UI/SceneFadeController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e4fa87eedbe2f24db81bdf66714343f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/World/DoorTransition.cs b/Assets/_Game/Scripts/World/DoorTransition.cs index 98cdd45..a905e0e 100644 --- a/Assets/_Game/Scripts/World/DoorTransition.cs +++ b/Assets/_Game/Scripts/World/DoorTransition.cs @@ -1,5 +1,6 @@ using System.Collections; using Animancer; +using BaseGames.Core; using BaseGames.Core.Events; using UnityEngine; @@ -48,9 +49,6 @@ namespace BaseGames.World [SerializeField] private bool _requiresKeyItem; [SerializeField] private string _requiredItemId; - [Header("事件频道")] - [SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest; - [Header("世界状态")] [SerializeField] private WorldStateRegistry _worldState; @@ -95,14 +93,23 @@ namespace BaseGames.World yield return state; // 等待动画完成 } - _onSceneLoadRequest?.Raise(new SceneLoadRequest + var request = new SceneLoadRequest { SceneName = _targetSceneAddress, EntryTransitionId = _targetTransitionId, TransitionType = _transitionType, ShowLoadingScreen = _transitionType == TransitionType.Scene, IsRespawn = false, - }); + }; + + var sceneService = ServiceLocator.GetOrDefault(); + if (sceneService != null) + { + sceneService.RequestTransition(request); + yield break; + } + + Debug.LogWarning("[DoorTransition] 无法发送过渡请求:ISceneService 未在 ServiceLocator 中注册。", this); } /// diff --git a/Assets/_Game/Scripts/World/RoomTransition.cs b/Assets/_Game/Scripts/World/RoomTransition.cs index 3b7bf4a..fa49e62 100644 --- a/Assets/_Game/Scripts/World/RoomTransition.cs +++ b/Assets/_Game/Scripts/World/RoomTransition.cs @@ -1,3 +1,4 @@ +using BaseGames.Core; using BaseGames.Core.Events; using UnityEngine; @@ -38,9 +39,6 @@ namespace BaseGames.World [SerializeField] private bool _requiresKeyItem; // 是否需要持有指定钥匙物品 [SerializeField] private string _requiredItemId; // 钥匙物品 ID(_requiresKeyItem = true 时生效) - [Header("事件频道")] - [SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest; - [Header("世界状态")] [SerializeField] private WorldStateRegistry _worldState; @@ -66,14 +64,23 @@ namespace BaseGames.World { if (_requiresKeyItem && !HasItem(_requiredItemId)) return; - _onSceneLoadRequest?.Raise(new SceneLoadRequest + var request = new SceneLoadRequest { SceneName = _targetSceneAddress, EntryTransitionId = _targetTransitionId, TransitionType = _transitionType, ShowLoadingScreen = _transitionType == TransitionType.Scene, IsRespawn = false, - }); + }; + + var sceneService = ServiceLocator.GetOrDefault(); + if (sceneService != null) + { + sceneService.RequestTransition(request); + return; + } + + Debug.LogWarning("[RoomTransition] 无法发送过渡请求:ISceneService 未在 ServiceLocator 中注册。", this); } /// 检查玩家是否持有指定物品(通过 WorldStateRegistry.IsCollected 检查)。