feat(prizes, ui): add configurable level prize flow and launcher assets
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
- добавлена новая система призов: PrizeBase, PrizeType, PrizeDatabase, LevelPrizeConfig и реализации для hopper, QR-кода, отсутствия приза и стойки с призом - добавлена конфигурация призов уровней через `prizes.json` и связанная логика загрузки/обработки в менеджерах игры - расширен UI главного меню и настроек уровней: добавлены LevelSettingsModal, LevelPrizeSettingsModal, QRCodeModal и PrizeStandModal - обновлена навигация модальных окон и связанный UI-код: ModalManager, MainMenu, RulesModal, PauseMenu, PrizeErrorDisplay, UIRowPointer и WinBannerController - доработаны настройки и генерация UI-элементов: обновлены SettingsDatabase, SettingsUIGenerator, SettingUIItem, ValueSelectorRow и editor drawer для настроек - обновлены и добавлены UI-prefab'ы для кнопок, строк настроек, панели групп и кнопки настроек уровня - обновлены сцены `MainMenu` и `Level UI` под новый флоу настройки уровней, выбора призов и отображения модалок - добавлены файлы лаунчера в `StreamingAssets`, включая `RRLauncher.exe`, служебные meta-файлы и документацию - улучшено логирование через FileLogger и удален устаревший код лицензирования - удалены больше неиспользуемые файлы: `Licensing.cs`, `SteppedSlider.cs` и recovery-сцены
This commit is contained in:
parent
765a5b0f1a
commit
a40b761c81
9
.gitignore
vendored
9
.gitignore
vendored
@ -10,6 +10,15 @@
|
||||
#
|
||||
## --------------------------------------------
|
||||
|
||||
## -------- RR .gitignore Section -------------
|
||||
#
|
||||
|
||||
*.lic.json
|
||||
*.req.json
|
||||
*.res.json
|
||||
|
||||
#
|
||||
## --------------------------------------------
|
||||
|
||||
.utmp/
|
||||
/[Ll]ibrary/
|
||||
|
||||
@ -926,6 +926,771 @@ MonoBehaviour:
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -260826145
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -6.328125
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 15
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -7.602539
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 17
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -0.9667969
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 86
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.6259766
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 339
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 36
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 129
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 130
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 131
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 132
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 133
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 134
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 193
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 195
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 197
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.9335938
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 328
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 68
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 161
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 162
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 163
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 164
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 165
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 166
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 167
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 194
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 196
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 198
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 329
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 70
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 71
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 72
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 82
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 84
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 168
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 169
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 170
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 171
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 172
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 177
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 179
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 180
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 181
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 182
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 183
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 185
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 200
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 206
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 212
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 216
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 271
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 333
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 41
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: -1.0546875
|
||||
m_YAdvance: 0
|
||||
m_SecondAdjustmentRecord:
|
||||
m_GlyphIndex: 341
|
||||
m_GlyphValueRecord:
|
||||
m_XPlacement: 0
|
||||
m_YPlacement: 0
|
||||
m_XAdvance: 0
|
||||
m_YAdvance: 0
|
||||
m_FeatureLookupFlags: -120513569
|
||||
m_MarkToBaseAdjustmentRecords: []
|
||||
m_MarkToMarkAdjustmentRecords: []
|
||||
m_ShouldReimportFontFeatures: 0
|
||||
|
||||
@ -447,6 +447,14 @@ MonoBehaviour:
|
||||
m_LigatureGlyphID: 730
|
||||
- m_ComponentGlyphIDs: 2d000000ac000000
|
||||
m_LigatureGlyphID: 1181
|
||||
- m_ComponentGlyphIDs: 57000000a9000000
|
||||
m_LigatureGlyphID: 773
|
||||
- m_ComponentGlyphIDs: 57000000ba010000
|
||||
m_LigatureGlyphID: 779
|
||||
- m_ComponentGlyphIDs: 57000000ad000000
|
||||
m_LigatureGlyphID: 1214
|
||||
- m_ComponentGlyphIDs: 5700000058000000
|
||||
m_LigatureGlyphID: 449
|
||||
m_GlyphPairAdjustmentRecords:
|
||||
- m_FirstAdjustmentRecord:
|
||||
m_GlyphIndex: 4
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5af159fe7617e0d49bac227578248a75
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 235559
|
||||
packageName: Violet Themed UI
|
||||
packageVersion: 1.2
|
||||
assetPath: Assets/Violet Theme Ui/Panels/Dark Panels Style 2 Orange.png
|
||||
uploadId: 767621
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
@ -70,7 +77,7 @@ TextureImporter:
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
@ -83,7 +90,7 @@ TextureImporter:
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
@ -96,7 +103,7 @@ TextureImporter:
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
@ -109,6 +116,32 @@ TextureImporter:
|
||||
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:
|
||||
@ -121,8 +154,9 @@ TextureImporter:
|
||||
width: 1002
|
||||
height: 1115
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 46, y: 43, z: 46, w: 186}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
@ -134,15 +168,18 @@ TextureImporter:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
spriteID: 5444f66c84462ba44aa5d31f9e249e08
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
Dark Panels Style 2 Orange_0: -3595877653700127741
|
||||
mipmapLimitGroupName:
|
||||
@ -150,10 +187,3 @@ TextureImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 235559
|
||||
packageName: Violet Themed UI
|
||||
packageVersion: 1.2
|
||||
assetPath: Assets/Violet Theme Ui/Panels/Dark Panels Style 2 Orange.png
|
||||
uploadId: 767621
|
||||
|
||||
@ -462,6 +462,17 @@
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "Down",
|
||||
"id": "adb11b37-a07d-49c1-a09e-1eb7df785b9e",
|
||||
"path": "<Gamepad>/dpad/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "d873cdef-5787-47ac-b0b5-a2889157168b",
|
||||
|
||||
260
Assets/Prefabs/UI/Button.prefab
Normal file
260
Assets/Prefabs/UI/Button.prefab
Normal file
@ -0,0 +1,260 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1758083603916175787
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7082477975537308933}
|
||||
- component: {fileID: 6418208233271057322}
|
||||
- component: {fileID: 8603317830485265751}
|
||||
- component: {fileID: 3891422000916034842}
|
||||
m_Layer: 0
|
||||
m_Name: Button
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7082477975537308933
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 7969020585539137307}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6418208233271057322
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8603317830485265751
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: -8111322212487550922, guid: 31b2877e77610584cac3a41c46e85320, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &3891422000916034842
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 0.76862746, g: 0.76862746, b: 0.76862746, a: 1}
|
||||
m_HighlightedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_PressedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_SelectedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_DisabledColor: {r: 0.63529414, g: 0.63529414, b: 0.63529414, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 8603317830485265751}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &2307937535396352836
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7969020585539137307}
|
||||
- component: {fileID: 2544164544592874034}
|
||||
- component: {fileID: 2985328858931436486}
|
||||
m_Layer: 0
|
||||
m_Name: Text (TMP)
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7969020585539137307
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2307937535396352836}
|
||||
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: 7082477975537308933}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2544164544592874034
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2307937535396352836}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &2985328858931436486
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2307937535396352836}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: 1
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4278190080
|
||||
m_fontColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 48
|
||||
m_fontSizeBase: 48
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 12
|
||||
m_fontSizeMax: 48
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
7
Assets/Prefabs/UI/Button.prefab.meta
Normal file
7
Assets/Prefabs/UI/Button.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c4e23b11ab109d4e9af1f2cc97b3515
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -92,6 +92,35 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 6828bed9c9414fe4bfd284a92d73f36b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.TextInputCell
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 6850433135119910662}
|
||||
symbolText: {fileID: 91819688666493321}
|
||||
backgroundImage: {fileID: 6850433135119910662}
|
||||
selectionHighlight: {fileID: 5330937721467949768}
|
||||
@ -240,8 +269,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: N
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontAsset: {fileID: 11400000, guid: 0161d805a3764c089bef00bfe00793f5, type: 2}
|
||||
m_sharedMaterial: {fileID: 2147374, guid: 0161d805a3764c089bef00bfe00793f5, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@ -265,7 +294,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 135.15
|
||||
m_fontSize: 101.85
|
||||
m_fontSizeBase: 200
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -402,7 +431,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!224 &4445986152597778029
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -127,8 +127,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: 1-1
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
|
||||
260
Assets/Prefabs/UI/LevelButton_Settings.prefab
Normal file
260
Assets/Prefabs/UI/LevelButton_Settings.prefab
Normal file
@ -0,0 +1,260 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1758083603916175787
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7082477975537308933}
|
||||
- component: {fileID: 6418208233271057322}
|
||||
- component: {fileID: 8603317830485265751}
|
||||
- component: {fileID: 3891422000916034842}
|
||||
m_Layer: 0
|
||||
m_Name: LevelButton_Settings
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7082477975537308933
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 7969020585539137307}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6418208233271057322
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8603317830485265751
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: -8111322212487550922, guid: 31b2877e77610584cac3a41c46e85320, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &3891422000916034842
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1758083603916175787}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 0.76862746, g: 0.76862746, b: 0.76862746, a: 1}
|
||||
m_HighlightedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_PressedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_SelectedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_DisabledColor: {r: 0.63529414, g: 0.63529414, b: 0.63529414, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 8603317830485265751}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &2307937535396352836
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7969020585539137307}
|
||||
- component: {fileID: 2544164544592874034}
|
||||
- component: {fileID: 2985328858931436486}
|
||||
m_Layer: 0
|
||||
m_Name: Text (TMP)
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7969020585539137307
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2307937535396352836}
|
||||
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: 7082477975537308933}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2544164544592874034
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2307937535396352836}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &2985328858931436486
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2307937535396352836}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: 1-1
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2036267684637915601, guid: 4ef792f79e96f814cac2dd9b8734773d, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4278190080
|
||||
m_fontColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 48
|
||||
m_fontSizeBase: 48
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
7
Assets/Prefabs/UI/LevelButton_Settings.prefab.meta
Normal file
7
Assets/Prefabs/UI/LevelButton_Settings.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcaf0cbd99d0a364ea9f6c350de4243e
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -389,8 +389,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
|
||||
@ -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: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
|
||||
@ -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: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
@ -167,7 +167,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 40.4
|
||||
m_fontSize: 37.2
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -279,8 +279,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: Option A
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, 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: []
|
||||
@ -304,10 +304,10 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 28
|
||||
m_fontSize: 30.8
|
||||
m_fontSizeBase: 28
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 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: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
@ -92,7 +92,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 40.4
|
||||
m_fontSize: 37.2
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -424,8 +424,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: Enter text...
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, 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: []
|
||||
@ -449,10 +449,10 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 32
|
||||
m_fontSize: 29.1
|
||||
m_fontSizeBase: 32
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 2
|
||||
|
||||
@ -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: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
@ -167,7 +167,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 40.4
|
||||
m_fontSize: 37.2
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
@ -563,8 +563,8 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_text: 100%
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
|
||||
@ -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: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
@ -92,7 +92,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 40.4
|
||||
m_fontSize: 37.2
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 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: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
@ -92,7 +92,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 40.4
|
||||
m_fontSize: 37.2
|
||||
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: 5302535af1044152a457ed104f1f4b91, type: 2}
|
||||
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, 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: []
|
||||
@ -229,7 +229,7 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 42.65
|
||||
m_fontSize: 38.9
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
|
||||
@ -210,8 +210,6 @@ 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
|
||||
@ -242,3 +240,4 @@ MonoBehaviour:
|
||||
button: 4
|
||||
dpadDirection: 1
|
||||
logCreateDevice: 1
|
||||
logPinPresses: 1
|
||||
|
||||
@ -131,39 +131,17 @@ TextureImporter:
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: plashka_levels_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 213
|
||||
y: 184
|
||||
width: 122
|
||||
height: 166
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: af4f057a4cc826b80800000000000000
|
||||
internalID: -8402999178471344902
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: plashka_levels_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 287
|
||||
x: 215
|
||||
y: 184
|
||||
width: 489
|
||||
width: 637
|
||||
height: 166
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 152, y: 0, z: 157, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
@ -175,28 +153,6 @@ TextureImporter:
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: plashka_levels_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 732
|
||||
y: 184
|
||||
width: 121
|
||||
height: 166
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: cdc553803c4120600800000000000000
|
||||
internalID: 432931342069882076
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
||||
using UnityEngine;
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.Settings;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Game2D.Behaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Компонент финиша уровня с возможностью выдачи маленького приза.
|
||||
/// Компонент финиша уровня с возможностью выдачи приза.
|
||||
/// Устанавливается на объект с триггером и тегом "Finish".
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
@ -16,9 +16,6 @@ namespace RR.Game2D.Behaviour
|
||||
[Tooltip("Включить выдачу приза при завершении уровня")]
|
||||
[SerializeField] private bool enablePrizeOnFinish = true;
|
||||
|
||||
[Tooltip("Ключ настройки для проверки включения выдачи призов (если пусто - не проверяется)")]
|
||||
[SerializeField] private string prizeEnabledSettingKey = "prizes.giveSmallPrize";
|
||||
|
||||
[Tooltip("Задержка перед выдачей приза (в секундах)")]
|
||||
[SerializeField] private float prizeDelay = 0.5f;
|
||||
|
||||
@ -26,17 +23,36 @@ namespace RR.Game2D.Behaviour
|
||||
[SerializeField] private bool debugMode;
|
||||
|
||||
private bool _prizeGiven;
|
||||
private bool _waitingForPrize;
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
// Проверяем, что это игрок
|
||||
if (!collision.CompareTag("Player")) return;
|
||||
|
||||
// Пытаемся выдать приз
|
||||
TryGivePrize();
|
||||
if (enablePrizeOnFinish && !_prizeGiven && HasPrizeForCurrentLevel())
|
||||
{
|
||||
// Есть приз — выдаём и ждём результата, потом переходим
|
||||
TryGivePrize();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Приза нет или он отключён — переходим сразу
|
||||
GameManager.Instance.NextLevel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, назначен ли приз для текущего уровня.
|
||||
/// </summary>
|
||||
private bool HasPrizeForCurrentLevel()
|
||||
{
|
||||
var psm = PrizeSystemManager.Instance;
|
||||
if (psm == null || psm.Database == null) return false;
|
||||
|
||||
// Переход на следующий уровень
|
||||
GameManager.Instance.NextLevel();
|
||||
string level = $"{GameManager.Instance.World}-{GameManager.Instance.Stage}";
|
||||
LevelPrizeConfig config = psm.Database.GetConfig(level);
|
||||
return config.type != PrizeType.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -60,20 +76,13 @@ namespace RR.Game2D.Behaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка настроек (если указан ключ)
|
||||
if (!string.IsNullOrEmpty(prizeEnabledSettingKey))
|
||||
{
|
||||
bool settingEnabled = SettingsManager.Instance.GetBool(prizeEnabledSettingKey);
|
||||
if (!settingEnabled)
|
||||
{
|
||||
if (debugMode)
|
||||
Debug.Log($"[LevelFinish] Выдача призов отключена в настройках ({prizeEnabledSettingKey}).");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Все проверки пройдены - выдаем приз
|
||||
_prizeGiven = true;
|
||||
_waitingForPrize = true;
|
||||
|
||||
// Подписываемся на результат выдачи приза
|
||||
var psm = PrizeSystemManager.Instance;
|
||||
if (psm != null)
|
||||
psm.OnAnyPrizeResult += OnPrizeFinished;
|
||||
|
||||
if (prizeDelay > 0)
|
||||
{
|
||||
@ -86,17 +95,41 @@ namespace RR.Game2D.Behaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выдает приз через PrizeSystemManager (учитывает флаг enableHopper).
|
||||
/// Вызывается после завершения выдачи приза (успешно или нет).
|
||||
/// </summary>
|
||||
private void OnPrizeFinished(PrizeType type, bool success)
|
||||
{
|
||||
if (!_waitingForPrize) return;
|
||||
_waitingForPrize = false;
|
||||
|
||||
var psm = PrizeSystemManager.Instance;
|
||||
if (psm != null)
|
||||
psm.OnAnyPrizeResult -= OnPrizeFinished;
|
||||
|
||||
if (debugMode)
|
||||
Debug.Log($"[LevelFinish] Приз «{type}» завершён (успех: {success}). Переход на следующий уровень.");
|
||||
|
||||
GameManager.Instance.NextLevel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выдает приз через PrizeSystemManager — тип приза берётся из JSON-базы для текущего уровня.
|
||||
/// </summary>
|
||||
private void GivePrize()
|
||||
{
|
||||
if (debugMode)
|
||||
Debug.Log("[LevelFinish] Выдаю приз игроку!");
|
||||
|
||||
if (RR.Common.Managers.PrizeSystemManager.Instance != null)
|
||||
RR.Common.Managers.PrizeSystemManager.Instance.GivePrize();
|
||||
if (PrizeSystemManager.Instance != null)
|
||||
PrizeSystemManager.Instance.GivePrizeForCurrentLevel();
|
||||
else
|
||||
PrizeSystem.GivePrize(); // fallback
|
||||
{
|
||||
if (debugMode)
|
||||
Debug.LogWarning("[LevelFinish] PrizeSystemManager не найден.");
|
||||
// Если менеджер не найден — переходим сразу
|
||||
_waitingForPrize = false;
|
||||
GameManager.Instance.NextLevel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -105,6 +138,17 @@ namespace RR.Game2D.Behaviour
|
||||
public void ResetPrize()
|
||||
{
|
||||
_prizeGiven = false;
|
||||
_waitingForPrize = false;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_waitingForPrize)
|
||||
{
|
||||
var psm = PrizeSystemManager.Instance;
|
||||
if (psm != null)
|
||||
psm.OnAnyPrizeResult -= OnPrizeFinished;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@ -43,6 +43,8 @@ namespace RR.Common.InspectorOverrides
|
||||
var minFloatProp = property.FindPropertyRelative("minFloat");
|
||||
var maxFloatProp = property.FindPropertyRelative("maxFloat");
|
||||
var displayAsPercentageProp = property.FindPropertyRelative("displayAsPercentage");
|
||||
var floatDisplayFormatProp = property.FindPropertyRelative("floatDisplayFormat");
|
||||
var floatStepProp = property.FindPropertyRelative("floatStep");
|
||||
|
||||
var applyProp = property.FindPropertyRelative("applyTarget");
|
||||
var audioTargetProp = property.FindPropertyRelative("audioTargetType");
|
||||
@ -106,6 +108,14 @@ namespace RR.Common.InspectorOverrides
|
||||
DrawProp(ref r, defFloatProp, "Default");
|
||||
DrawMinMaxRow(ref r, minFloatProp, maxFloatProp, "Min", "Max");
|
||||
DrawProp(ref r, displayAsPercentageProp, "Display as %");
|
||||
DrawProp(ref r, floatDisplayFormatProp, "Display Format");
|
||||
|
||||
// Показываем floatStep только если выбран ValueSelector
|
||||
var floatDisplayFormat = (FloatDisplayFormat)floatDisplayFormatProp.enumValueIndex;
|
||||
if (floatDisplayFormat == FloatDisplayFormat.ValueSelector)
|
||||
{
|
||||
DrawProp(ref r, floatStepProp, "Step");
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingValueType.String:
|
||||
@ -227,6 +237,18 @@ namespace RR.Common.InspectorOverrides
|
||||
h += line + space; // Default
|
||||
h += line + space; // range row
|
||||
h += line + space; // displayAsPercentage checkbox
|
||||
h += line + space; // floatDisplayFormat
|
||||
|
||||
// Добавляем высоту для floatStep если выбран ValueSelector
|
||||
var floatDisplayFormatProp = property.FindPropertyRelative("floatDisplayFormat");
|
||||
if (floatDisplayFormatProp != null)
|
||||
{
|
||||
var fDisplayFormat = (FloatDisplayFormat)floatDisplayFormatProp.enumValueIndex;
|
||||
if (fDisplayFormat == FloatDisplayFormat.ValueSelector)
|
||||
{
|
||||
h += line + space; // floatStep
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingValueType.Button:
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
namespace DefaultNamespace
|
||||
{
|
||||
public class Licensing
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cd2c0f3cbe34cc084ad53c2371229be
|
||||
timeCreated: 1773127780
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using RR.Common.Controls;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Audio;
|
||||
@ -10,6 +11,7 @@ using RR.Common.Settings;
|
||||
using RR.Common.Utility;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Entities;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RR.Game2D.Core.Managers
|
||||
{
|
||||
@ -104,6 +106,8 @@ namespace RR.Game2D.Core.Managers
|
||||
DontDestroyOnLoad(gameObject);
|
||||
// -------------------
|
||||
|
||||
StartCoroutine(CheckLicenseCoroutine());
|
||||
|
||||
// Initialize Logger
|
||||
if (Logger == null)
|
||||
{
|
||||
@ -699,6 +703,312 @@ public void TogglePause() => SetPause(!IsPaused);
|
||||
}
|
||||
// --
|
||||
|
||||
#endregion
|
||||
|
||||
#region DEFENCE
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct STARTUPINFO
|
||||
{
|
||||
public int cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public int dwX;
|
||||
public int dwY;
|
||||
public int dwXSize;
|
||||
public int dwYSize;
|
||||
public int dwXCountChars;
|
||||
public int dwYCountChars;
|
||||
public int dwFillAttribute;
|
||||
public int dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool CreateProcess(
|
||||
string lpApplicationName,
|
||||
string lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
string lpCurrentDirectory,
|
||||
ref STARTUPINFO lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
private const uint WAIT_TIMEOUT = 0x00000102;
|
||||
private const uint WAIT_OBJECT_0 = 0x00000000;
|
||||
|
||||
private System.Collections.IEnumerator CheckLicenseCoroutine()
|
||||
{
|
||||
// Ждём один кадр, чтобы Unity завершил инициализацию
|
||||
yield return null;
|
||||
|
||||
bool licenseValid = false;
|
||||
bool checkCompleted = false;
|
||||
//bool licenseValid = true;
|
||||
//bool checkCompleted = true;
|
||||
string errorMessage = "";
|
||||
|
||||
// Запускаем проверку в отдельном потоке (совместимо с IL2CPP)
|
||||
System.Threading.Thread thread = new System.Threading.Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
licenseValid = CheckLicense();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
checkCompleted = true;
|
||||
}
|
||||
});
|
||||
|
||||
thread.IsBackground = true;
|
||||
thread.Start();
|
||||
|
||||
float timerest = 30f;
|
||||
while (!checkCompleted && timerest > 0)
|
||||
{
|
||||
timerest -= Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (!checkCompleted) licenseValid = false;
|
||||
|
||||
if (!licenseValid)
|
||||
{
|
||||
yield return new WaitForSeconds(3f);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.isPlaying = false;
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
//exclusive
|
||||
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
|
||||
Screen.SetResolution(1920, 1080, true);
|
||||
ClickCenter();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool CheckLicense()
|
||||
{
|
||||
try
|
||||
{
|
||||
string launcherName = "RRLauncher.exe";
|
||||
string launcherPath = null;
|
||||
|
||||
// Поиск лаунчера рядом с исполняемым файлом
|
||||
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string pathInBase = Path.Combine(baseDirectory, launcherName);
|
||||
|
||||
if (File.Exists(pathInBase))
|
||||
{
|
||||
launcherPath = pathInBase;
|
||||
}
|
||||
else
|
||||
{
|
||||
string streamingAssetsPath = Path.Combine(Application.streamingAssetsPath, launcherName);
|
||||
if (File.Exists(streamingAssetsPath))
|
||||
{
|
||||
launcherPath = streamingAssetsPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
STARTUPINFO si = new STARTUPINFO();
|
||||
si.cb = Marshal.SizeOf(si);
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
string workingDir = Path.GetDirectoryName(launcherPath);
|
||||
|
||||
bool success = CreateProcess(
|
||||
launcherPath,
|
||||
null,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
false,
|
||||
0,
|
||||
IntPtr.Zero,
|
||||
workingDir,
|
||||
ref si,
|
||||
out pi);
|
||||
|
||||
if (!success) return false;
|
||||
uint waitResult = WaitForSingleObject(pi.hProcess, 30000);
|
||||
|
||||
if (waitResult == WAIT_TIMEOUT)
|
||||
{
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Получение кода возврата
|
||||
uint exitCode;
|
||||
if (!GetExitCodeProcess(pi.hProcess, out exitCode))
|
||||
{
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
ForceToFront();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception outerEx)
|
||||
{
|
||||
Debug.LogError($"[License] CRITICAL ERROR in CheckLicense: {outerEx.Message}");
|
||||
Debug.LogError($"[License] Stack trace: {outerEx.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region FORCE TO FRONT
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(IntPtr hwnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetActiveWindow();
|
||||
|
||||
public void ForceToFront()//Метод который возвращает фокус а окно после открытия защиты
|
||||
{
|
||||
IntPtr handle = GetActiveWindow();
|
||||
SetForegroundWindow(handle);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region клик в центр
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct INPUT
|
||||
{
|
||||
public uint type;
|
||||
public MOUSEINPUT mi;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MOUSEINPUT
|
||||
{
|
||||
public int dx;
|
||||
public int dy;
|
||||
public uint mouseData;
|
||||
public uint dwFlags;
|
||||
public uint time;
|
||||
public IntPtr dwExtraInfo;
|
||||
}
|
||||
|
||||
private const uint INPUT_MOUSE = 0;
|
||||
private const uint MOUSEEVENTF_MOVE = 0x0001;
|
||||
private const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
|
||||
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
|
||||
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
|
||||
public static void ClickCenter()
|
||||
{
|
||||
int screenX = Screen.currentResolution.width / 2;
|
||||
int screenY = Screen.currentResolution.height / 2;
|
||||
|
||||
// WinAPI использует координаты 0–65535
|
||||
int absoluteX = (int)(screenX * 65535 / Screen.currentResolution.width);
|
||||
int absoluteY = (int)(screenY * 65535 / Screen.currentResolution.height);
|
||||
|
||||
INPUT[] inputs = new INPUT[3];
|
||||
|
||||
// движение мыши
|
||||
inputs[0] = new INPUT
|
||||
{
|
||||
type = INPUT_MOUSE,
|
||||
mi = new MOUSEINPUT
|
||||
{
|
||||
dx = absoluteX,
|
||||
dy = absoluteY,
|
||||
dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
|
||||
}
|
||||
};
|
||||
|
||||
// нажатие
|
||||
inputs[1] = new INPUT
|
||||
{
|
||||
type = INPUT_MOUSE,
|
||||
mi = new MOUSEINPUT
|
||||
{
|
||||
dwFlags = MOUSEEVENTF_LEFTDOWN
|
||||
}
|
||||
};
|
||||
|
||||
// отпускание
|
||||
inputs[2] = new INPUT
|
||||
{
|
||||
type = INPUT_MOUSE,
|
||||
mi = new MOUSEINPUT
|
||||
{
|
||||
dwFlags = MOUSEEVENTF_LEFTUP
|
||||
}
|
||||
};
|
||||
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Common.UI
|
||||
@ -11,14 +12,19 @@ namespace RR.Common.UI
|
||||
public class HopperCheckController : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Запускает выдачу приза. Привязать к кнопке в инспекторе.
|
||||
/// Запускает выдачу приза хоппером. Привязать к кнопке в инспекторе.
|
||||
/// </summary>
|
||||
public void CheckGivePrize()
|
||||
{
|
||||
if (PrizeSystemManager.Instance != null)
|
||||
PrizeSystemManager.Instance.GivePrize();
|
||||
{
|
||||
// Используем новый API с указанием типа приза
|
||||
PrizeSystemManager.Instance.GivePrize(PrizeType.Hopper);
|
||||
}
|
||||
else
|
||||
PrizeSystem.GivePrize();
|
||||
{
|
||||
PrizeSystem.GivePrize(); // fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,30 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RR.Common.Controls;
|
||||
using UnityEngine;
|
||||
using RR.Common.Utility;
|
||||
using RR.Common.Prizes;
|
||||
using RR.Common.Settings;
|
||||
using RR.Common.UI;
|
||||
using RR.Common.Utility;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Центральный менеджер системы призов.
|
||||
/// Не знает конкретики о призах — читает конфигурацию из JSON (<see cref="PrizeDatabase"/>)
|
||||
/// и делегирует выдачу зарегистрированным исполнителям (<see cref="PrizeBase"/>).
|
||||
/// </summary>
|
||||
public class PrizeSystemManager : MonoBehaviour
|
||||
{
|
||||
public static PrizeSystemManager Instance { get; private set; }
|
||||
|
||||
[Header("Настройки выдачи призов")]
|
||||
[SerializeField] private bool givePrizes = true;
|
||||
|
||||
private bool _enableHopper;
|
||||
|
||||
[Header("Настройки генерации QR-кодов")]
|
||||
[Header("QR-коды (глобальная конфигурация)")]
|
||||
[SerializeField] private List<QRCodeInfo> qrCodes = new();
|
||||
private readonly List<Texture2D> generatedQRCodes = new();
|
||||
[SerializeField] private string phoneNumberMask = "+7XXXXXXXXXX";
|
||||
|
||||
[SerializeField] private string _phoneNumberMask = "+7XXXXXXXXXX";
|
||||
// [SerializeField] private string telegramLinkPrefix = "https://t.me/";
|
||||
// [SerializeField] private string whatsappLinkPrefix = "https://wa.me/";
|
||||
|
||||
/// <summary>Хранилище призов за уровни (JSON).</summary>
|
||||
public PrizeDatabase Database { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается после выдачи любого приза. Параметры: тип приза, успех.
|
||||
/// </summary>
|
||||
public event Action<PrizeType, bool> OnAnyPrizeResult;
|
||||
|
||||
// ── Исполнители ──
|
||||
private readonly Dictionary<PrizeType, PrizeBase> _executors = new();
|
||||
|
||||
|
||||
// ── QR-коды ──
|
||||
private readonly List<Texture2D> _generatedQRCodes = new();
|
||||
private Texture2D[] _qrCodesCache;
|
||||
private bool _qrCodesDirty = true;
|
||||
private string _phoneNumber = string.Empty;
|
||||
|
||||
// ────────────── Инициализация ──────────────
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
@ -36,88 +54,183 @@ namespace RR.Common.Managers
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
Debug.Log(givePrizes
|
||||
? "Prize System Initialized: Prizes will be given."
|
||||
: "Prize System Initialized: Prizes will NOT be given.");
|
||||
|
||||
// Получаем состояние хоппера из настроек
|
||||
var settings = SettingsManager.Instance;
|
||||
_enableHopper = settings != null && settings.GetBool("prizes.giveSmallPrize");
|
||||
|
||||
Debug.Log(_enableHopper ? "Hopper is enabled for prize dispensing." : "Hopper is disabled.");
|
||||
|
||||
// Получаем номер телефона из новой системы настроек (если он есть)
|
||||
RefreshPhoneNumber(settings);
|
||||
// Загружаем базу призов из JSON
|
||||
Database = new PrizeDatabase();
|
||||
Database.Load();
|
||||
|
||||
// QR-коды (глобальные)
|
||||
RefreshPhoneNumber();
|
||||
GenerateQRCodes();
|
||||
SubscribeToSettings();
|
||||
|
||||
// Подписываемся на изменение номера телефона для перегенерации QR-кодов
|
||||
if (settings != null)
|
||||
settings.OnSettingChanged += OnSettingChanged;
|
||||
// Регистрируем исполнителей — модальные окна регистрируют себя сами через static Current
|
||||
RegisterExecutor(new NonePrize());
|
||||
RegisterExecutor(new QRCodePrize(() => QRCodeModal.Current, GetQRCodes));
|
||||
RegisterExecutor(new HopperPrize());
|
||||
RegisterExecutor(new PrizeStandPrize(() => PrizeStandModal.Current));
|
||||
|
||||
Debug.Log("[PrizeSystemManager] Инициализирован. " +
|
||||
$"Исполнители: {_executors.Count}, Уровней в базе: {Database.GetAll().Count}");
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
var settings = SettingsManager.Instance;
|
||||
if (settings != null)
|
||||
settings.OnSettingChanged -= OnSettingChanged;
|
||||
UnsubscribeFromSettings();
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
private void OnSettingChanged(string key, SettingValue value)
|
||||
// ────────────── Публичный API ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Выдаёт приз за текущий уровень (World-Stage).
|
||||
/// Конфигурация берётся из <see cref="PrizeDatabase"/>.
|
||||
/// </summary>
|
||||
public void GivePrizeForCurrentLevel()
|
||||
{
|
||||
if (key == "prizes.giveSmallPrize")
|
||||
string level = $"{GameManager.Instance.World}-{GameManager.Instance.Stage}";
|
||||
GivePrizeForLevel(level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выдаёт приз за указанный уровень.
|
||||
/// </summary>
|
||||
public void GivePrizeForLevel(string levelName)
|
||||
{
|
||||
LevelPrizeConfig config = Database.GetConfig(levelName);
|
||||
|
||||
if (config.type == PrizeType.None)
|
||||
{
|
||||
_enableHopper = value.b;
|
||||
Debug.Log(_enableHopper
|
||||
? "[PrizeSystemManager] Хоппер включён."
|
||||
: "[PrizeSystemManager] Хоппер выключен.");
|
||||
Debug.Log($"[PrizeSystemManager] Уровень «{levelName}»: приз не назначен.");
|
||||
OnAnyPrizeResult?.Invoke(PrizeType.None, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == "prizes.phoneNumber")
|
||||
if (!_executors.TryGetValue(config.type, out PrizeBase executor))
|
||||
{
|
||||
RefreshPhoneNumber(SettingsManager.Instance);
|
||||
GenerateQRCodes();
|
||||
Debug.Log($"[PrizeSystemManager] Номер телефона изменён, QR-коды перегенерированы: {_phoneNumber}");
|
||||
Debug.LogWarning($"[PrizeSystemManager] Нет исполнителя для типа «{config.type}».");
|
||||
return;
|
||||
}
|
||||
|
||||
if (executor.IsGiving)
|
||||
{
|
||||
Debug.LogWarning($"[PrizeSystemManager] Исполнитель «{config.type}» занят.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[PrizeSystemManager] Уровень «{levelName}» → приз «{config.type}».");
|
||||
executor.Execute(this, config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обратная совместимость: выдаёт приз хоппером (если включён в настройках).
|
||||
/// </summary>
|
||||
public void GivePrize()
|
||||
{
|
||||
bool hopperEnabled = SettingsManager.Instance != null
|
||||
&& SettingsManager.Instance.GetBool("prizes.giveSmallPrize");
|
||||
|
||||
if (hopperEnabled && _executors.TryGetValue(PrizeType.Hopper, out PrizeBase executor))
|
||||
executor.Execute(this, new LevelPrizeConfig { type = PrizeType.Hopper });
|
||||
else
|
||||
Debug.Log("[PrizeSystemManager] Хоппер отключён в настройках.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выдаёт приз указанного типа с конфигурацией по умолчанию.
|
||||
/// </summary>
|
||||
public void GivePrize(PrizeType type)
|
||||
{
|
||||
if (!_executors.TryGetValue(type, out PrizeBase executor))
|
||||
{
|
||||
Debug.LogWarning($"[PrizeSystemManager] Нет исполнителя для «{type}».");
|
||||
return;
|
||||
}
|
||||
|
||||
executor.Execute(this, new LevelPrizeConfig { type = type });
|
||||
}
|
||||
|
||||
/// <summary>Возвращает QR-коды (кешируется до перегенерации).</summary>
|
||||
public Texture2D[] GetQRCodes()
|
||||
{
|
||||
if (_qrCodesDirty || _qrCodesCache == null)
|
||||
{
|
||||
_qrCodesCache = _generatedQRCodes.ToArray();
|
||||
_qrCodesDirty = false;
|
||||
}
|
||||
return _qrCodesCache;
|
||||
}
|
||||
|
||||
/// <summary>Идёт ли выдача какого-либо приза.</summary>
|
||||
public bool IsAnyPrizeGiving
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (PrizeBase ex in _executors.Values)
|
||||
if (ex.IsGiving) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshPhoneNumber(SettingsManager settings)
|
||||
|
||||
// ────────────── Регистрация исполнителей ──────────────
|
||||
|
||||
/// <summary>Регистрирует исполнитель приза. Заменяет предыдущий для того же типа.</summary>
|
||||
public void RegisterExecutor(PrizeBase executor)
|
||||
{
|
||||
_executors[executor.Type] = executor;
|
||||
executor.OnPrizeResult += success => OnAnyPrizeResult?.Invoke(executor.Type, success);
|
||||
}
|
||||
|
||||
// ────────────── QR-коды (глобальная генерация) ──────────────
|
||||
|
||||
private void RefreshPhoneNumber()
|
||||
{
|
||||
var settings = SettingsManager.Instance;
|
||||
_phoneNumber = settings?.GetString("prizes.phoneNumber") ?? string.Empty;
|
||||
_phoneNumber = PhoneNumberUtils.ValidatePhoneNumber(_phoneNumber);
|
||||
_phoneNumber = PhoneNumberUtils.ApplyPhoneMask(_phoneNumber, _phoneNumberMask);
|
||||
}
|
||||
|
||||
public void GivePrize()
|
||||
{
|
||||
if (_enableHopper)
|
||||
PrizeSystem.GivePrize();
|
||||
else
|
||||
Debug.Log("Hopper is disabled. Prize dispensing skipped.");
|
||||
}
|
||||
|
||||
public Texture2D[] GetQRCodes()
|
||||
{
|
||||
return generatedQRCodes.ToArray();
|
||||
_phoneNumber = PhoneNumberUtils.ApplyPhoneMask(_phoneNumber, phoneNumberMask);
|
||||
}
|
||||
|
||||
private void GenerateQRCodes()
|
||||
{
|
||||
// Очищаем предыдущие QR-коды
|
||||
if (generatedQRCodes.Count > 0) generatedQRCodes.Clear();
|
||||
_generatedQRCodes.Clear();
|
||||
_qrCodesDirty = true;
|
||||
|
||||
foreach (QRCodeInfo data in qrCodes)
|
||||
{
|
||||
if (!data.generateQRCode) continue;
|
||||
|
||||
string fullLink = data.GetFullLink(_phoneNumber);
|
||||
Texture2D qrTexture = QRMaker.MakeTexture(fullLink,
|
||||
8,
|
||||
true,
|
||||
QRCodeInfo.GetColor(data.darkColor),
|
||||
QRCodeInfo.GetColor(data.lightColor));
|
||||
generatedQRCodes.Add(qrTexture);
|
||||
Debug.Log($"Generated QR Code for link: {fullLink}");
|
||||
Texture2D tex = QRMaker.MakeTexture(
|
||||
fullLink, 8, true,
|
||||
QRCodeInfo.GetColor(data.darkColor),
|
||||
QRCodeInfo.GetColor(data.lightColor));
|
||||
|
||||
_generatedQRCodes.Add(tex);
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────── Настройки ──────────────
|
||||
|
||||
private void SubscribeToSettings()
|
||||
{
|
||||
var settings = SettingsManager.Instance;
|
||||
if (settings != null) settings.OnSettingChanged += OnSettingChanged;
|
||||
}
|
||||
|
||||
private void UnsubscribeFromSettings()
|
||||
{
|
||||
var settings = SettingsManager.Instance;
|
||||
if (settings != null) settings.OnSettingChanged -= OnSettingChanged;
|
||||
}
|
||||
|
||||
private void OnSettingChanged(string key, SettingValue value)
|
||||
{
|
||||
if (key == "prizes.phoneNumber")
|
||||
{
|
||||
RefreshPhoneNumber();
|
||||
GenerateQRCodes();
|
||||
Debug.Log("[PrizeSystemManager] QR-коды перегенерированы.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/Scripts/Prizes.meta
Normal file
8
Assets/Scripts/Prizes.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e49860832abbd94a91eb45be12c3661
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
72
Assets/Scripts/Prizes/HopperPrize.cs
Normal file
72
Assets/Scripts/Prizes/HopperPrize.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using RR.Common.Controls;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Исполнитель приза «Хоппер».
|
||||
/// Выдаёт капсулы последовательно. Количество и задержка берутся из <see cref="LevelPrizeConfig"/>.
|
||||
/// Делегирует низкоуровневую работу в <see cref="PrizeSystem"/>.
|
||||
/// </summary>
|
||||
public class HopperPrize : PrizeBase
|
||||
{
|
||||
public override PrizeType Type => PrizeType.Hopper;
|
||||
public override bool IsGiving => _isGiving;
|
||||
|
||||
private bool _isGiving;
|
||||
private Coroutine _coroutine;
|
||||
|
||||
public override void Execute(MonoBehaviour host, LevelPrizeConfig config)
|
||||
{
|
||||
if (_isGiving || PrizeSystem.IsPrizing)
|
||||
{
|
||||
GameManager.Logger?.Log("HopperPrize", "Уже идёт выдача приза.");
|
||||
return;
|
||||
}
|
||||
|
||||
int count = Mathf.Max(1, config.hopperCapsuleCount);
|
||||
float delay = Mathf.Max(0f, config.hopperDelayBetweenCapsules);
|
||||
|
||||
if (_coroutine != null) host.StopCoroutine(_coroutine);
|
||||
_coroutine = host.StartCoroutine(DispenseSequence(count, delay));
|
||||
}
|
||||
|
||||
private IEnumerator DispenseSequence(int count, float delay)
|
||||
{
|
||||
_isGiving = true;
|
||||
int dispensed = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
while (PrizeSystem.IsPrizing) yield return null;
|
||||
|
||||
bool? result = null;
|
||||
void OnResult(bool success) => result = success;
|
||||
|
||||
PrizeSystem.OnPrizeResult += OnResult;
|
||||
PrizeSystem.GivePrize();
|
||||
|
||||
while (result == null) yield return null;
|
||||
PrizeSystem.OnPrizeResult -= OnResult;
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
dispensed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[HopperPrize] Ошибка капсулы {i + 1}/{count}. Прерываю.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < count - 1 && delay > 0)
|
||||
yield return new WaitForSecondsRealtime(delay);
|
||||
}
|
||||
|
||||
_isGiving = false;
|
||||
InvokeResult(dispensed == count);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Prizes/HopperPrize.cs.meta
Normal file
3
Assets/Scripts/Prizes/HopperPrize.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a811c3d0dd1c47879760a9ae2f4759cc
|
||||
timeCreated: 1773216348
|
||||
44
Assets/Scripts/Prizes/LevelPrizeConfig.cs
Normal file
44
Assets/Scripts/Prizes/LevelPrizeConfig.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Конфигурация приза для одного уровня.
|
||||
/// Сериализуется в JSON через <see cref="PrizeDatabase"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class LevelPrizeConfig
|
||||
{
|
||||
public PrizeType type = PrizeType.None;
|
||||
|
||||
// ── Хоппер ──
|
||||
public int hopperCapsuleCount = 1;
|
||||
public float hopperDelayBetweenCapsules = 1f;
|
||||
|
||||
// ── Витрина ──
|
||||
public int prizeStandBoxCount = 6;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запись «уровень → приз» для JSON-сериализации.
|
||||
/// JsonUtility не поддерживает Dictionary, поэтому используем список.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class PrizeLevelEntry
|
||||
{
|
||||
public string level;
|
||||
public LevelPrizeConfig config = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Корневая структура файла prizes.json.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class PrizesFileData
|
||||
{
|
||||
public List<PrizeLevelEntry> levels = new();
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Prizes/LevelPrizeConfig.cs.meta
Normal file
3
Assets/Scripts/Prizes/LevelPrizeConfig.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d37003045dd2428a9e788ca5d27e5ea5
|
||||
timeCreated: 1773226187
|
||||
20
Assets/Scripts/Prizes/NonePrize.cs
Normal file
20
Assets/Scripts/Prizes/NonePrize.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Пустой приз — не выполняет никаких действий.
|
||||
/// Используется как значение по умолчанию, если для уровня приз не назначен.
|
||||
/// </summary>
|
||||
public class NonePrize : PrizeBase
|
||||
{
|
||||
public override PrizeType Type => PrizeType.None;
|
||||
|
||||
public override void Execute(MonoBehaviour host, LevelPrizeConfig config)
|
||||
{
|
||||
// Ничего не делаем
|
||||
InvokeResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Prizes/NonePrize.cs.meta
Normal file
3
Assets/Scripts/Prizes/NonePrize.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 647f58a6e1ab49e18855aeae812fa0df
|
||||
timeCreated: 1773226228
|
||||
38
Assets/Scripts/Prizes/PrizeBase.cs
Normal file
38
Assets/Scripts/Prizes/PrizeBase.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Абстрактный исполнитель приза.
|
||||
/// Каждый тип приза реализует <see cref="Execute"/> для своей логики выдачи.
|
||||
/// Исполнители создаются PrizeSystemManager, не хранят конфигурацию —
|
||||
/// получают её через <see cref="LevelPrizeConfig"/> при вызове.
|
||||
/// </summary>
|
||||
public abstract class PrizeBase
|
||||
{
|
||||
/// <summary>Тип приза, который обрабатывает этот исполнитель.</summary>
|
||||
public abstract PrizeType Type { get; }
|
||||
|
||||
/// <summary>Идёт ли процесс выдачи.</summary>
|
||||
public virtual bool IsGiving => false;
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается после завершения выдачи. true — успех, false — ошибка.
|
||||
/// </summary>
|
||||
public event Action<bool> OnPrizeResult;
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет выдачу приза.
|
||||
/// </summary>
|
||||
/// <param name="host">MonoBehaviour для запуска корутин.</param>
|
||||
/// <param name="config">Конфигурация приза для текущего уровня.</param>
|
||||
public abstract void Execute(MonoBehaviour host, LevelPrizeConfig config);
|
||||
|
||||
/// <summary>Уведомляет подписчиков о результате.</summary>
|
||||
protected void InvokeResult(bool success)
|
||||
{
|
||||
OnPrizeResult?.Invoke(success);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Prizes/PrizeBase.cs.meta
Normal file
2
Assets/Scripts/Prizes/PrizeBase.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 794461cf42ac3294c8d7f8cab43ec2fb
|
||||
126
Assets/Scripts/Prizes/PrizeDatabase.cs
Normal file
126
Assets/Scripts/Prizes/PrizeDatabase.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Хранилище призов за уровни. Загружает и сохраняет конфигурацию в JSON-файл.
|
||||
/// Если файла нет — создаёт новый с пустым списком (нет призов ни за один уровень).
|
||||
/// </summary>
|
||||
public class PrizeDatabase
|
||||
{
|
||||
private const string FileName = "prizes.json";
|
||||
|
||||
private readonly Dictionary<string, LevelPrizeConfig> _configs = new();
|
||||
private string _filePath;
|
||||
|
||||
/// <summary>Путь к JSON-файлу.</summary>
|
||||
public string FilePath => _filePath;
|
||||
|
||||
// ────────────── Загрузка / сохранение ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Загружает конфигурацию из JSON-файла рядом с исполняемым файлом.
|
||||
/// Если файл отсутствует — создаёт пустой.
|
||||
/// </summary>
|
||||
public void Load()
|
||||
{
|
||||
string root = Directory.GetParent(Application.dataPath)?.ToString()
|
||||
?? Application.dataPath;
|
||||
_filePath = Path.Combine(root, FileName);
|
||||
|
||||
_configs.Clear();
|
||||
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
string json = File.ReadAllText(_filePath);
|
||||
var data = JsonUtility.FromJson<PrizesFileData>(json);
|
||||
|
||||
if (data?.levels != null)
|
||||
{
|
||||
foreach (PrizeLevelEntry entry in data.levels)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(entry.level))
|
||||
_configs[entry.level] = entry.config ?? new LevelPrizeConfig();
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[PrizeDatabase] Загружено {_configs.Count} записей из {_filePath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Save();
|
||||
Debug.Log($"[PrizeDatabase] Файл не найден — создан пустой: {_filePath}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохраняет текущую конфигурацию в JSON-файл.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_filePath)) return;
|
||||
|
||||
var data = new PrizesFileData();
|
||||
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);
|
||||
|
||||
Debug.Log($"[PrizeDatabase] Сохранено {_configs.Count} записей в {_filePath}");
|
||||
}
|
||||
|
||||
// ────────────── Чтение ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает конфигурацию приза для указанного уровня.
|
||||
/// Если для уровня нет записи — возвращает <see cref="PrizeType.None"/>.
|
||||
/// </summary>
|
||||
public LevelPrizeConfig GetConfig(string level)
|
||||
{
|
||||
return _configs.TryGetValue(level, out var config) ? config : new LevelPrizeConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, есть ли запись для указанного уровня.
|
||||
/// </summary>
|
||||
public bool HasConfig(string level) => _configs.ContainsKey(level);
|
||||
|
||||
/// <summary>
|
||||
/// Все записи (только для чтения).
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, LevelPrizeConfig> GetAll() => _configs;
|
||||
|
||||
// ────────────── Запись ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает конфигурацию приза для уровня и сохраняет файл.
|
||||
/// </summary>
|
||||
public void SetConfig(string level, LevelPrizeConfig config)
|
||||
{
|
||||
_configs[level] = config;
|
||||
Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет запись для уровня (приз = None) и сохраняет файл.
|
||||
/// </summary>
|
||||
public void RemoveConfig(string level)
|
||||
{
|
||||
if (_configs.Remove(level))
|
||||
Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает все записи и сохраняет пустой файл.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_configs.Clear();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Prizes/PrizeDatabase.cs.meta
Normal file
3
Assets/Scripts/Prizes/PrizeDatabase.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3b65e25d8b740d59d9c30caa8e22107
|
||||
timeCreated: 1773226207
|
||||
107
Assets/Scripts/Prizes/PrizeStandPrize.cs
Normal file
107
Assets/Scripts/Prizes/PrizeStandPrize.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.UI;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Исполнитель приза «Витрина».
|
||||
/// Показывает модальное окно с выбором ячейки, затем открывает её
|
||||
/// через <see cref="PrizeStandController"/>. Количество ячеек берётся из <see cref="LevelPrizeConfig"/>.
|
||||
/// </summary>
|
||||
public class PrizeStandPrize : PrizeBase
|
||||
{
|
||||
public override PrizeType Type => PrizeType.PrizeStand;
|
||||
public override bool IsGiving => _isGiving;
|
||||
|
||||
private readonly Func<PrizeStandModal> _resolveModal;
|
||||
private bool _isGiving;
|
||||
private PrizeStandModal _activeModal;
|
||||
|
||||
/// <param name="resolveModal">Делегат для поиска модального окна в текущих сценах.</param>
|
||||
public PrizeStandPrize(Func<PrizeStandModal> resolveModal)
|
||||
{
|
||||
_resolveModal = resolveModal;
|
||||
}
|
||||
|
||||
public override void Execute(MonoBehaviour host, LevelPrizeConfig config)
|
||||
{
|
||||
if (_isGiving)
|
||||
{
|
||||
Debug.LogWarning("[PrizeStandPrize] Уже идёт выдача.");
|
||||
return;
|
||||
}
|
||||
|
||||
_isGiving = true;
|
||||
|
||||
// Отписываемся от предыдущего модального окна, если было
|
||||
if (_activeModal != null)
|
||||
_activeModal.OnBoxSelected -= OnBoxSelected;
|
||||
|
||||
_activeModal = _resolveModal?.Invoke();
|
||||
|
||||
if (_activeModal != null)
|
||||
{
|
||||
_activeModal.OnBoxSelected += OnBoxSelected;
|
||||
_activeModal.Show(config.prizeStandBoxCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[PrizeStandPrize] PrizeStandModal не найден на сцене.");
|
||||
_isGiving = false;
|
||||
InvokeResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnBoxSelected(int boxNumber)
|
||||
{
|
||||
// Отписываемся сразу после выбора
|
||||
if (_activeModal != null)
|
||||
{
|
||||
_activeModal.OnBoxSelected -= OnBoxSelected;
|
||||
_activeModal = null;
|
||||
}
|
||||
|
||||
if (boxNumber <= 0)
|
||||
{
|
||||
_isGiving = false;
|
||||
InvokeResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
PrizeStandController stand = ComSystemManager.GetPrizeStandController();
|
||||
if (stand == null)
|
||||
{
|
||||
Debug.LogError("[PrizeStandPrize] Витрина не подключена.");
|
||||
_isGiving = false;
|
||||
InvokeResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
void OnResult(bool s, string m)
|
||||
{
|
||||
stand.OpeningFinished -= OnResult;
|
||||
success = s;
|
||||
if (!s) Debug.LogWarning($"[PrizeStandPrize] Ошибка: {m}");
|
||||
}
|
||||
|
||||
stand.OpeningFinished += OnResult;
|
||||
|
||||
try
|
||||
{
|
||||
await stand.OpenBoxAsync(boxNumber);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[PrizeStandPrize] Исключение: {ex.Message}");
|
||||
stand.OpeningFinished -= OnResult;
|
||||
}
|
||||
|
||||
_isGiving = false;
|
||||
InvokeResult(success);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Prizes/PrizeStandPrize.cs.meta
Normal file
2
Assets/Scripts/Prizes/PrizeStandPrize.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd50029ec97605a46865a4723087e8fd
|
||||
18
Assets/Scripts/Prizes/PrizeType.cs
Normal file
18
Assets/Scripts/Prizes/PrizeType.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Тип приза. Определяет, какой приз выдаётся за уровень.
|
||||
/// </summary>
|
||||
public enum PrizeType
|
||||
{
|
||||
/// <summary>Нет приза.</summary>
|
||||
None = 0,
|
||||
/// <summary>QR-код — модальное окно с QR-кодом.</summary>
|
||||
QRCode = 1,
|
||||
/// <summary>Хоппер — физическая выдача капсул.</summary>
|
||||
Hopper = 2,
|
||||
/// <summary>Витрина — модальное окно с выбором ячейки.</summary>
|
||||
PrizeStand = 3
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Prizes/PrizeType.cs.meta
Normal file
3
Assets/Scripts/Prizes/PrizeType.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00f8ed3308f84f9d9505acb84b27ca12
|
||||
timeCreated: 1773226177
|
||||
72
Assets/Scripts/Prizes/QRCodePrize.cs
Normal file
72
Assets/Scripts/Prizes/QRCodePrize.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using RR.Common.UI;
|
||||
|
||||
namespace RR.Common.Prizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Исполнитель приза «QR-код».
|
||||
/// Показывает модальное окно с QR-кодами. Сами QR-коды получает через делегат —
|
||||
/// не знает откуда они берутся.
|
||||
/// </summary>
|
||||
public class QRCodePrize : PrizeBase
|
||||
{
|
||||
public override PrizeType Type => PrizeType.QRCode;
|
||||
public override bool IsGiving => _isGiving;
|
||||
|
||||
private readonly Func<QRCodeModal> _resolveModal;
|
||||
private readonly Func<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)
|
||||
{
|
||||
_resolveModal = resolveModal;
|
||||
_getQRCodes = getQRCodes;
|
||||
}
|
||||
|
||||
public override void Execute(MonoBehaviour host, LevelPrizeConfig config)
|
||||
{
|
||||
Texture2D[] codes = _getQRCodes?.Invoke();
|
||||
|
||||
if (codes == null || codes.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[QRCodePrize] Нет QR-кодов для отображения.");
|
||||
InvokeResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QRCodeModal modal = _resolveModal?.Invoke();
|
||||
if (modal == null)
|
||||
{
|
||||
Debug.LogWarning("[QRCodePrize] QRCodeModal не найден на сцене.");
|
||||
InvokeResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_isGiving = true;
|
||||
|
||||
// Отписываемся от предыдущего, если было
|
||||
if (_activeModal != null)
|
||||
_activeModal.OnClosed -= OnModalClosed;
|
||||
|
||||
_activeModal = modal;
|
||||
_activeModal.OnClosed += OnModalClosed;
|
||||
_activeModal.ShowQRCodes(codes);
|
||||
}
|
||||
|
||||
private void OnModalClosed()
|
||||
{
|
||||
if (_activeModal != null)
|
||||
{
|
||||
_activeModal.OnClosed -= OnModalClosed;
|
||||
_activeModal = null;
|
||||
}
|
||||
|
||||
_isGiving = false;
|
||||
InvokeResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Prizes/QRCodePrize.cs.meta
Normal file
3
Assets/Scripts/Prizes/QRCodePrize.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c9c07112ada4098b8c5b2a8dd6d714f
|
||||
timeCreated: 1773216325
|
||||
@ -32,6 +32,12 @@ namespace RR.Common.Settings
|
||||
ValueSelector // Выбор значения (со стрелками или дропдауном)
|
||||
}
|
||||
|
||||
public enum FloatDisplayFormat
|
||||
{
|
||||
Slider, // Обычный слайдер
|
||||
ValueSelector // Выбор значения (со стрелками)
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct SettingValue
|
||||
{
|
||||
@ -129,6 +135,12 @@ namespace RR.Common.Settings
|
||||
|
||||
[Tooltip("Отображать float значение как проценты (0-100%)")]
|
||||
public bool displayAsPercentage = false;
|
||||
|
||||
[Tooltip("Формат отображения для Float (слайдер или выбор значения)")]
|
||||
public FloatDisplayFormat floatDisplayFormat = FloatDisplayFormat.Slider;
|
||||
|
||||
[Tooltip("Шаг для выбора значения Float (используется только для ValueSelector)")]
|
||||
public float floatStep = 0.1f;
|
||||
|
||||
public ApplyTarget applyTarget = ApplyTarget.None;
|
||||
|
||||
|
||||
@ -31,6 +31,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 1
|
||||
floatDisplayFormat: 1
|
||||
floatStep: 0.01
|
||||
applyTarget: 1
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -57,6 +59,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 1
|
||||
floatDisplayFormat: 1
|
||||
floatStep: 0.01
|
||||
applyTarget: 1
|
||||
audioTargetType: 1
|
||||
buttonText:
|
||||
@ -83,6 +87,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -110,6 +116,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -136,6 +144,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -156,13 +166,15 @@ MonoBehaviour:
|
||||
defaultInt: 10
|
||||
defaultFloat: 0
|
||||
defaultString:
|
||||
minInt: 10
|
||||
minInt: 1
|
||||
maxInt: 1000
|
||||
intDisplayFormat: 1
|
||||
intStep: 5
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -183,13 +195,15 @@ MonoBehaviour:
|
||||
defaultInt: 10
|
||||
defaultFloat: 0
|
||||
defaultString:
|
||||
minInt: 10
|
||||
minInt: 1
|
||||
maxInt: 1000
|
||||
intDisplayFormat: 1
|
||||
intStep: 5
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -210,13 +224,15 @@ MonoBehaviour:
|
||||
defaultInt: 10
|
||||
defaultFloat: 0
|
||||
defaultString:
|
||||
minInt: 5
|
||||
minInt: 1
|
||||
maxInt: 1000
|
||||
intDisplayFormat: 1
|
||||
intStep: 5
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -243,6 +259,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -270,6 +288,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -297,6 +317,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
|
||||
@ -324,6 +346,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
|
||||
@ -351,6 +375,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
|
||||
@ -377,6 +403,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
|
||||
@ -403,6 +431,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
|
||||
@ -424,11 +454,13 @@ MonoBehaviour:
|
||||
defaultString:
|
||||
minInt: 1
|
||||
maxInt: 20
|
||||
intDisplayFormat: 0
|
||||
intDisplayFormat: 1
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -451,11 +483,13 @@ MonoBehaviour:
|
||||
defaultString:
|
||||
minInt: 0
|
||||
maxInt: 20
|
||||
intDisplayFormat: 0
|
||||
intDisplayFormat: 1
|
||||
intStep: 1
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -479,10 +513,12 @@ MonoBehaviour:
|
||||
minInt: 0
|
||||
maxInt: 3000
|
||||
intDisplayFormat: 1
|
||||
intStep: 10
|
||||
intStep: 1
|
||||
minFloat: 10
|
||||
maxFloat: 3000
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText:
|
||||
@ -509,6 +545,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
|
||||
@ -536,6 +574,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
|
||||
@ -563,6 +603,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
|
||||
@ -590,6 +632,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
|
||||
@ -617,6 +661,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0432\u0430\u043B\u044E\u0442\u0443"
|
||||
@ -643,6 +689,8 @@ MonoBehaviour:
|
||||
minFloat: 0
|
||||
maxFloat: 1
|
||||
displayAsPercentage: 0
|
||||
floatDisplayFormat: 0
|
||||
floatStep: 0.1
|
||||
applyTarget: 0
|
||||
audioTargetType: 0
|
||||
buttonText: "\u0422\u0435\u0441\u0442 \u0432\u044B\u0434\u0430\u0447\u0438 \u043F\u0440\u0438\u0437\u0430"
|
||||
@ -651,3 +699,31 @@ MonoBehaviour:
|
||||
buttonSprite: {fileID: 0}
|
||||
options: []
|
||||
defaultOptionIndex: 0
|
||||
- key: btn.levels_prizes
|
||||
displayName: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043F\u0440\u0438\u0437\u043E\u0432"
|
||||
description:
|
||||
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 \u043F\u0440\u0438\u0437\u043E\u0432"
|
||||
buttonData:
|
||||
buttonColor: {r: 0.16300285, g: 0.64997125, b: 0.9339623, a: 1}
|
||||
buttonSprite: {fileID: 0}
|
||||
options: []
|
||||
defaultOptionIndex: 0
|
||||
|
||||
@ -114,8 +114,16 @@ namespace RR.Common.UI
|
||||
break;
|
||||
|
||||
case SettingValueType.Float:
|
||||
UseSliderFloat(entry.minFloat, entry.maxFloat, v =>
|
||||
SetSafe(entry.key, SettingValue.Float(v)));
|
||||
// Для ValueSelector не создаём слайдер - его обрабатывает ValueSelectorRow
|
||||
if (entry.floatDisplayFormat == FloatDisplayFormat.ValueSelector)
|
||||
{
|
||||
// ValueSelectorRow сам управляет значением
|
||||
}
|
||||
else
|
||||
{
|
||||
UseSliderFloat(entry.minFloat, entry.maxFloat, v =>
|
||||
SetSafe(entry.key, SettingValue.Float(v)));
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingValueType.Int:
|
||||
|
||||
@ -263,6 +263,22 @@ namespace RR.Common.UI
|
||||
Debug.LogWarning($"[SettingsUIGenerator] ValueSelectorRow component not found on prefab for '{entry.key}'");
|
||||
}
|
||||
}
|
||||
|
||||
// Если это Float ValueSelector, настраиваем компонент ValueSelectorRow
|
||||
if (entry.valueType == SettingValueType.Float &&
|
||||
entry.floatDisplayFormat == FloatDisplayFormat.ValueSelector)
|
||||
{
|
||||
ValueSelectorRow valueSelector = item.GetComponent<ValueSelectorRow>();
|
||||
if (valueSelector != null)
|
||||
{
|
||||
float currentValue = SettingsManager.Instance.GetFloat(entry.key);
|
||||
valueSelector.Setup(entry, currentValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[SettingsUIGenerator] ValueSelectorRow component not found on prefab for '{entry.key}'");
|
||||
}
|
||||
}
|
||||
|
||||
spawnedItems.Add(item);
|
||||
}
|
||||
@ -272,7 +288,9 @@ namespace RR.Common.UI
|
||||
return e.valueType switch
|
||||
{
|
||||
SettingValueType.Bool => toggleRowPrefab,
|
||||
SettingValueType.Float => sliderRowPrefab,
|
||||
SettingValueType.Float => e.floatDisplayFormat == FloatDisplayFormat.ValueSelector
|
||||
? valueSelectorRowPrefab
|
||||
: sliderRowPrefab,
|
||||
SettingValueType.Int => e.intDisplayFormat == IntDisplayFormat.ValueSelector
|
||||
? valueSelectorRowPrefab
|
||||
: sliderRowPrefab ? sliderRowPrefab : dropdownRowPrefab,
|
||||
@ -296,8 +314,16 @@ namespace RR.Common.UI
|
||||
ValueSelectorRow valueSelector = spawnedItems[i].GetComponent<ValueSelectorRow>();
|
||||
if (valueSelector != null && !string.IsNullOrEmpty(valueSelector.Key))
|
||||
{
|
||||
int value = SettingsManager.Instance.GetInt(valueSelector.Key);
|
||||
valueSelector.SetValue(value);
|
||||
if (valueSelector.IsFloat)
|
||||
{
|
||||
float fValue = SettingsManager.Instance.GetFloat(valueSelector.Key);
|
||||
valueSelector.SetValue(fValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
int iValue = SettingsManager.Instance.GetInt(valueSelector.Key);
|
||||
valueSelector.SetValue(iValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,9 @@ namespace RR.Common.Settings.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Компонент-Selectable для элемента настроек с выбором значения стрелками влево/вправо.
|
||||
/// Поддерживает как int, так и float значения.
|
||||
/// При зажатии кнопки скорость изменения плавно увеличивается.
|
||||
/// Автоповтор целиком управляется Event System (inputActionsPerSecond).
|
||||
/// Работает на одном префабе вместе с SettingUIItem.
|
||||
/// </summary>
|
||||
public class ValueSelectorRow : Selectable, IMoveHandler
|
||||
@ -15,8 +18,31 @@ namespace RR.Common.Settings.UI
|
||||
[SerializeField] private TMP_Text valueText;
|
||||
|
||||
private SettingEntry _entry;
|
||||
private int _currentValue;
|
||||
private int _step = 1;
|
||||
private bool _isFloat;
|
||||
private int _currentInt;
|
||||
private float _currentFloat;
|
||||
private int _intStep = 1;
|
||||
private float _floatStep = 0.1f;
|
||||
|
||||
// ───────────────────── Ускорение при зажатии ─────────────────────
|
||||
|
||||
/// <summary>Максимальный множитель шага при длительном зажатии.</summary>
|
||||
private const int MaxStepMultiplier = 10;
|
||||
|
||||
/// <summary>Сколько вызовов OnMove до начала роста множителя.</summary>
|
||||
private const int MultiplierStartCount = 6;
|
||||
|
||||
/// <summary>Сколько вызовов OnMove до достижения максимального множителя.</summary>
|
||||
private const int MultiplierMaxCount = 30;
|
||||
|
||||
/// <summary>Если между двумя OnMove прошло больше этого времени — сброс множителя.</summary>
|
||||
private const float HoldResetTimeout = 0.25f;
|
||||
|
||||
private int _holdDirection; // -1 = left, +1 = right, 0 = idle
|
||||
private int _holdCount; // Счётчик последовательных OnMove в одном направлении
|
||||
private float _lastMoveTime; // Time.unscaledTime последнего OnMove
|
||||
|
||||
// ───────────────────── Int Setup ─────────────────────
|
||||
|
||||
public void Setup(SettingEntry entry, int currentValue)
|
||||
{
|
||||
@ -27,27 +53,55 @@ namespace RR.Common.Settings.UI
|
||||
}
|
||||
|
||||
_entry = entry;
|
||||
|
||||
// Валидация step
|
||||
_isFloat = false;
|
||||
|
||||
if (entry.intStep <= 0)
|
||||
{
|
||||
Debug.LogWarning($"[ValueSelectorRow] Invalid step {entry.intStep} for key {entry.key}, using 1");
|
||||
_step = 1;
|
||||
_intStep = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_step = entry.intStep;
|
||||
_intStep = entry.intStep;
|
||||
}
|
||||
|
||||
_currentValue = Mathf.Clamp(currentValue, entry.minInt, entry.maxInt);
|
||||
_currentInt = Mathf.Clamp(currentValue, entry.minInt, entry.maxInt);
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
// ───────────────────── Float Setup ─────────────────────
|
||||
|
||||
public void Setup(SettingEntry entry, float currentValue)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
Debug.LogError("[ValueSelectorRow] Cannot setup with null entry!");
|
||||
return;
|
||||
}
|
||||
|
||||
_entry = entry;
|
||||
_isFloat = true;
|
||||
|
||||
if (entry.floatStep <= 0f)
|
||||
{
|
||||
Debug.LogWarning($"[ValueSelectorRow] Invalid step {entry.floatStep} for key {entry.key}, using 0.1");
|
||||
_floatStep = 0.1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
_floatStep = entry.floatStep;
|
||||
}
|
||||
|
||||
_currentFloat = Mathf.Clamp(currentValue, entry.minFloat, entry.maxFloat);
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
// ───────────────────── Navigation ─────────────────────
|
||||
|
||||
public new void OnMove(AxisEventData eventData)
|
||||
{
|
||||
if (_entry == null || !IsInteractable())
|
||||
{
|
||||
// Пробрасываем событие к базовому классу для стандартной навигации
|
||||
base.OnMove(eventData);
|
||||
return;
|
||||
}
|
||||
@ -55,71 +109,166 @@ namespace RR.Common.Settings.UI
|
||||
switch (eventData.moveDir)
|
||||
{
|
||||
case MoveDirection.Left:
|
||||
DecreaseValue();
|
||||
ApplyMove(-1);
|
||||
eventData.Use();
|
||||
break;
|
||||
|
||||
case MoveDirection.Right:
|
||||
IncreaseValue();
|
||||
ApplyMove(1);
|
||||
eventData.Use();
|
||||
break;
|
||||
|
||||
case MoveDirection.Up:
|
||||
case MoveDirection.Down:
|
||||
// Пробрасываем вертикальную навигацию к базовому классу
|
||||
ResetHold();
|
||||
base.OnMove(eventData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DecreaseValue()
|
||||
// ───────────────────── Move Logic ─────────────────────
|
||||
|
||||
private void ApplyMove(int direction)
|
||||
{
|
||||
int newValue = Mathf.Max(_entry.minInt, _currentValue - _step);
|
||||
if (newValue != _currentValue)
|
||||
float now = Time.unscaledTime;
|
||||
|
||||
// Если направление сменилось или прошло слишком много времени — сбрасываем
|
||||
if (_holdDirection != direction || (now - _lastMoveTime) > HoldResetTimeout)
|
||||
{
|
||||
SetValue(newValue);
|
||||
_holdDirection = direction;
|
||||
_holdCount = 0;
|
||||
}
|
||||
|
||||
_lastMoveTime = now;
|
||||
_holdCount++;
|
||||
|
||||
// Вычисляем множитель шага по количеству последовательных нажатий
|
||||
int multiplier = 1;
|
||||
if (_holdCount > MultiplierStartCount)
|
||||
{
|
||||
float progress = Mathf.Clamp01(
|
||||
(float)(_holdCount - MultiplierStartCount) / (MultiplierMaxCount - MultiplierStartCount));
|
||||
multiplier = Mathf.Max(1, Mathf.RoundToInt(Mathf.Lerp(1f, MaxStepMultiplier, progress)));
|
||||
}
|
||||
|
||||
if (direction > 0)
|
||||
IncreaseValue(multiplier);
|
||||
else
|
||||
DecreaseValue(multiplier);
|
||||
}
|
||||
|
||||
private void ResetHold()
|
||||
{
|
||||
_holdDirection = 0;
|
||||
_holdCount = 0;
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
ResetHold();
|
||||
}
|
||||
|
||||
public override void OnDeselect(BaseEventData eventData)
|
||||
{
|
||||
base.OnDeselect(eventData);
|
||||
ResetHold();
|
||||
}
|
||||
|
||||
// ───────────────────── Value Changes ─────────────────────
|
||||
|
||||
private void DecreaseValue(int multiplier)
|
||||
{
|
||||
if (_isFloat)
|
||||
{
|
||||
float newValue = Mathf.Max(_entry.minFloat, _currentFloat - _floatStep * multiplier);
|
||||
if (!Mathf.Approximately(newValue, _currentFloat))
|
||||
SetValue(newValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
int newValue = Mathf.Max(_entry.minInt, _currentInt - _intStep * multiplier);
|
||||
if (newValue != _currentInt)
|
||||
SetValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void IncreaseValue()
|
||||
private void IncreaseValue(int multiplier)
|
||||
{
|
||||
int newValue = Mathf.Min(_entry.maxInt, _currentValue + _step);
|
||||
if (newValue != _currentValue)
|
||||
if (_isFloat)
|
||||
{
|
||||
SetValue(newValue);
|
||||
float newValue = Mathf.Min(_entry.maxFloat, _currentFloat + _floatStep * multiplier);
|
||||
if (!Mathf.Approximately(newValue, _currentFloat))
|
||||
SetValue(newValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
int newValue = Mathf.Min(_entry.maxInt, _currentInt + _intStep * multiplier);
|
||||
if (newValue != _currentInt)
|
||||
SetValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────── UI ─────────────────────
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
if (valueText != null)
|
||||
valueText.text = _currentValue.ToString();
|
||||
}
|
||||
if (valueText == null) return;
|
||||
|
||||
public void SetValue(int value)
|
||||
{
|
||||
if (_entry == null)
|
||||
return;
|
||||
|
||||
int clampedValue = Mathf.Clamp(value, _entry.minInt, _entry.maxInt);
|
||||
if (clampedValue != _currentValue)
|
||||
if (_isFloat)
|
||||
{
|
||||
_currentValue = clampedValue;
|
||||
UpdateUI();
|
||||
|
||||
// Сохраняем изменения через SettingsManager
|
||||
var manager = SettingsManager.Instance;
|
||||
if (manager != null)
|
||||
{
|
||||
manager.SetInt(_entry.key, _currentValue);
|
||||
}
|
||||
if (_entry != null && _entry.displayAsPercentage)
|
||||
valueText.text = (_currentFloat * 100f).ToString("0") + "%";
|
||||
else
|
||||
{
|
||||
Debug.LogError("[ValueSelectorRow] SettingsManager.Instance is null!");
|
||||
}
|
||||
valueText.text = _currentFloat.ToString("0.##");
|
||||
}
|
||||
else
|
||||
{
|
||||
valueText.text = _currentInt.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────── Public API (int) ─────────────────────
|
||||
|
||||
public void SetValue(int value)
|
||||
{
|
||||
if (_entry == null) return;
|
||||
|
||||
int clamped = Mathf.Clamp(value, _entry.minInt, _entry.maxInt);
|
||||
if (clamped != _currentInt)
|
||||
{
|
||||
_currentInt = clamped;
|
||||
UpdateUI();
|
||||
|
||||
var manager = SettingsManager.Instance;
|
||||
if (manager != null)
|
||||
manager.SetInt(_entry.key, _currentInt);
|
||||
else
|
||||
Debug.LogError("[ValueSelectorRow] SettingsManager.Instance is null!");
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────── Public API (float) ─────────────────────
|
||||
|
||||
public void SetValue(float value)
|
||||
{
|
||||
if (_entry == null) return;
|
||||
|
||||
float clamped = Mathf.Clamp(value, _entry.minFloat, _entry.maxFloat);
|
||||
if (!Mathf.Approximately(clamped, _currentFloat))
|
||||
{
|
||||
_currentFloat = clamped;
|
||||
UpdateUI();
|
||||
|
||||
var manager = SettingsManager.Instance;
|
||||
if (manager != null)
|
||||
manager.SetFloat(_entry.key, _currentFloat);
|
||||
else
|
||||
Debug.LogError("[ValueSelectorRow] SettingsManager.Instance is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFloat => _isFloat;
|
||||
public string Key => _entry?.key;
|
||||
}
|
||||
}
|
||||
|
||||
282
Assets/Scripts/UI/LevelSettingsModal.cs
Normal file
282
Assets/Scripts/UI/LevelSettingsModal.cs
Normal file
@ -0,0 +1,282 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Модальное окно выбора уровня для настройки награды за его прохождение.
|
||||
/// Строит список уровней из <see cref="SceneManager.sceneCountInBuildSettings"/> и
|
||||
/// предоставляет выбор текущего уровня как контекст для дальнейшего редактирования призов.
|
||||
/// </summary>
|
||||
public class LevelSettingsModal : Modal
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private Transform contentRoot;
|
||||
[SerializeField] private GameObject levelButtonPrefab;
|
||||
|
||||
[Header("Display")]
|
||||
[SerializeField] private bool rebuildOnOpen = true;
|
||||
[SerializeField] private bool includeOnlyLevelsFolder = true;
|
||||
[SerializeField] private string levelsFolderName = "Levels";
|
||||
[SerializeField] private List<string> ignoredSceneNames = new() { "MainMenu", "Level UI" };
|
||||
|
||||
[Header("Selection Visual")]
|
||||
[SerializeField] private Vector3 normalScale = Vector3.one;
|
||||
[SerializeField] private Vector3 selectedScale = new(1.05f, 1.05f, 1f);
|
||||
|
||||
[Header("Модальное окно настройки приза")]
|
||||
[Tooltip("Ссылка на LevelPrizeSettingsModal. Открывается при нажатии на кнопку уровня.")]
|
||||
[SerializeField] private LevelPrizeSettingsModal levelPrizeSettingsModal;
|
||||
|
||||
private readonly List<LevelButtonView> _spawnedButtons = new();
|
||||
private LevelButtonView _selectedButton;
|
||||
|
||||
/// <summary>
|
||||
/// Имя сцены выбранного уровня.
|
||||
/// Используется внешними компонентами как ключ для загрузки или сохранения настроек приза.
|
||||
/// </summary>
|
||||
public string SelectedSceneName => _selectedButton != null ? _selectedButton.SceneName : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Открывает окно выбора уровня.
|
||||
/// Перед открытием при необходимости пересобирает список кнопок из актуального набора сцен в Build Settings.
|
||||
/// </summary>
|
||||
public override void Open()
|
||||
{
|
||||
if (rebuildOnOpen || _spawnedButtons.Count == 0)
|
||||
{
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
base.Open();
|
||||
}
|
||||
|
||||
[ContextMenu("Rebuild Level Buttons")]
|
||||
public void Rebuild()
|
||||
{
|
||||
if (!ValidateReferences())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearButtons();
|
||||
|
||||
List<string> sceneNames = GetLevelSceneNames();
|
||||
foreach (string t in sceneNames)
|
||||
{
|
||||
CreateButton(t);
|
||||
}
|
||||
|
||||
if (_spawnedButtons.Count <= 0) return;
|
||||
SelectButton(_spawnedButtons[0]);
|
||||
SetFirstSelected(_spawnedButtons[0].Button);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, что в инспекторе назначены контейнер для кнопок и префаб кнопки уровня.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see langword="true"/>, если окно готово к построению списка уровней;
|
||||
/// иначе <see langword="false"/>.
|
||||
/// </returns>
|
||||
private bool ValidateReferences()
|
||||
{
|
||||
if (contentRoot == null)
|
||||
{
|
||||
Debug.LogError($"[{nameof(LevelSettingsModal)}] Content Root is not assigned on {name}.", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (levelButtonPrefab == null)
|
||||
{
|
||||
Debug.LogError($"[{nameof(LevelSettingsModal)}] Level Button Prefab is not assigned on {name}.", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает кнопку для конкретной сцены уровня и настраивает ее отображение.
|
||||
/// Нажатие на кнопку только выбирает уровень для последующей настройки награды.
|
||||
/// </summary>
|
||||
/// <param name="sceneName">Имя сцены уровня, отображаемое в интерфейсе.</param>
|
||||
private void CreateButton(string sceneName)
|
||||
{
|
||||
GameObject buttonObject = Instantiate(levelButtonPrefab, contentRoot);
|
||||
buttonObject.name = $"LevelButton_{sceneName}";
|
||||
|
||||
Button button = buttonObject.GetComponentInChildren<Button>(true);
|
||||
if (button == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[{nameof(LevelSettingsModal)}] Prefab '{levelButtonPrefab.name}' must contain a Button component.",
|
||||
buttonObject);
|
||||
Destroy(buttonObject);
|
||||
return;
|
||||
}
|
||||
|
||||
TMP_Text tmpText = buttonObject.GetComponentInChildren<TMP_Text>(true);
|
||||
if (tmpText != null)
|
||||
{
|
||||
tmpText.text = sceneName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Text legacyText = buttonObject.GetComponentInChildren<Text>(true);
|
||||
if (legacyText != null)
|
||||
{
|
||||
legacyText.text = sceneName;
|
||||
}
|
||||
}
|
||||
|
||||
LevelButtonView buttonView = new(buttonObject.transform, sceneName, buttonObject.transform as RectTransform, button);
|
||||
|
||||
button.onClick.RemoveAllListeners();
|
||||
button.onClick.AddListener(() => OnLevelButtonClicked(buttonView));
|
||||
|
||||
ApplySelectedVisual(buttonView, false);
|
||||
_spawnedButtons.Add(buttonView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает нажатие на кнопку уровня.
|
||||
/// Не запускает уровень, а только делает его текущим выбранным элементом для настройки приза.
|
||||
/// </summary>
|
||||
/// <param name="buttonView">Представление нажатой кнопки уровня.</param>
|
||||
private void OnLevelButtonClicked(LevelButtonView buttonView)
|
||||
{
|
||||
SelectButton(buttonView);
|
||||
Debug.Log($"[{nameof(LevelSettingsModal)}] Selected level: {buttonView.SceneName}");
|
||||
|
||||
// Открываем окно настройки приза для выбранного уровня
|
||||
if (levelPrizeSettingsModal != null)
|
||||
levelPrizeSettingsModal.OpenForLevel(buttonView.SceneName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет текущий выбранный уровень и переключает визуальное выделение между кнопками.
|
||||
/// </summary>
|
||||
/// <param name="buttonView">Кнопка уровня, которая должна стать активной.</param>
|
||||
private void SelectButton(LevelButtonView buttonView)
|
||||
{
|
||||
if (_selectedButton != null)
|
||||
{
|
||||
ApplySelectedVisual(_selectedButton, false);
|
||||
}
|
||||
|
||||
_selectedButton = buttonView;
|
||||
|
||||
if (_selectedButton != null)
|
||||
{
|
||||
ApplySelectedVisual(_selectedButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Включает или отключает визуальное состояние выбранного уровня.
|
||||
/// Используется, чтобы в интерфейсе было видно, для какого уровня сейчас настраивается награда.
|
||||
/// </summary>
|
||||
/// <param name="buttonView">Кнопка, для которой обновляется внешний вид.</param>
|
||||
/// <param name="isSelected">
|
||||
/// <see langword="true"/>, если кнопка должна быть показана как выбранная;
|
||||
/// иначе <see langword="false"/>.
|
||||
/// </param>
|
||||
private void ApplySelectedVisual(LevelButtonView buttonView, bool isSelected)
|
||||
{
|
||||
if (buttonView.RectTransform != null)
|
||||
{
|
||||
buttonView.RectTransform.localScale = isSelected ? selectedScale : normalScale;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Собирает список имен сцен уровней, доступных для настройки наград.
|
||||
/// Источником служат сцены, добавленные в Build Settings, с учетом настроенной фильтрации.
|
||||
/// </summary>
|
||||
/// <returns>Список имен сцен уровней для построения кнопок в окне настроек.</returns>
|
||||
private List<string> GetLevelSceneNames()
|
||||
{
|
||||
var result = new List<string>();
|
||||
int sceneCount = SceneManager.sceneCountInBuildSettings;
|
||||
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
string scenePath = SceneUtility.GetScenePathByBuildIndex(i);
|
||||
if (string.IsNullOrWhiteSpace(scenePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (includeOnlyLevelsFolder)
|
||||
{
|
||||
string folderToken = $"/{levelsFolderName}/";
|
||||
if (!scenePath.Replace("\\", "/").Contains(folderToken))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
string sceneName = Path.GetFileNameWithoutExtension(scenePath);
|
||||
if (ignoredSceneNames.Contains(sceneName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(sceneName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет ранее созданные кнопки уровней и сбрасывает текущий выбор.
|
||||
/// Используется перед полной пересборкой списка.
|
||||
/// </summary>
|
||||
private void ClearButtons()
|
||||
{
|
||||
foreach (LevelButtonView buttonView in _spawnedButtons)
|
||||
{
|
||||
if (buttonView.Root != null)
|
||||
{
|
||||
Destroy(buttonView.Root.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
_spawnedButtons.Clear();
|
||||
_selectedButton = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Внутреннее представление кнопки уровня, используемое модальным окном
|
||||
/// для хранения ссылки на UI-элементы и имя сцены выбранного уровня.
|
||||
/// </summary>
|
||||
private sealed class LevelButtonView
|
||||
{
|
||||
/// <summary>
|
||||
/// Инициализирует представление кнопки уровня, используемое для выбора уровня
|
||||
/// при настройке награды за его прохождение.
|
||||
/// </summary>
|
||||
/// <param name="root">Корневой объект экземпляра префаба кнопки.</param>
|
||||
/// <param name="sceneName">Имя сцены уровня.</param>
|
||||
/// <param name="rectTransform">Прямоугольная трансформация корневого UI-элемента.</param>
|
||||
/// <param name="button">Компонент кнопки, принимающий пользовательский ввод.</param>
|
||||
public LevelButtonView(Transform root, string sceneName, RectTransform rectTransform, Button button)
|
||||
{
|
||||
Root = root;
|
||||
SceneName = sceneName;
|
||||
RectTransform = rectTransform;
|
||||
Button = button;
|
||||
}
|
||||
|
||||
public Transform Root { get; }
|
||||
public string SceneName { get; }
|
||||
public RectTransform RectTransform { get; }
|
||||
public Button Button { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/LevelSettingsModal.cs.meta
Normal file
2
Assets/Scripts/UI/LevelSettingsModal.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0dc22ce8347791143b4d1ecd617292d7
|
||||
@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
@ -13,6 +14,11 @@ namespace RR.Common.UI
|
||||
[SerializeField] private Button newGameButton;
|
||||
[SerializeField] private Button quitButton;
|
||||
|
||||
[Header("Автовыбор при загрузке")]
|
||||
[SerializeField] private Selectable defaultSelectable;
|
||||
[Tooltip("Задержка перед автовыбором элемента (сек)")]
|
||||
[SerializeField] private float selectDelay = 1f;
|
||||
|
||||
[Header("Тексты")]
|
||||
[SerializeField] private TextMeshProUGUI paymentText;
|
||||
[SerializeField] private TextMeshProUGUI supportNumberText;
|
||||
@ -48,6 +54,17 @@ namespace RR.Common.UI
|
||||
// Обновляем тексты сразу при старте
|
||||
UpdateTextsFromSettings();
|
||||
}
|
||||
|
||||
// Автовыбор элемента при загрузке сцены
|
||||
if (defaultSelectable != null)
|
||||
StartCoroutine(SelectAfterDelay());
|
||||
}
|
||||
|
||||
private IEnumerator SelectAfterDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(selectDelay);
|
||||
if (defaultSelectable != null)
|
||||
defaultSelectable.Select();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
|
||||
358
Assets/Scripts/UI/Modal/LevelPrizeSettingsModal.cs
Normal file
358
Assets/Scripts/UI/Modal/LevelPrizeSettingsModal.cs
Normal file
@ -0,0 +1,358 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Модальное окно настройки приза за конкретный уровень.
|
||||
/// Полностью управляется с клавиатуры/геймпада через Input System.
|
||||
/// Навигация: вверх/вниз — переключение строк (зацикленно), влево/вправо — изменение значения.
|
||||
/// Настройки сохраняются автоматически при закрытии окна.
|
||||
/// </summary>
|
||||
public class LevelPrizeSettingsModal : Modal
|
||||
{
|
||||
[Header("UI — Общее")]
|
||||
[SerializeField] private TMP_Text titleText;
|
||||
[SerializeField] private TMP_Text prizeTypeLabel;
|
||||
|
||||
[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 prizeStandBoxCountLabel;
|
||||
|
||||
[Header("Строки (RectTransform)")]
|
||||
[Tooltip("RectTransform строки выбора типа приза.")]
|
||||
[SerializeField] private RectTransform prizeTypeRow;
|
||||
|
||||
[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;
|
||||
|
||||
// ── Строки ──
|
||||
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 string _levelName;
|
||||
private LevelPrizeConfig _config;
|
||||
|
||||
private static readonly PrizeType[] PrizeTypes =
|
||||
(PrizeType[])Enum.GetValues(typeof(PrizeType));
|
||||
|
||||
private int _currentTypeIndex;
|
||||
private int _activeRow;
|
||||
private bool _axisHeldX;
|
||||
private bool _axisHeldY;
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается после сохранения настроек (при закрытии окна). Параметр — имя уровня.
|
||||
/// </summary>
|
||||
public event Action<string> OnApplied;
|
||||
|
||||
// ────────────── Lifecycle ──────────────
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (navigateAction != null && navigateAction.action != null)
|
||||
{
|
||||
navigateAction.action.Enable();
|
||||
navigateAction.action.performed += OnNavigatePerformed;
|
||||
navigateAction.action.canceled += OnNavigateCanceled;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
if (navigateAction != null && navigateAction.action != null)
|
||||
{
|
||||
navigateAction.action.performed -= OnNavigatePerformed;
|
||||
navigateAction.action.canceled -= OnNavigateCanceled;
|
||||
}
|
||||
|
||||
_axisHeldX = false;
|
||||
_axisHeldY = false;
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
// ────────────── Ввод ──────────────
|
||||
|
||||
private void OnNavigatePerformed(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (!_isOpen) return;
|
||||
|
||||
Vector2 v = ctx.ReadValue<Vector2>();
|
||||
|
||||
// ── Вертикаль: переключение строк ──
|
||||
if (Mathf.Abs(v.y) >= deadZone)
|
||||
{
|
||||
if (!_axisHeldY)
|
||||
{
|
||||
_axisHeldY = true;
|
||||
MoveRow(v.y > 0f ? -1 : 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_axisHeldY = false;
|
||||
}
|
||||
|
||||
// ── Горизонталь: изменение значения ──
|
||||
if (Mathf.Abs(v.x) >= deadZone)
|
||||
{
|
||||
if (!_axisHeldX)
|
||||
{
|
||||
_axisHeldX = true;
|
||||
HandleLeftRight(v.x > 0f ? 1 : -1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_axisHeldX = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigateCanceled(InputAction.CallbackContext ctx)
|
||||
{
|
||||
_axisHeldX = false;
|
||||
_axisHeldY = false;
|
||||
}
|
||||
|
||||
// ────────────── Навигация строк ──────────────
|
||||
|
||||
private void MoveRow(int delta)
|
||||
{
|
||||
int next = _activeRow;
|
||||
for (int i = 0; i < ROW_COUNT; i++)
|
||||
{
|
||||
next = (next + delta + ROW_COUNT) % ROW_COUNT;
|
||||
if (IsRowVisible(next))
|
||||
{
|
||||
_activeRow = next;
|
||||
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 видны всегда
|
||||
}
|
||||
|
||||
private void HandleLeftRight(int direction)
|
||||
{
|
||||
switch (_activeRow)
|
||||
{
|
||||
case ROW_TYPE:
|
||||
CyclePrizeType(direction);
|
||||
break;
|
||||
|
||||
case ROW_PARAM:
|
||||
if (_config.type == PrizeType.Hopper)
|
||||
AdjustCapsules(direction);
|
||||
else if (_config.type == PrizeType.PrizeStand)
|
||||
AdjustBoxes(direction);
|
||||
break;
|
||||
|
||||
// ROW_CLOSE — лево/право ничего не делает
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────── Публичный API ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Открывает окно настройки приза для указанного уровня.
|
||||
/// </summary>
|
||||
public void OpenForLevel(string levelName)
|
||||
{
|
||||
_levelName = levelName;
|
||||
|
||||
PrizeDatabase db = PrizeSystemManager.Instance?.Database;
|
||||
_config = db != null
|
||||
? CopyConfig(db.GetConfig(levelName))
|
||||
: new LevelPrizeConfig();
|
||||
|
||||
_currentTypeIndex = 0;
|
||||
for (int i = 0; i < PrizeTypes.Length; i++)
|
||||
{
|
||||
if (PrizeTypes[i] == _config.type)
|
||||
{
|
||||
_currentTypeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_activeRow = ROW_TYPE;
|
||||
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))
|
||||
_activeRow = ROW_TYPE;
|
||||
|
||||
RefreshUI();
|
||||
RefreshRowHighlights();
|
||||
}
|
||||
|
||||
private void AdjustCapsules(int delta)
|
||||
{
|
||||
_config.hopperCapsuleCount = Mathf.Clamp(_config.hopperCapsuleCount + delta, 1, maxCapsules);
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void AdjustBoxes(int delta)
|
||||
{
|
||||
_config.prizeStandBoxCount = Mathf.Clamp(_config.prizeStandBoxCount + delta, 1, maxBoxes);
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_levelName) || _config == null) return;
|
||||
|
||||
PrizeDatabase db = PrizeSystemManager.Instance?.Database;
|
||||
if (db == null)
|
||||
{
|
||||
Debug.LogError("[LevelPrizeSettingsModal] PrizeDatabase недоступна.");
|
||||
return;
|
||||
}
|
||||
|
||||
db.SetConfig(_levelName, _config);
|
||||
Debug.Log($"[LevelPrizeSettingsModal] Уровень «{_levelName}» → приз «{_config.type}» сохранён.");
|
||||
|
||||
OnApplied?.Invoke(_levelName);
|
||||
}
|
||||
|
||||
// ────────────── UI ──────────────
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
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 (hopperParamsGroup) hopperParamsGroup.SetActive(isHopper);
|
||||
if (prizeStandParamsGroup) prizeStandParamsGroup.SetActive(isPrizeStand);
|
||||
|
||||
if (hopperCapsuleCountLabel)
|
||||
hopperCapsuleCountLabel.text = _config.hopperCapsuleCount.ToString();
|
||||
|
||||
if (prizeStandBoxCountLabel)
|
||||
prizeStandBoxCountLabel.text = _config.prizeStandBoxCount.ToString();
|
||||
}
|
||||
|
||||
private void RefreshRowHighlights()
|
||||
{
|
||||
if (_activeRow == ROW_CLOSE)
|
||||
{
|
||||
// Прячем указатель, фокус переходит на кнопку закрытия
|
||||
if (rowPointer) rowPointer.Hide();
|
||||
|
||||
if (closeModalButton && EventSystem.current)
|
||||
EventSystem.current.SetSelectedGameObject(closeModalButton.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (rowPointer)
|
||||
{
|
||||
RectTransform target = GetActiveRowTransform();
|
||||
if (target) rowPointer.SetTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RectTransform GetActiveRowTransform()
|
||||
{
|
||||
switch (_activeRow)
|
||||
{
|
||||
case ROW_TYPE:
|
||||
return 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;
|
||||
|
||||
default:
|
||||
return prizeTypeRow;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPrizeTypeName(PrizeType type) => type switch
|
||||
{
|
||||
PrizeType.None => "Нет приза",
|
||||
PrizeType.QRCode => "QR-код",
|
||||
PrizeType.Hopper => "Хоппер",
|
||||
PrizeType.PrizeStand => "Витрина",
|
||||
_ => type.ToString()
|
||||
};
|
||||
|
||||
private static LevelPrizeConfig CopyConfig(LevelPrizeConfig src) => new()
|
||||
{
|
||||
type = src.type,
|
||||
hopperCapsuleCount = src.hopperCapsuleCount,
|
||||
hopperDelayBetweenCapsules = src.hopperDelayBetweenCapsules,
|
||||
prizeStandBoxCount = src.prizeStandBoxCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/UI/Modal/LevelPrizeSettingsModal.cs.meta
Normal file
3
Assets/Scripts/UI/Modal/LevelPrizeSettingsModal.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 503701b468074130a5d4e963e9867215
|
||||
timeCreated: 1773226679
|
||||
@ -112,7 +112,7 @@ namespace RR.Common.UI
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
// Регистрируемся в менеджере
|
||||
ModalManager.Instance.Push(this);
|
||||
ModalManager.Instance?.Push(this);
|
||||
|
||||
// Подписываемся на действие закрытия
|
||||
if (closeAction)
|
||||
@ -131,7 +131,7 @@ namespace RR.Common.UI
|
||||
}
|
||||
|
||||
// Удаляемся из менеджера
|
||||
ModalManager.Instance.Pop(this);
|
||||
ModalManager.Instance?.Pop(this);
|
||||
}
|
||||
|
||||
private void OnCloseActionPerformed(InputAction.CallbackContext ctx)
|
||||
|
||||
@ -13,6 +13,7 @@ namespace RR.Common.UI
|
||||
public class ModalManager : MonoBehaviour
|
||||
{
|
||||
private static ModalManager _instance;
|
||||
private static bool _applicationQuitting;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton-инстанс менеджера модальных окон.
|
||||
@ -22,6 +23,7 @@ namespace RR.Common.UI
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_applicationQuitting) return null;
|
||||
if (_instance) return _instance;
|
||||
|
||||
// Ищем существующий в сцене
|
||||
@ -93,6 +95,24 @@ namespace RR.Common.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
_applicationQuitting = true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Сбрасываем статический флаг при выходе из Play Mode в редакторе,
|
||||
/// иначе при повторном запуске Instance всегда будет null.
|
||||
/// </summary>
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetStatics()
|
||||
{
|
||||
_applicationQuitting = false;
|
||||
_instance = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Следим за тем, чтобы фокус оставался внутри текущего модального окна
|
||||
|
||||
165
Assets/Scripts/UI/Modal/PrizeStandModal.cs
Normal file
165
Assets/Scripts/UI/Modal/PrizeStandModal.cs
Normal file
@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Модальное окно витрины с выбором ячейки/полки для выдачи приза.
|
||||
/// Отображает сетку кнопок-ячеек. Навигация — через Input System (Navigate).
|
||||
/// </summary>
|
||||
public class PrizeStandModal : Modal
|
||||
{
|
||||
/// <summary>Текущий экземпляр на сцене. Регистрируется автоматически.</summary>
|
||||
public static PrizeStandModal Current { get; private set; }
|
||||
|
||||
[Header("Контент")]
|
||||
[Tooltip("Контейнер для кнопок ячеек (например, GridLayoutGroup).")]
|
||||
[SerializeField] private Transform boxButtonsContainer;
|
||||
|
||||
[Tooltip("Префаб кнопки ячейки. Должен содержать Button и TMP_Text.")]
|
||||
[SerializeField] private GameObject boxButtonPrefab;
|
||||
|
||||
[Header("UI")]
|
||||
[Tooltip("Текст-заголовок окна (опционально).")]
|
||||
[SerializeField] private TMP_Text titleText;
|
||||
|
||||
[Tooltip("Текст заголовка по умолчанию.")]
|
||||
[SerializeField] private string defaultTitle = "Выберите ячейку";
|
||||
|
||||
[Header("Навигация")]
|
||||
[Tooltip("InputAction для навигации (Navigate из Input System).")]
|
||||
[SerializeField] private InputActionReference navigateAction;
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при выборе ячейки. Параметр — номер ячейки (1-based).
|
||||
/// Если игрок закрыл окно без выбора, вызывается с 0.
|
||||
/// </summary>
|
||||
public event Action<int> OnBoxSelected;
|
||||
|
||||
private readonly List<GameObject> _createdButtons = new();
|
||||
private bool _selectionMade;
|
||||
|
||||
// ────────────── Lifecycle ──────────────
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
Current = this;
|
||||
base.Awake();
|
||||
|
||||
// При закрытии без выбора — уведомляем с 0
|
||||
OnClosed += HandleClosedWithoutSelection;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Current == this) Current = null;
|
||||
OnClosed -= HandleClosedWithoutSelection;
|
||||
}
|
||||
|
||||
// ────────────── Публичный API ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Показывает окно с указанным количеством ячеек.
|
||||
/// </summary>
|
||||
/// <param name="boxCount">Количество ячеек для отображения.</param>
|
||||
public void Show(int boxCount)
|
||||
{
|
||||
_selectionMade = false;
|
||||
|
||||
BuildButtons(boxCount);
|
||||
|
||||
if (titleText != null)
|
||||
titleText.text = defaultTitle;
|
||||
|
||||
Open();
|
||||
GameManager.Instance?.SetPause(true);
|
||||
}
|
||||
|
||||
// ────────────── Внутреннее ──────────────
|
||||
|
||||
private void BuildButtons(int count)
|
||||
{
|
||||
ClearButtons();
|
||||
|
||||
if (boxButtonsContainer == null || boxButtonPrefab == null)
|
||||
{
|
||||
Debug.LogError("[PrizeStandModal] Контейнер или префаб кнопки не назначены.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= count; i++)
|
||||
{
|
||||
GameObject btnObj = Instantiate(boxButtonPrefab, boxButtonsContainer);
|
||||
btnObj.name = $"BoxButton_{i}";
|
||||
|
||||
// Настраиваем текст
|
||||
TMP_Text label = btnObj.GetComponentInChildren<TMP_Text>();
|
||||
if (label != null)
|
||||
label.text = i.ToString();
|
||||
|
||||
// Настраиваем кнопку
|
||||
Button btn = btnObj.GetComponent<Button>();
|
||||
if (btn != null)
|
||||
{
|
||||
int boxNumber = i; // локальная копия для замыкания
|
||||
btn.onClick.AddListener(() => OnBoxButtonClicked(boxNumber));
|
||||
}
|
||||
|
||||
_createdButtons.Add(btnObj);
|
||||
}
|
||||
|
||||
// Устанавливаем первый элемент для фокуса
|
||||
if (_createdButtons.Count > 0)
|
||||
{
|
||||
Button firstBtn = _createdButtons[0].GetComponent<Button>();
|
||||
if (firstBtn != null)
|
||||
SetFirstSelected(firstBtn);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBoxButtonClicked(int boxNumber)
|
||||
{
|
||||
_selectionMade = true;
|
||||
|
||||
Debug.Log($"[PrizeStandModal] Выбрана ячейка #{boxNumber}.");
|
||||
|
||||
Close();
|
||||
GameManager.Instance?.SetPause(false);
|
||||
|
||||
OnBoxSelected?.Invoke(boxNumber);
|
||||
}
|
||||
|
||||
private void HandleClosedWithoutSelection()
|
||||
{
|
||||
if (!_selectionMade)
|
||||
{
|
||||
Debug.Log("[PrizeStandModal] Окно закрыто без выбора.");
|
||||
GameManager.Instance?.SetPause(false);
|
||||
OnBoxSelected?.Invoke(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearButtons()
|
||||
{
|
||||
foreach (GameObject obj in _createdButtons)
|
||||
{
|
||||
if (obj != null) Destroy(obj);
|
||||
}
|
||||
_createdButtons.Clear();
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
ClearButtons();
|
||||
GameManager.Instance?.SetPause(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
Assets/Scripts/UI/Modal/PrizeStandModal.cs.meta
Normal file
2
Assets/Scripts/UI/Modal/PrizeStandModal.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ec130ebb3048714cbde73bc9deb13cd
|
||||
104
Assets/Scripts/UI/Modal/QRCodeModal.cs
Normal file
104
Assets/Scripts/UI/Modal/QRCodeModal.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Модальное окно для отображения QR-кодов.
|
||||
/// Создаёт Image-объекты с QR-кодами динамически в указанном контейнере.
|
||||
/// </summary>
|
||||
public class QRCodeModal : Modal
|
||||
{
|
||||
/// <summary>Текущий экземпляр на сцене. Регистрируется автоматически.</summary>
|
||||
public static QRCodeModal Current { get; private set; }
|
||||
|
||||
[Header("Контент")]
|
||||
[Tooltip("Контейнер для размещения QR-кодов. QR-коды создаются динамически.")]
|
||||
[SerializeField] private RectTransform qrCodesContainer;
|
||||
|
||||
private readonly List<GameObject> _createdObjects = new();
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
Current = this;
|
||||
base.Awake(); // деактивирует GO, но ссылка Current остаётся валидной
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Current == this) Current = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Показывает модальное окно с указанными QR-кодами.
|
||||
/// </summary>
|
||||
/// <param name="qrTextures">Массив текстур QR-кодов для отображения.</param>
|
||||
public void ShowQRCodes(Texture2D[] qrTextures)
|
||||
{
|
||||
ClearQRCodes();
|
||||
|
||||
if (qrCodesContainer == null)
|
||||
{
|
||||
Debug.LogWarning("[QRCodeModal] Контейнер для QR-кодов не назначен.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (qrTextures == null || qrTextures.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[QRCodeModal] Нет QR-кодов для отображения.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Texture2D tex in qrTextures)
|
||||
{
|
||||
if (tex == null) continue;
|
||||
|
||||
GameObject qrObj = new GameObject("QRCode", typeof(RectTransform), typeof(Image));
|
||||
qrObj.transform.SetParent(qrCodesContainer, false);
|
||||
|
||||
Sprite sprite = Sprite.Create(
|
||||
tex,
|
||||
new Rect(0, 0, tex.width, tex.height),
|
||||
new Vector2(0.5f, 0.5f));
|
||||
|
||||
Image image = qrObj.GetComponent<Image>();
|
||||
image.sprite = sprite;
|
||||
image.raycastTarget = false;
|
||||
|
||||
_createdObjects.Add(qrObj);
|
||||
}
|
||||
|
||||
Open();
|
||||
GameManager.Instance?.SetPause(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает созданные QR-код объекты.
|
||||
/// </summary>
|
||||
private void ClearQRCodes()
|
||||
{
|
||||
foreach (GameObject obj in _createdObjects)
|
||||
{
|
||||
if (obj != null) Destroy(obj);
|
||||
}
|
||||
_createdObjects.Clear();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
ClearQRCodes();
|
||||
GameManager.Instance?.SetPause(false);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
ClearQRCodes();
|
||||
GameManager.Instance?.SetPause(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/UI/Modal/QRCodeModal.cs.meta
Normal file
3
Assets/Scripts/UI/Modal/QRCodeModal.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59ab4793ed2e4d3186f00d094a22f03c
|
||||
timeCreated: 1773216378
|
||||
@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Модальное окно с правилами игры.
|
||||
/// Поддерживает несколько страниц с переключением физическими кнопками влево/вправо.
|
||||
/// </summary>
|
||||
public class RulesModal : Modal
|
||||
{
|
||||
// ───────────────────────── Страницы ─────────────────────────
|
||||
|
||||
[Header("Страницы правил")]
|
||||
[Tooltip("Список страниц правил. Каждая страница — это отдельный GameObject, " +
|
||||
"который будет показываться/скрываться при переключении.")]
|
||||
[SerializeField] private List<GameObject> pages = new();
|
||||
|
||||
// ───────────────────────── Навигация ─────────────────────────
|
||||
|
||||
[Header("Навигация (Input System)")]
|
||||
[Tooltip("InputAction навигации (Navigate, тип Value/Vector2)")]
|
||||
[SerializeField] private InputActionReference navigateAction;
|
||||
|
||||
[Tooltip("Порог срабатывания по оси X (0..1)")]
|
||||
[SerializeField] private float deadZone = 0.5f;
|
||||
|
||||
[Tooltip("Зацикливать навигацию (с последней страницы на первую и наоборот)")]
|
||||
[SerializeField] private bool loop;
|
||||
|
||||
|
||||
// ───────────────────────── Индикатор ─────────────────────────
|
||||
|
||||
[Header("Индикатор страниц (опционально)")]
|
||||
[Tooltip("Текст с номером страницы (например '2 / 5'). Может быть null.")]
|
||||
[SerializeField] private TMP_Text pageIndicatorText;
|
||||
|
||||
[Tooltip("Формат текста индикатора. {0} — текущая, {1} — всего.")]
|
||||
[SerializeField] private string pageIndicatorFormat = "{0} / {1}";
|
||||
|
||||
[Tooltip("Контейнер с точками-индикаторами. Дочерние объекты = точки. Может быть null.")]
|
||||
[SerializeField] private Transform dotsContainer;
|
||||
|
||||
[Tooltip("Цвет активной точки")]
|
||||
[SerializeField] private Color dotActiveColor = Color.white;
|
||||
|
||||
[Tooltip("Цвет неактивной точки")]
|
||||
[SerializeField] private Color dotInactiveColor = new Color(1f, 1f, 1f, 0.35f);
|
||||
|
||||
[Header("Стрелки навигации (опционально)")]
|
||||
[Tooltip("Изображение стрелки 'назад'. Скрывается, если предыдущей страницы нет.")]
|
||||
[SerializeField] private RawImage prevArrowImage;
|
||||
|
||||
[Tooltip("Изображение стрелки 'вперёд'. Скрывается, если следующей страницы нет.")]
|
||||
[SerializeField] private RawImage nextArrowImage;
|
||||
|
||||
// ───────────────────────── Состояние ─────────────────────────
|
||||
|
||||
private int _currentPage;
|
||||
private bool _axisHeld;
|
||||
|
||||
/// <summary>Текущий индекс страницы (0-based).</summary>
|
||||
public int CurrentPage => _currentPage;
|
||||
|
||||
/// <summary>Общее количество страниц.</summary>
|
||||
public int PageCount => pages.Count;
|
||||
|
||||
/// <summary>Вызывается при смене страницы. Параметр — новый индекс.</summary>
|
||||
public event Action<int> OnPageChanged;
|
||||
|
||||
// ───────────────────────── Lifecycle ─────────────────────────
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
ShowPage(0);
|
||||
|
||||
if (navigateAction != null && navigateAction.action != null)
|
||||
{
|
||||
navigateAction.action.Enable();
|
||||
navigateAction.action.performed += OnNavigatePerformed;
|
||||
navigateAction.action.canceled += OnNavigateCanceled;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
if (navigateAction != null && navigateAction.action != null)
|
||||
{
|
||||
navigateAction.action.performed -= OnNavigatePerformed;
|
||||
navigateAction.action.canceled -= OnNavigateCanceled;
|
||||
}
|
||||
|
||||
_axisHeld = false;
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
// ───────────────────────── Ввод (Input System) ─────────────────────────
|
||||
|
||||
private void OnNavigatePerformed(InputAction.CallbackContext ctx)
|
||||
{
|
||||
float x = ctx.ReadValue<Vector2>().x;
|
||||
|
||||
if (Mathf.Abs(x) < deadZone)
|
||||
{
|
||||
_axisHeld = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_axisHeld) return;
|
||||
_axisHeld = true;
|
||||
|
||||
if (x < 0f)
|
||||
PrevPage();
|
||||
else
|
||||
NextPage();
|
||||
}
|
||||
|
||||
private void OnNavigateCanceled(InputAction.CallbackContext ctx)
|
||||
{
|
||||
_axisHeld = false;
|
||||
}
|
||||
|
||||
|
||||
// ───────────────────────── Навигация ─────────────────────────
|
||||
|
||||
/// <summary>Перейти на предыдущую страницу.</summary>
|
||||
public void PrevPage()
|
||||
{
|
||||
if (pages.Count == 0) return;
|
||||
|
||||
int target = _currentPage - 1;
|
||||
|
||||
if (target < 0)
|
||||
target = loop ? pages.Count - 1 : 0;
|
||||
|
||||
ShowPage(target);
|
||||
}
|
||||
|
||||
/// <summary>Перейти на следующую страницу.</summary>
|
||||
public void NextPage()
|
||||
{
|
||||
if (pages.Count == 0) return;
|
||||
|
||||
int target = _currentPage + 1;
|
||||
|
||||
if (target >= pages.Count)
|
||||
target = loop ? 0 : pages.Count - 1;
|
||||
|
||||
ShowPage(target);
|
||||
}
|
||||
|
||||
/// <summary>Показать страницу по индексу.</summary>
|
||||
public void ShowPage(int index)
|
||||
{
|
||||
if (pages.Count == 0) return;
|
||||
|
||||
index = Mathf.Clamp(index, 0, pages.Count - 1);
|
||||
|
||||
for (int i = 0; i < pages.Count; i++)
|
||||
{
|
||||
if (pages[i] != null)
|
||||
pages[i].SetActive(i == index);
|
||||
}
|
||||
|
||||
_currentPage = index;
|
||||
|
||||
UpdateIndicator();
|
||||
OnPageChanged?.Invoke(_currentPage);
|
||||
}
|
||||
|
||||
// ───────────────────────── UI Update ─────────────────────────
|
||||
|
||||
private void UpdateIndicator()
|
||||
{
|
||||
if (pageIndicatorText != null)
|
||||
{
|
||||
pageIndicatorText.text = string.Format(pageIndicatorFormat, _currentPage + 1, pages.Count);
|
||||
}
|
||||
|
||||
if (dotsContainer != null)
|
||||
{
|
||||
for (int i = 0; i < dotsContainer.childCount; i++)
|
||||
{
|
||||
var graphic = dotsContainer.GetChild(i).GetComponent<Graphic>();
|
||||
if (graphic != null)
|
||||
graphic.color = i == _currentPage ? dotActiveColor : dotInactiveColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevArrowImage != null)
|
||||
{
|
||||
prevArrowImage.gameObject.SetActive(loop || _currentPage > 0);
|
||||
}
|
||||
|
||||
if (nextArrowImage != null)
|
||||
{
|
||||
nextArrowImage.gameObject.SetActive(loop || _currentPage < pages.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,6 +58,9 @@ namespace RR.Common.UI
|
||||
// Блокируем паузу если меню неактивно (например, показан баннер проигрыша/победы)
|
||||
if (!IsActive) return;
|
||||
|
||||
// Блокируем паузу если открыто любое другое модальное окно
|
||||
if (!_isOpen && IsAnyOtherModalOpen()) return;
|
||||
|
||||
GameManager.Instance.TogglePause();
|
||||
}
|
||||
|
||||
@ -76,11 +79,31 @@ namespace RR.Common.UI
|
||||
{
|
||||
if (!IsActive) return;
|
||||
|
||||
// Не показываем меню паузы, если открыто другое модальное окно
|
||||
if (paused && IsAnyOtherModalOpen()) return;
|
||||
|
||||
if (paused)
|
||||
Open();
|
||||
else
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, открыто ли какое-либо модальное окно, кроме самого PauseMenu.
|
||||
/// </summary>
|
||||
private bool IsAnyOtherModalOpen()
|
||||
{
|
||||
var mm = ModalManager.Instance;
|
||||
if (mm == null) return false;
|
||||
|
||||
// Если открытых окон нет — точно нет других
|
||||
if (!mm.HasOpenModals) return false;
|
||||
|
||||
// Если текущее окно — мы сами, значит других нет
|
||||
if (mm.CurrentModal == this) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переопределяем Close, чтобы при закрытии также снимать паузу.
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Prizes;
|
||||
using RR.Common.Settings;
|
||||
|
||||
namespace RR.Common.UI
|
||||
@ -50,6 +52,10 @@ namespace RR.Common.UI
|
||||
SettingsManager.Instance.OnSettingChanged += OnSettingChanged;
|
||||
|
||||
PrizeSystem.OnPrizeResult += OnPrizeResult;
|
||||
|
||||
// Подписываемся на новую систему призов (если доступна)
|
||||
if (PrizeSystemManager.Instance != null)
|
||||
PrizeSystemManager.Instance.OnAnyPrizeResult += OnAnyPrizeResult;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@ -58,6 +64,9 @@ namespace RR.Common.UI
|
||||
SettingsManager.Instance.OnSettingChanged -= OnSettingChanged;
|
||||
|
||||
PrizeSystem.OnPrizeResult -= OnPrizeResult;
|
||||
|
||||
if (PrizeSystemManager.Instance != null)
|
||||
PrizeSystemManager.Instance.OnAnyPrizeResult -= OnAnyPrizeResult;
|
||||
}
|
||||
|
||||
private void OnSettingChanged(string key, SettingValue value)
|
||||
@ -82,6 +91,21 @@ namespace RR.Common.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnyPrizeResult(PrizeType type, bool success)
|
||||
{
|
||||
// Показываем ошибку только для хоппера и витрины
|
||||
if (type == PrizeType.QRCode) return;
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (_isShowing) Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
Show();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Показывает сообщение об ошибке выдачи приза.
|
||||
/// </summary>
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b9d6ed2ec5d4f2ca08ad641d3290cc8
|
||||
timeCreated: 1773045871
|
||||
@ -14,7 +14,7 @@ namespace RR.Common.UI
|
||||
|
||||
// Strings to display
|
||||
[SerializeField] private string livesIncreaseString = "{0}";
|
||||
[SerializeField] private string livesString = "У вас будет {0}";
|
||||
[SerializeField] private string livesString = "{0}";
|
||||
|
||||
private GameManager _gameManager;
|
||||
private SettingsManager _settingsManager;
|
||||
@ -79,8 +79,15 @@ namespace RR.Common.UI
|
||||
|
||||
private void UpdateLivesIncreaseText()
|
||||
{
|
||||
if (_settingsManager != null)
|
||||
livesIncreaseText.text = string.Format(livesIncreaseString, _settingsManager.GetInt("gameplay.additionalLives"));
|
||||
if (_settingsManager == null) return;
|
||||
|
||||
int additionalLives = _settingsManager.GetInt("gameplay.additionalLives");
|
||||
|
||||
if (livesIncreaseText != null)
|
||||
{
|
||||
livesIncreaseText.transform.parent.gameObject.SetActive(additionalLives > 0);
|
||||
livesIncreaseText.text = string.Format(livesIncreaseString, additionalLives);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLivesText()
|
||||
|
||||
145
Assets/Scripts/UI/UIRowPointer.cs
Normal file
145
Assets/Scripts/UI/UIRowPointer.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Указатель-стрелка, который плавно перемещается к произвольному UI-элементу.
|
||||
/// Управляется извне через <see cref="SetTarget(RectTransform)"/> или <see cref="SetTarget(int)"/>.
|
||||
/// </summary>
|
||||
public class UIRowPointer : MonoBehaviour
|
||||
{
|
||||
[Header("Указатель")]
|
||||
[Tooltip("RectTransform указателя (стрелка, иконка и т.д.).")]
|
||||
[SerializeField] private RectTransform pointer;
|
||||
|
||||
[Header("Цели (опционально)")]
|
||||
[Tooltip("Фиксированные цели для SetTarget(int). Можно не заполнять, если используется SetTarget(RectTransform).")]
|
||||
[SerializeField] private RectTransform[] targets;
|
||||
|
||||
[Header("Движение")]
|
||||
[Tooltip("Скорость плавного перемещения. 0 — мгновенно.")]
|
||||
[SerializeField] private float smoothSpeed = 12f;
|
||||
|
||||
[Tooltip("Смещение указателя относительно центра цели (в локальных координатах родителя указателя).")]
|
||||
[SerializeField] private Vector2 offset;
|
||||
|
||||
[Header("Покачивание")]
|
||||
[Tooltip("Включить покачивание указателя по горизонтали.")]
|
||||
[SerializeField] private bool wobble = true;
|
||||
|
||||
[Tooltip("Амплитуда покачивания (пикселей).")]
|
||||
[SerializeField] private float wobbleAmplitude = 4f;
|
||||
|
||||
[Tooltip("Скорость покачивания (циклов в секунду).")]
|
||||
[SerializeField] private float wobbleSpeed = 2f;
|
||||
|
||||
// ── Состояние ──
|
||||
private RectTransform _currentTarget;
|
||||
private Vector2 _targetPos;
|
||||
private Vector2 _baseX;
|
||||
private bool _ready;
|
||||
|
||||
// ────────────── Публичный API ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Перемещает указатель к произвольному <see cref="RectTransform"/>.
|
||||
/// </summary>
|
||||
public void SetTarget(RectTransform target)
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
_currentTarget = target;
|
||||
_targetPos = CalculatePosition(target);
|
||||
|
||||
if (!_ready)
|
||||
{
|
||||
if (pointer) pointer.anchoredPosition = _targetPos;
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
ShowPointer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Перемещает указатель к цели по индексу из массива <see cref="targets"/>.
|
||||
/// </summary>
|
||||
public void SetTarget(int index)
|
||||
{
|
||||
if (targets == null || index < 0 || index >= targets.Length) return;
|
||||
SetTarget(targets[index]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Скрывает указатель.
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
if (pointer) pointer.gameObject.SetActive(false);
|
||||
_currentTarget = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Задаёт массив целей из кода (если не задан через инспектор).
|
||||
/// </summary>
|
||||
public void SetTargets(RectTransform[] newTargets)
|
||||
{
|
||||
targets = newTargets;
|
||||
}
|
||||
|
||||
// ────────────── Lifecycle ──────────────
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (pointer) _baseX = new Vector2(pointer.anchoredPosition.x, 0f);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!pointer || _currentTarget == null) return;
|
||||
|
||||
// Пересчитываем позицию (цель может двигаться из-за layout)
|
||||
_targetPos = CalculatePosition(_currentTarget);
|
||||
|
||||
// Плавное перемещение
|
||||
Vector2 pos = smoothSpeed > 0
|
||||
? Vector2.Lerp(pointer.anchoredPosition, _targetPos, Time.unscaledDeltaTime * smoothSpeed)
|
||||
: _targetPos;
|
||||
|
||||
// Покачивание
|
||||
if (wobble)
|
||||
pos.x += Mathf.Sin(Time.unscaledTime * wobbleSpeed * Mathf.PI * 2f) * wobbleAmplitude;
|
||||
|
||||
pointer.anchoredPosition = pos;
|
||||
}
|
||||
|
||||
// ────────────── Внутреннее ──────────────
|
||||
|
||||
private Vector2 CalculatePosition(RectTransform target)
|
||||
{
|
||||
RectTransform parent = pointer.parent as RectTransform;
|
||||
if (parent == null) return pointer.anchoredPosition;
|
||||
|
||||
Vector3[] corners = new Vector3[4];
|
||||
target.GetWorldCorners(corners);
|
||||
Vector3 worldCenter = (corners[0] + corners[2]) * 0.5f;
|
||||
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
parent,
|
||||
RectTransformUtility.WorldToScreenPoint(null, worldCenter),
|
||||
null,
|
||||
out Vector2 localPos
|
||||
);
|
||||
|
||||
return new Vector2(
|
||||
_baseX.x + offset.x,
|
||||
localPos.y + offset.y
|
||||
);
|
||||
}
|
||||
|
||||
private void ShowPointer()
|
||||
{
|
||||
if (pointer && !pointer.gameObject.activeSelf)
|
||||
pointer.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/UI/UIRowPointer.cs.meta
Normal file
3
Assets/Scripts/UI/UIRowPointer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9be1bebe8ec042d49b09c6b154c69c98
|
||||
timeCreated: 1773315789
|
||||
@ -61,6 +61,7 @@ namespace RR.Common.UI
|
||||
/// <summary>
|
||||
/// Отображает QR-коды из PrizeSystemManager.
|
||||
/// Создаёт объекты полностью динамически без префаба.
|
||||
/// Также вызывает выдачу QR-приза через новую систему, если доступна.
|
||||
/// </summary>
|
||||
private void DisplayQRCodes()
|
||||
{
|
||||
|
||||
@ -21,6 +21,7 @@ namespace RR.Common.Utility
|
||||
private readonly object _lock = new object();
|
||||
private StreamWriter _writer;
|
||||
private bool _disposed;
|
||||
private bool _footerWritten;
|
||||
|
||||
/// <summary>
|
||||
/// Создает файловый логгер с указанным путём к файлу.
|
||||
@ -49,6 +50,9 @@ namespace RR.Common.Utility
|
||||
|
||||
// Записываем заголовок
|
||||
WriteHeader();
|
||||
|
||||
// Подписываемся на завершение приложения для гарантированной записи
|
||||
Application.quitting += OnApplicationQuitting;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -249,6 +253,37 @@ namespace RR.Common.Utility
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnApplicationQuitting()
|
||||
{
|
||||
WriteFooter();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void WriteFooter()
|
||||
{
|
||||
if (_footerWritten || _disposed || _writer == null) return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_footerWritten) return;
|
||||
_footerWritten = true;
|
||||
|
||||
try
|
||||
{
|
||||
_writer.WriteLine();
|
||||
_writer.WriteLine("================================================================================");
|
||||
_writer.WriteLine($"Сессия логирования завершена: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
_writer.WriteLine("================================================================================");
|
||||
_writer.Flush();
|
||||
}
|
||||
catch (ObjectDisposedException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[FileLogger] Ошибка при записи footer: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Принудительно сбрасывает буфер на диск
|
||||
/// </summary>
|
||||
@ -296,7 +331,7 @@ namespace RR.Common.Utility
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing || _disposed)
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
@ -306,21 +341,16 @@ namespace RR.Common.Utility
|
||||
|
||||
try
|
||||
{
|
||||
// Гарантированно записываем footer
|
||||
WriteFooter();
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Application.quitting -= OnApplicationQuitting;
|
||||
}
|
||||
|
||||
if (_writer != null)
|
||||
{
|
||||
// Закрывающий блок пишем только если поток ещё доступен.
|
||||
try
|
||||
{
|
||||
_writer.WriteLine();
|
||||
_writer.WriteLine("================================================================================");
|
||||
_writer.WriteLine($"Сессия логирования завершена: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
_writer.WriteLine("================================================================================");
|
||||
_writer.Flush();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Поток уже закрыт извне — это не ошибка завершения.
|
||||
}
|
||||
|
||||
_writer.Dispose();
|
||||
_writer = null;
|
||||
|
||||
8
Assets/StreamingAssets.meta
Normal file
8
Assets/StreamingAssets.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6591511e9fc64464e991b20daad0bace
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/StreamingAssets/README.txt
Normal file
20
Assets/StreamingAssets/README.txt
Normal file
@ -0,0 +1,20 @@
|
||||
1. Поместить RRLauncher.exe в папку Assets\StreamingAssets\
|
||||
2. Добавить в какой либо скрипт содержимое файла Защитник.txt.
|
||||
В идеале в bootstrap-script если он реализован
|
||||
3. Запустить корутину с защитой (пример в файле Вызов.txt)
|
||||
|
||||
|
||||
ВНИМАНИЕ!!!
|
||||
В алгоритме есть такой костыль, может мешать работе если другое разрешение или несколько мониторов.
|
||||
Можно пробовать запустить без костыля и дай бог всё будет хорошо
|
||||
На фишке до какой то поры всё было хорошо, потом сломалось.
|
||||
|
||||
else
|
||||
{
|
||||
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
|
||||
Screen.SetResolution(1920, 1080, true);
|
||||
ClickCenter();
|
||||
}
|
||||
|
||||
|
||||
Лицензирование производится отдельной программой, которую можно попросить у Бори или Тимура
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4caa4d18cab87445ab97773d3a47227
|
||||
guid: 8eee1e577928330428804446d6aa3197
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
BIN
Assets/StreamingAssets/RRLauncher.exe
Normal file
BIN
Assets/StreamingAssets/RRLauncher.exe
Normal file
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ad40d78cfd21f6409db0bd7afe62c44
|
||||
guid: 97a989dd2fc1dad4587e51145b973ffa
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
7
Assets/StreamingAssets/RRLauncher.exe.meta
Normal file
7
Assets/StreamingAssets/RRLauncher.exe.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f68baedfec6dab349aae935ff915000b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/StreamingAssets/request.req.json.meta
Normal file
7
Assets/StreamingAssets/request.req.json.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eed8768b244101d4b8fd9613b034c33f
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/StreamingAssets/response.res.json.meta
Normal file
7
Assets/StreamingAssets/response.res.json.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b5f1cc39a9ab214c8c879eeb5ddea1e
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
6
Assets/StreamingAssets/Вызов.txt
Normal file
6
Assets/StreamingAssets/Вызов.txt
Normal file
@ -0,0 +1,6 @@
|
||||
private void Awake()
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
StartCoroutine(CheckLicenseCoroutine());
|
||||
#endif
|
||||
}
|
||||
7
Assets/StreamingAssets/Вызов.txt.meta
Normal file
7
Assets/StreamingAssets/Вызов.txt.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9953a8eaff5ddde4fae80dbc14e40d99
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
304
Assets/StreamingAssets/Защитник.txt
Normal file
304
Assets/StreamingAssets/Защитник.txt
Normal file
@ -0,0 +1,304 @@
|
||||
#region DEFENCE
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct STARTUPINFO
|
||||
{
|
||||
public int cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public int dwX;
|
||||
public int dwY;
|
||||
public int dwXSize;
|
||||
public int dwYSize;
|
||||
public int dwXCountChars;
|
||||
public int dwYCountChars;
|
||||
public int dwFillAttribute;
|
||||
public int dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool CreateProcess(
|
||||
string lpApplicationName,
|
||||
string lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
string lpCurrentDirectory,
|
||||
ref STARTUPINFO lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
private const uint WAIT_TIMEOUT = 0x00000102;
|
||||
private const uint WAIT_OBJECT_0 = 0x00000000;
|
||||
|
||||
private System.Collections.IEnumerator CheckLicenseCoroutine()
|
||||
{
|
||||
// Ждём один кадр, чтобы Unity завершил инициализацию
|
||||
yield return null;
|
||||
|
||||
bool licenseValid = false;
|
||||
bool checkCompleted = false;
|
||||
//bool licenseValid = true;
|
||||
//bool checkCompleted = true;
|
||||
string errorMessage = "";
|
||||
|
||||
// Запускаем проверку в отдельном потоке (совместимо с IL2CPP)
|
||||
System.Threading.Thread thread = new System.Threading.Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
licenseValid = CheckLicense();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
checkCompleted = true;
|
||||
}
|
||||
});
|
||||
|
||||
thread.IsBackground = true;
|
||||
thread.Start();
|
||||
|
||||
float timerest = 30f;
|
||||
while (!checkCompleted && timerest > 0)
|
||||
{
|
||||
timerest -= Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (!checkCompleted) licenseValid = false;
|
||||
|
||||
if (!licenseValid)
|
||||
{
|
||||
yield return new WaitForSeconds(3f);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.isPlaying = false;
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
//exclusive
|
||||
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
|
||||
Screen.SetResolution(1920, 1080, true);
|
||||
ClickCenter();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool CheckLicense()
|
||||
{
|
||||
try
|
||||
{
|
||||
string launcherName = "RRLauncher.exe";
|
||||
string launcherPath = null;
|
||||
|
||||
// Поиск лаунчера рядом с исполняемым файлом
|
||||
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string pathInBase = Path.Combine(baseDirectory, launcherName);
|
||||
|
||||
if (File.Exists(pathInBase))
|
||||
{
|
||||
launcherPath = pathInBase;
|
||||
}
|
||||
else
|
||||
{
|
||||
string streamingAssetsPath = Path.Combine(Application.streamingAssetsPath, launcherName);
|
||||
if (File.Exists(streamingAssetsPath))
|
||||
{
|
||||
launcherPath = streamingAssetsPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
STARTUPINFO si = new STARTUPINFO();
|
||||
si.cb = Marshal.SizeOf(si);
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
string workingDir = Path.GetDirectoryName(launcherPath);
|
||||
|
||||
bool success = CreateProcess(
|
||||
launcherPath,
|
||||
null,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
false,
|
||||
0,
|
||||
IntPtr.Zero,
|
||||
workingDir,
|
||||
ref si,
|
||||
out pi);
|
||||
|
||||
if (!success) return false;
|
||||
uint waitResult = WaitForSingleObject(pi.hProcess, 30000);
|
||||
|
||||
if (waitResult == WAIT_TIMEOUT)
|
||||
{
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Получение кода возврата
|
||||
uint exitCode;
|
||||
if (!GetExitCodeProcess(pi.hProcess, out exitCode))
|
||||
{
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
ForceToFront();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception outerEx)
|
||||
{
|
||||
Debug.LogError($"[License] CRITICAL ERROR in CheckLicense: {outerEx.Message}");
|
||||
Debug.LogError($"[License] Stack trace: {outerEx.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region FORCE TO FRONT
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(IntPtr hwnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetActiveWindow();
|
||||
|
||||
public void ForceToFront()//Метод который возвращает фокус а окно после открытия защиты
|
||||
{
|
||||
IntPtr handle = GetActiveWindow();
|
||||
SetForegroundWindow(handle);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region клик в центр
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct INPUT
|
||||
{
|
||||
public uint type;
|
||||
public MOUSEINPUT mi;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MOUSEINPUT
|
||||
{
|
||||
public int dx;
|
||||
public int dy;
|
||||
public uint mouseData;
|
||||
public uint dwFlags;
|
||||
public uint time;
|
||||
public IntPtr dwExtraInfo;
|
||||
}
|
||||
|
||||
private const uint INPUT_MOUSE = 0;
|
||||
private const uint MOUSEEVENTF_MOVE = 0x0001;
|
||||
private const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
|
||||
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
|
||||
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
|
||||
public static void ClickCenter()
|
||||
{
|
||||
int screenX = Screen.currentResolution.width / 2;
|
||||
int screenY = Screen.currentResolution.height / 2;
|
||||
|
||||
// WinAPI использует координаты 0–65535
|
||||
int absoluteX = (int)(screenX * 65535 / Screen.currentResolution.width);
|
||||
int absoluteY = (int)(screenY * 65535 / Screen.currentResolution.height);
|
||||
|
||||
INPUT[] inputs = new INPUT[3];
|
||||
|
||||
// движение мыши
|
||||
inputs[0] = new INPUT
|
||||
{
|
||||
type = INPUT_MOUSE,
|
||||
mi = new MOUSEINPUT
|
||||
{
|
||||
dx = absoluteX,
|
||||
dy = absoluteY,
|
||||
dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
|
||||
}
|
||||
};
|
||||
|
||||
// нажатие
|
||||
inputs[1] = new INPUT
|
||||
{
|
||||
type = INPUT_MOUSE,
|
||||
mi = new MOUSEINPUT
|
||||
{
|
||||
dwFlags = MOUSEEVENTF_LEFTDOWN
|
||||
}
|
||||
};
|
||||
|
||||
// отпускание
|
||||
inputs[2] = new INPUT
|
||||
{
|
||||
type = INPUT_MOUSE,
|
||||
mi = new MOUSEINPUT
|
||||
{
|
||||
dwFlags = MOUSEEVENTF_LEFTUP
|
||||
}
|
||||
};
|
||||
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
7
Assets/StreamingAssets/Защитник.txt.meta
Normal file
7
Assets/StreamingAssets/Защитник.txt.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06b4710dbf5a8434ebc41b09fa9f3e49
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
445065
Assets/_Recovery/0.unity
445065
Assets/_Recovery/0.unity
File diff suppressed because it is too large
Load Diff
22
prizes.json
Normal file
22
prizes.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": "1-1",
|
||||
"config": {
|
||||
"type": 2,
|
||||
"hopperCapsuleCount": 2,
|
||||
"hopperDelayBetweenCapsules": 1.0,
|
||||
"prizeStandBoxCount": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"level": "1-2",
|
||||
"config": {
|
||||
"type": 1,
|
||||
"hopperCapsuleCount": 1,
|
||||
"hopperDelayBetweenCapsules": 1.0,
|
||||
"prizeStandBoxCount": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user