feat(prizes, ui): complete Mar 24-Apr 2 sprint ahead of schedule
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- завершен спринт 24 марта - 2 апреля с опережением плана и закрытием задач раньше срока - расширена и стабилизирована система призов: добавлены и доработаны QR-tier конфиги, новые типы и модели данных, сохранение `qrCodes` в `prizes.json`, fallback-логика и синхронизация через `PrizeSystemManager`, `PrizeDatabase` и `LevelPrizeConfig` - переработан flow настройки QR-призов: добавлены `QRCodeSettingsModal` и связанный UI-код, разнесены короткий текст приза и полный текст сообщения, добавлены шаблоны форматирования, корректная подстановка текста в QR и в UI-модалках, а также сохранение конфигурации между запусками - доработан flow суперприза и текстовых настроек: добавлены fallback-правила показа, корректная обработка пустых строк и строк из одних пробелов, обновлены binders и показ текста в меню - реализована и обновлена отдельная настройка витрины: добавлена `PrizeStandSettingsModal`, обновлена навигация строк, логика выбора ячейки и порогов очков, циклический выбор ячеек, ускорение изменения значений и исправления для keyboard/gamepad UX - доработана игровая модалка витрины `PrizeStandModal`: добавлен вывод очков игрока, форматируемый текст счета, улучшено отображение ячеек и связанная логика выбора приза - исправлен подсчет очков и интеграция с prize flow: `ScoreSystem` переведен на событийное обновление текущего счета, добавлены `CurrentScore`, `CurrentScoreChanged` и корректный display score, витрина и связанные UI-элементы теперь получают актуальные очки, а не нулевые или несвоевременные preview-значения - переработаны модальные окна и навигация UI: улучшены `Modal`, `ModalManager`, `UIRowPointer`, `RulesModal`, `QRCodeModal`, `PrizeStandModal`, `LevelPrizeSettingsModal` и др., чтобы фокус, указатель и submit работали консистентно без мыши - обновлен win/prize UI: доработаны `WinBannerController`, `GameEndUIController`, `MainMenu`, `QRCodeUIController`, `TopPanelTextController` и настройки UI, улучшены тексты баннеров, отображение призов и итогового счета - обновлены сцены и ассеты под новый flow: `MainMenu`, `Level UI`, prefab'ы строк настроек и кнопок, глобальные префабы, шрифты TMP, иконки, recovery-данные и ресурсы UI - добавлены вспомогательные UI-компоненты и служебные скрипты: `ButtonTextFocusColor`, `SelectableDescriptionTarget`, `QRTier` и другие изменения, поддерживающие новый конфигурируемый prize UI
This commit is contained in:
parent
8528059a71
commit
78d7679e2b
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -66,5 +66,6 @@
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.sln": "*.csproj",
|
||||
},
|
||||
"dotnet.defaultSolution": "mario-like-platformer.sln"
|
||||
"dotnet.defaultSolution": "mario-like-platformer.sln",
|
||||
"chatgpt.openOnStartup": true
|
||||
}
|
||||
8
Assets/Animations/UI.meta
Normal file
8
Assets/Animations/UI.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f24e48a876fb8864691e82711ee77300
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
807
Assets/Animations/UI/ContinueButtonLoseBanner.controller
Normal file
807
Assets/Animations/UI/ContinueButtonLoseBanner.controller
Normal file
@ -0,0 +1,807 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1102 &-9160166684881753567
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Disabled
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: -3375221253030246349}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!74 &-8805915101983332661
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Pressed
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves: []
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings: []
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 1
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
--- !u!74 &-6884236676401631380
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Normal
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves:
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.r
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0.29411766
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.g
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0.1882353
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.b
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.a
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings:
|
||||
- serializedVersion: 2
|
||||
path: 271242783
|
||||
attribute: 2110649717
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
typeID: 114
|
||||
customType: 0
|
||||
isPPtrCurve: 0
|
||||
isIntCurve: 0
|
||||
isSerializeReferenceCurve: 0
|
||||
- serializedVersion: 2
|
||||
path: 271242783
|
||||
attribute: 269488542
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
typeID: 114
|
||||
customType: 0
|
||||
isPPtrCurve: 0
|
||||
isIntCurve: 0
|
||||
isSerializeReferenceCurve: 0
|
||||
- serializedVersion: 2
|
||||
path: 271242783
|
||||
attribute: 1618666769
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
typeID: 114
|
||||
customType: 0
|
||||
isPPtrCurve: 0
|
||||
isIntCurve: 0
|
||||
isSerializeReferenceCurve: 0
|
||||
- serializedVersion: 2
|
||||
path: 271242783
|
||||
attribute: 4185109675
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
typeID: 114
|
||||
customType: 0
|
||||
isPPtrCurve: 0
|
||||
isIntCurve: 0
|
||||
isSerializeReferenceCurve: 0
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 0
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves:
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.r
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0.29411766
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.g
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0.1882353
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.b
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
- serializedVersion: 2
|
||||
curve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 136
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
attribute: m_fontColor.a
|
||||
path: Text (TMP)
|
||||
classID: 114
|
||||
script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
flags: 0
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
--- !u!1101 &-6761771471712618867
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Highlighted
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 7930268325184067746}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1102 &-6541527621936351886
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Selected
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 2032299745283349265}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!74 &-3375221253030246349
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Disabled
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves: []
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings: []
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 1
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
--- !u!1102 &-1947531597702220630
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Pressed
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: -8805915101983332661}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: ContinueButtonLoseBanner
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters:
|
||||
- m_Name: Normal
|
||||
m_Type: 9
|
||||
m_DefaultFloat: 0
|
||||
m_DefaultInt: 0
|
||||
m_DefaultBool: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
- m_Name: Highlighted
|
||||
m_Type: 9
|
||||
m_DefaultFloat: 0
|
||||
m_DefaultInt: 0
|
||||
m_DefaultBool: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
- m_Name: Pressed
|
||||
m_Type: 9
|
||||
m_DefaultFloat: 0
|
||||
m_DefaultInt: 0
|
||||
m_DefaultBool: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
- m_Name: Selected
|
||||
m_Type: 9
|
||||
m_DefaultFloat: 0
|
||||
m_DefaultInt: 0
|
||||
m_DefaultBool: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
- m_Name: Disabled
|
||||
m_Type: 9
|
||||
m_DefaultFloat: 0
|
||||
m_DefaultInt: 0
|
||||
m_DefaultBool: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: Base Layer
|
||||
m_StateMachine: {fileID: 4174081742720699029}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
--- !u!1101 &1956646551242916261
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Disabled
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: -9160166684881753567}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!74 &2032299745283349265
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Selected
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves: []
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings: []
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 1
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
--- !u!1101 &2170026047367861550
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Normal
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 2628336966096873171}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1102 &2628336966096873171
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Normal
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: -6884236676401631380}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1107 &4174081742720699029
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Base Layer
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: 2628336966096873171}
|
||||
m_Position: {x: 270, y: 10, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: 7930268325184067746}
|
||||
m_Position: {x: 310, y: 70, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -1947531597702220630}
|
||||
m_Position: {x: 340, y: 140, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -6541527621936351886}
|
||||
m_Position: {x: 380, y: 200, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -9160166684881753567}
|
||||
m_Position: {x: 410, y: 270, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions:
|
||||
- {fileID: 2170026047367861550}
|
||||
- {fileID: -6761771471712618867}
|
||||
- {fileID: 6233993829499982361}
|
||||
- {fileID: 4588445812326766186}
|
||||
- {fileID: 1956646551242916261}
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: 2628336966096873171}
|
||||
--- !u!1101 &4588445812326766186
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Selected
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: -6541527621936351886}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!74 &4752574029292502486
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Highlighted
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves: []
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings: []
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 1
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
--- !u!1101 &6233993829499982361
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Pressed
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: -1947531597702220630}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1102 &7930268325184067746
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Highlighted
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 4752574029292502486}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94f2eda47e53ad246af3c15fec1f5ac1
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -267,10 +267,10 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: N
|
||||
m_text: "\u041D"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 0161d805a3764c089bef00bfe00793f5, type: 2}
|
||||
m_sharedMaterial: {fileID: 2147374, guid: 0161d805a3764c089bef00bfe00793f5, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -294,7 +294,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 101.85
|
||||
m_fontSize: 125.8
|
||||
m_fontSizeBase: 200
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -356,7 +356,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7722770590907352781
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -68,7 +68,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: -8111322212487550922, guid: 31b2877e77610584cac3a41c46e85320, type: 3}
|
||||
m_Sprite: {fileID: 8218771907821357143, guid: dd3ae1d0db6319e48abc746c0d56666c, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
@ -77,7 +77,7 @@ MonoBehaviour:
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
m_PixelsPerUnitMultiplier: 2
|
||||
--- !u!114 &7538078367188529335
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -101,9 +101,9 @@ MonoBehaviour:
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_PressedColor: {r: 0, g: 0.29411766, b: 0.1882353, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_DisabledColor: {r: 0.76862746, g: 0.76862746, b: 0.76862746, a: 1}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
@ -198,7 +198,7 @@ MonoBehaviour:
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 0
|
||||
m_Spacing: 0
|
||||
m_Spacing: 10
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 1
|
||||
@ -356,8 +356,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 15, y: 0}
|
||||
m_SizeDelta: {x: -30, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7557072363394649782
|
||||
CanvasRenderer:
|
||||
@ -396,8 +396,8 @@ MonoBehaviour:
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4281479730
|
||||
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
rgba: 4288954963
|
||||
m_fontColor: {r: 0.3254902, g: 0.25882354, b: 0.6431373, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
@ -414,14 +414,14 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 48
|
||||
m_fontSize: 36
|
||||
m_fontSizeBase: 24
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 24
|
||||
m_fontSizeMax: 48
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_fontSizeMin: 16
|
||||
m_fontSizeMax: 36
|
||||
m_fontStyle: 16
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
@ -453,7 +453,7 @@ MonoBehaviour:
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_margin: {x: 0, y: 0, z: 72.914246, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
|
||||
@ -124,8 +124,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -20, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 2.5}
|
||||
m_SizeDelta: {x: -56, y: -5}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2544164544592874034
|
||||
CanvasRenderer:
|
||||
@ -157,8 +157,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -188,14 +188,14 @@ MonoBehaviour:
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 48
|
||||
m_fontStyle: 0
|
||||
m_fontStyle: 1
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacing: -40
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
@ -294,7 +294,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: -8111322212487550922, guid: 31b2877e77610584cac3a41c46e85320, type: 3}
|
||||
m_Sprite: {fileID: 941719080601148414, guid: ad2afac0a797cb24790e071edd921cb9, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
@ -303,7 +303,7 @@ MonoBehaviour:
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
m_PixelsPerUnitMultiplier: 2.5
|
||||
--- !u!114 &7403115420713256969
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -142,8 +142,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -167,14 +167,14 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 37.2
|
||||
m_fontSize: 41.85
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
|
||||
@ -67,8 +67,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -92,14 +92,14 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 37.2
|
||||
m_fontSize: 41.85
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
|
||||
@ -74,7 +74,7 @@ MonoBehaviour:
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
m_PixelsPerUnitMultiplier: 10
|
||||
--- !u!1 &860684354036165143
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -142,8 +142,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -167,7 +167,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 37.2
|
||||
m_fontSize: 41.85
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -286,7 +286,7 @@ MonoBehaviour:
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
m_PixelsPerUnitMultiplier: 10
|
||||
--- !u!1 &1708180760562552811
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -563,8 +563,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: 100%
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
|
||||
@ -67,8 +67,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -92,14 +92,14 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 37.2
|
||||
m_fontSize: 41.85
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
@ -249,6 +249,7 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 5595287843043283953}
|
||||
- {fileID: 1097500978698976594}
|
||||
m_Father: {fileID: 3024015130496851427}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
@ -285,7 +286,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
@ -294,6 +295,81 @@ MonoBehaviour:
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 10
|
||||
--- !u!1 &5480912208465806252
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 5595287843043283953}
|
||||
- component: {fileID: 2346390858226532368}
|
||||
- component: {fileID: 3903572989110119516}
|
||||
m_Layer: 0
|
||||
m_Name: Checkmark Off
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &5595287843043283953
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5480912208465806252}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4046930335402022471}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -20, y: -20}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2346390858226532368
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5480912208465806252}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &3903572989110119516
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5480912208465806252}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.76862746, g: 0.76862746, b: 0.76862746, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &7951234635662685345
|
||||
GameObject:
|
||||
@ -438,14 +514,14 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_Color: {r: 0.80392164, g: 1, b: 0.10196079, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: -6415649246072548017, guid: 27834320931067c4b9127246cba948d2, type: 3}
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
|
||||
@ -67,8 +67,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -92,7 +92,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 37.2
|
||||
m_fontSize: 41.85
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -204,8 +204,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: 999999
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_sharedMaterial: {fileID: 818154761001154010, guid: 7041394783e612f4eae9131c0a535951, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -229,7 +229,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 38.9
|
||||
m_fontSize: 45.45
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -346,8 +346,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 125, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_AnchoredPosition: {x: 100, y: 0}
|
||||
m_SizeDelta: {x: 50, y: 75}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7372200330221479758
|
||||
CanvasRenderer:
|
||||
@ -546,8 +546,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -125, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_AnchoredPosition: {x: -100, y: 0}
|
||||
m_SizeDelta: {x: 50, y: 75}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6286042809169003938
|
||||
CanvasRenderer:
|
||||
|
||||
@ -92,10 +92,13 @@ MonoBehaviour:
|
||||
roleHandlerBindings:
|
||||
- role: prize_stand
|
||||
handlerType: 1
|
||||
prizeStandBoxesCount: 24
|
||||
prizeStandBoxesCount: 5
|
||||
- role: prize_stand_controller
|
||||
handlerType: 1
|
||||
prizeStandBoxesCount: 24
|
||||
prizeStandBoxesCount: 5
|
||||
- role: vitrina
|
||||
handlerType: 1
|
||||
prizeStandBoxesCount: 5
|
||||
--- !u!114 &-6245296037342915518
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -134,23 +137,26 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 6e1ef22bfa89b354ea58dcf83572936a, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Managers.PrizeSystemManager
|
||||
givePrizes: 1
|
||||
qrCodes:
|
||||
- generateQRCode: 1
|
||||
tier: 0
|
||||
linkPrefix: https://t.me/
|
||||
suffix: "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435!
|
||||
\u042F \u0432\u044B\u0438\u0433\u0440\u0430\u043B \u0432 \u0438\u0433\u0440\u0435!
|
||||
\u0416\u0434\u0443 \u0441\u0432\u043E\u0439 \u043F\u0440\u0438\u0437!"
|
||||
messageFormat: "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435!\\n\u042F
|
||||
\u0432\u044B\u0438\u0433\u0440\u0430\u043B \u0432 \u0438\u0433\u0440\u0435!
|
||||
\u0416\u0434\u0443 \u0441\u0432\u043E\u0439 \u043F\u0440\u0438\u0437: {0}"
|
||||
prizeText:
|
||||
darkColor: {x: 0, y: 0, z: 1}
|
||||
lightColor: {x: 1, y: 1, z: 1}
|
||||
- generateQRCode: 1
|
||||
tier: 0
|
||||
linkPrefix: https://wa.me/
|
||||
suffix: "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435!\\n
|
||||
\u042F \u0432\u044B\u0438\u0433\u0440\u0430\u043B \u0432 \u0438\u0433\u0440\u0435!
|
||||
\u0416\u0434\u0443 \u0441\u0432\u043E\u0439 \u043F\u0440\u0438\u0437!"
|
||||
messageFormat: "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435!\\n\u042F
|
||||
\u0432\u044B\u0438\u0433\u0440\u0430\u043B \u0432 \u0438\u0433\u0440\u0435!
|
||||
\u0416\u0434\u0443 \u0441\u0432\u043E\u0439 \u043F\u0440\u0438\u0437: {0}"
|
||||
prizeText:
|
||||
darkColor: {x: 0, y: 0.5, z: 0}
|
||||
lightColor: {x: 1, y: 1, z: 1}
|
||||
_phoneNumberMask: +7XXXXXXXXXX
|
||||
phoneNumberMask: +7XXXXXXXXXX
|
||||
--- !u!114 &6824661392768214298
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -210,6 +216,8 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 10f5cf4411135b245baf35e2705982c7, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Controls.ComInputToInputSystemAdapter
|
||||
useAnyPlayer: 1
|
||||
actionBindings: []
|
||||
pinBindings:
|
||||
- pinIndex: 0
|
||||
target: 1
|
||||
@ -240,4 +248,3 @@ MonoBehaviour:
|
||||
button: 4
|
||||
dpadDirection: 1
|
||||
logCreateDevice: 1
|
||||
logPinPresses: 1
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.7 KiB |
BIN
Assets/Resources/Sprites/UI/Components/icon_1.png
Normal file
BIN
Assets/Resources/Sprites/UI/Components/icon_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
173
Assets/Resources/Sprites/UI/Components/icon_1.png.meta
Normal file
173
Assets/Resources/Sprites/UI/Components/icon_1.png.meta
Normal file
@ -0,0 +1,173 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd3ae1d0db6319e48abc746c0d56666c
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 8218771907821357143
|
||||
second: icon_1_0
|
||||
- first:
|
||||
213: -1995127661073311715
|
||||
second: icon_1_1
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WindowsStoreApps
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: icon_1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 40
|
||||
y: 184
|
||||
width: 453
|
||||
height: 166
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 137, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 758c6a75181fe0270800000000000000
|
||||
internalID: 8218771907821357143
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
icon_1_0: 8218771907821357143
|
||||
icon_1_1: -1995127661073311715
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -129,12 +129,12 @@ TextureImporter:
|
||||
name: Crystal_art_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 233
|
||||
y: 243
|
||||
width: 564
|
||||
height: 712
|
||||
x: 234
|
||||
y: 244
|
||||
width: 562
|
||||
height: 710
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
@ -159,7 +159,9 @@ TextureImporter:
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
entries:
|
||||
- key: SpriteEditor.SliceSettings
|
||||
value: '{"sliceOnImport":false,"gridCellCount":{"x":1.0,"y":1.0},"gridSpriteSize":{"x":64.0,"y":64.0},"gridSpriteOffset":{"x":0.0,"y":0.0},"gridSpritePadding":{"x":0.0,"y":0.0},"pivot":{"x":0.5,"y":0.5},"pivotPixels":{"x":0.0,"y":0.0},"autoSlicingMethod":0,"spriteAlignment":0,"pivotUnitMode":0,"slicingType":0,"keepEmptyRects":false,"isAlternate":false}'
|
||||
nameFileIdTable:
|
||||
Crystal_art_0: -1993425484501360214
|
||||
mipmapLimitGroupName:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@ namespace RR.Game2D.Animations
|
||||
private SFXAsset _stepSounds;
|
||||
private SFXAsset _hurtSounds;
|
||||
private Vector3 _vfxSpawnPoint;
|
||||
private bool _deathScoreRegistered;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@ -74,6 +75,12 @@ namespace RR.Game2D.Animations
|
||||
|
||||
public void OnDeathEnd()
|
||||
{
|
||||
if (!_deathScoreRegistered)
|
||||
{
|
||||
ScoreSystem.Instance?.RegisterEnemyKilled();
|
||||
_deathScoreRegistered = true;
|
||||
}
|
||||
|
||||
// Создаем визуальный эффект смерти
|
||||
if (deathVFX) VFXPool.Instance.Spawn(deathVFX, _vfxSpawnPoint, 1f);
|
||||
|
||||
@ -88,4 +95,4 @@ namespace RR.Game2D.Animations
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_stepSounds.soundEffects, transform.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +151,6 @@ namespace RR.Game2D.Behaviour
|
||||
if (_isDead) return;
|
||||
_isDead = true;
|
||||
|
||||
ScoreSystem.Instance?.RegisterEnemyKilled();
|
||||
_animator.Play("Death");
|
||||
// Destroy(gameObject);
|
||||
}
|
||||
|
||||
@ -29,6 +29,11 @@ namespace RR.Game2D.Behaviour
|
||||
{
|
||||
// Проверяем, что это игрок
|
||||
if (!collision.CompareTag("Player")) return;
|
||||
|
||||
if (GameManager.Instance != null && GameManager.Instance.IsCurrentLevelFinal())
|
||||
{
|
||||
GameManager.Instance.SetPendingWinGameTime(GameManager.Instance.GetCurrentGameTime());
|
||||
}
|
||||
|
||||
if (enablePrizeOnFinish && !_prizeGiven && HasPrizeForCurrentLevel())
|
||||
{
|
||||
|
||||
@ -23,11 +23,13 @@ namespace RR.Game2D.Behaviour
|
||||
public int TotalCollectedCoins => CollectedCoins;
|
||||
public int KilledEnemies { get; private set; }
|
||||
public int EnvironmentalPoints { get; private set; }
|
||||
public int CurrentScore { get; private set; }
|
||||
public int FinalScore { get; private set; }
|
||||
public bool IsFinalized { get; private set; }
|
||||
|
||||
public event Action<int, int> StatsChanged;
|
||||
public event Action<int> EnvironmentalPointsChanged;
|
||||
public event Action<int> CurrentScoreChanged;
|
||||
public event Action<int, float, bool> ScoreCalculated;
|
||||
|
||||
private bool _subscribed;
|
||||
@ -75,9 +77,10 @@ namespace RR.Game2D.Behaviour
|
||||
if (amount <= 0 || IsFinalized) return;
|
||||
|
||||
// This is the cumulative coin count for score calculation.
|
||||
// It is intentionally separate from GameManager.Coins, which can wrap at 100 to award an extra life.
|
||||
// It is intentionally separate from GameManager.Coins, which is used by gameplay/UI and life-award thresholds.
|
||||
CollectedCoins += amount;
|
||||
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
|
||||
RefreshCurrentScore();
|
||||
}
|
||||
|
||||
public void RegisterEnemyKilled(int amount = 1)
|
||||
@ -85,6 +88,7 @@ namespace RR.Game2D.Behaviour
|
||||
if (amount <= 0 || IsFinalized) return;
|
||||
KilledEnemies += amount;
|
||||
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
|
||||
RefreshCurrentScore();
|
||||
}
|
||||
|
||||
public void RegisterEnvironmentalPoints(int amount = 1)
|
||||
@ -93,6 +97,26 @@ namespace RR.Game2D.Behaviour
|
||||
|
||||
EnvironmentalPoints += amount;
|
||||
EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints);
|
||||
RefreshCurrentScore();
|
||||
}
|
||||
|
||||
public int CalculatePreviewScore(float gameTime, bool isWin)
|
||||
{
|
||||
float e = isWin ? winMultiplier : loseMultiplier;
|
||||
float baseScore = EnvironmentalPoints + 45f * CollectedCoins + 90f * KilledEnemies + 10f * KilledEnemies * KilledEnemies;
|
||||
float rawScore = e * baseScore - 0.5f * gameTime;
|
||||
|
||||
return Mathf.Max(0, Mathf.RoundToInt(rawScore));
|
||||
}
|
||||
|
||||
public int GetDisplayScore(float gameTime, bool isWin = true)
|
||||
{
|
||||
if (IsFinalized)
|
||||
{
|
||||
return FinalScore;
|
||||
}
|
||||
|
||||
return CalculatePreviewScore(gameTime, isWin);
|
||||
}
|
||||
|
||||
public void ResetStats()
|
||||
@ -100,10 +124,12 @@ namespace RR.Game2D.Behaviour
|
||||
CollectedCoins = 0;
|
||||
KilledEnemies = 0;
|
||||
EnvironmentalPoints = 0;
|
||||
CurrentScore = 0;
|
||||
FinalScore = 0;
|
||||
IsFinalized = false;
|
||||
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
|
||||
EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints);
|
||||
CurrentScoreChanged?.Invoke(CurrentScore);
|
||||
}
|
||||
|
||||
private void TrySubscribe()
|
||||
@ -138,14 +164,24 @@ namespace RR.Game2D.Behaviour
|
||||
{
|
||||
if (IsFinalized) return;
|
||||
|
||||
float e = isWin ? winMultiplier : loseMultiplier;
|
||||
float baseScore = EnvironmentalPoints + 45f * CollectedCoins + 90f * KilledEnemies + 10f * KilledEnemies * KilledEnemies;
|
||||
float rawScore = e * baseScore - 0.5f * gameTime;
|
||||
|
||||
FinalScore = Mathf.Max(0, Mathf.RoundToInt(rawScore));
|
||||
FinalScore = CalculatePreviewScore(gameTime, isWin);
|
||||
CurrentScore = FinalScore;
|
||||
IsFinalized = true;
|
||||
|
||||
CurrentScoreChanged?.Invoke(CurrentScore);
|
||||
ScoreCalculated?.Invoke(FinalScore, gameTime, isWin);
|
||||
}
|
||||
|
||||
private void RefreshCurrentScore()
|
||||
{
|
||||
if (IsFinalized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float gameTime = GameManager.Instance != null ? GameManager.Instance.GetCurrentGameTime() : 0f;
|
||||
CurrentScore = CalculatePreviewScore(gameTime, true);
|
||||
CurrentScoreChanged?.Invoke(CurrentScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,22 +193,8 @@ namespace RR.Common.Controls
|
||||
|
||||
public PrizeStandController GetPrizeStandController(string deviceId = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
return _handlers.TryGetValue(deviceId, out IComControllerHandler handler)
|
||||
? handler as PrizeStandController
|
||||
: null;
|
||||
}
|
||||
|
||||
foreach (IComControllerHandler handler in _handlers.Values)
|
||||
{
|
||||
if (handler is PrizeStandController standController)
|
||||
{
|
||||
return standController;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
// Делегируем поиск витрины в ComSystemManager
|
||||
return ComSystemManager.GetPrizeStandController(deviceId);
|
||||
}
|
||||
|
||||
private string[] SelectPorts(string[] allPorts)
|
||||
|
||||
@ -49,7 +49,8 @@ namespace RR.Common.Controls
|
||||
[SerializeField] private RoleHandlerBinding[] roleHandlerBindings =
|
||||
{
|
||||
new RoleHandlerBinding { role = "prize_stand", handlerType = ControllerHandlerType.PrizeStand, prizeStandBoxesCount = 24 },
|
||||
new RoleHandlerBinding { role = "prize_stand_controller", handlerType = ControllerHandlerType.PrizeStand, prizeStandBoxesCount = 24 }
|
||||
new RoleHandlerBinding { role = "prize_stand_controller", handlerType = ControllerHandlerType.PrizeStand, prizeStandBoxesCount = 24 },
|
||||
new RoleHandlerBinding { role = "vitrina", handlerType = ControllerHandlerType.PrizeStand, prizeStandBoxesCount = 24 }
|
||||
};
|
||||
|
||||
public static UnityEvent<int> OnCreditAdded;
|
||||
@ -70,6 +71,8 @@ namespace RR.Common.Controls
|
||||
private string _lastDefaultDeviceId = string.Empty;
|
||||
private string _lastDefaultRole = string.Empty;
|
||||
private string _lastDefaultMac = string.Empty;
|
||||
private PrizeStandController _currentPrizeStand;
|
||||
private string _prizeStandDeviceId = string.Empty;
|
||||
|
||||
public LightController Light => _light;
|
||||
|
||||
@ -209,7 +212,17 @@ namespace RR.Common.Controls
|
||||
|
||||
public static PrizeStandController GetPrizeStandController(string deviceId = null)
|
||||
{
|
||||
return _instance?._manager?.GetPrizeStandController(deviceId);
|
||||
if (_instance == null)
|
||||
return null;
|
||||
|
||||
// Если указан конкретный deviceId и он совпадает с витриной
|
||||
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
return deviceId == _instance._prizeStandDeviceId ? _instance._currentPrizeStand : null;
|
||||
}
|
||||
|
||||
// Иначе возвращаем текущую витрину
|
||||
return _instance._currentPrizeStand;
|
||||
}
|
||||
|
||||
private IComControllerHandler CreateRoleHandler(string deviceId, string role)
|
||||
@ -230,9 +243,13 @@ namespace RR.Common.Controls
|
||||
PrizeStandController controller = new PrizeStandController(
|
||||
deviceId,
|
||||
(id, message) => _manager != null && _manager.SendToDevice(id, message),
|
||||
message => GameManager.Logger?.Log("PrizeStandController", $"[{deviceId}] {message}"));
|
||||
|
||||
controller.SetBoxesCount(binding.prizeStandBoxesCount);
|
||||
message => GameManager.Logger?.Log("PrizeStandController", $"[{deviceId}] {message}"),
|
||||
binding.prizeStandBoxesCount);
|
||||
|
||||
// Сохраняем витрину в ComSystemManager
|
||||
_currentPrizeStand = controller;
|
||||
_prizeStandDeviceId = deviceId;
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
@ -241,9 +258,8 @@ namespace RR.Common.Controls
|
||||
|
||||
private RoleHandlerBinding FindRoleBinding(string role)
|
||||
{
|
||||
for (int i = 0; i < roleHandlerBindings.Length; i++)
|
||||
foreach (RoleHandlerBinding binding in roleHandlerBindings)
|
||||
{
|
||||
RoleHandlerBinding binding = roleHandlerBindings[i];
|
||||
if (binding == null || string.IsNullOrWhiteSpace(binding.role))
|
||||
{
|
||||
continue;
|
||||
@ -285,6 +301,13 @@ namespace RR.Common.Controls
|
||||
|
||||
while (_manager.TryDequeueDisconnected(out DeviceDisconnectMessage dcMsg))
|
||||
{
|
||||
// Очищаем витрину если она отключилась
|
||||
if (dcMsg.DeviceId == _prizeStandDeviceId)
|
||||
{
|
||||
_currentPrizeStand = null;
|
||||
_prizeStandDeviceId = string.Empty;
|
||||
}
|
||||
|
||||
// Запоминаем идентификатор отключившегося устройства по умолчанию
|
||||
if (dcMsg.DeviceId == _lastDefaultDeviceId || string.IsNullOrWhiteSpace(_manager.DefaultDeviceId))
|
||||
{
|
||||
@ -378,6 +401,8 @@ namespace RR.Common.Controls
|
||||
_manager?.ShutdownAll(false);
|
||||
_manager = null;
|
||||
_initializeInvoked = false;
|
||||
_currentPrizeStand = null;
|
||||
_prizeStandDeviceId = string.Empty;
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
@ -385,6 +410,8 @@ namespace RR.Common.Controls
|
||||
_manager?.ShutdownAll(true);
|
||||
_manager = null;
|
||||
_initializeInvoked = false;
|
||||
_currentPrizeStand = null;
|
||||
_prizeStandDeviceId = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -407,7 +434,7 @@ namespace RR.Common.Controls
|
||||
bool hasOldRole = !string.IsNullOrWhiteSpace(oldRole);
|
||||
bool hasNewRole = !string.IsNullOrWhiteSpace(newRole);
|
||||
|
||||
// Если оба роля известны — сравниваем по роли
|
||||
// Если обе роли известны — сравниваем по роли
|
||||
if (hasOldRole && hasNewRole)
|
||||
{
|
||||
return string.Equals(oldRole, newRole, StringComparison.OrdinalIgnoreCase);
|
||||
@ -418,4 +445,3 @@ namespace RR.Common.Controls
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ namespace RR.Common.Controls
|
||||
// Зависит от ComHub и обрабатывает контроллеры витрины по ROLE.
|
||||
public sealed class PrizeStandController : IComControllerHandler
|
||||
{
|
||||
public static readonly string[] SupportedRoles = { "prize_stand", "prize_stand_controller" };
|
||||
public static readonly string[] SupportedRoles = { "prize_stand", "prize_stand_controller", "vitrina" };
|
||||
|
||||
/// <summary>
|
||||
/// По завершении операции открытия.
|
||||
@ -37,11 +37,16 @@ namespace RR.Common.Controls
|
||||
|
||||
private int _boxesCount;
|
||||
|
||||
public PrizeStandController(string deviceId, Func<string, string, bool> sendToDevice, Action<string> log)
|
||||
public PrizeStandController(
|
||||
string deviceId,
|
||||
Func<string, string, bool> sendToDevice,
|
||||
Action<string> log,
|
||||
int boxesCount = 0)
|
||||
{
|
||||
_deviceId = deviceId;
|
||||
_sendToDevice = sendToDevice;
|
||||
_log = log;
|
||||
_boxesCount = Math.Max(0, boxesCount);
|
||||
}
|
||||
|
||||
public void SetBoxesCount(int boxesCount)
|
||||
@ -49,6 +54,78 @@ namespace RR.Common.Controls
|
||||
_boxesCount = Math.Max(0, boxesCount);
|
||||
}
|
||||
|
||||
public int GetBoxesCount()
|
||||
{
|
||||
return _boxesCount;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<int, BoxStatus>> GetStatusesAsync(float timeoutSeconds = StatusRequestTimeout)
|
||||
{
|
||||
if (_boxesCount <= 0)
|
||||
{
|
||||
return new Dictionary<int, BoxStatus>();
|
||||
}
|
||||
|
||||
ClearStatusQueue();
|
||||
SendCommand(RequestStandStatus);
|
||||
|
||||
Dictionary<int, BoxStatus> statuses = new();
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
||||
while (sw.Elapsed.TotalSeconds < timeoutSeconds && statuses.Count < _boxesCount)
|
||||
{
|
||||
while (_statusQueue.TryDequeue(out BoxStatusEntry entry))
|
||||
{
|
||||
statuses[entry.BoxNumber] = entry.Status;
|
||||
|
||||
if (statuses.Count >= _boxesCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (statuses.Count >= _boxesCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
if (statuses.Count < _boxesCount)
|
||||
{
|
||||
_log?.Invoke("[Модуль витрины. Не дождался всех статусов ячеек]\n" +
|
||||
$"Получено статусов: {statuses.Count}/{_boxesCount}");
|
||||
}
|
||||
|
||||
return statuses;
|
||||
}
|
||||
|
||||
public async Task<int[]> GetReadyBoxesAsync(float timeoutSeconds = StatusRequestTimeout)
|
||||
{
|
||||
Dictionary<int, BoxStatus> allStatuses = await GetStatusesAsync(timeoutSeconds);
|
||||
|
||||
return allStatuses
|
||||
.Where(kvp => ReadyToOpenStatuses.Contains(kvp.Value))
|
||||
.Select(kvp => kvp.Key)
|
||||
.OrderBy(boxNumber => boxNumber)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public int[] GetReadyBoxes(int timeoutMs = 1000)
|
||||
{
|
||||
try
|
||||
{
|
||||
Task<int[]> task = Task.Run(() => GetReadyBoxesAsync(timeoutMs / 1000f));
|
||||
return task.Wait(timeoutMs) ? task.Result : Array.Empty<int>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.Invoke($"[Модуль витрины] Ошибка при получении готовых ячеек: {ex.Message}");
|
||||
return Array.Empty<int>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenBoxAsync(int boxNumber)
|
||||
{
|
||||
if (!await _lock.WaitAsync(0))
|
||||
@ -291,4 +368,3 @@ namespace RR.Common.Controls
|
||||
Error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,8 @@ namespace RR.Game2D.Core.Managers
|
||||
private int StagesByWorld { get; set; } = 3;
|
||||
public int Lives { get; set; } = 3;
|
||||
public int Coins { get; private set; } = 0;
|
||||
private int _lastLifeAwardCoinThreshold = 0;
|
||||
private float? _pendingWinGameTime;
|
||||
|
||||
private float _gameStartTime;
|
||||
|
||||
@ -339,6 +341,8 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
Lives = livesToGive;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
Coins = 0;
|
||||
_lastLifeAwardCoinThreshold = 0;
|
||||
_pendingWinGameTime = null;
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
ScoreSystem.Instance?.ResetStats();
|
||||
|
||||
@ -378,6 +382,8 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
Lives = StartingLives + 1;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
Coins = 0;
|
||||
_lastLifeAwardCoinThreshold = 0;
|
||||
_pendingWinGameTime = null;
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
ScoreSystem.Instance?.ResetStats();
|
||||
|
||||
@ -407,7 +413,7 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
|
||||
private void GameWin()
|
||||
{
|
||||
float totalTime = Time.unscaledTime - _gameStartTime;
|
||||
float totalTime = _pendingWinGameTime ?? (Time.unscaledTime - _gameStartTime);
|
||||
|
||||
// Отключаем отслеживание бездействия
|
||||
if (Inactivity != null) Inactivity.DisableInactivityCheck();
|
||||
@ -415,6 +421,7 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
Light?.SetLight(LightMode.FinalWave, 30f);
|
||||
|
||||
GameWonEvent?.Invoke(totalTime);
|
||||
_pendingWinGameTime = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -443,6 +450,8 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
Lives--;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
Coins = 0;
|
||||
_lastLifeAwardCoinThreshold = 0;
|
||||
_pendingWinGameTime = null;
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
|
||||
//print($"Lives: {lives}");
|
||||
@ -476,6 +485,39 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCurrentLevelFinal()
|
||||
{
|
||||
int nextWorld = World;
|
||||
int nextStage = Stage;
|
||||
|
||||
if (nextStage >= StagesByWorld)
|
||||
{
|
||||
nextWorld++;
|
||||
nextStage = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextStage++;
|
||||
}
|
||||
|
||||
return !SceneInBuildSettingsExists($"{nextWorld}-{nextStage}");
|
||||
}
|
||||
|
||||
public float GetCurrentGameTime()
|
||||
{
|
||||
return Time.unscaledTime - _gameStartTime;
|
||||
}
|
||||
|
||||
public void SetPendingWinGameTime(float gameTime)
|
||||
{
|
||||
_pendingWinGameTime = Mathf.Max(0f, gameTime);
|
||||
}
|
||||
|
||||
public float GetPendingOrCurrentWinGameTime()
|
||||
{
|
||||
return _pendingWinGameTime ?? GetCurrentGameTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Загрузка сцены по названию.
|
||||
/// Вызывает одноименный метод в <see cref="SceneManager"/>.
|
||||
@ -681,14 +723,17 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
/// </summary>
|
||||
public void AddCoin(int count = 1)
|
||||
{
|
||||
if (count <= 0) return;
|
||||
|
||||
Coins += count;
|
||||
// Можно заменить на другое поведение
|
||||
if (Coins >= 100)
|
||||
|
||||
int earnedLifeThreshold = Coins / 100;
|
||||
while (_lastLifeAwardCoinThreshold < earnedLifeThreshold)
|
||||
{
|
||||
Coins -= 100;
|
||||
AddLife();
|
||||
_lastLifeAwardCoinThreshold++;
|
||||
}
|
||||
// --
|
||||
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.Settings;
|
||||
using RR.Game2D.Core.Managers;
|
||||
@ -24,6 +25,9 @@ namespace RR.Common.Managers
|
||||
private float lastInputTime;
|
||||
private bool isInMainMenu = true;
|
||||
private bool isGameActive;
|
||||
private InputSnapshot lastInputSnapshot;
|
||||
private bool hasInputSnapshot;
|
||||
private string lastComInputState = "11111111111111111111111111";
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@ -120,11 +124,11 @@ namespace RR.Common.Managers
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Проверяем на наличие любого ввода каждый кадр
|
||||
CheckForAnyInput();
|
||||
// Проверяем, изменилось ли состояние ввода в этом кадре
|
||||
CheckForInputStateChanges();
|
||||
|
||||
// Проверяем таймер бездействия
|
||||
if (enableInactivityCheck && !isInMainMenu && isGameActive)
|
||||
if (enableInactivityCheck && inactivityTimeout > 0f && !isInMainMenu && isGameActive)
|
||||
{
|
||||
float timeSinceLastInput = Time.unscaledTime - lastInputTime;
|
||||
|
||||
@ -141,53 +145,34 @@ namespace RR.Common.Managers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет на наличие любого ввода с клавиатуры, мыши или геймпада
|
||||
/// Проверяет, изменилось ли состояние ввода с прошлого кадра.
|
||||
/// Зажатая кнопка без изменений считается бездействием.
|
||||
/// </summary>
|
||||
private void CheckForAnyInput()
|
||||
private void CheckForInputStateChanges()
|
||||
{
|
||||
if (isInMainMenu || !isGameActive)
|
||||
return;
|
||||
|
||||
// Проверяем клавиатуру
|
||||
if (Keyboard.current != null && Keyboard.current.anyKey.wasPressedThisFrame)
|
||||
InputSnapshot currentSnapshot = BuildInputSnapshot();
|
||||
if (!hasInputSnapshot)
|
||||
{
|
||||
ResetInactivityTimer();
|
||||
lastInputSnapshot = currentSnapshot;
|
||||
hasInputSnapshot = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем мышь
|
||||
if (Mouse.current != null)
|
||||
if (!currentSnapshot.Equals(lastInputSnapshot))
|
||||
{
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame ||
|
||||
Mouse.current.rightButton.wasPressedThisFrame ||
|
||||
Mouse.current.middleButton.wasPressedThisFrame ||
|
||||
Mouse.current.delta.ReadValue().sqrMagnitude > 0.1f)
|
||||
{
|
||||
ResetInactivityTimer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем геймпад
|
||||
if (Gamepad.current != null)
|
||||
{
|
||||
foreach (var control in Gamepad.current.allControls)
|
||||
{
|
||||
if (control is UnityEngine.InputSystem.Controls.ButtonControl button && button.wasPressedThisFrame)
|
||||
{
|
||||
ResetInactivityTimer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastInputSnapshot = currentSnapshot;
|
||||
ResetInactivityTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnComInput(string input)
|
||||
{
|
||||
if (!isInMainMenu && isGameActive)
|
||||
{
|
||||
ResetInactivityTimer();
|
||||
}
|
||||
lastComInputState = string.IsNullOrWhiteSpace(input)
|
||||
? "11111111111111111111111111"
|
||||
: input;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -233,6 +218,7 @@ namespace RR.Common.Managers
|
||||
{
|
||||
enableInactivityCheck = false;
|
||||
isGameActive = false;
|
||||
hasInputSnapshot = false;
|
||||
Debug.Log("[InactivityManager] Главное меню загружено - проверка отключена");
|
||||
}
|
||||
|
||||
@ -244,8 +230,20 @@ namespace RR.Common.Managers
|
||||
/// </summary>
|
||||
public void EnableInactivityCheck()
|
||||
{
|
||||
if (inactivityTimeout <= 0f)
|
||||
{
|
||||
enableInactivityCheck = false;
|
||||
isGameActive = false;
|
||||
ResetInactivityTimer();
|
||||
Debug.Log("[InactivityManager] Проверка бездействия не включена: таймаут = 0");
|
||||
return;
|
||||
}
|
||||
|
||||
enableInactivityCheck = true;
|
||||
isGameActive = true;
|
||||
lastComInputState = GetCurrentComInputState();
|
||||
lastInputSnapshot = BuildInputSnapshot();
|
||||
hasInputSnapshot = true;
|
||||
ResetInactivityTimer();
|
||||
Debug.Log("[InactivityManager] Проверка бездействия включена");
|
||||
}
|
||||
@ -257,6 +255,7 @@ namespace RR.Common.Managers
|
||||
{
|
||||
enableInactivityCheck = false;
|
||||
isGameActive = false;
|
||||
hasInputSnapshot = false;
|
||||
Debug.Log("[InactivityManager] Проверка бездействия отключена");
|
||||
}
|
||||
|
||||
@ -270,6 +269,9 @@ namespace RR.Common.Managers
|
||||
|
||||
if (inactivityTimeout == 0f)
|
||||
{
|
||||
enableInactivityCheck = false;
|
||||
isGameActive = false;
|
||||
hasInputSnapshot = false;
|
||||
Debug.Log("[InactivityManager] Отслеживание бездействия отключено (таймаут = 0)");
|
||||
}
|
||||
else
|
||||
@ -278,6 +280,154 @@ namespace RR.Common.Managers
|
||||
}
|
||||
}
|
||||
|
||||
private InputSnapshot BuildInputSnapshot()
|
||||
{
|
||||
return new InputSnapshot(
|
||||
BuildKeyboardStateHash(),
|
||||
BuildMouseStateHash(),
|
||||
BuildGamepadStateHash(),
|
||||
BuildComStateHash());
|
||||
}
|
||||
|
||||
private static int BuildKeyboardStateHash()
|
||||
{
|
||||
Keyboard keyboard = Keyboard.current;
|
||||
if (keyboard == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hash = 17;
|
||||
foreach (KeyControl key in keyboard.allKeys)
|
||||
{
|
||||
if (key.isPressed)
|
||||
{
|
||||
hash = hash * 31 + (int)key.keyCode;
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static int BuildMouseStateHash()
|
||||
{
|
||||
Mouse mouse = Mouse.current;
|
||||
if (mouse == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vector2 position = mouse.position.ReadValue();
|
||||
Vector2 scroll = mouse.scroll.ReadValue();
|
||||
|
||||
int hash = 17;
|
||||
hash = hash * 31 + (mouse.leftButton.isPressed ? 1 : 0);
|
||||
hash = hash * 31 + (mouse.rightButton.isPressed ? 1 : 0);
|
||||
hash = hash * 31 + (mouse.middleButton.isPressed ? 1 : 0);
|
||||
hash = hash * 31 + Mathf.RoundToInt(position.x);
|
||||
hash = hash * 31 + Mathf.RoundToInt(position.y);
|
||||
hash = hash * 31 + Mathf.RoundToInt(scroll.x);
|
||||
hash = hash * 31 + Mathf.RoundToInt(scroll.y);
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static int BuildGamepadStateHash()
|
||||
{
|
||||
Gamepad gamepad = Gamepad.current;
|
||||
if (gamepad == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hash = 17;
|
||||
foreach (InputControl control in gamepad.allControls)
|
||||
{
|
||||
if (control is ButtonControl button && button.isPressed)
|
||||
{
|
||||
hash = hash * 31 + control.name.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 leftStick = QuantizeStick(gamepad.leftStick.ReadValue());
|
||||
Vector2 rightStick = QuantizeStick(gamepad.rightStick.ReadValue());
|
||||
float leftTrigger = QuantizeAxis(gamepad.leftTrigger.ReadValue());
|
||||
float rightTrigger = QuantizeAxis(gamepad.rightTrigger.ReadValue());
|
||||
|
||||
hash = hash * 31 + Mathf.RoundToInt(leftStick.x * 10f);
|
||||
hash = hash * 31 + Mathf.RoundToInt(leftStick.y * 10f);
|
||||
hash = hash * 31 + Mathf.RoundToInt(rightStick.x * 10f);
|
||||
hash = hash * 31 + Mathf.RoundToInt(rightStick.y * 10f);
|
||||
hash = hash * 31 + Mathf.RoundToInt(leftTrigger * 10f);
|
||||
hash = hash * 31 + Mathf.RoundToInt(rightTrigger * 10f);
|
||||
return hash;
|
||||
}
|
||||
|
||||
private int BuildComStateHash()
|
||||
{
|
||||
string currentState = GetCurrentComInputState();
|
||||
if (string.IsNullOrWhiteSpace(currentState))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hash = 17;
|
||||
for (int i = 0; i < currentState.Length; i++)
|
||||
{
|
||||
hash = hash * 31 + currentState[i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
private string GetCurrentComInputState()
|
||||
{
|
||||
string currentState = ComSystemManager.GetLastInputState();
|
||||
if (!string.IsNullOrWhiteSpace(currentState))
|
||||
{
|
||||
lastComInputState = currentState;
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(lastComInputState)
|
||||
? "11111111111111111111111111"
|
||||
: lastComInputState;
|
||||
}
|
||||
|
||||
private static Vector2 QuantizeStick(Vector2 value)
|
||||
{
|
||||
return new Vector2(QuantizeAxis(value.x), QuantizeAxis(value.y));
|
||||
}
|
||||
|
||||
private static float QuantizeAxis(float value)
|
||||
{
|
||||
if (Mathf.Abs(value) < 0.15f)
|
||||
return 0f;
|
||||
|
||||
return Mathf.Round(value * 10f) / 10f;
|
||||
}
|
||||
|
||||
private readonly struct InputSnapshot : System.IEquatable<InputSnapshot>
|
||||
{
|
||||
private readonly int _keyboardHash;
|
||||
private readonly int _mouseHash;
|
||||
private readonly int _gamepadHash;
|
||||
private readonly int _comHash;
|
||||
|
||||
public InputSnapshot(int keyboardHash, int mouseHash, int gamepadHash, int comHash)
|
||||
{
|
||||
_keyboardHash = keyboardHash;
|
||||
_mouseHash = mouseHash;
|
||||
_gamepadHash = gamepadHash;
|
||||
_comHash = comHash;
|
||||
}
|
||||
|
||||
public bool Equals(InputSnapshot other)
|
||||
{
|
||||
return _keyboardHash == other._keyboardHash
|
||||
&& _mouseHash == other._mouseHash
|
||||
&& _gamepadHash == other._gamepadHash
|
||||
&& _comHash == other._comHash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает текущее время таймаута
|
||||
/// </summary>
|
||||
|
||||
@ -6,6 +6,7 @@ using RR.Common.Settings;
|
||||
using RR.Common.UI;
|
||||
using RR.Common.Utility;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using RR.Common.Controls;
|
||||
|
||||
namespace RR.Common.Managers
|
||||
{
|
||||
@ -36,9 +37,9 @@ namespace RR.Common.Managers
|
||||
|
||||
|
||||
// ── QR-коды ──
|
||||
private readonly List<Texture2D> _generatedQRCodes = new();
|
||||
private Texture2D[] _qrCodesCache;
|
||||
private bool _qrCodesDirty = true;
|
||||
private readonly List<GeneratedQRCodeEntry> _generatedQRCodes = new();
|
||||
private readonly Dictionary<QRTier, Texture2D[]> _qrCodesCacheByTier = new();
|
||||
private List<QRCodeInfo> _defaultQRCodeConfigs = new();
|
||||
private string _phoneNumber = string.Empty;
|
||||
|
||||
// ────────────── Инициализация ──────────────
|
||||
@ -53,10 +54,12 @@ namespace RR.Common.Managers
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
_defaultQRCodeConfigs = qrCodes != null ? new List<QRCodeInfo>(qrCodes) : new List<QRCodeInfo>();
|
||||
|
||||
// Загружаем базу призов из JSON
|
||||
Database = new PrizeDatabase();
|
||||
Database.Load();
|
||||
LoadQRCodeConfigs();
|
||||
|
||||
// QR-коды (глобальные)
|
||||
RefreshPhoneNumber();
|
||||
@ -87,8 +90,28 @@ namespace RR.Common.Managers
|
||||
/// </summary>
|
||||
public void GivePrizeForCurrentLevel()
|
||||
{
|
||||
string level = $"{GameManager.Instance.World}-{GameManager.Instance.Stage}";
|
||||
GivePrizeForLevel(level);
|
||||
GivePrizeForLevel(GetCurrentLevelName());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает конфигурацию приза для текущего уровня.
|
||||
/// Если база недоступна или конфиг не найден, возвращает конфиг без приза.
|
||||
/// </summary>
|
||||
public LevelPrizeConfig GetPrizeConfigForCurrentLevel()
|
||||
{
|
||||
return GetPrizeConfigForLevel(GetCurrentLevelName());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает конфигурацию приза для указанного уровня.
|
||||
/// Если база недоступна или конфиг не найден, возвращает конфиг без приза.
|
||||
/// </summary>
|
||||
public LevelPrizeConfig GetPrizeConfigForLevel(string levelName)
|
||||
{
|
||||
if (Database == null || string.IsNullOrWhiteSpace(levelName))
|
||||
return new LevelPrizeConfig();
|
||||
|
||||
return Database.GetConfig(levelName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,7 +119,7 @@ namespace RR.Common.Managers
|
||||
/// </summary>
|
||||
public void GivePrizeForLevel(string levelName)
|
||||
{
|
||||
LevelPrizeConfig config = Database.GetConfig(levelName);
|
||||
LevelPrizeConfig config = GetPrizeConfigForLevel(levelName);
|
||||
|
||||
if (config.type == PrizeType.None)
|
||||
{
|
||||
@ -149,15 +172,116 @@ namespace RR.Common.Managers
|
||||
executor.Execute(this, new LevelPrizeConfig { type = type });
|
||||
}
|
||||
|
||||
/// <summary>Возвращает QR-коды (кешируется до перегенерации).</summary>
|
||||
public Texture2D[] GetQRCodes()
|
||||
/// <summary>Возвращает QR-коды выбранного ранга.</summary>
|
||||
public Texture2D[] GetQRCodes(QRTier tier)
|
||||
{
|
||||
if (_qrCodesDirty || _qrCodesCache == null)
|
||||
if (_qrCodesCacheByTier.TryGetValue(tier, out Texture2D[] cached))
|
||||
{
|
||||
_qrCodesCache = _generatedQRCodes.ToArray();
|
||||
_qrCodesDirty = false;
|
||||
return cached;
|
||||
}
|
||||
return _qrCodesCache;
|
||||
|
||||
List<Texture2D> textures = new();
|
||||
foreach (GeneratedQRCodeEntry entry in _generatedQRCodes)
|
||||
{
|
||||
if (entry.Texture == null)
|
||||
continue;
|
||||
|
||||
if (tier == QRTier.None || entry.Tier == tier)
|
||||
textures.Add(entry.Texture);
|
||||
}
|
||||
|
||||
if (textures.Count == 0 && tier != QRTier.None)
|
||||
{
|
||||
foreach (GeneratedQRCodeEntry entry in _generatedQRCodes)
|
||||
{
|
||||
if (entry.Texture != null && entry.Tier == QRTier.None)
|
||||
textures.Add(entry.Texture);
|
||||
}
|
||||
}
|
||||
|
||||
Texture2D[] resolved = textures.ToArray();
|
||||
_qrCodesCacheByTier[tier] = resolved;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public List<QRCodeInfo> GetQRCodeConfigs()
|
||||
{
|
||||
return qrCodes != null ? new List<QRCodeInfo>(qrCodes) : new List<QRCodeInfo>();
|
||||
}
|
||||
|
||||
public void SetQRCodeConfigs(List<QRCodeInfo> configs)
|
||||
{
|
||||
qrCodes = MergeQRCodeConfigs(configs, _defaultQRCodeConfigs);
|
||||
Database?.SetQRCodes(qrCodes);
|
||||
GenerateQRCodes();
|
||||
}
|
||||
|
||||
/// <summary>Возвращает конфигурацию глобальной витрины.</summary>
|
||||
public PrizeStandSettings GetPrizeStandSettings()
|
||||
{
|
||||
return Database?.GetPrizeStandSettings() ?? new PrizeStandSettings();
|
||||
}
|
||||
|
||||
/// <summary>Сохраняет конфигурацию глобальной витрины.</summary>
|
||||
public void SetPrizeStandSettings(PrizeStandSettings settings)
|
||||
{
|
||||
Database?.SetPrizeStandSettings(settings);
|
||||
}
|
||||
|
||||
public PrizeStandConnectionInfo GetPrizeStandConnectionInfo()
|
||||
{
|
||||
PrizeStandController controller = ComSystemManager.GetPrizeStandController();
|
||||
if (controller == null)
|
||||
return new PrizeStandConnectionInfo(false, 0);
|
||||
|
||||
return new PrizeStandConnectionInfo(true, controller.GetBoxesCount());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает первый непустой короткий текст QR-приза по указанному приоритету рангов.
|
||||
/// По умолчанию ищет в порядке Gold → Silver → Bronze.
|
||||
/// </summary>
|
||||
public string GetPreferredQRCodePrizeText(params QRTier[] tierPriority)
|
||||
{
|
||||
QRTier[] tiers = tierPriority != null && tierPriority.Length > 0
|
||||
? tierPriority
|
||||
: new[] { QRTier.Gold, QRTier.Silver, QRTier.Bronze };
|
||||
|
||||
for (int tierIndex = 0; tierIndex < tiers.Length; tierIndex++)
|
||||
{
|
||||
QRTier tier = tiers[tierIndex];
|
||||
for (int i = 0; i < qrCodes.Count; i++)
|
||||
{
|
||||
if (qrCodes[i].tier != tier)
|
||||
continue;
|
||||
|
||||
string prizeText = qrCodes[i].prizeText;
|
||||
if (!string.IsNullOrWhiteSpace(prizeText))
|
||||
return prizeText.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public string GetQRCodeModalPrizeText(QRTier tier)
|
||||
{
|
||||
string directMatch = GetQRCodeModalPrizeTextForTier(tier);
|
||||
if (!string.IsNullOrWhiteSpace(directMatch))
|
||||
{
|
||||
return directMatch;
|
||||
}
|
||||
|
||||
if (tier != QRTier.None)
|
||||
{
|
||||
string fallback = GetQRCodeModalPrizeTextForTier(QRTier.None);
|
||||
if (!string.IsNullOrWhiteSpace(fallback))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>Идёт ли выдача какого-либо приза.</summary>
|
||||
@ -191,10 +315,160 @@ namespace RR.Common.Managers
|
||||
_phoneNumber = PhoneNumberUtils.ApplyPhoneMask(_phoneNumber, phoneNumberMask);
|
||||
}
|
||||
|
||||
private void LoadQRCodeConfigs()
|
||||
{
|
||||
if (Database == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Database.HasStoredQRCodes)
|
||||
{
|
||||
List<QRCodeInfo> storedConfigs = Database.GetQRCodes();
|
||||
List<QRCodeInfo> mergedConfigs = MergeQRCodeConfigs(storedConfigs, _defaultQRCodeConfigs);
|
||||
qrCodes = mergedConfigs;
|
||||
|
||||
if (AreQRCodeConfigsDifferent(storedConfigs, mergedConfigs))
|
||||
{
|
||||
Database.SetQRCodes(mergedConfigs);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (qrCodes != null && qrCodes.Count > 0)
|
||||
{
|
||||
Database.SetQRCodes(qrCodes);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetQRCodeModalPrizeTextForTier(QRTier tier)
|
||||
{
|
||||
for (int i = 0; i < qrCodes.Count; i++)
|
||||
{
|
||||
if (qrCodes[i].tier != tier)
|
||||
continue;
|
||||
|
||||
string modalPrizeText = qrCodes[i].prizeText;
|
||||
if (!string.IsNullOrWhiteSpace(modalPrizeText))
|
||||
{
|
||||
return modalPrizeText.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static List<QRCodeInfo> MergeQRCodeConfigs(List<QRCodeInfo> storedConfigs, List<QRCodeInfo> defaultConfigs)
|
||||
{
|
||||
List<QRCodeInfo> merged = new();
|
||||
List<QRCodeInfo> stored = storedConfigs ?? new List<QRCodeInfo>();
|
||||
List<QRCodeInfo> defaults = defaultConfigs ?? new List<QRCodeInfo>();
|
||||
|
||||
if (stored.Count == 0)
|
||||
{
|
||||
return new List<QRCodeInfo>(defaults);
|
||||
}
|
||||
|
||||
Dictionary<QRTier, int> tierUsage = new();
|
||||
for (int i = 0; i < stored.Count; i++)
|
||||
{
|
||||
QRCodeInfo info = stored[i];
|
||||
int sameTierIndex = tierUsage.TryGetValue(info.tier, out int currentIndex) ? currentIndex : 0;
|
||||
tierUsage[info.tier] = sameTierIndex + 1;
|
||||
|
||||
QRCodeInfo fallback = ResolveFallbackConfig(defaults, info.tier, sameTierIndex);
|
||||
merged.Add(MergeQRCodeInfo(info, fallback));
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
private static QRCodeInfo ResolveFallbackConfig(List<QRCodeInfo> defaults, QRTier tier, int sameTierIndex)
|
||||
{
|
||||
if (defaults == null || defaults.Count == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
int exactTierSeen = 0;
|
||||
for (int i = 0; i < defaults.Count; i++)
|
||||
{
|
||||
if (defaults[i].tier != tier)
|
||||
continue;
|
||||
|
||||
if (exactTierSeen == sameTierIndex)
|
||||
return defaults[i];
|
||||
|
||||
exactTierSeen++;
|
||||
}
|
||||
|
||||
int noneTierSeen = 0;
|
||||
for (int i = 0; i < defaults.Count; i++)
|
||||
{
|
||||
if (defaults[i].tier != QRTier.None)
|
||||
continue;
|
||||
|
||||
if (noneTierSeen == sameTierIndex)
|
||||
return defaults[i];
|
||||
|
||||
noneTierSeen++;
|
||||
}
|
||||
|
||||
return defaults[0];
|
||||
}
|
||||
|
||||
private static QRCodeInfo MergeQRCodeInfo(QRCodeInfo stored, QRCodeInfo fallback)
|
||||
{
|
||||
QRCodeInfo merged = stored;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(merged.linkPrefix))
|
||||
{
|
||||
merged.linkPrefix = fallback.linkPrefix;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(merged.messageFormat) ||
|
||||
(merged.messageFormat == "{0}" && !string.IsNullOrWhiteSpace(fallback.messageFormat) && fallback.messageFormat != "{0}"))
|
||||
{
|
||||
merged.messageFormat = fallback.messageFormat;
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
private static bool AreQRCodeConfigsDifferent(List<QRCodeInfo> left, List<QRCodeInfo> right)
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
return false;
|
||||
|
||||
int leftCount = left != null ? left.Count : 0;
|
||||
int rightCount = right != null ? right.Count : 0;
|
||||
if (leftCount != rightCount)
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < leftCount; i++)
|
||||
{
|
||||
QRCodeInfo a = left[i];
|
||||
QRCodeInfo b = right[i];
|
||||
if (a.generateQRCode != b.generateQRCode ||
|
||||
a.tier != b.tier ||
|
||||
a.linkPrefix != b.linkPrefix ||
|
||||
a.messageFormat != b.messageFormat ||
|
||||
a.prizeText != b.prizeText ||
|
||||
a.darkColor != b.darkColor ||
|
||||
a.lightColor != b.lightColor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void GenerateQRCodes()
|
||||
{
|
||||
_generatedQRCodes.Clear();
|
||||
_qrCodesDirty = true;
|
||||
_qrCodesCacheByTier.Clear();
|
||||
|
||||
foreach (QRCodeInfo data in qrCodes)
|
||||
{
|
||||
@ -206,7 +480,7 @@ namespace RR.Common.Managers
|
||||
QRCodeInfo.GetColor(data.darkColor),
|
||||
QRCodeInfo.GetColor(data.lightColor));
|
||||
|
||||
_generatedQRCodes.Add(tex);
|
||||
_generatedQRCodes.Add(new GeneratedQRCodeEntry(data.tier, tex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,5 +507,37 @@ namespace RR.Common.Managers
|
||||
Debug.Log("[PrizeSystemManager] QR-коды перегенерированы.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCurrentLevelName()
|
||||
{
|
||||
if (GameManager.Instance == null)
|
||||
return string.Empty;
|
||||
|
||||
return $"{GameManager.Instance.World}-{GameManager.Instance.Stage}";
|
||||
}
|
||||
|
||||
private readonly struct GeneratedQRCodeEntry
|
||||
{
|
||||
public readonly QRTier Tier;
|
||||
public readonly Texture2D Texture;
|
||||
|
||||
public GeneratedQRCodeEntry(QRTier tier, Texture2D texture)
|
||||
{
|
||||
Tier = tier;
|
||||
Texture = texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct PrizeStandConnectionInfo
|
||||
{
|
||||
public readonly bool IsConnected;
|
||||
public readonly int ConnectedBoxesCount;
|
||||
|
||||
public PrizeStandConnectionInfo(bool isConnected, int connectedBoxesCount)
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
ConnectedBoxesCount = connectedBoxesCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using RR.Common.Utility;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
@ -12,13 +12,30 @@ namespace RR.Common.Prizes
|
||||
public class LevelPrizeConfig
|
||||
{
|
||||
public PrizeType type = PrizeType.None;
|
||||
public QRTier qrTier = QRTier.None;
|
||||
|
||||
// ── Хоппер ──
|
||||
public int hopperCapsuleCount = 1;
|
||||
public float hopperDelayBetweenCapsules = 1f;
|
||||
}
|
||||
|
||||
// ── Витрина ──
|
||||
public int prizeStandBoxCount = 6;
|
||||
/// <summary>
|
||||
/// Настройки одной ячейки витрины.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PrizeStandBoxConfig
|
||||
{
|
||||
public int boxNumber = 1;
|
||||
public int requiredScore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Глобальные настройки витрины для всей игры.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PrizeStandSettings
|
||||
{
|
||||
public List<PrizeStandBoxConfig> boxes = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -38,6 +55,8 @@ namespace RR.Common.Prizes
|
||||
[Serializable]
|
||||
internal class PrizesFileData
|
||||
{
|
||||
public PrizeStandSettings prizeStandSettings = new();
|
||||
public List<QRCodeInfo> qrCodes = new();
|
||||
public List<PrizeLevelEntry> levels = new();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using RR.Common.Utility;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
@ -13,11 +14,18 @@ namespace RR.Common.Prizes
|
||||
private const string FileName = "prizes.json";
|
||||
|
||||
private readonly Dictionary<string, LevelPrizeConfig> _configs = new();
|
||||
private PrizeStandSettings _prizeStandSettings = new();
|
||||
private List<QRCodeInfo> _qrCodes = new();
|
||||
private bool _hasStoredQRCodes;
|
||||
private string _filePath;
|
||||
|
||||
/// <summary>Путь к JSON-файлу.</summary>
|
||||
public string FilePath => _filePath;
|
||||
|
||||
/// <summary>Глобальные настройки витрины.</summary>
|
||||
public PrizeStandSettings PrizeStandSettings => _prizeStandSettings;
|
||||
public bool HasStoredQRCodes => _hasStoredQRCodes;
|
||||
|
||||
// ────────────── Загрузка / сохранение ──────────────
|
||||
|
||||
/// <summary>
|
||||
@ -31,12 +39,20 @@ namespace RR.Common.Prizes
|
||||
_filePath = Path.Combine(root, FileName);
|
||||
|
||||
_configs.Clear();
|
||||
_prizeStandSettings = new PrizeStandSettings();
|
||||
_qrCodes = new List<QRCodeInfo>();
|
||||
_hasStoredQRCodes = false;
|
||||
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
string json = File.ReadAllText(_filePath);
|
||||
var data = JsonUtility.FromJson<PrizesFileData>(json);
|
||||
|
||||
_prizeStandSettings = data?.prizeStandSettings ?? new PrizeStandSettings();
|
||||
_qrCodes = data?.qrCodes != null ? new List<QRCodeInfo>(data.qrCodes) : new List<QRCodeInfo>();
|
||||
_hasStoredQRCodes = data?.qrCodes != null;
|
||||
NormalizePrizeStandSettings();
|
||||
|
||||
if (data?.levels != null)
|
||||
{
|
||||
foreach (PrizeLevelEntry entry in data.levels)
|
||||
@ -51,6 +67,7 @@ namespace RR.Common.Prizes
|
||||
else
|
||||
{
|
||||
Save();
|
||||
_hasStoredQRCodes = false;
|
||||
Debug.Log($"[PrizeDatabase] Файл не найден — создан пустой: {_filePath}");
|
||||
}
|
||||
}
|
||||
@ -63,11 +80,14 @@ namespace RR.Common.Prizes
|
||||
if (string.IsNullOrEmpty(_filePath)) return;
|
||||
|
||||
var data = new PrizesFileData();
|
||||
data.prizeStandSettings = _prizeStandSettings ?? new PrizeStandSettings();
|
||||
data.qrCodes = _qrCodes != null ? new List<QRCodeInfo>(_qrCodes) : new List<QRCodeInfo>();
|
||||
foreach (var kvp in _configs)
|
||||
data.levels.Add(new PrizeLevelEntry { level = kvp.Key, config = kvp.Value });
|
||||
|
||||
string json = JsonUtility.ToJson(data, true);
|
||||
File.WriteAllText(_filePath, json);
|
||||
_hasStoredQRCodes = true;
|
||||
|
||||
Debug.Log($"[PrizeDatabase] Сохранено {_configs.Count} записей в {_filePath}");
|
||||
}
|
||||
@ -93,6 +113,20 @@ namespace RR.Common.Prizes
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, LevelPrizeConfig> GetAll() => _configs;
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает глобальные настройки витрины.
|
||||
/// </summary>
|
||||
public PrizeStandSettings GetPrizeStandSettings()
|
||||
{
|
||||
NormalizePrizeStandSettings();
|
||||
return _prizeStandSettings;
|
||||
}
|
||||
|
||||
public List<QRCodeInfo> GetQRCodes()
|
||||
{
|
||||
return _qrCodes != null ? new List<QRCodeInfo>(_qrCodes) : new List<QRCodeInfo>();
|
||||
}
|
||||
|
||||
// ────────────── Запись ──────────────
|
||||
|
||||
/// <summary>
|
||||
@ -113,14 +147,47 @@ namespace RR.Common.Prizes
|
||||
Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохраняет глобальные настройки витрины.
|
||||
/// </summary>
|
||||
public void SetPrizeStandSettings(PrizeStandSettings settings)
|
||||
{
|
||||
_prizeStandSettings = settings ?? new PrizeStandSettings();
|
||||
NormalizePrizeStandSettings();
|
||||
Save();
|
||||
}
|
||||
|
||||
public void SetQRCodes(List<QRCodeInfo> qrCodes)
|
||||
{
|
||||
_qrCodes = qrCodes != null ? new List<QRCodeInfo>(qrCodes) : new List<QRCodeInfo>();
|
||||
Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает все записи и сохраняет пустой файл.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_configs.Clear();
|
||||
_prizeStandSettings = new PrizeStandSettings();
|
||||
_qrCodes = new List<QRCodeInfo>();
|
||||
Save();
|
||||
}
|
||||
|
||||
private void NormalizePrizeStandSettings()
|
||||
{
|
||||
_prizeStandSettings ??= new PrizeStandSettings();
|
||||
_prizeStandSettings.boxes ??= new List<PrizeStandBoxConfig>();
|
||||
|
||||
_prizeStandSettings.boxes.Sort((a, b) => a.boxNumber.CompareTo(b.boxNumber));
|
||||
|
||||
for (int i = _prizeStandSettings.boxes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
PrizeStandBoxConfig box = _prizeStandSettings.boxes[i];
|
||||
if (box == null || box.boxNumber <= 0)
|
||||
_prizeStandSettings.boxes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.UI;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Исполнитель приза «Витрина».
|
||||
/// Показывает модальное окно с выбором ячейки, затем открывает её
|
||||
/// через <see cref="PrizeStandController"/>. Количество ячеек берётся из <see cref="LevelPrizeConfig"/>.
|
||||
/// через <see cref="PrizeStandController"/>. Количество ячеек берётся из ComSystemManager.
|
||||
/// </summary>
|
||||
public class PrizeStandPrize : PrizeBase
|
||||
{
|
||||
@ -44,7 +48,28 @@ namespace RR.Common.Prizes
|
||||
if (_activeModal != null)
|
||||
{
|
||||
_activeModal.OnBoxSelected += OnBoxSelected;
|
||||
_activeModal.Show(config.prizeStandBoxCount);
|
||||
|
||||
var stand = ComSystemManager.GetPrizeStandController();
|
||||
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
|
||||
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
|
||||
: new PrizeStandConnectionInfo(stand != null, stand != null ? stand.GetBoxesCount() : 0);
|
||||
|
||||
float gameTime = GameManager.Instance != null ? GameManager.Instance.GetPendingOrCurrentWinGameTime() : 0f;
|
||||
int finalScore = ScoreSystem.Instance != null
|
||||
? ScoreSystem.Instance.GetDisplayScore(gameTime, true)
|
||||
: 0;
|
||||
|
||||
PrizeStandSettings settings = PrizeSystemManager.Instance != null
|
||||
? PrizeSystemManager.Instance.GetPrizeStandSettings()
|
||||
: new PrizeStandSettings();
|
||||
|
||||
_activeModal.Show(
|
||||
BuildAvailability(settings, connectionInfo, finalScore),
|
||||
connectionInfo.IsConnected,
|
||||
connectionInfo.ConnectedBoxesCount,
|
||||
gameTime,
|
||||
finalScore,
|
||||
true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -103,5 +128,33 @@ namespace RR.Common.Prizes
|
||||
_isGiving = false;
|
||||
InvokeResult(success);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<PrizeStandBoxAvailability> BuildAvailability(
|
||||
PrizeStandSettings settings,
|
||||
PrizeStandConnectionInfo connectionInfo,
|
||||
int finalScore)
|
||||
{
|
||||
List<PrizeStandBoxAvailability> result = new();
|
||||
if (settings?.boxes == null)
|
||||
return result;
|
||||
|
||||
foreach (PrizeStandBoxConfig box in settings.boxes)
|
||||
{
|
||||
if (box == null || box.boxNumber <= 0)
|
||||
continue;
|
||||
|
||||
bool isConnectedBox = connectionInfo.IsConnected
|
||||
&& (connectionInfo.ConnectedBoxesCount <= 0 || box.boxNumber <= connectionInfo.ConnectedBoxesCount);
|
||||
bool isUnlocked = finalScore >= box.requiredScore;
|
||||
|
||||
result.Add(new PrizeStandBoxAvailability(
|
||||
box.boxNumber,
|
||||
box.requiredScore,
|
||||
isUnlocked,
|
||||
isConnectedBox));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using RR.Common.UI;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
@ -15,13 +17,13 @@ namespace RR.Common.Prizes
|
||||
public override bool IsGiving => _isGiving;
|
||||
|
||||
private readonly Func<QRCodeModal> _resolveModal;
|
||||
private readonly Func<Texture2D[]> _getQRCodes;
|
||||
private readonly Func<QRTier, Texture2D[]> _getQRCodes;
|
||||
private bool _isGiving;
|
||||
private QRCodeModal _activeModal;
|
||||
|
||||
/// <param name="resolveModal">Делегат для поиска модального окна в текущих сценах.</param>
|
||||
/// <param name="getQRCodes">Делегат получения QR-текстур.</param>
|
||||
public QRCodePrize(Func<QRCodeModal> resolveModal, Func<Texture2D[]> getQRCodes)
|
||||
public QRCodePrize(Func<QRCodeModal> resolveModal, Func<QRTier, Texture2D[]> getQRCodes)
|
||||
{
|
||||
_resolveModal = resolveModal;
|
||||
_getQRCodes = getQRCodes;
|
||||
@ -29,7 +31,7 @@ namespace RR.Common.Prizes
|
||||
|
||||
public override void Execute(MonoBehaviour host, LevelPrizeConfig config)
|
||||
{
|
||||
Texture2D[] codes = _getQRCodes?.Invoke();
|
||||
Texture2D[] codes = _getQRCodes?.Invoke(config != null ? config.qrTier : QRTier.None);
|
||||
|
||||
if (codes == null || codes.Length == 0)
|
||||
{
|
||||
@ -47,6 +49,11 @@ namespace RR.Common.Prizes
|
||||
}
|
||||
|
||||
_isGiving = true;
|
||||
bool showFinalScore = GameManager.Instance != null && GameManager.Instance.IsCurrentLevelFinal();
|
||||
float gameTime = GameManager.Instance != null ? GameManager.Instance.GetPendingOrCurrentWinGameTime() : 0f;
|
||||
int finalScore = showFinalScore && ScoreSystem.Instance != null
|
||||
? ScoreSystem.Instance.CalculatePreviewScore(gameTime, true)
|
||||
: 0;
|
||||
|
||||
// Отписываемся от предыдущего, если было
|
||||
if (_activeModal != null)
|
||||
@ -54,7 +61,7 @@ namespace RR.Common.Prizes
|
||||
|
||||
_activeModal = modal;
|
||||
_activeModal.OnClosed += OnModalClosed;
|
||||
_activeModal.ShowQRCodes(codes);
|
||||
_activeModal.ShowQRCodes(codes, config != null ? config.qrTier : QRTier.None, gameTime, finalScore, showFinalScore);
|
||||
}
|
||||
|
||||
private void OnModalClosed()
|
||||
|
||||
13
Assets/Scripts/Prizes/QRTier.cs
Normal file
13
Assets/Scripts/Prizes/QRTier.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Ранг QR-приза. Настраивается вручную для уровня.
|
||||
/// </summary>
|
||||
public enum QRTier
|
||||
{
|
||||
None = 0,
|
||||
Bronze = 1,
|
||||
Silver = 2,
|
||||
Gold = 3
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Prizes/QRTier.cs.meta
Normal file
2
Assets/Scripts/Prizes/QRTier.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff9c72c9b5bc5e449b75aa9b2bec099f
|
||||
@ -15,7 +15,8 @@ MonoBehaviour:
|
||||
entries:
|
||||
- key: volume.music
|
||||
displayName: "\u0413\u0440\u043E\u043C\u043A\u043E\u0441\u0442\u044C \u043C\u0443\u0437\u044B\u043A\u0438"
|
||||
description:
|
||||
description: "\u0420\u0435\u0433\u0443\u043B\u0438\u0440\u0443\u0435\u0442 \u0433\u0440\u043E\u043C\u043A\u043E\u0441\u0442\u044C
|
||||
\u043C\u0443\u0437\u044B\u043A\u0438 \u0432 \u0438\u0433\u0440\u0435"
|
||||
group: "\u041C\u0443\u0437\u044B\u043A\u0430 \u0438 \u0437\u0432\u0443\u043A\u0438"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -43,7 +44,9 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: volume.sounds
|
||||
displayName: "\u0413\u0440\u043E\u043C\u043A\u043E\u0441\u0442\u044C \u0437\u0432\u0443\u043A\u043E\u0432"
|
||||
description:
|
||||
description: "\u0420\u0435\u0433\u0443\u043B\u0438\u0440\u0443\u0435\u0442 \u0433\u0440\u043E\u043C\u043A\u043E\u0441\u0442\u044C
|
||||
\u0432\u0441\u0435\u0445 \u0437\u0432\u0443\u043A\u043E\u0432 \u0432 \u0438\u0433\u0440\u0435,
|
||||
\u043A\u0440\u043E\u043C\u0435 \u043C\u0443\u0437\u044B\u043A\u0438"
|
||||
group: "\u041C\u0443\u0437\u044B\u043A\u0430 \u0438 \u0437\u0432\u0443\u043A\u0438"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -71,7 +74,9 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: payment.freeGame
|
||||
displayName: "\u0411\u0435\u0441\u043F\u043B\u0430\u0442\u043D\u0430\u044F \u0438\u0433\u0440\u0430"
|
||||
description:
|
||||
description: "\u0415\u0441\u043B\u0438 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E,
|
||||
\u0434\u043B\u044F \u043D\u0430\u0447\u0430\u043B\u0430 \u0438\u0433\u0440\u044B
|
||||
\u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u043E\u043F\u043B\u0430\u0442\u0430"
|
||||
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -100,7 +105,11 @@ MonoBehaviour:
|
||||
- key: payment.showSupportNumber
|
||||
displayName: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043D\u043E\u043C\u0435\u0440
|
||||
\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
|
||||
description:
|
||||
description: "\u0415\u0441\u043B\u0438 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E,
|
||||
\u043D\u0430 \u043D\u0430\u0447\u0430\u043B\u044C\u043D\u043E\u043C \u044D\u043A\u0440\u0430\u043D\u0435
|
||||
\u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u043E\u043C\u0435\u0440
|
||||
\u0442\u0435\u0445\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0430
|
||||
(\u0437\u0430\u0434\u0430\u0435\u0442\u0441\u044F \u043D\u0438\u0436\u0435)"
|
||||
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -157,7 +166,16 @@ MonoBehaviour:
|
||||
- key: payment.paymentImpulseCost
|
||||
displayName: "\u0421\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C \u0438\u043C\u043F\u0443\u043B\u044C\u0441\u0430
|
||||
\u043E\u043F\u043B\u0430\u0442\u044B"
|
||||
description:
|
||||
description: "\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442
|
||||
\u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C \u0438\u043C\u043F\u0443\u043B\u044C\u0441\u0430
|
||||
\u043E\u043F\u043B\u0430\u0442\u044B \u0441 \u043F\u043B\u0430\u0442\u0435\u0436\u043D\u044B\u0445
|
||||
\u0441\u0438\u0441\u0442\u0435\u043C (\u043C\u043E\u043D\u0435\u0442\u043E\u043F\u0440\u0438\u0435\u043C\u043D\u0438\u043A\u0438,
|
||||
\u043A\u0443\u043F\u044E\u0440\u043E\u043F\u0440\u0438\u0435\u043C\u043D\u0438\u043A\u0438,
|
||||
\u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u044B \u043E\u043F\u043B\u0430\u0442\u044B,
|
||||
\u0436\u0435\u0442\u043E\u043D\u043E\u043F\u0440\u0438\u0435\u043C\u043D\u0438\u043A\u0438).
|
||||
\u0415\u0441\u043B\u0438 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u0430 \u0431\u0435\u0441\u043F\u043B\u0430\u0442\u043D\u0430\u044F
|
||||
\u0438\u0433\u0440\u0430, \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435
|
||||
\u043C\u0435\u043D\u044F\u0435\u0442"
|
||||
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -186,7 +204,9 @@ MonoBehaviour:
|
||||
- key: payment.startGameCost
|
||||
displayName: "\u0421\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C \u043D\u0430\u0447\u0430\u043B\u0430
|
||||
\u0438\u0433\u0440\u044B"
|
||||
description:
|
||||
description: "\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442
|
||||
\u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C \u043D\u0430\u0447\u0430\u043B\u0430
|
||||
\u043D\u043E\u0432\u043E\u0439 \u0438\u0433\u0440\u044B"
|
||||
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -215,7 +235,9 @@ MonoBehaviour:
|
||||
- key: payment.continueGameCost
|
||||
displayName: "\u0421\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u044F
|
||||
\u0438\u0433\u0440\u044B"
|
||||
description:
|
||||
description: "\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442
|
||||
\u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u044F
|
||||
\u0438\u0433\u0440\u044B \u043F\u043E\u0441\u043B\u0435 \u043F\u043E\u0440\u0430\u0436\u0435\u043D\u0438\u044F"
|
||||
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -269,12 +291,52 @@ MonoBehaviour:
|
||||
buttonSprite: {fileID: 0}
|
||||
options: []
|
||||
defaultOptionIndex: 0
|
||||
- key: prizes.showBigPrizeText
|
||||
displayName: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
|
||||
description: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0442\u0435\u043A\u0441\u0442
|
||||
\u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437\u0430. \u0412 \u0442\u0435\u043A\u0441\u0442
|
||||
\u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437\u0430 \u0437\u0430\u043D\u043E\u0441\u044F\u0442\u0441\u044F
|
||||
\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u043F\u0440\u0438\u0437\u043E\u0432
|
||||
\u0438\u0437 QR-\u043A\u043E\u0434\u043E\u0432 \u0432 \u043F\u043E\u0440\u044F\u0434\u043A\u0435:\\n\\t\u0417\u043E\u043B\u043E\u0442\u043E\u0439
|
||||
\u2192 \u0421\u0435\u0440\u0435\u0431\u0440\u044F\u043D\u044B\u0439 \u2192
|
||||
\u0411\u0440\u043E\u043D\u0437\u043E\u0432\u044B\u0439\\n\u0422\u043E \u0435\u0441\u0442\u044C,
|
||||
\u0435\u0441\u043B\u0438 \u043D\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D
|
||||
\u043F\u0440\u0438\u0437 \u0437\u0430 \u0417\u043E\u043B\u043E\u0442\u043E\u0439
|
||||
QR, \u0442\u043E \u0431\u0435\u0440\u0435\u0442\u0441\u044F \u043F\u0440\u0438\u0437
|
||||
\u0437\u0430 \u0421\u0435\u0440\u0435\u0431\u0440\u044F\u043D\u044B\u0439 QR
|
||||
\u0438 \u0442.\u0434."
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
valueType: 0
|
||||
defaultBool: 1
|
||||
defaultInt: 0
|
||||
defaultFloat: 0
|
||||
defaultString:
|
||||
minInt: 0
|
||||
maxInt: 0
|
||||
intDisplayFormat: 0
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
buttonData:
|
||||
buttonColor: {r: 0, g: 0, b: 0, a: 0}
|
||||
buttonSprite: {fileID: 0}
|
||||
options: []
|
||||
defaultOptionIndex: 0
|
||||
- key: prizes.giveSmallPrize
|
||||
displayName: "\u0412\u044B\u0434\u0430\u0432\u0430\u0442\u044C \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0438\u0439
|
||||
\u043F\u0440\u0438\u0437"
|
||||
description:
|
||||
description: "(\u0423\u0421\u0422\u0410\u0420\u0415\u041B\u041E) \u0412\u044B\u0434\u0430\u0432\u0430\u0442\u044C
|
||||
\u043B\u0438 \u043F\u0440\u0438\u0437 \u0438\u0437 \u0445\u043E\u043F\u043F\u0435\u0440\u0430"
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
shownInSettings: 0
|
||||
exposed: 0
|
||||
valueType: 0
|
||||
defaultBool: 0
|
||||
@ -301,9 +363,10 @@ MonoBehaviour:
|
||||
- key: prizes.giveBigPrize
|
||||
displayName: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C QR-\u043A\u043E\u0434\u044B
|
||||
\u0434\u043B\u044F \u0441\u0432\u044F\u0437\u0438"
|
||||
description:
|
||||
description: "(\u0423\u0421\u0422\u0410\u0420\u0415\u041B\u041E) \u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C
|
||||
\u043B\u0438 QR-\u043A\u043E\u0434\u044B \u043F\u0440\u0438 \u0432\u044B\u0438\u0433\u0440\u044B\u0448\u0435"
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
shownInSettings: 0
|
||||
exposed: 0
|
||||
valueType: 0
|
||||
defaultBool: 1
|
||||
@ -330,7 +393,9 @@ MonoBehaviour:
|
||||
- key: prizes.errorGivePrize
|
||||
displayName: "\u0412\u044B\u0434\u0430\u0432\u0430\u0442\u044C \u043F\u0440\u0438\u0437
|
||||
\u043F\u0440\u0438 \u043E\u0448\u0438\u0431\u043A\u0435"
|
||||
description:
|
||||
description: "\u0412\u044B\u0434\u0430\u0432\u0430\u0442\u044C \u043B\u0438 \u043F\u0440\u0438\u0437
|
||||
\u043F\u0440\u0438 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u0439
|
||||
\u043E\u0448\u0438\u0431\u043A\u0435 \u0432\u044B\u0434\u0430\u0447\u0438 \u043F\u0440\u0438\u0437\u0430"
|
||||
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -359,7 +424,10 @@ MonoBehaviour:
|
||||
- key: prizes.showPrizeError
|
||||
displayName: "\u0412\u044B\u0432\u043E\u0434 \u043E\u0448\u0438\u0431\u043A\u0438
|
||||
\u0432\u044B\u0434\u0430\u0447\u0438 \u0432 \u043C\u0435\u043D\u044E"
|
||||
description:
|
||||
description: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043B\u0438
|
||||
\u043E\u0448\u0438\u0431\u043A\u0443 \u0432\u044B\u0434\u0430\u0447\u0438 \u043F\u0440\u0438\u0437\u0430
|
||||
\u0445\u043E\u043F\u043F\u0435\u0440\u043E\u043C \u0432 \u0433\u043B\u0430\u0432\u043D\u043E\u043C
|
||||
\u043C\u0435\u043D\u044E"
|
||||
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -423,7 +491,7 @@ MonoBehaviour:
|
||||
defaultBool: 0
|
||||
defaultInt: 1
|
||||
defaultFloat: 0
|
||||
defaultString: "\u041D\u0435\u0432\u0430\u043B\u044F\u0448\u043A\u0430"
|
||||
defaultString:
|
||||
minInt: 0
|
||||
maxInt: 20
|
||||
intDisplayFormat: 0
|
||||
@ -443,7 +511,11 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: gameplay.startLives
|
||||
displayName: "\u041D\u0430\u0447\u0430\u043B\u044C\u043D\u044B\u0435 \u0436\u0438\u0437\u043D\u0438"
|
||||
description:
|
||||
description: "\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442
|
||||
\u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0436\u0438\u0437\u043D\u0435\u0439
|
||||
\u043F\u0440\u0438 \u043D\u0430\u0447\u0430\u043B\u0435 \u0438\u0433\u0440\u044B
|
||||
\u0431\u0435\u0437 \u0434\u043E\u043F\u043B\u0430\u0442\u044B (\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435:
|
||||
1-20)"
|
||||
group: "\u0418\u0433\u0440\u043E\u0432\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -472,7 +544,10 @@ MonoBehaviour:
|
||||
- key: gameplay.additionalLives
|
||||
displayName: "\u0414\u043E\u043F. \u0436\u0438\u0437\u043D\u0438 \u0437\u0430
|
||||
\u043A\u0440\u0435\u0434\u0438\u0442\u044B"
|
||||
description:
|
||||
description: "\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442
|
||||
\u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0445
|
||||
\u0436\u0438\u0437\u043D\u0435\u0439 \u0437\u0430 \u0434\u043E\u043F\u043B\u0430\u0442\u0443
|
||||
(\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435: 0-20)"
|
||||
group: "\u0418\u0433\u0440\u043E\u0432\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -501,7 +576,13 @@ MonoBehaviour:
|
||||
- key: gameplay.inactivityTimeout
|
||||
displayName: "\u041C\u0430\u043A\u0441. \u0432\u0440\u0435\u043C\u044F \u0431\u0435\u0437
|
||||
\u0432\u0432\u043E\u0434\u0430"
|
||||
description:
|
||||
description: "\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442
|
||||
\u0432\u0440\u0435\u043C\u044F, \u0447\u0435\u0440\u0435\u0437 \u043A\u043E\u0442\u043E\u0440\u043E\u0435
|
||||
\u0438\u0433\u0440\u0430 \u0432\u044B\u0439\u0434\u0435\u0442 \u0432 \u0433\u043B\u0430\u0432\u043D\u043E\u0435
|
||||
\u043C\u0435\u043D\u044E, \u0435\u0441\u043B\u0438 \u0438\u0433\u0440\u043E\u043A
|
||||
\u043D\u0435 \u043D\u0430\u0436\u0438\u043C\u0430\u0435\u0442 \u043A\u043D\u043E\u043F\u043E\u043A,
|
||||
\u0438\u043B\u0438 \u0435\u0441\u043B\u0438 \u043A\u043D\u043E\u043F\u043A\u0438
|
||||
\u0437\u0430\u0436\u0430\u0442\u044B"
|
||||
group: "\u0418\u0433\u0440\u043E\u0432\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -529,7 +610,11 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.statistics
|
||||
displayName: "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
|
||||
description:
|
||||
description: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0438 \u043F\u043E \u0438\u0433\u0440\u0435
|
||||
(\u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0432\u044B\u0434\u0430\u043D\u043D\u044B\u0445
|
||||
\u043F\u0440\u0438\u0437\u043E\u0432, \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E
|
||||
\u0438\u0433\u0440)"
|
||||
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -558,7 +643,10 @@ MonoBehaviour:
|
||||
- key: btn.phoneNumber
|
||||
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
|
||||
\u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0430"
|
||||
description:
|
||||
description: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u0432\u0432\u043E\u0434\u0430 \u043D\u043E\u043C\u0435\u0440\u0430 \u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0430
|
||||
\u0434\u043B\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F \u043F\u0440\u0438\u0437\u0430
|
||||
\u043F\u043E QR-\u043A\u043E\u0434\u0443"
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -587,9 +675,11 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.bigPrize
|
||||
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
|
||||
description:
|
||||
description: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u0432\u0432\u043E\u0434\u0430 \u0434\u043B\u044F \u0437\u0430\u0434\u0430\u043D\u0438\u044F
|
||||
\u043F\u0440\u0438\u0437\u0430 \u043F\u043E QR-\u043A\u043E\u0434\u0443"
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
shownInSettings: 0
|
||||
exposed: 0
|
||||
valueType: 4
|
||||
defaultBool: 0
|
||||
@ -616,7 +706,9 @@ MonoBehaviour:
|
||||
- key: btn.supportNumber
|
||||
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
|
||||
\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
|
||||
description:
|
||||
description: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u0432\u0432\u043E\u0434\u0430 \u043D\u043E\u043C\u0435\u0440\u0430 \u0442\u0435\u0445\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438
|
||||
\u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0430"
|
||||
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -645,7 +737,10 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.currency
|
||||
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0432\u0430\u043B\u044E\u0442\u0443"
|
||||
description:
|
||||
description: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u0432\u0432\u043E\u0434\u0430 \u0432\u0430\u043B\u044E\u0442\u044B \u0431\u0430\u043B\u0430\u043D\u0441\u0430
|
||||
(\u0440\u0443\u0431., \u0436\u0435\u0442\u043E\u043D\u044B, \u0442\u0435\u043D\u0433\u0435
|
||||
\u0438 \u0442.\u0434.)"
|
||||
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -673,7 +768,9 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.hopper_test
|
||||
displayName: "\u0422\u0435\u0441\u0442 \u0432\u044B\u0434\u0430\u0447\u0438 \u043F\u0440\u0438\u0437\u0430"
|
||||
description:
|
||||
description: "\u0417\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0445\u043E\u043F\u043F\u0435\u0440
|
||||
\u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0435\u0433\u043E
|
||||
\u0438\u0441\u043F\u0440\u0430\u0432\u043D\u043E\u0441\u0438"
|
||||
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -701,7 +798,10 @@ MonoBehaviour:
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.levels_prizes
|
||||
displayName: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043F\u0440\u0438\u0437\u043E\u0432"
|
||||
description:
|
||||
description: "\u041E\u0442\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043F\u0440\u0438\u0437\u043E\u0432
|
||||
\u0437\u0430 \u043A\u0430\u0436\u0434\u044B\u0439 \u0438\u0437 \u0443\u0440\u043E\u0432\u043D\u0435\u0439
|
||||
\u0438\u0433\u0440\u044B"
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
@ -727,3 +827,61 @@ MonoBehaviour:
|
||||
buttonSprite: {fileID: 0}
|
||||
options: []
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.qr_settings
|
||||
displayName: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 QR-\u043A\u043E\u0434\u043E\u0432"
|
||||
description: "\u041E\u0442\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 QR-\u043A\u043E\u0434\u043E\u0432"
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
valueType: 4
|
||||
defaultBool: 1
|
||||
defaultInt: 0
|
||||
defaultFloat: 0
|
||||
defaultString:
|
||||
minInt: 0
|
||||
maxInt: 0
|
||||
intDisplayFormat: 0
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 QR-\u043A\u043E\u0434\u043E\u0432"
|
||||
buttonData:
|
||||
buttonColor: {r: 0.16300285, g: 0.64997125, b: 0.9339623, a: 1}
|
||||
buttonSprite: {fileID: 0}
|
||||
options: []
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.stand_settings
|
||||
displayName: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0432\u0438\u0442\u0440\u0438\u043D\u044B"
|
||||
description: "\u041E\u0442\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u043E\u043A\u043D\u043E
|
||||
\u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0412\u0438\u0442\u0440\u0438\u043D\u044B"
|
||||
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
|
||||
shownInSettings: 1
|
||||
exposed: 0
|
||||
valueType: 4
|
||||
defaultBool: 1
|
||||
defaultInt: 0
|
||||
defaultFloat: 0
|
||||
defaultString:
|
||||
minInt: 0
|
||||
maxInt: 0
|
||||
intDisplayFormat: 0
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0432\u0438\u0442\u0440\u0438\u043D\u044B"
|
||||
buttonData:
|
||||
buttonColor: {r: 0.16300285, g: 0.64997125, b: 0.9339623, a: 1}
|
||||
buttonSprite: {fileID: 0}
|
||||
options: []
|
||||
defaultOptionIndex: 0
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RR.Common.Settings;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
@ -75,6 +75,28 @@ namespace RR.Common.UI
|
||||
RefreshFromSettings();
|
||||
}
|
||||
|
||||
public void BindExternalDescriptionTarget(TMP_Text targetText)
|
||||
{
|
||||
if (entry == null) return;
|
||||
|
||||
var selectables = new List<Selectable>();
|
||||
GetComponentsInChildren(true, selectables);
|
||||
|
||||
string description = entry.description ?? string.Empty;
|
||||
foreach (Selectable selectable in selectables)
|
||||
{
|
||||
if (selectable == null) continue;
|
||||
|
||||
SelectableDescriptionTarget descriptionTarget =
|
||||
selectable.GetComponent<SelectableDescriptionTarget>();
|
||||
|
||||
if (descriptionTarget == null)
|
||||
descriptionTarget = selectable.gameObject.AddComponent<SelectableDescriptionTarget>();
|
||||
|
||||
descriptionTarget.Configure(targetText, description);
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshFromSettings()
|
||||
{
|
||||
if (entry == null) return;
|
||||
@ -346,4 +368,4 @@ namespace RR.Common.UI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,11 +26,13 @@ namespace RR.Common.UI
|
||||
private bool expanded = true;
|
||||
private string prefsKey;
|
||||
private bool rememberState;
|
||||
private string groupTitle = string.Empty;
|
||||
|
||||
public void Init(string title, string prefsKey, bool defaultExpanded, bool rememberState)
|
||||
{
|
||||
this.prefsKey = prefsKey;
|
||||
this.rememberState = rememberState;
|
||||
groupTitle = title ?? string.Empty;
|
||||
|
||||
if (titleText) titleText.text = title;
|
||||
|
||||
@ -47,6 +49,19 @@ namespace RR.Common.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void BindExternalDescriptionTarget(TMP_Text targetText)
|
||||
{
|
||||
if (headerButton == null) return;
|
||||
|
||||
SelectableDescriptionTarget descriptionTarget =
|
||||
headerButton.GetComponent<SelectableDescriptionTarget>();
|
||||
|
||||
if (descriptionTarget == null)
|
||||
descriptionTarget = headerButton.gameObject.AddComponent<SelectableDescriptionTarget>();
|
||||
|
||||
descriptionTarget.Configure(targetText, $"Группа {groupTitle}");
|
||||
}
|
||||
|
||||
public void Toggle() => SetExpanded(!expanded, save: true);
|
||||
|
||||
public void SetExpanded(bool value, bool save)
|
||||
|
||||
@ -54,6 +54,7 @@ namespace RR.Common.UI
|
||||
|
||||
private SettingsButtonBinder buttonBinder;
|
||||
private SettingEntry settingEntry;
|
||||
private const string BigPrizeSettingKey = "prizes.bigPrize";
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@ -175,15 +176,25 @@ namespace RR.Common.UI
|
||||
try
|
||||
{
|
||||
string value = SettingsManager.Instance.GetString(settingKey);
|
||||
return value ?? "";
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[TextInputSettingsBinder] Failed to load value for '{settingKey}': {ex.Message}");
|
||||
return "";
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private string NormalizeConfirmedValue(string text)
|
||||
{
|
||||
if (settingKey == BigPrizeSettingKey)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim();
|
||||
}
|
||||
|
||||
return text ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при подтверждении ввода текста
|
||||
/// </summary>
|
||||
@ -194,7 +205,9 @@ namespace RR.Common.UI
|
||||
Debug.LogError("[TextInputSettingsBinder] SettingsManager is not available!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
text = NormalizeConfirmedValue(text);
|
||||
|
||||
// Сохраняем значение в настройку
|
||||
SettingsManager.Instance.SetString(settingKey, text);
|
||||
|
||||
|
||||
63
Assets/Scripts/UI/ButtonTextFocusColor.cs
Normal file
63
Assets/Scripts/UI/ButtonTextFocusColor.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Меняет цвет TMP-текста внутри кнопки при фокусе и наведении.
|
||||
/// </summary>
|
||||
public class ButtonTextFocusColor : MonoBehaviour, ISelectHandler, IDeselectHandler, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
[SerializeField] private TextMeshProUGUI targetText;
|
||||
[SerializeField] private Color normalColor = new(0f, 0.29411766f, 0.1882353f, 1f);
|
||||
[SerializeField] private Color focusedColor = Color.white;
|
||||
|
||||
private bool _isPointerInside;
|
||||
private bool _isSelected;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ApplyCurrentState();
|
||||
}
|
||||
|
||||
public void OnSelect(BaseEventData eventData)
|
||||
{
|
||||
_isSelected = true;
|
||||
ApplyCurrentState();
|
||||
}
|
||||
|
||||
public void OnDeselect(BaseEventData eventData)
|
||||
{
|
||||
_isSelected = false;
|
||||
ApplyCurrentState();
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
_isPointerInside = true;
|
||||
ApplyCurrentState();
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
_isPointerInside = false;
|
||||
ApplyCurrentState();
|
||||
}
|
||||
|
||||
private void ApplyCurrentState()
|
||||
{
|
||||
if (targetText == null)
|
||||
return;
|
||||
|
||||
targetText.color = (_isSelected || _isPointerInside) ? focusedColor : normalColor;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void Reset()
|
||||
{
|
||||
targetText = GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/ButtonTextFocusColor.cs.meta
Normal file
2
Assets/Scripts/UI/ButtonTextFocusColor.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 056e6a675e11c3a438dfa0d692787ee1
|
||||
@ -2,6 +2,9 @@ using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.UI
|
||||
@ -91,7 +94,14 @@ namespace RR.Common.UI
|
||||
|
||||
if (winBanner)
|
||||
{
|
||||
winBanner.ShowWin(gameTime);
|
||||
LevelPrizeConfig prizeConfig = PrizeSystemManager.Instance != null
|
||||
? PrizeSystemManager.Instance.GetPrizeConfigForCurrentLevel()
|
||||
: new LevelPrizeConfig();
|
||||
|
||||
bool showFinalScore = GameManager.Instance != null && GameManager.Instance.IsCurrentLevelFinal();
|
||||
int finalScore = ScoreSystem.Instance != null ? ScoreSystem.Instance.FinalScore : 0;
|
||||
|
||||
winBanner.ShowWin(gameTime, prizeConfig, finalScore, showFinalScore);
|
||||
}
|
||||
|
||||
WinBannerShown?.Invoke(gameTime);
|
||||
|
||||
@ -4,6 +4,7 @@ using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Settings;
|
||||
|
||||
namespace RR.Common.UI
|
||||
@ -135,7 +136,23 @@ namespace RR.Common.UI
|
||||
// Обновляем описание суперприза
|
||||
if (superPrizeText != null)
|
||||
{
|
||||
bool showBigPrizeText = _settingsManager.GetBool("prizes.showBigPrizeText");
|
||||
superPrizeText.gameObject.SetActive(showBigPrizeText);
|
||||
if (!showBigPrizeText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string superPrize = _settingsManager.GetString("prizes.bigPrize");
|
||||
if (string.IsNullOrWhiteSpace(superPrize))
|
||||
{
|
||||
PrizeSystemManager prizeSystemManager = PrizeSystemManager.Instance;
|
||||
if (prizeSystemManager != null)
|
||||
{
|
||||
superPrize = prizeSystemManager.GetPreferredQRCodePrizeText();
|
||||
}
|
||||
}
|
||||
|
||||
superPrizeText.text = string.Format(superPrizeFormat, superPrize);
|
||||
}
|
||||
}
|
||||
@ -159,4 +176,4 @@ namespace RR.Common.UI
|
||||
paymentText.gameObject.SetActive(!canStartGame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
@ -12,74 +12,78 @@ namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Модальное окно настройки приза за конкретный уровень.
|
||||
/// Полностью управляется с клавиатуры/геймпада через Input System.
|
||||
/// Навигация: вверх/вниз — переключение строк (зацикленно), влево/вправо — изменение значения.
|
||||
/// Настройки сохраняются автоматически при закрытии окна.
|
||||
/// Для QR настраивает ранг на уровне, для витрины редактирует общую игровую конфигурацию.
|
||||
/// </summary>
|
||||
public class LevelPrizeSettingsModal : Modal
|
||||
{
|
||||
[Header("UI — Общее")]
|
||||
[SerializeField] private TMP_Text titleText;
|
||||
[SerializeField] private TMP_Text prizeTypeLabel;
|
||||
[SerializeField] private TMP_Text detailsText;
|
||||
|
||||
[Header("UI — QR")]
|
||||
[SerializeField] private GameObject qrParamsGroup;
|
||||
[SerializeField] private TMP_Text qrTierLabel;
|
||||
|
||||
[Header("UI — Параметры хоппера")]
|
||||
[SerializeField] private GameObject hopperParamsGroup;
|
||||
[SerializeField] private TMP_Text hopperCapsuleCountLabel;
|
||||
|
||||
[Header("UI — Параметры витрины")]
|
||||
[FormerlySerializedAs("showcaseParamsGroup")]
|
||||
[SerializeField] private GameObject prizeStandParamsGroup;
|
||||
[FormerlySerializedAs("showcaseBoxCountLabel")]
|
||||
[SerializeField] private TMP_Text prizeStandStatusLabel;
|
||||
[SerializeField] private TMP_Text prizeStandBoxCountLabel;
|
||||
[SerializeField] private TMP_Text prizeStandSelectedBoxLabel;
|
||||
[SerializeField] private TMP_Text prizeStandThresholdLabel;
|
||||
|
||||
[Header("Строки (RectTransform)")]
|
||||
[Tooltip("RectTransform строки выбора типа приза.")]
|
||||
[SerializeField] private RectTransform prizeTypeRow;
|
||||
[SerializeField] private RectTransform qrTierRow;
|
||||
[SerializeField] private RectTransform hopperParamRow;
|
||||
[SerializeField] private RectTransform prizeStandBoxCountRow;
|
||||
[SerializeField] private RectTransform prizeStandSelectedBoxRow;
|
||||
[SerializeField] private RectTransform prizeStandThresholdRow;
|
||||
|
||||
[Header("Указатель")]
|
||||
[Tooltip("Компонент UIRowPointer для визуальной стрелки-указателя (опционально).")]
|
||||
[SerializeField] private UIRowPointer rowPointer;
|
||||
|
||||
[Header("Кнопка закрытия")]
|
||||
[Tooltip("Кнопка закрытия окна. При фокусе на ней указатель прячется, но Submit срабатывает.")]
|
||||
[SerializeField] private Button closeModalButton;
|
||||
|
||||
[Header("Навигация (Input System)")]
|
||||
[Tooltip("InputAction навигации (Navigate, тип Value/Vector2)")]
|
||||
[SerializeField] private InputActionReference navigateAction;
|
||||
|
||||
[Tooltip("Порог срабатывания по осям (0..1)")]
|
||||
[SerializeField] private float deadZone = 0.5f;
|
||||
|
||||
[Header("Лимиты")]
|
||||
[SerializeField] private int maxCapsules = 10;
|
||||
[SerializeField] private int maxBoxes = 48;
|
||||
[SerializeField] private int defaultPrizeStandBoxesCount = 5;
|
||||
[SerializeField] private int scoreStep = 50;
|
||||
|
||||
// ── Строки ──
|
||||
private const int ROW_TYPE = 0;
|
||||
private const int ROW_PARAM = 1;
|
||||
private const int ROW_CLOSE = 2;
|
||||
private const int ROW_COUNT = 3;
|
||||
private const int ROW_TYPE = 0;
|
||||
private const int ROW_QR_TIER = 1;
|
||||
private const int ROW_HOPPER_PARAM = 2;
|
||||
private const int ROW_STAND_BOX_COUNT = 3;
|
||||
private const int ROW_STAND_SELECTED_BOX = 4;
|
||||
private const int ROW_STAND_THRESHOLD = 5;
|
||||
private const int ROW_CLOSE = 6;
|
||||
private const int ROW_COUNT = 7;
|
||||
|
||||
private static readonly PrizeType[] PrizeTypes = (PrizeType[])Enum.GetValues(typeof(PrizeType));
|
||||
private static readonly QRTier[] QRTiers = { QRTier.Bronze, QRTier.Silver, QRTier.Gold };
|
||||
|
||||
// ── Текущее состояние ──
|
||||
private string _levelName;
|
||||
private LevelPrizeConfig _config;
|
||||
|
||||
private static readonly PrizeType[] PrizeTypes =
|
||||
(PrizeType[])Enum.GetValues(typeof(PrizeType));
|
||||
|
||||
private PrizeStandSettings _prizeStandSettings;
|
||||
private int _currentTypeIndex;
|
||||
private int _currentTierIndex;
|
||||
private int _activeRow;
|
||||
private int _selectedPrizeStandBoxIndex;
|
||||
private bool _axisHeldX;
|
||||
private bool _axisHeldY;
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается после сохранения настроек (при закрытии окна). Параметр — имя уровня.
|
||||
/// </summary>
|
||||
public event Action<string> OnApplied;
|
||||
|
||||
// ────────────── Lifecycle ──────────────
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
@ -88,7 +92,7 @@ namespace RR.Common.UI
|
||||
{
|
||||
navigateAction.action.Enable();
|
||||
navigateAction.action.performed += OnNavigatePerformed;
|
||||
navigateAction.action.canceled += OnNavigateCanceled;
|
||||
navigateAction.action.canceled += OnNavigateCanceled;
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +101,7 @@ namespace RR.Common.UI
|
||||
if (navigateAction != null && navigateAction.action != null)
|
||||
{
|
||||
navigateAction.action.performed -= OnNavigatePerformed;
|
||||
navigateAction.action.canceled -= OnNavigateCanceled;
|
||||
navigateAction.action.canceled -= OnNavigateCanceled;
|
||||
}
|
||||
|
||||
_axisHeldX = false;
|
||||
@ -105,7 +109,12 @@ namespace RR.Common.UI
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
// ────────────── Ввод ──────────────
|
||||
protected override void OnReturnedToTop()
|
||||
{
|
||||
_axisHeldX = false;
|
||||
_axisHeldY = false;
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void OnNavigatePerformed(InputAction.CallbackContext ctx)
|
||||
{
|
||||
@ -113,7 +122,6 @@ namespace RR.Common.UI
|
||||
|
||||
Vector2 v = ctx.ReadValue<Vector2>();
|
||||
|
||||
// ── Вертикаль: переключение строк ──
|
||||
if (Mathf.Abs(v.y) >= deadZone)
|
||||
{
|
||||
if (!_axisHeldY)
|
||||
@ -127,7 +135,6 @@ namespace RR.Common.UI
|
||||
_axisHeldY = false;
|
||||
}
|
||||
|
||||
// ── Горизонталь: изменение значения ──
|
||||
if (Mathf.Abs(v.x) >= deadZone)
|
||||
{
|
||||
if (!_axisHeldX)
|
||||
@ -148,8 +155,6 @@ namespace RR.Common.UI
|
||||
_axisHeldY = false;
|
||||
}
|
||||
|
||||
// ────────────── Навигация строк ──────────────
|
||||
|
||||
private void MoveRow(int delta)
|
||||
{
|
||||
int next = _activeRow;
|
||||
@ -162,14 +167,21 @@ namespace RR.Common.UI
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private bool IsRowVisible(int row)
|
||||
{
|
||||
if (row == ROW_PARAM)
|
||||
return _config.type == PrizeType.Hopper || _config.type == PrizeType.PrizeStand;
|
||||
return true; // ROW_TYPE и ROW_CLOSE видны всегда
|
||||
return row switch
|
||||
{
|
||||
ROW_QR_TIER => _config.type == PrizeType.QRCode,
|
||||
ROW_HOPPER_PARAM => _config.type == PrizeType.Hopper,
|
||||
ROW_STAND_BOX_COUNT => _config.type == PrizeType.PrizeStand,
|
||||
ROW_STAND_SELECTED_BOX => _config.type == PrizeType.PrizeStand && GetPrizeStandBoxes().Count > 0,
|
||||
ROW_STAND_THRESHOLD => _config.type == PrizeType.PrizeStand && GetPrizeStandBoxes().Count > 0,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
private void HandleLeftRight(int direction)
|
||||
@ -179,23 +191,24 @@ namespace RR.Common.UI
|
||||
case ROW_TYPE:
|
||||
CyclePrizeType(direction);
|
||||
break;
|
||||
|
||||
case ROW_PARAM:
|
||||
if (_config.type == PrizeType.Hopper)
|
||||
AdjustCapsules(direction);
|
||||
else if (_config.type == PrizeType.PrizeStand)
|
||||
AdjustBoxes(direction);
|
||||
case ROW_QR_TIER:
|
||||
AdjustQrTier(direction);
|
||||
break;
|
||||
case ROW_HOPPER_PARAM:
|
||||
AdjustCapsules(direction);
|
||||
break;
|
||||
case ROW_STAND_BOX_COUNT:
|
||||
AdjustPrizeStandBoxCount(direction);
|
||||
break;
|
||||
case ROW_STAND_SELECTED_BOX:
|
||||
AdjustSelectedPrizeStandBox(direction);
|
||||
break;
|
||||
case ROW_STAND_THRESHOLD:
|
||||
AdjustPrizeStandThreshold(direction);
|
||||
break;
|
||||
|
||||
// ROW_CLOSE — лево/право ничего не делает
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────── Публичный API ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Открывает окно настройки приза для указанного уровня.
|
||||
/// </summary>
|
||||
public void OpenForLevel(string levelName)
|
||||
{
|
||||
_levelName = levelName;
|
||||
@ -204,104 +217,196 @@ namespace RR.Common.UI
|
||||
_config = db != null
|
||||
? CopyConfig(db.GetConfig(levelName))
|
||||
: new LevelPrizeConfig();
|
||||
_prizeStandSettings = db != null
|
||||
? CopyPrizeStandSettings(db.GetPrizeStandSettings())
|
||||
: CreateDefaultPrizeStandSettings();
|
||||
|
||||
_currentTypeIndex = 0;
|
||||
for (int i = 0; i < PrizeTypes.Length; i++)
|
||||
{
|
||||
if (PrizeTypes[i] == _config.type)
|
||||
{
|
||||
_currentTypeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EnsurePrizeStandSettingsInitialized();
|
||||
|
||||
_currentTypeIndex = Array.IndexOf(PrizeTypes, _config.type);
|
||||
if (_currentTypeIndex < 0)
|
||||
_currentTypeIndex = 0;
|
||||
|
||||
_currentTierIndex = GetTierIndex(_config.qrTier);
|
||||
_selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, Mathf.Max(0, GetPrizeStandBoxes().Count - 1));
|
||||
_activeRow = ROW_TYPE;
|
||||
|
||||
AutoResolveRows();
|
||||
RefreshUI();
|
||||
RefreshRowHighlights();
|
||||
Open();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Закрывает окно и сохраняет настройки.
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
Save();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
// ────────────── Внутреннее ──────────────
|
||||
|
||||
private void CyclePrizeType(int direction)
|
||||
{
|
||||
_currentTypeIndex = (_currentTypeIndex + direction + PrizeTypes.Length) % PrizeTypes.Length;
|
||||
_config.type = PrizeTypes[_currentTypeIndex];
|
||||
|
||||
// Если строка параметров стала невидимой — перескочить
|
||||
if (_activeRow == ROW_PARAM && !IsRowVisible(ROW_PARAM))
|
||||
if (_config.type == PrizeType.QRCode && _config.qrTier == QRTier.None)
|
||||
_config.qrTier = QRTiers[0];
|
||||
|
||||
EnsurePrizeStandSettingsInitialized();
|
||||
if (!IsRowVisible(_activeRow))
|
||||
_activeRow = ROW_TYPE;
|
||||
|
||||
RefreshUI();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void AdjustQrTier(int direction)
|
||||
{
|
||||
_currentTierIndex = (_currentTierIndex + direction + QRTiers.Length) % QRTiers.Length;
|
||||
_config.qrTier = QRTiers[_currentTierIndex];
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void AdjustCapsules(int delta)
|
||||
{
|
||||
_config.hopperCapsuleCount = Mathf.Clamp(_config.hopperCapsuleCount + delta, 1, maxCapsules);
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void AdjustBoxes(int delta)
|
||||
private void AdjustPrizeStandBoxCount(int delta)
|
||||
{
|
||||
_config.prizeStandBoxCount = Mathf.Clamp(_config.prizeStandBoxCount + delta, 1, maxBoxes);
|
||||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||||
int currentCount = boxes.Count;
|
||||
int nextCount = Mathf.Clamp(currentCount + delta, 1, maxBoxes);
|
||||
if (nextCount == currentCount)
|
||||
return;
|
||||
|
||||
if (nextCount > currentCount)
|
||||
{
|
||||
for (int i = currentCount + 1; i <= nextCount; i++)
|
||||
boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
|
||||
}
|
||||
else
|
||||
{
|
||||
boxes.RemoveRange(nextCount, currentCount - nextCount);
|
||||
}
|
||||
|
||||
NormalizePrizeStandBoxes();
|
||||
_selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, boxes.Count - 1);
|
||||
RefreshUI();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void AdjustSelectedPrizeStandBox(int delta)
|
||||
{
|
||||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||||
if (boxes.Count == 0)
|
||||
return;
|
||||
|
||||
_selectedPrizeStandBoxIndex = (_selectedPrizeStandBoxIndex + delta + boxes.Count) % boxes.Count;
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void AdjustPrizeStandThreshold(int direction)
|
||||
{
|
||||
PrizeStandBoxConfig selectedBox = GetSelectedPrizeStandBox();
|
||||
if (selectedBox == null)
|
||||
return;
|
||||
|
||||
selectedBox.requiredScore = Mathf.Max(0, selectedBox.requiredScore + direction * scoreStep);
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_levelName) || _config == null) return;
|
||||
if (string.IsNullOrEmpty(_levelName) || _config == null)
|
||||
return;
|
||||
|
||||
PrizeDatabase db = PrizeSystemManager.Instance?.Database;
|
||||
if (db == null)
|
||||
PrizeSystemManager manager = PrizeSystemManager.Instance;
|
||||
if (db == null || manager == null)
|
||||
{
|
||||
Debug.LogError("[LevelPrizeSettingsModal] PrizeDatabase недоступна.");
|
||||
return;
|
||||
}
|
||||
|
||||
db.SetConfig(_levelName, _config);
|
||||
manager.SetPrizeStandSettings(_prizeStandSettings);
|
||||
Debug.Log($"[LevelPrizeSettingsModal] Уровень «{_levelName}» → приз «{_config.type}» сохранён.");
|
||||
|
||||
OnApplied?.Invoke(_levelName);
|
||||
}
|
||||
|
||||
// ────────────── UI ──────────────
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
EnsurePrizeStandSettingsInitialized();
|
||||
|
||||
if (titleText)
|
||||
titleText.text = $"Приз за уровень {_levelName}";
|
||||
|
||||
if (prizeTypeLabel)
|
||||
prizeTypeLabel.text = GetPrizeTypeName(_config.type);
|
||||
|
||||
bool isHopper = _config.type == PrizeType.Hopper;
|
||||
bool isPrizeStand = _config.type == PrizeType.PrizeStand;
|
||||
if (qrParamsGroup) qrParamsGroup.SetActive(_config.type == PrizeType.QRCode);
|
||||
if (hopperParamsGroup) hopperParamsGroup.SetActive(_config.type == PrizeType.Hopper);
|
||||
if (prizeStandParamsGroup) prizeStandParamsGroup.SetActive(_config.type == PrizeType.PrizeStand);
|
||||
|
||||
if (hopperParamsGroup) hopperParamsGroup.SetActive(isHopper);
|
||||
if (prizeStandParamsGroup) prizeStandParamsGroup.SetActive(isPrizeStand);
|
||||
if (qrTierLabel)
|
||||
qrTierLabel.text = GetTierName(_config.qrTier);
|
||||
|
||||
if (hopperCapsuleCountLabel)
|
||||
hopperCapsuleCountLabel.text = _config.hopperCapsuleCount.ToString();
|
||||
|
||||
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
|
||||
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
|
||||
: new PrizeStandConnectionInfo(false, 0);
|
||||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||||
PrizeStandBoxConfig selectedBox = GetSelectedPrizeStandBox();
|
||||
|
||||
if (prizeStandStatusLabel)
|
||||
prizeStandStatusLabel.text = connectionInfo.IsConnected
|
||||
? $"Подключена ({connectionInfo.ConnectedBoxesCount} ячеек)"
|
||||
: "Не подключена";
|
||||
|
||||
if (prizeStandBoxCountLabel)
|
||||
prizeStandBoxCountLabel.text = _config.prizeStandBoxCount.ToString();
|
||||
prizeStandBoxCountLabel.text = boxes.Count.ToString();
|
||||
|
||||
if (prizeStandSelectedBoxLabel)
|
||||
prizeStandSelectedBoxLabel.text = selectedBox != null ? selectedBox.boxNumber.ToString() : "-";
|
||||
|
||||
if (prizeStandThresholdLabel)
|
||||
prizeStandThresholdLabel.text = selectedBox != null ? selectedBox.requiredScore.ToString() : "-";
|
||||
|
||||
if (detailsText)
|
||||
detailsText.text = BuildDetailsText(connectionInfo, boxes, selectedBox);
|
||||
}
|
||||
|
||||
private string BuildDetailsText(PrizeStandConnectionInfo connectionInfo, List<PrizeStandBoxConfig> boxes, PrizeStandBoxConfig selectedBox)
|
||||
{
|
||||
return _config.type switch
|
||||
{
|
||||
PrizeType.QRCode => $"Ранг QR-приза: {GetTierName(_config.qrTier)}",
|
||||
PrizeType.Hopper => $"Капсул за выдачу: {_config.hopperCapsuleCount}",
|
||||
PrizeType.PrizeStand => BuildPrizeStandDetails(connectionInfo, boxes, selectedBox),
|
||||
_ => "Для уровня приз не назначен."
|
||||
};
|
||||
}
|
||||
|
||||
private string BuildPrizeStandDetails(PrizeStandConnectionInfo connectionInfo, List<PrizeStandBoxConfig> boxes, PrizeStandBoxConfig selectedBox)
|
||||
{
|
||||
string connectionText = connectionInfo.IsConnected
|
||||
? $"Витрина подключена, доступно на устройстве: {connectionInfo.ConnectedBoxesCount} ячеек."
|
||||
: "Витрина не подключена. Редактируется сохранённая конфигурация.";
|
||||
|
||||
string selectedText = selectedBox != null
|
||||
? $"Выбрана ячейка {selectedBox.boxNumber}, порог: {selectedBox.requiredScore} очков."
|
||||
: "Ячейки не настроены.";
|
||||
|
||||
return $"{connectionText}\nСконфигурировано ячеек: {boxes.Count}\n{selectedText}";
|
||||
}
|
||||
|
||||
private void RefreshRowHighlights()
|
||||
{
|
||||
if (_activeRow == ROW_CLOSE)
|
||||
{
|
||||
// Прячем указатель, фокус переходит на кнопку закрытия
|
||||
if (rowPointer) rowPointer.Hide();
|
||||
|
||||
if (closeModalButton && EventSystem.current)
|
||||
@ -309,7 +414,6 @@ namespace RR.Common.UI
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (rowPointer)
|
||||
{
|
||||
RectTransform target = GetActiveRowTransform();
|
||||
@ -320,39 +424,166 @@ namespace RR.Common.UI
|
||||
|
||||
private RectTransform GetActiveRowTransform()
|
||||
{
|
||||
switch (_activeRow)
|
||||
return _activeRow switch
|
||||
{
|
||||
case ROW_TYPE:
|
||||
return prizeTypeRow;
|
||||
ROW_TYPE => prizeTypeRow,
|
||||
ROW_QR_TIER => qrTierRow != null ? qrTierRow : prizeTypeRow,
|
||||
ROW_HOPPER_PARAM => hopperParamRow != null ? hopperParamRow : prizeTypeRow,
|
||||
ROW_STAND_BOX_COUNT => prizeStandBoxCountRow != null ? prizeStandBoxCountRow : prizeTypeRow,
|
||||
ROW_STAND_SELECTED_BOX => prizeStandSelectedBoxRow != null ? prizeStandSelectedBoxRow : prizeTypeRow,
|
||||
ROW_STAND_THRESHOLD => prizeStandThresholdRow != null ? prizeStandThresholdRow : prizeTypeRow,
|
||||
_ => prizeTypeRow
|
||||
};
|
||||
}
|
||||
|
||||
case ROW_PARAM:
|
||||
if (_config.type == PrizeType.Hopper && hopperParamsGroup)
|
||||
return hopperParamsGroup.transform as RectTransform;
|
||||
if (_config.type == PrizeType.PrizeStand && prizeStandParamsGroup)
|
||||
return prizeStandParamsGroup.transform as RectTransform;
|
||||
return prizeTypeRow;
|
||||
private void AutoResolveRows()
|
||||
{
|
||||
if (prizeTypeRow == null && prizeTypeLabel != null)
|
||||
prizeTypeRow = prizeTypeLabel.transform.parent as RectTransform;
|
||||
|
||||
default:
|
||||
return prizeTypeRow;
|
||||
if (qrTierRow == null && qrTierLabel != null)
|
||||
qrTierRow = qrTierLabel.transform.parent as RectTransform;
|
||||
|
||||
if (hopperParamRow == null && hopperCapsuleCountLabel != null)
|
||||
hopperParamRow = hopperCapsuleCountLabel.transform.parent as RectTransform;
|
||||
|
||||
if (prizeStandBoxCountRow == null && prizeStandBoxCountLabel != null)
|
||||
prizeStandBoxCountRow = prizeStandBoxCountLabel.transform.parent as RectTransform;
|
||||
|
||||
if (prizeStandSelectedBoxRow == null && prizeStandSelectedBoxLabel != null)
|
||||
prizeStandSelectedBoxRow = prizeStandSelectedBoxLabel.transform.parent as RectTransform;
|
||||
|
||||
if (prizeStandThresholdRow == null && prizeStandThresholdLabel != null)
|
||||
prizeStandThresholdRow = prizeStandThresholdLabel.transform.parent as RectTransform;
|
||||
}
|
||||
|
||||
private void EnsurePrizeStandSettingsInitialized()
|
||||
{
|
||||
_prizeStandSettings ??= CreateDefaultPrizeStandSettings();
|
||||
_prizeStandSettings.boxes ??= new List<PrizeStandBoxConfig>();
|
||||
|
||||
if (_prizeStandSettings.boxes.Count == 0)
|
||||
{
|
||||
int initialCount = defaultPrizeStandBoxesCount;
|
||||
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
|
||||
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
|
||||
: new PrizeStandConnectionInfo(false, 0);
|
||||
if (connectionInfo.IsConnected && connectionInfo.ConnectedBoxesCount > 0)
|
||||
initialCount = Mathf.Clamp(connectionInfo.ConnectedBoxesCount, 1, maxBoxes);
|
||||
|
||||
for (int i = 1; i <= initialCount; i++)
|
||||
_prizeStandSettings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
|
||||
}
|
||||
|
||||
NormalizePrizeStandBoxes();
|
||||
}
|
||||
|
||||
private void NormalizePrizeStandBoxes()
|
||||
{
|
||||
List<PrizeStandBoxConfig> boxes = _prizeStandSettings.boxes;
|
||||
boxes.Sort((a, b) => a.boxNumber.CompareTo(b.boxNumber));
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
if (boxes[i] == null)
|
||||
boxes[i] = new PrizeStandBoxConfig();
|
||||
|
||||
boxes[i].boxNumber = i + 1;
|
||||
boxes[i].requiredScore = Mathf.Max(0, boxes[i].requiredScore);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPrizeTypeName(PrizeType type) => type switch
|
||||
private List<PrizeStandBoxConfig> GetPrizeStandBoxes()
|
||||
{
|
||||
PrizeType.None => "Нет приза",
|
||||
PrizeType.QRCode => "QR-код",
|
||||
PrizeType.Hopper => "Хоппер",
|
||||
PrizeType.PrizeStand => "Витрина",
|
||||
_ => type.ToString()
|
||||
};
|
||||
EnsurePrizeStandSettingsInitialized();
|
||||
return _prizeStandSettings.boxes;
|
||||
}
|
||||
|
||||
private static LevelPrizeConfig CopyConfig(LevelPrizeConfig src) => new()
|
||||
private PrizeStandBoxConfig GetSelectedPrizeStandBox()
|
||||
{
|
||||
type = src.type,
|
||||
hopperCapsuleCount = src.hopperCapsuleCount,
|
||||
hopperDelayBetweenCapsules = src.hopperDelayBetweenCapsules,
|
||||
prizeStandBoxCount = src.prizeStandBoxCount
|
||||
};
|
||||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||||
if (boxes.Count == 0)
|
||||
return null;
|
||||
|
||||
_selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, boxes.Count - 1);
|
||||
return boxes[_selectedPrizeStandBoxIndex];
|
||||
}
|
||||
|
||||
private static string GetPrizeTypeName(PrizeType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
PrizeType.None => "Нет приза",
|
||||
PrizeType.QRCode => "QR-код",
|
||||
PrizeType.Hopper => "Хоппер",
|
||||
PrizeType.PrizeStand => "Витрина",
|
||||
_ => type.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetTierName(QRTier tier)
|
||||
{
|
||||
return tier switch
|
||||
{
|
||||
QRTier.Bronze => "Бронзовый",
|
||||
QRTier.Silver => "Серебряный",
|
||||
QRTier.Gold => "Золотой",
|
||||
_ => "Не задан"
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetTierIndex(QRTier tier)
|
||||
{
|
||||
int index = Array.IndexOf(QRTiers, tier);
|
||||
return index >= 0 ? index : 0;
|
||||
}
|
||||
|
||||
private static LevelPrizeConfig CopyConfig(LevelPrizeConfig src)
|
||||
{
|
||||
return new LevelPrizeConfig
|
||||
{
|
||||
type = src.type,
|
||||
qrTier = src.qrTier,
|
||||
hopperCapsuleCount = src.hopperCapsuleCount,
|
||||
hopperDelayBetweenCapsules = src.hopperDelayBetweenCapsules
|
||||
};
|
||||
}
|
||||
|
||||
private static PrizeStandSettings CopyPrizeStandSettings(PrizeStandSettings src)
|
||||
{
|
||||
PrizeStandSettings copy = new PrizeStandSettings();
|
||||
if (src?.boxes == null)
|
||||
return copy;
|
||||
|
||||
for (int i = 0; i < src.boxes.Count; i++)
|
||||
{
|
||||
PrizeStandBoxConfig box = src.boxes[i];
|
||||
if (box == null)
|
||||
continue;
|
||||
|
||||
copy.boxes.Add(new PrizeStandBoxConfig
|
||||
{
|
||||
boxNumber = box.boxNumber,
|
||||
requiredScore = box.requiredScore
|
||||
});
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private PrizeStandSettings CreateDefaultPrizeStandSettings()
|
||||
{
|
||||
PrizeStandSettings settings = new PrizeStandSettings();
|
||||
for (int i = 1; i <= defaultPrizeStandBoxesCount; i++)
|
||||
{
|
||||
settings.boxes.Add(new PrizeStandBoxConfig
|
||||
{
|
||||
boxNumber = i,
|
||||
requiredScore = 0
|
||||
});
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 503701b468074130a5d4e963e9867215
|
||||
timeCreated: 1773226679
|
||||
guid: 503701b468074130a5d4e963e9867215
|
||||
@ -354,6 +354,18 @@ namespace RR.Common.UI
|
||||
firstSelected = selectable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается, когда окно снова становится верхним после закрытия вложенной модалки.
|
||||
/// </summary>
|
||||
protected virtual void OnReturnedToTop()
|
||||
{
|
||||
}
|
||||
|
||||
internal void NotifyReturnedToTop()
|
||||
{
|
||||
OnReturnedToTop();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected virtual void Reset()
|
||||
{
|
||||
|
||||
@ -340,6 +340,7 @@ namespace RR.Common.UI
|
||||
if (topModal != null)
|
||||
{
|
||||
SetInitialFocus(topModal);
|
||||
topModal.NotifyReturnedToTop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,10 @@ namespace RR.Common.UI
|
||||
|
||||
[Tooltip("Текст заголовка по умолчанию.")]
|
||||
[SerializeField] private string defaultTitle = "Выберите ячейку";
|
||||
[Tooltip("Текст с количеством очков игрока.")]
|
||||
[SerializeField] private TMP_Text scoreText;
|
||||
[Tooltip("Формат текста очков. Используйте {0} для подстановки значения.")]
|
||||
[SerializeField] private string scoreTextFormat = "Очки за игру: {0}";
|
||||
|
||||
[Header("Навигация")]
|
||||
[Tooltip("InputAction для навигации (Navigate из Input System).")]
|
||||
@ -68,14 +72,27 @@ namespace RR.Common.UI
|
||||
/// Показывает окно с указанным количеством ячеек.
|
||||
/// </summary>
|
||||
/// <param name="boxCount">Количество ячеек для отображения.</param>
|
||||
public void Show(int boxCount)
|
||||
public void Show(
|
||||
IReadOnlyList<PrizeStandBoxAvailability> boxes,
|
||||
bool isConnected,
|
||||
int connectedBoxesCount,
|
||||
float gameTime,
|
||||
int finalScore,
|
||||
bool showFinalScore)
|
||||
{
|
||||
_selectionMade = false;
|
||||
|
||||
BuildButtons(boxCount);
|
||||
BuildButtons(boxes);
|
||||
|
||||
if (titleText != null)
|
||||
{
|
||||
titleText.text = defaultTitle;
|
||||
}
|
||||
|
||||
if (scoreText != null)
|
||||
{
|
||||
scoreText.text = BuildScoreText(finalScore);
|
||||
}
|
||||
|
||||
Open();
|
||||
GameManager.Instance?.SetPause(true);
|
||||
@ -83,7 +100,7 @@ namespace RR.Common.UI
|
||||
|
||||
// ────────────── Внутреннее ──────────────
|
||||
|
||||
private void BuildButtons(int count)
|
||||
private void BuildButtons(IReadOnlyList<PrizeStandBoxAvailability> boxes)
|
||||
{
|
||||
ClearButtons();
|
||||
|
||||
@ -93,21 +110,30 @@ namespace RR.Common.UI
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= count; i++)
|
||||
if (boxes == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
PrizeStandBoxAvailability box = boxes[i];
|
||||
GameObject btnObj = Instantiate(boxButtonPrefab, boxButtonsContainer);
|
||||
btnObj.name = $"BoxButton_{i}";
|
||||
btnObj.name = $"BoxButton_{box.BoxNumber}";
|
||||
|
||||
// Настраиваем текст
|
||||
TMP_Text label = btnObj.GetComponentInChildren<TMP_Text>();
|
||||
if (label != null)
|
||||
label.text = i.ToString();
|
||||
{
|
||||
label.text = box.IsConnected
|
||||
? $"{box.BoxNumber}\n{box.requiredScore}"
|
||||
: $"{box.BoxNumber}\nOFF";
|
||||
}
|
||||
|
||||
// Настраиваем кнопку
|
||||
Button btn = btnObj.GetComponent<Button>();
|
||||
if (btn != null)
|
||||
{
|
||||
int boxNumber = i; // локальная копия для замыкания
|
||||
int boxNumber = box.BoxNumber;
|
||||
btn.interactable = box.IsAvailable;
|
||||
btn.onClick.AddListener(() => OnBoxButtonClicked(boxNumber));
|
||||
}
|
||||
|
||||
@ -115,11 +141,14 @@ namespace RR.Common.UI
|
||||
}
|
||||
|
||||
// Устанавливаем первый элемент для фокуса
|
||||
if (_createdButtons.Count > 0)
|
||||
for (int i = 0; i < _createdButtons.Count; i++)
|
||||
{
|
||||
Button firstBtn = _createdButtons[0].GetComponent<Button>();
|
||||
if (firstBtn != null)
|
||||
Button firstBtn = _createdButtons[i].GetComponent<Button>();
|
||||
if (firstBtn != null && firstBtn.interactable)
|
||||
{
|
||||
SetFirstSelected(firstBtn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,8 +187,44 @@ namespace RR.Common.UI
|
||||
{
|
||||
base.OnDisable();
|
||||
ClearButtons();
|
||||
if (scoreText != null)
|
||||
scoreText.text = string.Empty;
|
||||
GameManager.Instance?.SetPause(false);
|
||||
}
|
||||
|
||||
private string BuildScoreText(int finalScore)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(scoreTextFormat))
|
||||
{
|
||||
return finalScore.ToString();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return string.Format(scoreTextFormat, finalScore);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return $"{scoreTextFormat} {finalScore}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct PrizeStandBoxAvailability
|
||||
{
|
||||
public readonly int BoxNumber;
|
||||
public readonly int requiredScore;
|
||||
public readonly bool IsUnlocked;
|
||||
public readonly bool IsConnected;
|
||||
public bool IsAvailable => IsUnlocked && IsConnected;
|
||||
|
||||
public PrizeStandBoxAvailability(int boxNumber, int requiredScore, bool isUnlocked, bool isConnected)
|
||||
{
|
||||
BoxNumber = boxNumber;
|
||||
this.requiredScore = requiredScore;
|
||||
IsUnlocked = isUnlocked;
|
||||
IsConnected = isConnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
638
Assets/Scripts/UI/Modal/PrizeStandSettingsModal.cs
Normal file
638
Assets/Scripts/UI/Modal/PrizeStandSettingsModal.cs
Normal file
@ -0,0 +1,638 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Отдельная модалка настройки глобальной конфигурации витрины.
|
||||
/// Позволяет менять выбранную ячейку и порог очков для нее.
|
||||
/// </summary>
|
||||
public class PrizeStandSettingsModal : Modal
|
||||
{
|
||||
[Header("UI")]
|
||||
[SerializeField] private TMP_Text titleText;
|
||||
[SerializeField] private TMP_Text connectionStatusLabel;
|
||||
[SerializeField] private TMP_Text boxCountLabel;
|
||||
[SerializeField] private TMP_Text selectedBoxLabel;
|
||||
[SerializeField] private TMP_Text thresholdLabel;
|
||||
[SerializeField] private TMP_Text detailsText;
|
||||
|
||||
[Header("Rows")]
|
||||
[SerializeField] private RectTransform selectedBoxRow;
|
||||
[SerializeField] private RectTransform thresholdRow;
|
||||
|
||||
[Header("Pointer")]
|
||||
[SerializeField] private UIRowPointer rowPointer;
|
||||
|
||||
[Header("Close")]
|
||||
[SerializeField] private Button closeModalButton;
|
||||
|
||||
[Header("Navigation")]
|
||||
[SerializeField] private InputActionReference navigateAction;
|
||||
[SerializeField] private float deadZone = 0.5f;
|
||||
|
||||
[Header("Limits")]
|
||||
[SerializeField] private int defaultBoxesCount = 5;
|
||||
|
||||
private const int RowSelectedBox = 0;
|
||||
private const int RowThreshold = 1;
|
||||
private const int RowClose = 2;
|
||||
private const int RowCount = 3;
|
||||
private const int ThresholdStep = 1;
|
||||
private const int MaxStepMultiplier = 10;
|
||||
private const float HoldResetTimeout = 0.25f;
|
||||
private const float InitialHorizontalRepeatDelay = 0.25f;
|
||||
private const float ThresholdAccelerationTime = 0.8f;
|
||||
private const float MinimumHorizontalRepeatInterval = 0.03f;
|
||||
|
||||
private PrizeStandSettings _settings;
|
||||
private int _selectedBoxIndex;
|
||||
private int _activeRow;
|
||||
private bool _axisHeldY;
|
||||
private Selectable _pendingFocusTarget;
|
||||
private int _pendingFocusFrames;
|
||||
private Selectable _selectedBoxRowSelectable;
|
||||
private Selectable _thresholdRowSelectable;
|
||||
private PrizeStandRowMoveHandler _selectedBoxMoveHandler;
|
||||
private PrizeStandRowMoveHandler _thresholdMoveHandler;
|
||||
|
||||
public event Action OnApplied;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (navigateAction != null && navigateAction.action != null)
|
||||
{
|
||||
navigateAction.action.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
_axisHeldY = false;
|
||||
ResetHorizontalHold();
|
||||
_pendingFocusTarget = null;
|
||||
_pendingFocusFrames = 0;
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
protected override void OnReturnedToTop()
|
||||
{
|
||||
_axisHeldY = false;
|
||||
ResetHorizontalHold();
|
||||
_pendingFocusTarget = null;
|
||||
_pendingFocusFrames = 0;
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
public override void Open()
|
||||
{
|
||||
PrepareForEditing();
|
||||
base.Open();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
public void OpenForEditing()
|
||||
{
|
||||
Open();
|
||||
}
|
||||
|
||||
private void PrepareForEditing()
|
||||
{
|
||||
PrizeSystemManager manager = PrizeSystemManager.Instance;
|
||||
_settings = manager != null
|
||||
? CopyPrizeStandSettings(manager.GetPrizeStandSettings())
|
||||
: CreateDefaultSettings();
|
||||
|
||||
EnsureSettingsInitialized();
|
||||
AutoResolveRows();
|
||||
AutoResolveValueLabels();
|
||||
EnsureRowSelectables();
|
||||
_selectedBoxIndex = 0;
|
||||
_activeRow = RowSelectedBox;
|
||||
|
||||
RefreshUI();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
Save();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessNavigationInput();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!_isOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyPendingFocus();
|
||||
}
|
||||
|
||||
private void ProcessNavigationInput()
|
||||
{
|
||||
if (navigateAction == null || navigateAction.action == null)
|
||||
{
|
||||
_axisHeldY = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 value = navigateAction.action.ReadValue<Vector2>();
|
||||
|
||||
if (Mathf.Abs(value.y) >= deadZone)
|
||||
{
|
||||
if (!_axisHeldY)
|
||||
{
|
||||
_axisHeldY = true;
|
||||
MoveRow(value.y > 0f ? -1 : 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_axisHeldY = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveRow(int delta)
|
||||
{
|
||||
_activeRow = (_activeRow + delta + RowCount) % RowCount;
|
||||
ResetHorizontalHold();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void AdjustSelectedBox(int direction)
|
||||
{
|
||||
EnsureSettingsInitialized();
|
||||
int boxCount = _settings.boxes.Count;
|
||||
if (boxCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedBoxIndex = (_selectedBoxIndex + direction) % boxCount;
|
||||
if (_selectedBoxIndex < 0)
|
||||
{
|
||||
_selectedBoxIndex += boxCount;
|
||||
}
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
internal void HandleRowMove(int rowIndex, int direction, int multiplier)
|
||||
{
|
||||
if (!_isOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (rowIndex)
|
||||
{
|
||||
case RowSelectedBox:
|
||||
AdjustSelectedBox(direction);
|
||||
break;
|
||||
case RowThreshold:
|
||||
AdjustThreshold(direction, multiplier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustThreshold(int direction, int multiplier)
|
||||
{
|
||||
PrizeStandBoxConfig box = GetSelectedBox();
|
||||
if (box == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
box.requiredScore = Mathf.Max(0, box.requiredScore + direction * ThresholdStep * Mathf.Max(1, multiplier));
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
PrizeSystemManager manager = PrizeSystemManager.Instance;
|
||||
if (manager == null)
|
||||
{
|
||||
Debug.LogError("[PrizeStandSettingsModal] PrizeSystemManager недоступен.");
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureSettingsInitialized();
|
||||
NormalizeBoxes();
|
||||
manager.SetPrizeStandSettings(_settings);
|
||||
OnApplied?.Invoke();
|
||||
}
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
EnsureSettingsInitialized();
|
||||
|
||||
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
|
||||
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
|
||||
: new PrizeStandConnectionInfo(false, 0);
|
||||
PrizeStandBoxConfig selectedBox = GetSelectedBox();
|
||||
|
||||
if (titleText != null)
|
||||
{
|
||||
titleText.text = "Настройки витрины";
|
||||
}
|
||||
|
||||
if (connectionStatusLabel != null)
|
||||
{
|
||||
connectionStatusLabel.text = connectionInfo.IsConnected
|
||||
? "Витрина подключена"
|
||||
: "Витрина не подключена";
|
||||
}
|
||||
|
||||
if (boxCountLabel != null)
|
||||
{
|
||||
boxCountLabel.text = _settings.boxes.Count.ToString();
|
||||
}
|
||||
|
||||
if (selectedBoxLabel != null)
|
||||
{
|
||||
selectedBoxLabel.text = selectedBox != null ? selectedBox.boxNumber.ToString() : "-";
|
||||
}
|
||||
|
||||
if (thresholdLabel != null)
|
||||
{
|
||||
thresholdLabel.text = selectedBox != null ? selectedBox.requiredScore.ToString() : "-";
|
||||
}
|
||||
|
||||
if (detailsText != null)
|
||||
detailsText.text = string.Empty;
|
||||
}
|
||||
|
||||
private void RefreshRowHighlights()
|
||||
{
|
||||
Selectable selectableToFocus = null;
|
||||
|
||||
if (_activeRow == RowClose)
|
||||
{
|
||||
if (rowPointer != null) rowPointer.Hide();
|
||||
|
||||
selectableToFocus = closeModalButton;
|
||||
}
|
||||
else
|
||||
{
|
||||
RectTransform target = _activeRow switch
|
||||
{
|
||||
RowSelectedBox => selectedBoxRow,
|
||||
RowThreshold => thresholdRow,
|
||||
_ => selectedBoxRow
|
||||
};
|
||||
|
||||
selectableToFocus = _activeRow switch
|
||||
{
|
||||
RowSelectedBox => _selectedBoxRowSelectable,
|
||||
RowThreshold => _thresholdRowSelectable,
|
||||
_ => _selectedBoxRowSelectable
|
||||
};
|
||||
|
||||
if (rowPointer != null && target != null)
|
||||
{
|
||||
rowPointer.SetTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
FocusSelectable(selectableToFocus);
|
||||
}
|
||||
|
||||
private void AutoResolveRows()
|
||||
{
|
||||
if (selectedBoxRow == null && selectedBoxLabel != null)
|
||||
{
|
||||
selectedBoxRow = selectedBoxLabel.transform.parent as RectTransform;
|
||||
}
|
||||
|
||||
if (thresholdRow == null && thresholdLabel != null)
|
||||
{
|
||||
thresholdRow = thresholdLabel.transform.parent as RectTransform;
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoResolveValueLabels()
|
||||
{
|
||||
selectedBoxLabel = ResolveValueLabel(selectedBoxRow, selectedBoxLabel);
|
||||
thresholdLabel = ResolveValueLabel(thresholdRow, thresholdLabel);
|
||||
}
|
||||
|
||||
private static TMP_Text ResolveValueLabel(RectTransform row, TMP_Text current)
|
||||
{
|
||||
if (row == null)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
TMP_Text[] texts = row.GetComponentsInChildren<TMP_Text>(true);
|
||||
TMP_Text best = null;
|
||||
float bestScore = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < texts.Length; i++)
|
||||
{
|
||||
TMP_Text text = texts[i];
|
||||
if (text == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RectTransform textRect = text.rectTransform;
|
||||
if (textRect == null || !textRect.IsChildOf(row))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 localCenter = row.InverseTransformPoint(textRect.TransformPoint(textRect.rect.center));
|
||||
float score = Mathf.Abs(localCenter.x) + Mathf.Abs(localCenter.y) * 0.25f;
|
||||
|
||||
if (best == null || score < bestScore)
|
||||
{
|
||||
best = text;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
if (best != null)
|
||||
{
|
||||
return best;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private void EnsureRowSelectables()
|
||||
{
|
||||
_selectedBoxRowSelectable = EnsureRowSelectable(selectedBoxRow);
|
||||
_thresholdRowSelectable = EnsureRowSelectable(thresholdRow);
|
||||
_selectedBoxMoveHandler = EnsureRowMoveHandler(selectedBoxRow, RowSelectedBox);
|
||||
_thresholdMoveHandler = EnsureRowMoveHandler(thresholdRow, RowThreshold);
|
||||
}
|
||||
|
||||
private Selectable EnsureRowSelectable(RectTransform row)
|
||||
{
|
||||
if (row == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Selectable selectable = row.GetComponent<Selectable>();
|
||||
if (selectable == null)
|
||||
{
|
||||
selectable = row.gameObject.AddComponent<Selectable>();
|
||||
selectable.transition = Selectable.Transition.None;
|
||||
selectable.navigation = new Navigation { mode = Navigation.Mode.None };
|
||||
}
|
||||
|
||||
selectable.interactable = true;
|
||||
return selectable;
|
||||
}
|
||||
|
||||
private PrizeStandRowMoveHandler EnsureRowMoveHandler(RectTransform row, int rowIndex)
|
||||
{
|
||||
if (row == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PrizeStandRowMoveHandler handler = row.GetComponent<PrizeStandRowMoveHandler>();
|
||||
if (handler == null)
|
||||
{
|
||||
handler = row.gameObject.AddComponent<PrizeStandRowMoveHandler>();
|
||||
}
|
||||
|
||||
handler.Initialize(this, rowIndex);
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void FocusSelectable(Selectable selectable)
|
||||
{
|
||||
if (selectable == null || !selectable.gameObject.activeInHierarchy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetFirstSelected(selectable);
|
||||
_pendingFocusTarget = selectable;
|
||||
_pendingFocusFrames = 2;
|
||||
ApplyPendingFocus();
|
||||
}
|
||||
|
||||
private void ApplyPendingFocus()
|
||||
{
|
||||
if (_pendingFocusTarget == null || _pendingFocusFrames <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EventSystem.current == null || !_pendingFocusTarget.gameObject.activeInHierarchy)
|
||||
{
|
||||
_pendingFocusTarget = null;
|
||||
_pendingFocusFrames = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (EventSystem.current.currentSelectedGameObject != _pendingFocusTarget.gameObject)
|
||||
{
|
||||
EventSystem.current.SetSelectedGameObject(null);
|
||||
EventSystem.current.SetSelectedGameObject(_pendingFocusTarget.gameObject);
|
||||
_pendingFocusTarget.Select();
|
||||
}
|
||||
|
||||
_pendingFocusFrames--;
|
||||
if (_pendingFocusFrames <= 0)
|
||||
{
|
||||
_pendingFocusTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSettingsInitialized()
|
||||
{
|
||||
_settings ??= CreateDefaultSettings();
|
||||
_settings.boxes ??= new List<PrizeStandBoxConfig>();
|
||||
|
||||
if (_settings.boxes.Count == 0)
|
||||
{
|
||||
for (int i = 1; i <= defaultBoxesCount; i++)
|
||||
{
|
||||
_settings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
|
||||
}
|
||||
}
|
||||
|
||||
NormalizeBoxes();
|
||||
}
|
||||
|
||||
private void ResetHorizontalHold()
|
||||
{
|
||||
_selectedBoxMoveHandler?.ResetHold();
|
||||
_thresholdMoveHandler?.ResetHold();
|
||||
}
|
||||
|
||||
private void NormalizeBoxes()
|
||||
{
|
||||
_settings.boxes.Sort((a, b) => a.boxNumber.CompareTo(b.boxNumber));
|
||||
|
||||
for (int i = 0; i < _settings.boxes.Count; i++)
|
||||
{
|
||||
_settings.boxes[i] ??= new PrizeStandBoxConfig();
|
||||
_settings.boxes[i].boxNumber = i + 1;
|
||||
_settings.boxes[i].requiredScore = Mathf.Max(0, _settings.boxes[i].requiredScore);
|
||||
}
|
||||
}
|
||||
|
||||
private PrizeStandBoxConfig GetSelectedBox()
|
||||
{
|
||||
EnsureSettingsInitialized();
|
||||
if (_settings.boxes.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_selectedBoxIndex = Mathf.Clamp(_selectedBoxIndex, 0, _settings.boxes.Count - 1);
|
||||
return _settings.boxes[_selectedBoxIndex];
|
||||
}
|
||||
|
||||
private PrizeStandSettings CreateDefaultSettings()
|
||||
{
|
||||
PrizeStandSettings settings = new PrizeStandSettings();
|
||||
for (int i = 1; i <= defaultBoxesCount; i++)
|
||||
{
|
||||
settings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static PrizeStandSettings CopyPrizeStandSettings(PrizeStandSettings src)
|
||||
{
|
||||
PrizeStandSettings copy = new PrizeStandSettings();
|
||||
if (src?.boxes == null)
|
||||
{
|
||||
return copy;
|
||||
}
|
||||
|
||||
for (int i = 0; i < src.boxes.Count; i++)
|
||||
{
|
||||
PrizeStandBoxConfig box = src.boxes[i];
|
||||
if (box == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
copy.boxes.Add(new PrizeStandBoxConfig
|
||||
{
|
||||
boxNumber = box.boxNumber,
|
||||
requiredScore = box.requiredScore
|
||||
});
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PrizeStandRowMoveHandler : MonoBehaviour, IMoveHandler, IDeselectHandler
|
||||
{
|
||||
private const int MaxStepMultiplier = 10;
|
||||
private const int MultiplierStartCount = 6;
|
||||
private const int MultiplierMaxCount = 30;
|
||||
private const float HoldResetTimeout = 0.25f;
|
||||
|
||||
private PrizeStandSettingsModal _owner;
|
||||
private int _rowIndex;
|
||||
private int _holdDirection;
|
||||
private int _holdCount;
|
||||
private float _lastMoveTime;
|
||||
|
||||
public void Initialize(PrizeStandSettingsModal owner, int rowIndex)
|
||||
{
|
||||
_owner = owner;
|
||||
_rowIndex = rowIndex;
|
||||
ResetHold();
|
||||
}
|
||||
|
||||
public void OnMove(AxisEventData eventData)
|
||||
{
|
||||
if (_owner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (eventData.moveDir)
|
||||
{
|
||||
case MoveDirection.Left:
|
||||
ApplyMove(-1);
|
||||
eventData.Use();
|
||||
break;
|
||||
case MoveDirection.Right:
|
||||
ApplyMove(1);
|
||||
eventData.Use();
|
||||
break;
|
||||
case MoveDirection.Up:
|
||||
case MoveDirection.Down:
|
||||
ResetHold();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDeselect(BaseEventData eventData)
|
||||
{
|
||||
ResetHold();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ResetHold();
|
||||
}
|
||||
|
||||
public void ResetHold()
|
||||
{
|
||||
_holdDirection = 0;
|
||||
_holdCount = 0;
|
||||
_lastMoveTime = 0f;
|
||||
}
|
||||
|
||||
private void ApplyMove(int direction)
|
||||
{
|
||||
float now = Time.unscaledTime;
|
||||
|
||||
if (_holdDirection != direction || (now - _lastMoveTime) > HoldResetTimeout)
|
||||
{
|
||||
_holdDirection = direction;
|
||||
_holdCount = 0;
|
||||
}
|
||||
|
||||
_lastMoveTime = now;
|
||||
_holdCount++;
|
||||
|
||||
int multiplier = 1;
|
||||
if (_holdCount > MultiplierStartCount)
|
||||
{
|
||||
float progress = Mathf.Clamp01(
|
||||
(float)(_holdCount - MultiplierStartCount) /
|
||||
Mathf.Max(1, MultiplierMaxCount - MultiplierStartCount));
|
||||
multiplier = Mathf.Max(1, Mathf.RoundToInt(Mathf.Lerp(1f, MaxStepMultiplier, progress)));
|
||||
}
|
||||
|
||||
_owner.HandleRowMove(_rowIndex, direction, multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/Modal/PrizeStandSettingsModal.cs.meta
Normal file
2
Assets/Scripts/UI/Modal/PrizeStandSettingsModal.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 997f34264511d1741835844a4afbf0a1
|
||||
@ -1,7 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
using RR.Common.Prizes;
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.UI
|
||||
@ -18,13 +21,25 @@ namespace RR.Common.UI
|
||||
[Header("Контент")]
|
||||
[Tooltip("Контейнер для размещения QR-кодов. QR-коды создаются динамически.")]
|
||||
[SerializeField] private RectTransform qrCodesContainer;
|
||||
[Tooltip("Текст для показа названия приза. Если не назначен, можно оставить пустым.")]
|
||||
[SerializeField] private TMP_Text prizeText;
|
||||
[Tooltip("Шаблон текста приза. Если содержит '{0}', он будет заменён на название приза. Иначе значение считается токеном для замены внутри текста TMP.")]
|
||||
[SerializeField] private string prizeTextTemplatePlaceholder = "{0}";
|
||||
|
||||
private readonly List<GameObject> _createdObjects = new();
|
||||
private string _defaultPrizeTextTemplate;
|
||||
private bool _defaultPrizeTextActive;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
Current = this;
|
||||
base.Awake(); // деактивирует GO, но ссылка Current остаётся валидной
|
||||
|
||||
if (prizeText != null)
|
||||
{
|
||||
_defaultPrizeTextTemplate = prizeText.text;
|
||||
_defaultPrizeTextActive = prizeText.gameObject.activeSelf;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@ -36,7 +51,7 @@ namespace RR.Common.UI
|
||||
/// Показывает модальное окно с указанными QR-кодами.
|
||||
/// </summary>
|
||||
/// <param name="qrTextures">Массив текстур QR-кодов для отображения.</param>
|
||||
public void ShowQRCodes(Texture2D[] qrTextures)
|
||||
public void ShowQRCodes(Texture2D[] qrTextures, QRTier tier, float gameTime, int finalScore, bool showFinalScore)
|
||||
{
|
||||
ClearQRCodes();
|
||||
|
||||
@ -71,6 +86,8 @@ namespace RR.Common.UI
|
||||
_createdObjects.Add(qrObj);
|
||||
}
|
||||
|
||||
UpdatePrizeText(tier);
|
||||
|
||||
Open();
|
||||
GameManager.Instance?.SetPause(true);
|
||||
}
|
||||
@ -91,6 +108,7 @@ namespace RR.Common.UI
|
||||
{
|
||||
base.Close();
|
||||
ClearQRCodes();
|
||||
ResetPrizeText();
|
||||
GameManager.Instance?.SetPause(false);
|
||||
}
|
||||
|
||||
@ -98,7 +116,78 @@ namespace RR.Common.UI
|
||||
{
|
||||
base.OnDisable();
|
||||
ClearQRCodes();
|
||||
ResetPrizeText();
|
||||
GameManager.Instance?.SetPause(false);
|
||||
}
|
||||
|
||||
private void UpdatePrizeText(QRTier tier)
|
||||
{
|
||||
if (prizeText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrizeSystemManager prizeSystemManager = PrizeSystemManager.Instance;
|
||||
string prizeLine = prizeSystemManager != null
|
||||
? prizeSystemManager.GetQRCodeModalPrizeText(tier)
|
||||
: string.Empty;
|
||||
|
||||
bool hasPrizeLine = !string.IsNullOrWhiteSpace(prizeLine);
|
||||
prizeText.gameObject.SetActive(hasPrizeLine);
|
||||
if (hasPrizeLine)
|
||||
{
|
||||
prizeText.text = ApplyPrizeTextTemplate(prizeLine);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPrizeText()
|
||||
{
|
||||
if (prizeText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
prizeText.text = _defaultPrizeTextTemplate;
|
||||
prizeText.gameObject.SetActive(_defaultPrizeTextActive);
|
||||
}
|
||||
|
||||
private string ApplyPrizeTextTemplate(string prizeLine)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(prizeTextTemplatePlaceholder) &&
|
||||
prizeTextTemplatePlaceholder.Contains("{0}"))
|
||||
{
|
||||
return prizeTextTemplatePlaceholder.Replace("{0}", prizeLine);
|
||||
}
|
||||
|
||||
string template = _defaultPrizeTextTemplate;
|
||||
if (string.IsNullOrWhiteSpace(template) && prizeText != null)
|
||||
{
|
||||
template = prizeText.text;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
{
|
||||
return prizeLine;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(prizeTextTemplatePlaceholder) &&
|
||||
template.Contains(prizeTextTemplatePlaceholder))
|
||||
{
|
||||
return template.Replace(prizeTextTemplatePlaceholder, prizeLine);
|
||||
}
|
||||
|
||||
if (template.Contains("{0}"))
|
||||
{
|
||||
return template.Replace("{0}", prizeLine);
|
||||
}
|
||||
|
||||
string trimmedTemplate = template.TrimEnd();
|
||||
if (trimmedTemplate.EndsWith(":") || trimmedTemplate.EndsWith("-"))
|
||||
{
|
||||
return $"{template} {prizeLine}".Trim();
|
||||
}
|
||||
|
||||
return prizeLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
681
Assets/Scripts/UI/Modal/QRCodeSettingsModal.cs
Normal file
681
Assets/Scripts/UI/Modal/QRCodeSettingsModal.cs
Normal file
@ -0,0 +1,681 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
using RR.Common.Utility;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Отдельная модалка глобальной настройки QR-кодов по градациям.
|
||||
/// Не привязана к уровням: уровень выбирает только qrTier в LevelPrizeSettingsModal.
|
||||
/// </summary>
|
||||
public class QRCodeSettingsModal : Modal
|
||||
{
|
||||
[Header("UI")]
|
||||
[SerializeField] private TMP_Text titleText;
|
||||
[SerializeField] private TMP_Text tierLabel;
|
||||
[SerializeField] private TMP_Text prizeTextLabel;
|
||||
[SerializeField] private Button editPrizeTextButton;
|
||||
[SerializeField] private TextInputModal textInputModal;
|
||||
|
||||
[Header("Rows")]
|
||||
[SerializeField] private RectTransform tierRow;
|
||||
[SerializeField] private RectTransform prizeTextRow;
|
||||
|
||||
[Header("Pointer")]
|
||||
[SerializeField] private UIRowPointer rowPointer;
|
||||
|
||||
[Header("Close")]
|
||||
[SerializeField] private Button closeModalButton;
|
||||
|
||||
[Header("Navigation")]
|
||||
[SerializeField] private InputActionReference navigateAction;
|
||||
[SerializeField] private InputActionReference submitAction;
|
||||
[SerializeField] private float deadZone = 0.5f;
|
||||
|
||||
[Header("Behaviour")]
|
||||
[SerializeField] private bool createEntryIfMissing = true;
|
||||
[SerializeField] private string prizeTextModalTitle = "Введите текст приза QR";
|
||||
[SerializeField] private int prizeTextMaxLength = 12;
|
||||
|
||||
private static readonly QRTier[] EditableTiers = { QRTier.Bronze, QRTier.Silver, QRTier.Gold };
|
||||
private const int MaxPrizeTextLength = 12;
|
||||
private const int RowTier = 0;
|
||||
private const int RowPrizeText = 1;
|
||||
private const int RowClose = 2;
|
||||
private const int RowCount = 3;
|
||||
|
||||
private PrizeSystemManager _manager;
|
||||
private List<QRCodeInfo> _workingEntries;
|
||||
private int _currentTierIndex;
|
||||
private int _activeRow;
|
||||
private string _currentPrizeText = string.Empty;
|
||||
private bool _axisHeldX;
|
||||
private bool _axisHeldY;
|
||||
private bool _isPrizeTextInputOpen;
|
||||
private Selectable _pendingFocusTarget;
|
||||
private int _pendingFocusFrames;
|
||||
private Selectable _tierRowSelectable;
|
||||
private Selectable _prizeTextRowSelectable;
|
||||
private RectTransform _prizeTextPointerTarget;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (editPrizeTextButton != null)
|
||||
{
|
||||
Graphic buttonGraphic = editPrizeTextButton.GetComponent<Graphic>();
|
||||
if (buttonGraphic != null)
|
||||
{
|
||||
editPrizeTextButton.targetGraphic = buttonGraphic;
|
||||
}
|
||||
|
||||
editPrizeTextButton.onClick.AddListener(OpenPrizeTextInput);
|
||||
}
|
||||
|
||||
if (textInputModal == null)
|
||||
{
|
||||
textInputModal = FindFirstObjectByType<TextInputModal>(FindObjectsInactive.Include);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (navigateAction != null && navigateAction.action != null)
|
||||
{
|
||||
navigateAction.action.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
_isPrizeTextInputOpen = false;
|
||||
_pendingFocusTarget = null;
|
||||
_pendingFocusFrames = 0;
|
||||
_axisHeldX = false;
|
||||
_axisHeldY = false;
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
protected override void OnReturnedToTop()
|
||||
{
|
||||
_isPrizeTextInputOpen = false;
|
||||
_pendingFocusTarget = null;
|
||||
_pendingFocusFrames = 0;
|
||||
_axisHeldX = false;
|
||||
_axisHeldY = false;
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (editPrizeTextButton != null)
|
||||
{
|
||||
editPrizeTextButton.onClick.RemoveListener(OpenPrizeTextInput);
|
||||
}
|
||||
|
||||
if (textInputModal != null)
|
||||
{
|
||||
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
|
||||
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Open()
|
||||
{
|
||||
PrepareForEditing();
|
||||
base.Open();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
public void OpenForEditing()
|
||||
{
|
||||
Open();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
Save();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
ApplyCurrentFieldValues();
|
||||
|
||||
if (_manager == null)
|
||||
{
|
||||
Debug.LogWarning("[QRCodeSettingsModal] PrizeSystemManager не найден.");
|
||||
return;
|
||||
}
|
||||
|
||||
_manager.SetQRCodeConfigs(_workingEntries);
|
||||
}
|
||||
|
||||
private void PrepareForEditing()
|
||||
{
|
||||
_manager = PrizeSystemManager.Instance;
|
||||
_workingEntries = _manager != null ? _manager.GetQRCodeConfigs() : new List<QRCodeInfo>();
|
||||
|
||||
_currentTierIndex = Mathf.Clamp(_currentTierIndex, 0, EditableTiers.Length - 1);
|
||||
_activeRow = RowTier;
|
||||
AutoResolveRows();
|
||||
EnsureRowSelectables();
|
||||
LoadCurrentTierIntoFields();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void ProcessNavigationInput()
|
||||
{
|
||||
if (navigateAction == null || navigateAction.action == null)
|
||||
{
|
||||
_axisHeldX = false;
|
||||
_axisHeldY = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 value = navigateAction.action.ReadValue<Vector2>();
|
||||
|
||||
if (Mathf.Abs(value.y) >= deadZone)
|
||||
{
|
||||
if (!_axisHeldY)
|
||||
{
|
||||
_axisHeldY = true;
|
||||
MoveRow(value.y > 0f ? -1 : 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_axisHeldY = false;
|
||||
}
|
||||
|
||||
if (Mathf.Abs(value.x) >= deadZone)
|
||||
{
|
||||
if (!_axisHeldX)
|
||||
{
|
||||
_axisHeldX = true;
|
||||
HandleLeftRight(value.x > 0f ? 1 : -1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_axisHeldX = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isOpen || _isPrizeTextInputOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessNavigationInput();
|
||||
HandleSubmit();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!_isOpen || _isPrizeTextInputOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyPendingFocus();
|
||||
}
|
||||
|
||||
private void MoveRow(int delta)
|
||||
{
|
||||
_activeRow = (_activeRow + delta + RowCount) % RowCount;
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void HandleLeftRight(int direction)
|
||||
{
|
||||
if (_activeRow != RowTier)
|
||||
return;
|
||||
|
||||
ApplyCurrentFieldValues();
|
||||
_currentTierIndex = (_currentTierIndex + direction + EditableTiers.Length) % EditableTiers.Length;
|
||||
LoadCurrentTierIntoFields();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void LoadCurrentTierIntoFields()
|
||||
{
|
||||
if (titleText != null)
|
||||
{
|
||||
titleText.text = $"Настройки QR: {GetTierName(CurrentTier)}";
|
||||
}
|
||||
|
||||
if (tierLabel != null)
|
||||
{
|
||||
tierLabel.text = GetTierName(CurrentTier);
|
||||
}
|
||||
|
||||
List<QRCodeInfo> entries = GetEntriesForTier(CurrentTier, includeFallbackTemplates: false);
|
||||
_currentPrizeText = ResolveDisplayedPrizeText(entries);
|
||||
UpdatePrizeTextLabel(_currentPrizeText);
|
||||
}
|
||||
|
||||
private void ApplyCurrentFieldValues()
|
||||
{
|
||||
if (_workingEntries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentPrizeText = NormalizePrizeText(_currentPrizeText);
|
||||
|
||||
List<int> tierIndices = GetEntryIndicesForTier(CurrentTier);
|
||||
if (tierIndices.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < tierIndices.Count; i++)
|
||||
{
|
||||
int index = tierIndices[i];
|
||||
QRCodeInfo info = _workingEntries[index];
|
||||
info.tier = CurrentTier;
|
||||
info.prizeText = _currentPrizeText;
|
||||
_workingEntries[index] = info;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createEntryIfMissing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<QRCodeInfo> templateEntries = GetEntriesForTier(QRTier.None, includeFallbackTemplates: false);
|
||||
if (templateEntries.Count == 0)
|
||||
{
|
||||
templateEntries.Add(CreateDefaultEntry(CurrentTier));
|
||||
}
|
||||
|
||||
for (int i = 0; i < templateEntries.Count; i++)
|
||||
{
|
||||
QRCodeInfo clone = templateEntries[i];
|
||||
clone.tier = CurrentTier;
|
||||
clone.prizeText = _currentPrizeText;
|
||||
_workingEntries.Add(clone);
|
||||
}
|
||||
}
|
||||
|
||||
private static QRCodeInfo CreateDefaultEntry(QRTier tier)
|
||||
{
|
||||
return new QRCodeInfo
|
||||
{
|
||||
tier = tier,
|
||||
generateQRCode = true,
|
||||
linkPrefix = string.Empty,
|
||||
messageFormat = "{0}",
|
||||
prizeText = string.Empty,
|
||||
darkColor = new Vector3(0f, 0f, 0f),
|
||||
lightColor = new Vector3(1f, 1f, 1f)
|
||||
};
|
||||
}
|
||||
|
||||
private void OpenPrizeTextInput()
|
||||
{
|
||||
if (textInputModal == null)
|
||||
{
|
||||
Debug.LogWarning("[QRCodeSettingsModal] TextInputModal не назначен.");
|
||||
return;
|
||||
}
|
||||
|
||||
_isPrizeTextInputOpen = true;
|
||||
FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton);
|
||||
|
||||
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
|
||||
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
|
||||
textInputModal.OnTextConfirmed += OnPrizeTextConfirmed;
|
||||
textInputModal.OnTextCancelled += OnPrizeTextCancelled;
|
||||
|
||||
_currentPrizeText = NormalizePrizeText(_currentPrizeText);
|
||||
textInputModal.SetAlphabet(TextInputAlphabets.RussianPrizeText);
|
||||
textInputModal.SetCellCount(GetPrizeTextCellCount());
|
||||
textInputModal.OpenForInput(prizeTextModalTitle, _currentPrizeText);
|
||||
}
|
||||
|
||||
public void OpenPrizeTextEditor()
|
||||
{
|
||||
OpenPrizeTextInput();
|
||||
}
|
||||
|
||||
private void OnPrizeTextConfirmed(string text)
|
||||
{
|
||||
_isPrizeTextInputOpen = false;
|
||||
|
||||
if (textInputModal != null)
|
||||
{
|
||||
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
|
||||
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
|
||||
}
|
||||
|
||||
_currentPrizeText = NormalizePrizeText(text);
|
||||
UpdatePrizeTextLabel(_currentPrizeText);
|
||||
FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton);
|
||||
}
|
||||
|
||||
private void OnPrizeTextCancelled()
|
||||
{
|
||||
_isPrizeTextInputOpen = false;
|
||||
|
||||
if (textInputModal != null)
|
||||
{
|
||||
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
|
||||
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
|
||||
}
|
||||
|
||||
FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton);
|
||||
}
|
||||
|
||||
private void UpdatePrizeTextLabel(string text)
|
||||
{
|
||||
if (prizeTextLabel != null)
|
||||
{
|
||||
string normalized = NormalizePrizeText(text);
|
||||
prizeTextLabel.text = string.IsNullOrWhiteSpace(normalized) ? "<пусто>" : normalized;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshRowHighlights()
|
||||
{
|
||||
RectTransform target = GetActiveRowTransform();
|
||||
Selectable selectableToFocus = null;
|
||||
|
||||
switch (_activeRow)
|
||||
{
|
||||
case RowTier:
|
||||
selectableToFocus = _tierRowSelectable;
|
||||
break;
|
||||
case RowPrizeText:
|
||||
selectableToFocus = _prizeTextRowSelectable;
|
||||
break;
|
||||
case RowClose:
|
||||
selectableToFocus = closeModalButton;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_activeRow == RowClose)
|
||||
{
|
||||
if (rowPointer != null)
|
||||
{
|
||||
rowPointer.Hide();
|
||||
}
|
||||
}
|
||||
else if (rowPointer != null && target != null)
|
||||
{
|
||||
rowPointer.SetTarget(target);
|
||||
}
|
||||
|
||||
FocusSelectable(selectableToFocus);
|
||||
}
|
||||
|
||||
private void AutoResolveRows()
|
||||
{
|
||||
if (tierRow == null && tierLabel != null)
|
||||
{
|
||||
tierRow = tierLabel.transform.parent as RectTransform;
|
||||
}
|
||||
|
||||
if (prizeTextRow == null)
|
||||
{
|
||||
if (prizeTextLabel != null)
|
||||
prizeTextRow = prizeTextLabel.transform.parent as RectTransform;
|
||||
else if (editPrizeTextButton != null)
|
||||
prizeTextRow = editPrizeTextButton.transform.parent as RectTransform;
|
||||
}
|
||||
|
||||
_prizeTextPointerTarget = ResolvePrizeTextPointerTarget();
|
||||
}
|
||||
|
||||
private QRTier CurrentTier => EditableTiers[Mathf.Clamp(_currentTierIndex, 0, EditableTiers.Length - 1)];
|
||||
|
||||
private static string GetTierName(QRTier tier)
|
||||
{
|
||||
return tier switch
|
||||
{
|
||||
QRTier.Bronze => "Бронзовый",
|
||||
QRTier.Silver => "Серебряный",
|
||||
QRTier.Gold => "Золотой",
|
||||
_ => "Не задан"
|
||||
};
|
||||
}
|
||||
|
||||
private List<QRCodeInfo> GetEntriesForTier(QRTier tier, bool includeFallbackTemplates)
|
||||
{
|
||||
List<QRCodeInfo> result = new();
|
||||
if (_workingEntries == null)
|
||||
return result;
|
||||
|
||||
for (int i = 0; i < _workingEntries.Count; i++)
|
||||
{
|
||||
if (_workingEntries[i].tier == tier)
|
||||
result.Add(_workingEntries[i]);
|
||||
}
|
||||
|
||||
if (result.Count == 0 && includeFallbackTemplates && tier != QRTier.None)
|
||||
{
|
||||
for (int i = 0; i < _workingEntries.Count; i++)
|
||||
{
|
||||
if (_workingEntries[i].tier == QRTier.None)
|
||||
result.Add(_workingEntries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<int> GetEntryIndicesForTier(QRTier tier)
|
||||
{
|
||||
List<int> result = new();
|
||||
if (_workingEntries == null)
|
||||
return result;
|
||||
|
||||
for (int i = 0; i < _workingEntries.Count; i++)
|
||||
{
|
||||
if (_workingEntries[i].tier == tier)
|
||||
result.Add(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string ResolveDisplayedPrizeText(List<QRCodeInfo> tierEntries)
|
||||
{
|
||||
if (tierEntries == null || tierEntries.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
for (int i = 0; i < tierEntries.Count; i++)
|
||||
{
|
||||
string candidate = NormalizePrizeText(tierEntries[i].prizeText);
|
||||
if (!string.IsNullOrWhiteSpace(candidate))
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private int GetPrizeTextCellCount()
|
||||
{
|
||||
return Mathf.Clamp(prizeTextMaxLength, 1, MaxPrizeTextLength);
|
||||
}
|
||||
|
||||
private RectTransform GetActiveRowTransform()
|
||||
{
|
||||
return _activeRow switch
|
||||
{
|
||||
RowTier => tierRow,
|
||||
RowPrizeText => _prizeTextPointerTarget,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private void EnsureRowSelectables()
|
||||
{
|
||||
_tierRowSelectable = EnsureRowSelectable(tierRow);
|
||||
_prizeTextRowSelectable = editPrizeTextButton != null
|
||||
? editPrizeTextButton
|
||||
: EnsureRowSelectable(_prizeTextPointerTarget);
|
||||
}
|
||||
|
||||
private RectTransform ResolvePrizeTextPointerTarget()
|
||||
{
|
||||
if (editPrizeTextButton != null)
|
||||
{
|
||||
RectTransform buttonRect = editPrizeTextButton.transform as RectTransform;
|
||||
if (buttonRect != null)
|
||||
{
|
||||
return buttonRect;
|
||||
}
|
||||
}
|
||||
|
||||
if (prizeTextRow != null)
|
||||
{
|
||||
return prizeTextRow;
|
||||
}
|
||||
|
||||
return prizeTextLabel != null ? prizeTextLabel.transform.parent as RectTransform : null;
|
||||
}
|
||||
|
||||
private Selectable EnsureRowSelectable(RectTransform row)
|
||||
{
|
||||
if (row == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Selectable selectable = row.GetComponent<Selectable>();
|
||||
if (selectable == null)
|
||||
{
|
||||
selectable = row.gameObject.AddComponent<Selectable>();
|
||||
selectable.transition = Selectable.Transition.None;
|
||||
selectable.navigation = new Navigation { mode = Navigation.Mode.None };
|
||||
}
|
||||
|
||||
selectable.interactable = true;
|
||||
return selectable;
|
||||
}
|
||||
|
||||
private void FocusSelectable(Selectable selectable)
|
||||
{
|
||||
if (selectable == null || !selectable.gameObject.activeInHierarchy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetFirstSelected(selectable);
|
||||
_pendingFocusTarget = selectable;
|
||||
_pendingFocusFrames = 2;
|
||||
ApplyPendingFocus();
|
||||
}
|
||||
|
||||
private void ApplyPendingFocus()
|
||||
{
|
||||
if (_pendingFocusTarget == null || _pendingFocusFrames <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EventSystem.current == null || !_pendingFocusTarget.gameObject.activeInHierarchy)
|
||||
{
|
||||
_pendingFocusTarget = null;
|
||||
_pendingFocusFrames = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (EventSystem.current.currentSelectedGameObject != _pendingFocusTarget.gameObject)
|
||||
{
|
||||
EventSystem.current.SetSelectedGameObject(null);
|
||||
EventSystem.current.SetSelectedGameObject(_pendingFocusTarget.gameObject);
|
||||
_pendingFocusTarget.Select();
|
||||
}
|
||||
|
||||
_pendingFocusFrames--;
|
||||
if (_pendingFocusFrames <= 0)
|
||||
{
|
||||
_pendingFocusTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSubmit()
|
||||
{
|
||||
InputAction currentSubmitAction = GetSubmitAction();
|
||||
if (currentSubmitAction == null || !currentSubmitAction.WasPerformedThisFrame())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject selectedObject = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null;
|
||||
|
||||
switch (_activeRow)
|
||||
{
|
||||
case RowPrizeText:
|
||||
if (selectedObject == editPrizeTextButton?.gameObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OpenPrizeTextInput();
|
||||
break;
|
||||
case RowClose:
|
||||
if (selectedObject == closeModalButton?.gameObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private InputAction GetSubmitAction()
|
||||
{
|
||||
if (submitAction != null && submitAction.action != null)
|
||||
{
|
||||
return submitAction.action;
|
||||
}
|
||||
|
||||
if (EventSystem.current?.currentInputModule == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
object inputModule = EventSystem.current.currentInputModule;
|
||||
PropertyInfo submitProperty = inputModule.GetType().GetProperty("submit", BindingFlags.Instance | BindingFlags.Public);
|
||||
object submitObject = submitProperty != null ? submitProperty.GetValue(inputModule) : null;
|
||||
InputActionReference submitReference = submitObject as InputActionReference;
|
||||
return submitReference != null ? submitReference.action : null;
|
||||
}
|
||||
|
||||
private string NormalizePrizeText(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string normalized = text.Trim();
|
||||
int maxLength = GetPrizeTextCellCount();
|
||||
if (normalized.Length > maxLength)
|
||||
{
|
||||
normalized = normalized.Substring(0, maxLength);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/Modal/QRCodeSettingsModal.cs.meta
Normal file
2
Assets/Scripts/UI/Modal/QRCodeSettingsModal.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cad5e57ea5b58e64598b4f42f39073be
|
||||
@ -99,6 +99,12 @@ namespace RR.Common.UI
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
protected override void OnReturnedToTop()
|
||||
{
|
||||
_axisHeld = false;
|
||||
UpdateIndicator();
|
||||
}
|
||||
|
||||
// ───────────────────────── Ввод (Input System) ─────────────────────────
|
||||
|
||||
private void OnNavigatePerformed(InputAction.CallbackContext ctx)
|
||||
|
||||
@ -39,7 +39,7 @@ namespace RR.Common.UI
|
||||
{
|
||||
if (_qrCodes != null) ClearQRCodes();
|
||||
|
||||
Texture2D[] qrCodesImages = _prizeSystemManager.GetQRCodes();
|
||||
Texture2D[] qrCodesImages = _prizeSystemManager.GetQRCodes(RR.Common.Prizes.QRTier.None);
|
||||
_qrCodes = new GameObject[qrCodesImages.Length];
|
||||
|
||||
for (int i = 0; i < qrCodesImages.Length; i++)
|
||||
@ -68,4 +68,4 @@ namespace RR.Common.UI
|
||||
_qrCodes = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
Assets/Scripts/UI/SelectableDescriptionTarget.cs
Normal file
34
Assets/Scripts/UI/SelectableDescriptionTarget.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
public class SelectableDescriptionTarget : MonoBehaviour, ISelectHandler, IPointerEnterHandler
|
||||
{
|
||||
private TMP_Text _targetText;
|
||||
private string _description;
|
||||
|
||||
public void Configure(TMP_Text targetText, string description)
|
||||
{
|
||||
_targetText = targetText;
|
||||
_description = description ?? string.Empty;
|
||||
}
|
||||
|
||||
public void OnSelect(BaseEventData eventData)
|
||||
{
|
||||
ApplyDescription();
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
ApplyDescription();
|
||||
}
|
||||
|
||||
private void ApplyDescription()
|
||||
{
|
||||
if (_targetText == null) return;
|
||||
_targetText.text = _description;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/SelectableDescriptionTarget.cs.meta
Normal file
2
Assets/Scripts/UI/SelectableDescriptionTarget.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fed2ea875f1d40745a5d202709155d1c
|
||||
@ -4,6 +4,7 @@ using UnityEngine.InputSystem;
|
||||
using UnityEngine.EventSystems;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using RR.Common.Settings;
|
||||
using TMPro;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
@ -26,6 +27,10 @@ namespace RR.Common.UI
|
||||
[Tooltip("Первый элемент для фокуса при открытии. Если не задан - ищется автоматически.")]
|
||||
[SerializeField] private Selectable firstSelectable;
|
||||
|
||||
[Header("Описание")]
|
||||
[Tooltip("Текстовый элемент, в который показывается описание выбранной настройки.")]
|
||||
[SerializeField] private TMP_Text focusedSettingDescriptionText;
|
||||
|
||||
private SettingsManager settingsManager;
|
||||
|
||||
protected override void Awake()
|
||||
@ -110,6 +115,8 @@ namespace RR.Common.UI
|
||||
|
||||
private void OnGeneratorBuildComplete()
|
||||
{
|
||||
BindDescriptionTargets();
|
||||
|
||||
// Настраиваем навигацию после построения
|
||||
SetupExplicitNavigation();
|
||||
|
||||
@ -286,6 +293,7 @@ namespace RR.Common.UI
|
||||
{
|
||||
base.Open();
|
||||
RefreshSettings();
|
||||
ClearFocusedDescription();
|
||||
|
||||
// Выбираем первый элемент для навигации
|
||||
SelectFirstElement();
|
||||
@ -359,6 +367,12 @@ namespace RR.Common.UI
|
||||
Save();
|
||||
Close();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
ClearFocusedDescription();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
private void OnSettingsLoaded()
|
||||
{
|
||||
@ -369,6 +383,40 @@ namespace RR.Common.UI
|
||||
{
|
||||
GameManager.Logger?.Log(name, "Saved settings");
|
||||
}
|
||||
|
||||
private void BindDescriptionTargets()
|
||||
{
|
||||
if (focusedSettingDescriptionText == null) return;
|
||||
|
||||
var items = GetComponentsInChildren<SettingUIItem>(true);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item != null)
|
||||
item.BindExternalDescriptionTarget(focusedSettingDescriptionText);
|
||||
}
|
||||
|
||||
var groups = GetComponentsInChildren<SettingsGroupPanel>(true);
|
||||
foreach (var group in groups)
|
||||
{
|
||||
if (group != null)
|
||||
group.BindExternalDescriptionTarget(focusedSettingDescriptionText);
|
||||
}
|
||||
|
||||
if (saveButton != null)
|
||||
{
|
||||
var descriptionTarget = saveButton.GetComponent<SelectableDescriptionTarget>();
|
||||
if (descriptionTarget == null)
|
||||
descriptionTarget = saveButton.gameObject.AddComponent<SelectableDescriptionTarget>();
|
||||
|
||||
descriptionTarget.Configure(focusedSettingDescriptionText, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearFocusedDescription()
|
||||
{
|
||||
if (focusedSettingDescriptionText != null)
|
||||
focusedSettingDescriptionText.text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -100,6 +100,19 @@ namespace RR.Common.UI
|
||||
" ", "-", "_"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Алфавит для коротких названий призов: начинается с пробела, чтобы пустые ячейки не отображались нулями.
|
||||
/// </summary>
|
||||
public static readonly string[] RussianPrizeText = new string[]
|
||||
{
|
||||
" ",
|
||||
"А", "Б", "В", "Г", "Д", "Е", "Ё", "Ж", "З", "И", "Й",
|
||||
"К", "Л", "М", "Н", "О", "П", "Р", "С", "Т", "У", "Ф",
|
||||
"Х", "Ц", "Ч", "Ш", "Щ", "Ъ", "Ы", "Ь", "Э", "Ю", "Я",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"-", "_"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Email-friendly алфавит (английские буквы в нижнем регистре, цифры, @ и точка)
|
||||
/// </summary>
|
||||
|
||||
@ -39,7 +39,7 @@ namespace RR.Common.UI
|
||||
levelNumber = $"{GameManager.Instance.World}-{GameManager.Instance.Stage}";
|
||||
|
||||
livesText.text = livesCount.ToString();
|
||||
coinsText.text = coinsCount.ToString();
|
||||
coinsText.text = FormatCoins(coinsCount);
|
||||
levelText.text = levelNumber;
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ namespace RR.Common.UI
|
||||
if (coinsCount != newCoins)
|
||||
{
|
||||
coinsCount = newCoins;
|
||||
coinsText.text = coinsCount.ToString();
|
||||
coinsText.text = FormatCoins(coinsCount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,5 +56,10 @@ namespace RR.Common.UI
|
||||
{
|
||||
livesText.text = newLives.ToString();
|
||||
}
|
||||
|
||||
private static string FormatCoins(int totalCoins)
|
||||
{
|
||||
return (totalCoins % 100).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
@ -33,11 +34,27 @@ namespace RR.Common.UI
|
||||
[Tooltip("Скорость покачивания (циклов в секунду).")]
|
||||
[SerializeField] private float wobbleSpeed = 2f;
|
||||
|
||||
[Header("Selection Tracking")]
|
||||
[Tooltip("Задержка перед скрытием указателя, если выбранный элемент временно не найден.")]
|
||||
[SerializeField] private float unmatchedHideDelay = 0.15f;
|
||||
|
||||
// ── Состояние ──
|
||||
private RectTransform _currentTarget;
|
||||
private Vector2 _targetPos;
|
||||
private Vector2 _baseX;
|
||||
private bool _ready;
|
||||
private bool _followSelection;
|
||||
private Transform _selectionScope;
|
||||
private bool _hideWhenSelectionUnmatched;
|
||||
private readonly System.Collections.Generic.List<SelectionBinding> _selectionBindings = new();
|
||||
private float _unmatchedSelectionTime;
|
||||
|
||||
private sealed class SelectionBinding
|
||||
{
|
||||
public Transform SelectionRoot;
|
||||
public RectTransform PointerTarget;
|
||||
public bool HidePointer;
|
||||
}
|
||||
|
||||
// ────────────── Публичный API ──────────────
|
||||
|
||||
@ -86,6 +103,72 @@ namespace RR.Common.UI
|
||||
targets = newTargets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переключает указатель в режим следования за выбранным UI-элементом.
|
||||
/// </summary>
|
||||
public void FollowSelection(Transform scope, bool hideWhenSelectionUnmatched = false)
|
||||
{
|
||||
_selectionScope = scope;
|
||||
_hideWhenSelectionUnmatched = hideWhenSelectionUnmatched;
|
||||
_followSelection = true;
|
||||
_unmatchedSelectionTime = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отключает режим следования за выбранным UI-элементом.
|
||||
/// </summary>
|
||||
public void StopFollowingSelection()
|
||||
{
|
||||
_followSelection = false;
|
||||
_selectionScope = null;
|
||||
_hideWhenSelectionUnmatched = false;
|
||||
_unmatchedSelectionTime = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает runtime-привязки выбранных элементов к строкам указателя.
|
||||
/// </summary>
|
||||
public void ClearSelectionBindings()
|
||||
{
|
||||
_selectionBindings.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Привязывает выбранный UI-элемент или его дочерние элементы к строке указателя.
|
||||
/// </summary>
|
||||
public void RegisterSelectionBinding(Transform selectionRoot, RectTransform pointerTarget)
|
||||
{
|
||||
if (selectionRoot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectionBindings.Add(new SelectionBinding
|
||||
{
|
||||
SelectionRoot = selectionRoot,
|
||||
PointerTarget = pointerTarget,
|
||||
HidePointer = false
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Привязывает выбранный UI-элемент к скрытию указателя.
|
||||
/// </summary>
|
||||
public void RegisterHideBinding(Transform selectionRoot)
|
||||
{
|
||||
if (selectionRoot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectionBindings.Add(new SelectionBinding
|
||||
{
|
||||
SelectionRoot = selectionRoot,
|
||||
PointerTarget = null,
|
||||
HidePointer = true
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────── Lifecycle ──────────────
|
||||
|
||||
private void Awake()
|
||||
@ -95,6 +178,8 @@ namespace RR.Common.UI
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
UpdateSelectionTracking();
|
||||
|
||||
if (!pointer || _currentTarget == null) return;
|
||||
|
||||
// Пересчитываем позицию (цель может двигаться из-за layout)
|
||||
@ -114,6 +199,112 @@ namespace RR.Common.UI
|
||||
|
||||
// ────────────── Внутреннее ──────────────
|
||||
|
||||
private void UpdateSelectionTracking()
|
||||
{
|
||||
if (!_followSelection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject selected = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null;
|
||||
if (selected == null)
|
||||
{
|
||||
HandleUnmatchedSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
Transform selectedTransform = selected.transform;
|
||||
if (_selectionScope != null && !selectedTransform.IsChildOf(_selectionScope))
|
||||
{
|
||||
HandleUnmatchedSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
SelectionBinding binding = FindSelectionBinding(selectedTransform);
|
||||
if (binding != null)
|
||||
{
|
||||
_unmatchedSelectionTime = 0f;
|
||||
|
||||
if (binding.HidePointer || binding.PointerTarget == null)
|
||||
{
|
||||
Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
SetTarget(binding.PointerTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
RectTransform trackedTarget = FindTrackedTarget(selectedTransform);
|
||||
if (trackedTarget != null)
|
||||
{
|
||||
_unmatchedSelectionTime = 0f;
|
||||
SetTarget(trackedTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
HandleUnmatchedSelection();
|
||||
}
|
||||
|
||||
private SelectionBinding FindSelectionBinding(Transform selectedTransform)
|
||||
{
|
||||
for (int i = 0; i < _selectionBindings.Count; i++)
|
||||
{
|
||||
SelectionBinding binding = _selectionBindings[i];
|
||||
if (binding?.SelectionRoot == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (selectedTransform == binding.SelectionRoot || selectedTransform.IsChildOf(binding.SelectionRoot))
|
||||
{
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RectTransform FindTrackedTarget(Transform selectedTransform)
|
||||
{
|
||||
if (targets == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
{
|
||||
RectTransform target = targets[i];
|
||||
if (target == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (selectedTransform == target || selectedTransform.IsChildOf(target))
|
||||
{
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleUnmatchedSelection()
|
||||
{
|
||||
if (_hideWhenSelectionUnmatched)
|
||||
{
|
||||
_unmatchedSelectionTime += Time.unscaledDeltaTime;
|
||||
if (_unmatchedSelectionTime >= unmatchedHideDelay)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_unmatchedSelectionTime = 0f;
|
||||
}
|
||||
|
||||
private Vector2 CalculatePosition(RectTransform target)
|
||||
{
|
||||
RectTransform parent = pointer.parent as RectTransform;
|
||||
|
||||
@ -4,7 +4,9 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
using RR.Common.Settings;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
@ -19,10 +21,20 @@ namespace RR.Common.UI
|
||||
[Header("Элементы баннера выигрыша")]
|
||||
[SerializeField] private TextMeshProUGUI winText;
|
||||
[SerializeField] private Button mainMenuButton;
|
||||
|
||||
[Header("Опциональная разметка режимов")]
|
||||
[Tooltip("Корневой объект обычной победной плашки. Если не задан, используется только winText.")]
|
||||
[SerializeField] private GameObject winContentRoot;
|
||||
[Tooltip("Корневой объект призовой плашки. Если не задан, используется только winText.")]
|
||||
[SerializeField] private GameObject prizeContentRoot;
|
||||
[SerializeField] private TextMeshProUGUI prizeTitleText;
|
||||
[SerializeField] private TextMeshProUGUI prizeDetailsText;
|
||||
|
||||
[Header("QR-коды")]
|
||||
[Tooltip("Контейнер для размещения QR-кодов. QR-коды создаются динамически.")]
|
||||
[SerializeField] private RectTransform qrCodesContainer;
|
||||
[Tooltip("Корневой объект секции QR. Если не задан, используется объект контейнера QR.")]
|
||||
[SerializeField] private GameObject qrSectionRoot;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при закрытии баннера выигрыша.
|
||||
@ -42,67 +54,168 @@ namespace RR.Common.UI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Показывает баннер выигрыша с указанным временем игры.
|
||||
/// Показывает баннер выигрыша или призовой баннер для последнего уровня.
|
||||
/// </summary>
|
||||
/// <param name="gameTime">Время игры в секундах</param>
|
||||
public void ShowWin(float gameTime)
|
||||
/// <param name="gameTime">Время игры в секундах.</param>
|
||||
/// <param name="prizeConfig">Конфиг приза последнего уровня.</param>
|
||||
/// <param name="finalScore">Итоговый счёт за игру.</param>
|
||||
/// <param name="showFinalScore">Нужно ли показывать итоговый счёт в баннере.</param>
|
||||
public void ShowWin(float gameTime, LevelPrizeConfig prizeConfig, int finalScore, bool showFinalScore)
|
||||
{
|
||||
if (winText)
|
||||
{
|
||||
winText.text = $"Время игры: {TimeFormatHelper.FormatTime(gameTime)}";
|
||||
}
|
||||
|
||||
// Отображаем QR-коды
|
||||
DisplayQRCodes();
|
||||
|
||||
ApplyBannerState(ResolveBannerState(gameTime, prizeConfig ?? new LevelPrizeConfig(), finalScore, showFinalScore));
|
||||
Open();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отображает QR-коды из PrizeSystemManager.
|
||||
/// Создаёт объекты полностью динамически без префаба.
|
||||
/// Также вызывает выдачу QR-приза через новую систему, если доступна.
|
||||
/// </summary>
|
||||
private void DisplayQRCodes()
|
||||
|
||||
private void ApplyBannerState(BannerState state)
|
||||
{
|
||||
if (winContentRoot != null)
|
||||
winContentRoot.SetActive(!state.IsPrizeBanner);
|
||||
|
||||
if (prizeContentRoot != null)
|
||||
prizeContentRoot.SetActive(state.IsPrizeBanner);
|
||||
|
||||
if (winText)
|
||||
winText.text = state.FallbackWinText;
|
||||
|
||||
if (prizeTitleText)
|
||||
prizeTitleText.text = state.PrizeTitle;
|
||||
|
||||
if (prizeDetailsText)
|
||||
prizeDetailsText.text = state.PrizeDetails;
|
||||
|
||||
if ((prizeTitleText == null || prizeDetailsText == null) && winText)
|
||||
winText.text = state.LegacyCombinedText;
|
||||
|
||||
DisplayQRCodes(state.ShowQRCodes);
|
||||
}
|
||||
|
||||
private BannerState ResolveBannerState(float gameTime, LevelPrizeConfig prizeConfig, int finalScore, bool showFinalScore)
|
||||
{
|
||||
string formattedTime = TimeFormatHelper.FormatTime(gameTime);
|
||||
string scoreLine = showFinalScore ? $"\nОчки за игру: {finalScore}" : string.Empty;
|
||||
bool canShowQRCodes = prizeConfig.type == PrizeType.QRCode && CanDisplayBigPrizeQRCodes() && HasQRCodes(prizeConfig.qrTier);
|
||||
string qrTierName = GetQRTierName(prizeConfig.qrTier);
|
||||
string prizeStandSummary = GetPrizeStandSummary(finalScore, showFinalScore);
|
||||
|
||||
return prizeConfig.type switch
|
||||
{
|
||||
PrizeType.QRCode when canShowQRCodes => CreatePrizeState(
|
||||
"Ваш приз",
|
||||
$"Отсканируйте {qrTierName} QR-код, чтобы получить приз.\nВремя игры: {formattedTime}{scoreLine}",
|
||||
$"Ваш приз: {qrTierName} QR-код\nВремя игры: {formattedTime}{scoreLine}",
|
||||
showQRCodes: true),
|
||||
PrizeType.Hopper => CreatePrizeState(
|
||||
"Ваш приз",
|
||||
$"Капсулы: {prizeConfig.hopperCapsuleCount}\nВремя игры: {formattedTime}{scoreLine}",
|
||||
$"Ваш приз: капсулы\nКоличество: {prizeConfig.hopperCapsuleCount}\nВремя игры: {formattedTime}{scoreLine}"),
|
||||
PrizeType.PrizeStand => CreatePrizeState(
|
||||
"Ваш приз",
|
||||
$"{prizeStandSummary}\nВремя игры: {formattedTime}{scoreLine}",
|
||||
$"Ваш приз: витрина\n{prizeStandSummary}\nВремя игры: {formattedTime}{scoreLine}"),
|
||||
_ => CreateWinState(formattedTime, finalScore, showFinalScore)
|
||||
};
|
||||
}
|
||||
|
||||
private static BannerState CreatePrizeState(string title, string details, string legacyCombinedText, bool showQRCodes = false)
|
||||
{
|
||||
return new BannerState
|
||||
{
|
||||
IsPrizeBanner = true,
|
||||
PrizeTitle = title,
|
||||
PrizeDetails = details,
|
||||
LegacyCombinedText = legacyCombinedText,
|
||||
ShowQRCodes = showQRCodes,
|
||||
FallbackWinText = string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static BannerState CreateWinState(string formattedTime, int finalScore, bool showFinalScore)
|
||||
{
|
||||
string winText = showFinalScore
|
||||
? $"Время игры: {formattedTime}\nОчки за игру: {finalScore}"
|
||||
: $"Время игры: {formattedTime}";
|
||||
|
||||
return new BannerState
|
||||
{
|
||||
IsPrizeBanner = false,
|
||||
PrizeTitle = string.Empty,
|
||||
PrizeDetails = string.Empty,
|
||||
LegacyCombinedText = winText,
|
||||
FallbackWinText = winText,
|
||||
ShowQRCodes = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отображает QR-коды из PrizeSystemManager, если для финального уровня назначен QR-приз.
|
||||
/// </summary>
|
||||
private void DisplayQRCodes(bool shouldDisplay)
|
||||
{
|
||||
// Очищаем ранее созданные QR-коды
|
||||
ClearQRCodes();
|
||||
|
||||
if (qrCodesContainer == null) return;
|
||||
|
||||
// Проверяем настройку показа QR-кодов
|
||||
if (SettingsManager.Instance == null || !SettingsManager.Instance.GetBool("prizes.giveBigPrize"))
|
||||
|
||||
if (qrCodesContainer == null)
|
||||
return;
|
||||
|
||||
if (PrizeSystemManager.Instance == null) return;
|
||||
|
||||
Texture2D[] qrCodes = PrizeSystemManager.Instance.GetQRCodes();
|
||||
if (qrCodes == null || qrCodes.Length == 0) return;
|
||||
|
||||
// Создаём QR-коды динамически
|
||||
|
||||
GetQRSectionRoot().SetActive(shouldDisplay);
|
||||
if (!shouldDisplay || PrizeSystemManager.Instance == null)
|
||||
return;
|
||||
|
||||
QRTier tier = PrizeSystemManager.Instance.GetPrizeConfigForCurrentLevel().qrTier;
|
||||
Texture2D[] qrCodes = PrizeSystemManager.Instance.GetQRCodes(tier);
|
||||
if (qrCodes == null || qrCodes.Length == 0)
|
||||
{
|
||||
GetQRSectionRoot().SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanDisplayBigPrizeQRCodes()
|
||||
{
|
||||
return SettingsManager.Instance != null && SettingsManager.Instance.GetBool("prizes.giveBigPrize");
|
||||
}
|
||||
|
||||
private bool HasQRCodes(QRTier tier)
|
||||
{
|
||||
if (PrizeSystemManager.Instance == null)
|
||||
return false;
|
||||
|
||||
Texture2D[] qrCodes = PrizeSystemManager.Instance.GetQRCodes(tier);
|
||||
if (qrCodes == null || qrCodes.Length == 0)
|
||||
return false;
|
||||
|
||||
foreach (Texture2D qrCode in qrCodes)
|
||||
{
|
||||
if (qrCode != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private GameObject GetQRSectionRoot()
|
||||
{
|
||||
return qrSectionRoot != null ? qrSectionRoot : qrCodesContainer.gameObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает созданные QR-код объекты.
|
||||
@ -135,7 +248,66 @@ namespace RR.Common.UI
|
||||
{
|
||||
base.OnDisable();
|
||||
ClearQRCodes();
|
||||
if (qrCodesContainer != null)
|
||||
GetQRSectionRoot().SetActive(true);
|
||||
if (winContentRoot != null)
|
||||
winContentRoot.SetActive(true);
|
||||
if (prizeContentRoot != null)
|
||||
prizeContentRoot.SetActive(true);
|
||||
}
|
||||
|
||||
private static string GetQRTierName(QRTier tier)
|
||||
{
|
||||
return tier switch
|
||||
{
|
||||
QRTier.Bronze => "бронзовый",
|
||||
QRTier.Silver => "серебряный",
|
||||
QRTier.Gold => "золотой",
|
||||
_ => "обычный"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetPrizeStandSummary(int finalScore, bool showFinalScore)
|
||||
{
|
||||
PrizeSystemManager manager = PrizeSystemManager.Instance;
|
||||
if (manager == null)
|
||||
return "Витрина недоступна";
|
||||
|
||||
PrizeStandConnectionInfo connection = manager.GetPrizeStandConnectionInfo();
|
||||
PrizeStandSettings settings = manager.GetPrizeStandSettings();
|
||||
int unlockedCount = 0;
|
||||
|
||||
if (settings?.boxes != null)
|
||||
{
|
||||
for (int i = 0; i < settings.boxes.Count; i++)
|
||||
{
|
||||
PrizeStandBoxConfig box = settings.boxes[i];
|
||||
if (box == null)
|
||||
continue;
|
||||
|
||||
bool isConnected = connection.IsConnected
|
||||
&& (connection.ConnectedBoxesCount <= 0 || box.boxNumber <= connection.ConnectedBoxesCount);
|
||||
bool isUnlocked = !showFinalScore || finalScore >= box.requiredScore;
|
||||
if (isConnected && isUnlocked)
|
||||
unlockedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
string connectionText = connection.IsConnected
|
||||
? $"Витрина подключена, ячеек на устройстве: {connection.ConnectedBoxesCount}"
|
||||
: "Витрина не подключена";
|
||||
|
||||
return $"{connectionText}\nЯчеек доступно по очкам: {unlockedCount}";
|
||||
}
|
||||
|
||||
private struct BannerState
|
||||
{
|
||||
public bool IsPrizeBanner;
|
||||
public bool ShowQRCodes;
|
||||
public string PrizeTitle;
|
||||
public string PrizeDetails;
|
||||
public string LegacyCombinedText;
|
||||
public string FallbackWinText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using QRCoder;
|
||||
using RR.Common.Prizes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace RR.Common.Utility
|
||||
@ -84,13 +86,23 @@ namespace RR.Common.Utility
|
||||
/// </summary>
|
||||
public bool generateQRCode;
|
||||
/// <summary>
|
||||
/// Ранг QR-кода.
|
||||
/// </summary>
|
||||
public QRTier tier;
|
||||
/// <summary>
|
||||
/// Префикс ссылки (например, "https://example.com/").
|
||||
/// </summary>
|
||||
public string linkPrefix;
|
||||
/// <summary>
|
||||
/// Суффикс, добавляемый в качестве параметра запроса.
|
||||
/// Шаблон сообщения для мессенджера. Используйте {0} для подстановки текста приза.
|
||||
/// </summary>
|
||||
public string suffix;
|
||||
[TextArea(2, 5)]
|
||||
[FormerlySerializedAs("suffix")]
|
||||
public string messageFormat;
|
||||
/// <summary>
|
||||
/// Короткий текст приза, который подставляется в шаблон сообщения.
|
||||
/// </summary>
|
||||
public string prizeText;
|
||||
/// <summary>
|
||||
/// Темный цвет в QR-коде.
|
||||
/// </summary>
|
||||
@ -114,8 +126,38 @@ namespace RR.Common.Utility
|
||||
/// </summary>
|
||||
/// <param name="data">Основные данные для включения в ссылку.</param>
|
||||
/// <returns>Полная ссылка в формате: {linkPrefix}{data}?text={suffix}</returns>
|
||||
public string GetFullLink(string data) => $"{linkPrefix}{data}?text={ProcessSuffix(suffix)}";
|
||||
|
||||
public string GetFullLink(string data) => $"{linkPrefix}{data}?text={ProcessSuffix(BuildMessageText())}";
|
||||
|
||||
public string BuildMessageText()
|
||||
{
|
||||
string format = messageFormat ?? string.Empty;
|
||||
string prize = prizeText ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(format))
|
||||
{
|
||||
return prize;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (format.Contains("{0}"))
|
||||
{
|
||||
return string.Format(format, prize);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
Debug.LogWarning($"[QRCodeInfo] Некорректный формат текста QR: '{format}'.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(prize))
|
||||
{
|
||||
return format;
|
||||
}
|
||||
|
||||
return $"{format}\n{prize}";
|
||||
}
|
||||
|
||||
private string ProcessSuffix(string rawSuffix) => Uri.EscapeDataString(rawSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f534e2680fea30549b21bbae25117e86
|
||||
guid: 7769fb98cc2c8b84a84ee2010fafe903
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
61219
Assets/_Recovery/0 (1).unity
Normal file
61219
Assets/_Recovery/0 (1).unity
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/_Recovery/0 (1).unity.meta
Normal file
7
Assets/_Recovery/0 (1).unity.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09910f991bc746544ad0f0c9a923f7b1
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
54469
Assets/_Recovery/0.unity
Normal file
54469
Assets/_Recovery/0.unity
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/_Recovery/0.unity.meta
Normal file
7
Assets/_Recovery/0.unity.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4da74cb807a72eb4c826a38c04cb1823
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
89
prizes.json
89
prizes.json
@ -1,21 +1,98 @@
|
||||
{
|
||||
"prizeStandSettings": {
|
||||
"boxes": [
|
||||
{
|
||||
"boxNumber": 1,
|
||||
"requiredScore": 50
|
||||
},
|
||||
{
|
||||
"boxNumber": 2,
|
||||
"requiredScore": 50
|
||||
},
|
||||
{
|
||||
"boxNumber": 3,
|
||||
"requiredScore": 50
|
||||
},
|
||||
{
|
||||
"boxNumber": 4,
|
||||
"requiredScore": 50
|
||||
},
|
||||
{
|
||||
"boxNumber": 5,
|
||||
"requiredScore": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
"qrCodes": [
|
||||
{
|
||||
"generateQRCode": true,
|
||||
"tier": 1,
|
||||
"linkPrefix": "https://t.me/",
|
||||
"messageFormat": "Здравствуйте!\\nЯ выиграл в игре! Жду свой приз: {0}",
|
||||
"prizeText": "В",
|
||||
"darkColor": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"lightColor": {
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"z": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"generateQRCode": true,
|
||||
"tier": 2,
|
||||
"linkPrefix": "https://t.me/",
|
||||
"messageFormat": "Здравствуйте!\\nЯ выиграл в игре! Жду свой приз: {0}",
|
||||
"prizeText": "Б",
|
||||
"darkColor": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"lightColor": {
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"z": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"generateQRCode": true,
|
||||
"tier": 3,
|
||||
"linkPrefix": "https://t.me/",
|
||||
"messageFormat": "Здравствуйте!\\nЯ выиграл в игре! Жду свой приз: {0}",
|
||||
"prizeText": "",
|
||||
"darkColor": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"lightColor": {
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"z": 1.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"levels": [
|
||||
{
|
||||
"level": "1-1",
|
||||
"config": {
|
||||
"type": 2,
|
||||
"type": 3,
|
||||
"qrTier": 1,
|
||||
"hopperCapsuleCount": 2,
|
||||
"hopperDelayBetweenCapsules": 1.0,
|
||||
"prizeStandBoxCount": 5
|
||||
"hopperDelayBetweenCapsules": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"level": "1-2",
|
||||
"config": {
|
||||
"type": 1,
|
||||
"type": 3,
|
||||
"qrTier": 2,
|
||||
"hopperCapsuleCount": 1,
|
||||
"hopperDelayBetweenCapsules": 1.0,
|
||||
"prizeStandBoxCount": 6
|
||||
"hopperDelayBetweenCapsules": 1.0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user