feat: add hopper prize system, prize error display, levitator component
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
Система выдачи призов хоппером: - 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:
parent
c1d8cb89c9
commit
6da2812c8e
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
File diff suppressed because one or more lines are too long
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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: []
|
||||
|
||||
@ -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
@ -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;
|
||||
|
||||
@ -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));//можно вырезать
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
43
Assets/Scripts/Entities/Levitator.cs
Normal file
43
Assets/Scripts/Entities/Levitator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Entities/Levitator.cs.meta
Normal file
3
Assets/Scripts/Entities/Levitator.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c6ca3620bc9428ca29bb90a17cf20e5
|
||||
timeCreated: 1772801174
|
||||
24
Assets/Scripts/Managers/HopperCheckController.cs
Normal file
24
Assets/Scripts/Managers/HopperCheckController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Managers/HopperCheckController.cs.meta
Normal file
2
Assets/Scripts/Managers/HopperCheckController.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05c7dec00b0ae2d4298dba74c8203979
|
||||
@ -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.");
|
||||
|
||||
@ -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
|
||||
|
||||
153
Assets/Scripts/UI/PrizeErrorDisplay.cs
Normal file
153
Assets/Scripts/UI/PrizeErrorDisplay.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/UI/PrizeErrorDisplay.cs.meta
Normal file
3
Assets/Scripts/UI/PrizeErrorDisplay.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84903e265bb04a51ab3f3c6ed5914880
|
||||
timeCreated: 1772783740
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user