feat: add hopper prize system, prize error display, levitator component
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled

Система выдачи призов хоппером:
- PrizeSystemManager теперь читает enableHopper из настройки prizes.giveSmallPrize
  вместо захардкоженного SerializeField (реагирует на изменения в реальном времени)
- LevelFinish: исправлен ключ настройки на prizes.giveSmallPrize
- Добавлен HopperCheckController для тестирования выдачи приза кнопкой в настройках
- Проверка датчика хоппера перенесена в ComDeviceClient (2-й бит INPUT)
- PrizeGivenBuffer заменён с PlayerPrefs на потокобезопасный Interlocked-буфер
  (исправлен краш при вызове PlayerPrefs из фонового потока платы)
- Добавлен PrizeSystem.OnPrizeResult (Action<bool>) — событие результата выдачи

Отображение ошибки выдачи приза:
- Добавлен PrizeErrorDisplay — UI-компонент, подписанный на OnPrizeResult
- Зависит от настройки prizes.showPrizeError (реагирует на изменения в реальном времени)

QR-коды:
- PrizeSystemManager подписан на изменение prizes.phoneNumber — QR-коды
  перегенерируются автоматически при смене номера
- WinBannerController показывает QR-коды только при включённой prizes.giveBigPrize

Прочее:
- Добавлен Levitator — компонент плавной левитации объекта (высота, скорость, плавность)
- Настройки: группы поддержки/тестов реорганизованы, добавлена кнопка btn.hopper_test
This commit is contained in:
Данил Ярош 2026-03-06 16:47:40 +03:00
parent c1d8cb89c9
commit 6da2812c8e
26 changed files with 3150 additions and 4711 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,7 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 6866002081039623265}

View File

@ -9,10 +9,10 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 3897414768386052035}
- component: {fileID: 8918354566690060046}
- component: {fileID: 8292445450809725220}
- component: {fileID: 5533639768978301200}
- component: {fileID: 8919454405038276609}
- component: {fileID: 2739203642713137311}
- component: {fileID: 2117915465640148810}
m_Layer: 0
m_Name: Crystal
m_TagString: Coin
@ -30,14 +30,14 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_LocalScale: {x: 0.25, y: 0.25, z: 0.25}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 2113465535973362073}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!58 &8918354566690060046
CircleCollider2D:
--- !u!60 &5533639768978301200
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@ -71,29 +71,24 @@ CircleCollider2D:
m_CompositeOperation: 0
m_CompositeOrder: 0
m_Offset: {x: 0, y: 0}
m_Radius: 0.4
--- !u!95 &8292445450809725220
Animator:
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 351781492710161714}
m_Enabled: 0
m_Avatar: {fileID: 0}
m_Controller: {fileID: 9100000, guid: 5dc3990bcb1185e4fb2dfaeb428cc8c8, type: 2}
m_CullingMode: 2
m_UpdateMode: 0
m_ApplyRootMotion: 0
m_LinearVelocityBlending: 0
m_StabilizeFeet: 0
m_AnimatePhysics: 0
m_WarningMessage:
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
m_SpriteTilingProperty:
border: {x: 0, y: 0, z: 0, w: 0}
pivot: {x: 0, y: 0}
oldSize: {x: 0, y: 0}
newSize: {x: 0, y: 0}
adaptiveTilingThreshold: 0
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: -0.02322805, y: 3.4153097}
- {x: -0.90321255, y: 2.325301}
- {x: -0.7108127, y: 0.31190002}
- {x: 0.027216375, y: -0.52824646}
- {x: 0.7176478, y: 0.2845608}
- {x: 0.9100473, y: 2.3253012}
m_UseDelaunayMesh: 1
--- !u!114 &8919454405038276609
MonoBehaviour:
m_ObjectHideFlags: 0
@ -126,6 +121,21 @@ MonoBehaviour:
volume: 0.25
pitch: 1
pickupVFX: {fileID: 0}
--- !u!114 &2117915465640148810
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 351781492710161714}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3c6ca3620bc9428ca29bb90a17cf20e5, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Behaviour.Levitator
height: 0.1
speed: 0.5
smoothness: 1
--- !u!1 &455729165605353665
GameObject:
m_ObjectHideFlags: 0

View File

@ -135,7 +135,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Managers.PrizeSystemManager
givePrizes: 1
enableHopper: 0
qrCodes:
- generateQRCode: 1
linkPrefix: https://t.me/
@ -222,14 +221,6 @@ MonoBehaviour:
target: 1
button: 3
dpadDirection: 3
- pinIndex: 4
target: 1
button: 0
dpadDirection: 0
- pinIndex: 5
target: 1
button: 1
dpadDirection: 1
- pinIndex: 2
target: 0
button: 10
@ -238,6 +229,14 @@ MonoBehaviour:
target: 0
button: 11
dpadDirection: 0
- pinIndex: 4
target: 1
button: 0
dpadDirection: 0
- pinIndex: 5
target: 1
button: 1
dpadDirection: 1
- pinIndex: 6
target: 0
button: 4

View File

@ -82362,7 +82362,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4213635907731438243, guid: 70235a41586931d41996f4a80dd71752, type: 3}
propertyPath: enablePrizeOnFinish
value: 0
value: 1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []

View File

@ -34079,55 +34079,6 @@ SpriteRenderer:
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!1 &1385837654
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1385837656}
- component: {fileID: 1385837655}
m_Layer: 0
m_Name: Test Timeline
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!320 &1385837655
PlayableDirector:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1385837654}
m_Enabled: 1
serializedVersion: 3
m_PlayableAsset: {fileID: 11400000, guid: ae5e41b9187d29d4ea5da477e5146663, type: 2}
m_InitialState: 1
m_WrapMode: 2
m_DirectorUpdateMode: 1
m_InitialTime: 0
m_SceneBindings: []
m_ExposedReferences:
m_References: []
--- !u!4 &1385837656
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1385837654}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 151.00003, y: 26.89306, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!4 &1401340222 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 4535171522523579101, guid: 6ab87d9d9d9f20e4c86df42ae805f6f4, type: 3}
@ -236237,4 +236188,3 @@ SceneRoots:
- {fileID: 2086062556}
- {fileID: 1685954871}
- {fileID: 7680842389365283255}
- {fileID: 1385837656}

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ namespace RR.Game2D.Behaviour
[SerializeField] private bool enablePrizeOnFinish = true;
[Tooltip("Ключ настройки для проверки включения выдачи призов (если пусто - не проверяется)")]
[SerializeField] private string prizeEnabledSettingKey = "prize.smallPrizeEnabled";
[SerializeField] private string prizeEnabledSettingKey = "prizes.giveSmallPrize";
[Tooltip("Задержка перед выдачей приза (в секундах)")]
[SerializeField] private float prizeDelay = 0.5f;

View File

@ -256,7 +256,7 @@ namespace RR.Common.Controls
// Проверяем последний пин только если он в пределах массива
if (input.Length > 0 && input.Length <= PIN_COUNT && input[^1] == POSITIVE)
PlayerPrefs.SetInt("PrizeGivenBuffer", 1); //пишем в буфер чтобы между кадрами и порядком вызова не проебать
PrizeSystem.SignalPrizeGiven();
// DebugWindow.Message("Инпуты: " + string.Join("", buttonState));//можно вырезать
}

View File

@ -242,6 +242,14 @@ namespace RR.Common.Controls
string rawData = line.Substring(5);
if (string.IsNullOrWhiteSpace(rawData)) return;
// Проверяем датчик хоппера: 2-й бит с начала в нормальном двоичном представлении
if (long.TryParse(rawData, out long rawValue))
{
string rawBinary = Convert.ToString(rawValue, 2);
if (rawBinary.Length >= 2 && rawBinary[1] == '0')
PrizeSystem.SignalPrizeGiven();
}
string bits = DecimalToBinary(rawData);
_lastInputBits = bits;
InputReceived?.Invoke(DeviceId, bits);

View File

@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Threading;
using RR.Common.Managers;
using RR.Game2D.Core.Managers;
using UnityEngine;
@ -17,6 +19,30 @@ namespace RR.Common.Controls
private static bool _prizing;
private static bool _lastPrizeSuccess = true;
/// <summary>
/// Потокобезопасный буфер для сигнала от датчика хоппера.
/// Записывается из любого потока (ComDeviceClient, COMInput), читается из корутины Prize().
/// </summary>
private static int _prizeGivenBuffer;
/// <summary>
/// Устанавливает флаг выдачи приза. Потокобезопасно — можно вызывать из любого потока.
/// </summary>
public static void SignalPrizeGiven()
{
Interlocked.Exchange(ref _prizeGivenBuffer, 1);
}
private static void ResetPrizeBuffer()
{
Interlocked.Exchange(ref _prizeGivenBuffer, 0);
}
private static bool IsPrizeBufferSet()
{
return Interlocked.CompareExchange(ref _prizeGivenBuffer, 0, 0) == 1;
}
public static readonly string key_starts = "STARTS";
public static readonly string key_startsNul = "STARTSNUL";
public static readonly string key_prizesgiven = "PRIZESGIVEN";
@ -24,6 +50,11 @@ namespace RR.Common.Controls
[SerializeField] AudioClip prizeErrorSound;
/// <summary>
/// Вызывается после завершения выдачи приза. true — успех, false — ошибка.
/// </summary>
public static event Action<bool> OnPrizeResult;
//===========================start: PUBLIC API===========================//
/// <summary>
/// Возвращает true, если последняя выдача приза прошла успешно, false - если была ошибка.
@ -106,12 +137,11 @@ namespace RR.Common.Controls
float startTime = Time.time;
bool success = false;
PlayerPrefs.SetInt("PrizeGivenBuffer", 0);//На всякий случай подготовка буфера
//проверить где он пишется
ResetPrizeBuffer();
while (Time.time - startTime < timeout)
{
if (PlayerPrefs.GetInt("PrizeGivenBuffer", 0) == 1)
if (IsPrizeBufferSet())
{
success = true;
IncreasePrizeCounters();
@ -128,6 +158,8 @@ namespace RR.Common.Controls
_prizing = false;
if (!success) AudioManager.Instance.PlaySfx(prizeErrorSound);
OnPrizeResult?.Invoke(success);
}

View File

@ -0,0 +1,43 @@
using UnityEngine;
namespace RR.Common.Behaviour
{
/// <summary>
/// Компонент левитации объекта — плавное движение вверх-вниз относительно начальной позиции.
/// </summary>
public class Levitator : MonoBehaviour
{
[Header("Параметры левитации")]
[Tooltip("Высота левитации (амплитуда отклонения от начальной позиции).")]
[SerializeField, Min(0f)] private float height = 0.1f;
[Tooltip("Скорость левитации (частота колебаний).")]
[SerializeField, Min(0f)] private float speed = 1f;
[Tooltip("Плавность движения. Чем выше значение, тем более плавные переходы на пиках.")]
[SerializeField, Range(0f, 1f)] private float smoothness = 1f;
private Vector3 _startPosition;
private float _phase;
private void Start()
{
_startPosition = transform.localPosition;
}
private void Update()
{
_phase += speed * Time.deltaTime;
// Базовое синусоидальное движение
float raw = Mathf.Sin(_phase * Mathf.PI * 2f);
// Применяем плавность: интерполяция между линейным треугольным и синусоидальным движением
float triangle = Mathf.PingPong(_phase * 2f, 2f) - 1f;
float value = Mathf.Lerp(triangle, raw, smoothness);
transform.localPosition = _startPosition + Vector3.up * (value * height);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3c6ca3620bc9428ca29bb90a17cf20e5
timeCreated: 1772801174

View File

@ -0,0 +1,24 @@
using RR.Common.Controls;
using RR.Common.Managers;
using UnityEngine;
namespace RR.Common.UI
{
/// <summary>
/// Простой компонент для проверки выдачи приза хоппером.
/// Повесить на объект в сцене и привязать метод CheckGivePrize() к кнопке через UnityEvent.
/// </summary>
public class HopperCheckController : MonoBehaviour
{
/// <summary>
/// Запускает выдачу приза. Привязать к кнопке в инспекторе.
/// </summary>
public void CheckGivePrize()
{
if (PrizeSystemManager.Instance != null)
PrizeSystemManager.Instance.GivePrize();
else
PrizeSystem.GivePrize();
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 05c7dec00b0ae2d4298dba74c8203979

View File

@ -12,7 +12,8 @@ namespace RR.Common.Managers
[Header("Настройки выдачи призов")]
[SerializeField] private bool givePrizes = true;
[SerializeField] private bool enableHopper = false;
private bool _enableHopper;
[Header("Настройки генерации QR-кодов")]
[SerializeField] private List<QRCodeInfo> qrCodes = new();
@ -39,26 +40,58 @@ namespace RR.Common.Managers
? "Prize System Initialized: Prizes will be given."
: "Prize System Initialized: Prizes will NOT be given.");
Debug.Log(enableHopper ? "Hopper is enabled for prize dispensing." : "Hopper is disabled.");
// Получаем состояние хоппера из настроек
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);
GenerateQRCodes();
// Подписываемся на изменение номера телефона для перегенерации QR-кодов
if (settings != null)
settings.OnSettingChanged += OnSettingChanged;
}
private void OnDestroy()
{
var settings = SettingsManager.Instance;
if (settings != null)
settings.OnSettingChanged -= OnSettingChanged;
}
private void OnSettingChanged(string key, SettingValue value)
{
if (key == "prizes.giveSmallPrize")
{
_enableHopper = value.b;
Debug.Log(_enableHopper
? "[PrizeSystemManager] Хоппер включён."
: "[PrizeSystemManager] Хоппер выключен.");
return;
}
if (key == "prizes.phoneNumber")
{
RefreshPhoneNumber(SettingsManager.Instance);
GenerateQRCodes();
Debug.Log($"[PrizeSystemManager] Номер телефона изменён, QR-коды перегенерированы: {_phoneNumber}");
}
}
private void RefreshPhoneNumber(SettingsManager settings)
{
_phoneNumber = settings?.GetString("prizes.phoneNumber") ?? string.Empty;
_phoneNumber = PhoneNumberUtils.ValidatePhoneNumber(_phoneNumber);
_phoneNumber = PhoneNumberUtils.ApplyPhoneMask(_phoneNumber, _phoneNumberMask);
GenerateQRCodes();
}
// Если не инициализируется в GameManager, инициализировать тут
// private void Awake()
// {
// Initialize();
// }
public void GivePrize()
{
if (enableHopper)
if (_enableHopper)
PrizeSystem.GivePrize();
else
Debug.Log("Hopper is disabled. Prize dispensing skipped.");

View File

@ -95,7 +95,7 @@ MonoBehaviour:
displayName: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043D\u043E\u043C\u0435\u0440
\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
shownInSettings: 1
exposed: 0
valueType: 0
@ -121,7 +121,7 @@ MonoBehaviour:
- key: payment.supportNumber
displayName: "\u041D\u043E\u043C\u0435\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
shownInSettings: 0
exposed: 0
valueType: 3
@ -309,7 +309,7 @@ MonoBehaviour:
displayName: "\u0412\u044B\u0434\u0430\u0432\u0430\u0442\u044C \u043F\u0440\u0438\u0437
\u043F\u0440\u0438 \u043E\u0448\u0438\u0431\u043A\u0435"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
shownInSettings: 1
exposed: 0
valueType: 0
@ -336,7 +336,7 @@ MonoBehaviour:
displayName: "\u0412\u044B\u0432\u043E\u0434 \u043E\u0448\u0438\u0431\u043A\u0438
\u0432\u044B\u0434\u0430\u0447\u0438 \u0432 \u043C\u0435\u043D\u044E"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
shownInSettings: 1
exposed: 0
valueType: 0
@ -575,7 +575,7 @@ MonoBehaviour:
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
shownInSettings: 1
exposed: 0
valueType: 4
@ -625,3 +625,29 @@ MonoBehaviour:
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: btn.hopper_test
displayName: "\u0422\u0435\u0441\u0442 \u0432\u044B\u0434\u0430\u0447\u0438 \u043F\u0440\u0438\u0437\u0430"
description:
group: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438 \u0442\u0435\u0441\u0442\u044B"
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
applyTarget: 0
audioTargetType: 0
buttonText: "\u0422\u0435\u0441\u0442 \u0432\u044B\u0434\u0430\u0447\u0438 \u043F\u0440\u0438\u0437\u0430"
buttonData:
buttonColor: {r: 0.16300285, g: 0.64997125, b: 0.9339623, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0

View File

@ -0,0 +1,153 @@
using UnityEngine;
using TMPro;
using RR.Common.Controls;
using RR.Common.Settings;
namespace RR.Common.UI
{
/// <summary>
/// UI-компонент для отображения ошибки выдачи приза хоппером.
/// Подписывается на PrizeSystem.OnPrizeResult и показывает сообщение об ошибке,
/// если выдача приза завершилась неудачно (таймаут датчика).
///
/// Зависит от настройки prizes.showPrizeError.
/// </summary>
public class PrizeErrorDisplay : MonoBehaviour
{
[Header("UI")]
[Tooltip("Текст сообщения об ошибке. Если не задан, ищется на этом же объекте.")]
[SerializeField] private TextMeshProUGUI errorText;
[Tooltip("Объект-контейнер ошибки (для скрытия/показа). Если не задан, используется gameObject.")]
[SerializeField] private GameObject errorContainer;
[Header("Настройки")]
[Tooltip("Текст ошибки, который отображается игроку.")]
[SerializeField] private string errorMessage = "Ошибка выдачи приза!";
[Header("Анимация")]
[Tooltip("CanvasGroup для плавного появления/исчезновения.")]
[SerializeField] private CanvasGroup canvasGroup;
[Tooltip("Длительность анимации появления/исчезновения.")]
[SerializeField] private float fadeDuration = 0.3f;
private bool _enabled;
private bool _isShowing;
private Coroutine _fadeCoroutine;
private void OnEnable()
{
if (errorContainer == null) errorContainer = gameObject;
if (errorText == null) errorText = GetComponentInChildren<TextMeshProUGUI>();
Hide(instant: true);
_enabled = SettingsManager.Instance != null
&& SettingsManager.Instance.GetBool("prizes.showPrizeError");
if (SettingsManager.Instance != null)
SettingsManager.Instance.OnSettingChanged += OnSettingChanged;
PrizeSystem.OnPrizeResult += OnPrizeResult;
}
private void OnDisable()
{
if (SettingsManager.Instance != null)
SettingsManager.Instance.OnSettingChanged -= OnSettingChanged;
PrizeSystem.OnPrizeResult -= OnPrizeResult;
}
private void OnSettingChanged(string key, SettingValue value)
{
if (key != "prizes.showPrizeError") return;
_enabled = value.b;
if (!_enabled)
Hide(instant: true);
}
private void OnPrizeResult(bool success)
{
if (success)
{
if (_isShowing) Hide();
}
else
{
Show();
}
}
/// <summary>
/// Показывает сообщение об ошибке выдачи приза.
/// </summary>
public void Show()
{
if (!_enabled) return;
_isShowing = true;
if (errorText) errorText.text = errorMessage;
errorContainer.SetActive(true);
if (canvasGroup)
{
if (_fadeCoroutine != null) StopCoroutine(_fadeCoroutine);
_fadeCoroutine = StartCoroutine(Fade(0f, 1f, fadeDuration));
}
Debug.LogWarning("[PrizeErrorDisplay] Ошибка выдачи приза отображена.");
}
/// <summary>
/// Скрывает сообщение об ошибке.
/// </summary>
public void Hide(bool instant = false)
{
_isShowing = false;
if (_fadeCoroutine != null)
{
StopCoroutine(_fadeCoroutine);
_fadeCoroutine = null;
}
if (instant || !canvasGroup)
{
if (canvasGroup) canvasGroup.alpha = 0f;
errorContainer.SetActive(false);
}
else
{
_fadeCoroutine = StartCoroutine(FadeAndDisable());
}
}
private System.Collections.IEnumerator FadeAndDisable()
{
yield return Fade(canvasGroup.alpha, 0f, fadeDuration);
errorContainer.SetActive(false);
_fadeCoroutine = null;
}
private System.Collections.IEnumerator Fade(float from, float to, float duration)
{
if (!canvasGroup) yield break;
float elapsed = 0f;
canvasGroup.alpha = from;
while (elapsed < duration)
{
elapsed += Time.unscaledDeltaTime;
canvasGroup.alpha = Mathf.Lerp(from, to, elapsed / duration);
yield return null;
}
canvasGroup.alpha = to;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 84903e265bb04a51ab3f3c6ed5914880
timeCreated: 1772783740

View File

@ -5,6 +5,7 @@ using UnityEngine.UI;
using TMPro;
using RR.Common.Managers;
using RR.Common.Settings;
using RR.Game2D.Core.Managers;
namespace RR.Common.UI
@ -67,6 +68,11 @@ namespace RR.Common.UI
ClearQRCodes();
if (qrCodesContainer == null) return;
// Проверяем настройку показа QR-кодов
if (SettingsManager.Instance == null || !SettingsManager.Instance.GetBool("prizes.giveBigPrize"))
return;
if (PrizeSystemManager.Instance == null) return;
Texture2D[] qrCodes = PrizeSystemManager.Instance.GetQRCodes();

View File

@ -17,7 +17,7 @@ EditorBuildSettings:
- enabled: 1
path: Assets/Scenes/Levels/1-2.unity
guid: 86bb899abf617614ea731dfb70ed822e
- enabled: 1
- enabled: 0
path: Assets/Scenes/Levels/1-3.unity
guid: 970503f52fc318443aa802884a95f012
- enabled: 0