feat(qr): add QR code generation and phone number validation utilities

This commit is contained in:
Данил Ярош 2025-11-14 17:06:07 +03:00
parent 90fafefbe5
commit 93e466ebcf
20 changed files with 2696 additions and 59 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,6 +14,7 @@ GameObject:
- component: {fileID: 4577859410310799261}
- component: {fileID: -6245296037342915518}
- component: {fileID: 6566017136146807207}
- component: {fileID: 5633451267958679388}
- component: {fileID: 6824661392768214298}
- component: {fileID: -3912971501865491093}
m_Layer: 0
@ -77,6 +78,7 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::ComWrapper
saveMoneyBetweenSessions: 0
enablePrizeChecking: 0
keepAlive: 1
--- !u!114 &-6245296037342915518
MonoBehaviour:
m_ObjectHideFlags: 0
@ -103,6 +105,32 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::PrizeSystem
prizeErrorSound: {fileID: 0}
--- !u!114 &5633451267958679388
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4763496076470024150}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6e1ef22bfa89b354ea58dcf83572936a, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::PrizeSystemManager
givePrizes: 1
enableHopper: 0
qrCodes:
- generateQRCode: 1
linkPrefix: https://t.me/
suffix: "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435!
\u042F \u0432\u044B\u0438\u0433\u0440\u0430\u043B \u0432 \u0438\u0433\u0440\u0435!
\u0416\u0434\u0443 \u0441\u0432\u043E\u0439 \u043F\u0440\u0438\u0437!"
- generateQRCode: 1
linkPrefix: https://wa.me/
suffix: "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435!
\u042F \u0432\u044B\u0438\u0433\u0440\u0430\u043B \u0432 \u0438\u0433\u0440\u0435!
\u0416\u0434\u0443 \u0441\u0432\u043E\u0439 \u043F\u0440\u0438\u0437!"
_phoneNumberMask: +7XXXXXXXXXX
--- !u!114 &6824661392768214298
MonoBehaviour:
m_ObjectHideFlags: 0
@ -116,7 +144,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Managers.PaymentManager
saveMoneyBetweenSessions: 0
enableFreePlay: 0
enableFreeGame: 0
addCreditAction: {fileID: -2889265436170138120, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
OnCreditsChanged:
m_PersistentCalls:

View File

@ -989,6 +989,7 @@ RectTransform:
- {fileID: 437440678}
- {fileID: 630106232}
- {fileID: 1291956699}
- {fileID: 1765260307}
m_Father: {fileID: 1724863672}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.343, y: 0.175}
@ -1962,7 +1963,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 322.3085, y: -145.87494}
m_AnchoredPosition: {x: 300.5153, y: -162.7992}
m_SizeDelta: {x: 350, y: 125}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &738730170
@ -2199,6 +2200,68 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 925700843}
m_CullTransparentMesh: 1
--- !u!1 &954402732
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 954402733}
- component: {fileID: 954402734}
m_Layer: 5
m_Name: Content
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &954402733
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 954402732}
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: 1794447736}
- {fileID: 1251210174}
m_Father: {fileID: 1765260307}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -0.2598877, y: 0.23867798}
m_SizeDelta: {x: -0.52001953, y: -0.480011}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &954402734
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 954402732}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8a8695521f0d02e499659fee002a26c2, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GridLayoutGroup
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 4
m_StartCorner: 0
m_StartAxis: 0
m_CellSize: {x: 200, y: 200}
m_Spacing: {x: 50, y: 0}
m_Constraint: 0
m_ConstraintCount: 2
--- !u!1 &1061150945
GameObject:
m_ObjectHideFlags: 0
@ -2841,6 +2904,81 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1162184469}
m_CullTransparentMesh: 1
--- !u!1 &1251210173
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1251210174}
- component: {fileID: 1251210176}
- component: {fileID: 1251210175}
m_Layer: 5
m_Name: GameObject_1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &1251210174
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1251210173}
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: 954402733}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 420.47992, y: -135.02136}
m_SizeDelta: {x: 200, y: 200}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1251210175
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1251210173}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 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: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1251210176
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1251210173}
m_CullTransparentMesh: 1
--- !u!1 &1256436979
GameObject:
m_ObjectHideFlags: 0
@ -2996,9 +3134,9 @@ RectTransform:
m_Father: {fileID: 294888497}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0.71428573}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0.31300002}
m_AnchoredPosition: {x: -2.4137878, y: 0.44125366}
m_SizeDelta: {x: -4.8276, y: 0.8829956}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1291956700
MonoBehaviour:
@ -3893,6 +4031,131 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1724863671}
m_CullTransparentMesh: 1
--- !u!1 &1765260306
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1765260307}
- component: {fileID: 1765260308}
m_Layer: 5
m_Name: QRPlaceholder
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1765260307
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1765260306}
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: 954402733}
m_Father: {fileID: 294888497}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.0064899777, y: 0.31300002}
m_AnchorMax: {x: 1, y: 0.699684}
m_AnchoredPosition: {x: -1.4869995, y: 0.58657837}
m_SizeDelta: {x: -5.6500244, y: -0.480011}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1765260308
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1765260306}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: daf828422caf7c74db507420de242849, type: 3}
m_Name:
m_EditorClassIdentifier: '::'
content: {fileID: 954402733}
--- !u!1 &1794447735
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1794447736}
- component: {fileID: 1794447738}
- component: {fileID: 1794447737}
m_Layer: 5
m_Name: GameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &1794447736
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1794447735}
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: 954402733}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 170.47992, y: -135.02136}
m_SizeDelta: {x: 200, y: 200}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1794447737
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1794447735}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 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: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1794447738
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1794447735}
m_CullTransparentMesh: 1
--- !u!1 &1871409155
GameObject:
m_ObjectHideFlags: 0

View File

@ -214,7 +214,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &28542763
RectTransform:
m_ObjectHideFlags: 0
@ -1336,7 +1336,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 50, y: -50}
m_AnchoredPosition: {x: 50, y: -162.5}
m_SizeDelta: {x: 400, y: 175}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &254276107
@ -8335,10 +8335,10 @@ RectTransform:
- {fileID: 247400896}
m_Father: {fileID: 1042850270}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 50, y: -50}
m_SizeDelta: {x: 400, y: 175}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1707733840
MonoBehaviour:
@ -9817,7 +9817,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &1932206569
RectTransform:
m_ObjectHideFlags: 0
@ -11027,7 +11027,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
m_IsActive: 1
--- !u!114 &2082092898
MonoBehaviour:
m_ObjectHideFlags: 0
@ -15844,6 +15844,10 @@ PrefabInstance:
propertyPath: m_Name
value: Player_New
objectReference: {fileID: 0}
- target: {fileID: 1310793554455274069, guid: 0abcb4ad4184fc645aa8c976260b8a2f, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []

View File

@ -9,17 +9,17 @@ using UnityEngine;
/// </summary>
public class PrizeSystem : MonoBehaviour
{
[SerializeField] AudioClip prizeErrorSound;
private static PrizeSystem Instance { get; set; }
public static string key_starts = "STARTS";
public static string key_startsNul = "STARTSNUL";
public static string key_prizesgiven = "PRIZESGIVEN";
public static string key_prizesgivenNul = "PRIZESGIVENNUL";
private static PrizeSystem instance;
private static Coroutine prizeCoroutine;
private static bool _prizing = false;
public static readonly string key_starts = "STARTS";
public static readonly string key_startsNul = "STARTSNUL";
public static readonly string key_prizesgiven = "PRIZESGIVEN";
public static readonly string key_prizesgivenNul = "PRIZESGIVENNUL";
[SerializeField] AudioClip prizeErrorSound;
//===========================start: PUBLIC API===========================//
/// <summary>
@ -34,7 +34,15 @@ public class PrizeSystem : MonoBehaviour
/// </remarks>
public void Initialize()
{
instance = this;
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
InitCounters();
}
@ -66,8 +74,8 @@ public class PrizeSystem : MonoBehaviour
}
//
// DebugWindow.Message("Выдаю приз");
if (prizeCoroutine != null) instance.StopCoroutine(prizeCoroutine);
prizeCoroutine = instance.StartCoroutine(instance.Prize());
if (prizeCoroutine != null) Instance.StopCoroutine(prizeCoroutine);
prizeCoroutine = Instance.StartCoroutine(Instance.Prize());
}
//===========================end: PUBLIC API===========================//
@ -77,7 +85,7 @@ public class PrizeSystem : MonoBehaviour
_prizing = true;
// DebugWindow.Message("Начинаю выдачу");
float timeout = 10; //сек
const float timeout = 10; //сек
ComWrapper.Send("301");//включаем мотор
float startTime = Time.time;
bool success = false;
@ -104,12 +112,13 @@ public class PrizeSystem : MonoBehaviour
if (!success) AudioManager.Instance.PlaySfx(prizeErrorSound);
}
void IncreasePrizeCounters()
private static void IncreasePrizeCounters()
{
PlayerPrefs.SetInt(key_prizesgiven, PlayerPrefs.GetInt(key_prizesgiven) + 1);
PlayerPrefs.SetInt(key_prizesgivenNul, PlayerPrefs.GetInt(key_prizesgivenNul) + 1);
}
void InitCounters()
private static void InitCounters()
{
if (!PlayerPrefs.HasKey(key_starts)) PlayerPrefs.SetInt(key_starts, 0);
if (!PlayerPrefs.HasKey(key_startsNul)) PlayerPrefs.SetInt(key_startsNul, 0);

View File

@ -28,9 +28,10 @@ namespace RR.Game2D.Core.Managers
// Globals
[Header("Globals")]
private GameObject globals;
[SerializeField] private string globalsPrefabResourcePath = "Prefabs/Global/Globals";
[SerializeField] private string AudioMixerResourcePath = "Audio/Master";
private GameObject globals;
[Header("Пауза")]
public bool IsPaused { get; private set; }
@ -71,7 +72,7 @@ namespace RR.Game2D.Core.Managers
// Singleton
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
private static void Initialize()
{
if (Instance != null) return;
GameObject go = new GameObject(nameof(GameManager));

View File

@ -11,6 +11,7 @@ namespace RR.Common.Managers
private ComWrapper _comWrapper;
private In _in;
private PrizeSystem _prizeSystem;
private PrizeSystemManager _prizeSystemManager;
private PaymentManager _paymentManager;
private ComInputToInputSystemAdapter _adapter;
@ -21,13 +22,15 @@ namespace RR.Common.Managers
_comWrapper = GetComponent<ComWrapper>();
_in = GetComponent<In>();
_prizeSystem = GetComponent<PrizeSystem>();
_prizeSystemManager = GetComponent<PrizeSystemManager>();
_paymentManager = GetComponent<PaymentManager>();
_adapter = GetComponent<ComInputToInputSystemAdapter>();
if (_settingsManager == null) gameObject.AddComponent<SettingsManager>();
if (_comWrapper == null) gameObject.AddComponent<ComWrapper>();
if (_in == null) gameObject.AddComponent<In>();
if (_prizeSystem == null) gameObject.AddComponent<PrizeSystem>();
if (_settingsManager == null) gameObject.AddComponent<SettingsManager>();
if (_prizeSystemManager == null) gameObject.AddComponent<PrizeSystemManager>();
if (_paymentManager == null) gameObject.AddComponent<PaymentManager>();
if (_adapter == null) gameObject.AddComponent<ComInputToInputSystemAdapter>();
@ -43,6 +46,9 @@ namespace RR.Common.Managers
_prizeSystem.Initialize();
GameManager.Logger.Log(name, "PrizeSystem successfully initialized.");
_prizeSystemManager.Initialize();
GameManager.Logger.Log(name, "PrizeSystemManager successfully initialized.");
_paymentManager.Initialize();
GameManager.Logger.Log(name, "PaymentManager successfully initialized.");

View File

@ -111,7 +111,7 @@ namespace RR.Common.Managers
CreditsCount = newCreditsCount;
OnCreditsChanged?.Invoke(CreditsCount);
OnCreditAdded?.Invoke(CreditsCount);
GameManager.Logger?.Log("PaymentManager", $"Кредит добавлен. Всего: {CreditsCount}");
Debug.Log($"Кредит добавлен. Всего: {CreditsCount}", this);
}
/// <summary>
@ -123,7 +123,7 @@ namespace RR.Common.Managers
CreditsCount = newCreditsCount;
OnCreditsChanged?.Invoke(CreditsCount);
OnCreditRemoved?.Invoke(CreditsCount);
GameManager.Logger?.Log("PaymentManager", $"Кредит удален. Осталось: {CreditsCount}");
Debug.Log($"Кредит удален. Осталось: {CreditsCount}", this);
}
private void SetFreePlay()

View File

@ -1,16 +1,85 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using RR.Common.Managers;
using UnityEngine;
using RR.Common.Utility;
using RR.Game2D.Core.Managers;
public class PrizeSystemManager : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
public static PrizeSystemManager Instance { get; private set; }
[Header("Настройки выдачи призов")]
[SerializeField] private bool givePrizes = true;
[SerializeField] private bool enableHopper = false;
[Header("Настройки генерации QR-кодов")]
[SerializeField] private List<QRCodeData> qrCodes = new();
private readonly List<Texture2D> generatedQRCodes = new();
[SerializeField] private string _phoneNumberMask = "+7XXXXXXXXXX";
// [SerializeField] private string telegramLinkPrefix = "https://t.me/";
// [SerializeField] private string whatsappLinkPrefix = "https://wa.me/";
private string _phoneNumber = string.Empty;
public void Initialize()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
Debug.Log(givePrizes
? "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.");
_phoneNumber = SettingsManager.Instance.GetCurrentGameSettings().phoneNumber;
_phoneNumber = PhoneNumberUtils.ValidatePhoneNumber(_phoneNumber);
_phoneNumber = PhoneNumberUtils.ApplyPhoneMask(_phoneNumber, _phoneNumberMask);
GenerateQRCodes();
}
// Update is called once per frame
void Update()
// Если не инициализируется в GameManager, инициализировать тут
// private void Awake()
// {
// Initialize();
// }
public void GivePrize()
{
if (enableHopper)
PrizeSystem.GivePrize();
else
Debug.Log("Hopper is disabled. Prize dispensing skipped.");
}
public Texture2D[] GetQRCodes()
{
return generatedQRCodes.ToArray();
}
private void GenerateQRCodes()
{
// Очищаем предыдущие QR-коды
if (generatedQRCodes.Count > 0) generatedQRCodes.Clear();
foreach (QRCodeData data in qrCodes)
{
if (!data.generateQRCode) continue;
string fullLink = data.GetFullLink(_phoneNumber);
Texture2D qrTexture = QRMaker.MakeTexture(fullLink);
generatedQRCodes.Add(qrTexture);
Debug.Log($"Generated QR Code for link: {fullLink}");
}
}
}

View File

@ -1,4 +1,5 @@
using RR.Common.UI;
using RR.Common.Utility;
using RR.Game2D.Core.Managers;
using UnityEngine;
@ -13,9 +14,10 @@ namespace RR.Common.Behaviour.ScriptableObjects
public float musicVolume;
public float sfxVolume;
// Payment settings
// Payment/prizing settings
public bool isFreeGame;
public bool isGivingPrizes;
public string phoneNumber;
// Player settings
public int startLivesCount;
@ -29,6 +31,7 @@ namespace RR.Common.Behaviour.ScriptableObjects
isFreeGame = false;
isGivingPrizes = true;
phoneNumber = "9123456789";
startLivesCount = 3;
livesIncreasedByCredits = 1;
@ -47,6 +50,8 @@ namespace RR.Common.Behaviour.ScriptableObjects
if (livesIncreasedByCredits < 0)
livesIncreasedByCredits = 0;
phoneNumber = PhoneNumberUtils.ValidatePhoneNumber(phoneNumber);
}
public void ApplySettings()
@ -59,7 +64,6 @@ namespace RR.Common.Behaviour.ScriptableObjects
GameManager.Instance.Audio.SetMusicVolume(musicVolume);
GameManager.Instance.Audio.SetSfxVolume(sfxVolume);
}
}
}
}

View File

@ -17,6 +17,6 @@ MonoBehaviour:
sfxVolume: 1
isFreeGame: 0
isGivingPrizes: 1
phoneNumber: 9187853714
startLivesCount: 3
livesIncreasedByCredits: 1
setLivesToDefaultAfterFirstLose: 1

View File

@ -0,0 +1,56 @@
using RR.Game2D.Core.Managers;
using UnityEngine;
using UnityEngine.UI;
public class QRCodeUIController : MonoBehaviour
{
[SerializeField] private Transform content;
private PrizeSystemManager _prizeSystemManager;
private GameObject[] _qrCodes;
private void Start()
{
_prizeSystemManager = PrizeSystemManager.Instance;
GameManager.Instance.GameWonEvent += AddQRCodes;
}
private void OnDestroy()
{
GameManager.Instance.GameWonEvent -= AddQRCodes;
}
private void AddQRCodes(float _)
{
if (_qrCodes != null) ClearQRCodes();
Texture2D[] qrCodesImages = _prizeSystemManager.GetQRCodes();
_qrCodes = new GameObject[qrCodesImages.Length];
for (int i = 0; i < qrCodesImages.Length; i++)
{
_qrCodes[i] = new GameObject("QRCode_" + i, typeof(RectTransform), typeof(Image));
_qrCodes[i].transform.SetParent(content, false);
Sprite sprite = Sprite.Create(qrCodesImages[i],
new Rect(0, 0, qrCodesImages[i].width,
qrCodesImages[i].height),
new Vector2(0.5f, 0.5f));
_qrCodes[i].GetComponent<Image>().sprite = sprite;
}
}
private void ClearQRCodes()
{
if (_qrCodes == null) return;
foreach (GameObject qrCode in _qrCodes)
{
Destroy(qrCode);
}
_qrCodes = null;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: daf828422caf7c74db507420de242849

View File

@ -0,0 +1,85 @@
using UnityEngine;
using static System.String;
namespace RR.Common.Utility
{
/// <summary>
/// Утилитарный класс для работы с номерами телефонов.
/// Предоставляет методы для валидации и форматирования телефонных номеров.
/// </summary>
public static class PhoneNumberUtils
{
/// <summary>
/// Валидирует номер телефона и возвращает только цифры без кода страны.
/// Поддерживает российские номера, начинающиеся с 7 или 8.
/// </summary>
/// <param name="number">Исходный номер телефона в любом формате (может содержать пробелы, дефисы и другие символы).</param>
/// <returns>
/// Строка из 10 цифр, представляющая номер без кода страны.
/// Возвращает пустую строку, если формат номера неверный.
/// </returns>
/// <remarks>
/// Принимаемые форматы:
/// - 11 цифр, начинающиеся с 7 или 8 (например, 89001234567 или 79001234567)
/// - 10 цифр без кода страны (например, 9001234567)
/// При неверном формате выводится предупреждение в консоль Unity.
/// </remarks>
public static string ValidatePhoneNumber(string number)
{
// Оставляем только цифры
string digitsOnly = System.Text.RegularExpressions.Regex.Replace(number, @"\D", "");
// Проверяем формат
if ((digitsOnly.StartsWith("8") || digitsOnly.StartsWith("7")) && digitsOnly.Length == 11)
{
return digitsOnly[1..];
}
else if (digitsOnly.Length == 10)
{
return digitsOnly;
}
else
{
// Неверный формат, возвращаем номер по умолчанию
Debug.LogWarning($"Неверный формат номера телефона: {number}. Заменяю на номер по умолчанию.");
return Empty;
}
}
/// <summary>
/// Применяет маску форматирования к номеру телефона.
/// </summary>
/// <param name="number">Номер телефона для форматирования.</param>
/// <param name="mask">Маска форматирования. По умолчанию "+7XXXXXXXXXX".</param>
/// <param name="maskPlaceholder">Символ-заполнитель в маске, который будет заменен на цифры. По умолчанию 'X'.</param>
/// <returns>
/// Отформатированный номер телефона согласно маске.
/// Если номер невалиден, возвращается пустая строка.
/// </returns>
/// <remarks>
/// Метод сначала валидирует номер с помощью ValidatePhoneNumber, затем применяет маску.
/// Символы маски, не являющиеся заполнителями, остаются без изменений.
/// </remarks>
/// <example>
/// ApplyPhoneMask("89001234567", "+7 (XXX) XXX-XX-XX") вернет "+7 (900) 123-45-67"
/// </example>
public static string ApplyPhoneMask(string number, string mask = "+7XXXXXXXXXX", char maskPlaceholder = 'X')
{
string digitsOnly = ValidatePhoneNumber(number);
if (IsNullOrEmpty(digitsOnly))
return Empty;
char[] maskedNumber = mask.ToCharArray();
int digitIndex = 0;
for (int i = 0; i < maskedNumber.Length; i++)
{
if (maskedNumber[i] != maskPlaceholder || digitIndex >= digitsOnly.Length) continue;
maskedNumber[i] = digitsOnly[digitIndex];
digitIndex++;
}
return new string(maskedNumber);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9e654e8fac614d7a8b2f5fd06e51792f
timeCreated: 1763113002

View File

@ -0,0 +1,7 @@
namespace RR.Common.Utility
{
public class QRCodeTest
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 22feb19078804d8e83f3739276b1d8c8
timeCreated: 1763126902

View File

@ -0,0 +1,98 @@
using System;
using System.Drawing;
using System.IO;
using QRCoder;
using UnityEngine;
using Color = System.Drawing.Color;
namespace RR.Common.Utility
{
/// <summary>
/// Утилитарный класс для создания QR-кодов в Unity.
/// Предоставляет статические методы для генерации QR-кодов в виде текстур.
/// </summary>
public class QRMaker : MonoBehaviour
{
/// <summary>
/// Создает текстуру QR-кода из переданного текста.
/// </summary>
/// <param name="text">Текст или данные для кодирования в QR-код.</param>
/// <param name="pixelsPerModule">Количество пикселей на один модуль QR-кода. По умолчанию 8.</param>
/// <param name="drawQuietZones">Определяет, нужно ли рисовать тихие зоны (белые поля) вокруг QR-кода. По умолчанию true.</param>
/// <returns>Texture2D с изображением QR-кода, готовая для использования в UI или материалах.</returns>
public static Texture2D MakeTexture(string text, int pixelsPerModule = 8,
bool drawQuietZones = true)
{
QRCodeGenerator gen = new();
QRCoder.QRCodeData data = gen.CreateQrCode(text, QRCodeGenerator.ECCLevel.M);
PngByteQRCode png = new(data);
// Генерим PNG-байты без System.Drawing
byte[] bytes = png.GetGraphic(pixelsPerModule);
Texture2D tex = new(150, 150, TextureFormat.RGBA32, false);
tex.LoadImage(bytes); // раскодирует PNG → RGBA
tex.Apply(false, false);
return tex;
}
// public static Texture2D MakeTexture(string text, Image icon = null, int pixelsPerModule = 8,
// bool drawQuietZones = true)
// {
// QRCodeGenerator gen = new();
// QRCoder.QRCodeData data = gen.CreateQrCode(text, QRCodeGenerator.ECCLevel.M);
// QRCode code = new(data);
// Bitmap bitmapIcon = code.GetGraphic(pixelsPerModule,
// Color.Black,
// Color.White,
// icon == null ? null : new Bitmap(icon),
// 15,
// 10,
// drawQuietZones);
// Texture2D tex = new(150, 150, TextureFormat.RGBA32, false);
// tex.LoadImage(bitmapIcon);
// }
// Сохранить как PNG-файл
public static string SavePng(string text, string filePath, int ppm = 8)
{
QRCodeGenerator gen = new();
QRCoder.QRCodeData data = gen.CreateQrCode(text, QRCodeGenerator.ECCLevel.M);
PngByteQRCode png = new PngByteQRCode(data);
byte[] bytes = png.GetGraphic(ppm, Color.Black, Color.White, true);
File.WriteAllBytes(filePath, bytes);
return filePath;
}
}
/// <summary>
/// Структура данных для конфигурации генерации QR-кода.
/// Содержит параметры для создания полных ссылок с префиксом и суффиксом.
/// </summary>
[Serializable]
public struct QRCodeData
{
/// <summary>
/// Флаг, определяющий, нужно ли генерировать QR-код.
/// </summary>
public bool generateQRCode;
/// <summary>
/// Префикс ссылки (например, "https://example.com/").
/// </summary>
public string linkPrefix;
/// <summary>
/// Суффикс, добавляемый в качестве параметра запроса.
/// </summary>
public string suffix;
/// <summary>
/// Формирует полную ссылку, объединяя префикс, данные и суффикс.
/// </summary>
/// <param name="data">Основные данные для включения в ссылку.</param>
/// <returns>Полная ссылка в формате: {linkPrefix}{data}?text={suffix}</returns>
public string GetFullLink(string data) => $"{linkPrefix}{data}?text={ProcessSuffix(suffix)}";
private string ProcessSuffix(string rawSuffix) => Uri.EscapeDataString(rawSuffix);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6cd7d86305114f21b64b8ef43acbdf7f
timeCreated: 1763113697