This commit is contained in:
2026-06-07 11:49:55 +08:00
parent ff0f3bde54
commit 1897658a00
98 changed files with 9903 additions and 13907 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: 71036718b64664d45b677feb655e131f
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: 37aad17d1dc06794ebd3d5506f40408d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
%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: 96b10c11e6173394a8fa8d9c614b0035, type: 3}
m_Name: CMB_DS_EnemyBody
m_EditorClassIdentifier:
sourceId: Enemy_Body
skillId:
BaseDamage: 1
DamageMultiplier: 1
Type: 0
Category: 0
Flags: 0
Tags: 1
KnockbackForce: 6
HitStunDuration: 0.15
BreakLevel: 1
FxType: 2
ComboWindowDuration: 0
CancelWindowEnd: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: caae9c7600281fe4e8d8637fa3fd2ca1
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -40,6 +40,22 @@
"key": "CREDITS_TITLE", "key": "CREDITS_TITLE",
"value": "制作团队" "value": "制作团队"
}, },
{
"key": "LOADING_TIP_COMBAT",
"value": "把握闪避与格挡的时机,是战斗的关键。"
},
{
"key": "LOADING_TIP_EXPLORE",
"value": "多探索每个角落,常有隐藏的道路与收集物。"
},
{
"key": "LOADING_TIP_SAVE",
"value": "在存档点休息可保存进度并恢复状态。"
},
{
"key": "LOADING_TITLE",
"value": "加载中…"
},
{ {
"key": "MAP_CLOSE_HINT", "key": "MAP_CLOSE_HINT",
"value": "关闭地图" "value": "关闭地图"
@@ -92,6 +108,18 @@
"key": "MODE_STEELSOUL_DESC", "key": "MODE_STEELSOUL_DESC",
"value": "钢铁之魂:一命模式,死亡即清空存档。" "value": "钢铁之魂:一命模式,死亡即清空存档。"
}, },
{
"key": "PAUSE_MAIN_MENU",
"value": "返回主菜单"
},
{
"key": "PAUSE_RESUME",
"value": "继续"
},
{
"key": "PAUSE_TITLE",
"value": "暂停"
},
{ {
"key": "REBIND_WAITING_PROMPT", "key": "REBIND_WAITING_PROMPT",
"value": "按下新按键…" "value": "按下新按键…"

View File

@@ -40,6 +40,22 @@
"key": "CREDITS_TITLE", "key": "CREDITS_TITLE",
"value": "Credits" "value": "Credits"
}, },
{
"key": "LOADING_TIP_COMBAT",
"value": "Timing your dodge and parry is the key to combat."
},
{
"key": "LOADING_TIP_EXPLORE",
"value": "Explore every corner — hidden paths and collectibles await."
},
{
"key": "LOADING_TIP_SAVE",
"value": "Rest at save points to save progress and restore yourself."
},
{
"key": "LOADING_TITLE",
"value": "Loading…"
},
{ {
"key": "MAP_CLOSE_HINT", "key": "MAP_CLOSE_HINT",
"value": "Close Map" "value": "Close Map"
@@ -92,6 +108,18 @@
"key": "MODE_STEELSOUL_DESC", "key": "MODE_STEELSOUL_DESC",
"value": "Steel Soul: one life. Death wipes the save." "value": "Steel Soul: one life. Death wipes the save."
}, },
{
"key": "PAUSE_MAIN_MENU",
"value": "Return to Title"
},
{
"key": "PAUSE_RESUME",
"value": "Resume"
},
{
"key": "PAUSE_TITLE",
"value": "Paused"
},
{ {
"key": "REBIND_WAITING_PROMPT", "key": "REBIND_WAITING_PROMPT",
"value": "Press New Key…" "value": "Press New Key…"

View File

@@ -0,0 +1,22 @@
%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: 814076691b7b57e4eaf67f32530baf5f, type: 3}
m_Name: UI_LoadingConfig
m_EditorClassIdentifier:
_tipKeys:
- LOADING_TIP_EXPLORE
- LOADING_TIP_SAVE
- LOADING_TIP_COMBAT
_titleKey: LOADING_TITLE
_minDisplayTime: 0.5
_expectedLoadTime: 2.5
_fillLerpSpeed: 1.6

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1c55f0c949291244b848f4806386e386
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -19,12 +19,6 @@ MonoBehaviour:
requiresSave: 0 requiresSave: 0
sceneKey: sceneKey:
eventChannel: {fileID: 0} eventChannel: {fileID: 0}
- labelKey: MENU_CONTINUE
icon: {fileID: 0}
action: 1
requiresSave: 1
sceneKey:
eventChannel: {fileID: 0}
- labelKey: MENU_SETTINGS - labelKey: MENU_SETTINGS
icon: {fileID: 0} icon: {fileID: 0}
action: 2 action: 2

View File

@@ -0,0 +1,25 @@
%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: ff0448c32aab82546bd71b33da6b2c9a, type: 3}
m_Name: UI_NewGameModeConfig
m_EditorClassIdentifier:
_titleKey: MODE_SELECT_TITLE
_backLabelKey: BTN_BACK
_items:
- level: 1
labelKey: MODE_NORMAL
descKey:
icon: {fileID: 0}
- level: 3
labelKey: MODE_STEELSOUL
descKey: MODE_STEELSOUL_DESC
icon: {fileID: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 88d72c8f39c5edc4588bbf4001140352
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
%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: a7dd5202b8fce1d40b073624a3b22953, type: 3}
m_Name: UI_PauseMenuConfig
m_EditorClassIdentifier:
_items:
- labelKey: PAUSE_RESUME
icon: {fileID: 0}
action: 0
sceneKey:
eventChannel: {fileID: 0}
- labelKey: MENU_SETTINGS
icon: {fileID: 0}
action: 1
sceneKey:
eventChannel: {fileID: 0}
- labelKey: PAUSE_MAIN_MENU
icon: {fileID: 0}
action: 2
sceneKey:
eventChannel: {fileID: 0}
- labelKey: MENU_QUIT
icon: {fileID: 0}
action: 3
sceneKey:
eventChannel: {fileID: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 74401971c9590c4479c11b9879aa3a73
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -246,6 +246,7 @@ GameObject:
- component: {fileID: 566091224506171403} - component: {fileID: 566091224506171403}
- component: {fileID: 3038514324348343369} - component: {fileID: 3038514324348343369}
- component: {fileID: 8116248334753210478} - component: {fileID: 8116248334753210478}
- component: {fileID: 2674139131311821727}
m_Layer: 9 m_Layer: 9
m_Name: Player m_Name: Player
m_TagString: Player m_TagString: Player
@@ -728,6 +729,36 @@ MonoBehaviour:
_worldUnitsPerCell: 18 _worldUnitsPerCell: 18
_worldOriginOffset: {x: 0, y: 0} _worldOriginOffset: {x: 0, y: 0}
_onRoomEntered: {fileID: 11400000, guid: 133c65f23b6631846ab619edb0e44708, type: 2} _onRoomEntered: {fileID: 11400000, guid: 133c65f23b6631846ab619edb0e44708, type: 2}
--- !u!114 &2674139131311821727
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6834103521996502824}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2865735d009e06d4b88c8f81b149b7d0, type: 3}
m_Name:
m_EditorClassIdentifier:
_onHitLight: {fileID: 0}
_onHitMedium: {fileID: 0}
_onHitHeavy: {fileID: 0}
_onParrySuccess: {fileID: 0}
_onTakeHit: {fileID: 0}
_onDeath: {fileID: 0}
_onHeal: {fileID: 0}
_onLandImpact: {fileID: 0}
_onAttackWhoosh: {fileID: 0}
_onJumpLaunch: {fileID: 0}
_onFootstep: {fileID: 0}
_footstepSoundPlayer: {fileID: 0}
_onFormSwitch:
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
_namedPresets: []
_sfxPresets: []
--- !u!1001 &5247753868797769228 --- !u!1001 &5247753868797769228
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -0,0 +1,363 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3035235918834792336
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2919920352459249042}
- component: {fileID: 4907917836521015712}
- component: {fileID: 7314739420037221720}
m_Layer: 0
m_Name: UI_Control_InputPrompt
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2919920352459249042
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3035235918834792336}
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: 8396107093623177839}
- {fileID: 8822216105837004139}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 120, y: 44}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &4907917836521015712
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3035235918834792336}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 3
m_Spacing: 8
m_ChildForceExpandWidth: 0
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &7314739420037221720
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3035235918834792336}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier:
m_HorizontalFit: 2
m_VerticalFit: 2
--- !u!1 &7840775759246172065
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8822216105837004139}
- component: {fileID: 925913538673358552}
- component: {fileID: 2399597280849773084}
- component: {fileID: 2961596559903832300}
- component: {fileID: 52593765190361193}
m_Layer: 0
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8822216105837004139
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7840775759246172065}
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: 2919920352459249042}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 200, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &925913538673358552
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7840775759246172065}
m_CullTransparentMesh: 1
--- !u!114 &2399597280849773084
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7840775759246172065}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u63D0\u793A"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 0.8, g: 0.8, b: 0.82, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 24
m_fontSizeBase: 24
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 4096
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &2961596559903832300
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7840775759246172065}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: 40
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &52593765190361193
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7840775759246172065}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 8
_overrideFontSize: 0
--- !u!1 &8362265496831051138
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8396107093623177839}
- component: {fileID: 6552766975594788561}
- component: {fileID: 8600880343474015460}
- component: {fileID: 474139563583708950}
- component: {fileID: 5885575795706592637}
m_Layer: 0
m_Name: Icon
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8396107093623177839
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8362265496831051138}
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: 2919920352459249042}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6552766975594788561
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8362265496831051138}
m_CullTransparentMesh: 1
--- !u!114 &8600880343474015460
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8362265496831051138}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &474139563583708950
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8362265496831051138}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 32
m_MinHeight: 32
m_PreferredWidth: 40
m_PreferredHeight: 40
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &5885575795706592637
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8362265496831051138}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b8d9e520f0d671e458003b2974ccb45e, type: 3}
m_Name:
m_EditorClassIdentifier:
_mode: 0
_actionName: Interact
_bindingPath:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: af08378b9a38f4b488e558ff64361791
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -14,13 +14,14 @@ GameObject:
- component: {fileID: 3321591285440411207} - component: {fileID: 3321591285440411207}
- component: {fileID: 1171217503679730641} - component: {fileID: 1171217503679730641}
- component: {fileID: 6247134819645328346} - component: {fileID: 6247134819645328346}
- component: {fileID: 5153181738271838075}
m_Layer: 0 m_Layer: 0
m_Name: UI_MainMenu_Button m_Name: UI_MainMenu_Button
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 0
--- !u!224 &7174374360084985875 --- !u!224 &7174374360084985875
RectTransform: RectTransform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -63,7 +64,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.06} m_Color: {r: 1, g: 1, b: 1, a: 0.4862745}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
@@ -158,6 +159,26 @@ MonoBehaviour:
_button: {fileID: 3321591285440411207} _button: {fileID: 3321591285440411207}
_label: {fileID: 5199662468546267514} _label: {fileID: 5199662468546267514}
_icon: {fileID: 3358762196943047451} _icon: {fileID: 3358762196943047451}
--- !u!114 &5153181738271838075
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 157705238739733498}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 56
m_PreferredWidth: -1
m_PreferredHeight: 64
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &1456531517881710782 --- !u!1 &1456531517881710782
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -363,140 +363,6 @@ MonoBehaviour:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
m_IsOn: 1 m_IsOn: 1
--- !u!1 &1642535255620507499
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8676543220236400150}
- component: {fileID: 7326606244306365918}
- component: {fileID: 1973024271951704419}
m_Layer: 0
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8676543220236400150
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1642535255620507499}
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: 1907908674324065035}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -7.5, y: -0.5}
m_SizeDelta: {x: -35, y: -13}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7326606244306365918
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1642535255620507499}
m_CullTransparentMesh: 1
--- !u!114 &1973024271951704419
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1642535255620507499}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Option A
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 14
m_fontSizeBase: 14
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &1828398156914186694 --- !u!1 &1828398156914186694
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -689,6 +555,7 @@ GameObject:
serializedVersion: 6 serializedVersion: 6
m_Component: m_Component:
- component: {fileID: 5547771668927895346} - component: {fileID: 5547771668927895346}
- component: {fileID: 1624066527964928816}
- component: {fileID: 4432176973927115646} - component: {fileID: 4432176973927115646}
m_Layer: 0 m_Layer: 0
m_Name: UI_Setting_DropdownRow m_Name: UI_Setting_DropdownRow
@@ -718,6 +585,26 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 480, y: 44} m_SizeDelta: {x: 480, y: 44}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1624066527964928816
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2833855665454200082}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 40
m_PreferredWidth: -1
m_PreferredHeight: 44
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &4432176973927115646 --- !u!114 &4432176973927115646
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -978,7 +865,7 @@ RectTransform:
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 8676543220236400150} - {fileID: 6833398410597970263}
- {fileID: 5673278229776192608} - {fileID: 5673278229776192608}
- {fileID: 6914002101499912006} - {fileID: 6914002101499912006}
m_Father: {fileID: 5547771668927895346} m_Father: {fileID: 5547771668927895346}
@@ -1068,7 +955,7 @@ MonoBehaviour:
m_Interactable: 1 m_Interactable: 1
m_TargetGraphic: {fileID: 8243765438168473865} m_TargetGraphic: {fileID: 8243765438168473865}
m_Template: {fileID: 6914002101499912006} m_Template: {fileID: 6914002101499912006}
m_CaptionText: {fileID: 1973024271951704419} m_CaptionText: {fileID: 542709698955734455}
m_CaptionImage: {fileID: 0} m_CaptionImage: {fileID: 0}
m_Placeholder: {fileID: 0} m_Placeholder: {fileID: 0}
m_ItemText: {fileID: 6120727421307163512} m_ItemText: {fileID: 6120727421307163512}
@@ -1305,6 +1192,140 @@ MonoBehaviour:
m_FillOrigin: 0 m_FillOrigin: 0
m_UseSpriteMesh: 0 m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1 m_PixelsPerUnitMultiplier: 1
--- !u!1 &6239441624728588921
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6833398410597970263}
- component: {fileID: 2992093815540951595}
- component: {fileID: 542709698955734455}
m_Layer: 0
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6833398410597970263
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6239441624728588921}
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: 1907908674324065035}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -7.5, y: -0.5}
m_SizeDelta: {x: -35, y: -13}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2992093815540951595
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6239441624728588921}
m_CullTransparentMesh: 1
--- !u!114 &542709698955734455
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6239441624728588921}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Option A
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 14
m_fontSizeBase: 14
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &6457714868409238319 --- !u!1 &6457714868409238319
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -9,6 +9,7 @@ GameObject:
serializedVersion: 6 serializedVersion: 6
m_Component: m_Component:
- component: {fileID: 1188174262370179090} - component: {fileID: 1188174262370179090}
- component: {fileID: 8856493230818322502}
m_Layer: 0 m_Layer: 0
m_Name: UI_Setting_Header m_Name: UI_Setting_Header
m_TagString: Untagged m_TagString: Untagged
@@ -36,6 +37,26 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 480, y: 32} m_SizeDelta: {x: 480, y: 32}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &8856493230818322502
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 289442062472622113}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 32
m_PreferredWidth: -1
m_PreferredHeight: 36
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &6990715320569207602 --- !u!1 &6990715320569207602
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -789,6 +789,7 @@ GameObject:
serializedVersion: 6 serializedVersion: 6
m_Component: m_Component:
- component: {fileID: 534764500401432470} - component: {fileID: 534764500401432470}
- component: {fileID: 1513367629320811599}
- component: {fileID: 1763515077177812284} - component: {fileID: 1763515077177812284}
m_Layer: 0 m_Layer: 0
m_Name: UI_Setting_SliderRow m_Name: UI_Setting_SliderRow
@@ -819,6 +820,26 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 480, y: 44} m_SizeDelta: {x: 480, y: 44}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1513367629320811599
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8959349832593751440}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 40
m_PreferredWidth: -1
m_PreferredHeight: 44
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &1763515077177812284 --- !u!114 &1763515077177812284
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -271,6 +271,7 @@ GameObject:
serializedVersion: 6 serializedVersion: 6
m_Component: m_Component:
- component: {fileID: 1428818964736103486} - component: {fileID: 1428818964736103486}
- component: {fileID: 3573291440363293926}
- component: {fileID: 4398321019914005696} - component: {fileID: 4398321019914005696}
m_Layer: 0 m_Layer: 0
m_Name: UI_Setting_ToggleRow m_Name: UI_Setting_ToggleRow
@@ -300,6 +301,26 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 480, y: 44} m_SizeDelta: {x: 480, y: 44}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &3573291440363293926
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3055060066778635686}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 40
m_PreferredWidth: -1
m_PreferredHeight: 44
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &4398321019914005696 --- !u!114 &4398321019914005696
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -326,85 +347,6 @@ MonoBehaviour:
m_ChildScaleWidth: 0 m_ChildScaleWidth: 0
m_ChildScaleHeight: 0 m_ChildScaleHeight: 0
m_ReverseArrangement: 0 m_ReverseArrangement: 0
--- !u!1 &4266586670429411161
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7493993138355221773}
- component: {fileID: 7236726997102272855}
- component: {fileID: 5058325138044312319}
m_Layer: 0
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7493993138355221773
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4266586670429411161}
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: 6473902974054121827}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 9, y: -0.5}
m_SizeDelta: {x: -28, y: -3}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7236726997102272855
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4266586670429411161}
m_CullTransparentMesh: 1
--- !u!114 &5058325138044312319
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4266586670429411161}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Toggle
--- !u!1 &8258555473425308654 --- !u!1 &8258555473425308654
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -480,6 +422,85 @@ MonoBehaviour:
m_FillOrigin: 0 m_FillOrigin: 0
m_UseSpriteMesh: 0 m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1 m_PixelsPerUnitMultiplier: 1
--- !u!1 &8605874378357371549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3548007727314882439}
- component: {fileID: 5778144136359093115}
- component: {fileID: 4442023718036717773}
m_Layer: 0
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3548007727314882439
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8605874378357371549}
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: 6473902974054121827}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 9, y: -0.5}
m_SizeDelta: {x: -28, y: -3}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5778144136359093115
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8605874378357371549}
m_CullTransparentMesh: 1
--- !u!114 &4442023718036717773
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8605874378357371549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Toggle
--- !u!1 &9090532220244762198 --- !u!1 &9090532220244762198
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -511,7 +532,7 @@ RectTransform:
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 8196243737921290128} - {fileID: 8196243737921290128}
- {fileID: 7493993138355221773} - {fileID: 3548007727314882439}
m_Father: {fileID: 1428818964736103486} m_Father: {fileID: 1428818964736103486}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMin: {x: 0.5, y: 0.5}

View File

@@ -0,0 +1,739 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &848703412458411002
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6580917992385474283}
- component: {fileID: 3635399485132057637}
- component: {fileID: 8408492814139929697}
- component: {fileID: 4441984699968881689}
- component: {fileID: 4045137910155871014}
m_Layer: 0
m_Name: UI_LoadingScreen
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6580917992385474283
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 848703412458411002}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6472227455787079483}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!223 &3635399485132057637
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 848703412458411002}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 0
m_AdditionalShaderChannelsFlag: 0
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 99
m_TargetDisplay: 0
--- !u!114 &8408492814139929697
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 848703412458411002}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0.5
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!114 &4441984699968881689
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 848703412458411002}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &4045137910155871014
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 848703412458411002}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 57313fea7dbe62f42826481b78a5e989, type: 3}
m_Name:
m_EditorClassIdentifier:
_config: {fileID: 11400000, guid: 1c55f0c949291244b848f4806386e386, type: 2}
_loadingRoot: {fileID: 6895743969849290809}
_progressFill: {fileID: 1474927014269033191}
_titleText: {fileID: 5639425013680570449}
_tipText: {fileID: 235275732515892626}
_backgroundArts:
- {fileID: 7719192390630279241}
_tipMessages: []
_minDisplayTime: 0.5
_fillLerpSpeed: 1.6
_expectedLoadTime: 2.5
_onLoadingStarted: {fileID: 11400000, guid: 78de6bbfc7741ad47ad568ba0b65fde0, type: 2}
_onLoadingComplete: {fileID: 11400000, guid: 1e4580d266c4bd44c8b25a70d26608d3, type: 2}
_onLoadingProgressUpdated: {fileID: 11400000, guid: 2d366d52ee6a8214c8cd94d14e9ccd15, type: 2}
--- !u!1 &937892540335618354
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6358757063047093772}
- component: {fileID: 9099041953553400929}
- component: {fileID: 235275732515892626}
- component: {fileID: 8086465632997046668}
m_Layer: 0
m_Name: TipText
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6358757063047093772
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 937892540335618354}
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: 6472227455787079483}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0}
m_AnchorMax: {x: 0.5, y: 0}
m_AnchoredPosition: {x: 0, y: 130}
m_SizeDelta: {x: 1200, y: 48}
m_Pivot: {x: 0.5, y: 0}
--- !u!222 &9099041953553400929
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 937892540335618354}
m_CullTransparentMesh: 1
--- !u!114 &235275732515892626
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 937892540335618354}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text:
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 28
m_fontSizeBase: 28
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &8086465632997046668
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 937892540335618354}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 8
_overrideFontSize: 0
--- !u!1 &1432416420336893244
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1281560495663496195}
- component: {fileID: 180065094292787537}
- component: {fileID: 1474927014269033191}
- component: {fileID: 7810340148927196280}
m_Layer: 0
m_Name: ProgressBarFill
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1281560495663496195
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1432416420336893244}
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: 1936447358316777049}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &180065094292787537
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1432416420336893244}
m_CullTransparentMesh: 1
--- !u!114 &1474927014269033191
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1432416420336893244}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.85, g: 0.8, b: 0.55, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 3
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 0
m_FillAmount: 0
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &7810340148927196280
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1432416420336893244}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 2
_overrideFontSize: 0
--- !u!1 &3200061798109558024
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6645450485567123666}
- component: {fileID: 546440650392669039}
- component: {fileID: 5639425013680570449}
- component: {fileID: 7538201509063449178}
m_Layer: 0
m_Name: Title
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6645450485567123666
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3200061798109558024}
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: 6472227455787079483}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 60}
m_SizeDelta: {x: 1200, y: 80}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &546440650392669039
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3200061798109558024}
m_CullTransparentMesh: 1
--- !u!114 &5639425013680570449
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3200061798109558024}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Loading
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 56
m_fontSizeBase: 56
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &7538201509063449178
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3200061798109558024}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 9
_overrideFontSize: 0
--- !u!1 &5152817451551210031
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7317325615962215621}
- component: {fileID: 2989277873825394337}
- component: {fileID: 7719192390630279241}
- component: {fileID: 377346564664366483}
m_Layer: 0
m_Name: Background
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7317325615962215621
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5152817451551210031}
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: 6472227455787079483}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2989277873825394337
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5152817451551210031}
m_CullTransparentMesh: 1
--- !u!114 &7719192390630279241
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5152817451551210031}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.04, g: 0.05, b: 0.07, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &377346564664366483
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5152817451551210031}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 3
_overrideFontSize: 0
--- !u!1 &6895743969849290809
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6472227455787079483}
- component: {fileID: 3212534505034294223}
m_Layer: 0
m_Name: LoadingRoot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &6472227455787079483
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6895743969849290809}
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: 7317325615962215621}
- {fileID: 1936447358316777049}
- {fileID: 6645450485567123666}
- {fileID: 6358757063047093772}
m_Father: {fileID: 6580917992385474283}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &3212534505034294223
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6895743969849290809}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 50572ad108fd3354da04ea8fba66370b, type: 3}
m_Name:
m_EditorClassIdentifier:
_theme: {fileID: 11400000, guid: 88bab5495f6103f4d9d69fa043d4c3e1, type: 2}
_applyOnEnable: 1
--- !u!1 &7198921208041521223
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1936447358316777049}
- component: {fileID: 588689631386492451}
- component: {fileID: 3360803303079870087}
m_Layer: 0
m_Name: ProgressBarTrack
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1936447358316777049
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7198921208041521223}
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: 1281560495663496195}
m_Father: {fileID: 6472227455787079483}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0}
m_AnchorMax: {x: 0.5, y: 0}
m_AnchoredPosition: {x: 0, y: 90}
m_SizeDelta: {x: 760, y: 14}
m_Pivot: {x: 0.5, y: 0}
--- !u!222 &588689631386492451
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7198921208041521223}
m_CullTransparentMesh: 1
--- !u!114 &3360803303079870087
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7198921208041521223}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.12}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 104a4b4dd2ebd7a4ca81c4f197a80973
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,923 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &132792757024396132
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5013767866730934759}
- component: {fileID: 3826295305779948218}
- component: {fileID: 4389457874283392541}
m_Layer: 0
m_Name: Overlay
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5013767866730934759
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 132792757024396132}
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: 930885186095589141}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3826295305779948218
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 132792757024396132}
m_CullTransparentMesh: 1
--- !u!114 &4389457874283392541
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 132792757024396132}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.6}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &1439823143860394840
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8667591003487989381}
- component: {fileID: 921917165921182109}
- component: {fileID: 1201076125941424393}
- component: {fileID: 3178175634072204337}
m_Layer: 0
m_Name: DescText
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8667591003487989381
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1439823143860394840}
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: 8445538168454897546}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: 0, y: 130}
m_SizeDelta: {x: -80, y: 80}
m_Pivot: {x: 0.5, y: 0}
--- !u!222 &921917165921182109
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1439823143860394840}
m_CullTransparentMesh: 1
--- !u!114 &1201076125941424393
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1439823143860394840}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text:
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 0.82, g: 0.6, b: 0.6, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 22
m_fontSizeBase: 22
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &3178175634072204337
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1439823143860394840}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 8
_overrideFontSize: 0
--- !u!1 &4910660113018059512
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8964765921447618202}
- component: {fileID: 5179200552784874568}
- component: {fileID: 7249582173193869292}
- component: {fileID: 6711586627624800794}
- component: {fileID: 5436033847389176718}
m_Layer: 0
m_Name: Title
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8964765921447618202
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4910660113018059512}
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: 8445538168454897546}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: -50}
m_SizeDelta: {x: -60, y: 60}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &5179200552784874568
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4910660113018059512}
m_CullTransparentMesh: 1
--- !u!114 &7249582173193869292
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4910660113018059512}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u9009\u62E9\u6A21\u5F0F"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 40
m_fontSizeBase: 40
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &6711586627624800794
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4910660113018059512}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 964e3a3ab86805244bbde47d5f54950b, type: 3}
m_Name:
m_EditorClassIdentifier:
_key: MODE_SELECT_TITLE
_table: UI
_fontConfig: {fileID: 0}
--- !u!114 &5436033847389176718
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4910660113018059512}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 9
_overrideFontSize: 0
--- !u!1 &5687203378483448106
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5055693708021008172}
- component: {fileID: 109314798685320505}
- component: {fileID: 9044910138967092514}
- component: {fileID: 5888601491657349608}
m_Layer: 0
m_Name: Btn_Back
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5055693708021008172
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5687203378483448106}
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: 28908523273285851}
m_Father: {fileID: 8445538168454897546}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0}
m_AnchorMax: {x: 0.5, y: 0}
m_AnchoredPosition: {x: 0, y: 46}
m_SizeDelta: {x: 260, y: 60}
m_Pivot: {x: 0.5, y: 0}
--- !u!222 &109314798685320505
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5687203378483448106}
m_CullTransparentMesh: 1
--- !u!114 &9044910138967092514
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5687203378483448106}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.06}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &5888601491657349608
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5687203378483448106}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 9044910138967092514}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &5875196121685722319
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8445538168454897546}
- component: {fileID: 6143544721916450244}
- component: {fileID: 4236464133269767410}
- component: {fileID: 3509900547606771796}
m_Layer: 0
m_Name: DialogBox
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8445538168454897546
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5875196121685722319}
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: 8964765921447618202}
- {fileID: 7735213733944224958}
- {fileID: 8667591003487989381}
- {fileID: 5055693708021008172}
m_Father: {fileID: 930885186095589141}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 760, y: 520}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6143544721916450244
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5875196121685722319}
m_CullTransparentMesh: 1
--- !u!114 &4236464133269767410
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5875196121685722319}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.1, g: 0.11, b: 0.15, a: 0.98}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &3509900547606771796
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5875196121685722319}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 3
_overrideFontSize: 0
--- !u!1 &6055991032612735772
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 28908523273285851}
- component: {fileID: 1853166028230881582}
- component: {fileID: 1402790317286044993}
- component: {fileID: 6594418656527094283}
- component: {fileID: 9001019247933305520}
m_Layer: 0
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &28908523273285851
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6055991032612735772}
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: 5055693708021008172}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1853166028230881582
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6055991032612735772}
m_CullTransparentMesh: 1
--- !u!114 &1402790317286044993
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6055991032612735772}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u8FD4\u56DE"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 28
m_fontSizeBase: 28
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &6594418656527094283
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6055991032612735772}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 964e3a3ab86805244bbde47d5f54950b, type: 3}
m_Name:
m_EditorClassIdentifier:
_key: BTN_BACK
_table: UI
_fontConfig: {fileID: 0}
--- !u!114 &9001019247933305520
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6055991032612735772}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 7
_overrideFontSize: 0
--- !u!1 &7185985686371678151
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7735213733944224958}
- component: {fileID: 6313864741675616316}
m_Layer: 0
m_Name: OptionContainer
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7735213733944224958
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7185985686371678151}
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: 8445538168454897546}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: 0, y: -130}
m_SizeDelta: {x: 460, y: 200}
m_Pivot: {x: 0.5, y: 1}
--- !u!114 &6313864741675616316
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7185985686371678151}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 1
m_Spacing: 12
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!1 &8505703364856402198
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 930885186095589141}
- component: {fileID: 3416386247171768618}
- component: {fileID: 3698489702814546874}
- component: {fileID: 7581647026707499664}
m_Layer: 0
m_Name: UI_NewGameModePanel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &930885186095589141
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8505703364856402198}
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: 5013767866730934759}
- {fileID: 8445538168454897546}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!225 &3416386247171768618
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8505703364856402198}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!114 &3698489702814546874
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8505703364856402198}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 84dfb5f813928934b9da0994f3c074eb, type: 3}
m_Name:
m_EditorClassIdentifier:
_firstSelected: {fileID: 0}
_canvasGroup: {fileID: 3416386247171768618}
_fadeInDuration: 0.15
_selectFirstOnEnable: 1
_defaultMode: 0
_canCancel: 1
_config: {fileID: 11400000, guid: 88d72c8f39c5edc4588bbf4001140352, type: 2}
_container: {fileID: 7735213733944224958}
_buttonPrefab: {fileID: 6247134819645328346, guid: e2fb1298d0d8c754e9329131507a17ee, type: 3}
_titleText: {fileID: 6711586627624800794}
_descText: {fileID: 1201076125941424393}
_btnBack: {fileID: 5888601491657349608}
--- !u!114 &7581647026707499664
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8505703364856402198}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 50572ad108fd3354da04ea8fba66370b, type: 3}
m_Name:
m_EditorClassIdentifier:
_theme: {fileID: 11400000, guid: 88bab5495f6103f4d9d69fa043d4c3e1, type: 2}
_applyOnEnable: 1

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: da8bb1f5d5606574e8be08aac75f5df3
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,460 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &2774594045133527740
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2133854998375789241}
- component: {fileID: 1186890221368936983}
- component: {fileID: 5385363682437886184}
m_Layer: 0
m_Name: Dim
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2133854998375789241
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2774594045133527740}
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: 8775082930616069399}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1186890221368936983
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2774594045133527740}
m_CullTransparentMesh: 1
--- !u!114 &5385363682437886184
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2774594045133527740}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.72}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &2927528151102838886
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1431591709047051056}
- component: {fileID: 4391915977973589056}
- component: {fileID: 7318176828362162655}
- component: {fileID: 8615670272833101985}
- component: {fileID: 4365598704319647184}
m_Layer: 0
m_Name: Title
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1431591709047051056
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2927528151102838886}
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: 8775082930616069399}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 220}
m_SizeDelta: {x: 800, y: 90}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4391915977973589056
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2927528151102838886}
m_CullTransparentMesh: 1
--- !u!114 &7318176828362162655
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2927528151102838886}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u6682\u505C"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_sharedMaterial: {fileID: 1445647621079294416, guid: c9ebf78286d2b6345b16575012502acc, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 64
m_fontSizeBase: 64
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &8615670272833101985
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2927528151102838886}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 964e3a3ab86805244bbde47d5f54950b, type: 3}
m_Name:
m_EditorClassIdentifier:
_key: PAUSE_TITLE
_table: UI
_fontConfig: {fileID: 0}
--- !u!114 &4365598704319647184
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2927528151102838886}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1fa7f5f3910b010489199ef7ddee773e, type: 3}
m_Name:
m_EditorClassIdentifier:
_kind: 9
_overrideFontSize: 0
--- !u!1 &4403954470527421291
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7999552713258881202}
- component: {fileID: 8785352439545448378}
m_Layer: 0
m_Name: MenuPanel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7999552713258881202
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4403954470527421291}
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: 8775082930616069399}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: -40}
m_SizeDelta: {x: 520, y: 360}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &8785352439545448378
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4403954470527421291}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 4
m_Spacing: 12
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!1 &6290509185992221595
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8775082930616069399}
- component: {fileID: 7892818653885871022}
- component: {fileID: 3327748105207433154}
- component: {fileID: 7681371240377189452}
- component: {fileID: 5812811190345792890}
- component: {fileID: 5040914954197509735}
- component: {fileID: 7585071610646537230}
m_Layer: 0
m_Name: UI_PauseScreen
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8775082930616069399
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6290509185992221595}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2133854998375789241}
- {fileID: 1431591709047051056}
- {fileID: 7999552713258881202}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!223 &7892818653885871022
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6290509185992221595}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 0
m_AdditionalShaderChannelsFlag: 0
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 50
m_TargetDisplay: 0
--- !u!114 &3327748105207433154
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6290509185992221595}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0.5
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!114 &7681371240377189452
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6290509185992221595}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!225 &5812811190345792890
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6290509185992221595}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!114 &5040914954197509735
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6290509185992221595}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 70ebf028cc731414caf75f2c7f4c28b4, type: 3}
m_Name:
m_EditorClassIdentifier:
_firstSelected: {fileID: 0}
_canvasGroup: {fileID: 5812811190345792890}
_fadeInDuration: 0.15
_selectFirstOnEnable: 1
_defaultMode: 0
_canCancel: 1
_config: {fileID: 11400000, guid: 74401971c9590c4479c11b9879aa3a73, type: 2}
_container: {fileID: 7999552713258881202}
_buttonPrefab: {fileID: 6247134819645328346, guid: e2fb1298d0d8c754e9329131507a17ee, type: 3}
_onResumeRequested: {fileID: 11400000, guid: f4a33cc49cd41d8498eefb1afd736cce, type: 2}
_onSceneLoadRequest: {fileID: 11400000, guid: 7a4675ba5f3b784448ce2d1e0048f119, type: 2}
--- !u!114 &7585071610646537230
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6290509185992221595}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 50572ad108fd3354da04ea8fba66370b, type: 3}
m_Name:
m_EditorClassIdentifier:
_theme: {fileID: 11400000, guid: 88bab5495f6103f4d9d69fa043d4c3e1, type: 2}
_applyOnEnable: 1

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 400d92f229c1eee4cab36a5a733263fa
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13930,7 +13930,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: a655e2461396a8348a32a13144438e8e, type: 3} m_Script: {fileID: 11500000, guid: a655e2461396a8348a32a13144438e8e, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
_defaultSource: {fileID: 0} _defaultSource: {fileID: 11400000, guid: caae9c7600281fe4e8d8637fa3fd2ca1, type: 2}
_hitCooldown: 0.1 _hitCooldown: 0.1
_id: _id:
_rivalHitBoxMask: _rivalHitBoxMask:
@@ -206359,10 +206359,18 @@ PrefabInstance:
propertyPath: navSegments.Array.data[397].cellClearances.Array.data[149] propertyPath: navSegments.Array.data[397].cellClearances.Array.data[149]
value: 8.989149 value: 8.989149
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1102186504, guid: f69fa61624ad24b45aea231f95b304f7, type: 3}
propertyPath: m_Enabled
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1148014305, guid: f69fa61624ad24b45aea231f95b304f7, type: 3} - target: {fileID: 1148014305, guid: f69fa61624ad24b45aea231f95b304f7, type: 3}
propertyPath: m_IsActive propertyPath: m_IsActive
value: 1 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1578608361, guid: f69fa61624ad24b45aea231f95b304f7, type: 3}
propertyPath: m_Enabled
value: 0
objectReference: {fileID: 0}
- target: {fileID: 84902510026916610, guid: f69fa61624ad24b45aea231f95b304f7, type: 3} - target: {fileID: 84902510026916610, guid: f69fa61624ad24b45aea231f95b304f7, type: 3}
propertyPath: m_IsActive propertyPath: m_IsActive
value: 0 value: 0
@@ -206431,6 +206439,10 @@ PrefabInstance:
propertyPath: m_IsActive propertyPath: m_IsActive
value: 1 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 4691926645901067482, guid: f69fa61624ad24b45aea231f95b304f7, type: 3}
propertyPath: m_Enabled
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6900265410266641283, guid: f69fa61624ad24b45aea231f95b304f7, type: 3} - target: {fileID: 6900265410266641283, guid: f69fa61624ad24b45aea231f95b304f7, type: 3}
propertyPath: m_Cameras.Array.size propertyPath: m_Cameras.Array.size
value: 0 value: 0

View File

@@ -57,7 +57,6 @@ namespace BaseGames.Core
SceneName = sm?.LastCheckpointScene, SceneName = sm?.LastCheckpointScene,
EntryTransitionId = sm?.LastCheckpointSpawnId, EntryTransitionId = sm?.LastCheckpointSpawnId,
TransitionType = TransitionType.Scene, TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
IsRespawn = true, IsRespawn = true,
}); });

View File

@@ -15,7 +15,8 @@ namespace BaseGames.Core.Events
/// <summary>过渡类型,决定 <see cref="BaseGames.Core.SceneService"/> 的演出行为(淡出时长、加载画面等)。 /// <summary>过渡类型,决定 <see cref="BaseGames.Core.SceneService"/> 的演出行为(淡出时长、加载画面等)。
/// 默认 <see cref="TransitionType.Room"/>,向后兼容旧请求。</summary> /// 默认 <see cref="TransitionType.Room"/>,向后兼容旧请求。</summary>
public TransitionType TransitionType; public TransitionType TransitionType;
/// <summary>是否显示加载画面(由 TransitionType 自动推导,通常无需手动设置)。</summary> /// <summary>是否显示加载画面。<b>无需手动设置</b>:由 <see cref="BaseGames.Core.SceneService"/>
/// 统一按 <see cref="TransitionType"/> 推导Scene→显示其余→不显示调用点设置的值会被覆盖。</summary>
public bool ShowLoadingScreen; public bool ShowLoadingScreen;
/// <summary>死亡复活时为 true不执行正常过渡动画</summary> /// <summary>死亡复活时为 true不执行正常过渡动画</summary>
public bool IsRespawn; public bool IsRespawn;

View File

@@ -21,57 +21,69 @@ namespace BaseGames.Core
[Header("Event Channels - Raise")] [Header("Event Channels - Raise")]
[SerializeField] private StringEventChannelSO _onSceneLoaded; [SerializeField] private StringEventChannelSO _onSceneLoaded;
[Header("Event Channels - Raise加载画面")] [Header("Event Channels - Raise加载进度")]
[Tooltip("ShowLoadingScreen=true 时,在加载开始时发布。")] [Tooltip("ShowLoadingScreen=true 时,持续发布加载进度 [0,1]\n" +
[SerializeField] private VoidEventChannelSO _onLoadingStarted; "加载画面的显示/隐藏由 SceneService 统一归口(见 SceneService._onLoadingStarted/Complete\n" +
[Tooltip("ShowLoadingScreen=true 时,在加载完成时发布。")] "本组件只负责发布真实进度。")]
[SerializeField] private VoidEventChannelSO _onLoadingComplete;
[Tooltip("ShowLoadingScreen=true 时,持续发布加载进度 [0,1]")]
[SerializeField] private FloatEventChannelSO _onLoadingProgressUpdated; [SerializeField] private FloatEventChannelSO _onLoadingProgressUpdated;
private string _currentRoomScene; private string _currentRoomScene;
private AsyncOperationHandle<SceneInstance> _currentHandle; private AsyncOperationHandle<SceneInstance> _currentHandle;
// 加载进度按阶段加权,使进度条单调推进、不在末尾从 0.9 直接跳到 1
// ① 加载新场景Addressables → 区间 [0, kLoadWeight]
// ② 卸载旧场景(无旧场景时直接补满)→ 区间 [kLoadWeight, 1]
// 注意Addressables 对单个场景的 PercentComplete 颗粒度较粗(本地常 0→1 跳变),
// 末端视觉平滑由 LoadingScreenManager 的填充缓动负责,这里只保证语义上的真实与单调。
private const float kLoadWeight = 0.85f;
private const float kUnloadWeight = 1f - kLoadWeight; // 0.15
public IEnumerator LoadSceneCoroutine(SceneLoadRequest request) public IEnumerator LoadSceneCoroutine(SceneLoadRequest request)
{ {
if (request.ShowLoadingScreen) bool showLoading = request.ShowLoadingScreen;
_onLoadingStarted?.Raise();
if (showLoading)
_onLoadingProgressUpdated?.Raise(0f);
// 先加载新场景Additive成功后再卸载旧场景 // 先加载新场景Additive成功后再卸载旧场景
// 顺序保证:若加载失败,旧场景仍保持可用,不会出现无场景的空状态 // 顺序保证:若加载失败,旧场景仍保持可用,不会出现无场景的空状态
var loadOp = AssetLoader.LoadSceneAsync(request.SceneName, LoadSceneMode.Additive); var loadOp = AssetLoader.LoadSceneAsync(request.SceneName, LoadSceneMode.Additive);
// 逐帧轮询以上报进度(不能直接 yield return loadOp那样无法回调进度 // 阶段 ①:逐帧轮询以上报真实进度(不能直接 yield return loadOp那样无法回调进度
while (!loadOp.IsDone) while (!loadOp.IsDone)
{ {
if (request.ShowLoadingScreen) if (showLoading)
_onLoadingProgressUpdated?.Raise(loadOp.PercentComplete * 0.9f); _onLoadingProgressUpdated?.Raise(loadOp.PercentComplete * kLoadWeight);
yield return null; yield return null;
} }
if (loadOp.Status != AsyncOperationStatus.Succeeded) if (loadOp.Status != AsyncOperationStatus.Succeeded)
{ {
Debug.LogError($"[SceneLoader] 加载场景失败:{request.SceneName}(旧场景保持不变)"); Debug.LogError($"[SceneLoader] 加载场景失败:{request.SceneName}(旧场景保持不变)");
if (request.ShowLoadingScreen) yield break; // 加载画面收尾由 SceneService 在外层统一负责(无论成败都会隐藏)
_onLoadingComplete?.Raise();
yield break;
} }
// 新场景加载成功,再卸载旧场景 // 加载阶段结束:进度推进到分段边界
if (showLoading)
_onLoadingProgressUpdated?.Raise(kLoadWeight);
// 阶段 ②:新场景加载成功,再卸载旧场景(占进度条末段 [kLoadWeight, 1]
if (!string.IsNullOrEmpty(_currentRoomScene) && _currentHandle.IsValid()) if (!string.IsNullOrEmpty(_currentRoomScene) && _currentHandle.IsValid())
{ {
var unloadOp = AssetLoader.UnloadSceneAsync(_currentHandle); var unloadOp = AssetLoader.UnloadSceneAsync(_currentHandle);
yield return unloadOp; while (!unloadOp.IsDone)
{
if (showLoading)
_onLoadingProgressUpdated?.Raise(kLoadWeight + unloadOp.PercentComplete * kUnloadWeight);
yield return null;
}
} }
_currentHandle = loadOp; _currentHandle = loadOp;
_currentRoomScene = request.SceneName; _currentRoomScene = request.SceneName;
if (request.ShowLoadingScreen) if (showLoading)
{
_onLoadingProgressUpdated?.Raise(1f); _onLoadingProgressUpdated?.Raise(1f);
_onLoadingComplete?.Raise();
}
_onSceneLoaded?.Raise(request.SceneName); _onSceneLoaded?.Raise(request.SceneName);
} }

View File

@@ -51,6 +51,12 @@ namespace BaseGames.Core
[SerializeField] private VoidEventChannelSO _onFadeInRequest; [SerializeField] private VoidEventChannelSO _onFadeInRequest;
[SerializeField] private VoidEventChannelSO _onFadeOutRequest; [SerializeField] private VoidEventChannelSO _onFadeOutRequest;
[Tooltip("加载画面显隐由 SceneService 统一归口包裹两条加载路径SceneLoader 与流式 coordinator\n" +
"保证流式房间Room_*,走 ISceneLoadCoordinator的 Scene 过渡也能显示加载画面。\n" +
"进度条进度仍由 SceneLoader 发布到 EVT_LoadingProgressUpdated。")]
[SerializeField] private VoidEventChannelSO _onLoadingStarted;
[SerializeField] private VoidEventChannelSO _onLoadingComplete;
[Tooltip("场景加载完成、WorldStateRegistry 已就绪后触发。\n" + [Tooltip("场景加载完成、WorldStateRegistry 已就绪后触发。\n" +
"场景内物体应订阅此事件,从 WorldStateRegistry 读取存档状态并应用(替代在 Start() 中读取)。\n" + "场景内物体应订阅此事件,从 WorldStateRegistry 读取存档状态并应用(替代在 Start() 中读取)。\n" +
"触发后会等待一帧,确保所有处理器执行完毕,再执行淡入显示场景。\n" + "触发后会等待一帧,确保所有处理器执行完毕,再执行淡入显示场景。\n" +
@@ -80,6 +86,11 @@ namespace BaseGames.Core
private void HandleSceneLoadRequest(SceneLoadRequest request) private void HandleSceneLoadRequest(SceneLoadRequest request)
{ {
// 加载画面是否显示,由 TransitionType 统一推导(见 SceneLoadRequest.ShowLoadingScreen 注释):
// Scene跨大区/进游戏/快速旅行/复活)→ 显示Room/Seamless/AtmosphericFade → 不显示。
// 各调用点无需再手动设置 ShowLoadingScreen避免出现"是 Scene 却没显示"的不一致。
request.ShowLoadingScreen = request.TransitionType == TransitionType.Scene;
// Seamless / AtmosphericFade 由 ITransitionDirector 处理(需要预加载支持) // Seamless / AtmosphericFade 由 ITransitionDirector 处理(需要预加载支持)
if (request.TransitionType == TransitionType.Seamless || if (request.TransitionType == TransitionType.Seamless ||
request.TransitionType == TransitionType.AtmosphericFade) request.TransitionType == TransitionType.AtmosphericFade)
@@ -113,9 +124,14 @@ namespace BaseGames.Core
if (fadeDuration > 0f) if (fadeDuration > 0f)
yield return new WaitForSeconds(fadeDuration); yield return new WaitForSeconds(fadeDuration);
// 加载画面统一在此显隐,包裹下面两条加载路径(黑幕已落下后再揭开加载画面)。
bool showLoading = request.ShowLoadingScreen;
if (showLoading) _onLoadingStarted?.Raise();
// 流式模式优先:若流式协调器已注册且声明对本场景的所有权,委托给流式系统加载。 // 流式模式优先:若流式协调器已注册且声明对本场景的所有权,委托给流式系统加载。
// 这确保复活 / 快速传送等使用 Room/Scene 类型的路径也能正确触发冷却和卸载生命周期, // 这确保复活 / 快速传送等使用 Room/Scene 类型的路径也能正确触发冷却和卸载生命周期,
// 避免前一房间在 RoomStreamingManager 中永远停留在 Active 状态。 // 避免前一房间在 RoomStreamingManager 中永远停留在 Active 状态。
// 注意:流式路径不发布逐帧进度,加载画面表现为不确定进度(结尾由缓动补满)。
var coordinator = ServiceLocator.GetOrDefault<ISceneLoadCoordinator>(); var coordinator = ServiceLocator.GetOrDefault<ISceneLoadCoordinator>();
if (coordinator != null && coordinator.OwnsScene(request.SceneName)) if (coordinator != null && coordinator.OwnsScene(request.SceneName))
{ {
@@ -124,6 +140,7 @@ namespace BaseGames.Core
} }
else if (_sceneLoader != null) else if (_sceneLoader != null)
{ {
// SceneLoader 负责逐帧发布真实进度EVT_LoadingProgressUpdated
yield return StartCoroutine(_sceneLoader.LoadSceneCoroutine(request)); yield return StartCoroutine(_sceneLoader.LoadSceneCoroutine(request));
} }
else else
@@ -131,6 +148,8 @@ namespace BaseGames.Core
Debug.LogError("[SceneService] _sceneLoader 未赋值,场景加载中断。请在 Inspector 中绑定 SceneLoader 组件。"); Debug.LogError("[SceneService] _sceneLoader 未赋值,场景加载中断。请在 Inspector 中绑定 SceneLoader 组件。");
} }
if (showLoading) _onLoadingComplete?.Raise();
// 通知WorldStateRegistry 已就绪,场景物体应在此帧内从中读取存档状态并应用初始状态。 // 通知WorldStateRegistry 已就绪,场景物体应在此帧内从中读取存档状态并应用初始状态。
// 订阅者WorldStateRegistrySaver、各场景 StateApplier 等)会在同一帧同步执行。 // 订阅者WorldStateRegistrySaver、各场景 StateApplier 等)会在同一帧同步执行。
_onSceneWorldStateRestored?.Raise(); _onSceneWorldStateRestored?.Raise();

View File

@@ -12,7 +12,7 @@ namespace BaseGames.Editor.Debugging
/// 直接加载首关(<see cref="AddressKeys.SceneGameChapter1"/>,当前映射到 TestRoomA并生成玩家 /// 直接加载首关(<see cref="AddressKeys.SceneGameChapter1"/>,当前映射到 TestRoomA并生成玩家
/// 便于在编辑器中快速验证游戏内系统(如地图)。 /// 便于在编辑器中快速验证游戏内系统(如地图)。
/// <para> /// <para>
/// 复刻 <c>MainMenuController.HandleSlotConfirmed</c> 的核心动作:建立内存存档 + 发场景过渡请求。 /// 复刻 <c>DataDrivenMainMenuController.HandleSlotConfirmed</c> 的核心动作:建立内存存档 + 发场景过渡请求。
/// 仅在 Play 模式可用。 /// 仅在 Play 模式可用。
/// </para> /// </para>
/// 菜单BaseGames ▸ Debug ▸ Enter First Room (Play) /// 菜单BaseGames ▸ Debug ▸ Enter First Room (Play)
@@ -49,7 +49,6 @@ namespace BaseGames.Editor.Debugging
SceneName = AddressKeys.SceneGameChapter1, // 当前映射到 TestRoomA SceneName = AddressKeys.SceneGameChapter1, // 当前映射到 TestRoomA
EntryTransitionId = null, // 默认出生点 EntryTransitionId = null, // 默认出生点
TransitionType = TransitionType.Scene, TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
IsRespawn = false, IsRespawn = false,
}); });

View File

@@ -138,8 +138,22 @@ namespace BaseGames.Editor
() => IsFieldBound(FindInLoadedScene<BootSequencer>(PersistentName), "_onSplashStartRequest")), () => IsFieldBound(FindInLoadedScene<BootSequencer>(PersistentName), "_onSplashStartRequest")),
("BootSequencer._onSplashComplete 已绑定", ("BootSequencer._onSplashComplete 已绑定",
() => IsFieldBound(FindInLoadedScene<BootSequencer>(PersistentName), "_onSplashComplete")), () => IsFieldBound(FindInLoadedScene<BootSequencer>(PersistentName), "_onSplashComplete")),
("SceneLoader._onLoadingStarted 已绑定", ("SceneService._onLoadingStarted 已绑定",
() => IsFieldBound(FindInLoadedScene<SceneLoader>(PersistentName), "_onLoadingStarted")), () => IsFieldBound(FindInLoadedScene<SceneService>(PersistentName), "_onLoadingStarted")),
("SceneService._onLoadingComplete 已绑定",
() => IsFieldBound(FindInLoadedScene<SceneService>(PersistentName), "_onLoadingComplete")),
("SceneLoader._onLoadingProgressUpdated 已绑定",
() => IsFieldBound(FindInLoadedScene<SceneLoader>(PersistentName), "_onLoadingProgressUpdated")),
("LoadingScreenManager._loadingRoot 已绑定",
() => IsFieldBound(FindInLoadedScene<LoadingScreenManager>(PersistentName), "_loadingRoot")),
("LoadingScreenManager._progressFill 已绑定",
() => IsFieldBound(FindInLoadedScene<LoadingScreenManager>(PersistentName), "_progressFill")),
("LoadingScreenManager._tipText 已绑定",
() => IsFieldBound(FindInLoadedScene<LoadingScreenManager>(PersistentName), "_tipText")),
("LoadingScreenManager._backgroundArts 非空",
() => IsArrayFieldNonEmpty(FindInLoadedScene<LoadingScreenManager>(PersistentName), "_backgroundArts")),
("LoadingScreenManager._config 已绑定",
() => IsFieldBound(FindInLoadedScene<LoadingScreenManager>(PersistentName), "_config")),
("GameManager._bootSequencer 已绑定", ("GameManager._bootSequencer 已绑定",
() => IsFieldBound(FindInLoadedScene<GameManager>(PersistentName), "_bootSequencer")), () => IsFieldBound(FindInLoadedScene<GameManager>(PersistentName), "_bootSequencer")),
("GameManager._onSceneLoadRequest 已绑定", ("GameManager._onSceneLoadRequest 已绑定",
@@ -168,14 +182,14 @@ namespace BaseGames.Editor
var checks = new (string label, System.Func<bool> fn)[] var checks = new (string label, System.Func<bool> fn)[]
{ {
("MainMenuController 组件存在", () => FindInLoadedScene<MainMenuController>(MainMenuName) != null), ("DataDrivenMainMenuController 组件存在", () => FindInLoadedScene<DataDrivenMainMenuController>(MainMenuName) != null),
("SaveSlotController 组件存在", () => FindInLoadedScene<SaveSlotController>(MainMenuName) != null), ("SaveSlotController 组件存在", () => FindInLoadedScene<SaveSlotController>(MainMenuName) != null),
("MainMenuController._onSceneLoadRequest 已绑定", ("DataDrivenMainMenuController._onSceneLoadRequest 已绑定",
() => IsFieldBound(FindInLoadedScene<MainMenuController>(MainMenuName), "_onSceneLoadRequest")), () => IsFieldBound(FindInLoadedScene<DataDrivenMainMenuController>(MainMenuName), "_onSceneLoadRequest")),
("MainMenuController._onSlotConfirmed 已绑定", ("DataDrivenMainMenuController._onSlotConfirmed 已绑定",
() => IsFieldBound(FindInLoadedScene<MainMenuController>(MainMenuName), "_onSlotConfirmed")), () => IsFieldBound(FindInLoadedScene<DataDrivenMainMenuController>(MainMenuName), "_onSlotConfirmed")),
("MainMenuController._firstGameSceneKey 非空", ("DataDrivenMainMenuController._firstGameSceneKey 非空",
() => IsStringFieldNonEmpty(FindInLoadedScene<MainMenuController>(MainMenuName), "_firstGameSceneKey")), () => IsStringFieldNonEmpty(FindInLoadedScene<DataDrivenMainMenuController>(MainMenuName), "_firstGameSceneKey")),
("Scene_MainMenu 已加入 Build Settings", ("Scene_MainMenu 已加入 Build Settings",
() => IsInBuildSettings(MainMenuName)), () => IsInBuildSettings(MainMenuName)),
}; };
@@ -296,14 +310,23 @@ namespace BaseGames.Editor
CheckField<GameManager>(PersistentName, "_bootSequencer", report, ref passCount, ref failCount); CheckField<GameManager>(PersistentName, "_bootSequencer", report, ref passCount, ref failCount);
CheckField<GameManager>(PersistentName, "_onSceneLoadRequest", report, ref passCount, ref failCount); CheckField<GameManager>(PersistentName, "_onSceneLoadRequest", report, ref passCount, ref failCount);
CheckField<GameManager>(PersistentName, "_onSceneLoaded", report, ref passCount, ref failCount); CheckField<GameManager>(PersistentName, "_onSceneLoaded", report, ref passCount, ref failCount);
CheckField<SceneLoader>(PersistentName, "_onLoadingStarted", report, ref passCount, ref failCount); CheckField<SceneService>(PersistentName, "_onLoadingStarted", report, ref passCount, ref failCount);
CheckField<SceneLoader>(PersistentName, "_onLoadingComplete", report, ref passCount, ref failCount); CheckField<SceneService>(PersistentName, "_onLoadingComplete", report, ref passCount, ref failCount);
CheckField<SceneLoader>(PersistentName, "_onLoadingProgressUpdated", report, ref passCount, ref failCount); CheckField<SceneLoader>(PersistentName, "_onLoadingProgressUpdated", report, ref passCount, ref failCount);
// LoadingScreenManager 自身 UI 字段
CheckField<LoadingScreenManager>(PersistentName, "_loadingRoot", report, ref passCount, ref failCount);
CheckField<LoadingScreenManager>(PersistentName, "_progressFill", report, ref passCount, ref failCount);
CheckField<LoadingScreenManager>(PersistentName, "_tipText", report, ref passCount, ref failCount);
if (IsArrayFieldNonEmpty(FindInLoadedScene<LoadingScreenManager>(PersistentName), "_backgroundArts"))
passCount++;
else { failCount++; report.Add("[未绑定] LoadingScreenManager._backgroundArts 为空(在 Persistent。"); }
CheckField<LoadingScreenManager>(PersistentName, "_config", report, ref passCount, ref failCount);
// Main menu scene // Main menu scene
CheckComponent<MainMenuController>(MainMenuName, "MainMenuController", report, ref passCount, ref failCount); CheckComponent<DataDrivenMainMenuController>(MainMenuName, "DataDrivenMainMenuController", report, ref passCount, ref failCount);
CheckField<MainMenuController>(MainMenuName, "_onSceneLoadRequest", report, ref passCount, ref failCount); CheckField<DataDrivenMainMenuController>(MainMenuName, "_onSceneLoadRequest", report, ref passCount, ref failCount);
CheckField<MainMenuController>(MainMenuName, "_firstGameSceneKey", report, ref passCount, ref failCount, requireNonEmpty: true); CheckField<DataDrivenMainMenuController>(MainMenuName, "_firstGameSceneKey", report, ref passCount, ref failCount, requireNonEmpty: true);
if (!IsInBuildSettings(MainMenuName)) if (!IsInBuildSettings(MainMenuName))
{ {
@@ -499,6 +522,19 @@ namespace BaseGames.Editor
return prop != null && !string.IsNullOrEmpty(prop.stringValue); return prop != null && !string.IsNullOrEmpty(prop.stringValue);
} }
/// <summary>数组字段是否存在且至少有一个非空(对象引用)元素。</summary>
private static bool IsArrayFieldNonEmpty(Object target, string fieldName)
{
if (target == null) return false;
var so = new SerializedObject(target);
var prop = so.FindProperty(fieldName);
if (prop == null || !prop.isArray) return false;
for (int i = 0; i < prop.arraySize; i++)
if (prop.GetArrayElementAtIndex(i).objectReferenceValue != null)
return true;
return false;
}
private static bool IsInBuildSettings(string sceneName) private static bool IsInBuildSettings(string sceneName)
{ {
foreach (var scene in EditorBuildSettings.scenes) foreach (var scene in EditorBuildSettings.scenes)

View File

@@ -12,6 +12,7 @@ using BaseGames.Enemies.Boss;
using BaseGames.Enemies.Navigation; using BaseGames.Enemies.Navigation;
using BaseGames.Enemies.Perception; using BaseGames.Enemies.Perception;
using BaseGames.Equipment; using BaseGames.Equipment;
using BaseGames.Feedback;
using BaseGames.Parry; using BaseGames.Parry;
using BaseGames.Player; using BaseGames.Player;
using BaseGames.Player.States; using BaseGames.Player.States;
@@ -84,6 +85,9 @@ namespace BaseGames.Editor
ShieldComponent shield = GetOrAddComponent<ShieldComponent>(root); ShieldComponent shield = GetOrAddComponent<ShieldComponent>(root);
PlayerWallDetector wallDetector = GetOrAddComponent<PlayerWallDetector>(root); PlayerWallDetector wallDetector = GetOrAddComponent<PlayerWallDetector>(root);
EquipmentManager equipmentManager = GetOrAddComponent<EquipmentManager>(root); EquipmentManager equipmentManager = GetOrAddComponent<EquipmentManager>(root);
// PlayerFeedbackEquipmentManager.Awake 经 GetComponent<PlayerFeedback>() 取本节点引用(护符效果反馈),
// 缺失会触发断言。须与 EquipmentManager 同节点。Feel 反馈链(MMF_Player)留待 Inspector 配置。
GetOrAddComponent<PlayerFeedback>(root);
GetOrAddComponent<SkillModifierRegistry>(root); GetOrAddComponent<SkillModifierRegistry>(root);
StatusEffectManager statusEffectManager = GetOrAddComponent<StatusEffectManager>(root); StatusEffectManager statusEffectManager = GetOrAddComponent<StatusEffectManager>(root);
// PlayerController 最后添加RequireComponent 会拉取上方已加好的组件 // PlayerController 最后添加RequireComponent 会拉取上方已加好的组件

View File

@@ -10,6 +10,7 @@ using BaseGames.UI;
using BaseGames.UI.HUD; using BaseGames.UI.HUD;
using BaseGames.UI.MainMenu; using BaseGames.UI.MainMenu;
using BaseGames.UI.Menus; using BaseGames.UI.Menus;
using BaseGames.UI.Settings;
using BaseGames.UI.Splash; using BaseGames.UI.Splash;
using BaseGames.World; using BaseGames.World;
using BaseGames.World.Map; using BaseGames.World.Map;
@@ -106,36 +107,41 @@ namespace BaseGames.Editor
GameObject vcamAGo = GetOrCreateChild(camera, "VCamA").gameObject; GameObject vcamAGo = GetOrCreateChild(camera, "VCamA").gameObject;
CinemachineCamera vcamA = GetOrAddComponent<CinemachineCamera>(vcamAGo); CinemachineCamera vcamA = GetOrAddComponent<CinemachineCamera>(vcamAGo);
GetOrAddComponent<CinemachineConfiner3D>(vcamAGo); // 扩展组件顺序:偏置扩展(AsymmetricDamping/AdaptiveLookahead)必须在 CinemachineConfiner3D 之前、
GetOrAddComponent<CameraAxisLockExtension>(vcamAGo); // AxisLock 必须在之后,否则偏置会把相机推出限位区域(见 CameraStateController.ValidateVCamExtensionOrder
GetOrAddComponent<CameraAsymmetricDampingExtension>(vcamAGo); GetOrAddComponent<CameraAsymmetricDampingExtension>(vcamAGo);
GetOrAddComponent<CameraAdaptiveLookaheadExtension>(vcamAGo); GetOrAddComponent<CameraAdaptiveLookaheadExtension>(vcamAGo);
GetOrAddComponent<CinemachineConfiner3D>(vcamAGo);
GetOrAddComponent<CameraAxisLockExtension>(vcamAGo);
// CinemachinePositionComposerBody 阶段组件必须存在ConfigureSlot 依赖它写入所有相机跟随参数 // CinemachinePositionComposerBody 阶段组件必须存在ConfigureSlot 依赖它写入所有相机跟随参数
var composerA = GetOrAddComponent<CinemachinePositionComposer>(vcamAGo); var composerA = GetOrAddComponent<CinemachinePositionComposer>(vcamAGo);
ApplyComposerDefaults(composerA); ApplyComposerDefaults(composerA);
// 幂等纠正:重跑脚手架时若既有 VCam 组件顺序不对,一并修正
CameraAreaEditor.FixVCamExtensionOrder(vcamA);
GameObject vcamBGo = GetOrCreateChild(camera, "VCamB").gameObject; GameObject vcamBGo = GetOrCreateChild(camera, "VCamB").gameObject;
CinemachineCamera vcamB = GetOrAddComponent<CinemachineCamera>(vcamBGo); CinemachineCamera vcamB = GetOrAddComponent<CinemachineCamera>(vcamBGo);
GetOrAddComponent<CinemachineConfiner3D>(vcamBGo);
GetOrAddComponent<CameraAxisLockExtension>(vcamBGo);
GetOrAddComponent<CameraAsymmetricDampingExtension>(vcamBGo); GetOrAddComponent<CameraAsymmetricDampingExtension>(vcamBGo);
GetOrAddComponent<CameraAdaptiveLookaheadExtension>(vcamBGo); GetOrAddComponent<CameraAdaptiveLookaheadExtension>(vcamBGo);
GetOrAddComponent<CinemachineConfiner3D>(vcamBGo);
GetOrAddComponent<CameraAxisLockExtension>(vcamBGo);
var composerB = GetOrAddComponent<CinemachinePositionComposer>(vcamBGo); var composerB = GetOrAddComponent<CinemachinePositionComposer>(vcamBGo);
ApplyComposerDefaults(composerB); ApplyComposerDefaults(composerB);
CameraAreaEditor.FixVCamExtensionOrder(vcamB);
GameObject uiRootGo = GetOrCreateChild(ui, "UIRoot").gameObject; GameObject uiRootGo = GetOrCreateChild(ui, "UIRoot").gameObject;
UIManager uiManager = GetOrAddComponent<UIManager>(uiRootGo); UIManager uiManager = GetOrAddComponent<UIManager>(uiRootGo);
// 统一 UI 导航栈:常驻此处,经 ServiceLocator 暴露,主菜单与游戏内共用。
UINavigator uiNavigator = GetOrAddComponent<UINavigator>(uiRootGo);
AssignAsset(uiNavigator, "_onUICancelPressed", report, false, "EVT_UICancelPressed");
GameObject hudCanvasGo = GetOrCreateCanvas(uiRootGo.transform, "HUD Canvas", 0); GameObject hudCanvasGo = GetOrCreateCanvas(uiRootGo.transform, "HUD Canvas", 0);
GameObject hudRootGo = GetOrCreateChild(hudCanvasGo.transform, "HUDRoot").gameObject; GameObject hudRootGo = GetOrCreateChild(hudCanvasGo.transform, "HUDRoot").gameObject;
HUDController hudController = GetOrAddComponent<HUDController>(hudRootGo); HUDController hudController = GetOrAddComponent<HUDController>(hudRootGo);
GameObject pauseRootGo = GetOrCreateChild(uiRootGo.transform, "PauseMenuRoot").gameObject; // 暂停菜单:实例化 UI_PauseScreen 预制件(样式在预制件、菜单项在 UI_PauseMenuConfig数据驱动
PauseMenuController pauseMenuCtrl = GetOrAddComponent<PauseMenuController>(pauseRootGo); GameObject pauseRootGo = EnsurePauseScreenInstance(uiRootGo.transform, report);
Button pauseBtnResume = GetOrAddComponent<Button>(GetOrCreateChild(pauseRootGo.transform, "Btn_Resume").gameObject);
Button pauseBtnSettings = GetOrAddComponent<Button>(GetOrCreateChild(pauseRootGo.transform, "Btn_Settings").gameObject);
Button pauseBtnMainMenu = GetOrAddComponent<Button>(GetOrCreateChild(pauseRootGo.transform, "Btn_MainMenu").gameObject);
Button pauseBtnQuit = GetOrAddComponent<Button>(GetOrCreateChild(pauseRootGo.transform, "Btn_Quit").gameObject);
GameObject settingsRootGo = GetOrCreateChild(uiRootGo.transform, "SettingsRoot").gameObject; GameObject settingsRootGo = GetOrCreateChild(uiRootGo.transform, "SettingsRoot").gameObject;
GameObject mapRootGo = GetOrCreateChild(uiRootGo.transform, "MapRoot").gameObject; GameObject mapRootGo = GetOrCreateChild(uiRootGo.transform, "MapRoot").gameObject;
GameObject shopRootGo = GetOrCreateChild(uiRootGo.transform, "ShopRoot").gameObject; GameObject shopRootGo = GetOrCreateChild(uiRootGo.transform, "ShopRoot").gameObject;
@@ -165,8 +171,7 @@ namespace BaseGames.Editor
AssignAsset(gameManager, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest"); AssignAsset(gameManager, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest");
AssignAsset(gameManager, "_onSceneLoaded", report, false, "EVT_SceneLoaded"); AssignAsset(gameManager, "_onSceneLoaded", report, false, "EVT_SceneLoaded");
AssignAsset(sceneLoader, "_onLoadingStarted", report, false, "EVT_LoadingStarted"); // 加载进度由 SceneLoader 发布;加载画面显隐已归口到 SceneService见下方 sceneService 绑定)。
AssignAsset(sceneLoader, "_onLoadingComplete", report, false, "EVT_LoadingComplete");
AssignAsset(sceneLoader, "_onLoadingProgressUpdated", report, false, "EVT_LoadingProgressUpdated"); AssignAsset(sceneLoader, "_onLoadingProgressUpdated", report, false, "EVT_LoadingProgressUpdated");
// ── Canvas_Splash启动演出────────────────────────────────────── // ── Canvas_Splash启动演出──────────────────────────────────────
@@ -183,23 +188,9 @@ namespace BaseGames.Editor
AssignAsset(splashCtrl, "_onSplashStartRequest", report, false, "EVT_SplashStartRequest"); AssignAsset(splashCtrl, "_onSplashStartRequest", report, false, "EVT_SplashStartRequest");
AssignAsset(splashCtrl, "_onSplashComplete", report, false, "EVT_SplashComplete"); AssignAsset(splashCtrl, "_onSplashComplete", report, false, "EVT_SplashComplete");
// ── LoadingScreenManager加载遮罩────────────────────────────── // ── 加载界面(实例化 UI_LoadingScreen 预制件)──────────────────────
GameObject loadingCanvasGo = GetOrCreateCanvas(ui.transform, "Canvas_Loading", 99); // 样式/美术在预制件里由美术编辑、内容/时长在 UI_LoadingConfig 由策划编辑(数据驱动,对齐 MainMenu 范式)。
LoadingScreenManager loadingMgr = GetOrAddComponent<LoadingScreenManager>(loadingCanvasGo); EnsureLoadingScreenInstance(ui.transform, report);
GameObject loadingRootGo = GetOrCreateChild(loadingCanvasGo.transform, "LoadingRoot").gameObject;
AssignReference(loadingMgr, "_loadingRoot", loadingRootGo);
GameObject progressFillGo = GetOrCreateChild(loadingRootGo.transform, "ProgressBarFill").gameObject;
Image progressFillImg = GetOrAddComponent<Image>(progressFillGo);
progressFillImg.type = Image.Type.Filled;
progressFillImg.fillMethod = Image.FillMethod.Horizontal;
AssignReference(loadingMgr, "_progressFill", progressFillImg);
GameObject tipTextGo = GetOrCreateChild(loadingRootGo.transform, "TipText").gameObject;
TextMeshProUGUI tipText = GetOrAddComponent<TextMeshProUGUI>(tipTextGo);
AssignReference(loadingMgr, "_tipText", tipText);
AssignAsset(loadingMgr, "_onLoadingStarted", report, false, "EVT_LoadingStarted");
AssignAsset(loadingMgr, "_onLoadingComplete", report, false, "EVT_LoadingComplete");
AssignAsset(loadingMgr, "_onLoadingProgressUpdated", report, false, "EVT_LoadingProgressUpdated");
loadingCanvasGo.SetActive(false);
// ── SceneFadeController场景切换黑幕─────────────────────────── // ── SceneFadeController场景切换黑幕───────────────────────────
// 纯逻辑节点:监听淡出/淡入事件后调用 SceneFeedback.Play()。 // 纯逻辑节点:监听淡出/淡入事件后调用 SceneFeedback.Play()。
@@ -246,6 +237,9 @@ namespace BaseGames.Editor
AssignAsset(sceneService, "_onFadeOutRequest", report, false, "EVT_FadeOutRequest"); AssignAsset(sceneService, "_onFadeOutRequest", report, false, "EVT_FadeOutRequest");
// 场景加载完毕、世界状态恢复后触发;场景物体据此应用存档状态,淡入前保证画面正确 // 场景加载完毕、世界状态恢复后触发;场景物体据此应用存档状态,淡入前保证画面正确
AssignAsset(sceneService, "_onSceneWorldStateRestored", report, true, "EVT_SceneWorldStateRestored"); AssignAsset(sceneService, "_onSceneWorldStateRestored", report, true, "EVT_SceneWorldStateRestored");
// 加载画面显隐归口到 SceneService包裹 SceneLoader 与流式 coordinator 两条加载路径)
AssignAsset(sceneService, "_onLoadingStarted", report, false, "EVT_LoadingStarted");
AssignAsset(sceneService, "_onLoadingComplete", report, false, "EVT_LoadingComplete");
AssignReference(sceneService, "_sceneLoader", sceneLoader); AssignReference(sceneService, "_sceneLoader", sceneLoader);
AssignAsset(sceneLoader, "_onSceneLoaded", report, false, "EVT_SceneLoaded"); AssignAsset(sceneLoader, "_onSceneLoaded", report, false, "EVT_SceneLoaded");
@@ -301,12 +295,7 @@ namespace BaseGames.Editor
AssignReference(deathScreenController, "_deathMessage", deathMessage); AssignReference(deathScreenController, "_deathMessage", deathMessage);
AssignAsset(deathScreenController, "_onDeathScreenConfirmed", report, true, "EVT_DeathScreenConfirmed"); AssignAsset(deathScreenController, "_onDeathScreenConfirmed", report, true, "EVT_DeathScreenConfirmed");
AssignReference(pauseMenuCtrl, "_btnResume", pauseBtnResume); // 暂停菜单引用_config / 事件频道)由 EnsurePauseScreenInstance 在实例化时绑定。
AssignReference(pauseMenuCtrl, "_btnSettings", pauseBtnSettings);
AssignReference(pauseMenuCtrl, "_btnMainMenu", pauseBtnMainMenu);
AssignReference(pauseMenuCtrl, "_btnQuit", pauseBtnQuit);
AssignAsset(pauseMenuCtrl, "_onResumeRequested", report, false, "EVT_ResumeRequested");
AssignAsset(pauseMenuCtrl, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest");
AddScaffoldNote(hudRootGo, "HUDController 已挂载。其内部图片/文本/图标 Prefab 依赖较多,需后续手工补 UI 资源与事件频道。", report); AddScaffoldNote(hudRootGo, "HUDController 已挂载。其内部图片/文本/图标 Prefab 依赖较多,需后续手工补 UI 资源与事件频道。", report);
@@ -375,13 +364,15 @@ namespace BaseGames.Editor
subTmp.color = new Color(0.7f, 0.66f, 0.55f, 0.9f); subTmp.raycastTarget = false; subTmp.characterSpacing = 8f; subTmp.color = new Color(0.7f, 0.66f, 0.55f, 0.9f); subTmp.raycastTarget = false; subTmp.characterSpacing = 8f;
BindLocalizedText(subtitleRt.gameObject, "MENU_SUBTITLE"); BindLocalizedText(subtitleRt.gameObject, "MENU_SUBTITLE");
// ── 主菜单控制器 ────────────────────────────────────────────────── // ── 数据驱动主菜单控制器(退役旧 MainMenuController──────────────
MainMenuController menuCtrl = GetOrAddComponent<MainMenuController>(canvasGo); RemoveComponentByTypeName(canvasGo, "MainMenuController");
DataDrivenMainMenuController menuCtrl = GetOrAddComponent<DataDrivenMainMenuController>(canvasGo);
AssignAsset(menuCtrl, "_onGameStateChanged", report, false, "EVT_GameStateChanged", "EVT_GameState"); AssignAsset(menuCtrl, "_onGameStateChanged", report, false, "EVT_GameStateChanged", "EVT_GameState");
AssignAsset(menuCtrl, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest"); AssignAsset(menuCtrl, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest");
AssignAsset(menuCtrl, "_onSlotConfirmed", report, false, "EVT_SlotConfirmed"); AssignAsset(menuCtrl, "_onSlotConfirmed", report, false, "EVT_SlotConfirmed");
// 取消键ESC / 手柄 B由 UINavigator 统一消费,主菜单控制器不再订阅。
// ── 主按钮区域(底部居中竖排,带 CanvasGroup 供入场动画)───────────── // ── 主按钮容器(数据驱动:据 UI_MainMenuConfig 在运行时生成按钮)─────
var menuPanelRt = GetOrCreateUIChild(canvasGo.transform, "MenuPanel"); var menuPanelRt = GetOrCreateUIChild(canvasGo.transform, "MenuPanel");
SetRect(menuPanelRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), SetRect(menuPanelRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
new Vector2(0f, 170f), new Vector2(560f, 470f)); new Vector2(0f, 170f), new Vector2(560f, 470f));
@@ -392,28 +383,17 @@ namespace BaseGames.Editor
menuVlg.childControlWidth = true; menuVlg.childControlHeight = true; menuVlg.childControlWidth = true; menuVlg.childControlHeight = true;
menuVlg.childForceExpandWidth = true; menuVlg.childForceExpandHeight = false; menuVlg.childForceExpandWidth = true; menuVlg.childForceExpandHeight = false;
GameObject btnNewGameGo = GetOrCreateButtonChild(menuPanelGo.transform, "Btn_NewGame", "New Game"); // 移除旧硬编码按钮(改由控制器运行时据表生成)
GameObject btnContinueGo = GetOrCreateButtonChild(menuPanelGo.transform, "Btn_Continue", "Continue"); foreach (var bn in new[] { "Btn_NewGame", "Btn_Continue", "Btn_Settings", "Btn_Credits", "Btn_Quit" })
GameObject btnSettingsGo = GetOrCreateButtonChild(menuPanelGo.transform, "Btn_Settings", "Settings"); DeleteChildIfPresent(menuPanelGo.transform, bn);
GameObject btnCreditsGo = GetOrCreateButtonChild(menuPanelGo.transform, "Btn_Credits", "Credits");
GameObject btnQuitGo = GetOrCreateButtonChild(menuPanelGo.transform, "Btn_Quit", "Quit");
foreach (var b in new[] { btnNewGameGo, btnContinueGo, btnSettingsGo, btnCreditsGo, btnQuitGo })
{
StyleAsTextButton(b);
var le = GetOrAddComponent<LayoutElement>(b);
le.preferredHeight = 64f; le.minHeight = 56f;
}
BindLocalizedButton(btnNewGameGo, "MENU_NEW_GAME");
BindLocalizedButton(btnContinueGo, "MENU_CONTINUE");
BindLocalizedButton(btnSettingsGo, "MENU_SETTINGS");
BindLocalizedButton(btnCreditsGo, "MENU_CREDITS");
BindLocalizedButton(btnQuitGo, "MENU_QUIT");
AssignReference(menuCtrl, "_btnNewGame", btnNewGameGo.GetComponent<Button>()); var menuConfig = LoadControlAsset<MainMenuConfigSO>("Assets/_Game/Data/UI/UI_MainMenuConfig.asset", report);
AssignReference(menuCtrl, "_btnContinue", btnContinueGo.GetComponent<Button>()); var menuBtnPrefab = LoadControlAsset<GameObject>("Assets/_Game/Prefabs/UI/Controls/UI_MainMenu_Button.prefab", report);
AssignReference(menuCtrl, "_btnSettings", btnSettingsGo.GetComponent<Button>()); var menuBtnView = menuBtnPrefab != null ? menuBtnPrefab.GetComponent<MainMenuButtonView>() : null;
AssignReference(menuCtrl, "_btnCredits", btnCreditsGo.GetComponent<Button>());
AssignReference(menuCtrl, "_btnQuit", btnQuitGo.GetComponent<Button>()); AssignReference(menuCtrl, "_config", menuConfig);
AssignReference(menuCtrl, "_container", menuPanelRt);
AssignReference(menuCtrl, "_buttonPrefab", menuBtnView);
AssignReference(menuCtrl, "_mainButtonsGroup", menuGroup); AssignReference(menuCtrl, "_mainButtonsGroup", menuGroup);
AssignReference(menuCtrl, "_mainButtonsRect", menuPanelRt); AssignReference(menuCtrl, "_mainButtonsRect", menuPanelRt);
@@ -423,9 +403,11 @@ namespace BaseGames.Editor
GameObject saveSlotPanelGo = saveSlotPanelRt.gameObject; GameObject saveSlotPanelGo = saveSlotPanelRt.gameObject;
saveSlotPanelGo.SetActive(false); saveSlotPanelGo.SetActive(false);
SaveSlotController saveSlotCtrl = GetOrAddComponent<SaveSlotController>(saveSlotPanelGo); SaveSlotController saveSlotCtrl = GetOrAddComponent<SaveSlotController>(saveSlotPanelGo);
// CanvasGroup导航器把覆盖确认 / 模式选择对话框模态压在其上时,屏蔽本面板交互(避免方向键穿透)。
var saveSlotGroup = GetOrAddComponent<CanvasGroup>(saveSlotPanelGo);
AssignAsset(saveSlotCtrl, "_onSlotConfirmed", report, false, "EVT_SlotConfirmed"); AssignAsset(saveSlotCtrl, "_onSlotConfirmed", report, false, "EVT_SlotConfirmed");
AssignReference(menuCtrl, "_saveSlotPanel", saveSlotPanelGo); AssignReference(saveSlotCtrl, "_canvasGroup", saveSlotGroup);
AssignReference(menuCtrl, "_saveSlotController", saveSlotCtrl); AssignReference(menuCtrl, "_saveSlotPanel", saveSlotCtrl);
// 近乎不透明的遮罩(拦截背后点击,并遮住主菜单避免文字透出) // 近乎不透明的遮罩(拦截背后点击,并遮住主菜单避免文字透出)
GetOrCreateImage(saveSlotPanelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true) GetOrCreateImage(saveSlotPanelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true)
@@ -467,7 +449,7 @@ namespace BaseGames.Editor
if (regionRegistry == null) if (regionRegistry == null)
report.Add("未找到 RegionRegistry 资产SaveSlotUI._regionRegistry 未绑定(存档槽背景图失效)。先运行 BaseGames/Setup/Create Project Assets。"); report.Add("未找到 RegionRegistry 资产SaveSlotUI._regionRegistry 未绑定(存档槽背景图失效)。先运行 BaseGames/Setup/Create Project Assets。");
// 返回按钮(关闭存档槽面板 → 绑定 MainMenuController._btnCloseSaveSlot // 返回按钮(关闭存档槽面板 → 绑定 DataDrivenMainMenuController._btnCloseSaveSlot
GameObject slotBackGo = GetOrCreateButtonChild(saveSlotPanelGo.transform, "BackButton", "Back"); GameObject slotBackGo = GetOrCreateButtonChild(saveSlotPanelGo.transform, "BackButton", "Back");
var slotBackRt = (RectTransform)slotBackGo.transform; var slotBackRt = (RectTransform)slotBackGo.transform;
SetRect(slotBackRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), SetRect(slotBackRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
@@ -476,31 +458,41 @@ namespace BaseGames.Editor
BindLocalizedButton(slotBackGo, "BTN_BACK"); BindLocalizedButton(slotBackGo, "BTN_BACK");
AssignReference(menuCtrl, "_btnCloseSaveSlot", slotBackGo.GetComponent<Button>()); AssignReference(menuCtrl, "_btnCloseSaveSlot", slotBackGo.GetComponent<Button>());
// ── ConfirmDialog(覆盖 / 删除确认)───────────────────── // ── ConfirmDialog / NewGameMode 作为 Canvas 直接子节点(非 SaveSlotPanel 子节点)──
ConfirmDialogController confirmCtrl = BuildConfirmDialog(saveSlotPanelGo.transform); // 关键:模态压栈会屏蔽 SaveSlotPanel 的 CanvasGroup若对话框是其子节点会被一同屏蔽。
// 故挂到 Canvas 根SaveSlotPanel 的兄弟,且因创建在后渲染于其上),彻底规避父子 CanvasGroup 传播。
DeleteChildIfPresent(saveSlotPanelGo.transform, "ConfirmDialog");
DeleteChildIfPresent(saveSlotPanelGo.transform, "NewGameMode");
ConfirmDialogController confirmCtrl = BuildConfirmDialog(canvasGo.transform);
AssignReference(saveSlotCtrl, "_confirmDialog", confirmCtrl); AssignReference(saveSlotCtrl, "_confirmDialog", confirmCtrl);
// ── NewGameMode(新游戏模式选择:普通 / 钢铁之魂)──────────────────── NewGameModeController modeCtrl = BuildNewGameMode(canvasGo.transform);
NewGameModeController modeCtrl = BuildNewGameMode(saveSlotPanelGo.transform);
AssignReference(saveSlotCtrl, "_modeSelect", modeCtrl); AssignReference(saveSlotCtrl, "_modeSelect", modeCtrl);
// ── SettingsPanel音量 / 画面 / 可访问性 / 语言)────────────────── // ── SettingsPanel音量 / 画面 / 可访问性 / 语言)──────────────────
var settingsPanelRt = GetOrCreateUIChild(canvasGo.transform, "SettingsPanel"); var settingsPanelRt = GetOrCreateUIChild(canvasGo.transform, "SettingsPanel");
StretchFull(settingsPanelRt); StretchFull(settingsPanelRt);
BuildSettingsPanel(settingsPanelRt.gameObject, menuCtrl, report); BuildSettingsPanel(settingsPanelRt.gameObject, menuCtrl, report);
// 整面板根作为导航器压栈对象UISimplePanel内含的数据驱动设置实例自管其 OnEnable 构建。
var settingsPanel = GetOrAddComponent<UISimplePanel>(settingsPanelRt.gameObject);
AssignReference(settingsPanel, "_canvasGroup", GetOrAddComponent<CanvasGroup>(settingsPanelRt.gameObject));
settingsPanelRt.gameObject.SetActive(false); settingsPanelRt.gameObject.SetActive(false);
AssignReference(menuCtrl, "_settingsPanel", settingsPanelRt.gameObject); AssignReference(menuCtrl, "_settingsPanel", settingsPanel);
// ── CreditsPanel制作团队──────────────────────────────────────── // ── CreditsPanel制作团队────────────────────────────────────────
var creditsPanelRt = GetOrCreateUIChild(canvasGo.transform, "CreditsPanel"); var creditsPanelRt = GetOrCreateUIChild(canvasGo.transform, "CreditsPanel");
StretchFull(creditsPanelRt); StretchFull(creditsPanelRt);
BuildCreditsPanel(creditsPanelRt.gameObject, menuCtrl, report); BuildCreditsPanel(creditsPanelRt.gameObject, menuCtrl, report);
var creditsPanel = GetOrAddComponent<UISimplePanel>(creditsPanelRt.gameObject);
AssignReference(creditsPanel, "_canvasGroup", GetOrAddComponent<CanvasGroup>(creditsPanelRt.gameObject));
creditsPanelRt.gameObject.SetActive(false); creditsPanelRt.gameObject.SetActive(false);
AssignReference(menuCtrl, "_creditsPanel", creditsPanelRt.gameObject); AssignReference(menuCtrl, "_creditsPanel", creditsPanel);
report.Add("设置 MainMenuController._firstGameSceneKey 为第一个游戏场景的 Addressable Key字符串。"); report.Add("设置 DataDrivenMainMenuController._firstGameSceneKey 为第一个游戏场景的 Addressable Key字符串。");
report.Add("主菜单按钮由 DataDrivenMainMenuController 据 UI_MainMenuConfig 在运行时生成(编辑器下 MenuPanel 为空属正常)。");
report.Add("存档槽卡片已含完整布局与文本(区域 / 时长 / 时间 / 灵珠 / 生命 / 钢魂徽章),空槽显示\"开始新游戏\"提示。"); report.Add("存档槽卡片已含完整布局与文本(区域 / 时长 / 时间 / 灵珠 / 生命 / 钢魂徽章),空槽显示\"开始新游戏\"提示。");
report.Add("ConfirmDialog / NewGameMode 已作为 SaveSlotPanel 子节点生成并接线;需补本地化键:" report.Add("ConfirmDialog / NewGameMode 已作为 Canvas 直接子节点(非 SaveSlotPanel 子节点,规避模态 CanvasGroup 传播)生成并接线;需补本地化键:"
+ "CONFIRM_OVERWRITE_TITLE / CONFIRM_OVERWRITE_BODY / CONFIRM_DELETE_TITLE / CONFIRM_DELETE_BODY / MODE_STEELSOUL_DESCUI 表)。"); + "CONFIRM_OVERWRITE_TITLE / CONFIRM_OVERWRITE_BODY / CONFIRM_DELETE_TITLE / CONFIRM_DELETE_BODY / MODE_STEELSOUL_DESCUI 表)。");
MarkDirtyAndLog("Main Menu 场景脚手架", root, report); MarkDirtyAndLog("Main Menu 场景脚手架", root, report);
@@ -625,6 +617,7 @@ namespace BaseGames.Editor
GameObject confirmGo = rootRt.gameObject; GameObject confirmGo = rootRt.gameObject;
confirmGo.SetActive(false); confirmGo.SetActive(false);
ConfirmDialogController confirmCtrl = GetOrAddComponent<ConfirmDialogController>(confirmGo); ConfirmDialogController confirmCtrl = GetOrAddComponent<ConfirmDialogController>(confirmGo);
var confirmGroup = GetOrAddComponent<CanvasGroup>(confirmGo);
GetOrCreateImage(confirmGo.transform, "Overlay", new Color(0f, 0f, 0f, 0.6f), true).transform.SetAsFirstSibling(); GetOrCreateImage(confirmGo.transform, "Overlay", new Color(0f, 0f, 0f, 0.6f), true).transform.SetAsFirstSibling();
@@ -645,7 +638,7 @@ namespace BaseGames.Editor
BindLocalizedButton(yesGo, "CONFIRM_YES"); BindLocalizedButton(yesGo, "CONFIRM_YES");
BindLocalizedButton(noGo, "CONFIRM_NO"); BindLocalizedButton(noGo, "CONFIRM_NO");
AssignReference(confirmCtrl, "_root", confirmGo); AssignReference(confirmCtrl, "_canvasGroup", confirmGroup);
AssignReference(confirmCtrl, "_titleText", titleTmp); AssignReference(confirmCtrl, "_titleText", titleTmp);
AssignReference(confirmCtrl, "_bodyText", bodyTmp); AssignReference(confirmCtrl, "_bodyText", bodyTmp);
AssignReference(confirmCtrl, "_confirmLabel", GetButtonLabel(yesGo)); AssignReference(confirmCtrl, "_confirmLabel", GetButtonLabel(yesGo));
@@ -656,46 +649,45 @@ namespace BaseGames.Editor
} }
/// <summary>构建新游戏模式选择面板(居中模态:普通 / 钢铁之魂 / 返回 + 钢魂说明),返回控制器。</summary> /// <summary>构建新游戏模式选择面板(居中模态:普通 / 钢铁之魂 / 返回 + 钢魂说明),返回控制器。</summary>
/// <summary>
/// 实例化 / 复用 <c>UI_NewGameModePanel</c> 预制件作为难度选择面板(命名 NewGameMode挂在 MainMenu Canvas 下)。
/// 数据驱动:样式在预制件、难度项在 UI_NewGameModeConfig。返回其 <see cref="NewGameModeController"/>(接 SaveSlot._modeSelect
/// </summary>
private static NewGameModeController BuildNewGameMode(Transform parent) private static NewGameModeController BuildNewGameMode(Transform parent)
{ {
var rootRt = GetOrCreateUIChild(parent, "NewGameMode"); const string instName = "NewGameMode";
StretchFull(rootRt); const string prefabPath = "Assets/_Game/Prefabs/UI/UI_NewGameModePanel.prefab";
GameObject modeGo = rootRt.gameObject; const string configPath = "Assets/_Game/Data/UI/UI_NewGameModeConfig.asset";
modeGo.SetActive(false);
NewGameModeController modeCtrl = GetOrAddComponent<NewGameModeController>(modeGo);
GetOrCreateImage(modeGo.transform, "Overlay", new Color(0f, 0f, 0f, 0.6f), true).transform.SetAsFirstSibling(); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
Transform existing = parent.Find(instName);
var boxRt = GetOrCreateUIChild(modeGo.transform, "DialogBox"); if (prefab == null)
SetRect(boxRt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(760f, 460f)); {
GetOrAddComponent<Image>(boxRt.gameObject).color = new Color(0.10f, 0.11f, 0.15f, 0.98f); Debug.LogWarning("[Scaffold] 缺少 UI_NewGameModePanel 预制件请先运行「BaseGames/UI/控件库/生成新游戏难度选择(预制件 + 默认配置)」,再重跑本脚手架。");
return existing != null ? existing.GetComponent<NewGameModeController>() : null;
}
var modeTitle = GetOrCreateText(boxRt.transform, "TitleText", "Select Mode", 40f, GoldText, TextAlignmentOptions.Center); GameObject go;
SetRect((RectTransform)modeTitle.transform, new Vector2(0f,1f), new Vector2(1f,1f), new Vector2(0.5f,1f), new Vector2(0f,-46f), new Vector2(-60f,56f)); if (existing != null && PrefabUtility.GetCorrespondingObjectFromSource(existing.gameObject) != null)
{
go = existing.gameObject; // 已是预制件实例:复用
}
else
{
if (existing != null) Undo.DestroyObjectImmediate(existing.gameObject); // 历史裸物体:替换
go = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
go.name = instName;
Undo.RegisterCreatedObjectUndo(go, "Instantiate UI_NewGameModePanel");
}
GameObject normalGo = GetOrCreateButtonChild(boxRt.transform, "Btn_Normal", "Normal"); var ctrl = go.GetComponent<NewGameModeController>();
SetRect((RectTransform)normalGo.transform, new Vector2(0.5f,1f), new Vector2(0.5f,1f), new Vector2(0.5f,1f), new Vector2(0f,-130f), new Vector2(420f,66f)); if (ctrl != null)
GameObject steelGo = GetOrCreateButtonChild(boxRt.transform, "Btn_SteelSoul", "Steel Soul"); {
SetRect((RectTransform)steelGo.transform, new Vector2(0.5f,1f), new Vector2(0.5f,1f), new Vector2(0.5f,1f), new Vector2(0f,-206f), new Vector2(420f,66f)); var cfg = AssetDatabase.LoadAssetAtPath<NewGameModeConfigSO>(configPath);
steelGo.GetComponent<Image>().color = new Color(0.30f, 0.33f, 0.40f, 0.85f); if (cfg != null) AssignReference(ctrl, "_config", cfg);
}
var steelDesc = GetOrCreateText(boxRt.transform, "SteelSoulDesc", "Steel Soul: one life. Death wipes the save.", 22f, new Color(0.8f,0.55f,0.55f,1f), TextAlignmentOptions.Center); return ctrl;
SetRect((RectTransform)steelDesc.transform, new Vector2(0f,0f), new Vector2(1f,0f), new Vector2(0.5f,0f), new Vector2(0f,130f), new Vector2(-80f,60f));
GameObject backGo = GetOrCreateButtonChild(boxRt.transform, "Btn_Back", "Back");
SetRect((RectTransform)backGo.transform, new Vector2(0.5f,0f), new Vector2(0.5f,0f), new Vector2(0.5f,0f), new Vector2(0f,46f), new Vector2(260f,60f));
StyleAsTextButton(backGo, 28f);
BindLocalizedText(modeTitle.gameObject, "MODE_SELECT_TITLE");
BindLocalizedButton(normalGo, "MODE_NORMAL");
BindLocalizedButton(steelGo, "MODE_STEELSOUL");
BindLocalizedButton(backGo, "BTN_BACK");
AssignReference(modeCtrl, "_root", modeGo);
AssignReference(modeCtrl, "_btnNormal", normalGo.GetComponent<Button>());
AssignReference(modeCtrl, "_btnSteelSoul", steelGo.GetComponent<Button>());
AssignReference(modeCtrl, "_btnBack", backGo.GetComponent<Button>());
AssignReference(modeCtrl, "_steelSoulDescText", steelDesc);
return modeCtrl;
} }
// ───────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────
@@ -1302,10 +1294,15 @@ namespace BaseGames.Editor
// 设置面板 / 制作团队面板构建器 // 设置面板 / 制作团队面板构建器
// ───────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────
/// <summary>构建设置面板内容(音量 / 画面 / 可访问性 / 语言)并绑定 SettingsPanelController 全部字段。</summary> /// <summary>
private static void BuildSettingsPanel(GameObject panelGo, MainMenuController menuCtrl, List<string> report) /// 构建设置面板:退役旧 SettingsPanelController改用数据驱动 UI_SettingsPanel 预制件
/// (自带 SettingsSchemaSO + 行预制件 + DataDrivenSettingsPanel运行时据表生成控件行
/// </summary>
private static void BuildSettingsPanel(GameObject panelGo, DataDrivenMainMenuController menuCtrl, List<string> report)
{ {
var ctrl = GetOrAddComponent<BaseGames.UI.SettingsPanelController>(panelGo); // 退役旧硬编码控制器与内联内容(幂等替换)
RemoveComponentByTypeName(panelGo, "SettingsPanelController");
DeleteChildIfPresent(panelGo.transform, "Content");
GetOrCreateImage(panelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true).transform.SetAsFirstSibling(); GetOrCreateImage(panelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true).transform.SetAsFirstSibling();
@@ -1314,55 +1311,12 @@ namespace BaseGames.Editor
SetRect((RectTransform)titleTmp.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -70f), new Vector2(900f, 80f)); SetRect((RectTransform)titleTmp.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -70f), new Vector2(900f, 80f));
BindLocalizedText(titleTmp.gameObject, "SETTINGS_TITLE"); BindLocalizedText(titleTmp.gameObject, "SETTINGS_TITLE");
var content = GetOrCreateUIChild(panelGo.transform, "Content"); // 数据驱动设置面板预制件(运行时据 UI_SettingsSchema 生成 Slider/Toggle/Dropdown 行)
SetRect(content, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -10f), new Vector2(860f, 720f)); var settingsInst = InstantiateControlPrefabOnce(
var vlg = GetOrAddComponent<VerticalLayoutGroup>(content.gameObject); "Assets/_Game/Prefabs/UI/Controls/UI_SettingsPanel.prefab", panelGo.transform, "DataDrivenSettings", report);
vlg.spacing = 8f; vlg.childAlignment = TextAnchor.UpperCenter; if (settingsInst != null && settingsInst.transform is RectTransform settingsRt)
vlg.childControlWidth = true; vlg.childControlHeight = true; SetRect(settingsRt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
vlg.childForceExpandWidth = true; vlg.childForceExpandHeight = false; new Vector2(0f, -10f), new Vector2(560f, 720f));
// 音量
var rMaster = CreateSettingRow(content, "Row_Master", "SETTINGS_MASTER_VOLUME");
var rBgm = CreateSettingRow(content, "Row_BGM", "SETTINGS_BGM_VOLUME");
var rSfx = CreateSettingRow(content, "Row_SFX", "SETTINGS_SFX_VOLUME");
var rAmbient = CreateSettingRow(content, "Row_Ambient", "SETTINGS_AMBIENT_VOLUME");
var sMaster = GetOrCreateSliderInRow(rMaster, 0f, 1f, 1f);
var sBgm = GetOrCreateSliderInRow(rBgm, 0f, 1f, 0.8f);
var sSfx = GetOrCreateSliderInRow(rSfx, 0f, 1f, 1f);
var sAmbient = GetOrCreateSliderInRow(rAmbient, 0f, 1f, 0.8f);
// 画面
var rVsync = CreateSettingRow(content, "Row_VSync", "SETTINGS_VSYNC");
var tVsync = GetOrCreateToggleInRow(rVsync);
var rFps = CreateSettingRow(content, "Row_FPS", "SETTINGS_FPS");
var dFps = GetOrCreateDropdownInRow(rFps, new[] { "30", "60", "120", "∞" });
// 可访问性
var rUiScale = CreateSettingRow(content, "Row_UIScale", "SETTINGS_UI_SCALE");
var sUiScale = GetOrCreateSliderInRow(rUiScale, 0.8f, 1.5f, 1f, 76f);
var uiScaleVal = GetOrCreateText(rUiScale, "ValueText", "100%", 22f, new Color(0.8f,0.78f,0.7f,1f), TextAlignmentOptions.MidlineRight);
SetRect((RectTransform)uiScaleVal.transform, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-12f, 0f), new Vector2(64f, 36f));
var rColorblind = CreateSettingRow(content, "Row_Colorblind", "SETTINGS_COLORBLIND");
var dColorblind = GetOrCreateDropdownInRow(rColorblind, new[] { "关闭", "红色盲", "绿色盲", "蓝黄色盲" });
var rShake = CreateSettingRow(content, "Row_ScreenShake", "SETTINGS_SCREEN_SHAKE");
var tShake = GetOrCreateToggleInRow(rShake);
// 语言
var rLang = CreateSettingRow(content, "Row_Language", "SETTINGS_LANGUAGE");
var dLang = GetOrCreateDropdownInRow(rLang, new[] { "中文", "English", "日本語", "한국어" });
// 绑定 SettingsPanelController 字段
AssignReference(ctrl, "_masterVolume", sMaster);
AssignReference(ctrl, "_bgmVolume", sBgm);
AssignReference(ctrl, "_sfxVolume", sSfx);
AssignReference(ctrl, "_ambientVolume", sAmbient);
AssignReference(ctrl, "_vSyncToggle", tVsync);
AssignReference(ctrl, "_fpsDropdown", dFps);
AssignReference(ctrl, "_uiScaleSlider", sUiScale);
AssignReference(ctrl, "_uiScaleValueText", uiScaleVal);
AssignReference(ctrl, "_colorblindDropdown", dColorblind);
AssignReference(ctrl, "_screenShakeToggle", tShake);
AssignReference(ctrl, "_languageDropdown", dLang);
// 返回按钮 // 返回按钮
GameObject backGo = GetOrCreateButtonChild(panelGo.transform, "BackButton", "Back"); GameObject backGo = GetOrCreateButtonChild(panelGo.transform, "BackButton", "Back");
@@ -1373,7 +1327,7 @@ namespace BaseGames.Editor
} }
/// <summary>构建制作团队面板(标题 + 滚动正文 + 返回)。</summary> /// <summary>构建制作团队面板(标题 + 滚动正文 + 返回)。</summary>
private static void BuildCreditsPanel(GameObject panelGo, MainMenuController menuCtrl, List<string> report) private static void BuildCreditsPanel(GameObject panelGo, DataDrivenMainMenuController menuCtrl, List<string> report)
{ {
GetOrCreateImage(panelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true).transform.SetAsFirstSibling(); GetOrCreateImage(panelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true).transform.SetAsFirstSibling();
@@ -1393,6 +1347,54 @@ namespace BaseGames.Editor
AssignReference(menuCtrl, "_btnCloseCredits", backGo.GetComponent<Button>()); AssignReference(menuCtrl, "_btnCloseCredits", backGo.GetComponent<Button>());
} }
// ── 数据驱动迁移助手 ──────────────────────────────────────────────────
/// <summary>
/// 按类型名移除组件(不引用类型符号,便于退役后删除脚本而不破坏脚手架编译)。
/// 已成为 missing script 的组件不会被此方法移除GetComponents 返回 null
/// </summary>
private static void RemoveComponentByTypeName(GameObject go, string typeName)
{
if (go == null) return;
foreach (var c in go.GetComponents<MonoBehaviour>())
if (c != null && c.GetType().Name == typeName)
Object.DestroyImmediate(c);
}
/// <summary>若存在同名子节点则删除(幂等替换旧硬编码内容)。</summary>
private static void DeleteChildIfPresent(Transform parent, string name)
{
var t = parent.Find(name);
if (t != null) Object.DestroyImmediate(t.gameObject);
}
/// <summary>加载控件库资产(不存在则记入报告)。</summary>
private static T LoadControlAsset<T>(string path, List<string> report) where T : Object
{
var a = AssetDatabase.LoadAssetAtPath<T>(path);
if (a == null)
report.Add($"未找到资产 {path}(请先运行 BaseGames/UI/控件库 下的生成菜单)。");
return a;
}
/// <summary>把控件库预制件实例化为指定父节点的子节点(按名幂等:已存在则复用)。</summary>
private static GameObject InstantiateControlPrefabOnce(string prefabPath, Transform parent,
string instanceName, List<string> report)
{
var existing = parent.Find(instanceName);
if (existing != null) return existing.gameObject;
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (prefab == null)
{
report.Add($"未找到预制件 {prefabPath}(请先运行 BaseGames/UI/控件库 下的生成菜单)。");
return null;
}
var inst = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
inst.name = instanceName;
return inst;
}
private static void AssignReference(Object target, string propertyName, Object value) private static void AssignReference(Object target, string propertyName, Object value)
{ {
AssignReference(target, propertyName, value, null); AssignReference(target, propertyName, value, null);
@@ -1428,6 +1430,95 @@ namespace BaseGames.Editor
serializedObject.ApplyModifiedPropertiesWithoutUndo(); serializedObject.ApplyModifiedPropertiesWithoutUndo();
} }
/// <summary>
/// 实例化 / 复用 <c>UI_LoadingScreen</c> 预制件作为 Persistent 的加载界面(命名 Canvas_Loading
/// 历史的非预制件裸物体会被替换为预制件实例;绑定 <c>_config</c> 与三个加载事件频道。
/// 预制件缺失时仅在 report 提示(先跑「生成加载界面」菜单),不报错。
/// </summary>
private static void EnsureLoadingScreenInstance(Transform uiParent, List<string> report)
{
const string instName = "Canvas_Loading";
const string prefabPath = "Assets/_Game/Prefabs/UI/UI_LoadingScreen.prefab";
const string configPath = "Assets/_Game/Data/UI/UI_LoadingConfig.asset";
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (prefab == null)
{
report.Add("缺少 UI_LoadingScreen 预制件请先运行菜单「BaseGames/UI/控件库/生成加载界面(预制件 + 默认配置)」,再重跑本脚手架。");
return;
}
Transform existing = uiParent.Find(instName);
GameObject go;
if (existing != null && PrefabUtility.GetCorrespondingObjectFromSource(existing.gameObject) != null)
{
go = existing.gameObject; // 已是预制件实例:复用
}
else
{
if (existing != null) Undo.DestroyObjectImmediate(existing.gameObject); // 历史裸物体:替换为预制件实例
go = (GameObject)PrefabUtility.InstantiatePrefab(prefab, uiParent);
go.name = instName;
Undo.RegisterCreatedObjectUndo(go, "Instantiate UI_LoadingScreen");
}
var loadingMgr = go.GetComponent<LoadingScreenManager>();
if (loadingMgr != null)
{
var cfg = AssetDatabase.LoadAssetAtPath<BaseGames.UI.LoadingScreenConfigSO>(configPath);
if (cfg != null) AssignReference(loadingMgr, "_config", cfg);
AssignAsset(loadingMgr, "_onLoadingStarted", report, false, "EVT_LoadingStarted");
AssignAsset(loadingMgr, "_onLoadingComplete", report, false, "EVT_LoadingComplete");
AssignAsset(loadingMgr, "_onLoadingProgressUpdated", report, false, "EVT_LoadingProgressUpdated");
}
report.Add("Canvas_Loading由 UI_LoadingScreen 预制件实例化(样式在预制件内由美术编辑;内容/时长在 UI_LoadingConfig 由策划编辑)。");
}
/// <summary>
/// 实例化 / 复用 <c>UI_PauseScreen</c> 预制件作为暂停面板(命名 PauseMenuRoot供 UIManager PanelId.Pause 注册)。
/// 历史的裸物体会被替换为预制件实例;绑定 <c>_config</c> 与事件频道。返回面板根 GameObject。
/// </summary>
private static GameObject EnsurePauseScreenInstance(Transform uiParent, List<string> report)
{
const string instName = "PauseMenuRoot";
const string prefabPath = "Assets/_Game/Prefabs/UI/UI_PauseScreen.prefab";
const string configPath = "Assets/_Game/Data/UI/UI_PauseMenuConfig.asset";
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
Transform existing = uiParent.Find(instName);
if (prefab == null)
{
report.Add("缺少 UI_PauseScreen 预制件请先运行「BaseGames/UI/控件库/生成暂停界面(预制件 + 默认配置)」,再重跑本脚手架。");
return existing != null ? existing.gameObject : GetOrCreateChild(uiParent, instName).gameObject;
}
GameObject go;
if (existing != null && PrefabUtility.GetCorrespondingObjectFromSource(existing.gameObject) != null)
{
go = existing.gameObject; // 已是预制件实例:复用
}
else
{
if (existing != null) Undo.DestroyObjectImmediate(existing.gameObject); // 历史裸物体:替换
go = (GameObject)PrefabUtility.InstantiatePrefab(prefab, uiParent);
go.name = instName;
Undo.RegisterCreatedObjectUndo(go, "Instantiate UI_PauseScreen");
}
var ctrl = go.GetComponent<DataDrivenPauseMenuController>();
if (ctrl != null)
{
var cfg = AssetDatabase.LoadAssetAtPath<PauseMenuConfigSO>(configPath);
if (cfg != null) AssignReference(ctrl, "_config", cfg);
// 与 GameManager._onResumeRequested 同源:实际资产名为 EVT_PauseResumedEVT_ResumeRequested 不存在,回退)
AssignAsset(ctrl, "_onResumeRequested", report, false, "EVT_ResumeRequested", "EVT_PauseResumed");
AssignAsset(ctrl, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest");
}
report.Add("PauseMenuRoot由 UI_PauseScreen 预制件实例化(样式在预制件、菜单项在 UI_PauseMenuConfig。");
return go;
}
private static void AssignAsset(Object target, string propertyName, List<string> report, bool required, params string[] candidates) private static void AssignAsset(Object target, string propertyName, List<string> report, bool required, params string[] candidates)
{ {
Object asset = FindFirstAsset(candidates); Object asset = FindFirstAsset(candidates);

View File

@@ -0,0 +1,67 @@
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using BaseGames.Localization;
using BaseGames.UI.MainMenu;
namespace BaseGames.Editor.UI
{
/// <summary>
/// <see cref="DataDrivenMainMenuController"/> 自定义 Inspector。
///
/// 菜单按钮运行时据 UI_MainMenuConfig 生成,编辑器下 MenuPanel 默认为空。
/// 本编辑器提供「预览 / 清除」按钮:在编辑器内据表生成按钮,让美术直观查看布局、
/// 就近调整 UI_MainMenu_Button 预制件样式与 MenuPanel 布局参数。预览按钮运行时会被自动清空重建。
/// </summary>
[CustomEditor(typeof(DataDrivenMainMenuController))]
public class DataDrivenMainMenuControllerEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var ctrl = (DataDrivenMainMenuController)target;
EditorGUILayout.Space(8);
EditorGUILayout.HelpBox(
"菜单按钮在运行时据 UI_MainMenuConfig 自动生成(编辑器下 MenuPanel 为空属正常)。\n" +
"点「预览」在编辑器内据表生成按钮以查看 / 调整样式与布局;样式改 UI_MainMenu_Button 预制件 / UI_Theme_Default。\n" +
"预览按钮仅用于编辑器查看,进入 Play 会自动清空并据表重建。",
MessageType.Info);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("▶ 预览菜单(编辑器内)", GUILayout.Height(26)))
PreviewMenu(ctrl);
if (GUILayout.Button("✕ 清除预览", GUILayout.Height(26), GUILayout.Width(120)))
{
ctrl.ClearMenu();
MarkDirty(ctrl);
}
}
}
private static void PreviewMenu(DataDrivenMainMenuController ctrl)
{
if (Application.isPlaying)
{
EditorUtility.DisplayDialog("预览菜单", "请退出 Play 模式后再使用编辑器预览。", "确定");
return;
}
ctrl.BuildMenu(); // 内部 ClearMenu + 据表实例化按钮(编辑器用 DestroyImmediate
// 标签:运行时 SetKey 在无服务时显示 key这里改用编辑器预览显示本地化译文
foreach (var loc in ctrl.GetComponentsInChildren<LocalizedText>(true))
loc.UpdateEditorPreview();
MarkDirty(ctrl);
}
private static void MarkDirty(DataDrivenMainMenuController ctrl)
{
EditorUtility.SetDirty(ctrl);
if (!Application.isPlaying)
EditorSceneManager.MarkSceneDirty(ctrl.gameObject.scene);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 93f9600681435a74187c249850a0f71c guid: 9a79a1b78119328418b64ed2c384ea9c
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,68 @@
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using BaseGames.Localization;
using BaseGames.UI.Settings;
namespace BaseGames.Editor.UI
{
/// <summary>
/// <see cref="DataDrivenSettingsPanel"/> 自定义 Inspector。
///
/// 设置行运行时据 SettingsSchemaSO 生成,编辑器下行容器默认为空。
/// 本编辑器提供「预览 / 清除」按钮:在编辑器内据表生成行,让美术直观查看分节与布局、
/// 就近调整 UI_Setting_*Row 预制件样式与容器布局参数。预览行运行时会被自动清空重建。
/// (与 <see cref="DataDrivenMainMenuControllerEditor"/> 同一套预览模式。)
/// </summary>
[CustomEditor(typeof(DataDrivenSettingsPanel))]
public class DataDrivenSettingsPanelEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var panel = (DataDrivenSettingsPanel)target;
EditorGUILayout.Space(8);
EditorGUILayout.HelpBox(
"设置行在运行时据 SettingsSchemaSO 自动生成(编辑器下行容器为空属正常)。\n" +
"点「预览」在编辑器内据表生成行以查看 / 调整样式与布局;样式改 UI_Setting_*Row 预制件 / UI_Theme_Default。\n" +
"预览行仅用于编辑器查看(不绑定 ISettingsService进入 Play 会自动清空并据表重建。",
MessageType.Info);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("▶ 预览设置行(编辑器内)", GUILayout.Height(26)))
PreviewRows(panel);
if (GUILayout.Button("✕ 清除预览", GUILayout.Height(26), GUILayout.Width(120)))
{
panel.Clear();
MarkDirty(panel);
}
}
}
private static void PreviewRows(DataDrivenSettingsPanel panel)
{
if (Application.isPlaying)
{
EditorUtility.DisplayDialog("预览设置行", "请退出 Play 模式后再使用编辑器预览。", "确定");
return;
}
panel.Build(); // 内部 Clear + 据表实例化行(编辑器无 ISettingsService仅生成行 + 标签)
// 标签:运行时 SetKey 在无服务时显示 key这里改用编辑器预览显示本地化译文
foreach (var loc in panel.GetComponentsInChildren<LocalizedText>(true))
loc.UpdateEditorPreview();
MarkDirty(panel);
}
private static void MarkDirty(DataDrivenSettingsPanel panel)
{
EditorUtility.SetDirty(panel);
if (!Application.isPlaying)
EditorSceneManager.MarkSceneDirty(panel.gameObject.scene);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5e1f947f274273f4c9a5a310fc40a625 guid: 9afba7ce47e6eeb41a8d08751bd4ff13
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -350,7 +350,6 @@ namespace BaseGames.Editor.UI
var cancelBtn = MakeButton(boxGo.transform, "CancelButton", "取消", new Vector2( 110, -90), out TMP_Text cancelLabel); var cancelBtn = MakeButton(boxGo.transform, "CancelButton", "取消", new Vector2( 110, -90), out TMP_Text cancelLabel);
var dialog = GetOrAddComponent<ConfirmDialogController>(dialogGo); var dialog = GetOrAddComponent<ConfirmDialogController>(dialogGo);
AssignRef(dialog, "_root", dialogGo);
AssignRef(dialog, "_titleText", title); AssignRef(dialog, "_titleText", title);
AssignRef(dialog, "_bodyText", body); AssignRef(dialog, "_bodyText", body);
AssignRef(dialog, "_confirmLabel", confirmLabel); AssignRef(dialog, "_confirmLabel", confirmLabel);

View File

@@ -359,6 +359,7 @@ namespace BaseGames.Editor.UI
private static GameObject BuildSettingHeader(List<string> report) private static GameObject BuildSettingHeader(List<string> report)
{ {
var go = NewUI(PfSettingHeader, 480, 32); var go = NewUI(PfSettingHeader, 480, 32);
var hdrLe = go.AddComponent<LayoutElement>(); hdrLe.preferredHeight = 36f; hdrLe.minHeight = 32f;
var lblGo = NewUIChild(go.transform, "Label", out var rt); var lblGo = NewUIChild(go.transform, "Label", out var rt);
Stretch(rt); Stretch(rt);
var lbl = lblGo.AddComponent<TextMeshProUGUI>(); var lbl = lblGo.AddComponent<TextMeshProUGUI>();
@@ -414,6 +415,7 @@ namespace BaseGames.Editor.UI
private static GameObject SettingRowRoot(string name, out TMP_Text label) private static GameObject SettingRowRoot(string name, out TMP_Text label)
{ {
var go = NewUI(name, 480, 44); var go = NewUI(name, 480, 44);
var rowLe = go.AddComponent<LayoutElement>(); rowLe.preferredHeight = 44f; rowLe.minHeight = 40f;
var h = go.AddComponent<HorizontalLayoutGroup>(); var h = go.AddComponent<HorizontalLayoutGroup>();
h.spacing = 12; h.childAlignment = TextAnchor.MiddleLeft; h.spacing = 12; h.childAlignment = TextAnchor.MiddleLeft;
h.childForceExpandWidth = false; h.childForceExpandHeight = false; h.childForceExpandWidth = false; h.childForceExpandHeight = false;
@@ -539,6 +541,10 @@ namespace BaseGames.Editor.UI
if (s_theme != null) AssignRef(uiBtn, "_theme", s_theme); if (s_theme != null) AssignRef(uiBtn, "_theme", s_theme);
var view = go.AddComponent<MainMenuButtonView>(); var view = go.AddComponent<MainMenuButtonView>();
// LayoutElement放入竖排布局VerticalLayoutGroup childControlHeight时保持固定行高避免被压扁
var le = go.AddComponent<LayoutElement>();
le.preferredHeight = 64f; le.minHeight = 56f;
var iconGo = NewUIChild(go.transform, "Icon", out var irt); var iconGo = NewUIChild(go.transform, "Icon", out var irt);
irt.anchorMin = new Vector2(0, 0.5f); irt.anchorMax = new Vector2(0, 0.5f); irt.pivot = new Vector2(0, 0.5f); irt.anchorMin = new Vector2(0, 0.5f); irt.anchorMax = new Vector2(0, 0.5f); irt.pivot = new Vector2(0, 0.5f);
irt.anchoredPosition = new Vector2(14, 0); irt.sizeDelta = new Vector2(32, 32); irt.anchoredPosition = new Vector2(14, 0); irt.sizeDelta = new Vector2(32, 32);

View File

@@ -0,0 +1,157 @@
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BaseGames.UI;
using BaseGames.UI.Theme;
namespace BaseGames.Editor.UI
{
/// <summary>
/// 输入提示控件脚手架:生成 / 放置 <c>UI_Control_InputPrompt</c> 预制件
/// —— 一个「按键图标 + 文字」的可复用提示(如菜单底部「Ⓐ 确定」「Ⓑ 返回」)。
///
/// <para>图标走已有的设备适配链路(零代码):</para>
/// 控件内 <see cref="InputIconImage"/> 据 ActionName 经 <c>IInputIconService</c> 解析
/// <b>当前设备</b>(键鼠/PS/Xbox/Switch+ 改键后的实际绑定 → 自动显示正确按键图标,
/// 设备切换/改键时自动刷新。<b>前提:美术把按键 sprite 填入 ICN_Keyboard/Xbox/PlayStation/Switch 资产。</b>
///
/// <para>策划/美术用法:</para>
/// 菜单「向选中节点放置 ▸ InputPrompt」放进任意 Canvas
/// 在 Icon 的 InputIconImage 填 ActionName如 Interact/JumpLabel 填提示文字(可挂 LocalizedText
///
/// 菜单BaseGames/UI/控件库/...
/// </summary>
public static class UIInputPromptScaffold
{
private const string ControlsDir = "Assets/_Game/Prefabs/UI/Controls";
private const string ThemePath = "Assets/_Game/Data/UI/Themes/UI_Theme_Default.asset";
private const string PfPrompt = "UI_Control_InputPrompt";
[MenuItem("BaseGames/UI/控件库/生成输入提示控件UI_Control_InputPrompt")]
public static void GeneratePrompt()
{
EnsureFolder(ControlsDir);
BuildPrompt();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[UIInputPrompt] 已生成 {ControlsDir}/{PfPrompt}.prefab。\n" +
"用法:菜单「向选中节点放置 ▸ InputPrompt」放进 UIIcon 填 ActionName、Label 填文字。\n" +
"图标随当前设备自动切换;需美术把按键 sprite 填入 ICN_Keyboard/Xbox/PlayStation/Switch 资产。");
}
[MenuItem("BaseGames/UI/控件库/向选中节点放置 ▸ InputPrompt")]
private static void PlacePrompt()
{
string path = $"{ControlsDir}/{PfPrompt}.prefab";
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null)
{
EditorUtility.DisplayDialog("控件库",
$"未找到预制件:{path}\n请先执行「生成输入提示控件UI_Control_InputPrompt」。", "确定");
return;
}
Transform parent = Selection.activeTransform;
if (parent == null)
{
var canvas = Object.FindObjectOfType<Canvas>();
parent = canvas != null ? canvas.transform : null;
}
var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
if (instance == null) return;
Undo.RegisterCreatedObjectUndo(instance, $"Place {PfPrompt}");
Selection.activeGameObject = instance;
EditorGUIUtility.PingObject(instance);
}
// ── 构建 ─────────────────────────────────────────────────────────────
private static void BuildPrompt()
{
var theme = AssetDatabase.LoadAssetAtPath<UIThemeSO>(ThemePath);
// 根:横向布局(图标 + 文字),自适应内容宽度
var root = NewUI(PfPrompt, 120f, 44f);
var h = root.AddComponent<HorizontalLayoutGroup>();
h.spacing = 8f; h.childAlignment = TextAnchor.MiddleLeft;
h.childForceExpandWidth = false; h.childForceExpandHeight = false;
h.childControlWidth = true; h.childControlHeight = true;
var fitter = root.AddComponent<ContentSizeFitter>();
fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// 图标Image + InputIconImage同物体InputIconImage 要求 Image
var iconGo = NewUIChild(root.transform, "Icon", out _);
var iconImg = iconGo.AddComponent<Image>();
iconImg.preserveAspect = true;
iconImg.sprite = Knob(); // 编辑期占位图;运行时由 InputIconImage 替换为当前设备按键图(无图则自动隐藏)
var iconLe = iconGo.AddComponent<LayoutElement>();
iconLe.preferredWidth = 40f; iconLe.preferredHeight = 40f;
iconLe.minWidth = 32f; iconLe.minHeight = 32f;
var iconComp = iconGo.AddComponent<InputIconImage>();
SetEnum(iconComp, "_mode", (int)InputIconImage.LookupMode.ByActionName);
SetString(iconComp, "_actionName", "Interact"); // 占位 ActionName策划按需改
// 文字:提示标签(可挂 LocalizedText默认占位
var lblGo = NewUIChild(root.transform, "Label", out _);
var lbl = lblGo.AddComponent<TextMeshProUGUI>();
lbl.text = "提示"; lbl.fontSize = 24f; lbl.alignment = TextAlignmentOptions.MidlineLeft;
lbl.color = new Color(0.80f, 0.80f, 0.82f, 1f);
lbl.raycastTarget = false;
var lblLe = lblGo.AddComponent<LayoutElement>();
lblLe.preferredHeight = 40f;
SetEnum(lblGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Text_Secondary);
_ = theme; // 配色随就近父级 UIThemeApplier本控件不自带 applier避免嵌套重复应用
string path = $"{ControlsDir}/{PfPrompt}.prefab";
PrefabUtility.SaveAsPrefabAsset(root, path);
Object.DestroyImmediate(root);
}
// ── 助手 ─────────────────────────────────────────────────────────────
private static GameObject NewUI(string name, float w, float h)
{
var go = new GameObject(name, typeof(RectTransform));
((RectTransform)go.transform).sizeDelta = new Vector2(w, h);
return go;
}
private static GameObject NewUIChild(Transform parent, string name, out RectTransform rt)
{
var go = new GameObject(name, typeof(RectTransform));
rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
return go;
}
private static void SetEnum(Component c, string prop, int value)
{
var so = new SerializedObject(c);
var p = so.FindProperty(prop);
if (p != null) { p.enumValueIndex = value; so.ApplyModifiedPropertiesWithoutUndo(); }
}
private static void SetString(Component c, string prop, string value)
{
var so = new SerializedObject(c);
var p = so.FindProperty(prop);
if (p != null) { p.stringValue = value; so.ApplyModifiedPropertiesWithoutUndo(); }
}
private static Sprite Knob() => AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Knob.psd");
private static void EnsureFolder(string dir)
{
string[] parts = dir.Split('/');
string cur = parts[0];
for (int i = 1; i < parts.Length; i++)
{
string next = $"{cur}/{parts[i]}";
if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(cur, parts[i]);
cur = next;
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 8adf13ec10899df439ee33bc9dcbcdeb guid: 67910478514f5d342b774044378cd877
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,275 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BaseGames.UI;
using BaseGames.UI.Theme;
using BaseGames.Core.Events;
using BaseGames.Localization;
using BaseGames.Editor.Localization;
namespace BaseGames.Editor.UI
{
/// <summary>
/// 加载界面脚手架:一键生成 / 更新 <c>UI_LoadingScreen</c> 预制件 + 默认 <c>UI_LoadingConfig</c> 数据表,
/// 并补充默认中/英文案。对齐项目「Prefab样式/美术)+ Config数据+ Theme配色」范式。
///
/// <para>生成后策划美术编辑方式:</para>
/// <list type="bullet">
/// <item>美术:双击 <c>UI_LoadingScreen.prefab</c> 进 Prefab Mode改背景图 / 进度条 sprite / 字体 / 加装饰 / 调布局。</item>
/// <item>策划:选 <c>UI_LoadingConfig.asset</c>,改提示文案 key、标题 key、加载时长/手感。</item>
/// <item>配色:改 <c>UI_Theme_Default</c>(带 UIThemeRole 的元素自动套用)。</item>
/// </list>
///
/// 菜单BaseGames/UI/控件库/生成加载界面(预制件 + 默认配置)
/// </summary>
public static class UILoadingScreenScaffold
{
private const string PrefabDir = "Assets/_Game/Prefabs/UI";
private const string ConfigDir = "Assets/_Game/Data/UI";
private const string ThemePath = "Assets/_Game/Data/UI/Themes/UI_Theme_Default.asset";
private const string PrefabName = "UI_LoadingScreen";
private const string ConfigName = "UI_LoadingConfig";
[MenuItem("BaseGames/UI/控件库/生成加载界面(预制件 + 默认配置)")]
public static void GeneratePrefab()
{
EnsureFolder(PrefabDir);
EnsureFolder(ConfigDir);
var report = new List<string>();
var theme = AssetDatabase.LoadAssetAtPath<UIThemeSO>(ThemePath);
var config = EnsureDefaultConfig(report);
SeedLocalization(report);
BuildPrefab(theme, config, report);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
var sb = new System.Text.StringBuilder("[UILoadingScreen] 加载界面已生成/更新:\n");
foreach (var r in report) sb.AppendLine(" • " + r);
sb.AppendLine("用法重跑「BaseGames/Scene/Setup/Scaffold Persistent Scene」把预制件实例化进 Persistent。");
sb.AppendLine("美术改样式 → 编辑 UI_LoadingScreen 预制件;策划改内容 → 编辑 UI_LoadingConfig配色 → UI_Theme_Default。");
Debug.Log(sb.ToString());
}
// ── 预制件构建 ───────────────────────────────────────────────────────
private static void BuildPrefab(UIThemeSO theme, LoadingScreenConfigSO config, List<string> report)
{
// 根:全屏 Overlay Canvas常驻 activeLoadingScreenManager 挂此处,靠保持 active 才能订阅事件)
var root = new GameObject(PrefabName,
typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
var canvas = root.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 99;
var scaler = root.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = 0.5f;
var mgr = root.AddComponent<LoadingScreenManager>();
// LoadingRoot铺满全屏初始 inactive由管理器 Show/Hide 切换)。
// 主题应用器挂在此处:每次 ShowSetActive true时 OnEnable 应用主题配色。
var loadingRootGo = NewUIChild(root.transform, "LoadingRoot", out var loadingRootRt);
Stretch(loadingRootRt);
var applier = loadingRootGo.AddComponent<UIThemeApplier>();
if (theme != null) AssignRef(applier, "_theme", theme);
// Background铺满美术可换 sprite或复制多个变体作随机背景池
var bgGo = NewUIChild(loadingRootGo.transform, "Background", out var bgRt);
Stretch(bgRt);
var bgImg = bgGo.AddComponent<Image>();
bgImg.color = new Color(0.04f, 0.05f, 0.07f, 1f);
SetEnum(bgGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Graphic_Background);
// ProgressBarTrack底部居中+ ProgressBarFill
var trackGo = NewUIChild(loadingRootGo.transform, "ProgressBarTrack", out var trackRt);
SetRect(trackRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
new Vector2(0f, 90f), new Vector2(760f, 14f));
var trackImg = trackGo.AddComponent<Image>();
trackImg.sprite = Standard(); trackImg.type = Image.Type.Sliced;
trackImg.color = new Color(1f, 1f, 1f, 0.12f);
trackImg.raycastTarget = false;
var fillGo = NewUIChild(trackGo.transform, "ProgressBarFill", out var fillRt);
Stretch(fillRt);
var fillImg = fillGo.AddComponent<Image>();
fillImg.sprite = Standard();
fillImg.type = Image.Type.Filled;
fillImg.fillMethod = Image.FillMethod.Horizontal;
fillImg.fillOrigin = (int)Image.OriginHorizontal.Left;
fillImg.fillAmount = 0f;
fillImg.color = new Color(0.85f, 0.80f, 0.55f, 1f);
fillImg.raycastTarget = false;
SetEnum(fillGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Graphic_Accent);
// Title居中偏上
var titleGo = NewUIChild(loadingRootGo.transform, "Title", out var titleRt);
SetRect(titleRt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
new Vector2(0f, 60f), new Vector2(1200f, 80f));
var title = titleGo.AddComponent<TextMeshProUGUI>();
title.text = "Loading"; title.alignment = TextAlignmentOptions.Center; title.fontSize = 56f;
title.raycastTarget = false;
SetEnum(titleGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Text_Header);
// TipText进度条上方
var tipGo = NewUIChild(loadingRootGo.transform, "TipText", out var tipRt);
SetRect(tipRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
new Vector2(0f, 130f), new Vector2(1200f, 48f));
var tip = tipGo.AddComponent<TextMeshProUGUI>();
tip.text = ""; tip.alignment = TextAlignmentOptions.Center; tip.fontSize = 28f;
tip.raycastTarget = false;
SetEnum(tipGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Text_Secondary);
// 绑定管理器视图引用 + 数据/事件(让预制件自足,掉哪都能用)
AssignRef(mgr, "_loadingRoot", loadingRootGo);
AssignRef(mgr, "_progressFill", fillImg);
AssignRef(mgr, "_titleText", title);
AssignRef(mgr, "_tipText", tip);
AssignArray(mgr, "_backgroundArts", new Object[] { bgImg });
AssignRef(mgr, "_config", config);
AssignRef(mgr, "_onLoadingStarted", FindAsset<VoidEventChannelSO>("EVT_LoadingStarted"));
AssignRef(mgr, "_onLoadingComplete", FindAsset<VoidEventChannelSO>("EVT_LoadingComplete"));
AssignRef(mgr, "_onLoadingProgressUpdated", FindAsset<FloatEventChannelSO>("EVT_LoadingProgressUpdated"));
// 关键Canvas 根保持 active仅 LoadingRoot 初始隐藏(否则管理器被自己关闭、永不订阅事件)
loadingRootGo.SetActive(false);
string path = $"{PrefabDir}/{PrefabName}.prefab";
PrefabUtility.SaveAsPrefabAsset(root, path);
Object.DestroyImmediate(root);
report.Add(path);
}
// ── 默认配置资产 ─────────────────────────────────────────────────────
private static LoadingScreenConfigSO EnsureDefaultConfig(List<string> report)
{
string path = $"{ConfigDir}/{ConfigName}.asset";
var cfg = AssetDatabase.LoadAssetAtPath<LoadingScreenConfigSO>(path);
bool created = cfg == null;
if (created)
{
cfg = ScriptableObject.CreateInstance<LoadingScreenConfigSO>();
AssetDatabase.CreateAsset(cfg, path);
}
if (created) // 仅新建时填默认,避免覆盖策划已有编辑
{
var so = new SerializedObject(cfg);
so.FindProperty("_titleKey").stringValue = "LOADING_TITLE";
var tips = so.FindProperty("_tipKeys");
string[] keys = { "LOADING_TIP_EXPLORE", "LOADING_TIP_SAVE", "LOADING_TIP_COMBAT" };
tips.arraySize = keys.Length;
for (int i = 0; i < keys.Length; i++) tips.GetArrayElementAtIndex(i).stringValue = keys[i];
so.ApplyModifiedPropertiesWithoutUndo();
report.Add($"{path}(默认标题/提示 key");
}
return cfg;
}
// ── 默认本地化文案(中/英;已存在的 key 不覆盖)─────────────────────────
private static void SeedLocalization(List<string> report)
{
var zh = new Dictionary<string, string>
{
{ "LOADING_TITLE", "加载中…" },
{ "LOADING_TIP_EXPLORE", "多探索每个角落,常有隐藏的道路与收集物。" },
{ "LOADING_TIP_SAVE", "在存档点休息可保存进度并恢复状态。" },
{ "LOADING_TIP_COMBAT", "把握闪避与格挡的时机,是战斗的关键。" },
};
var en = new Dictionary<string, string>
{
{ "LOADING_TITLE", "Loading…" },
{ "LOADING_TIP_EXPLORE", "Explore every corner — hidden paths and collectibles await." },
{ "LOADING_TIP_SAVE", "Rest at save points to save progress and restore yourself." },
{ "LOADING_TIP_COMBAT", "Timing your dodge and parry is the key to combat." },
};
int added = MergeWriteUI(Language.ChineseSimplified, zh) + MergeWriteUI(Language.English, en);
report.Add($"本地化新增 {added} 条已存在的不覆盖可用「BaseGames/Localization/表格编辑器」补译)。");
}
private static int MergeWriteUI(Language lang, Dictionary<string, string> kv)
{
var dict = LocalizationFileIO.Read(lang, LocalizationTable.UI);
int added = 0;
foreach (var p in kv)
if (!dict.ContainsKey(p.Key)) { dict[p.Key] = p.Value; added++; }
if (added > 0) LocalizationFileIO.Write(lang, LocalizationTable.UI, dict);
return added;
}
// ── 通用助手 ─────────────────────────────────────────────────────────
private static GameObject NewUIChild(Transform parent, string name, out RectTransform rt)
{
var go = new GameObject(name, typeof(RectTransform));
rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
return go;
}
private static void Stretch(RectTransform rt)
{
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
}
private static void SetRect(RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 pivot,
Vector2 anchoredPos, Vector2 size)
{
rt.anchorMin = aMin; rt.anchorMax = aMax; rt.pivot = pivot;
rt.anchoredPosition = anchoredPos; rt.sizeDelta = size;
}
private static void SetEnum(Component c, string prop, int value)
{
var so = new SerializedObject(c);
var p = so.FindProperty(prop);
if (p != null) { p.enumValueIndex = value; so.ApplyModifiedPropertiesWithoutUndo(); }
}
private static void AssignRef(Object target, string prop, Object value)
{
var so = new SerializedObject(target);
var p = so.FindProperty(prop);
if (p == null) { Debug.LogWarning($"[UILoadingScreen] 未找到属性 {target.GetType().Name}.{prop}"); return; }
p.objectReferenceValue = value;
so.ApplyModifiedPropertiesWithoutUndo();
}
private static void AssignArray(Object target, string prop, Object[] values)
{
var so = new SerializedObject(target);
var p = so.FindProperty(prop);
if (p == null || !p.isArray) return;
p.arraySize = values.Length;
for (int i = 0; i < values.Length; i++) p.GetArrayElementAtIndex(i).objectReferenceValue = values[i];
so.ApplyModifiedPropertiesWithoutUndo();
}
private static T FindAsset<T>(string name) where T : Object
{
foreach (var guid in AssetDatabase.FindAssets($"{name} t:{typeof(T).Name}"))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<T>(path);
if (asset != null && asset.name == name) return asset;
}
return null;
}
private static Sprite Standard() => AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd");
private static void EnsureFolder(string dir)
{
string[] parts = dir.Split('/');
string cur = parts[0];
for (int i = 1; i < parts.Length; i++)
{
string next = $"{cur}/{parts[i]}";
if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(cur, parts[i]);
cur = next;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2513f08dc745f2a45b2209eb81f2ca05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,265 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BaseGames.UI.MainMenu;
using BaseGames.UI.Theme;
using BaseGames.Core.Events;
using BaseGames.Localization;
using BaseGames.Editor.Localization;
namespace BaseGames.Editor.UI
{
/// <summary>
/// 新游戏难度选择脚手架:生成 / 更新 <c>UI_NewGameModePanel</c> 预制件 + 默认 <c>UI_NewGameModeConfig</c> 表 + 中英文案。
/// 该面板是 MainMenu Canvas 的子面板(非独立 Canvas由 SaveSlotController 经 ShowAsync 模态弹出。
///
/// 美术 → 改 UI_NewGameModePanel 预制件(暗化/对话框/标题/说明/返回样式,难度按钮样式见 UI_MainMenu_Button
/// 策划 → 改 UI_NewGameModeConfig增删/重排难度、改标签/说明)。
///
/// 菜单BaseGames/UI/控件库/生成新游戏难度选择(预制件 + 默认配置)
/// </summary>
public static class UINewGameModeScaffold
{
private const string PrefabDir = "Assets/_Game/Prefabs/UI";
private const string ConfigDir = "Assets/_Game/Data/UI";
private const string ThemePath = "Assets/_Game/Data/UI/Themes/UI_Theme_Default.asset";
private const string PrefabName = "UI_NewGameModePanel";
private const string ConfigName = "UI_NewGameModeConfig";
private const string BtnPrefabPath = "Assets/_Game/Prefabs/UI/Controls/UI_MainMenu_Button.prefab";
[MenuItem("BaseGames/UI/控件库/生成新游戏难度选择(预制件 + 默认配置)")]
public static void GeneratePrefab()
{
EnsureFolder(PrefabDir);
EnsureFolder(ConfigDir);
var report = new List<string>();
var theme = AssetDatabase.LoadAssetAtPath<UIThemeSO>(ThemePath);
var config = EnsureDefaultConfig(report);
SeedLocalization(report);
BuildPrefab(theme, config, report);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
var sb = new System.Text.StringBuilder("[UINewGameMode] 难度选择已生成/更新:\n");
foreach (var r in report) sb.AppendLine(" • " + r);
sb.AppendLine("用法重跑「BaseGames/Scene/Setup/Scaffold MainMenu Scene」实例化进 MainMenu接 SaveSlotController._modeSelect。");
Debug.Log(sb.ToString());
}
private static void BuildPrefab(UIThemeSO theme, NewGameModeConfigSO config, List<string> report)
{
// 根:铺满全屏的面板(非 Canvas作为 MainMenu Canvas 子节点)。初始隐藏,由导航器激活。
var root = new GameObject(PrefabName, typeof(RectTransform), typeof(CanvasGroup));
var rootRt = (RectTransform)root.transform;
Stretch(rootRt);
var ctrl = root.AddComponent<NewGameModeController>();
var applier = root.AddComponent<UIThemeApplier>();
if (theme != null) AssignRef(applier, "_theme", theme);
// 暗化遮罩(铺满,挡点击)
var overlayGo = NewUIChild(root.transform, "Overlay", out var overlayRt);
Stretch(overlayRt);
var overlay = overlayGo.AddComponent<Image>();
overlay.color = new Color(0f, 0f, 0f, 0.6f); overlay.raycastTarget = true;
// 对话框
var boxGo = NewUIChild(root.transform, "DialogBox", out var boxRt);
SetRect(boxRt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
Vector2.zero, new Vector2(760f, 520f));
var box = boxGo.AddComponent<Image>();
box.sprite = Standard(); box.type = Image.Type.Sliced;
box.color = new Color(0.10f, 0.11f, 0.15f, 0.98f);
SetEnum(boxGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Graphic_Background);
// 标题
var titleGo = NewUIChild(boxGo.transform, "Title", out var titleRt);
SetRect(titleRt, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0.5f, 1f),
new Vector2(0f, -50f), new Vector2(-60f, 60f));
var title = titleGo.AddComponent<TextMeshProUGUI>();
title.text = "选择难度"; title.alignment = TextAlignmentOptions.Center; title.fontSize = 40f;
title.raycastTarget = false;
var titleLoc = titleGo.AddComponent<LocalizedText>();
SetString(titleLoc, "_key", "MODE_SELECT_TITLE");
SetEnum(titleGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Text_Header);
// 难度按钮容器(竖排)
var optionGo = NewUIChild(boxGo.transform, "OptionContainer", out var optionRt);
SetRect(optionRt, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f),
new Vector2(0f, -130f), new Vector2(460f, 200f));
var vlg = optionGo.AddComponent<VerticalLayoutGroup>();
vlg.spacing = 12f; vlg.childAlignment = TextAnchor.UpperCenter;
vlg.childControlWidth = true; vlg.childControlHeight = true;
vlg.childForceExpandWidth = true; vlg.childForceExpandHeight = false;
// 说明文本(选中项时显示)
var descGo = NewUIChild(boxGo.transform, "DescText", out var descRt);
SetRect(descRt, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0.5f, 0f),
new Vector2(0f, 130f), new Vector2(-80f, 80f));
var desc = descGo.AddComponent<TextMeshProUGUI>();
desc.text = ""; desc.alignment = TextAlignmentOptions.Center; desc.fontSize = 22f;
desc.color = new Color(0.82f, 0.6f, 0.6f, 1f); desc.raycastTarget = false; desc.enableWordWrapping = true;
SetEnum(descGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Text_Secondary);
// 返回按钮
var backGo = NewUIChild(boxGo.transform, "Btn_Back", out var backRt);
SetRect(backRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
new Vector2(0f, 46f), new Vector2(260f, 60f));
var backImg = backGo.AddComponent<Image>();
backImg.sprite = Standard(); backImg.type = Image.Type.Sliced; backImg.color = new Color(1f, 1f, 1f, 0.06f);
var backBtn = backGo.AddComponent<Button>(); backBtn.targetGraphic = backImg;
var backLblGo = NewUIChild(backGo.transform, "Label", out var backLblRt);
Stretch(backLblRt);
var backLbl = backLblGo.AddComponent<TextMeshProUGUI>();
backLbl.text = "返回"; backLbl.alignment = TextAlignmentOptions.Center; backLbl.fontSize = 28f;
backLblGo.AddComponent<LocalizedText>();
SetString(backLblGo.GetComponent<LocalizedText>(), "_key", "BTN_BACK");
SetEnum(backLblGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Text_Primary);
// 绑定控制器
AssignRef(ctrl, "_config", config);
AssignRef(ctrl, "_container", optionRt);
AssignRef(ctrl, "_canvasGroup", root.GetComponent<CanvasGroup>());
AssignRef(ctrl, "_titleText", titleLoc);
AssignRef(ctrl, "_descText", desc);
AssignRef(ctrl, "_btnBack", backBtn);
var btnPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(BtnPrefabPath);
var btnView = btnPrefab != null ? btnPrefab.GetComponent<MainMenuButtonView>() : null;
if (btnView == null) report.Add($"未找到按钮预制件 {BtnPrefabPath}(请先运行「生成主菜单」)。");
AssignRef(ctrl, "_buttonPrefab", btnView);
root.SetActive(false); // 结果面板由导航器激活
string path = $"{PrefabDir}/{PrefabName}.prefab";
PrefabUtility.SaveAsPrefabAsset(root, path);
Object.DestroyImmediate(root);
report.Add(path);
}
private static NewGameModeConfigSO EnsureDefaultConfig(List<string> report)
{
string path = $"{ConfigDir}/{ConfigName}.asset";
var cfg = AssetDatabase.LoadAssetAtPath<NewGameModeConfigSO>(path);
bool created = cfg == null;
if (created) { cfg = ScriptableObject.CreateInstance<NewGameModeConfigSO>(); AssetDatabase.CreateAsset(cfg, path); }
if (created)
{
var items = new (DifficultyLevel lvl, string label, string desc)[]
{
(DifficultyLevel.Normal, "MODE_NORMAL", ""),
(DifficultyLevel.SteelSoul, "MODE_STEELSOUL", "MODE_STEELSOUL_DESC"),
};
var so = new SerializedObject(cfg);
var prop = so.FindProperty("_items");
prop.arraySize = items.Length;
for (int i = 0; i < items.Length; i++)
{
var el = prop.GetArrayElementAtIndex(i);
el.FindPropertyRelative("level").enumValueIndex = (int)items[i].lvl;
el.FindPropertyRelative("labelKey").stringValue = items[i].label;
el.FindPropertyRelative("descKey").stringValue = items[i].desc;
}
so.ApplyModifiedPropertiesWithoutUndo();
report.Add($"{path}(默认 {items.Length} 项:普通 / 钢铁之魂)");
}
return cfg;
}
private static void SeedLocalization(List<string> report)
{
var zh = new Dictionary<string, string>
{
{ "MODE_SELECT_TITLE", "选择难度" },
{ "MODE_NORMAL", "普通" },
{ "MODE_STEELSOUL", "钢铁之魂" },
{ "MODE_STEELSOUL_DESC", "一命模式:死亡即清空存档,请谨慎选择。" },
{ "BTN_BACK", "返回" },
};
var en = new Dictionary<string, string>
{
{ "MODE_SELECT_TITLE", "Select Mode" },
{ "MODE_NORMAL", "Normal" },
{ "MODE_STEELSOUL", "Steel Soul" },
{ "MODE_STEELSOUL_DESC", "One life. Death wipes the save — choose with care." },
{ "BTN_BACK", "Back" },
};
int added = MergeWriteUI(Language.ChineseSimplified, zh) + MergeWriteUI(Language.English, en);
report.Add($"本地化新增 {added} 条(已存在不覆盖)。");
}
private static int MergeWriteUI(Language lang, Dictionary<string, string> kv)
{
var dict = LocalizationFileIO.Read(lang, LocalizationTable.UI);
int added = 0;
foreach (var p in kv)
if (!dict.ContainsKey(p.Key)) { dict[p.Key] = p.Value; added++; }
if (added > 0) LocalizationFileIO.Write(lang, LocalizationTable.UI, dict);
return added;
}
// ── 助手 ─────────────────────────────────────────────────────────────
private static GameObject NewUIChild(Transform parent, string name, out RectTransform rt)
{
var go = new GameObject(name, typeof(RectTransform));
rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
return go;
}
private static void Stretch(RectTransform rt)
{
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
}
private static void SetRect(RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 pivot,
Vector2 anchoredPos, Vector2 size)
{
rt.anchorMin = aMin; rt.anchorMax = aMax; rt.pivot = pivot;
rt.anchoredPosition = anchoredPos; rt.sizeDelta = size;
}
private static void SetEnum(Component c, string prop, int value)
{
var so = new SerializedObject(c);
var p = so.FindProperty(prop);
if (p != null) { p.enumValueIndex = value; so.ApplyModifiedPropertiesWithoutUndo(); }
}
private static void SetString(Component c, string prop, string value)
{
if (c == null) return;
var so = new SerializedObject(c);
var p = so.FindProperty(prop);
if (p != null) { p.stringValue = value; so.ApplyModifiedPropertiesWithoutUndo(); }
}
private static void AssignRef(Object target, string prop, Object value)
{
var so = new SerializedObject(target);
var p = so.FindProperty(prop);
if (p == null) { Debug.LogWarning($"[UINewGameMode] 未找到属性 {target.GetType().Name}.{prop}"); return; }
p.objectReferenceValue = value;
so.ApplyModifiedPropertiesWithoutUndo();
}
private static Sprite Standard() => AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd");
private static void EnsureFolder(string dir)
{
string[] parts = dir.Split('/');
string cur = parts[0];
for (int i = 1; i < parts.Length; i++)
{
string next = $"{cur}/{parts[i]}";
if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(cur, parts[i]);
cur = next;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df82a9fb6a00ca740badcfaefc083560
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,255 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BaseGames.UI;
using BaseGames.UI.MainMenu;
using BaseGames.UI.Theme;
using BaseGames.Core.Events;
using BaseGames.Localization;
using BaseGames.Editor.Localization;
namespace BaseGames.Editor.UI
{
/// <summary>
/// 暂停界面脚手架:生成 / 更新 <c>UI_PauseScreen</c> 预制件 + 默认 <c>UI_PauseMenuConfig</c> 表 + 中英文案。
/// 对齐「Prefab样式+ Config数据+ Theme配色」范式。
///
/// <para>编辑方式:</para>
/// 美术 → 双击 <c>UI_PauseScreen.prefab</c> 改暗化背景/标题/布局/按钮样式(按钮样式见 UI_MainMenu_Button
/// 策划 → 选 <c>UI_PauseMenuConfig.asset</c> 增删/重排/改标签/改动作。
///
/// 菜单BaseGames/UI/控件库/生成暂停界面(预制件 + 默认配置)
/// </summary>
public static class UIPauseScreenScaffold
{
private const string PrefabDir = "Assets/_Game/Prefabs/UI";
private const string ControlsDir = "Assets/_Game/Prefabs/UI/Controls";
private const string ConfigDir = "Assets/_Game/Data/UI";
private const string ThemePath = "Assets/_Game/Data/UI/Themes/UI_Theme_Default.asset";
private const string PrefabName = "UI_PauseScreen";
private const string ConfigName = "UI_PauseMenuConfig";
private const string BtnPrefabPath = "Assets/_Game/Prefabs/UI/Controls/UI_MainMenu_Button.prefab";
[MenuItem("BaseGames/UI/控件库/生成暂停界面(预制件 + 默认配置)")]
public static void GeneratePrefab()
{
EnsureFolder(PrefabDir);
EnsureFolder(ConfigDir);
var report = new List<string>();
var theme = AssetDatabase.LoadAssetAtPath<UIThemeSO>(ThemePath);
var config = EnsureDefaultConfig(report);
SeedLocalization(report);
BuildPrefab(theme, config, report);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
var sb = new System.Text.StringBuilder("[UIPauseScreen] 暂停界面已生成/更新:\n");
foreach (var r in report) sb.AppendLine(" • " + r);
sb.AppendLine("用法重跑「BaseGames/Scene/Setup/Scaffold Persistent Scene」实例化进 Persistent作为 PanelId.Pause 面板)。");
Debug.Log(sb.ToString());
}
// ── 预制件构建 ───────────────────────────────────────────────────────
private static void BuildPrefab(UIThemeSO theme, PauseMenuConfigSO config, List<string> report)
{
// 根:全屏 Overlay CanvassortOrder 50HUD 之上、加载/Splash 之下)
var root = new GameObject(PrefabName,
typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(CanvasGroup));
var canvas = root.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 50;
var scaler = root.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = 0.5f;
var ctrl = root.AddComponent<DataDrivenPauseMenuController>();
// 暗化背景(铺满,挡住下层点击)
var dimGo = NewUIChild(root.transform, "Dim", out var dimRt);
Stretch(dimRt);
var dimImg = dimGo.AddComponent<Image>();
dimImg.color = new Color(0f, 0f, 0f, 0.72f);
dimImg.raycastTarget = true;
// 主题应用器挂根(每次 OnEnable 应用配色到带 UIThemeRole 的子节点)
var applier = root.AddComponent<UIThemeApplier>();
if (theme != null) AssignRef(applier, "_theme", theme);
// 标题
var titleGo = NewUIChild(root.transform, "Title", out var titleRt);
SetRect(titleRt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
new Vector2(0f, 220f), new Vector2(800f, 90f));
var title = titleGo.AddComponent<TextMeshProUGUI>();
title.text = "暂停"; title.alignment = TextAlignmentOptions.Center; title.fontSize = 64f;
title.raycastTarget = false;
titleGo.AddComponent<LocalizedText>(); // key 由脚手架下面绑 PAUSE_TITLE
SetString(titleGo.GetComponent<LocalizedText>(), "_key", "PAUSE_TITLE");
SetEnum(titleGo.AddComponent<UIThemeRole>(), "_kind", (int)UIThemeRoleKind.Text_Header);
// 按钮容器(居中竖排;运行时据表生成按钮)
var menuGo = NewUIChild(root.transform, "MenuPanel", out var menuRt);
SetRect(menuRt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
new Vector2(0f, -40f), new Vector2(520f, 360f));
var vlg = menuGo.AddComponent<VerticalLayoutGroup>();
vlg.spacing = 12f; vlg.childAlignment = TextAnchor.MiddleCenter;
vlg.childControlWidth = true; vlg.childControlHeight = true;
vlg.childForceExpandWidth = true; vlg.childForceExpandHeight = false;
// 绑定控制器
AssignRef(ctrl, "_config", config);
AssignRef(ctrl, "_container", menuRt);
AssignRef(ctrl, "_canvasGroup", root.GetComponent<CanvasGroup>());
var btnPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(BtnPrefabPath);
var btnView = btnPrefab != null ? btnPrefab.GetComponent<MainMenuButtonView>() : null;
if (btnView == null) report.Add($"未找到按钮预制件 {BtnPrefabPath}(请先运行「生成主菜单」)。");
AssignRef(ctrl, "_buttonPrefab", btnView);
// resume 频道与 GameManager 同源:实际资产名 EVT_PauseResumedEVT_ResumeRequested 不存在)
AssignRef(ctrl, "_onResumeRequested", FindAsset<VoidEventChannelSO>("EVT_PauseResumed")
?? FindAsset<VoidEventChannelSO>("EVT_ResumeRequested"));
AssignRef(ctrl, "_onSceneLoadRequest", FindAsset<SceneLoadRequestEventChannelSO>("EVT_SceneLoadRequest"));
string path = $"{PrefabDir}/{PrefabName}.prefab";
PrefabUtility.SaveAsPrefabAsset(root, path);
Object.DestroyImmediate(root);
report.Add(path);
}
// ── 默认配置 ─────────────────────────────────────────────────────────
private static PauseMenuConfigSO EnsureDefaultConfig(List<string> report)
{
string path = $"{ConfigDir}/{ConfigName}.asset";
var cfg = AssetDatabase.LoadAssetAtPath<PauseMenuConfigSO>(path);
bool created = cfg == null;
if (created) { cfg = ScriptableObject.CreateInstance<PauseMenuConfigSO>(); AssetDatabase.CreateAsset(cfg, path); }
if (created)
{
var items = new (string key, PauseMenuAction a)[]
{
("PAUSE_RESUME", PauseMenuAction.Resume),
("MENU_SETTINGS", PauseMenuAction.OpenSettings),
("PAUSE_MAIN_MENU", PauseMenuAction.ReturnToMainMenu),
("MENU_QUIT", PauseMenuAction.Quit),
};
var so = new SerializedObject(cfg);
var prop = so.FindProperty("_items");
prop.arraySize = items.Length;
for (int i = 0; i < items.Length; i++)
{
var el = prop.GetArrayElementAtIndex(i);
el.FindPropertyRelative("labelKey").stringValue = items[i].key;
el.FindPropertyRelative("action").enumValueIndex = (int)items[i].a;
el.FindPropertyRelative("sceneKey").stringValue = "";
}
so.ApplyModifiedPropertiesWithoutUndo();
report.Add($"{path}(默认 {items.Length} 项)");
}
return cfg;
}
// ── 默认本地化(已存在的 key 不覆盖)────────────────────────────────────
private static void SeedLocalization(List<string> report)
{
var zh = new Dictionary<string, string>
{
{ "PAUSE_TITLE", "暂停" },
{ "PAUSE_RESUME", "继续" },
{ "PAUSE_MAIN_MENU", "返回主菜单" },
};
var en = new Dictionary<string, string>
{
{ "PAUSE_TITLE", "Paused" },
{ "PAUSE_RESUME", "Resume" },
{ "PAUSE_MAIN_MENU", "Return to Title" },
};
int added = MergeWriteUI(Language.ChineseSimplified, zh) + MergeWriteUI(Language.English, en);
report.Add($"本地化新增 {added} 条已存在不覆盖MENU_SETTINGS/MENU_QUIT 复用主菜单译文)。");
}
private static int MergeWriteUI(Language lang, Dictionary<string, string> kv)
{
var dict = LocalizationFileIO.Read(lang, LocalizationTable.UI);
int added = 0;
foreach (var p in kv)
if (!dict.ContainsKey(p.Key)) { dict[p.Key] = p.Value; added++; }
if (added > 0) LocalizationFileIO.Write(lang, LocalizationTable.UI, dict);
return added;
}
// ── 助手 ─────────────────────────────────────────────────────────────
private static GameObject NewUIChild(Transform parent, string name, out RectTransform rt)
{
var go = new GameObject(name, typeof(RectTransform));
rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
return go;
}
private static void Stretch(RectTransform rt)
{
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
}
private static void SetRect(RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 pivot,
Vector2 anchoredPos, Vector2 size)
{
rt.anchorMin = aMin; rt.anchorMax = aMax; rt.pivot = pivot;
rt.anchoredPosition = anchoredPos; rt.sizeDelta = size;
}
private static void SetEnum(Component c, string prop, int value)
{
var so = new SerializedObject(c);
var p = so.FindProperty(prop);
if (p != null) { p.enumValueIndex = value; so.ApplyModifiedPropertiesWithoutUndo(); }
}
private static void SetString(Component c, string prop, string value)
{
if (c == null) return;
var so = new SerializedObject(c);
var p = so.FindProperty(prop);
if (p != null) { p.stringValue = value; so.ApplyModifiedPropertiesWithoutUndo(); }
}
private static void AssignRef(Object target, string prop, Object value)
{
var so = new SerializedObject(target);
var p = so.FindProperty(prop);
if (p == null) { Debug.LogWarning($"[UIPauseScreen] 未找到属性 {target.GetType().Name}.{prop}"); return; }
p.objectReferenceValue = value;
so.ApplyModifiedPropertiesWithoutUndo();
}
private static T FindAsset<T>(string name) where T : Object
{
foreach (var guid in AssetDatabase.FindAssets($"{name} t:{typeof(T).Name}"))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<T>(path);
if (asset != null && asset.name == name) return asset;
}
return null;
}
private static void EnsureFolder(string dir)
{
string[] parts = dir.Split('/');
string cur = parts[0];
for (int i = 1; i < parts.Length; i++)
{
string next = $"{cur}/{parts[i]}";
if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(cur, parts[i]);
cur = next;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d74eff677fd2a04ab74c05b8cced069
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -214,8 +214,8 @@ namespace BaseGames.Input
BindPerformed(_ui, "Navigate", ctx => NavigateEvent?.Invoke(ctx.ReadValue<Vector2>())); BindPerformed(_ui, "Navigate", ctx => NavigateEvent?.Invoke(ctx.ReadValue<Vector2>()));
BindCanceled(_ui, "Navigate", _ => NavigateEvent?.Invoke(Vector2.zero)); BindCanceled(_ui, "Navigate", _ => NavigateEvent?.Invoke(Vector2.zero));
BindStarted(_ui, "Submit", () => SubmitEvent?.Invoke()); BindStarted(_ui, "Submit", () => SubmitEvent?.Invoke());
// UI 模式下 ESC 与手柄 Start 均归为 Cancel返回/关闭栈顶UI map 不再有 Pause action。
BindStarted(_ui, "Cancel", () => { CancelEvent?.Invoke(); _onUICancelPressed?.Raise(); }); BindStarted(_ui, "Cancel", () => { CancelEvent?.Invoke(); _onUICancelPressed?.Raise(); });
BindStarted(_ui, "Pause", HandlePause);
BindPerformed(_ui, "Point", ctx => PointEvent?.Invoke(ctx.ReadValue<Vector2>())); BindPerformed(_ui, "Point", ctx => PointEvent?.Invoke(ctx.ReadValue<Vector2>()));
// R13-N3 小地图缩放档位切换Action 名称需在 InputActionAsset UI Map 中添加(可选) // R13-N3 小地图缩放档位切换Action 名称需在 InputActionAsset UI Map 中添加(可选)
BindStarted(_ui, "CycleMinimapZoom", () => CycleMinimapZoomEvent?.Invoke()); BindStarted(_ui, "CycleMinimapZoom", () => CycleMinimapZoomEvent?.Invoke());

View File

@@ -31,7 +31,6 @@ namespace BaseGames.Challenge
SceneName = _challengeSceneName, SceneName = _challengeSceneName,
EntryTransitionId = string.Empty, EntryTransitionId = string.Empty,
TransitionType = TransitionType.Scene, TransitionType = TransitionType.Scene,
ShowLoadingScreen = false,
IsRespawn = false, IsRespawn = false,
}); });
} }

View File

@@ -114,7 +114,6 @@ namespace BaseGames.Support.AntiSoftlock
SceneName = scene, SceneName = scene,
EntryId = spawn, EntryId = spawn,
TransitionType = TransitionType.Scene, TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
IsRespawn = true, IsRespawn = true,
}); });
} }

View File

@@ -36,6 +36,23 @@ namespace BaseGames.UI
[Tooltip("打开时是否自动把焦点设到首项。")] [Tooltip("打开时是否自动把焦点设到首项。")]
[SerializeField] protected bool _selectFirstOnEnable = true; [SerializeField] protected bool _selectFirstOnEnable = true;
[Header("导航栈")]
[Tooltip("被压栈时正下方面板的处理方式Replace 停用下层整屏切换Modal 保留下层可见但屏蔽其交互(对话框)。")]
[SerializeField] protected PushMode _defaultMode = PushMode.Replace;
[Tooltip("是否允许 ESC / 手柄 B 取消本面板(栈顶时)。确认破坏性操作的根面板可关闭。")]
[SerializeField] protected bool _canCancel = true;
/// <summary>被压栈方式(<see cref="IUINavigator.Push"/> 未显式指定 mode 时采用)。</summary>
public PushMode DefaultMode => _defaultMode;
/// <summary>栈顶时 ESC / 手柄 B 是否可取消本面板。</summary>
public bool CanCancel => _canCancel;
/// <summary>键盘 / 手柄默认焦点项(<see cref="_firstSelected"/> 优先,否则 <see cref="ResolveFirstSelected"/>)。供导航器出/入栈聚焦。</summary>
public GameObject FirstSelectableGO
=> _firstSelected != null ? _firstSelected.gameObject : ResolveFirstSelected();
/// <summary>事件订阅容器OnDisable 自动清理。子类订阅用 <c>channel.Subscribe(..).AddTo(_subs)</c>。</summary> /// <summary>事件订阅容器OnDisable 自动清理。子类订阅用 <c>channel.Subscribe(..).AddTo(_subs)</c>。</summary>
protected readonly CompositeDisposable _subs = new(); protected readonly CompositeDisposable _subs = new();
@@ -44,6 +61,7 @@ namespace BaseGames.UI
// ── 生命周期 ────────────────────────────────────────────────────────── // ── 生命周期 ──────────────────────────────────────────────────────────
protected virtual void OnEnable() protected virtual void OnEnable()
{ {
if (_canvasGroup == null) _canvasGroup = GetComponent<CanvasGroup>(); // 惰性解析(兼容运行时适配/未连线)
OnPanelOpen(); OnPanelOpen();
if (_canvasGroup != null && _fadeInDuration > 0f) PlayFadeIn(); if (_canvasGroup != null && _fadeInDuration > 0f) PlayFadeIn();
if (_selectFirstOnEnable) FocusFirst(); if (_selectFirstOnEnable) FocusFirst();
@@ -62,9 +80,29 @@ namespace BaseGames.UI
/// <summary>面板关闭时调用OnDisable_subs 已清理之后)。子类在此释放非 _subs 资源。</summary> /// <summary>面板关闭时调用OnDisable_subs 已清理之后)。子类在此释放非 _subs 资源。</summary>
protected virtual void OnPanelClose() { } protected virtual void OnPanelClose() { }
// ── 导航栈交互屏蔽 ────────────────────────────────────────────────────
/// <summary>
/// 切换本面板"是否参与交互/导航"。导航器把模态对话框压在本面板之上时,
/// 以此屏蔽本面板(<see cref="CanvasGroup"/> interactable+blocksRaycasts=false——
/// 子控件 <c>Selectable.IsInteractable()</c> 随之为 false自动退出 Unity 导航图,
/// 杜绝方向键穿透到下层(无 CanvasGroup 时为空操作)。
/// </summary>
public void SetInteractableLayer(bool on)
{
if (_canvasGroup == null) return;
_canvasGroup.interactable = on;
_canvasGroup.blocksRaycasts = on;
}
// ── 焦点 ────────────────────────────────────────────────────────────── // ── 焦点 ──────────────────────────────────────────────────────────────
/// <summary>关闭子面板、本面板恢复为栈顶时调用(<see cref="IUIManager.CloseTopPanel"/> 触发)。</summary> /// <summary>本面板重新成为栈顶(上层出栈)时调用:默认聚焦首项。</summary>
public virtual void OnFocusRestored() => FocusFirst(); public virtual void OnFocusGained() => FocusFirst();
/// <summary>本面板被上层压栈覆盖时调用(可选钩子,默认无操作)。</summary>
public virtual void OnFocusLost() { }
/// <summary>兼容旧 <see cref="IFocusable"/> 路径:等价于 <see cref="OnFocusGained"/>。</summary>
public virtual void OnFocusRestored() => OnFocusGained();
/// <summary>将 EventSystem 焦点设到首项(优先 <see cref="_firstSelected"/>,否则 <see cref="ResolveFirstSelected"/>)。</summary> /// <summary>将 EventSystem 焦点设到首项(优先 <see cref="_firstSelected"/>,否则 <see cref="ResolveFirstSelected"/>)。</summary>
protected void FocusFirst() protected void FocusFirst()

View File

@@ -8,7 +8,7 @@ namespace BaseGames.UI
{ {
/// <summary> /// <summary>
/// 滑条控件封装Slider + 实时数值标签 + 一行式 <see cref="Bind"/> /// 滑条控件封装Slider + 实时数值标签 + 一行式 <see cref="Bind"/>
/// 消除 <see cref="SettingsPanelController"/> 里"移除监听→设值→加监听→更新标签"的重复样板。 /// 消除设置面板里"移除监听→设值→加监听→更新标签"的重复样板。
/// </summary> /// </summary>
[DisallowMultipleComponent] [DisallowMultipleComponent]
public class UISlider : MonoBehaviour public class UISlider : MonoBehaviour

View File

@@ -31,99 +31,5 @@ namespace BaseGames.UI
// 此处保留供将来添加设备切换时的其他 UI 响应(提示动画、音效反馈等)。 // 此处保留供将来添加设备切换时的其他 UI 响应(提示动画、音效反馈等)。
} }
} }
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// 单个按键图标 Image 组件。
///
/// 支持两种查询模式:
/// • ByActionName推荐填写 ActionName如 "Interact"
/// 由 IInputIconService 自动解析当前设备 + 改键后的实际绑定路径 → 图标。
/// • ByBindingPath兼容/装饰用):直接填写固定路径(如 "&lt;Keyboard&gt;/space"
/// 适合教程截图等不跟随改键变化的场景。
/// </summary>
[RequireComponent(typeof(Image))]
public class InputIconImage : MonoBehaviour
{
public enum LookupMode { ByActionName, ByBindingPath }
[SerializeField] private LookupMode _mode = LookupMode.ByActionName;
[Tooltip("Action 名称,如 Interact / Jump / Attack仅 ByActionName 模式使用)")]
[SerializeField] private string _actionName;
[Tooltip("固定绑定路径,如 <Keyboard>/space仅 ByBindingPath 模式使用)")]
[SerializeField] private string _bindingPath;
private Image _image;
private IInputIconService _iconService;
// ── 静态注册表:替换 FindObjectsByTypeO(1) 注册/注销O(n) 广播 ────────
private static readonly List<InputIconImage> _registry = new();
/// <summary>通知注册表内所有已启用实例刷新图标(设备切换时调用)。</summary>
internal static void RefreshAll()
{
for (int i = _registry.Count - 1; i >= 0; i--)
{
if (_registry[i] != null) _registry[i].Refresh();
else _registry.RemoveAt(i); // 清理已销毁的残留引用
}
}
private void Awake() => _image = GetComponent<Image>();
private void OnEnable()
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += Refresh;
_registry.Add(this);
Refresh();
}
private void OnDisable()
{
if (_iconService != null)
_iconService.OnIconSetChanged -= Refresh;
_registry.Remove(this);
}
/// <summary>刷新图标显示。设备切换或改键后由 InputDeviceIconSwitcher / InputIconService 调用。</summary>
public void Refresh()
{
if (_image == null) return;
// 若组件在 IInputIconService 注册前 Enable此处补重试并补订阅
if (_iconService == null)
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += Refresh;
}
Sprite sprite = null;
if (_mode == LookupMode.ByActionName && !string.IsNullOrEmpty(_actionName))
{
sprite = _iconService?.GetActionIcon(_actionName);
}
else if (_mode == LookupMode.ByBindingPath && !string.IsNullOrEmpty(_bindingPath))
{
// 使用固定路径在当前图标集查找(不随改键变化),适合装饰性按键说明
sprite = _iconService?.GetPathIcon(_bindingPath);
}
if (sprite != null)
{
_image.sprite = sprite;
_image.enabled = true;
}
else
{
_image.enabled = false;
}
}
}
} }

View File

@@ -0,0 +1,104 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
namespace BaseGames.UI
{
/// <summary>
/// 单个按键图标 Image 组件。
///
/// 支持两种查询模式:
/// • ByActionName推荐填写 ActionName如 "Interact"
/// 由 IInputIconService 自动解析当前设备 + 改键后的实际绑定路径 → 图标。
/// • ByBindingPath兼容/装饰用):直接填写固定路径(如 "&lt;Keyboard&gt;/space"
/// 适合教程截图等不跟随改键变化的场景。
///
/// ⚠️ 必须独立成文件(类名 = 文件名):作为可序列化的 MonoBehaviour
/// 若与其他类同处一个 .cs 文件二级类Unity 无法为其写出有效的 m_Script 引用,
/// 挂载后存盘 / 重载会变成"缺失脚本"。
/// </summary>
[RequireComponent(typeof(Image))]
public class InputIconImage : MonoBehaviour
{
public enum LookupMode { ByActionName, ByBindingPath }
[SerializeField] private LookupMode _mode = LookupMode.ByActionName;
[Tooltip("Action 名称,如 Interact / Jump / Attack仅 ByActionName 模式使用)")]
[SerializeField] private string _actionName;
[Tooltip("固定绑定路径,如 <Keyboard>/space仅 ByBindingPath 模式使用)")]
[SerializeField] private string _bindingPath;
private Image _image;
private IInputIconService _iconService;
// ── 静态注册表:替换 FindObjectsByTypeO(1) 注册/注销O(n) 广播 ────────
private static readonly List<InputIconImage> _registry = new();
/// <summary>通知注册表内所有已启用实例刷新图标(设备切换时调用)。</summary>
internal static void RefreshAll()
{
for (int i = _registry.Count - 1; i >= 0; i--)
{
if (_registry[i] != null) _registry[i].Refresh();
else _registry.RemoveAt(i); // 清理已销毁的残留引用
}
}
private void Awake() => _image = GetComponent<Image>();
private void OnEnable()
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += Refresh;
_registry.Add(this);
Refresh();
}
private void OnDisable()
{
if (_iconService != null)
_iconService.OnIconSetChanged -= Refresh;
_registry.Remove(this);
}
/// <summary>刷新图标显示。设备切换或改键后由 InputDeviceIconSwitcher / InputIconService 调用。</summary>
public void Refresh()
{
if (_image == null) return;
// 若组件在 IInputIconService 注册前 Enable此处补重试并补订阅
if (_iconService == null)
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += Refresh;
}
Sprite sprite = null;
if (_mode == LookupMode.ByActionName && !string.IsNullOrEmpty(_actionName))
{
sprite = _iconService?.GetActionIcon(_actionName);
}
else if (_mode == LookupMode.ByBindingPath && !string.IsNullOrEmpty(_bindingPath))
{
// 使用固定路径在当前图标集查找(不随改键变化),适合装饰性按键说明
sprite = _iconService?.GetPathIcon(_bindingPath);
}
if (sprite != null)
{
_image.sprite = sprite;
_image.enabled = true;
}
else
{
_image.enabled = false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b8d9e520f0d671e458003b2974ccb45e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
using UnityEngine;
namespace BaseGames.UI
{
/// <summary>
/// 加载界面数据配置(策划编辑)。承载随机提示文案、标题与时长/手感参数。
/// <para>
/// 职责边界:本表只放<b>数据</b>。视觉样式(背景图、进度条 sprite、配色、布局、装饰全部在
/// <c>UI_LoadingScreen</c> 预制件里由美术在 Prefab Mode 编辑,配色随 <c>UI_Theme_Default</c> 主题;
/// 本表<b>不</b>放图片/颜色字段。
/// </para>
/// <see cref="LoadingScreenManager"/> 运行时优先读取本表;未指定时回退到组件上的序列化默认值。
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/UI/Loading Config", fileName = "UI_LoadingConfig")]
public class LoadingScreenConfigSO : ScriptableObject
{
[Header("内容(本地化 KeyUI 表)")]
[Tooltip("随机加载提示的本地化 Key 列表(如 LOADING_TIP_EXPLORE。每次加载随机取一条留空则不显示提示。")]
[SerializeField] private string[] _tipKeys;
[Tooltip("加载界面标题的本地化 Key如 LOADING_TITLE。留空则不显示标题。")]
[SerializeField] private string _titleKey;
[Header("时长 / 手感")]
[Tooltip("加载画面最短显示时长(秒),避免一闪而过。")]
[SerializeField] private float _minDisplayTime = 0.5f;
[Tooltip("预计加载时长(秒)。进度条按此时间平滑爬升至约 90%,真正加载完成时再冲满。\n" +
"建议设为该游戏典型场景的加载耗时:偏长则爬得慢、偏短则更快到 90%。")]
[SerializeField] private float _expectedLoadTime = 2.5f;
[Tooltip("进度条填充缓动速度(每秒可填充比例),用于平滑收尾。")]
[SerializeField] private float _fillLerpSpeed = 1.6f;
public string[] TipKeys => _tipKeys;
public string TitleKey => _titleKey;
public float MinDisplayTime => _minDisplayTime;
public float ExpectedLoadTime => _expectedLoadTime;
public float FillLerpSpeed => _fillLerpSpeed;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 814076691b7b57e4eaf67f32530baf5f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -12,26 +12,64 @@ namespace BaseGames.UI
/// </summary> /// </summary>
public class LoadingScreenManager : MonoBehaviour public class LoadingScreenManager : MonoBehaviour
{ {
[Tooltip("数据配置(策划编辑):随机提示、标题、时长/手感。\n" +
"非空时优先于下方序列化默认值;为空则回退默认值(向后兼容)。\n" +
"视觉样式(图片/配色/布局)在 UI_LoadingScreen 预制件里由美术编辑,不在此表。")]
[SerializeField] private LoadingScreenConfigSO _config;
[Header("视图引用(由 UI_LoadingScreen 预制件绑定)")]
[SerializeField] private GameObject _loadingRoot; [SerializeField] private GameObject _loadingRoot;
[SerializeField] private Image _progressFill; [SerializeField] private Image _progressFill;
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _tipText; [SerializeField] private TMP_Text _tipText;
[SerializeField] private Image[] _backgroundArts; [SerializeField] private Image[] _backgroundArts;
[Header("默认值(无 _config 时回退使用)")]
[SerializeField] private string[] _tipMessages; // 本地化 key对应 "UI" 表中的条目,如 "tip_explore" [SerializeField] private string[] _tipMessages; // 本地化 key对应 "UI" 表中的条目,如 "tip_explore"
[SerializeField] private float _minDisplayTime = 0.5f; [SerializeField] private float _minDisplayTime = 0.5f;
[Tooltip("进度条填充缓动速度(每秒可填充的比例),用于把显示值平滑推向目标、避免瞬跳;越大越快、越小越平滑。")]
[SerializeField] private float _fillLerpSpeed = 1.6f;
[Tooltip("预计加载时长(秒),用于‘感知进度’:进度条按时间平滑爬升至约 90%,真正加载完成时再冲满。\n" +
"原因Addressables 对单个场景的真实进度高度前置(毫秒内跳到 ~90% 再长时间停滞),\n" +
"直接驱动会让进度条‘瞬间近满→卡死’;按时间爬升能保证进度条始终可见地推进。\n" +
"建议设为该游戏场景的典型加载耗时;偏长则爬得慢、偏短则更快到 90%。")]
[SerializeField] private float _expectedLoadTime = 2.5f;
[Header("Event Channels")] [Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onLoadingStarted; [SerializeField] private VoidEventChannelSO _onLoadingStarted;
[SerializeField] private VoidEventChannelSO _onLoadingComplete; [SerializeField] private VoidEventChannelSO _onLoadingComplete;
[SerializeField] private FloatEventChannelSO _onLoadingProgressUpdated; [SerializeField] private FloatEventChannelSO _onLoadingProgressUpdated;
// 未完成时进度条按时间爬升的上限(约 90%),余下 10% 留给"加载完成"那一冲。
private const float kCreepCap = 0.9f;
private float _shownAt; private float _shownAt;
private float _targetProgress; // 真实进度 [0,1],仅用于判定"是否已完成"(达到 1 冲满)
private float _displayedProgress; // 进度条当前显示值(感知进度,缓动推进)
private bool _isShowing;
private readonly CompositeDisposable _subs = new(); private readonly CompositeDisposable _subs = new();
// 配置取值_config 优先,否则用组件上的序列化默认值(向后兼容)。
private float MinDisplay => _config != null ? _config.MinDisplayTime : _minDisplayTime;
private float ExpectedLoad => _config != null ? _config.ExpectedLoadTime : _expectedLoadTime;
private float FillSpeed => _config != null ? _config.FillLerpSpeed : _fillLerpSpeed;
private string[] TipKeys => (_config != null && _config.TipKeys != null && _config.TipKeys.Length > 0)
? _config.TipKeys : _tipMessages;
private string TitleKey => _config != null ? _config.TitleKey : null;
private void OnEnable() private void OnEnable()
{ {
_onLoadingStarted?.Subscribe(Show).AddTo(_subs); _onLoadingStarted?.Subscribe(Show).AddTo(_subs);
_onLoadingComplete?.Subscribe(Hide).AddTo(_subs); _onLoadingComplete?.Subscribe(Hide).AddTo(_subs);
_onLoadingProgressUpdated?.Subscribe(SetProgress).AddTo(_subs); _onLoadingProgressUpdated?.Subscribe(SetProgress).AddTo(_subs);
// 关键:本组件的宿主 Canvas 必须保持 activeOnEnable 才会执行并订阅上面的事件
// (管理器不能挂在被自己关闭的 GameObject 上,否则永不订阅、加载画面永不显示)。
// 因此这里只把内层遮罩根节点初始隐藏,显隐由 Show()/Hide() 切换 _loadingRoot。
_isShowing = false;
if (_loadingRoot != null) _loadingRoot.SetActive(false);
} }
private void OnDisable() private void OnDisable()
{ {
@@ -43,6 +81,9 @@ namespace BaseGames.UI
public void Show() public void Show()
{ {
_shownAt = Time.unscaledTime; _shownAt = Time.unscaledTime;
_targetProgress = 0f;
_displayedProgress = 0f;
_isShowing = true;
if (_loadingRoot != null) _loadingRoot.SetActive(true); if (_loadingRoot != null) _loadingRoot.SetActive(true);
if (_progressFill != null) _progressFill.fillAmount = 0f; if (_progressFill != null) _progressFill.fillAmount = 0f;
@@ -53,41 +94,76 @@ namespace BaseGames.UI
_backgroundArts[Random.Range(0, _backgroundArts.Length)].enabled = true; _backgroundArts[Random.Range(0, _backgroundArts.Length)].enabled = true;
} }
// 标题config 提供 key留空则隐藏 Title 节点)
if (_titleText != null)
{
string titleKey = TitleKey;
bool hasTitle = !string.IsNullOrEmpty(titleKey);
_titleText.gameObject.SetActive(hasTitle);
if (hasTitle)
_titleText.text = LocalizationManager.Get(titleKey, LocalizationTable.UI);
}
// 随机提示(通过 LocalizationManager 解析 key // 随机提示(通过 LocalizationManager 解析 key
if (_tipText != null && _tipMessages != null && _tipMessages.Length > 0) var tipKeys = TipKeys;
_tipText.text = LocalizationManager.Get(_tipMessages[Random.Range(0, _tipMessages.Length)], LocalizationTable.UI); if (_tipText != null && tipKeys != null && tipKeys.Length > 0)
_tipText.text = LocalizationManager.Get(tipKeys[Random.Range(0, tipKeys.Length)], LocalizationTable.UI);
} }
public void Hide() => StartCoroutine(HideAfterMinTime()); public void Hide() => StartCoroutine(HideAfterMinTime());
public void SetProgress(float value) /// <summary>接收真实加载进度。注意:进度条显示采用基于时间的"感知进度"(见 <see cref="Update"/>
{ /// 这里的真实值仅用于判定"是否已完成"(达到 1 时冲满)——因为 Addressables 单场景的真实进度
if (_progressFill != null) /// 高度前置、不适合直接驱动进度条。</summary>
_progressFill.fillAmount = Mathf.Clamp01(value); public void SetProgress(float value) => _targetProgress = Mathf.Clamp01(value);
}
// ── 内部 ───────────────────────────────────────────────────────────── // ── 内部 ─────────────────────────────────────────────────────────────
// 缓存等待对象以避免典型路径(剩余时间 == _minDisplayTime下的重复分配。 // 进度条采用"感知进度"未完成时按时间预算_expectedLoadTime平滑爬升至 kCreepCap(~90%)
private WaitForSecondsRealtime _cachedFullWait; // 加载完成_targetProgress 达到 1由 SceneLoader 末帧或 Hide() 置位)时冲满到 1。
// 不直接跟随 Addressables 的真实 PercentComplete —— 它对单场景高度前置(毫秒内近满后长时间停滞),
// 直接映射会让进度条"瞬间近满→卡死"。使用 unscaledDeltaTime不受 timeScale 影响。
private void Update()
{
if (!_isShowing || _progressFill == null) return;
float effectiveTarget;
if (_targetProgress >= 1f)
{
effectiveTarget = 1f; // 加载完成 → 冲满
}
else
{
float elapsed = Time.unscaledTime - _shownAt;
float expected = ExpectedLoad;
effectiveTarget = expected > 0f
? Mathf.Clamp01(elapsed / expected) * kCreepCap // 按时间爬升至 ~90%
: kCreepCap;
}
_displayedProgress = Mathf.MoveTowards(
_displayedProgress, effectiveTarget, FillSpeed * Time.unscaledDeltaTime);
_progressFill.fillAmount = _displayedProgress;
}
private IEnumerator HideAfterMinTime() private IEnumerator HideAfterMinTime()
{ {
float elapsed = Time.unscaledTime - _shownAt; // 收尾:把目标推到满,让缓动平滑填满进度条后再隐藏。
float remaining = _minDisplayTime - elapsed; _targetProgress = 1f;
if (remaining > 0f)
// 同时满足两个条件才隐藏:① 至少显示 MinDisplay ② 进度条视觉已填满。
// 安全上限避免极端卡顿(缓动追不上 / 进度条缺失)时永不隐藏。
float minDisplay = MinDisplay;
float safetyDeadline = Time.unscaledTime + minDisplay + 1.5f;
while (Time.unscaledTime < safetyDeadline)
{ {
// 完整剩余 ≈ 显示时间时复用缓存对象;否则按需新建(罕见路径)。 bool minTimeElapsed = Time.unscaledTime - _shownAt >= minDisplay;
if (Mathf.Approximately(remaining, _minDisplayTime)) bool barFilled = _progressFill == null || _displayedProgress >= 0.999f;
{ if (minTimeElapsed && barFilled) break;
_cachedFullWait ??= new WaitForSecondsRealtime(_minDisplayTime); yield return null;
yield return _cachedFullWait;
}
else
{
yield return new WaitForSecondsRealtime(remaining);
}
} }
_isShowing = false;
if (_loadingRoot != null) _loadingRoot.SetActive(false); if (_loadingRoot != null) _loadingRoot.SetActive(false);
} }
} }

View File

@@ -1,20 +1,22 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
using BaseGames.Core; using BaseGames.Core;
using BaseGames.Core.Assets; using BaseGames.Core.Assets;
using BaseGames.Core.Events; using BaseGames.Core.Events;
using BaseGames.UI;
using BaseGames.UI.Menus;
namespace BaseGames.UI.MainMenu namespace BaseGames.UI.MainMenu
{ {
/// <summary> /// <summary>
/// 数据驱动主菜单控制器(<see cref="MainMenuController"/> 的表驱动版,非破坏性并存)。 /// 数据驱动主菜单控制器(表驱动按钮列表 + 子面板编排)。
/// ///
/// 按钮列表据 <see cref="MainMenuConfigSO"/> 生成(标签/图标/顺序/动作纯数据,策划改表零代码) /// 按钮列表据 <see cref="MainMenuConfigSO"/> 生成(标签/图标/顺序/动作纯数据,策划改表零代码)
/// 子面板开关、存档槽流程、入场动画、状态锁定等编排仍在本控制器(场景耦合,不下放配置表)。 /// 子面板(存档槽 / 设置 / 制作人员)经统一 <see cref="IUINavigator"/> 压栈栈式回退、ESC 逐层关闭、
/// 动作派发:内置 NewGame/Continue/OpenSettings/OpenCredits/LoadScene/Quit + 事件频道 RaiseEvent。 /// 焦点恢复均由导航器负责,本控制器不再自管取消键与面板显隐。主按钮组作为"栈底上下文"
/// (不入栈),据 <see cref="IUINavigator.Depth"/> 在有子面板打开时屏蔽自身交互。
/// </summary> /// </summary>
public class DataDrivenMainMenuController : MonoBehaviour public class DataDrivenMainMenuController : MonoBehaviour
{ {
@@ -24,20 +26,21 @@ namespace BaseGames.UI.MainMenu
[SerializeField] private Transform _container; [SerializeField] private Transform _container;
[SerializeField] private MainMenuButtonView _buttonPrefab; [SerializeField] private MainMenuButtonView _buttonPrefab;
[Header("主按钮组(入场动画)")] [Header("主按钮组(入场动画 / 栈底屏蔽")]
[SerializeField] private CanvasGroup _mainButtonsGroup; [SerializeField] private CanvasGroup _mainButtonsGroup;
[SerializeField] private RectTransform _mainButtonsRect; [SerializeField] private RectTransform _mainButtonsRect;
[Header("子面板")] [Header("子面板(导航器压栈对象,整面板根须挂 UIPanelBase")]
[SerializeField] private GameObject _saveSlotPanel; [SerializeField] private SaveSlotController _saveSlotPanel;
[SerializeField] private BaseGames.UI.Menus.SaveSlotController _saveSlotController; [Tooltip("设置面板根UISimplePanel内含数据驱动设置实例。")]
[SerializeField] private GameObject _settingsPanel; [SerializeField] private UIPanelBase _settingsPanel;
[SerializeField] private GameObject _creditsPanel; [Tooltip("制作人员面板根UISimplePanel。")]
[SerializeField] private UIPanelBase _creditsPanel;
[Header("子面板关闭按钮(可选)")] [Header("子面板关闭按钮(可选,等价于 ESC出栈一层")]
[SerializeField] private Button _btnCloseSaveSlot; [SerializeField] private UnityEngine.UI.Button _btnCloseSaveSlot;
[SerializeField] private Button _btnCloseSettings; [SerializeField] private UnityEngine.UI.Button _btnCloseSettings;
[SerializeField] private Button _btnCloseCredits; [SerializeField] private UnityEngine.UI.Button _btnCloseCredits;
[Header("入场动画")] [Header("入场动画")]
[SerializeField] private float _entrySlideOffset = 80f; [SerializeField] private float _entrySlideOffset = 80f;
@@ -57,24 +60,19 @@ namespace BaseGames.UI.MainMenu
private readonly List<(MainMenuConfigSO.Item item, MainMenuButtonView view)> _buttons = new(); private readonly List<(MainMenuConfigSO.Item item, MainMenuButtonView view)> _buttons = new();
private Vector2 _buttonsPanelOriginalPos; private Vector2 _buttonsPanelOriginalPos;
private MainMenuButtonView _firstButton; private MainMenuButtonView _firstButton;
private IUINavigator _nav;
// ── 生命周期 ────────────────────────────────────────────────────────── // ── 生命周期 ──────────────────────────────────────────────────────────
private void Awake() private void Awake()
{ {
if (_buttonPrefab != null) _buttonPrefab.gameObject.SetActive(false);
_btnCloseSaveSlot?.onClick.AddListener(() => CloseSubPanel(_saveSlotPanel));
_btnCloseSettings?.onClick.AddListener(() => CloseSubPanel(_settingsPanel));
_btnCloseCredits? .onClick.AddListener(() => CloseSubPanel(_creditsPanel));
if (_mainButtonsRect != null) if (_mainButtonsRect != null)
_buttonsPanelOriginalPos = _mainButtonsRect.anchoredPosition; _buttonsPanelOriginalPos = _mainButtonsRect.anchoredPosition;
SetPanel(_saveSlotPanel, false); _btnCloseSaveSlot?.onClick.AddListener(() => Nav?.Pop());
SetPanel(_settingsPanel, false); _btnCloseSettings?.onClick.AddListener(() => Nav?.Pop());
SetPanel(_creditsPanel, false); _btnCloseCredits? .onClick.AddListener(() => Nav?.Pop());
SetButtonsGroupVisible(false);
SetButtonsGroupVisible(false);
BuildMenu(); BuildMenu();
} }
@@ -82,9 +80,16 @@ namespace BaseGames.UI.MainMenu
{ {
_onGameStateChanged?.Subscribe(HandleGameStateChanged).AddTo(_subs); _onGameStateChanged?.Subscribe(HandleGameStateChanged).AddTo(_subs);
_onSlotConfirmed? .Subscribe(HandleSlotConfirmed).AddTo(_subs); _onSlotConfirmed? .Subscribe(HandleSlotConfirmed).AddTo(_subs);
_nav = ServiceLocator.GetOrDefault<IUINavigator>();
if (_nav != null) _nav.StackChanged += HandleStackChanged;
} }
private void OnDisable() => _subs.Clear(); private void OnDisable()
{
_subs.Clear();
if (_nav != null) _nav.StackChanged -= HandleStackChanged;
}
private void Start() => StartCoroutine(PlayEntryAnimation()); private void Start() => StartCoroutine(PlayEntryAnimation());
@@ -92,10 +97,7 @@ namespace BaseGames.UI.MainMenu
/// <summary>据配置重建主菜单按钮列表public 以便编辑器/测试验证)。</summary> /// <summary>据配置重建主菜单按钮列表public 以便编辑器/测试验证)。</summary>
public void BuildMenu() public void BuildMenu()
{ {
foreach (var (_, view) in _buttons) if (view != null) Destroy(view.gameObject); ClearMenu();
_buttons.Clear();
_firstButton = null;
if (_config == null || _container == null || _buttonPrefab == null) return; if (_config == null || _container == null || _buttonPrefab == null) return;
foreach (var item in _config.Items) foreach (var item in _config.Items)
@@ -111,6 +113,20 @@ namespace BaseGames.UI.MainMenu
RefreshConditional(); RefreshConditional();
} }
/// <summary>清空菜单按钮(容器全部子节点)。编辑器预览与运行时重建共用。</summary>
public void ClearMenu()
{
_buttons.Clear();
_firstButton = null;
if (_container == null) return;
for (int i = _container.childCount - 1; i >= 0; i--)
{
var child = _container.GetChild(i).gameObject;
if (Application.isPlaying) Destroy(child);
else DestroyImmediate(child);
}
}
/// <summary>根据存档存在性刷新 requiresSave 按钮的可用性(如"继续")。</summary> /// <summary>根据存档存在性刷新 requiresSave 按钮的可用性(如"继续")。</summary>
public void RefreshConditional() public void RefreshConditional()
{ {
@@ -126,27 +142,22 @@ namespace BaseGames.UI.MainMenu
switch (item.action) switch (item.action)
{ {
case MainMenuAction.NewGame: case MainMenuAction.NewGame:
_saveSlotController?.SetMode(BaseGames.UI.Menus.SaveSlotPanelMode.NewGame); if (_saveSlotPanel != null) { _saveSlotPanel.SetMode(SaveSlotPanelMode.NewGame); Nav?.Push(_saveSlotPanel); }
OpenSubPanel(_saveSlotPanel);
break; break;
case MainMenuAction.Continue: case MainMenuAction.Continue:
_saveSlotController?.SetMode(BaseGames.UI.Menus.SaveSlotPanelMode.Continue); if (_saveSlotPanel != null) { _saveSlotPanel.SetMode(SaveSlotPanelMode.Continue); Nav?.Push(_saveSlotPanel); }
OpenSubPanel(_saveSlotPanel);
break; break;
case MainMenuAction.OpenSettings: case MainMenuAction.OpenSettings:
OpenSubPanel(_settingsPanel); if (_settingsPanel != null) Nav?.Push(_settingsPanel);
break; break;
case MainMenuAction.OpenCredits: case MainMenuAction.OpenCredits:
OpenSubPanel(_creditsPanel); if (_creditsPanel != null) Nav?.Push(_creditsPanel);
if (_btnCloseCredits != null)
EventSystem.current?.SetSelectedGameObject(_btnCloseCredits.gameObject);
break; break;
case MainMenuAction.LoadScene: case MainMenuAction.LoadScene:
_onSceneLoadRequest?.Raise(new SceneLoadRequest _onSceneLoadRequest?.Raise(new SceneLoadRequest
{ {
SceneName = string.IsNullOrEmpty(item.sceneKey) ? _firstGameSceneKey : item.sceneKey, SceneName = string.IsNullOrEmpty(item.sceneKey) ? _firstGameSceneKey : item.sceneKey,
TransitionType = TransitionType.Scene, TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
}); });
break; break;
case MainMenuAction.Quit: case MainMenuAction.Quit:
@@ -158,32 +169,25 @@ namespace BaseGames.UI.MainMenu
} }
} }
// ── 子面板编排 ──────────────────────────────────────────────────────── // ── 栈底按钮组屏蔽(有子面板打开时禁用主按钮,避免方向键穿透)──────────
private void OpenSubPanel(GameObject panel) private IUINavigator Nav => _nav ??= ServiceLocator.GetOrDefault<IUINavigator>();
{
SetMainButtonsInteractable(false);
SetPanel(panel, true);
}
private void CloseSubPanel(GameObject panel) private void HandleStackChanged()
{ {
SetPanel(panel, false); bool anyOpen = Nav != null && Nav.Depth > 0;
SetMainButtonsInteractable(true); if (_mainButtonsGroup != null)
if (_firstButton != null) {
_mainButtonsGroup.interactable = !anyOpen;
_mainButtonsGroup.blocksRaycasts = !anyOpen;
}
if (!anyOpen && _firstButton != null)
EventSystem.current?.SetSelectedGameObject(_firstButton.Button.gameObject); EventSystem.current?.SetSelectedGameObject(_firstButton.Button.gameObject);
} }
private void SetMainButtonsInteractable(bool on) // ── 存档槽确认 ────────────────────────────────────────────────────────
{
if (_mainButtonsGroup == null) return;
_mainButtonsGroup.interactable = on;
_mainButtonsGroup.blocksRaycasts = on;
}
// ── 存档槽确认(与 MainMenuController 一致)────────────────────────────
private void HandleSlotConfirmed(int _) private void HandleSlotConfirmed(int _)
{ {
SetPanel(_saveSlotPanel, false); Nav?.PopToRoot(); // 关闭存档槽(及其上任何子对话框),准备进场景
var svc = ServiceLocator.GetOrDefault<ISaveService>(); var svc = ServiceLocator.GetOrDefault<ISaveService>();
string checkpointScene = svc?.LastCheckpointScene; string checkpointScene = svc?.LastCheckpointScene;
@@ -194,14 +198,13 @@ namespace BaseGames.UI.MainMenu
SceneName = hasCheckpoint ? checkpointScene : _firstGameSceneKey, SceneName = hasCheckpoint ? checkpointScene : _firstGameSceneKey,
EntryTransitionId = hasCheckpoint ? svc.LastCheckpointSpawnId : null, EntryTransitionId = hasCheckpoint ? svc.LastCheckpointSpawnId : null,
TransitionType = TransitionType.Scene, TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
}); });
} }
private void HandleGameStateChanged(GameStateId state) private void HandleGameStateChanged(GameStateId state)
{ {
bool isMainMenu = state == GameStates.MainMenu; bool isMainMenu = state == GameStates.MainMenu;
if (_mainButtonsGroup != null) if (_mainButtonsGroup != null && Nav != null && Nav.Depth == 0)
{ {
_mainButtonsGroup.interactable = isMainMenu; _mainButtonsGroup.interactable = isMainMenu;
_mainButtonsGroup.blocksRaycasts = isMainMenu; _mainButtonsGroup.blocksRaycasts = isMainMenu;
@@ -243,11 +246,6 @@ namespace BaseGames.UI.MainMenu
return s != null && (s.HasSave(0) || s.HasSave(1) || s.HasSave(2)); return s != null && (s.HasSave(0) || s.HasSave(1) || s.HasSave(2));
} }
private static void SetPanel(GameObject panel, bool active)
{
if (panel != null) panel.SetActive(active);
}
private void SetButtonsGroupVisible(bool visible) private void SetButtonsGroupVisible(bool visible)
{ {
if (_mainButtonsGroup == null) return; if (_mainButtonsGroup == null) return;

View File

@@ -1,263 +0,0 @@
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using BaseGames.Core;
using BaseGames.Core.Assets;
using BaseGames.Core.Events;
namespace BaseGames.UI.MainMenu
{
/// <summary>
/// 主菜单 UI 控制器(挂载在 Scene_MainMenu 的根 Canvas 上)。
///
/// 面板结构(按 Inspector 绑定):
/// ├── MainButtonsPanel — 主按钮组(新游戏 / 继续 / 设置 / 制作团队 / 退出)
/// ├── SaveSlotPanel — 存档槽选择(新游戏 & 继续共用)
/// ├── SettingsPanel — 设置面板
/// └── CreditsPanel — 制作团队面板
///
/// 入场动画:主按钮组从下方滑入(代码驱动,无需 Animator
///
/// 流程:
/// 玩家选择存档槽SaveSlotController 发布 _onSlotConfirmed
/// → 关闭存档槽面板 → 发布 SceneLoadRequest目标游戏场景
/// → GameManager 响应,进入 LoadingScene 状态,显示加载画面,最终切换到 Gameplay。
/// </summary>
public class MainMenuController : MonoBehaviour
{
// ── 面板引用 ──────────────────────────────────────────────────────────
[Header("面板")]
[SerializeField] private CanvasGroup _mainButtonsGroup;
[SerializeField] private RectTransform _mainButtonsRect; // 用于滑入动画
[SerializeField] private GameObject _saveSlotPanel;
[Tooltip("存档槽面板控制器。打开前调用 SetMode 区分新游戏 / 继续语境。")]
[SerializeField] private BaseGames.UI.Menus.SaveSlotController _saveSlotController;
[SerializeField] private GameObject _settingsPanel;
[SerializeField] private GameObject _creditsPanel;
// ── 按钮引用 ──────────────────────────────────────────────────────────
[Header("主菜单按钮")]
[SerializeField] private Button _btnNewGame;
[SerializeField] private Button _btnContinue;
[SerializeField] private Button _btnSettings;
[SerializeField] private Button _btnCredits;
[SerializeField] private Button _btnQuit;
// ── 按钮(子面板关闭)────────────────────────────────────────────────
[Header("子面板关闭按钮(可选)")]
[SerializeField] private Button _btnCloseSaveSlot;
[SerializeField] private Button _btnCloseSettings;
[SerializeField] private Button _btnCloseCredits;
// ── 入场动画参数 ──────────────────────────────────────────────────────
[Header("入场动画")]
[Tooltip("按钮组初始偏移(像素,向下)")]
[SerializeField] private float _entrySlideOffset = 80f;
[Tooltip("入场动画持续时间(秒)")]
[SerializeField] private float _entryDuration = 0.55f;
// ── 游戏场景 ──────────────────────────────────────────────────────────
[Header("场景")]
[Tooltip("新游戏 / 继续后进入的第一个游戏场景Addressable Key")]
[SerializeField] private string _firstGameSceneKey = AddressKeys.SceneGameChapter1;
// ── Event Channels ────────────────────────────────────────────────────
[Header("Event Channels - Listen")]
[SerializeField] private GameStateEventChannelSO _onGameStateChanged;
[Tooltip("SaveSlotController 完成选槽后发布(携带槽索引)")]
[SerializeField] private IntEventChannelSO _onSlotConfirmed;
[Header("Event Channels - Raise")]
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
// ── 内部状态 ──────────────────────────────────────────────────────────
private readonly CompositeDisposable _subs = new();
private Vector2 _buttonsPanelOriginalPos;
// ── 生命周期 ──────────────────────────────────────────────────────────
private void Awake()
{
// 按钮绑定
_btnNewGame? .onClick.AddListener(OnNewGameClicked);
_btnContinue?.onClick.AddListener(OnContinueClicked);
_btnSettings?.onClick.AddListener(OnSettingsClicked);
_btnCredits? .onClick.AddListener(OnCreditsClicked);
_btnQuit? .onClick.AddListener(Application.Quit);
_btnCloseSaveSlot?.onClick.AddListener(() => CloseSubPanel(_saveSlotPanel, _btnNewGame));
_btnCloseSettings?.onClick.AddListener(() => CloseSubPanel(_settingsPanel, _btnSettings));
_btnCloseCredits? .onClick.AddListener(() => CloseSubPanel(_creditsPanel, _btnCredits));
// 记录按钮组原始位置(供动画使用)
if (_mainButtonsRect != null)
_buttonsPanelOriginalPos = _mainButtonsRect.anchoredPosition;
// 初始状态:隐藏子面板,主按钮组不可见(等待入场动画)
SetPanel(_saveSlotPanel, false);
SetPanel(_settingsPanel, false);
SetPanel(_creditsPanel, false);
SetButtonsGroupVisible(false);
// 刷新"继续"按钮可用性(需要至少一个有效存档)
RefreshContinueButton();
}
private void OnEnable()
{
_onGameStateChanged?.Subscribe(HandleGameStateChanged).AddTo(_subs);
_onSlotConfirmed? .Subscribe(HandleSlotConfirmed).AddTo(_subs);
}
private void OnDisable() => _subs.Clear();
private void Start()
{
// 场景初始化完成后播放入场动画
StartCoroutine(PlayEntryAnimation());
}
// ── 入场动画 ─────────────────────────────────────────────────────────
private IEnumerator PlayEntryAnimation()
{
if (_mainButtonsGroup == null) yield break;
Vector2 startPos = _buttonsPanelOriginalPos - new Vector2(0f, _entrySlideOffset);
if (_mainButtonsRect != null)
_mainButtonsRect.anchoredPosition = startPos;
float elapsed = 0f;
while (elapsed < _entryDuration)
{
float t = Mathf.SmoothStep(0f, 1f, elapsed / _entryDuration);
_mainButtonsGroup.alpha = t;
if (_mainButtonsRect != null)
_mainButtonsRect.anchoredPosition = Vector2.Lerp(startPos, _buttonsPanelOriginalPos, t);
elapsed += Time.unscaledDeltaTime;
yield return null;
}
_mainButtonsGroup.alpha = 1f;
if (_mainButtonsRect != null)
_mainButtonsRect.anchoredPosition = _buttonsPanelOriginalPos;
_mainButtonsGroup.interactable = true;
_mainButtonsGroup.blocksRaycasts = true;
// 手柄导航:入场动画完成后将焦点置于第一个按钮
EventSystem.current?.SetSelectedGameObject(_btnNewGame?.gameObject);
}
// ── 按钮回调 ─────────────────────────────────────────────────────────
private void OnNewGameClicked()
{
_saveSlotController?.SetMode(BaseGames.UI.Menus.SaveSlotPanelMode.NewGame);
OpenSubPanel(_saveSlotPanel);
}
private void OnContinueClicked()
{
_saveSlotController?.SetMode(BaseGames.UI.Menus.SaveSlotPanelMode.Continue);
OpenSubPanel(_saveSlotPanel);
}
private void OnSettingsClicked() => OpenSubPanel(_settingsPanel); // SettingsPanelController 自行设焦点
private void OnCreditsClicked()
{
OpenSubPanel(_creditsPanel);
// Credits 面板无独立控制器,打开时把焦点交给返回按钮(键盘 / 手柄可直接退出)
if (_btnCloseCredits != null)
EventSystem.current?.SetSelectedGameObject(_btnCloseCredits.gameObject);
}
/// <summary>打开子面板:禁用主按钮组交互,避免键盘/手柄导航"穿透"到背后的主菜单按钮。</summary>
private void OpenSubPanel(GameObject panel)
{
SetMainButtonsInteractable(false);
SetPanel(panel, true);
}
/// <summary>关闭子面板:恢复主按钮组交互,并把焦点恢复到对应主菜单按钮(导航连续性)。</summary>
private void CloseSubPanel(GameObject panel, Button focusAfter)
{
SetPanel(panel, false);
SetMainButtonsInteractable(true);
if (focusAfter != null)
EventSystem.current?.SetSelectedGameObject(focusAfter.gameObject);
}
private void SetMainButtonsInteractable(bool on)
{
if (_mainButtonsGroup == null) return;
_mainButtonsGroup.interactable = on;
_mainButtonsGroup.blocksRaycasts = on;
}
// ── 存档槽确认 ───────────────────────────────────────────────────────
private void HandleSlotConfirmed(int _)
{
SetPanel(_saveSlotPanel, false);
// 继续游戏:存档已记录检查点场景时加载该场景并落在存档点出生位;
// 否则(新游戏 / 存档尚无检查点)加载首关。
var svc = ServiceLocator.GetOrDefault<ISaveService>();
string checkpointScene = svc?.LastCheckpointScene;
bool hasCheckpoint = !string.IsNullOrEmpty(checkpointScene);
_onSceneLoadRequest?.Raise(new SceneLoadRequest
{
SceneName = hasCheckpoint ? checkpointScene : _firstGameSceneKey,
EntryTransitionId = hasCheckpoint ? svc.LastCheckpointSpawnId : null,
TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
});
}
// ── 游戏状态响应 ─────────────────────────────────────────────────────
private void HandleGameStateChanged(GameStateId state)
{
bool isMainMenu = state == GameStates.MainMenu;
// 离开 MainMenu加载游戏中时锁定所有交互防止重复点击
if (_mainButtonsGroup != null)
{
_mainButtonsGroup.interactable = isMainMenu;
_mainButtonsGroup.blocksRaycasts = isMainMenu;
}
}
// ── 工具方法 ─────────────────────────────────────────────────────────
private void RefreshContinueButton()
{
if (_btnContinue == null) return;
var saveService = ServiceLocator.GetOrDefault<ISaveService>();
bool hasAny = saveService != null
&& (saveService.HasSave(0) || saveService.HasSave(1) || saveService.HasSave(2));
_btnContinue.interactable = hasAny;
}
private static void SetPanel(GameObject panel, bool active)
{
if (panel != null) panel.SetActive(active);
}
private void SetButtonsGroupVisible(bool visible)
{
if (_mainButtonsGroup == null) return;
_mainButtonsGroup.alpha = visible ? 1f : 0f;
_mainButtonsGroup.interactable = visible;
_mainButtonsGroup.blocksRaycasts = visible;
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using UnityEngine;
using BaseGames.Core.Events;
namespace BaseGames.UI.MainMenu
{
/// <summary>
/// 新游戏难度选择数据表(策划编辑)。按顺序列出可选难度;
/// <see cref="NewGameModeController"/> 据此生成按钮,点击返回对应 <see cref="DifficultyLevel"/>。
/// 策划可增删 / 重排 / 改标签 / 改说明,无需改代码。样式改 UI_NewGameModePanel / UI_MainMenu_Button 预制件。
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/UI/New Game Mode Config", fileName = "UI_NewGameModeConfig")]
public class NewGameModeConfigSO : ScriptableObject
{
[Serializable]
public struct Item
{
[Tooltip("此项对应的难度等级。")]
public DifficultyLevel level;
[Tooltip("难度名标签本地化 KeyUI 表,如 MODE_NORMAL。")]
public string labelKey;
[Tooltip("难度说明本地化 Key选中该项时显示可空如 MODE_STEELSOUL_DESC。")]
public string descKey;
[Tooltip("难度图标(可空)。")]
public Sprite icon;
}
[Tooltip("面板标题本地化 Key。")]
[SerializeField] private string _titleKey = "MODE_SELECT_TITLE";
[Tooltip("返回按钮本地化 Key。")]
[SerializeField] private string _backLabelKey = "BTN_BACK";
[SerializeField] private Item[] _items;
public string TitleKey => _titleKey;
public string BackLabelKey => _backLabelKey;
public Item[] Items => _items;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ff0448c32aab82546bd71b33da6b2c9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,4 +1,6 @@
using System; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
@@ -9,99 +11,100 @@ using BaseGames.Localization;
namespace BaseGames.UI.MainMenu namespace BaseGames.UI.MainMenu
{ {
/// <summary> /// <summary>
/// 新游戏模式选择面板(普通 / 钢铁之魂):开新档前选择难度模式。 /// 新游戏模式(难度)选择面板,<b>数据驱动</b>:据 <see cref="NewGameModeConfigSO"/> 生成难度按钮,
/// /// 经 <see cref="IUINavigator"/> 模态压栈,返回 <see cref="DifficultyLevel"/>
/// 设计: /// 点返回 / 按 ESC 取消则返回 null由 <see cref="UIResultPanel{T}"/> 兜底)。
/// · 自包含、场景无关——本地 SetActive 显隐 + 回调,不走 UIManager 面板栈, /// 选中某项时在共享说明区显示其 descKey手柄/鼠标导航通用)。
/// 与 MainMenuController 现有的子面板管理方式一致 /// 策划改 UI_NewGameModeConfig 即可增删/重排难度、改标签/说明;样式改 UI_NewGameModePanel / UI_MainMenu_Button 预制件
/// · 选定后通过 onModeChosen 回调把 DifficultyLevel 交还给调用方SaveSlotController
/// 由调用方负责 CreateSlot(slot, steelSoul) + IDifficultyService.BeginNewGame(level)。
/// · 钢铁之魂为破坏性/高难选项,默认焦点置于普通,并显示一段警示文案。
/// </summary> /// </summary>
public class NewGameModeController : MonoBehaviour public class NewGameModeController : UIResultPanel<DifficultyLevel?>
{ {
[Header("根节点(显隐用,留空则用本 GameObject")] [Header("数据表 / 选项列表")]
[SerializeField] private GameObject _root; [SerializeField] private NewGameModeConfigSO _config;
[Tooltip("难度按钮的父节点(通常挂 VerticalLayoutGroup。")]
[SerializeField] private Transform _container;
[SerializeField] private MainMenuButtonView _buttonPrefab;
[Header("按钮")] [Header("引用")]
[SerializeField] private Button _btnNormal; [SerializeField] private LocalizedText _titleText;
[SerializeField] private Button _btnSteelSoul; [Tooltip("当前选中难度的说明文本(随选中项切换)。")]
[SerializeField] private Button _btnBack; [SerializeField] private TMP_Text _descText;
[SerializeField] private Button _btnBack;
[Header("钢铁之魂说明")] // 取消 / ESC / 返回默认结果:未选择。
[Tooltip("选中钢铁之魂时显示的警示文案(一命模式,死亡即清档)。走本地化键 MODE_STEELSOUL_DESC。")] protected override DifficultyLevel? CancelResult => null;
[SerializeField] private TMP_Text _steelSoulDescText;
[SerializeField] private string _steelSoulDescKey = "MODE_STEELSOUL_DESC";
private Action<DifficultyLevel> _onModeChosen; private readonly List<(MainMenuButtonView view, string descKey)> _options = new();
private Action _onBack; private MainMenuButtonView _firstButton;
private GameObject _lastSelected;
private void Awake() private void Awake() => _btnBack?.onClick.AddListener(() => Complete(null));
protected override void OnPanelOpen() => BuildMenu();
/// <summary>默认焦点:第一项(通常普通难度),避免误选高难项。</summary>
protected override GameObject ResolveFirstSelected()
=> _firstButton != null ? _firstButton.Button.gameObject
: _btnBack != null ? _btnBack.gameObject : null;
/// <summary>据配置重建难度按钮public 以便编辑器预览/测试)。</summary>
public void BuildMenu()
{ {
_btnNormal? .onClick.AddListener(() => Choose(DifficultyLevel.Normal)); ClearMenu();
_btnSteelSoul?.onClick.AddListener(() => Choose(DifficultyLevel.SteelSoul)); if (_titleText != null && _config != null) _titleText.SetKey(_config.TitleKey);
_btnBack? .onClick.AddListener(HandleBack); if (_config == null || _container == null || _buttonPrefab == null) return;
SetVisible(false);
}
/// <summary> foreach (var item in _config.Items)
/// 弹出模式选择。
/// </summary>
/// <param name="onModeChosen">玩家选定模式后回调(面板已自动关闭),携带难度档位。</param>
/// <param name="onBack">点击返回 / 取消后回调(可选)。</param>
public void Show(Action<DifficultyLevel> onModeChosen, Action onBack = null)
{
_onModeChosen = onModeChosen;
_onBack = onBack;
if (_steelSoulDescText != null && !string.IsNullOrEmpty(_steelSoulDescKey))
{ {
string s = LocalizationManager.Get(_steelSoulDescKey, LocalizationTable.UI); var view = Instantiate(_buttonPrefab, _container);
_steelSoulDescText.text = string.IsNullOrEmpty(s) ? _steelSoulDescKey : s; view.gameObject.SetActive(true);
var level = item.level;
view.Bind(item.labelKey, item.icon, () => Complete(level));
_options.Add((view, item.descKey));
if (_firstButton == null) _firstButton = view;
} }
if (_descText != null) _descText.text = string.Empty;
SetVisible(true); _lastSelected = null;
// 默认焦点置于普通模式(避免误选一命模式)
EventSystem.current?.SetSelectedGameObject(_btnNormal != null
? _btnNormal.gameObject
: _btnSteelSoul?.gameObject);
} }
/// <summary>外部强制关闭,不触发回调。</summary> private void ClearMenu()
public void Close()
{ {
_onModeChosen = null; _options.Clear();
_onBack = null; _firstButton = null;
SetVisible(false); if (_container == null) return;
for (int i = _container.childCount - 1; i >= 0; i--)
{
var c = _container.GetChild(i).gameObject;
if (Application.isPlaying) Destroy(c); else DestroyImmediate(c);
}
} }
// ── 回调 ────────────────────────────────────────────────────────────── // 显示当前选中难度的说明(轮询 EventSystem 选中项;手柄/鼠标导航通用,按钮无需额外组件)。
private void Update()
private void Choose(DifficultyLevel level)
{ {
var cb = _onModeChosen; if (_descText == null || EventSystem.current == null) return;
SetVisible(false); var sel = EventSystem.current.currentSelectedGameObject;
_onModeChosen = null; if (sel == _lastSelected) return;
_onBack = null; _lastSelected = sel;
cb?.Invoke(level);
string descKey = null;
foreach (var (view, dk) in _options)
if (view != null && view.Button != null && view.Button.gameObject == sel) { descKey = dk; break; }
_descText.text = string.IsNullOrEmpty(descKey)
? string.Empty
: LocalizationManager.Get(descKey, LocalizationTable.UI);
} }
private void HandleBack() /// <summary>弹出难度选择并等待结果DifficultyLevel / null=取消)。由导航器压栈管理。</summary>
public Task<DifficultyLevel?> ShowAsync(CancellationToken ct = default)
{ {
var cb = _onBack; var nav = GetService<IUINavigator>();
SetVisible(false); if (nav == null)
_onModeChosen = null; {
_onBack = null; Debug.LogError("[NewGameMode] 未找到 IUINavigator 服务,无法弹出模式选择。", this);
cb?.Invoke(); return Task.FromResult<DifficultyLevel?>(null);
} }
return nav.PushForResultAsync<DifficultyLevel?>(this, ct);
// ── 工具 ──────────────────────────────────────────────────────────────
private void SetVisible(bool visible)
{
var go = _root != null ? _root : gameObject;
go.SetActive(visible);
} }
} }
} }

View File

@@ -1,31 +1,30 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro; using TMPro;
using BaseGames.UI;
using BaseGames.Localization; using BaseGames.Localization;
namespace BaseGames.UI.Menus namespace BaseGames.UI.Menus
{ {
/// <summary> /// <summary>
/// 通用模态确认对话框(是/否):用于删除 / 覆盖 / 退出等确认场景。 /// 通用模态确认对话框(是/否):用于删除 / 覆盖 / 退出 / 传送等确认场景。
/// ///
/// 设计: /// <para>双调用模式(同类不同实例各用其一,互不冲突):</para>
/// · 自包含、场景无关——通过本地 SetActive 显隐 + 回调 API 工作,不依赖 UIManager 面板栈, /// <list type="bullet">
/// 因此既能用于主菜单场景(不走 UIManager也能在游戏内复用。 /// <item><b>导航器 async主菜单</b><see cref="ShowAsync"/> 经 <see cref="IUINavigator"/> 压栈,
/// · 标题 / 正文 / 按钮文案均走本地化键LocalizationManager.Get传 null 则保留 Inspector 原文。 /// 栈式回退、ESC 取消、焦点恢复统一由导航器负责。</item>
/// · 默认焦点置于"取消"按钮,防止手柄连按误触确认(破坏性操作安全默认)。 /// <item><b>回调 legacy游戏内地图传送等尚未接入导航器的场景</b><see cref="Show"/> 本地 SetActive
/// 显隐 + onConfirm/onCancel 回调,不依赖导航器。</item>
/// </list>
/// ///
/// 用法: /// 标题 / 正文 / 按钮文案走本地化键(<see cref="LocalizationManager"/>);传 null 保留 Inspector 原文。
/// _confirmDialog.Show("CONFIRM_DELETE_TITLE", "CONFIRM_DELETE_BODY", /// 默认焦点置于"取消",防手柄/键盘连按误触破坏性确认。
/// onConfirm: () => DoDelete(),
/// onCancel: () => {});
/// </summary> /// </summary>
public class ConfirmDialogController : MonoBehaviour public class ConfirmDialogController : UIResultPanel<bool>
{ {
[Header("根节点(显隐用,留空则用本 GameObject")]
[SerializeField] private GameObject _root;
[Header("文本")] [Header("文本")]
[SerializeField] private TMP_Text _titleText; [SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _bodyText; [SerializeField] private TMP_Text _bodyText;
@@ -38,78 +37,89 @@ namespace BaseGames.UI.Menus
[SerializeField] private Button _btnConfirm; [SerializeField] private Button _btnConfirm;
[SerializeField] private Button _btnCancel; [SerializeField] private Button _btnCancel;
private Action _onConfirm; // 取消 / ESC / 销毁默认结果:否。
private Action _onCancel; protected override bool CancelResult => false;
// legacy 回调模式状态
private Action _legacyConfirm;
private Action _legacyCancel;
private bool _legacyMode;
private void Awake() private void Awake()
{ {
_btnConfirm?.onClick.AddListener(HandleConfirm); _btnConfirm?.onClick.AddListener(() => OnButton(true));
_btnCancel? .onClick.AddListener(HandleCancel); _btnCancel? .onClick.AddListener(() => OnButton(false));
SetVisible(false); // 不在此 SetActive(false):面板初始由场景/脚手架序列化为隐藏,激活完全交给导航器
// (对象初始 inactive 时 Awake 会被推迟到首次激活才执行,若在此关闭会立刻自我隐藏)。
} }
/// <summary> /// <summary>默认焦点:取消按钮(破坏性操作安全默认)。</summary>
/// 弹出确认框。 protected override GameObject ResolveFirstSelected()
/// </summary> => _btnCancel != null ? _btnCancel.gameObject
/// <param name="titleKey">标题本地化键null 保留原文。</param> : _btnConfirm != null ? _btnConfirm.gameObject : null;
/// <param name="bodyKey">正文本地化键null 保留原文。</param>
/// <param name="onConfirm">点击确认后回调(确认框已自动关闭)。</param> // ── 导航器 async 路径(主菜单)────────────────────────────────────────
/// <param name="onCancel">点击取消后回调(可选)。</param> /// <summary>弹出确认框并等待结果true=确认 / false=取消)。由导航器压栈管理。</summary>
/// <param name="confirmKey">确认按钮文案本地化键(可选)。</param> public Task<bool> ShowAsync(string titleKey, string bodyKey, CancellationToken ct = default,
/// <param name="cancelKey">取消按钮文案本地化键(可选)。</param> string confirmKey = null, string cancelKey = null)
{
_legacyMode = false;
ApplyText(titleKey, bodyKey, confirmKey, cancelKey);
var nav = GetService<IUINavigator>();
if (nav == null)
{
Debug.LogError("[ConfirmDialog] 未找到 IUINavigator 服务,无法以 async 模式弹出。", this);
return Task.FromResult(false);
}
return nav.PushForResultAsync<bool>(this, ct);
}
// ── legacy 回调路径(游戏内尚未接入导航器的调用方)──────────────────
/// <summary>弹出确认框(回调式,本地显隐,不走导航器)。</summary>
public void Show(string titleKey, string bodyKey, Action onConfirm, Action onCancel = null, public void Show(string titleKey, string bodyKey, Action onConfirm, Action onCancel = null,
string confirmKey = null, string cancelKey = null) string confirmKey = null, string cancelKey = null)
{ {
_onConfirm = onConfirm; _legacyMode = true;
_onCancel = onCancel; _legacyConfirm = onConfirm;
_legacyCancel = onCancel;
if (_titleText != null && titleKey != null) _titleText.text = Loc(titleKey); ApplyText(titleKey, bodyKey, confirmKey, cancelKey);
if (_bodyText != null && bodyKey != null) _bodyText.text = Loc(bodyKey); gameObject.SetActive(true); // OnEnable 经 UIPanelBase 自动聚焦取消按钮
if (_confirmLabel != null && confirmKey != null) _confirmLabel.text = Loc(confirmKey);
if (_cancelLabel != null && cancelKey != null) _cancelLabel.text = Loc(cancelKey);
SetVisible(true);
// 安全默认:焦点置于取消,避免手柄/键盘连按直接确认破坏性操作
EventSystem.current?.SetSelectedGameObject(_btnCancel != null
? _btnCancel.gameObject
: _btnConfirm?.gameObject);
} }
/// <summary>外部强制关闭(如父面板被关闭时)。不触发任何回调。</summary> /// <summary>外部强制关闭(仅 legacy 模式有效)。不触发任何回调。</summary>
public void Close() public void Close()
{ {
_onConfirm = null; if (!_legacyMode) return;
_onCancel = null; _legacyConfirm = null;
SetVisible(false); _legacyCancel = null;
gameObject.SetActive(false);
} }
// ── 按钮回调 ────────────────────────────────────────────────────────── // ── 按钮 ──────────────────────────────────────────────────────────────
private void OnButton(bool confirmed)
private void HandleConfirm()
{ {
var cb = _onConfirm; if (_legacyMode)
SetVisible(false); {
_onConfirm = null; var cb = confirmed ? _legacyConfirm : _legacyCancel;
_onCancel = null; _legacyConfirm = null;
cb?.Invoke(); _legacyCancel = null;
} gameObject.SetActive(false);
cb?.Invoke();
private void HandleCancel() }
{ else
var cb = _onCancel; {
SetVisible(false); Complete(confirmed); // 设置结果 + 由导航器出栈
_onConfirm = null; }
_onCancel = null;
cb?.Invoke();
} }
// ── 工具 ────────────────────────────────────────────────────────────── // ── 工具 ──────────────────────────────────────────────────────────────
private void ApplyText(string titleKey, string bodyKey, string confirmKey, string cancelKey)
private void SetVisible(bool visible)
{ {
var go = _root != null ? _root : gameObject; if (_titleText != null && titleKey != null) _titleText.text = Loc(titleKey);
go.SetActive(visible); if (_bodyText != null && bodyKey != null) _bodyText.text = Loc(bodyKey);
if (_confirmLabel != null && confirmKey != null) _confirmLabel.text = Loc(confirmKey);
if (_cancelLabel != null && cancelKey != null) _cancelLabel.text = Loc(cancelKey);
} }
private static string Loc(string key) private static string Loc(string key)

View File

@@ -0,0 +1,102 @@
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core.Assets;
using BaseGames.Core.Events;
using BaseGames.UI.MainMenu; // 复用通用菜单按钮视图 MainMenuButtonView
namespace BaseGames.UI
{
/// <summary>
/// 数据驱动暂停菜单(仿 <see cref="MainMenu.DataDrivenMainMenuController"/>)。
/// 据 <see cref="PauseMenuConfigSO"/> 在运行时生成按钮、派发动作;生命周期/焦点/淡入由 <see cref="UIPanelBase"/> 统一处理。
/// 策划改 UI_PauseMenuConfig 即可增删/重排/改标签/改动作,零代码;样式改 UI_PauseScreen / UI_MainMenu_Button 预制件。
/// </summary>
public class DataDrivenPauseMenuController : UIPanelBase
{
[Header("数据表 / 按钮列表")]
[SerializeField] private PauseMenuConfigSO _config;
[Tooltip("按钮的父节点(通常挂 VerticalLayoutGroup。")]
[SerializeField] private Transform _container;
[SerializeField] private MainMenuButtonView _buttonPrefab;
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onResumeRequested;
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
private IUIManager _uiManager;
private readonly List<MainMenuButtonView> _buttons = new();
private MainMenuButtonView _firstButton;
// 暂停面板由 UIManager 开启,此时 ServiceLocator 已就绪
protected override void OnPanelOpen()
{
_uiManager = GetService<IUIManager>();
BuildMenu();
}
protected override void OnPanelClose() => _uiManager = null;
/// <summary>默认焦点 / 焦点恢复回到首个按钮。</summary>
protected override GameObject ResolveFirstSelected()
=> _firstButton != null ? _firstButton.Button.gameObject : null;
/// <summary>据配置重建按钮列表public 以便编辑器预览/测试)。</summary>
public void BuildMenu()
{
ClearMenu();
if (_config == null || _container == null || _buttonPrefab == null) return;
foreach (var item in _config.Items)
{
var view = Instantiate(_buttonPrefab, _container);
view.gameObject.SetActive(true);
var captured = item;
view.Bind(item.labelKey, item.icon, () => Dispatch(captured));
_buttons.Add(view);
if (_firstButton == null) _firstButton = view;
}
}
private void ClearMenu()
{
_buttons.Clear();
_firstButton = null;
if (_container == null) return;
for (int i = _container.childCount - 1; i >= 0; i--)
{
var child = _container.GetChild(i).gameObject;
if (Application.isPlaying) Destroy(child);
else DestroyImmediate(child);
}
}
// ── 动作派发 ──────────────────────────────────────────────────────────
private void Dispatch(PauseMenuConfigSO.Item item)
{
switch (item.action)
{
case PauseMenuAction.Resume:
_onResumeRequested?.Raise();
_uiManager?.CloseTopPanel();
break;
case PauseMenuAction.OpenSettings:
_uiManager?.OpenPanel(PanelId.Settings);
break;
case PauseMenuAction.ReturnToMainMenu:
_uiManager?.CloseTopPanel();
_onSceneLoadRequest?.Raise(new SceneLoadRequest
{
SceneName = string.IsNullOrEmpty(item.sceneKey) ? AddressKeys.SceneMainMenu : item.sceneKey,
TransitionType = TransitionType.Scene,
});
break;
case PauseMenuAction.Quit:
Application.Quit();
break;
case PauseMenuAction.RaiseEvent:
item.eventChannel?.Raise();
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70ebf028cc731414caf75f2c7f4c28b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
using System;
using UnityEngine;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
/// <summary>暂停菜单项动作类型。常用动作内置;任意自定义走事件频道。</summary>
public enum PauseMenuAction
{
Resume, // 继续游戏(关闭暂停面板)
OpenSettings, // 打开设置面板
ReturnToMainMenu, // 返回主菜单(场景加载)
Quit, // 退出游戏
RaiseEvent, // 触发 eventChannel万能扩展
}
/// <summary>
/// 暂停菜单数据驱动表(策划编辑)。按顺序列出暂停菜单项;
/// <see cref="DataDrivenPauseMenuController"/> 据此生成按钮并派发动作。
/// 策划可增删 / 重排 / 改标签图标 / 改动作,无需改代码。样式改 UI_PauseScreen / UI_MainMenu_Button 预制件。
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/UI/Pause Menu Config", fileName = "UI_PauseMenuConfig")]
public class PauseMenuConfigSO : ScriptableObject
{
[Serializable]
public struct Item
{
[Tooltip("按钮标签本地化 KeyUI 表)。")]
public string labelKey;
[Tooltip("按钮图标(可空)。")]
public Sprite icon;
[Tooltip("点击动作。")]
public PauseMenuAction action;
[Tooltip("ReturnToMainMenu 的目标场景 Addressable Key留空用默认主菜单。")]
public string sceneKey;
[Tooltip("RaiseEvent 动作触发的事件频道。")]
public VoidEventChannelSO eventChannel;
}
[SerializeField] private Item[] _items;
public Item[] Items => _items;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7dd5202b8fce1d40b073624a3b22953
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,68 +0,0 @@
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core.Assets;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
/// <summary>
/// 暂停菜单控制器(架构 10_UIModule §5
/// 挂载在 Canvas_Menu → PauseMenuPanel GameObject 上。
/// 按钮绑定在 Awake 中完成;生命周期 / 焦点由 <see cref="UIPanelBase"/> 统一处理。
/// </summary>
public class PauseMenuController : UIPanelBase
{
// UIManager 通过 ServiceLocator 解析,开启时自动获取,无需 Inspector 直接绑定具体类型
private IUIManager _uiManager;
[Header("按钮引用")]
[SerializeField] private Button _btnResume;
[SerializeField] private Button _btnSettings;
[SerializeField] private Button _btnMainMenu;
[SerializeField] private Button _btnQuit;
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onResumeRequested;
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
private void Awake()
{
_btnResume?.onClick.AddListener(Resume);
_btnSettings?.onClick.AddListener(OpenSettings);
_btnMainMenu?.onClick.AddListener(GoToMainMenu);
_btnQuit?.onClick.AddListener(Application.Quit);
}
// 暂停面板由 UIManager 开启,此时 ServiceLocator 已就绪
protected override void OnPanelOpen() => _uiManager = GetService<IUIManager>();
protected override void OnPanelClose() => _uiManager = null;
/// <summary>默认焦点 / 焦点恢复回到"继续"按钮(基类 FocusFirst 调用)。</summary>
protected override GameObject ResolveFirstSelected()
=> _btnResume != null ? _btnResume.gameObject : null;
// ── 按钮回调 ──────────────────────────────────────────────────────────
private void Resume()
{
_onResumeRequested?.Raise();
_uiManager?.CloseTopPanel();
}
private void OpenSettings()
{
_uiManager?.OpenPanel(PanelId.Settings);
}
private void GoToMainMenu()
{
_uiManager?.CloseTopPanel();
_onSceneLoadRequest?.Raise(new SceneLoadRequest
{
SceneName = AddressKeys.SceneMainMenu,
TransitionType = TransitionType.Scene,
ShowLoadingScreen = false,
});
}
}
}

View File

@@ -3,13 +3,10 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
using BaseGames.Core; using BaseGames.Core;
using BaseGames.Core.Events; using BaseGames.Core.Events;
using BaseGames.Core.Save; using BaseGames.Core.Save;
using BaseGames.Localization; using BaseGames.UI;
using BaseGames.World.Map;
using BaseGames.UI.MainMenu; using BaseGames.UI.MainMenu;
namespace BaseGames.UI.Menus namespace BaseGames.UI.Menus
@@ -26,19 +23,21 @@ namespace BaseGames.UI.Menus
/// <summary> /// <summary>
/// 驱动主菜单存档槽选择面板(新游戏 / 继续 / 删除)。 /// 驱动主菜单存档槽选择面板(新游戏 / 继续 / 删除)。
/// ///
/// <para>本面板经 <see cref="IUINavigator"/> 压栈(由主菜单控制器 Push。覆盖确认 / 模式选择
/// 这两个子对话框走导航器结果面板(<see cref="ConfirmDialogController.ShowAsync"/> /
/// <see cref="NewGameModeController.ShowAsync"/>)——线性 <c>await</c>无回调嵌套ESC 逐层回退。</para>
///
/// 前端选档流程: /// 前端选档流程:
/// · 新游戏开档前先选模式(普通 / 钢铁之魂),占用槽位需覆盖确认。 /// · 新游戏开档前先选模式(普通 / 钢铁之魂),占用槽位需覆盖确认。
/// · 删除强制走通用确认对话框(无静默删除旁路)。 /// · 删除强制走通用确认对话框(无静默删除旁路)。
/// · 卡片展示游玩时长 / 区域 / 货币 / 生命 / 钢魂徽章。 /// · 卡片展示游玩时长 / 区域 / 货币 / 生命 / 钢魂徽章。
/// /// 模式由主菜单控制器在压栈前经 <see cref="SetMode"/> 指定。
/// 模式由 MainMenuController 在打开面板前通过 <see cref="SetMode"/> 指定。
/// ConfirmDialog 与 NewGameMode 面板经 Inspector 注入(同处 MainMenu 场景)。
/// </summary> /// </summary>
public class SaveSlotController : MonoBehaviour, IFocusable public class SaveSlotController : UIPanelBase
{ {
[SerializeField] private SaveSlotUI[] _slotUIs; [SerializeField] private SaveSlotUI[] _slotUIs;
[Header("子面板Inspector 注入,同处 MainMenu 场景)")] [Header("子对话框Inspector 注入,同处 MainMenu 场景,走导航器结果面板")]
[Tooltip("通用确认对话框,用于覆盖 / 删除确认。为空时:覆盖退化为直接建档,删除被忽略(绝不静默删除)。")] [Tooltip("通用确认对话框,用于覆盖 / 删除确认。为空时:覆盖退化为直接建档,删除被忽略(绝不静默删除)。")]
[SerializeField] private ConfirmDialogController _confirmDialog; [SerializeField] private ConfirmDialogController _confirmDialog;
[Tooltip("新游戏模式选择面板。为空时新游戏退化为普通模式。")] [Tooltip("新游戏模式选择面板。为空时新游戏退化为普通模式。")]
@@ -48,7 +47,7 @@ namespace BaseGames.UI.Menus
[SerializeField] private IntEventChannelSO _onSlotConfirmed; [SerializeField] private IntEventChannelSO _onSlotConfirmed;
[Header("焦点")] [Header("焦点")]
[Tooltip("面板恢复为栈顶时自动聚焦的默认按钮,通常为第一个存档槽的选择按钮。")] [Tooltip("面板默认聚焦的按钮,通常为第一个存档槽的选择按钮。")]
[SerializeField] private Button _defaultFocusButton; [SerializeField] private Button _defaultFocusButton;
private SaveSlotPanelMode _mode = SaveSlotPanelMode.NewGame; private SaveSlotPanelMode _mode = SaveSlotPanelMode.NewGame;
@@ -60,40 +59,24 @@ namespace BaseGames.UI.Menus
if (_slotUIs[i] != null) _slotUIs[i].Init(i, this); if (_slotUIs[i] != null) _slotUIs[i].Init(i, this);
} }
/// <summary>由 MainMenuController 在 SetActive(true) 之前调用,决定本次打开语境。</summary> /// <summary>由主菜单控制器在压栈前调用,决定本次打开语境。</summary>
public void SetMode(SaveSlotPanelMode mode) => _mode = mode; public void SetMode(SaveSlotPanelMode mode) => _mode = mode;
// ── IFocusable ──────────────────────────────────────────────────────── /// <summary>默认 / 恢复焦点回到首个存档槽按钮。</summary>
public void OnFocusRestored() => StartCoroutine(RestoreFocusNextFrame()); protected override GameObject ResolveFirstSelected()
=> _defaultFocusButton != null ? _defaultFocusButton.gameObject : null;
private System.Collections.IEnumerator RestoreFocusNextFrame() // ── 生命周期UIPanelBase 驱动)──────────────────────────────────────
{ protected override void OnPanelOpen()
yield return null;
if (UnityEngine.EventSystems.EventSystem.current != null && _defaultFocusButton != null)
UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(_defaultFocusButton.gameObject);
}
private void OnEnable()
{ {
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
var ct = _cts.Token; RunGuarded(RefreshAsync(_cts.Token));
var task = RefreshAsync(ct);
task.ContinueWith(t =>
{
if (t.IsFaulted && !(t.Exception?.InnerException is OperationCanceledException))
Debug.LogException(t.Exception?.InnerException ?? t.Exception, this);
}, TaskScheduler.FromCurrentSynchronizationContext());
// 面板打开时设置初始焦点(键盘 / 手柄导航入口)
StartCoroutine(RestoreFocusNextFrame());
} }
private void OnDisable() protected override void OnPanelClose()
{ {
// 关闭子对话框,避免下次打开残留 // 注意:不再级联 Close 子对话框(旧实现于此强关 confirm/mode是"ESC 一次关多层"的根因)。
_confirmDialog?.Close(); // 子对话框生命周期完全交给导航器;若本面板随场景卸载,其 await 由结果面板兜底收口。
_modeSelect?.Close();
_cts?.Cancel(); _cts?.Cancel();
_cts?.Dispose(); _cts?.Dispose();
_cts = null; _cts = null;
@@ -119,47 +102,42 @@ namespace BaseGames.UI.Menus
} }
// ── 选槽 ──────────────────────────────────────────────────────────────── // ── 选槽 ────────────────────────────────────────────────────────────────
/// <summary>选中指定槽位。行为取决于当前模式与槽位是否有档。由 SaveSlotUI 内部按钮调用。</summary> /// <summary>选中指定槽位。行为取决于当前模式与槽位是否有档。由 SaveSlotUI 内部按钮调用。</summary>
public void OnSlotSelected(int slotIndex) public void OnSlotSelected(int slotIndex)
{ {
if (slotIndex < 0 || slotIndex >= _slotUIs.Length) return; if (slotIndex < 0 || slotIndex >= _slotUIs.Length) return;
RunGuarded(HandleSlotSelectedAsync(slotIndex));
}
private async Task HandleSlotSelectedAsync(int slotIndex)
{
var svc = ServiceLocator.GetOrDefault<ISaveService>(); var svc = ServiceLocator.GetOrDefault<ISaveService>();
if (svc == null) return; if (svc == null) return;
var ct = _cts?.Token ?? CancellationToken.None;
bool hasData = svc.HasSave(slotIndex);
if (_mode == SaveSlotPanelMode.Continue) if (_mode == SaveSlotPanelMode.Continue)
{ {
if (!hasData) return; // 继续模式:空槽不可选 if (!svc.HasSave(slotIndex)) return; // 继续模式:空槽不可选
_ = ContinueSlotAsync(slotIndex); await ContinueSlotAsync(slotIndex);
return; return;
} }
// ── 新游戏模式 ── // ── 新游戏模式 ──
if (hasData) if (svc.HasSave(slotIndex) && _confirmDialog != null)
{ {
// 占用槽位:先覆盖确认 bool ok = await _confirmDialog.ShowAsync("CONFIRM_OVERWRITE_TITLE", "CONFIRM_OVERWRITE_BODY", ct);
if (_confirmDialog != null) if (!ok) return; // 取消覆盖
_confirmDialog.Show("CONFIRM_OVERWRITE_TITLE", "CONFIRM_OVERWRITE_BODY",
onConfirm: () => BeginNewGameFlow(slotIndex));
else
BeginNewGameFlow(slotIndex); // 无对话框时退化为直接建档
} }
else
{
BeginNewGameFlow(slotIndex);
}
}
/// <summary>开新档流程:选模式 → 建档。</summary> DifficultyLevel level = DifficultyLevel.Normal;
private void BeginNewGameFlow(int slotIndex)
{
if (_modeSelect != null) if (_modeSelect != null)
_modeSelect.Show(level => _ = StartNewGameAsync(slotIndex, level)); {
else DifficultyLevel? chosen = await _modeSelect.ShowAsync(ct);
_ = StartNewGameAsync(slotIndex, DifficultyLevel.Normal); // 无模式面板时退化为普通 if (chosen == null) return; // 取消模式选择
level = chosen.Value;
}
await StartNewGameAsync(slotIndex, level);
} }
private async Task StartNewGameAsync(int slotIndex, DifficultyLevel level) private async Task StartNewGameAsync(int slotIndex, DifficultyLevel level)
@@ -190,7 +168,6 @@ namespace BaseGames.UI.Menus
} }
// ── 删除(强制确认)──────────────────────────────────────────────────── // ── 删除(强制确认)────────────────────────────────────────────────────
/// <summary>请求删除指定槽位。强制经通用确认对话框;无对话框时忽略,绝不静默删除。</summary> /// <summary>请求删除指定槽位。强制经通用确认对话框;无对话框时忽略,绝不静默删除。</summary>
public void OnSlotDeleteRequested(int slotIndex) public void OnSlotDeleteRequested(int slotIndex)
{ {
@@ -201,9 +178,15 @@ namespace BaseGames.UI.Menus
Debug.LogWarning("[SaveSlotController] 未配置 ConfirmDialog删除请求被忽略防止静默删除。"); Debug.LogWarning("[SaveSlotController] 未配置 ConfirmDialog删除请求被忽略防止静默删除。");
return; return;
} }
RunGuarded(DeleteFlowAsync(slotIndex));
}
_confirmDialog.Show("CONFIRM_DELETE_TITLE", "CONFIRM_DELETE_BODY", private async Task DeleteFlowAsync(int slotIndex)
onConfirm: () => _ = DeleteAndRefreshAsync(slotIndex)); {
var ct = _cts?.Token ?? CancellationToken.None;
bool ok = await _confirmDialog.ShowAsync("CONFIRM_DELETE_TITLE", "CONFIRM_DELETE_BODY", ct);
if (!ok) return;
await DeleteAndRefreshAsync(slotIndex);
} }
private async Task DeleteAndRefreshAsync(int slotIndex) private async Task DeleteAndRefreshAsync(int slotIndex)
@@ -213,5 +196,16 @@ namespace BaseGames.UI.Menus
await svc.DeleteSlotAsync(slotIndex); await svc.DeleteSlotAsync(slotIndex);
await RefreshAsync(_cts?.Token ?? CancellationToken.None); await RefreshAsync(_cts?.Token ?? CancellationToken.None);
} }
// ── 工具 ──────────────────────────────────────────────────────────────
/// <summary>fire-and-forget Task 的统一异常护栏(吞掉取消,记录其余)。</summary>
private void RunGuarded(Task task)
{
task.ContinueWith(t =>
{
if (t.IsFaulted && !(t.Exception?.InnerException is OperationCanceledException))
Debug.LogException(t.Exception?.InnerException ?? t.Exception, this);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
} }
} }

View File

@@ -1,142 +0,0 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Localization;
namespace BaseGames.UI
{
/// <summary>
/// 设置面板控制器(架构 10_UIModule §7
/// 驱动 SettingsManager 的音量与画面设置,并从当前配置初始化控件值。
/// 生命周期 / 焦点由 <see cref="UIPanelBase"/> 统一处理。
/// </summary>
public class SettingsPanelController : UIPanelBase
{
// ISettingsService 通过 ServiceLocator 获取,无需 Inspector 直接注入具体类,
// 支持测试场景替换 Mock 实现。
private ISettingsService _settings;
[Header("音量滑条")]
[SerializeField] private Slider _masterVolume;
[SerializeField] private Slider _bgmVolume;
[SerializeField] private Slider _sfxVolume;
[SerializeField] private Slider _ambientVolume;
[Header("画面")]
[SerializeField] private Toggle _vSyncToggle;
[SerializeField] private TMP_Dropdown _fpsDropdown; // 30 / 60 / 120 / 无限
[Header("可访问性")]
[SerializeField] private Slider _uiScaleSlider; // 0.8 ~ 1.5
[SerializeField] private TMP_Text _uiScaleValueText; // 实时显示 "100%"
[SerializeField] private TMP_Dropdown _colorblindDropdown; // None / Prot / Deut / Trit
[SerializeField] private Toggle _screenShakeToggle;
[Header("语言")]
[SerializeField] private TMP_Dropdown _languageDropdown; // 中文 / English / 日本語 / 한국어
[Header("按键重绑定")]
[SerializeField] private GameObject _rebindPanelRoot; // RebindPanel GameObject
private static readonly int[] FpsOptions = { 30, 60, 120, -1 };
// 语言下拉项顺序(与脚手架填充的显示项一一对应)
private static readonly Language[] LanguageOptions =
{ Language.ChineseSimplified, Language.English, Language.Japanese, Language.Korean };
protected override void OnPanelOpen()
{
_settings = ServiceLocator.GetOrDefault<ISettingsService>();
if (_settings == null) return;
var data = _settings.Current;
// 初始化控件值(先移除监听再设置值再添加,防止面板重开时重复注册)
InitSlider(_masterVolume, data.MasterVolume, v => _settings.SetMasterVolume(v));
InitSlider(_bgmVolume, data.BGMVolume, v => _settings.SetBGMVolume(v));
InitSlider(_sfxVolume, data.SFXVolume, v => _settings.SetSFXVolume(v));
InitSlider(_ambientVolume,data.AmbientVolume, v => _settings.SetAmbientVolume(v));
if (_vSyncToggle != null)
{
_vSyncToggle.onValueChanged.RemoveAllListeners();
_vSyncToggle.isOn = data.VSync;
_vSyncToggle.onValueChanged.AddListener(v => _settings.SetVSync(v));
}
if (_fpsDropdown != null)
{
_fpsDropdown.onValueChanged.RemoveAllListeners();
int idx = System.Array.IndexOf(FpsOptions, data.TargetFPS);
_fpsDropdown.value = idx >= 0 ? idx : 1; // default 60
_fpsDropdown.onValueChanged.AddListener(i =>
_settings.SetTargetFrameRate(FpsOptions[Mathf.Clamp(i, 0, FpsOptions.Length - 1)]));
}
// ── 可访问性 ──────────────────────────────────────────────────────
if (_uiScaleSlider != null)
{
_uiScaleSlider.onValueChanged.RemoveAllListeners();
_uiScaleSlider.minValue = 0.8f;
_uiScaleSlider.maxValue = 1.5f;
_uiScaleSlider.value = Mathf.Clamp(data.UIScale, _uiScaleSlider.minValue, _uiScaleSlider.maxValue);
UpdateUIScaleLabel(_uiScaleSlider.value);
_uiScaleSlider.onValueChanged.AddListener(v =>
{
_settings.SetUIScale(v);
UpdateUIScaleLabel(v);
});
}
if (_colorblindDropdown != null)
{
_colorblindDropdown.onValueChanged.RemoveAllListeners();
_colorblindDropdown.value = (int)data.ColorblindMode;
_colorblindDropdown.onValueChanged.AddListener(i =>
_settings.SetColorblindMode((ColorblindMode)Mathf.Clamp(i, 0, 3)));
}
if (_screenShakeToggle != null)
{
_screenShakeToggle.onValueChanged.RemoveAllListeners();
_screenShakeToggle.isOn = data.ScreenShakeEnabled;
_screenShakeToggle.onValueChanged.AddListener(v => _settings.SetScreenShakeEnabled(v));
}
// ── 语言 ──────────────────────────────────────────────────────────
if (_languageDropdown != null)
{
_languageDropdown.onValueChanged.RemoveAllListeners();
var loc = ServiceLocator.GetOrDefault<ILocalizationService>();
int idx = loc != null ? System.Array.IndexOf(LanguageOptions, loc.CurrentLanguage) : 0;
_languageDropdown.value = idx >= 0 ? idx : 0;
_languageDropdown.RefreshShownValue();
_languageDropdown.onValueChanged.AddListener(i =>
ServiceLocator.GetOrDefault<ILocalizationService>()?
.SetLanguage(LanguageOptions[Mathf.Clamp(i, 0, LanguageOptions.Length - 1)]));
}
}
private void UpdateUIScaleLabel(float v)
{
if (_uiScaleValueText != null)
_uiScaleValueText.text = Mathf.RoundToInt(v * 100f) + "%";
}
// ── 辅助 ──────────────────────────────────────────────────────────────
private static void InitSlider(Slider slider, float value, UnityEngine.Events.UnityAction<float> onChange)
{
if (slider == null) return;
slider.onValueChanged.RemoveAllListeners();
slider.value = value;
slider.onValueChanged.AddListener(onChange);
}
// ── 焦点 ──────────────────────────────────────────────────────────────
/// <summary>默认焦点 / 焦点恢复回到主音量滑条(基类 FocusFirst 调用)。</summary>
protected override GameObject ResolveFirstSelected()
=> _masterVolume != null ? _masterVolume.gameObject : null;
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a5e5751b1ad3a45439165d543174bbe1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace BaseGames.UI
{
/// <summary>
/// 统一 UI 导航栈服务。所有面板(主菜单子面板 + 游戏内面板)经此压栈/出栈,
/// 由它统一保证只有栈顶可交互、下层被屏蔽出导航图、ESC 逐层回退、出栈恢复焦点。
///
/// <para>设计要点:</para>
/// <list type="bullet">
/// <item>单一取消入口:导航器是 EVT_UICancelPressed 的唯一消费者ESC 只关栈顶一层。</item>
/// <item>场景作用域:面板可能位于会被卸载的关卡 / 主菜单场景,导航器订阅 sceneUnloaded
/// 清理随场景销毁的栈层,每次操作对已销毁面板兜底。</item>
/// <item>非面板的"底层上下文"(如主菜单按钮组、游戏内 HUD不入栈由各自上下文
/// 订阅 <see cref="StackChanged"/> / 读 <see cref="Depth"/> 自行屏蔽。</item>
/// </list>
/// </summary>
public interface IUINavigator
{
/// <summary>当前栈顶面板;空栈为 null。</summary>
UIPanelBase Top { get; }
/// <summary>栈深度(已打开的面板层数)。</summary>
int Depth { get; }
/// <summary>栈结构变化(任何 Push / Pop / 场景清理)后触发,供底层上下文屏蔽自身。</summary>
event Action StackChanged;
/// <summary>压栈打开面板。<paramref name="mode"/> 为空时用面板自身 <see cref="UIPanelBase.DefaultMode"/>。</summary>
void Push(UIPanelBase panel, PushMode? mode = null);
/// <summary>关闭栈顶并恢复下层(若有)。空栈无操作。</summary>
void Pop();
/// <summary>清空整个栈(逐层关闭)。</summary>
void PopToRoot();
/// <summary>
/// 压栈打开结果面板并等待玩家给出结果(确认 / 选择)。
/// 面板被出栈(含 ESC 取消)、被销毁或 <paramref name="ct"/> 取消时,
/// 以面板的取消默认值兜底完成,绝不悬挂 await。
/// </summary>
Task<T> PushForResultAsync<T>(UIResultPanel<T> panel, CancellationToken ct = default);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c910e685c9b35d4b90d187e2e20e614
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
namespace BaseGames.UI
{
/// <summary>
/// 面板压栈方式:决定一个面板压栈时,其正下方的面板如何处理。
/// </summary>
public enum PushMode
{
/// <summary>替换:压栈时停用下方面板(整屏切换,下层不可见、不参与导航)。</summary>
Replace,
/// <summary>模态:压栈时下方面板保持可见,但屏蔽其交互与射线(对话框叠在其上)。</summary>
Modal,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2dad1d08b7f80394493ca81439a7eb02
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using BaseGames.Core;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
/// <summary>
/// <see cref="IUINavigator"/> 的实现:统一 UI 导航栈。常驻 Persistent 场景,
/// 经 ServiceLocator 暴露,主菜单与游戏内共用同一套栈语义。
///
/// <para>压栈:记录压栈前焦点;据新面板 <see cref="PushMode"/> 处理下方面板
/// Replace→停用Modal→保留可见但 <see cref="UIPanelBase.SetInteractableLayer"/>(false)
/// 使其退出导航图);激活新面板并延后一帧聚焦其首项。</para>
/// <para>出栈:关闭栈顶,按其压栈时的 mode 还原下方面板,恢复焦点到压栈前的选中项。</para>
/// <para>取消:本类是 EVT_UICancelPressed 的唯一消费者,仅在栈顶 <see cref="UIPanelBase.CanCancel"/>
/// 时出栈一层(逐层回退)。</para>
/// </summary>
[DefaultExecutionOrder(+40)] // 早于 UIManager(+50),确保委托方解析得到本服务
public class UINavigator : MonoBehaviour, IUINavigator
{
[Tooltip("UI 取消操作ESC / 手柄 B·Circle。本导航器为唯一订阅者按下时关闭栈顶一层。对应 EVT_UICancelPressed。")]
[SerializeField] private VoidEventChannelSO _onUICancelPressed;
private readonly Stack<UIStackEntry> _stack = new();
private readonly CompositeDisposable _subs = new();
private Coroutine _focusRoutine;
public UIPanelBase Top => _stack.Count > 0 ? _stack.Peek().Panel : null;
public int Depth => _stack.Count;
public event Action StackChanged;
// ── 生命周期 ──────────────────────────────────────────────────────────
private void OnEnable()
{
ServiceLocator.Register<IUINavigator>(this);
_onUICancelPressed?.Subscribe(HandleCancel).AddTo(_subs);
SceneManager.sceneUnloaded += OnSceneUnloaded;
}
private void OnDisable()
{
SceneManager.sceneUnloaded -= OnSceneUnloaded;
_subs.Clear();
ServiceLocator.Unregister<IUINavigator>(this);
}
// ── 压栈 ──────────────────────────────────────────────────────────────
public void Push(UIPanelBase panel, PushMode? mode = null)
{
if (panel == null) return;
PurgeDead();
PushMode m = mode ?? panel.DefaultMode;
// 处理下方面板Replace 停用、Modal 屏蔽交互(退出导航图,杜绝上下键穿透)。
if (_stack.Count > 0)
{
var below = _stack.Peek().Panel;
if (below != null)
{
below.OnFocusLost();
if (m == PushMode.Replace) below.gameObject.SetActive(false);
else below.SetInteractableLayer(false);
}
}
var entry = new UIStackEntry
{
Panel = panel,
Mode = m,
FocusToRestore = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null,
OwningScene = panel.gameObject.scene,
};
_stack.Push(entry);
panel.gameObject.SetActive(true);
panel.SetInteractableLayer(true);
FocusNextFrame(panel.FirstSelectableGO);
StackChanged?.Invoke();
}
// ── 出栈 ──────────────────────────────────────────────────────────────
public void Pop()
{
PurgeDead();
if (_stack.Count == 0) return;
var top = _stack.Pop();
if (top.Panel != null) top.Panel.gameObject.SetActive(false);
UIPanelBase below = _stack.Count > 0 ? _stack.Peek().Panel : null;
if (below != null)
{
// 按 top 压栈时的 mode 还原下方面板。
if (top.Mode == PushMode.Replace) below.gameObject.SetActive(true);
else below.SetInteractableLayer(true);
below.OnFocusGained();
}
// 恢复焦点到压栈前的选中项(失效则回落到下层首项)。
GameObject restore = top.FocusToRestore != null && top.FocusToRestore.activeInHierarchy
? top.FocusToRestore
: below != null ? below.FirstSelectableGO : null;
FocusNextFrame(restore);
StackChanged?.Invoke();
}
public void PopToRoot()
{
while (_stack.Count > 0) Pop();
}
// ── 取消ESC / 手柄 B──────────────────────────────────────────────
private void HandleCancel()
{
PurgeDead();
if (_stack.Count == 0) return; // 栈空(如主菜单根):无操作
if (Top != null && !Top.CanCancel) return;
Pop(); // 仅关栈顶一层
}
// ── 结果面板 ──────────────────────────────────────────────────────────
public Task<T> PushForResultAsync<T>(UIResultPanel<T> panel, CancellationToken ct = default)
{
if (panel == null) return Task.FromResult<T>(default);
Task<T> task = panel.BeginResult(ct);
Push(panel, PushMode.Modal); // 结果对话框天然模态:下层保留可见但屏蔽交互
return task;
}
// ── 场景卸载清理 ──────────────────────────────────────────────────────
private void OnSceneUnloaded(Scene s)
{
if (_stack.Count == 0) return;
// Stack 无法删中间项:过滤后按原序重建(保留非本场景且未销毁的层)。
var kept = new List<UIStackEntry>();
foreach (var e in _stack) // 枚举顺序:栈顶→栈底
if (e.Panel != null && e.OwningScene != s) kept.Add(e);
if (kept.Count == _stack.Count) return; // 无变化
_stack.Clear();
for (int i = kept.Count - 1; i >= 0; i--) _stack.Push(kept[i]);
StackChanged?.Invoke();
}
private void PurgeDead()
{
bool changed = false;
while (_stack.Count > 0 && _stack.Peek().Panel == null) { _stack.Pop(); changed = true; }
if (changed) StackChanged?.Invoke();
}
// ── 焦点(延后一帧,避开 OnEnable / UISelectionRestorer 同帧竞争)────────
private void FocusNextFrame(GameObject target)
{
if (_focusRoutine != null) StopCoroutine(_focusRoutine);
if (!isActiveAndEnabled) return;
_focusRoutine = StartCoroutine(FocusRoutine(target));
}
private IEnumerator FocusRoutine(GameObject target)
{
yield return null;
_focusRoutine = null;
if (target == null || !target.activeInHierarchy) yield break;
if (EventSystem.current != null)
EventSystem.current.SetSelectedGameObject(target);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b777118c6c3387b4e81cbf5bb56c8a23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,60 @@
using System.Threading;
using System.Threading.Tasks;
namespace BaseGames.UI
{
/// <summary>
/// 返回结果的模态面板基类(确认框、模式 / 难度选择等)。
///
/// <para>用法(调用方走线性 await无回调嵌套</para>
/// <code>
/// bool ok = await _confirmDialog.ShowAsync("CONFIRM_OVERWRITE_TITLE", "CONFIRM_OVERWRITE_BODY", ct);
/// if (!ok) return;
/// </code>
///
/// <para>结果通道由 <see cref="TaskCompletionSource{T}"/> 承载,并对所有"提前结束"路径兜底
/// ESC 取消出栈、外部强关、所属场景卸载、CancellationToken 取消),保证 await 绝不悬挂。</para>
/// </summary>
public abstract class UIResultPanel<T> : UIPanelBase
{
private TaskCompletionSource<T> _tcs;
private CancellationTokenRegistration _ctReg;
/// <summary>取消 / 默认结果ESC、返回、销毁、场景卸载、ct 取消时以此完成。</summary>
protected abstract T CancelResult { get; }
/// <summary>由导航器 <see cref="IUINavigator.PushForResultAsync{T}"/> 调用:开启一轮结果等待。</summary>
internal Task<T> BeginResult(CancellationToken ct)
{
// 复用面板:若上一轮仍未决,先以默认值收口,避免句柄泄漏。
ResolvePending();
_tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
_ctReg = ct.CanBeCanceled ? ct.Register(ResolvePending) : default;
return _tcs.Task;
}
/// <summary>具体按钮回调:以 <paramref name="result"/> 完成并出栈自身。</summary>
protected void Complete(T result)
{
var tcs = _tcs;
if (tcs != null && tcs.TrySetResult(result))
{
_ctReg.Dispose();
GetService<IUINavigator>()?.Pop(); // 弹出自己(栈顶)
}
}
/// <summary>以取消默认值收口未决结果(幂等)。</summary>
private void ResolvePending()
{
var tcs = _tcs;
if (tcs != null && tcs.TrySetResult(CancelResult))
_ctReg.Dispose();
}
// 出栈(含 ESC 取消)会 SetActive(false) → OnDisable → OnPanelClose
// 场景卸载 / 销毁同样经 OnDisable。统一在此兜底覆盖所有提前结束路径。
protected override void OnPanelClose() => ResolvePending();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87869bd9c1e862141bff2eff5fcbaf51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
using UnityEngine;
using UnityEngine.SceneManagement;
namespace BaseGames.UI
{
/// <summary>
/// 导航栈中一层的记录。<see cref="FocusToRestore"/> 记录压栈前的选中项,
/// 出栈时据此恢复键盘 / 手柄焦点;<see cref="OwningScene"/> 用于场景卸载时清理本层。
/// </summary>
internal sealed class UIStackEntry
{
public UIPanelBase Panel;
public GameObject FocusToRestore;
public PushMode Mode;
public Scene OwningScene;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f4f0c943bfa478e4aa7b24aafb5192f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -10,7 +10,7 @@ namespace BaseGames.UI.Settings
/// <summary> /// <summary>
/// 数据驱动设置面板:据 <see cref="SettingsSchemaSO"/> 生成控件行并绑定 <see cref="ISettingsService"/>。 /// 数据驱动设置面板:据 <see cref="SettingsSchemaSO"/> 生成控件行并绑定 <see cref="ISettingsService"/>。
/// ///
/// 取代硬编码的 <see cref="BaseGames.UI.SettingsPanelController"/>:策划改表即可增删 / 重排 / 改标签 / 分节, /// 取代旧的硬编码设置面板控制器:策划改表即可增删 / 重排 / 改标签 / 分节,
/// 无需改代码。每行据 <see cref="SettingKey"/> 自动选用 Slider / Toggle / Dropdown 行预制件, /// 无需改代码。每行据 <see cref="SettingKey"/> 自动选用 Slider / Toggle / Dropdown 行预制件,
/// 并复用通用控件 <see cref="UISlider"/> / <see cref="UIDropdown"/>。 /// 并复用通用控件 <see cref="UISlider"/> / <see cref="UIDropdown"/>。
/// ///
@@ -73,10 +73,17 @@ namespace BaseGames.UI.Settings
} }
} }
private void Clear() /// <summary>清空已生成的行(容器全部子节点)。编辑器预览与运行时关闭共用(编辑期用 DestroyImmediate。</summary>
public void Clear()
{ {
foreach (var go in _spawned) if (go != null) Destroy(go);
_spawned.Clear(); _spawned.Clear();
if (_container == null) return;
for (int i = _container.childCount - 1; i >= 0; i--)
{
var child = _container.GetChild(i).gameObject;
if (Application.isPlaying) Destroy(child);
else DestroyImmediate(child);
}
} }
private GameObject PrefabFor(ControlKind kind) => kind switch private GameObject PrefabFor(ControlKind kind) => kind switch

View File

@@ -53,13 +53,9 @@ namespace BaseGames.UI
[SerializeField] private VoidEventChannelSO _onSpellSelectOpen; [SerializeField] private VoidEventChannelSO _onSpellSelectOpen;
[Tooltip("打开统一背包菜单InventoryHub。对应 EVT_InventoryOpen。")] [Tooltip("打开统一背包菜单InventoryHub。对应 EVT_InventoryOpen。")]
[SerializeField] private VoidEventChannelSO _onInventoryOpen; [SerializeField] private VoidEventChannelSO _onInventoryOpen;
[Tooltip("UI 取消操作ESC / 手柄 B·Circle全局关闭栈顶面板。对应 EVT_UICancelPressed。")]
[SerializeField] private VoidEventChannelSO _onUICancelPressed;
// ── 面板栈结构 ──────────────────────────────────────────────────────── // ── 面板栈:委托给统一的 UINavigator不再自管栈─────────────────────
private readonly Stack<GameObject> _panelStack = new(); private IUINavigator _navigator;
/// <summary>O(1) 成员判断,与 _panelStack 保持同步,替代 Stack.Contains O(n)。</summary>
private readonly HashSet<GameObject> _openPanelSet = new();
private readonly Dictionary<PanelId, GameObject> _panelRegistry = new(); private readonly Dictionary<PanelId, GameObject> _panelRegistry = new();
private readonly CompositeDisposable _subs = new(); private readonly CompositeDisposable _subs = new();
@@ -114,6 +110,7 @@ namespace BaseGames.UI
private void OnEnable() private void OnEnable()
{ {
ServiceLocator.Register<IUIManager>(this); ServiceLocator.Register<IUIManager>(this);
_navigator = ServiceLocator.GetOrDefault<IUINavigator>();
_onGameStateChanged?.Subscribe(HandleGameStateChanged).AddTo(_subs); _onGameStateChanged?.Subscribe(HandleGameStateChanged).AddTo(_subs);
_onPauseRequested?.Subscribe(TogglePause).AddTo(_subs); _onPauseRequested?.Subscribe(TogglePause).AddTo(_subs);
_onFastTravelOpen?.Subscribe(OpenMap).AddTo(_subs); _onFastTravelOpen?.Subscribe(OpenMap).AddTo(_subs);
@@ -122,7 +119,7 @@ namespace BaseGames.UI
_onCharmPanelOpen?.Subscribe(OpenCharmPanel).AddTo(_subs); _onCharmPanelOpen?.Subscribe(OpenCharmPanel).AddTo(_subs);
_onSpellSelectOpen?.Subscribe(OpenSpellSelect).AddTo(_subs); _onSpellSelectOpen?.Subscribe(OpenSpellSelect).AddTo(_subs);
_onInventoryOpen?.Subscribe(OpenInventory).AddTo(_subs); _onInventoryOpen?.Subscribe(OpenInventory).AddTo(_subs);
_onUICancelPressed?.Subscribe(HandleUICancelPressed).AddTo(_subs); // 取消键ESC / 手柄 B由 UINavigator 统一消费,UIManager 不再订阅。
} }
private void OnDisable() private void OnDisable()
@@ -239,46 +236,44 @@ namespace BaseGames.UI
}; };
} }
/// <summary>打开指定 GameObject 面板并压栈已在栈中则忽略O(1) 判断)。</summary> /// <summary>打开指定 GameObject 面板:经统一 <see cref="IUINavigator"/> 压栈。</summary>
public void OpenPanel(GameObject panel) public void OpenPanel(GameObject panel)
{ {
if (panel == null) return; if (panel == null) return;
if (!_openPanelSet.Add(panel)) return; var uiPanel = EnsurePanel(panel);
if (_panelStack.Count > 0) _panelStack.Peek().SetActive(false); if (uiPanel != null) Navigator?.Push(uiPanel);
panel.SetActive(true);
_panelStack.Push(panel);
} }
/// <summary>关闭栈顶面板并恢复上一层(如有);上一层若实现 IFocusable 则自动恢复焦点。</summary> /// <summary>关闭栈顶面板并恢复上一层(委托给导航器)。</summary>
public void CloseTopPanel() public void CloseTopPanel() => Navigator?.Pop();
/// <summary>
/// 适配器:保证面板根挂有 <see cref="UIPanelBase"/>(导航器压栈对象)。
/// 既有面板若未挂则运行时补 <see cref="UISimplePanel"/> + CanvasGroup
/// 使所有游戏内面板无需逐个改脚手架即可纳入统一导航栈。
/// </summary>
private static UIPanelBase EnsurePanel(GameObject go)
{ {
if (_panelStack.Count == 0) return; var p = go.GetComponent<UIPanelBase>();
var top = _panelStack.Pop(); if (p == null)
_openPanelSet.Remove(top);
top.SetActive(false);
if (_panelStack.Count > 0)
{ {
var restored = _panelStack.Peek(); if (go.GetComponent<CanvasGroup>() == null) go.AddComponent<CanvasGroup>();
restored.SetActive(true); p = go.AddComponent<UISimplePanel>();
restored.GetComponent<IFocusable>()?.OnFocusRestored();
} }
return p;
} }
// ── 快捷事件回调 ────────────────────────────────────────────────────── // ── 快捷事件回调 ──────────────────────────────────────────────────────
private void HandleUICancelPressed()
{
if (_panelStack.Count > 0)
CloseTopPanel();
}
private void TogglePause() private void TogglePause()
{ {
if (_panelRegistry.TryGetValue(PanelId.Pause, out var pausePanel) if (_panelRegistry.TryGetValue(PanelId.Pause, out var pausePanel)
&& _panelStack.Count > 0 && _panelStack.Peek() == pausePanel) && Navigator?.Top != null && Navigator.Top.gameObject == pausePanel)
CloseTopPanel(); Navigator.Pop();
else else
OpenPanel(PanelId.Pause); OpenPanel(PanelId.Pause);
} }
private IUINavigator Navigator => _navigator ??= ServiceLocator.GetOrDefault<IUINavigator>();
private void OpenShop(string _) => OpenPanel(PanelId.Shop); private void OpenShop(string _) => OpenPanel(PanelId.Shop);
private void OpenMap() => OpenPanel(PanelId.Map); private void OpenMap() => OpenPanel(PanelId.Map);
private void OpenCharmPanel() => OpenPanel(PanelId.CharmPanel); private void OpenCharmPanel() => OpenPanel(PanelId.CharmPanel);
@@ -335,8 +330,9 @@ namespace BaseGames.UI
} }
#if UNITY_EDITOR #if UNITY_EDITOR
/// <summary>仅供 UIManagerEditor 实时可视化面板栈(由栈顶到栈底顺序)。</summary> /// <summary>仅供 UIManagerEditor 实时可视化栈顶面板(栈本体已迁移至 UINavigator)。</summary>
public GameObject[] EditorGetPanelSnapshot() => _panelStack.ToArray(); public GameObject[] EditorGetPanelSnapshot()
=> Navigator?.Top != null ? new[] { Navigator.Top.gameObject } : System.Array.Empty<GameObject>();
#endif #endif
[ContextMenu("测试:打开 Pause 面板")] [ContextMenu("测试:打开 Pause 面板")]

View File

@@ -18,6 +18,10 @@ namespace BaseGames.UI
[DefaultExecutionOrder(100)] [DefaultExecutionOrder(100)]
public class UISelectionRestorer : MonoBehaviour public class UISelectionRestorer : MonoBehaviour
{ {
[Tooltip("始终保持选中:选中丢失(如鼠标点击空白处)即立即恢复,无需等待导航键。\n" +
"适合\"菜单始终有一项高亮\"的手感;关闭则仅在按下方向/确认键时恢复(避免抢占鼠标)。")]
[SerializeField] private bool _keepSelectionAlways = true;
private GameObject _lastSelected; private GameObject _lastSelected;
private void Update() private void Update()
@@ -32,8 +36,8 @@ namespace BaseGames.UI
return; return;
} }
// 选中已丢失:仅当出现导航/确认意图时恢复,避免抢占鼠标操作 // 选中已丢失:始终保持模式下立即恢复;否则仅在出现导航/确认意图时恢复
if (!NavigationIntentThisFrame(es)) return; if (!_keepSelectionAlways && !NavigationIntentThisFrame(es)) return;
if (_lastSelected == null || !_lastSelected.activeInHierarchy) return; if (_lastSelected == null || !_lastSelected.activeInHierarchy) return;
var sel = _lastSelected.GetComponent<Selectable>(); var sel = _lastSelected.GetComponent<Selectable>();

View File

@@ -98,7 +98,6 @@ namespace BaseGames.World
SceneName = _targetSceneAddress, SceneName = _targetSceneAddress,
EntryTransitionId = _targetTransitionId, EntryTransitionId = _targetTransitionId,
TransitionType = _transitionType, TransitionType = _transitionType,
ShowLoadingScreen = _transitionType == TransitionType.Scene,
IsRespawn = false, IsRespawn = false,
}; };

View File

@@ -141,7 +141,6 @@ namespace BaseGames.World.Map
SceneName = targetRoomId, // RoomId 即场景 Addressable key"Room_" 前缀,见 ISceneLoadCoordinator SceneName = targetRoomId, // RoomId 即场景 Addressable key"Room_" 前缀,见 ISceneLoadCoordinator
EntryTransitionId = null, // 默认出生点;精确落到传送站点为后续增强项 EntryTransitionId = null, // 默认出生点;精确落到传送站点为后续增强项
TransitionType = TransitionType.Scene, // 完整淡出 + 加载画面,符合快速旅行演出 TransitionType = TransitionType.Scene, // 完整淡出 + 加载画面,符合快速旅行演出
ShowLoadingScreen = true,
IsRespawn = false, IsRespawn = false,
}); });
} }

View File

@@ -69,7 +69,6 @@ namespace BaseGames.World
SceneName = _targetSceneAddress, SceneName = _targetSceneAddress,
EntryTransitionId = _targetTransitionId, EntryTransitionId = _targetTransitionId,
TransitionType = _transitionType, TransitionType = _transitionType,
ShowLoadingScreen = _transitionType == TransitionType.Scene,
IsRespawn = false, IsRespawn = false,
}; };

View File

@@ -623,15 +623,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
""interactions"": """", ""interactions"": """",
""initialStateCheck"": false ""initialStateCheck"": false
}, },
{
""name"": ""Pause"",
""type"": ""Button"",
""id"": ""2d22c992-df5f-4d63-8a35-42a1464f7ff3"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{ {
""name"": ""Submit"", ""name"": ""Submit"",
""type"": ""Button"", ""type"": ""Button"",
@@ -645,7 +636,7 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
""name"": ""Cancel"", ""name"": ""Cancel"",
""type"": ""Button"", ""type"": ""Button"",
""id"": ""b304717b-ed90-4179-8543-fac11a253476"", ""id"": ""b304717b-ed90-4179-8543-fac11a253476"",
""expectedControlType"": ""Button"", ""expectedControlType"": """",
""processors"": """", ""processors"": """",
""interactions"": """", ""interactions"": """",
""initialStateCheck"": false ""initialStateCheck"": false
@@ -1015,17 +1006,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
""isComposite"": false, ""isComposite"": false,
""isPartOfComposite"": true ""isPartOfComposite"": true
}, },
{
""name"": """",
""id"": ""5c3030c2-489b-43cc-92c3-c5eed849affa"",
""path"": ""<Keyboard>/escape"",
""interactions"": """",
""processors"": """",
""groups"": ""Keyboard&Mouse"",
""action"": ""Pause"",
""isComposite"": false,
""isPartOfComposite"": false
},
{ {
""name"": """", ""name"": """",
""id"": ""5632ce4b-cb65-4d5a-a133-262727b68139"", ""id"": ""5632ce4b-cb65-4d5a-a133-262727b68139"",
@@ -1033,7 +1013,7 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
""interactions"": """", ""interactions"": """",
""processors"": """", ""processors"": """",
""groups"": ""Gamepad"", ""groups"": ""Gamepad"",
""action"": ""Pause"", ""action"": ""Cancel"",
""isComposite"": false, ""isComposite"": false,
""isPartOfComposite"": false ""isPartOfComposite"": false
}, },
@@ -1051,7 +1031,7 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
{ {
""name"": """", ""name"": """",
""id"": ""e6b74122-53a6-4ad1-85fb-dce8de71bf51"", ""id"": ""e6b74122-53a6-4ad1-85fb-dce8de71bf51"",
""path"": ""*/{Cancel}"", ""path"": ""<Keyboard>/escape"",
""interactions"": """", ""interactions"": """",
""processors"": """", ""processors"": """",
""groups"": ""Keyboard&Mouse;Gamepad;Touch;Joystick;XR"", ""groups"": ""Keyboard&Mouse;Gamepad;Touch;Joystick;XR"",
@@ -1335,7 +1315,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
// UI // UI
m_UI = asset.FindActionMap("UI", throwIfNotFound: true); m_UI = asset.FindActionMap("UI", throwIfNotFound: true);
m_UI_Navigate = m_UI.FindAction("Navigate", throwIfNotFound: true); m_UI_Navigate = m_UI.FindAction("Navigate", throwIfNotFound: true);
m_UI_Pause = m_UI.FindAction("Pause", throwIfNotFound: true);
m_UI_Submit = m_UI.FindAction("Submit", throwIfNotFound: true); m_UI_Submit = m_UI.FindAction("Submit", throwIfNotFound: true);
m_UI_Cancel = m_UI.FindAction("Cancel", throwIfNotFound: true); m_UI_Cancel = m_UI.FindAction("Cancel", throwIfNotFound: true);
m_UI_Point = m_UI.FindAction("Point", throwIfNotFound: true); m_UI_Point = m_UI.FindAction("Point", throwIfNotFound: true);
@@ -1714,7 +1693,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
private readonly InputActionMap m_UI; private readonly InputActionMap m_UI;
private List<IUIActions> m_UIActionsCallbackInterfaces = new List<IUIActions>(); private List<IUIActions> m_UIActionsCallbackInterfaces = new List<IUIActions>();
private readonly InputAction m_UI_Navigate; private readonly InputAction m_UI_Navigate;
private readonly InputAction m_UI_Pause;
private readonly InputAction m_UI_Submit; private readonly InputAction m_UI_Submit;
private readonly InputAction m_UI_Cancel; private readonly InputAction m_UI_Cancel;
private readonly InputAction m_UI_Point; private readonly InputAction m_UI_Point;
@@ -1744,10 +1722,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
/// </summary> /// </summary>
public InputAction @Navigate => m_Wrapper.m_UI_Navigate; public InputAction @Navigate => m_Wrapper.m_UI_Navigate;
/// <summary> /// <summary>
/// Provides access to the underlying input action "UI/Pause".
/// </summary>
public InputAction @Pause => m_Wrapper.m_UI_Pause;
/// <summary>
/// Provides access to the underlying input action "UI/Submit". /// Provides access to the underlying input action "UI/Submit".
/// </summary> /// </summary>
public InputAction @Submit => m_Wrapper.m_UI_Submit; public InputAction @Submit => m_Wrapper.m_UI_Submit;
@@ -1828,9 +1802,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
@Navigate.started += instance.OnNavigate; @Navigate.started += instance.OnNavigate;
@Navigate.performed += instance.OnNavigate; @Navigate.performed += instance.OnNavigate;
@Navigate.canceled += instance.OnNavigate; @Navigate.canceled += instance.OnNavigate;
@Pause.started += instance.OnPause;
@Pause.performed += instance.OnPause;
@Pause.canceled += instance.OnPause;
@Submit.started += instance.OnSubmit; @Submit.started += instance.OnSubmit;
@Submit.performed += instance.OnSubmit; @Submit.performed += instance.OnSubmit;
@Submit.canceled += instance.OnSubmit; @Submit.canceled += instance.OnSubmit;
@@ -1884,9 +1855,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
@Navigate.started -= instance.OnNavigate; @Navigate.started -= instance.OnNavigate;
@Navigate.performed -= instance.OnNavigate; @Navigate.performed -= instance.OnNavigate;
@Navigate.canceled -= instance.OnNavigate; @Navigate.canceled -= instance.OnNavigate;
@Pause.started -= instance.OnPause;
@Pause.performed -= instance.OnPause;
@Pause.canceled -= instance.OnPause;
@Submit.started -= instance.OnSubmit; @Submit.started -= instance.OnSubmit;
@Submit.performed -= instance.OnSubmit; @Submit.performed -= instance.OnSubmit;
@Submit.canceled -= instance.OnSubmit; @Submit.canceled -= instance.OnSubmit;
@@ -2134,13 +2102,6 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" /> /// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
void OnNavigate(InputAction.CallbackContext context); void OnNavigate(InputAction.CallbackContext context);
/// <summary> /// <summary>
/// Method invoked when associated input action "Pause" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
/// </summary>
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
void OnPause(InputAction.CallbackContext context);
/// <summary>
/// Method invoked when associated input action "Submit" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />. /// Method invoked when associated input action "Submit" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
/// </summary> /// </summary>
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" /> /// <seealso cref="UnityEngine.InputSystem.InputAction.started" />

View File

@@ -537,15 +537,6 @@
"interactions": "", "interactions": "",
"initialStateCheck": false "initialStateCheck": false
}, },
{
"name": "Pause",
"type": "Button",
"id": "2d22c992-df5f-4d63-8a35-42a1464f7ff3",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{ {
"name": "Submit", "name": "Submit",
"type": "Button", "type": "Button",
@@ -559,7 +550,7 @@
"name": "Cancel", "name": "Cancel",
"type": "Button", "type": "Button",
"id": "b304717b-ed90-4179-8543-fac11a253476", "id": "b304717b-ed90-4179-8543-fac11a253476",
"expectedControlType": "Button", "expectedControlType": "",
"processors": "", "processors": "",
"interactions": "", "interactions": "",
"initialStateCheck": false "initialStateCheck": false
@@ -929,17 +920,6 @@
"isComposite": false, "isComposite": false,
"isPartOfComposite": true "isPartOfComposite": true
}, },
{
"name": "",
"id": "5c3030c2-489b-43cc-92c3-c5eed849affa",
"path": "<Keyboard>/escape",
"interactions": "",
"processors": "",
"groups": "Keyboard&Mouse",
"action": "Pause",
"isComposite": false,
"isPartOfComposite": false
},
{ {
"name": "", "name": "",
"id": "5632ce4b-cb65-4d5a-a133-262727b68139", "id": "5632ce4b-cb65-4d5a-a133-262727b68139",
@@ -947,7 +927,7 @@
"interactions": "", "interactions": "",
"processors": "", "processors": "",
"groups": "Gamepad", "groups": "Gamepad",
"action": "Pause", "action": "Cancel",
"isComposite": false, "isComposite": false,
"isPartOfComposite": false "isPartOfComposite": false
}, },
@@ -965,7 +945,7 @@
{ {
"name": "", "name": "",
"id": "e6b74122-53a6-4ad1-85fb-dce8de71bf51", "id": "e6b74122-53a6-4ad1-85fb-dce8de71bf51",
"path": "*/{Cancel}", "path": "<Keyboard>/escape",
"interactions": "", "interactions": "",
"processors": "", "processors": "",
"groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR",