added WinBannerController and LoseBannerController, implemented game end UI logic
This commit is contained in:
parent
dafd317242
commit
dc18ca9ff3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
184
Assets/Prefabs/Utilities/Messages/MessageBubble_Custom_1.prefab
Normal file
184
Assets/Prefabs/Utilities/Messages/MessageBubble_Custom_1.prefab
Normal file
@ -0,0 +1,184 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1001 &8454563988256824744
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2735431105283990082, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: actions.Array.size
|
||||
value: 4
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'actions.Array.data[0]'
|
||||
value: 1880696228193501281
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'actions.Array.data[1]'
|
||||
value: 1880696228193501282
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'actions.Array.data[2]'
|
||||
value: 1880696228193501283
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'actions.Array.data[3]'
|
||||
value: 1880696228193501284
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'managedReferences[-2]'
|
||||
value:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'managedReferences[1880696228193501280]'
|
||||
value: Assembly-CSharp RR.Common.Behaviour.FreezePlayerAction
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'managedReferences[1880696228193501281]'
|
||||
value: Assembly-CSharp RR.Common.Behaviour.MessageAppearAction
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'managedReferences[1880696228193501282]'
|
||||
value: Assembly-CSharp RR.Common.Behaviour.WaitAction
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'managedReferences[1880696228193501283]'
|
||||
value: Assembly-CSharp RR.Common.Behaviour.MessageDisappearAction
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: 'managedReferences[1880696228193501284]'
|
||||
value: Assembly-CSharp RR.Common.Behaviour.DeleteGameObjectAction
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501281].endScale
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501282].duration
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501283].endScale
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501280].freezeMode
|
||||
value: 7
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501281].startScale
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501283].startScale
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501284].targetObject
|
||||
value:
|
||||
objectReference: {fileID: 4826131747682954655}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501281].transitionSpeed
|
||||
value: 5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3905633718102506837, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: managedReferences[1880696228193501283].transitionSpeed
|
||||
value: 3
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4011995222962286647, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: MessageBubble_Custom_1
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
--- !u!1 &4826131747682954655 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 4011995222962286647, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
m_PrefabInstance: {fileID: 8454563988256824744}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88d5927bc948ef94f89df153ca3ad9da
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1180,12 +1180,19 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 294888496}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c815924369a0a64458e3c85a564de8b8, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: 8e0feede366043ef951637fa0f816177, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.ModalFocusTrap
|
||||
modalRoot: {fileID: 294888496}
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.WinBannerController
|
||||
firstSelected: {fileID: 1256436981}
|
||||
enforce: 1
|
||||
blockBackground: 1
|
||||
enforceFocus: 1
|
||||
canvasGroup: {fileID: 294888500}
|
||||
fadeDuration: 0.15
|
||||
closeAction: {fileID: 0}
|
||||
closeButton: {fileID: 0}
|
||||
winText: {fileID: 630106233}
|
||||
mainMenuButton: {fileID: 1256436981}
|
||||
qrCodesContainer: {fileID: 954402733}
|
||||
--- !u!1 &322757142
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -2712,7 +2719,6 @@ GameObject:
|
||||
- component: {fileID: 1069927039}
|
||||
- component: {fileID: 1069927042}
|
||||
- component: {fileID: 1069927041}
|
||||
- component: {fileID: 1069927043}
|
||||
m_Layer: 5
|
||||
m_Name: LoseBanner
|
||||
m_TagString: Untagged
|
||||
@ -2793,7 +2799,13 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 7fd0f5dd52ebd7f41a46cb02a7f83497, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: '::'
|
||||
bannersController: {fileID: 1724863673}
|
||||
firstSelected: {fileID: 1871409157}
|
||||
blockBackground: 1
|
||||
enforceFocus: 1
|
||||
canvasGroup: {fileID: 1069927042}
|
||||
fadeDuration: 0.15
|
||||
closeAction: {fileID: 0}
|
||||
closeButton: {fileID: 0}
|
||||
loseText: {fileID: 1131560888}
|
||||
creditText: {fileID: 322757144}
|
||||
continueButton: {fileID: 1871409157}
|
||||
@ -2813,21 +2825,6 @@ CanvasGroup:
|
||||
m_Interactable: 1
|
||||
m_BlocksRaycasts: 1
|
||||
m_IgnoreParentGroups: 0
|
||||
--- !u!114 &1069927043
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1069927037}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c815924369a0a64458e3c85a564de8b8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.ModalFocusTrap
|
||||
modalRoot: {fileID: 1069927037}
|
||||
firstSelected: {fileID: 1871409157}
|
||||
enforce: 1
|
||||
--- !u!1 &1131560886
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -3860,6 +3857,20 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1527626192}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1573983874
|
||||
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: fc1515ab76e54f068e2f2207940fab32, type: 3}
|
||||
m_Name: UI/Cancel
|
||||
m_EditorClassIdentifier: Unity.InputSystem::UnityEngine.InputSystem.InputActionReference
|
||||
m_Asset: {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
m_ActionId: ab443aed-9e90-4080-8896-92640a5b6838
|
||||
--- !u!1 &1579229932
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -4136,7 +4147,6 @@ GameObject:
|
||||
- component: {fileID: 1724863672}
|
||||
- component: {fileID: 1724863676}
|
||||
- component: {fileID: 1724863675}
|
||||
- component: {fileID: 1724863674}
|
||||
- component: {fileID: 1724863673}
|
||||
m_Layer: 5
|
||||
m_Name: GameEndPanel
|
||||
@ -4178,22 +4188,10 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 1b61ec11c32d8aa42b00ee29ce87def2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: '::'
|
||||
winPanel: {fileID: 294888500}
|
||||
mainMenuButtonWin: {fileID: 1256436981}
|
||||
winText: {fileID: 630106233}
|
||||
losePanel: {fileID: 1069927042}
|
||||
--- !u!225 &1724863674
|
||||
CanvasGroup:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1724863671}
|
||||
m_Enabled: 1
|
||||
m_Alpha: 1
|
||||
m_Interactable: 1
|
||||
m_BlocksRaycasts: 1
|
||||
m_IgnoreParentGroups: 0
|
||||
winBanner: {fileID: 294888501}
|
||||
loseBanner: {fileID: 1069927041}
|
||||
backgroundOverlay: {fileID: 1724863675}
|
||||
overlayColor: {r: 0, g: 0, b: 0, a: 0.39215687}
|
||||
--- !u!114 &1724863675
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -4580,8 +4578,14 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 55d821bd8bf8bb14c857f497ec6f2a0d, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
close: {fileID: 5054652661136980948, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
panel: {fileID: 1946308725}
|
||||
firstSelected: {fileID: 1065887711}
|
||||
blockBackground: 1
|
||||
enforceFocus: 1
|
||||
canvasGroup: {fileID: 1946308725}
|
||||
fadeDuration: 0.15
|
||||
closeAction: {fileID: 1573983874}
|
||||
closeButton: {fileID: 1065887711}
|
||||
pauseToggle: {fileID: 2080140806}
|
||||
resumeButton: {fileID: 1065887711}
|
||||
quitButton: {fileID: 1294478665}
|
||||
bannerController: {fileID: 1724863673}
|
||||
@ -4845,6 +4849,20 @@ MonoBehaviour:
|
||||
livesText: {fileID: 250473979}
|
||||
coinsText: {fileID: 122148826}
|
||||
levelText: {fileID: 1162184471}
|
||||
--- !u!114 &2080140806
|
||||
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: fc1515ab76e54f068e2f2207940fab32, type: 3}
|
||||
m_Name: UI/Cancel
|
||||
m_EditorClassIdentifier: Unity.InputSystem::UnityEngine.InputSystem.InputActionReference
|
||||
m_Asset: {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
m_ActionId: ab443aed-9e90-4080-8896-92640a5b6838
|
||||
--- !u!1 &2082762285
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -193714,8 +193714,7 @@ MonoBehaviour:
|
||||
- rid: 1880696163501343116
|
||||
isOneTime: 1
|
||||
messagePlaceholderPrefab: {fileID: 230978959095769727, guid: 0f453bdec7ad567458080bacb277b1c0, type: 3}
|
||||
messageBubblePrefab: {fileID: 4011995222962286647, guid: 969225a5614fedf41878291f57bdeb88, type: 3}
|
||||
defaultMessageOffset: {x: 0, y: 0.5, z: 0}
|
||||
messageBubblePrefab: {fileID: 4826131747682954655, guid: 88d5927bc948ef94f89df153ca3ad9da, type: 3}
|
||||
references:
|
||||
version: 2
|
||||
RefIds:
|
||||
@ -193724,130 +193723,118 @@ MonoBehaviour:
|
||||
data:
|
||||
text: "\u041F\u0440\u0438\u0432\u0435\u0442! \u0422\u044B \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0448\u044C
|
||||
\u043C\u043D\u0435?"
|
||||
target: {fileID: 2072794048}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: -3550973283731103874, guid: 4db2fbc7e829aec46b75cceb207881b9, type: 3}
|
||||
- rid: 1880696163501343106
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "... \u041F\u0440\u0438\u0432\u0435\u0442"
|
||||
target: {fileID: 0}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: 420805981189327916, guid: 8526ea9be1b0a56429731ec9f3edf875, type: 3}
|
||||
- rid: 1880696163501343107
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u041C\u043E\u0436\u0435\u0442, \u0441\u043D\u0430\u0447\u0430\u043B\u0430
|
||||
\u0441\u043A\u0430\u0436\u0435\u0448\u044C, \u043A\u0430\u043A \u0442\u044B
|
||||
\u0440\u0430\u0437\u0433\u043E\u0432\u0430\u0440\u0438\u0432\u0430\u0435\u0448\u044C?"
|
||||
target: {fileID: 0}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: 420805981189327916, guid: 8526ea9be1b0a56429731ec9f3edf875, type: 3}
|
||||
- rid: 1880696163501343108
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u041C\u0435\u043D\u044F \u0437\u043E\u0432\u0443\u0442 \u0422\u0430\u043C\u0430
|
||||
\u0413\u043E\u0447\u0438\u043D\u0433"
|
||||
target: {fileID: 2072794048}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: -3550973283731103874, guid: 4db2fbc7e829aec46b75cceb207881b9, type: 3}
|
||||
- rid: 1880696163501343109
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u042F \u043E\u0447\u0435\u043D\u044C \u0443\u043C\u043D\u044B\u0439
|
||||
\u0440\u043E\u0431\u043E\u0442-\u0442\u0430\u043C\u0430\u0433\u043E\u0447\u0438"
|
||||
target: {fileID: 2072794048}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: -3550973283731103874, guid: 4db2fbc7e829aec46b75cceb207881b9, type: 3}
|
||||
- rid: 1880696163501343110
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u0410 \u0440\u0430\u0437\u0433\u043E\u0432\u0430\u0440\u0438\u0432\u0430\u044E
|
||||
\u043F\u0440\u0438 \u043F\u043E\u043C\u043E\u0449\u0438 \u0434\u0438\u043D\u0430\u043C\u0438\u043A\u043E\u0432
|
||||
\u0432\u043D\u0443\u0442\u0440\u0438"
|
||||
target: {fileID: 2072794048}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: -3550973283731103874, guid: 4db2fbc7e829aec46b75cceb207881b9, type: 3}
|
||||
- rid: 1880696163501343111
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u041C\u0435\u043D\u044F \u0437\u043E\u0432\u0443\u0442 \u0410\u043B\u0438\u0441\u0430.
|
||||
\u042F \u043F\u043E\u043C\u043E\u0433\u0443 \u0442\u0435\u0431\u0435!"
|
||||
target: {fileID: 0}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: 420805981189327916, guid: 8526ea9be1b0a56429731ec9f3edf875, type: 3}
|
||||
- rid: 1880696163501343112
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u041D\u043E \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0441\u043A\u0430\u0436\u0438,
|
||||
\u0447\u0442\u043E \u043F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u043E?"
|
||||
target: {fileID: 0}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: 420805981189327916, guid: 8526ea9be1b0a56429731ec9f3edf875, type: 3}
|
||||
- rid: 1880696163501343113
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u041F\u0430\u0441\u043A\u043E\u0441\u0442\u043D\u0438\u043A \u0414\u0430\u043D\u0440\u043E
|
||||
\u0441\u043B\u043E\u043C\u0430\u043B \u043C\u043E\u0439 \u0430\u0432\u0442\u043E\u043C\u0430\u0442!"
|
||||
target: {fileID: 2072794048}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: -3550973283731103874, guid: 4db2fbc7e829aec46b75cceb207881b9, type: 3}
|
||||
- rid: 1880696163501343114
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u0410 \u044F \u0432\u044B\u043F\u0430\u043B \u0438\u0437 \u043D\u0435\u0433\u043E,
|
||||
\u0438 \u043F\u043E\u0442\u0435\u0440\u044F\u043B\u0441\u044F..."
|
||||
target: {fileID: 2072794048}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: -3550973283731103874, guid: 4db2fbc7e829aec46b75cceb207881b9, type: 3}
|
||||
- rid: 1880696163501343115
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u041D\u0443 \u0442\u0430\u043A \u0447\u0442\u043E, \u043F\u043E\u043C\u043E\u0436\u0435\u0448\u044C
|
||||
\u043C\u043D\u0435?"
|
||||
target: {fileID: 2072794048}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: -3550973283731103874, guid: 4db2fbc7e829aec46b75cceb207881b9, type: 3}
|
||||
- rid: 1880696163501343116
|
||||
type: {class: AddMessageAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
text: "\u0414\u0430! \u0414\u0430\u0432\u0430\u0439 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043C
|
||||
\u0435\u0433\u043E!"
|
||||
target: {fileID: 0}
|
||||
offset: {x: 0, y: 0, z: 0}
|
||||
speakerIcon: {fileID: 420805981189327916, guid: 8526ea9be1b0a56429731ec9f3edf875, type: 3}
|
||||
- rid: 1880696163501343124
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 2
|
||||
duration: 3
|
||||
- rid: 1880696163501343125
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1
|
||||
duration: 3
|
||||
- rid: 1880696163501343126
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 2
|
||||
duration: 3
|
||||
- rid: 1880696163501343127
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1
|
||||
duration: 3
|
||||
- rid: 1880696163501343128
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1
|
||||
duration: 3
|
||||
- rid: 1880696163501343129
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 2
|
||||
duration: 3
|
||||
- rid: 1880696163501343131
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1
|
||||
duration: 3
|
||||
- rid: 1880696163501343132
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1
|
||||
duration: 3
|
||||
- rid: 1880696163501343133
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1
|
||||
duration: 3
|
||||
- rid: 1880696163501343134
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1
|
||||
duration: 3
|
||||
- rid: 1880696163501343135
|
||||
type: {class: WaitAction, ns: RR.Common.Behaviour, asm: Assembly-CSharp}
|
||||
data:
|
||||
duration: 1.5
|
||||
duration: 4
|
||||
--- !u!61 &1094121057
|
||||
BoxCollider2D:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -390772,11 +390759,6 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
|
||||
m_PrefabInstance: {fileID: 2070698805}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &2072794048 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 5993946430821065449, guid: 7fd3a37dc702ece42b51e5867473f0ff, type: 3}
|
||||
m_PrefabInstance: {fileID: 5189102261111869253}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &2073324771
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -207,7 +207,6 @@ GameObject:
|
||||
- component: {fileID: 28542764}
|
||||
- component: {fileID: 28542767}
|
||||
- component: {fileID: 28542766}
|
||||
- component: {fileID: 28542768}
|
||||
m_Layer: 5
|
||||
m_Name: LevelSelectModal
|
||||
m_TagString: Untagged
|
||||
@ -285,12 +284,15 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 5b96039a6a73f764295ca4b3de5b5318, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
close: {fileID: 5054652661136980948, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
firstSelected: {fileID: 1846005930}
|
||||
blockBackground: 1
|
||||
enforceFocus: 1
|
||||
canvasGroup: {fileID: 28542767}
|
||||
fadeDuration: 0.15
|
||||
closeAction: {fileID: 0}
|
||||
closeButton: {fileID: 1846005930}
|
||||
content: {fileID: 1792218763}
|
||||
levelButtonPrefab: {fileID: -2647512120749192222, guid: 292805c897bde6f4fb6d6cfb3010ae3a, type: 3}
|
||||
closeButton: {fileID: 1846005930}
|
||||
fadeDuration: 0.15
|
||||
--- !u!225 &28542767
|
||||
CanvasGroup:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -303,21 +305,6 @@ CanvasGroup:
|
||||
m_Interactable: 1
|
||||
m_BlocksRaycasts: 1
|
||||
m_IgnoreParentGroups: 0
|
||||
--- !u!114 &28542768
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 28542762}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c815924369a0a64458e3c85a564de8b8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.ModalFocusTrap
|
||||
modalRoot: {fileID: 28542762}
|
||||
firstSelected: {fileID: 1846005930}
|
||||
enforce: 1
|
||||
--- !u!1 &37257833
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -9123,7 +9110,6 @@ GameObject:
|
||||
- component: {fileID: 1460883872}
|
||||
- component: {fileID: 1460883871}
|
||||
- component: {fileID: 1460883870}
|
||||
- component: {fileID: 1460883869}
|
||||
- component: {fileID: 1460883868}
|
||||
m_Layer: 5
|
||||
m_Name: AdminStatsModal
|
||||
@ -9165,6 +9151,13 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: df89ed1d48bc36140864491f04301407, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: '::'
|
||||
firstSelected: {fileID: 683903128}
|
||||
blockBackground: 1
|
||||
enforceFocus: 1
|
||||
canvasGroup: {fileID: 1460883870}
|
||||
fadeDuration: 0.15
|
||||
closeAction: {fileID: 0}
|
||||
closeButton: {fileID: 683903128}
|
||||
gamesPlayedTextPrefix: "\u0418\u0433\u0440 \u0441\u044B\u0433\u0440\u0430\u043D\u043E: "
|
||||
gamesPlayedText: {fileID: 820785317}
|
||||
gamesPlayedNulTextPrefix: "\u0418\u0433\u0440 \u0441\u044B\u0433\u0440\u0430\u043D\u043E
|
||||
@ -9177,24 +9170,6 @@ MonoBehaviour:
|
||||
prizesGivenNulText: {fileID: 674695656}
|
||||
resetGamesPlayedNulButton: {fileID: 728958718}
|
||||
resetPrizesGivenNulButton: {fileID: 804461262}
|
||||
closeButton: {fileID: 683903128}
|
||||
canvasGroup: {fileID: 1460883870}
|
||||
fadeDuration: 0.15
|
||||
--- !u!114 &1460883869
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1460883866}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c815924369a0a64458e3c85a564de8b8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.ModalFocusTrap
|
||||
modalRoot: {fileID: 1412406033}
|
||||
firstSelected: {fileID: 683903128}
|
||||
enforce: 1
|
||||
--- !u!225 &1460883870
|
||||
CanvasGroup:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -9480,7 +9455,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.132, y: 0.5}
|
||||
m_AnchorMax: {x: 0.846, y: 0.77500004}
|
||||
m_AnchoredPosition: {x: -54.50001, y: 5}
|
||||
m_AnchoredPosition: {x: -54.500015, y: 5}
|
||||
m_SizeDelta: {x: -107, y: -10}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1474919456
|
||||
@ -11939,7 +11914,7 @@ RectTransform:
|
||||
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: -0.000019041576}
|
||||
m_AnchoredPosition: {x: 0, y: 0.00088984595}
|
||||
m_SizeDelta: {x: 0, y: 300}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &1792218764
|
||||
@ -13085,7 +13060,6 @@ GameObject:
|
||||
- component: {fileID: 1932206569}
|
||||
- component: {fileID: 1932206571}
|
||||
- component: {fileID: 1932206570}
|
||||
- component: {fileID: 1932206572}
|
||||
- component: {fileID: 1932206575}
|
||||
- component: {fileID: 1932206573}
|
||||
m_Layer: 5
|
||||
@ -13153,21 +13127,6 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1932206568}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1932206572
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1932206568}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c815924369a0a64458e3c85a564de8b8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.ModalFocusTrap
|
||||
modalRoot: {fileID: 1932206568}
|
||||
firstSelected: {fileID: 917079365}
|
||||
enforce: 1
|
||||
--- !u!114 &1932206573
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -13180,11 +13139,16 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 85d4ced96061fd14592d8a7f7fb824de, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.SettingsUI
|
||||
firstSelected: {fileID: 917079365}
|
||||
blockBackground: 1
|
||||
enforceFocus: 1
|
||||
canvasGroup: {fileID: 1932206575}
|
||||
fadeDuration: 0.15
|
||||
closeAction: {fileID: 0}
|
||||
closeButton: {fileID: 0}
|
||||
triggerKey: {fileID: -3077985071382975143, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
saveButton: {fileID: 917079365}
|
||||
canvasGroup: {fileID: 1932206575}
|
||||
content: {fileID: 1537614489}
|
||||
fadeDuration: 0.15
|
||||
--- !u!225 &1932206575
|
||||
CanvasGroup:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -13317,7 +13281,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.132, y: 0.21750003}
|
||||
m_AnchorMax: {x: 0.846, y: 0.5}
|
||||
m_AnchoredPosition: {x: -54.50001, y: -79}
|
||||
m_AnchoredPosition: {x: -54.500015, y: -79}
|
||||
m_SizeDelta: {x: -107, y: -63}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1959311160
|
||||
|
||||
@ -1,142 +1,100 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using RR.Common.Controls;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
using RR.Common.Controls;
|
||||
|
||||
public class AdminStatsWindowUIController : MonoBehaviour
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
[Header("Тексты статистики")]
|
||||
[SerializeField] private string gamesPlayedTextPrefix = "Игр сыграно: ";
|
||||
[SerializeField] private TextMeshProUGUI gamesPlayedText;
|
||||
[SerializeField] private string gamesPlayedNulTextPrefix = "Игр сыграно (обнуляемый): ";
|
||||
[SerializeField] private TextMeshProUGUI gamesPlayedNulText;
|
||||
[SerializeField] private string prizesGivenTextPrefix = "Призов выдано: ";
|
||||
[SerializeField] private TextMeshProUGUI prizesGivenText;
|
||||
[SerializeField] private string prizesGivenNulTextPrefix = "Призов выдано (обнуляемый): ";
|
||||
[SerializeField] private TextMeshProUGUI prizesGivenNulText;
|
||||
|
||||
[Header("Кнопки")]
|
||||
[SerializeField] private Button resetGamesPlayedNulButton;
|
||||
[SerializeField] private Button resetPrizesGivenNulButton;
|
||||
[SerializeField] private Button closeButton;
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
[SerializeField] private float fadeDuration = 0.15f;
|
||||
|
||||
private string _gamesPlayedKey;
|
||||
private string _gamesPlayedNulKey;
|
||||
private string _prizesGivenKey;
|
||||
private string _prizesGivenNulKey;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_gamesPlayedKey = PrizeSystem.key_starts;
|
||||
_gamesPlayedNulKey = PrizeSystem.key_startsNul;
|
||||
_prizesGivenKey = PrizeSystem.key_prizesgiven;
|
||||
_prizesGivenNulKey = PrizeSystem.key_prizesgivenNul;
|
||||
|
||||
if (resetGamesPlayedNulButton) resetGamesPlayedNulButton.onClick.AddListener(() => ResetPlayerPrefsStat(_gamesPlayedNulKey));
|
||||
|
||||
if (resetPrizesGivenNulButton) resetPrizesGivenNulButton.onClick.AddListener( () => ResetPlayerPrefsStat(_prizesGivenNulKey));
|
||||
|
||||
if (closeButton) closeButton.onClick.AddListener(Toggle);
|
||||
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (resetGamesPlayedNulButton) resetGamesPlayedNulButton.onClick.RemoveListener(() => ResetPlayerPrefsStat(_gamesPlayedNulKey));
|
||||
|
||||
if (resetPrizesGivenNulButton) resetPrizesGivenNulButton.onClick.RemoveListener( () => ResetPlayerPrefsStat(_prizesGivenNulKey));
|
||||
|
||||
if (closeButton) closeButton.onClick.RemoveListener(Toggle);
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
if (gameObject.activeSelf)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
Open();
|
||||
}
|
||||
}
|
||||
|
||||
private void Open()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
UpdateText();
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(0f, 1f, fadeDuration));
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(1f, 0f, fadeDuration, () => { gameObject.SetActive(false); }));
|
||||
}
|
||||
|
||||
private void UpdateText()
|
||||
{
|
||||
if (gamesPlayedText)
|
||||
{
|
||||
int gamesPlayed = PlayerPrefs.GetInt(_gamesPlayedKey, 0);
|
||||
gamesPlayedText.text = gamesPlayedTextPrefix + gamesPlayed;
|
||||
}
|
||||
|
||||
if (gamesPlayedNulText)
|
||||
{
|
||||
int gamesPlayedNul = PlayerPrefs.GetInt(_gamesPlayedNulKey, 0);
|
||||
gamesPlayedNulText.text = gamesPlayedNulTextPrefix + gamesPlayedNul;
|
||||
}
|
||||
|
||||
if (prizesGivenText)
|
||||
{
|
||||
int prizesGiven = PlayerPrefs.GetInt(_prizesGivenKey, 0);
|
||||
prizesGivenText.text = prizesGivenTextPrefix + prizesGiven;
|
||||
}
|
||||
|
||||
if (prizesGivenNulText)
|
||||
{
|
||||
int prizesGivenNul = PlayerPrefs.GetInt(_prizesGivenNulKey, 0);
|
||||
prizesGivenNulText.text = prizesGivenNulTextPrefix + prizesGivenNul;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPlayerPrefsStat(string key)
|
||||
{
|
||||
PlayerPrefs.SetInt(key, 0);
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для плавного перехода (затухания)
|
||||
/// Модальное окно статистики администратора.
|
||||
/// Отображает статистику игр и призов с возможностью сброса.
|
||||
/// </summary>
|
||||
/// <param name="from">Начальная прозрачность</param>
|
||||
/// <param name="to">Конечная прозрачность</param>
|
||||
/// <param name="time">Время перехода</param>
|
||||
/// <param name="onDone">Действие при выполнении перехода</param>
|
||||
private IEnumerator Fade(float from, float to, float time, System.Action onDone = null)
|
||||
public class AdminStatsWindowUIController : Modal
|
||||
{
|
||||
if (!canvasGroup) { onDone?.Invoke(); yield break; }
|
||||
float t = 0f;
|
||||
canvasGroup.alpha = from;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
while (t < time)
|
||||
[Header("Тексты статистики")]
|
||||
[SerializeField] private string gamesPlayedTextPrefix = "Игр сыграно: ";
|
||||
[SerializeField] private TextMeshProUGUI gamesPlayedText;
|
||||
[SerializeField] private string gamesPlayedNulTextPrefix = "Игр сыграно (обнуляемый): ";
|
||||
[SerializeField] private TextMeshProUGUI gamesPlayedNulText;
|
||||
[SerializeField] private string prizesGivenTextPrefix = "Призов выдано: ";
|
||||
[SerializeField] private TextMeshProUGUI prizesGivenText;
|
||||
[SerializeField] private string prizesGivenNulTextPrefix = "Призов выдано (обнуляемый): ";
|
||||
[SerializeField] private TextMeshProUGUI prizesGivenNulText;
|
||||
|
||||
[Header("Кнопки")]
|
||||
[SerializeField] private Button resetGamesPlayedNulButton;
|
||||
[SerializeField] private Button resetPrizesGivenNulButton;
|
||||
|
||||
private string _gamesPlayedKey;
|
||||
private string _gamesPlayedNulKey;
|
||||
private string _prizesGivenKey;
|
||||
private string _prizesGivenNulKey;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
canvasGroup.alpha = Mathf.Lerp(from, to, t / time);
|
||||
yield return null;
|
||||
base.Awake();
|
||||
|
||||
_gamesPlayedKey = PrizeSystem.key_starts;
|
||||
_gamesPlayedNulKey = PrizeSystem.key_startsNul;
|
||||
_prizesGivenKey = PrizeSystem.key_prizesgiven;
|
||||
_prizesGivenNulKey = PrizeSystem.key_prizesgivenNul;
|
||||
|
||||
if (resetGamesPlayedNulButton)
|
||||
resetGamesPlayedNulButton.onClick.AddListener(() => ResetPlayerPrefsStat(_gamesPlayedNulKey));
|
||||
|
||||
if (resetPrizesGivenNulButton)
|
||||
resetPrizesGivenNulButton.onClick.AddListener(() => ResetPlayerPrefsStat(_prizesGivenNulKey));
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (resetGamesPlayedNulButton)
|
||||
resetGamesPlayedNulButton.onClick.RemoveListener(() => ResetPlayerPrefsStat(_gamesPlayedNulKey));
|
||||
|
||||
if (resetPrizesGivenNulButton)
|
||||
resetPrizesGivenNulButton.onClick.RemoveListener(() => ResetPlayerPrefsStat(_prizesGivenNulKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переопределяем Open для обновления текста при открытии.
|
||||
/// </summary>
|
||||
public override void Open()
|
||||
{
|
||||
UpdateText();
|
||||
base.Open();
|
||||
}
|
||||
|
||||
private void UpdateText()
|
||||
{
|
||||
if (gamesPlayedText)
|
||||
{
|
||||
int gamesPlayed = PlayerPrefs.GetInt(_gamesPlayedKey, 0);
|
||||
gamesPlayedText.text = gamesPlayedTextPrefix + gamesPlayed;
|
||||
}
|
||||
|
||||
if (gamesPlayedNulText)
|
||||
{
|
||||
int gamesPlayedNul = PlayerPrefs.GetInt(_gamesPlayedNulKey, 0);
|
||||
gamesPlayedNulText.text = gamesPlayedNulTextPrefix + gamesPlayedNul;
|
||||
}
|
||||
|
||||
if (prizesGivenText)
|
||||
{
|
||||
int prizesGiven = PlayerPrefs.GetInt(_prizesGivenKey, 0);
|
||||
prizesGivenText.text = prizesGivenTextPrefix + prizesGiven;
|
||||
}
|
||||
|
||||
if (prizesGivenNulText)
|
||||
{
|
||||
int prizesGivenNul = PlayerPrefs.GetInt(_prizesGivenNulKey, 0);
|
||||
prizesGivenNulText.text = prizesGivenNulTextPrefix + prizesGivenNul;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPlayerPrefsStat(string key)
|
||||
{
|
||||
PlayerPrefs.SetInt(key, 0);
|
||||
UpdateText();
|
||||
}
|
||||
canvasGroup.alpha = to;
|
||||
canvasGroup.blocksRaycasts = to > 0.99f;
|
||||
onDone?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +1,38 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
using TMPro;
|
||||
using UnityEngine.EventSystems;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Контроллер панели завершения игры.
|
||||
/// Управляет отображением баннеров выигрыша и проигрыша.
|
||||
/// </summary>
|
||||
public class GameEndUIController : MonoBehaviour
|
||||
{
|
||||
[Header("Меню выигрыша")]
|
||||
[SerializeField] private CanvasGroup winPanel;
|
||||
[SerializeField] private Button mainMenuButtonWin;
|
||||
[SerializeField] private TextMeshProUGUI winText;
|
||||
[Header("Баннеры")]
|
||||
[SerializeField] private WinBannerController winBanner;
|
||||
[SerializeField] private LoseBannerController loseBanner;
|
||||
|
||||
[Header("Меню проигрыша")]
|
||||
[SerializeField] private CanvasGroup losePanel;
|
||||
[Header("Фон")]
|
||||
[SerializeField] private Image backgroundOverlay;
|
||||
[SerializeField] private Color overlayColor = new Color(0, 0, 0, 100f/255f);
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при показе баннера выигрыша.
|
||||
/// </summary>
|
||||
public event Action<float> WinBannerShown;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при показе баннера проигрыша.
|
||||
/// </summary>
|
||||
public event Action<float> LoseBannerShown;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при закрытии любого баннера.
|
||||
/// </summary>
|
||||
public event Action BannerDismissed;
|
||||
|
||||
private void OnEnable()
|
||||
@ -30,6 +41,16 @@ namespace RR.Common.UI
|
||||
|
||||
GameManager.Instance.GameWonEvent += OnGameWon;
|
||||
GameManager.Instance.GameOverEvent += OnGameOver;
|
||||
|
||||
// Подписываемся на события закрытия баннеров
|
||||
if (winBanner)
|
||||
{
|
||||
winBanner.WinBannerDismissed += OnBannerDismissed;
|
||||
}
|
||||
if (loseBanner)
|
||||
{
|
||||
loseBanner.ContinueClicked += OnBannerDismissed;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@ -38,105 +59,81 @@ namespace RR.Common.UI
|
||||
|
||||
GameManager.Instance.GameWonEvent -= OnGameWon;
|
||||
GameManager.Instance.GameOverEvent -= OnGameOver;
|
||||
|
||||
// Отписываемся от событий закрытия баннеров
|
||||
if (winBanner)
|
||||
{
|
||||
winBanner.WinBannerDismissed -= OnBannerDismissed;
|
||||
}
|
||||
if (loseBanner)
|
||||
{
|
||||
loseBanner.ContinueClicked -= OnBannerDismissed;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (mainMenuButtonWin) mainMenuButtonWin.onClick.AddListener(OnMainMenuClicked);
|
||||
|
||||
if (losePanel) losePanel.GetComponent<LoseBannerController>().ContinueClicked += OnContinueClicked;
|
||||
|
||||
// Изначально скрыто
|
||||
SetVisible(winPanel, false, true);
|
||||
SetVisible(losePanel, false, true);
|
||||
GetComponent<Image>().color = new Color(0, 0,0, 0);
|
||||
// Изначально скрываем фон
|
||||
if (backgroundOverlay)
|
||||
{
|
||||
backgroundOverlay.color = new Color(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMainMenuClicked()
|
||||
{
|
||||
if(winPanel.interactable) SetVisible(winPanel, false);
|
||||
if(losePanel.interactable) SetVisible(losePanel, false);
|
||||
BannerDismissed?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.LoadScene("MainMenu", false);
|
||||
}
|
||||
|
||||
private void OnContinueClicked()
|
||||
{
|
||||
if(winPanel.interactable) SetVisible(winPanel, false);
|
||||
if(losePanel.interactable) SetVisible(losePanel, false);
|
||||
GetComponent<Image>().color = new Color(0, 0,0, 0);
|
||||
BannerDismissed?.Invoke();
|
||||
}
|
||||
|
||||
private void OnGameWon(float gameTime)
|
||||
{
|
||||
GameManager.Instance.SetPause(true);
|
||||
if (GameManager.Instance)
|
||||
{
|
||||
GameManager.Instance.SetPause(true);
|
||||
}
|
||||
|
||||
GetComponent<Image>().color = new Color(0, 0,0, 100f/255f);
|
||||
ShowOverlay();
|
||||
|
||||
winText.text = $"Время игры: {FormatTime(gameTime)}";
|
||||
SetVisible(winPanel, true);
|
||||
|
||||
EventSystem.current.firstSelectedGameObject = mainMenuButtonWin.gameObject;
|
||||
EventSystem.current.SetSelectedGameObject(mainMenuButtonWin.gameObject);
|
||||
if (winBanner)
|
||||
{
|
||||
winBanner.ShowWin(gameTime);
|
||||
}
|
||||
|
||||
WinBannerShown?.Invoke(gameTime);
|
||||
}
|
||||
|
||||
private void OnGameOver(float gameTime)
|
||||
{
|
||||
GameManager.Instance.SetPause(true);
|
||||
if (GameManager.Instance)
|
||||
{
|
||||
GameManager.Instance.SetPause(true);
|
||||
}
|
||||
|
||||
GetComponent<Image>().color = new Color(0, 0,0, 100f/255f);
|
||||
ShowOverlay();
|
||||
|
||||
SetVisible(losePanel, true);
|
||||
if (loseBanner)
|
||||
{
|
||||
loseBanner.ShowLose(gameTime);
|
||||
}
|
||||
|
||||
LoseBannerShown?.Invoke(gameTime);
|
||||
}
|
||||
|
||||
private void SetVisible(CanvasGroup canvasGroup, bool visible, bool instant = false)
|
||||
|
||||
private void ShowOverlay()
|
||||
{
|
||||
if (!canvasGroup) return;
|
||||
|
||||
canvasGroup.interactable = visible;
|
||||
canvasGroup.blocksRaycasts = visible;
|
||||
|
||||
if (instant)
|
||||
if (backgroundOverlay)
|
||||
{
|
||||
canvasGroup.alpha = visible ? 1f : 0f;
|
||||
backgroundOverlay.color = overlayColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(canvasGroup, visible ? 1f : 0f, 0.15f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для плавного изменения видимости
|
||||
/// </summary>
|
||||
/// <param name="cg">Группа холстов</param>
|
||||
/// <param name="target">Целевое значение альфа-канала</param>
|
||||
/// <param name="duration">Продолжительность перехода</param>
|
||||
private static IEnumerator Fade(CanvasGroup cg, float target, float duration)
|
||||
{
|
||||
float start = cg.alpha;
|
||||
float t = 0f;
|
||||
while (t < duration)
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
cg.alpha = Mathf.Lerp(start, target, t / duration);
|
||||
yield return null;
|
||||
}
|
||||
cg.alpha = target;
|
||||
}
|
||||
|
||||
public static string FormatTime(float gameTime)
|
||||
private void HideOverlay()
|
||||
{
|
||||
TimeSpan interval = TimeSpan.FromSeconds(gameTime);
|
||||
|
||||
return interval.ToString(@"mm\:ss");
|
||||
if (backgroundOverlay)
|
||||
{
|
||||
backgroundOverlay.color = new Color(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBannerDismissed()
|
||||
{
|
||||
HideOverlay();
|
||||
BannerDismissed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,52 +1,30 @@
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
public class LevelSelectModal : MonoBehaviour
|
||||
/// <summary>
|
||||
/// Модальное окно выбора уровня.
|
||||
/// Наследуется от Modal для автоматического управления фокусом и блокировки фона.
|
||||
/// </summary>
|
||||
public class LevelSelectModal : Modal
|
||||
{
|
||||
[Header("Управление")]
|
||||
[SerializeField] private InputActionReference close;
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
[Header("Контент")]
|
||||
[SerializeField] private Transform content; // Grid parent
|
||||
[SerializeField] private LevelButton levelButtonPrefab;
|
||||
[SerializeField] private Button closeButton;
|
||||
[SerializeField] private float fadeDuration = 0.15f;
|
||||
|
||||
private readonly List<LevelButton> _levelsPool = new();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (closeButton) closeButton.onClick.AddListener(Close);
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ==== ПУБЛИЧНЫЙ API ====
|
||||
/// <summary>
|
||||
/// Метод для открытия модального окна
|
||||
/// Метод для открытия модального окна.
|
||||
/// Переопределяет базовый метод для сборки кнопок перед открытием.
|
||||
/// </summary>
|
||||
public void Open()
|
||||
public override void Open()
|
||||
{
|
||||
BuildIfNeeded();
|
||||
gameObject.SetActive(true);
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(0f, 1f, fadeDuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для закрытия модального окна
|
||||
/// </summary>
|
||||
private void Close()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(1f, 0f, fadeDuration, () => gameObject.SetActive(false)));
|
||||
base.Open();
|
||||
}
|
||||
|
||||
// ==== ВНУТРЕННЕЕ ====
|
||||
@ -68,41 +46,15 @@ namespace RR.Common.UI
|
||||
.ThenBy(e => e.stage)
|
||||
.ToList();
|
||||
|
||||
foreach (LevelButton btn in levels.Select(lv => Instantiate(levelButtonPrefab, content)))
|
||||
foreach (LevelEntry _ in levels)
|
||||
{
|
||||
LevelButton btn = Instantiate(levelButtonPrefab, content);
|
||||
_levelsPool.Add(btn);
|
||||
}
|
||||
|
||||
for (int index = 0; index < _levelsPool.Count; index++)
|
||||
{
|
||||
LevelButton btn = _levelsPool[index];
|
||||
// if (index == 0) btn.Setup(
|
||||
// $"{levels[index].world}-{levels[index].stage}",
|
||||
// levels[index].world,
|
||||
// levels[index].stage,
|
||||
// onDown: closeButton,
|
||||
// onUp: closeButton,
|
||||
// onLeft: closeButton,
|
||||
// onRight: _levelsPool.Count > 1 ? _levelsPool[1].GetComponent<Button>() : closeButton
|
||||
// );
|
||||
// else if (index == _levelsPool.Count - 1) btn.Setup(
|
||||
// $"{levels[index].world}-{levels[index].stage}",
|
||||
// levels[index].world,
|
||||
// levels[index].stage,
|
||||
// onDown: closeButton,
|
||||
// onUp: closeButton,
|
||||
// onLeft: _levelsPool.Count > 1 ? _levelsPool[index - 1].GetComponent<Button>() : closeButton,
|
||||
// onRight: closeButton
|
||||
// );
|
||||
// else btn.Setup(
|
||||
// $"{levels[index].world}-{levels[index].stage}",
|
||||
// levels[index].world,
|
||||
// levels[index].stage,
|
||||
// onUp: closeButton,
|
||||
// onDown: closeButton,
|
||||
// onLeft: _levelsPool[index - 1].GetComponent<Button>(),
|
||||
// onRight: _levelsPool[index + 1].GetComponent<Button>()
|
||||
// );
|
||||
|
||||
btn.Setup(
|
||||
$"{levels[index].world}-{levels[index].stage}",
|
||||
@ -119,16 +71,9 @@ namespace RR.Common.UI
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_levelsPool.Count <= 0) return;
|
||||
// Navigation closeNav = closeButton.navigation;
|
||||
// closeNav.mode = Navigation.Mode.Explicit;
|
||||
// closeNav.selectOnUp = _levelsPool.First().GetComponent<Button>();
|
||||
// closeNav.selectOnDown = _levelsPool.First().GetComponent<Button>();
|
||||
// closeButton.navigation = closeNav;
|
||||
}
|
||||
|
||||
private struct LevelEntry { public string name; public int world; public int stage; }
|
||||
private struct LevelEntry { public int world; public int stage; }
|
||||
|
||||
/// <summary>
|
||||
/// Метод для перебора уровней в игре
|
||||
@ -140,52 +85,20 @@ namespace RR.Common.UI
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
string path = SceneUtility.GetScenePathByBuildIndex(i);
|
||||
string buttonName = System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
string sceneName = System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
// фильтр "только уровни" по формату имени W-S:
|
||||
string[] parts = buttonName.Split('-');
|
||||
string[] parts = sceneName.Split('-');
|
||||
if (parts.Length != 2) continue;
|
||||
if (!int.TryParse(parts[0], out int world)) continue;
|
||||
if (!int.TryParse(parts[1], out int stage)) continue;
|
||||
|
||||
// Исключим служебные сцены, даже если вдруг совпадут по формату (на всякий случай)
|
||||
if (buttonName.Equals("Level UI", System.StringComparison.OrdinalIgnoreCase)) continue;
|
||||
if (buttonName.Equals("MainMenu", System.StringComparison.OrdinalIgnoreCase)) continue;
|
||||
// Исключим служебные сцены
|
||||
if (sceneName.Equals("Level UI", System.StringComparison.OrdinalIgnoreCase)) continue;
|
||||
if (sceneName.Equals("MainMenu", System.StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
yield return new LevelEntry { name = buttonName, world = world, stage = stage };
|
||||
yield return new LevelEntry { world = world, stage = stage };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для плавного перехода (затухания)
|
||||
/// </summary>
|
||||
/// <param name="from">Начальная прозрачность</param>
|
||||
/// <param name="to">Конечная прозрачнеость</param>
|
||||
/// <param name="time">Время перехода</param>
|
||||
/// <param name="onDone">Действие при выполнении перехода</param>
|
||||
private IEnumerator Fade(float from, float to, float time, System.Action onDone = null)
|
||||
{
|
||||
if (!canvasGroup) { onDone?.Invoke(); yield break; }
|
||||
float t = 0f;
|
||||
canvasGroup.alpha = from;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
while (t < time)
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
canvasGroup.alpha = Mathf.Lerp(from, to, t / time);
|
||||
yield return null;
|
||||
}
|
||||
canvasGroup.alpha = to;
|
||||
canvasGroup.interactable = to > 0.99f;
|
||||
canvasGroup.blocksRaycasts = to > 0.99f;
|
||||
onDone?.Invoke();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// ESC закрыть
|
||||
if (gameObject.activeSelf && close.action.WasPressedThisFrame())
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,17 +5,17 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
using RR.Common.UI;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
public class LoseBannerController : MonoBehaviour
|
||||
/// <summary>
|
||||
/// Контроллер баннера проигрыша.
|
||||
/// Модальное окно, которое отображается при поражении игрока.
|
||||
/// </summary>
|
||||
public class LoseBannerController : Modal
|
||||
{
|
||||
[SerializeField] private GameEndUIController bannersController;
|
||||
|
||||
[Header("Кнопки")]
|
||||
[Header("Элементы баннера проигрыша")]
|
||||
[SerializeField] private TextMeshProUGUI loseText;
|
||||
[SerializeField] private TextMeshProUGUI creditText;
|
||||
[SerializeField] private Button continueButton;
|
||||
@ -28,25 +28,40 @@ namespace RR.Common.UI
|
||||
private PaymentManager _paymentManager;
|
||||
private Coroutine _continueCountdownCoroutine;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при нажатии "Продолжить" или закрытии баннера.
|
||||
/// </summary>
|
||||
public event Action ContinueClicked;
|
||||
|
||||
private void OnEnable()
|
||||
protected override void Awake()
|
||||
{
|
||||
bannersController.LoseBannerShown += OnLoseBannerShown;
|
||||
base.Awake();
|
||||
|
||||
if (continueButton)
|
||||
{
|
||||
continueButton.onClick.AddListener(OnContinueButtonClicked);
|
||||
}
|
||||
}
|
||||
|
||||
_paymentManager = GameManager.Instance.Payment;
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
_paymentManager = GameManager.Instance?.Payment;
|
||||
|
||||
if (_paymentManager != null)
|
||||
{
|
||||
int credits = _paymentManager.CreditsCount;
|
||||
creditText.text = string.Format(creditTextFormat, credits);
|
||||
if (creditText)
|
||||
{
|
||||
creditText.text = string.Format(creditTextFormat, credits);
|
||||
}
|
||||
_paymentManager.OnCreditAdded.AddListener(UnlockContinueButton);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
protected override void OnDisable()
|
||||
{
|
||||
bannersController.LoseBannerShown -= OnLoseBannerShown;
|
||||
// Остановим корутину, если объект деактивируется
|
||||
if (_continueCountdownCoroutine != null)
|
||||
{
|
||||
@ -58,43 +73,46 @@ namespace RR.Common.UI
|
||||
{
|
||||
_paymentManager.OnCreditAdded.RemoveListener(UnlockContinueButton);
|
||||
}
|
||||
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
/// <summary>
|
||||
/// Показывает баннер проигрыша с указанным временем игры.
|
||||
/// </summary>
|
||||
/// <param name="gameTime">Время игры в секундах</param>
|
||||
public void ShowLose(float gameTime)
|
||||
{
|
||||
if (continueButton) continueButton.onClick.AddListener(OnContinueClicked);
|
||||
|
||||
// Инициализируем видимость/интерактивность кнопок в безопасное состояние
|
||||
if (continueButton) continueButton.gameObject.SetActive(false);
|
||||
if (continueButton && continueButtonText) continueButtonText.text = string.Empty;
|
||||
}
|
||||
|
||||
private void OnLoseBannerShown(float gameTime)
|
||||
{
|
||||
// EventSystem.current.firstSelectedGameObject = continueButton.gameObject;
|
||||
EventSystem.current.SetSelectedGameObject(continueButton.gameObject);
|
||||
|
||||
loseText.text = $"Время игры: {GameEndUIController.FormatTime(gameTime)}";
|
||||
creditText.text = string.Format(creditTextFormat, _paymentManager.CreditsCount);
|
||||
if (loseText)
|
||||
{
|
||||
loseText.text = $"Время игры: {TimeFormatHelper.FormatTime(gameTime)}";
|
||||
}
|
||||
|
||||
if (creditText && _paymentManager != null)
|
||||
{
|
||||
creditText.text = string.Format(creditTextFormat, _paymentManager.CreditsCount);
|
||||
}
|
||||
|
||||
if (continueButtonCountdown <= 0f)
|
||||
{
|
||||
ContinueClicked?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.LoadScene("MainMenu", false);
|
||||
GameManager.Instance?.SetPause(false);
|
||||
GameManager.Instance?.LoadScene("MainMenu", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (continueButton)
|
||||
{
|
||||
continueButton.gameObject.SetActive(true);
|
||||
continueButton.interactable = _paymentManager.CreditsCount > 0;
|
||||
continueButton.interactable = _paymentManager != null && _paymentManager.CreditsCount > 0;
|
||||
}
|
||||
|
||||
Open();
|
||||
|
||||
// Остановим предыдущую корутину, если есть
|
||||
if (_continueCountdownCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continueCountdownCoroutine);
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
_continueCountdownCoroutine = StartCoroutine(ContinueCountdownCoroutine(continueButtonCountdown));
|
||||
@ -102,19 +120,15 @@ namespace RR.Common.UI
|
||||
|
||||
private IEnumerator ContinueCountdownCoroutine(float duration)
|
||||
{
|
||||
|
||||
float elapsed = 0f;
|
||||
// Обновляем сразу первоначальный текст
|
||||
int initialSeconds = Mathf.RoundToInt(duration);
|
||||
UpdateContinueText(initialSeconds);
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
// Using yield return null and manual timing with unscaled time for more reliable updates
|
||||
float startTime = Time.unscaledTime;
|
||||
yield return null;
|
||||
|
||||
// Wait for approximately 1 second using unscaled time
|
||||
while (Time.unscaledTime - startTime < 1.0f)
|
||||
{
|
||||
yield return null;
|
||||
@ -125,9 +139,12 @@ namespace RR.Common.UI
|
||||
UpdateContinueText(remaining);
|
||||
}
|
||||
|
||||
if (continueButton)
|
||||
// Таймер истёк — закрываем баннер и выходим в меню
|
||||
Close();
|
||||
ContinueClicked?.Invoke();
|
||||
|
||||
if (GameManager.Instance)
|
||||
{
|
||||
ContinueClicked?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.LoadScene("MainMenu", false);
|
||||
}
|
||||
@ -138,11 +155,15 @@ namespace RR.Common.UI
|
||||
private void UpdateContinueText(int secondsRemaining)
|
||||
{
|
||||
if (continueButtonText)
|
||||
{
|
||||
continueButtonText.text = $"Продолжить? ({secondsRemaining})";
|
||||
}
|
||||
}
|
||||
|
||||
private void UnlockContinueButton(int newCredits)
|
||||
{
|
||||
if (_paymentManager == null) return;
|
||||
|
||||
if (_paymentManager.IsFreePlayEnabled() || SettingsManager.Instance.GetCurrentGameSettings().isFreeGame)
|
||||
{
|
||||
if (continueButton)
|
||||
@ -158,7 +179,7 @@ namespace RR.Common.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void OnContinueClicked()
|
||||
private void OnContinueButtonClicked()
|
||||
{
|
||||
// При клике на "Продолжить" — остановим таймер и продолжим игру
|
||||
if (_continueCountdownCoroutine != null)
|
||||
@ -167,9 +188,14 @@ namespace RR.Common.UI
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
Close();
|
||||
ContinueClicked?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.ContinueGame();
|
||||
|
||||
if (GameManager.Instance)
|
||||
{
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.ContinueGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/UI/Modal.meta
Normal file
3
Assets/Scripts/UI/Modal.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f4ee3155ab74532bb53c936f11318c1
|
||||
timeCreated: 1765785807
|
||||
333
Assets/Scripts/UI/Modal/Modal.cs
Normal file
333
Assets/Scripts/UI/Modal/Modal.cs
Normal file
@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Базовый компонент модального окна.
|
||||
/// Автоматически регистрируется в ModalManager при активации.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class Modal : MonoBehaviour
|
||||
{
|
||||
[Header("Настройки модального окна")]
|
||||
[Tooltip("Первый элемент, который получит фокус при открытии окна.")]
|
||||
[SerializeField] private Selectable firstSelected;
|
||||
|
||||
[Tooltip("Блокировать интерактивность элементов за пределами модального окна.")]
|
||||
[SerializeField] private bool blockBackground = true;
|
||||
|
||||
[Tooltip("Автоматически возвращать фокус внутрь окна, если он уходит наружу.")]
|
||||
[SerializeField] private bool enforceFocus = true;
|
||||
|
||||
[Header("Анимация")]
|
||||
[Tooltip("CanvasGroup для анимации появления/исчезновения.")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
|
||||
[Tooltip("Длительность анимации появления/исчезновения.")]
|
||||
[SerializeField] private float fadeDuration = 0.15f;
|
||||
|
||||
[Header("Закрытие")]
|
||||
[Tooltip("InputAction для закрытия модального окна (например, Escape).")]
|
||||
[SerializeField] private InputActionReference closeAction;
|
||||
|
||||
[Tooltip("Кнопка закрытия модального окна.")]
|
||||
[SerializeField] private Button closeButton;
|
||||
|
||||
/// <summary>
|
||||
/// Первый элемент для фокуса при открытии.
|
||||
/// </summary>
|
||||
public Selectable FirstSelected => firstSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Блокировать ли фон при открытии.
|
||||
/// </summary>
|
||||
public bool BlockBackground => blockBackground;
|
||||
|
||||
/// <summary>
|
||||
/// Следить ли за фокусом и возвращать его внутрь окна.
|
||||
/// </summary>
|
||||
public bool EnforceFocus => enforceFocus;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое перед открытием окна.
|
||||
/// </summary>
|
||||
public event Action OnOpening;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое после открытия окна.
|
||||
/// </summary>
|
||||
public event Action OnOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое перед закрытием окна.
|
||||
/// </summary>
|
||||
public event Action OnClosing;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое после закрытия окна.
|
||||
/// </summary>
|
||||
public event Action OnClosed;
|
||||
|
||||
protected bool _isOpen;
|
||||
protected Coroutine _fadeCoroutine;
|
||||
|
||||
/// <summary>
|
||||
/// Открыто ли модальное окно.
|
||||
/// </summary>
|
||||
public bool IsOpen => _isOpen;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (closeButton)
|
||||
{
|
||||
closeButton.onClick.AddListener(Close);
|
||||
}
|
||||
|
||||
// Если canvasGroup не указан или указан на родительском объекте,
|
||||
// создаём/получаем CanvasGroup на самом модальном окне
|
||||
if (!canvasGroup || canvasGroup.gameObject != gameObject)
|
||||
{
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
if (!canvasGroup)
|
||||
{
|
||||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
// Изначально скрыто
|
||||
if (canvasGroup)
|
||||
{
|
||||
canvasGroup.alpha = 0f;
|
||||
canvasGroup.interactable = false;
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
// Регистрируемся в менеджере
|
||||
ModalManager.Instance.Push(this);
|
||||
|
||||
// Подписываемся на действие закрытия
|
||||
if (closeAction)
|
||||
{
|
||||
closeAction.action.Enable();
|
||||
closeAction.action.performed += OnCloseActionPerformed;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
// Отписываемся от действия закрытия
|
||||
if (closeAction)
|
||||
{
|
||||
closeAction.action.performed -= OnCloseActionPerformed;
|
||||
}
|
||||
|
||||
// Удаляемся из менеджера
|
||||
ModalManager.Instance.Pop(this);
|
||||
}
|
||||
|
||||
private void OnCloseActionPerformed(InputAction.CallbackContext ctx)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Открывает модальное окно.
|
||||
/// </summary>
|
||||
public virtual void Open()
|
||||
{
|
||||
if (_isOpen) return;
|
||||
|
||||
OnOpening?.Invoke();
|
||||
|
||||
gameObject.SetActive(true);
|
||||
_isOpen = true;
|
||||
|
||||
if (canvasGroup)
|
||||
{
|
||||
if (_fadeCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_fadeCoroutine);
|
||||
}
|
||||
_fadeCoroutine = StartCoroutine(FadeCoroutine(0f, 1f, fadeDuration, () =>
|
||||
{
|
||||
OnOpened?.Invoke();
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
OnOpened?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Закрывает модальное окно.
|
||||
/// </summary>
|
||||
public virtual void Close()
|
||||
{
|
||||
if (!_isOpen && !gameObject.activeSelf) return;
|
||||
|
||||
OnClosing?.Invoke();
|
||||
|
||||
if (canvasGroup && fadeDuration > 0f)
|
||||
{
|
||||
if (_fadeCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_fadeCoroutine);
|
||||
}
|
||||
_fadeCoroutine = StartCoroutine(FadeCoroutine(canvasGroup.alpha, 0f, fadeDuration, () =>
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
_isOpen = false;
|
||||
OnClosed?.Invoke();
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (canvasGroup)
|
||||
{
|
||||
canvasGroup.alpha = 0f;
|
||||
canvasGroup.interactable = false;
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
gameObject.SetActive(false);
|
||||
_isOpen = false;
|
||||
OnClosed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Мгновенно открывает окно без анимации.
|
||||
/// </summary>
|
||||
public void OpenInstant()
|
||||
{
|
||||
if (_isOpen) return;
|
||||
|
||||
OnOpening?.Invoke();
|
||||
|
||||
gameObject.SetActive(true);
|
||||
_isOpen = true;
|
||||
|
||||
if (canvasGroup)
|
||||
{
|
||||
canvasGroup.alpha = 1f;
|
||||
canvasGroup.interactable = true;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
|
||||
OnOpened?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Мгновенно закрывает окно без анимации.
|
||||
/// </summary>
|
||||
public void CloseInstant()
|
||||
{
|
||||
if (!_isOpen && !gameObject.activeSelf) return;
|
||||
|
||||
OnClosing?.Invoke();
|
||||
|
||||
if (canvasGroup)
|
||||
{
|
||||
canvasGroup.alpha = 0f;
|
||||
canvasGroup.interactable = false;
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
_isOpen = false;
|
||||
OnClosed?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переключает состояние окна (открыто/закрыто).
|
||||
/// </summary>
|
||||
public void Toggle()
|
||||
{
|
||||
if (_isOpen)
|
||||
Close();
|
||||
else
|
||||
Open();
|
||||
}
|
||||
|
||||
private IEnumerator FadeCoroutine(float from, float to, float duration, Action onComplete = null)
|
||||
{
|
||||
if (!canvasGroup)
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
yield break;
|
||||
}
|
||||
|
||||
// При открытии сразу делаем интерактивным
|
||||
if (to > from)
|
||||
{
|
||||
canvasGroup.interactable = true;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
|
||||
float elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
canvasGroup.alpha = Mathf.Lerp(from, to, t);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
canvasGroup.alpha = to;
|
||||
|
||||
// При закрытии выключаем интерактивность
|
||||
if (to < from)
|
||||
{
|
||||
canvasGroup.interactable = false;
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
|
||||
onComplete?.Invoke();
|
||||
_fadeCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает первый элемент для фокуса (можно менять динамически).
|
||||
/// </summary>
|
||||
public void SetFirstSelected(Selectable selectable)
|
||||
{
|
||||
firstSelected = selectable;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected virtual void Reset()
|
||||
{
|
||||
// Пытаемся автоматически найти CanvasGroup
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
if (!canvasGroup)
|
||||
{
|
||||
canvasGroup = GetComponentInChildren<CanvasGroup>();
|
||||
}
|
||||
|
||||
// Ищем кнопку закрытия
|
||||
Button[] buttons = GetComponentsInChildren<Button>(true);
|
||||
foreach (Button btn in buttons)
|
||||
{
|
||||
string btnName = btn.name.ToLower();
|
||||
if (btnName.Contains("close") || btnName.Contains("back") || btnName.Contains("cancel"))
|
||||
{
|
||||
closeButton = btn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ищем первый Selectable для фокуса
|
||||
firstSelected = GetComponentInChildren<Selectable>(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/UI/Modal/Modal.cs.meta
Normal file
3
Assets/Scripts/UI/Modal/Modal.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46e5f9b83fbe4d6c90471d64468f6787
|
||||
timeCreated: 1765785953
|
||||
386
Assets/Scripts/UI/Modal/ModalManager.cs
Normal file
386
Assets/Scripts/UI/Modal/ModalManager.cs
Normal file
@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Центральный менеджер модальных окон.
|
||||
/// Управляет стеком открытых модальных окон и фокусом между ними.
|
||||
/// </summary>
|
||||
public class ModalManager : MonoBehaviour
|
||||
{
|
||||
private static ModalManager _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton-инстанс менеджера модальных окон.
|
||||
/// Создаётся автоматически при первом обращении.
|
||||
/// </summary>
|
||||
public static ModalManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance) return _instance;
|
||||
|
||||
// Ищем существующий в сцене
|
||||
_instance = FindAnyObjectByType<ModalManager>();
|
||||
if (_instance) return _instance;
|
||||
|
||||
// Создаём новый
|
||||
GameObject go = new GameObject("[ModalManager]");
|
||||
_instance = go.AddComponent<ModalManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Информация о состоянии UI до открытия модального окна.
|
||||
/// </summary>
|
||||
private class ModalStackEntry
|
||||
{
|
||||
public Modal Modal;
|
||||
public GameObject PreviousSelected;
|
||||
public List<CanvasGroup> DisabledGroups = new();
|
||||
}
|
||||
|
||||
private readonly Stack<ModalStackEntry> _stack = new();
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при открытии любого модального окна.
|
||||
/// </summary>
|
||||
public event Action<Modal> OnModalOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при закрытии любого модального окна.
|
||||
/// </summary>
|
||||
public event Action<Modal> OnModalClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Текущее активное модальное окно (на вершине стека).
|
||||
/// </summary>
|
||||
public Modal CurrentModal => _stack.Count > 0 ? _stack.Peek().Modal : null;
|
||||
|
||||
/// <summary>
|
||||
/// Есть ли открытые модальные окна.
|
||||
/// </summary>
|
||||
public bool HasOpenModals => _stack.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Количество открытых модальных окон.
|
||||
/// </summary>
|
||||
public int OpenModalCount => _stack.Count;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_instance && _instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
_instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Следим за тем, чтобы фокус оставался внутри текущего модального окна
|
||||
if (_stack.Count == 0) return;
|
||||
|
||||
ModalStackEntry top = _stack.Peek();
|
||||
if (!top.Modal || !top.Modal.gameObject.activeInHierarchy) return;
|
||||
if (!top.Modal.EnforceFocus) return;
|
||||
|
||||
EnsureFocusInModal(top.Modal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регистрирует модальное окно в стеке (вызывается из Modal.OnEnable).
|
||||
/// </summary>
|
||||
internal void Push(Modal modal)
|
||||
{
|
||||
if (modal == null) return;
|
||||
|
||||
// Проверяем, не зарегистрировано ли уже это окно
|
||||
foreach (ModalStackEntry entry in _stack)
|
||||
{
|
||||
if (entry.Modal == modal) return;
|
||||
}
|
||||
|
||||
ModalStackEntry newEntry = new ModalStackEntry
|
||||
{
|
||||
Modal = modal,
|
||||
PreviousSelected = EventSystem.current ? EventSystem.current.currentSelectedGameObject : null
|
||||
};
|
||||
|
||||
// Блокируем интерактивность всего, что не принадлежит этому модальному окну
|
||||
if (modal.BlockBackground)
|
||||
{
|
||||
BlockBackgroundInteraction(modal, newEntry);
|
||||
}
|
||||
|
||||
_stack.Push(newEntry);
|
||||
|
||||
// Устанавливаем фокус на первый элемент модального окна
|
||||
SetInitialFocus(modal);
|
||||
|
||||
OnModalOpened?.Invoke(modal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет модальное окно из стека (вызывается из Modal.OnDisable).
|
||||
/// </summary>
|
||||
internal void Pop(Modal modal)
|
||||
{
|
||||
if (modal == null) return;
|
||||
if (_stack.Count == 0) return;
|
||||
|
||||
// Ищем и удаляем модальное окно из стека
|
||||
Stack<ModalStackEntry> temp = new Stack<ModalStackEntry>();
|
||||
ModalStackEntry foundEntry = null;
|
||||
|
||||
while (_stack.Count > 0)
|
||||
{
|
||||
ModalStackEntry entry = _stack.Pop();
|
||||
if (entry.Modal == modal)
|
||||
{
|
||||
foundEntry = entry;
|
||||
break;
|
||||
}
|
||||
temp.Push(entry);
|
||||
}
|
||||
|
||||
// Возвращаем остальные в стек
|
||||
while (temp.Count > 0)
|
||||
{
|
||||
_stack.Push(temp.Pop());
|
||||
}
|
||||
|
||||
if (foundEntry == null) return;
|
||||
|
||||
// Восстанавливаем интерактивность заблокированных групп
|
||||
RestoreBackgroundInteraction(foundEntry);
|
||||
|
||||
// Восстанавливаем фокус
|
||||
RestoreFocus(foundEntry);
|
||||
|
||||
OnModalClosed?.Invoke(modal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Закрывает все открытые модальные окна.
|
||||
/// </summary>
|
||||
public void CloseAll()
|
||||
{
|
||||
while (_stack.Count > 0)
|
||||
{
|
||||
ModalStackEntry entry = _stack.Peek();
|
||||
if (entry.Modal != null)
|
||||
{
|
||||
entry.Modal.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
_stack.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Закрывает текущее (верхнее) модальное окно.
|
||||
/// </summary>
|
||||
public void CloseCurrent()
|
||||
{
|
||||
if (_stack.Count == 0) return;
|
||||
|
||||
Modal current = _stack.Peek().Modal;
|
||||
if (current != null)
|
||||
{
|
||||
current.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void BlockBackgroundInteraction(Modal modal, ModalStackEntry entry)
|
||||
{
|
||||
// Находим все CanvasGroup, которые не принадлежат модальному окну
|
||||
CanvasGroup[] allGroups = FindObjectsByType<CanvasGroup>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
|
||||
foreach (CanvasGroup group in allGroups)
|
||||
{
|
||||
if (!group || !group.interactable) continue;
|
||||
if (IsUnderModal(group.transform, modal)) continue;
|
||||
|
||||
// Не блокируем родительские CanvasGroup модального окна
|
||||
if (IsParentOfModal(group.transform, modal)) continue;
|
||||
|
||||
// Не блокируем CanvasGroup, если модальное окно является его потомком
|
||||
if (modal.transform.IsChildOf(group.transform)) continue;
|
||||
|
||||
// Проверяем, не принадлежит ли эта группа другому модальному окну в стеке
|
||||
bool belongsToOtherModal = false;
|
||||
foreach (ModalStackEntry stackEntry in _stack)
|
||||
{
|
||||
if (stackEntry.Modal && IsUnderModal(group.transform, stackEntry.Modal))
|
||||
{
|
||||
belongsToOtherModal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (belongsToOtherModal) continue;
|
||||
|
||||
group.interactable = false;
|
||||
entry.DisabledGroups.Add(group);
|
||||
}
|
||||
|
||||
// Если у модального окна нет CanvasGroup-родителя, блокируем все Selectable напрямую
|
||||
if (entry.DisabledGroups.Count == 0)
|
||||
{
|
||||
BlockSelectablesOutsideModal(modal, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlockSelectablesOutsideModal(Modal modal, ModalStackEntry entry)
|
||||
{
|
||||
Selectable[] all = FindObjectsByType<Selectable>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
|
||||
foreach (Selectable s in all)
|
||||
{
|
||||
if (!s || !s.interactable) continue;
|
||||
if (IsUnderModal(s.transform, modal)) continue;
|
||||
|
||||
// Добавляем временную CanvasGroup для блокировки
|
||||
CanvasGroup cg = s.GetComponent<CanvasGroup>();
|
||||
if (!cg)
|
||||
{
|
||||
cg = s.gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
if (cg.interactable)
|
||||
{
|
||||
cg.interactable = false;
|
||||
entry.DisabledGroups.Add(cg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreBackgroundInteraction(ModalStackEntry entry)
|
||||
{
|
||||
foreach (CanvasGroup group in entry.DisabledGroups)
|
||||
{
|
||||
if (group)
|
||||
{
|
||||
group.interactable = true;
|
||||
}
|
||||
}
|
||||
entry.DisabledGroups.Clear();
|
||||
}
|
||||
|
||||
private void SetInitialFocus(Modal modal)
|
||||
{
|
||||
if (!EventSystem.current) return;
|
||||
|
||||
GameObject toSelect = null;
|
||||
|
||||
if (modal.FirstSelected && modal.FirstSelected.gameObject.activeInHierarchy)
|
||||
{
|
||||
toSelect = modal.FirstSelected.gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
Selectable any = modal.GetComponentInChildren<Selectable>(includeInactive: false);
|
||||
if (any) toSelect = any.gameObject;
|
||||
}
|
||||
|
||||
if (!toSelect) return;
|
||||
|
||||
// Сбрасываем и устанавливаем фокус
|
||||
EventSystem.current.SetSelectedGameObject(null);
|
||||
EventSystem.current.SetSelectedGameObject(toSelect);
|
||||
}
|
||||
|
||||
private void RestoreFocus(ModalStackEntry entry)
|
||||
{
|
||||
if (!EventSystem.current) return;
|
||||
|
||||
// Если есть другие модальные окна в стеке, фокусируемся на верхнем
|
||||
if (_stack.Count > 0)
|
||||
{
|
||||
Modal topModal = _stack.Peek().Modal;
|
||||
if (topModal != null)
|
||||
{
|
||||
SetInitialFocus(topModal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Иначе восстанавливаем предыдущий фокус
|
||||
GameObject toSelect = null;
|
||||
|
||||
if (entry.PreviousSelected && entry.PreviousSelected.activeInHierarchy)
|
||||
{
|
||||
Selectable selectable = entry.PreviousSelected.GetComponent<Selectable>();
|
||||
if (selectable && selectable.interactable)
|
||||
{
|
||||
toSelect = entry.PreviousSelected;
|
||||
}
|
||||
}
|
||||
|
||||
// Если предыдущий элемент недоступен, ищем любой доступный
|
||||
if (!toSelect)
|
||||
{
|
||||
Selectable anySelectable = FindAnyObjectByType<Selectable>(FindObjectsInactive.Exclude);
|
||||
if (anySelectable && anySelectable.interactable)
|
||||
{
|
||||
toSelect = anySelectable.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
EventSystem.current.SetSelectedGameObject(toSelect);
|
||||
}
|
||||
|
||||
private void EnsureFocusInModal(Modal modal)
|
||||
{
|
||||
if (!EventSystem.current) return;
|
||||
|
||||
GameObject current = EventSystem.current.currentSelectedGameObject;
|
||||
|
||||
// Если текущий выделенный элемент находится внутри модального окна, всё ок
|
||||
if (current && IsUnderModal(current.transform, modal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Иначе возвращаем фокус внутрь модального окна
|
||||
SetInitialFocus(modal);
|
||||
}
|
||||
|
||||
private bool IsUnderModal(Transform t, Modal modal)
|
||||
{
|
||||
if (!modal) return false;
|
||||
Transform root = modal.transform;
|
||||
for (Transform cur = t; cur; cur = cur.parent)
|
||||
{
|
||||
if (cur == root) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, является ли указанный Transform родителем модального окна.
|
||||
/// </summary>
|
||||
private bool IsParentOfModal(Transform t, Modal modal)
|
||||
{
|
||||
if (!modal || !t) return false;
|
||||
Transform modalTransform = modal.transform;
|
||||
for (Transform cur = modalTransform.parent; cur; cur = cur.parent)
|
||||
{
|
||||
if (cur == t) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/UI/Modal/ModalManager.cs.meta
Normal file
3
Assets/Scripts/UI/Modal/ModalManager.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7437b0d73d7e4c7e90d74fe313806cba
|
||||
timeCreated: 1765785807
|
||||
@ -1,177 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ModalFocusTrap : MonoBehaviour
|
||||
{
|
||||
[Header("Modal")]
|
||||
public GameObject modalRoot;
|
||||
public Selectable firstSelected;
|
||||
|
||||
[Header("Behavior")]
|
||||
[Tooltip("Следить и возвращать фокус внутрь модалки, если он уходит наружу.")]
|
||||
public bool enforce = true;
|
||||
|
||||
private bool _wasOpenLastFrame;
|
||||
private GameObject _prevSelectedGlobal;
|
||||
private readonly List<Selectable> _outside = new();
|
||||
private readonly Dictionary<Selectable, bool> _outsidePrev = new();
|
||||
private Selectable _lastModalSelected;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
if (modalRoot != null) return;
|
||||
Transform sibling = transform.parent ? transform.parent.Find("LevelSelectModal") : null;
|
||||
if (sibling) modalRoot = sibling.gameObject;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!modalRoot) return;
|
||||
|
||||
bool isOpen = modalRoot.activeSelf;
|
||||
switch (isOpen)
|
||||
{
|
||||
case true when !_wasOpenLastFrame:
|
||||
OnModalOpened();
|
||||
break;
|
||||
case false when _wasOpenLastFrame:
|
||||
OnModalClosed();
|
||||
break;
|
||||
}
|
||||
|
||||
_wasOpenLastFrame = isOpen;
|
||||
|
||||
if (isOpen && enforce) EnsureFocusInsideModal();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_wasOpenLastFrame) OnModalClosed();
|
||||
}
|
||||
|
||||
private void OnModalOpened()
|
||||
{
|
||||
// запомнить, что было выделено до открытия
|
||||
_prevSelectedGlobal = EventSystem.current ? EventSystem.current.currentSelectedGameObject : null;
|
||||
|
||||
// выключить интерактив вне модалки
|
||||
CollectOutsideSelectables();
|
||||
foreach (Selectable s in _outside.Where(s => s))
|
||||
{
|
||||
_outsidePrev[s] = s.interactable;
|
||||
s.interactable = false;
|
||||
}
|
||||
|
||||
// сфокусировать элемент внутри модалки
|
||||
GameObject toSelect = null;
|
||||
if (firstSelected && firstSelected.gameObject.activeInHierarchy)
|
||||
toSelect = firstSelected.gameObject;
|
||||
else
|
||||
{
|
||||
Selectable any = modalRoot.GetComponentInChildren<Selectable>(includeInactive: false);
|
||||
if (any) toSelect = any.gameObject;
|
||||
}
|
||||
|
||||
if (!toSelect || !EventSystem.current) return;
|
||||
EventSystem.current.SetSelectedGameObject(null);
|
||||
EventSystem.current.SetSelectedGameObject(toSelect);
|
||||
_lastModalSelected = toSelect.GetComponent<Selectable>();
|
||||
}
|
||||
|
||||
private void OnModalClosed()
|
||||
{
|
||||
// Восстанавливаем интерактивность всех внешних элементов
|
||||
foreach ((Selectable selectable, bool value) in _outsidePrev)
|
||||
{
|
||||
if (selectable) selectable.interactable = value;
|
||||
}
|
||||
_outsidePrev.Clear();
|
||||
_outside.Clear();
|
||||
|
||||
// Возвращаем фокус на элемент, который был выделен до открытия модалки
|
||||
if (EventSystem.current)
|
||||
{
|
||||
GameObject toSelect = null;
|
||||
|
||||
// Проверяем, что предыдущий элемент всё ещё активен и интерактивен
|
||||
if (_prevSelectedGlobal && _prevSelectedGlobal.activeInHierarchy)
|
||||
{
|
||||
Selectable selectable = _prevSelectedGlobal.GetComponent<Selectable>();
|
||||
if (selectable && selectable.interactable)
|
||||
{
|
||||
toSelect = _prevSelectedGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
// Если предыдущий элемент недоступен, ищем любой доступный Selectable
|
||||
if (!toSelect)
|
||||
{
|
||||
Selectable anySelectable = Object.FindAnyObjectByType<Selectable>(FindObjectsInactive.Exclude);
|
||||
if (anySelectable && anySelectable.interactable && !IsUnderModal(anySelectable.transform))
|
||||
{
|
||||
toSelect = anySelectable.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
EventSystem.current.SetSelectedGameObject(toSelect);
|
||||
}
|
||||
|
||||
_lastModalSelected = null;
|
||||
_prevSelectedGlobal = null;
|
||||
}
|
||||
|
||||
private void CollectOutsideSelectables()
|
||||
{
|
||||
_outside.Clear();
|
||||
_outsidePrev.Clear();
|
||||
|
||||
Selectable[] all = Object.FindObjectsByType<Selectable>(
|
||||
FindObjectsInactive.Include,
|
||||
FindObjectsSortMode.None);
|
||||
|
||||
foreach (Selectable s in all)
|
||||
{
|
||||
if (!s || !s.gameObject.activeInHierarchy) continue;
|
||||
if (!IsUnderModal(s.transform))
|
||||
_outside.Add(s);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsUnderModal(Transform t)
|
||||
{
|
||||
if (!modalRoot) return false;
|
||||
Transform root = modalRoot.transform;
|
||||
for (Transform cur = t; cur; cur = cur.parent)
|
||||
if (cur == root) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void EnsureFocusInsideModal()
|
||||
{
|
||||
if (!EventSystem.current) return;
|
||||
|
||||
GameObject cur = EventSystem.current.currentSelectedGameObject;
|
||||
if (!cur || !IsUnderModal(cur.transform))
|
||||
{
|
||||
GameObject fallback =
|
||||
(_lastModalSelected && _lastModalSelected.gameObject.activeInHierarchy) ? _lastModalSelected.gameObject :
|
||||
(firstSelected && firstSelected.gameObject.activeInHierarchy) ? firstSelected.gameObject :
|
||||
modalRoot.GetComponentInChildren<Selectable>()?.gameObject;
|
||||
|
||||
if (!fallback) return;
|
||||
EventSystem.current.SetSelectedGameObject(fallback);
|
||||
_lastModalSelected = fallback.GetComponent<Selectable>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastModalSelected = cur.GetComponent<Selectable>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c815924369a0a64458e3c85a564de8b8
|
||||
@ -1,143 +1,102 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
public class PauseMenu : MonoBehaviour
|
||||
/// <summary>
|
||||
/// Меню паузы. Наследуется от Modal для автоматического управления фокусом.
|
||||
/// Открывается/закрывается через GameManager.PauseStateChanged.
|
||||
/// </summary>
|
||||
public class PauseMenu : Modal
|
||||
{
|
||||
[Header("Управление")]
|
||||
[SerializeField] private InputActionReference close;
|
||||
[Header("Управление паузой")]
|
||||
[SerializeField] private InputActionReference pauseToggle;
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] private CanvasGroup panel;
|
||||
[Header("Кнопки меню")]
|
||||
[SerializeField] private Button resumeButton;
|
||||
[SerializeField] private Button quitButton;
|
||||
|
||||
[Header("Связь с другими элементами")]
|
||||
[SerializeField] private GameEndUIController bannerController;
|
||||
//[SerializeField] private Button restartButton;
|
||||
//[SerializeField] private KeyCode toggleKey = KeyCode.Escape; // Esc
|
||||
|
||||
private bool isActive = true;
|
||||
private bool _isActive = true;
|
||||
|
||||
private void OnEnable()
|
||||
protected override void Awake()
|
||||
{
|
||||
// Настраиваем обработчики кнопок
|
||||
if (resumeButton) resumeButton.onClick.AddListener(OnResumeClicked);
|
||||
if (quitButton) quitButton.onClick.AddListener(OnQuitClicked);
|
||||
|
||||
if (pauseToggle) pauseToggle.action.performed += OnPauseToggle;
|
||||
|
||||
// Устанавливаем первый выбранный элемент
|
||||
SetFirstSelected(resumeButton);
|
||||
|
||||
|
||||
if (GameManager.Instance != null)
|
||||
GameManager.Instance.PauseStateChanged += HandlePauseChanged;
|
||||
|
||||
bannerController.WinBannerShown += (f) => { isActive = false; };
|
||||
bannerController.LoseBannerShown += (f) => { isActive = false; };
|
||||
bannerController.BannerDismissed += () => { isActive = true; };
|
||||
if (bannerController)
|
||||
{
|
||||
bannerController.WinBannerShown += _ => { _isActive = false; };
|
||||
bannerController.LoseBannerShown += _ => { _isActive = false; };
|
||||
bannerController.BannerDismissed += () => { _isActive = true; };
|
||||
}
|
||||
|
||||
// Изначально скрыто - не вызываем base.Awake(), делаем это вручную
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
|
||||
private void OnPauseToggle(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (GameManager.Instance != null)
|
||||
GameManager.Instance.PauseStateChanged -= HandlePauseChanged;
|
||||
GameManager.Instance.TogglePause();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// ReSharper disable once EventUnsubscriptionViaAnonymousDelegate
|
||||
if (close) close.action.performed -= _ => GameManager.Instance.TogglePause();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (resumeButton) resumeButton.onClick.AddListener(OnResumeClicked);
|
||||
if (quitButton) quitButton.onClick.AddListener(OnQuitClicked);
|
||||
//if (restartButton) restartButton.onClick.AddListener(OnRestartClicked);
|
||||
|
||||
// Изначально скрыто
|
||||
SetVisible(false, true);
|
||||
if (pauseToggle) pauseToggle.action.performed -= OnPauseToggle;
|
||||
|
||||
if (close) close.action.performed += _ => GameManager.Instance.TogglePause();
|
||||
if (GameManager.Instance != null)
|
||||
GameManager.Instance.PauseStateChanged -= HandlePauseChanged;
|
||||
}
|
||||
|
||||
// private void Update()
|
||||
// {
|
||||
// if (close.action.WasPressedThisFrame() && !GameManager.Instance.IsPaused)
|
||||
// GameManager.Instance.TogglePause();
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Обработка события паузы
|
||||
/// Обработка события паузы от GameManager.
|
||||
/// </summary>
|
||||
/// <param name="paused">Ставить ли на паузу</param>
|
||||
private void HandlePauseChanged(bool paused)
|
||||
{
|
||||
if (!isActive) return;
|
||||
if (!_isActive) return;
|
||||
|
||||
if (paused)
|
||||
{
|
||||
EventSystem.current.firstSelectedGameObject = resumeButton.gameObject;
|
||||
EventSystem.current.SetSelectedGameObject(resumeButton.gameObject);
|
||||
}
|
||||
|
||||
SetVisible(paused);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для изменения отображения
|
||||
/// </summary>
|
||||
/// <param name="visible">Отображать ли меню паузы</param>
|
||||
/// <param name="instant">Мгновенно или нет</param>
|
||||
private void SetVisible(bool visible, bool instant = false)
|
||||
{
|
||||
if (!panel) return;
|
||||
|
||||
panel.interactable = visible;
|
||||
panel.blocksRaycasts = visible;
|
||||
|
||||
if (instant)
|
||||
{
|
||||
panel.alpha = visible ? 1f : 0f;
|
||||
}
|
||||
Open();
|
||||
else
|
||||
{
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(panel, visible ? 1f : 0f, 0.15f));
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для плавного изменения видимости
|
||||
/// Переопределяем Close, чтобы при закрытии также снимать паузу.
|
||||
/// </summary>
|
||||
/// <param name="cg">Группа холстов</param>
|
||||
/// <param name="target">Целевое значение альфа-канала</param>
|
||||
/// <param name="duration">Продолжительность перехода</param>
|
||||
private static IEnumerator Fade(CanvasGroup cg, float target, float duration)
|
||||
public override void Close()
|
||||
{
|
||||
float start = cg.alpha;
|
||||
float t = 0f;
|
||||
while (t < duration)
|
||||
base.Close();
|
||||
|
||||
// Если игра ещё на паузе, снимаем её
|
||||
if (GameManager.Instance != null && GameManager.Instance.IsPaused)
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
cg.alpha = Mathf.Lerp(start, target, t / duration);
|
||||
yield return null;
|
||||
GameManager.Instance.SetPause(false);
|
||||
}
|
||||
cg.alpha = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для возобновления игры
|
||||
/// Метод для возобновления игры.
|
||||
/// </summary>
|
||||
public static void OnResumeClicked() => GameManager.Instance.TogglePause();
|
||||
|
||||
//public void OnRestartClicked()
|
||||
//{
|
||||
// GameManager.Instance.SetPause(false);
|
||||
// GameManager.Instance.ResetLevel();
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для выхода в главное меню
|
||||
/// Метод для выхода в главное меню.
|
||||
/// </summary>
|
||||
public void OnQuitClicked()
|
||||
{
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.InputSystem;
|
||||
@ -7,27 +6,31 @@ using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Модальное окно настроек игры.
|
||||
/// Управляет отображением и сохранением настроек через SettingsManager.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CanvasGroup))]
|
||||
public class SettingsUI : MonoBehaviour
|
||||
public class SettingsUI : Modal
|
||||
{
|
||||
[Header("Управление")]
|
||||
[Header("Дополнительное управление")]
|
||||
[SerializeField] private InputActionReference triggerKey;
|
||||
[SerializeField] private Button saveButton;
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
[Header("Контент настроек")]
|
||||
[SerializeField] private Transform content;
|
||||
[SerializeField] private float fadeDuration = 0.15f;
|
||||
|
||||
private SettingsUIList settingsUIList;
|
||||
|
||||
private SettingsManager settingsManager;
|
||||
|
||||
private void Awake()
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
settingsManager = SettingsManager.Instance;
|
||||
|
||||
if (triggerKey != null) triggerKey.action.started += Toggle;
|
||||
if (triggerKey != null)
|
||||
triggerKey.action.started += OnTriggerKey;
|
||||
|
||||
if (settingsManager == null)
|
||||
{
|
||||
@ -38,9 +41,8 @@ namespace RR.Common.UI
|
||||
settingsManager.OnSettingsLoaded += UpdateSettings;
|
||||
settingsManager.OnSettingsSaved += SaveSettingsHandler;
|
||||
|
||||
if (saveButton) saveButton.onClick.AddListener(SaveAndClose);
|
||||
|
||||
gameObject.SetActive(false);
|
||||
if (saveButton)
|
||||
saveButton.onClick.AddListener(SaveAndClose);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@ -51,14 +53,16 @@ namespace RR.Common.UI
|
||||
settingsManager.OnSettingsSaved -= SaveSettingsHandler;
|
||||
}
|
||||
|
||||
if (triggerKey != null) triggerKey.action.started -= Toggle;
|
||||
if (triggerKey != null)
|
||||
triggerKey.action.started -= OnTriggerKey;
|
||||
|
||||
if (saveButton) saveButton.onClick.RemoveListener(SaveAndClose);
|
||||
if (saveButton)
|
||||
saveButton.onClick.RemoveListener(SaveAndClose);
|
||||
}
|
||||
|
||||
private void Toggle(InputAction.CallbackContext ctx)
|
||||
private void OnTriggerKey(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (gameObject.activeSelf)
|
||||
if (IsOpen)
|
||||
{
|
||||
SaveAndClose();
|
||||
}
|
||||
@ -68,22 +72,22 @@ namespace RR.Common.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void Open()
|
||||
/// <summary>
|
||||
/// Переопределяем Open для обновления настроек при открытии.
|
||||
/// </summary>
|
||||
public override void Open()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(0f, 1f, fadeDuration));
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
StartCoroutine(Fade(1f, 0f, fadeDuration, () => { gameObject.SetActive(false); }));
|
||||
UpdateSettings();
|
||||
base.Open();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
UpdateSettings();
|
||||
settingsUIList.SaveSettings();
|
||||
if (settingsUIList == null)
|
||||
settingsUIList = content.GetComponent<SettingsUIList>();
|
||||
|
||||
if (settingsUIList != null)
|
||||
settingsUIList.SaveSettings();
|
||||
}
|
||||
|
||||
public void SaveAndClose()
|
||||
@ -94,38 +98,17 @@ namespace RR.Common.UI
|
||||
|
||||
private void UpdateSettings()
|
||||
{
|
||||
if (settingsUIList == null) settingsUIList = content.GetComponent<SettingsUIList>();
|
||||
if (settingsUIList != null) settingsUIList.UpdateSettings(settingsManager.GetCurrentGameSettings());
|
||||
if (settingsUIList == null)
|
||||
settingsUIList = content.GetComponent<SettingsUIList>();
|
||||
|
||||
if (settingsUIList != null)
|
||||
settingsUIList.UpdateSettings(settingsManager.GetCurrentGameSettings());
|
||||
}
|
||||
|
||||
private void SaveSettingsHandler()
|
||||
{
|
||||
GameManager.Logger.Log(name, "Saved settings");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для плавного перехода (затухания)
|
||||
/// </summary>
|
||||
/// <param name="from">Начальная прозрачность</param>
|
||||
/// <param name="to">Конечная прозрачность</param>
|
||||
/// <param name="time">Время перехода</param>
|
||||
/// <param name="onDone">Действие при выполнении перехода</param>
|
||||
private IEnumerator Fade(float from, float to, float time, System.Action onDone = null)
|
||||
{
|
||||
if (!canvasGroup) { onDone?.Invoke(); yield break; }
|
||||
float t = 0f;
|
||||
canvasGroup.alpha = from;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
while (t < time)
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
canvasGroup.alpha = Mathf.Lerp(from, to, t / time);
|
||||
yield return null;
|
||||
}
|
||||
canvasGroup.alpha = to;
|
||||
canvasGroup.blocksRaycasts = to > 0.99f;
|
||||
onDone?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
Assets/Scripts/UI/TimeFormatHelper.cs
Normal file
22
Assets/Scripts/UI/TimeFormatHelper.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для форматирования времени в UI.
|
||||
/// </summary>
|
||||
public static class TimeFormatHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Форматирует время в строку вида mm:ss.
|
||||
/// </summary>
|
||||
/// <param name="seconds">Время в секундах</param>
|
||||
/// <returns>Отформатированная строка времени</returns>
|
||||
public static string FormatTime(float seconds)
|
||||
{
|
||||
TimeSpan interval = TimeSpan.FromSeconds(seconds);
|
||||
return interval.ToString(@"mm\:ss");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/UI/TimeFormatHelper.cs.meta
Normal file
3
Assets/Scripts/UI/TimeFormatHelper.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bba4010c66d4779b16aeaa3fda74069
|
||||
timeCreated: 1765888933
|
||||
134
Assets/Scripts/UI/WinBannerController.cs
Normal file
134
Assets/Scripts/UI/WinBannerController.cs
Normal file
@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Контроллер баннера выигрыша.
|
||||
/// Модальное окно, которое отображается при победе игрока.
|
||||
/// </summary>
|
||||
public class WinBannerController : Modal
|
||||
{
|
||||
[Header("Элементы баннера выигрыша")]
|
||||
[SerializeField] private TextMeshProUGUI winText;
|
||||
[SerializeField] private Button mainMenuButton;
|
||||
|
||||
[Header("QR-коды")]
|
||||
[Tooltip("Контейнер для размещения QR-кодов. QR-коды создаются динамически.")]
|
||||
[SerializeField] private RectTransform qrCodesContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при закрытии баннера выигрыша.
|
||||
/// </summary>
|
||||
public event Action WinBannerDismissed;
|
||||
|
||||
private readonly List<GameObject> _createdQRCodeObjects = new();
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (mainMenuButton)
|
||||
{
|
||||
mainMenuButton.onClick.AddListener(OnMainMenuClicked);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Показывает баннер выигрыша с указанным временем игры.
|
||||
/// </summary>
|
||||
/// <param name="gameTime">Время игры в секундах</param>
|
||||
public void ShowWin(float gameTime)
|
||||
{
|
||||
if (winText)
|
||||
{
|
||||
winText.text = $"Время игры: {TimeFormatHelper.FormatTime(gameTime)}";
|
||||
}
|
||||
|
||||
// Отображаем QR-коды
|
||||
DisplayQRCodes();
|
||||
|
||||
Open();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отображает QR-коды из PrizeSystemManager.
|
||||
/// Создаёт объекты полностью динамически без префаба.
|
||||
/// </summary>
|
||||
private void DisplayQRCodes()
|
||||
{
|
||||
// Очищаем ранее созданные QR-коды
|
||||
ClearQRCodes();
|
||||
|
||||
if (qrCodesContainer == null) return;
|
||||
if (PrizeSystemManager.Instance == null) return;
|
||||
|
||||
Texture2D[] qrCodes = PrizeSystemManager.Instance.GetQRCodes();
|
||||
if (qrCodes == null || qrCodes.Length == 0) return;
|
||||
|
||||
// Создаём QR-коды динамически
|
||||
foreach (Texture2D qrTexture in qrCodes)
|
||||
{
|
||||
if (qrTexture == null) continue;
|
||||
|
||||
// Создаём GameObject с Image
|
||||
GameObject qrObject = new GameObject("QRCode", typeof(RectTransform), typeof(Image));
|
||||
qrObject.transform.SetParent(qrCodesContainer, false);
|
||||
|
||||
// Конвертируем Texture2D в Sprite
|
||||
Sprite qrSprite = Sprite.Create(
|
||||
qrTexture,
|
||||
new Rect(0, 0, qrTexture.width, qrTexture.height),
|
||||
new Vector2(0.5f, 0.5f)
|
||||
);
|
||||
|
||||
// Настраиваем Image
|
||||
Image image = qrObject.GetComponent<Image>();
|
||||
image.sprite = qrSprite;
|
||||
image.raycastTarget = false;
|
||||
|
||||
_createdQRCodeObjects.Add(qrObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает созданные QR-код объекты.
|
||||
/// </summary>
|
||||
private void ClearQRCodes()
|
||||
{
|
||||
foreach (GameObject obj in _createdQRCodeObjects)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
Destroy(obj);
|
||||
}
|
||||
}
|
||||
_createdQRCodeObjects.Clear();
|
||||
}
|
||||
|
||||
private void OnMainMenuClicked()
|
||||
{
|
||||
Close();
|
||||
WinBannerDismissed?.Invoke();
|
||||
|
||||
if (GameManager.Instance)
|
||||
{
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.LoadScene("MainMenu", false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
ClearQRCodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/UI/WinBannerController.cs.meta
Normal file
3
Assets/Scripts/UI/WinBannerController.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e0feede366043ef951637fa0f816177
|
||||
timeCreated: 1765888795
|
||||
@ -701,7 +701,7 @@ PlayerSettings:
|
||||
framebufferDepthMemorylessMode: 0
|
||||
qualitySettingsNames: []
|
||||
projectName: Mario-like platformer
|
||||
organizationId: dis_bro
|
||||
organizationId:
|
||||
cloudEnabled: 0
|
||||
legacyClampBlendShapeWeights: 0
|
||||
hmiLoadingImage: {fileID: 0}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user