commit
33b2ed843c
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -266,18 +266,18 @@
|
||||
"path": "<Gamepad>/dpad/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Gamepad",
|
||||
"groups": "",
|
||||
"action": "Jump",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "c2638292-b3cd-4923-95b0-184c5c5fd5f3",
|
||||
"path": "<XRController>/secondaryButton",
|
||||
"id": "4b902c05-3ac4-47e2-b251-5772c1d59859",
|
||||
"path": "<Gamepad>/rightShoulder",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "XR",
|
||||
"groups": "Gamepad",
|
||||
"action": "Jump",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
@ -296,10 +296,10 @@
|
||||
{
|
||||
"name": "",
|
||||
"id": "b68728dc-63a0-476c-96f9-9f2e24f2ca57",
|
||||
"path": "<Gamepad>/buttonEast",
|
||||
"path": "<Gamepad>/rightShoulder",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Gamepad",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Interact",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
@ -430,6 +430,15 @@
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "AddCredit",
|
||||
"type": "Button",
|
||||
"id": "dc985cb4-1124-4d19-b5da-8d3200cec195",
|
||||
"expectedControlType": "",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
@ -740,6 +749,50 @@
|
||||
"action": "Secret",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "e9ac3494-4f47-41d5-8024-94294f144f2a",
|
||||
"path": "<Keyboard>/f6",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "AddCredit",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "One Modifier",
|
||||
"id": "8e7b642e-3796-45cc-8dc1-c5c8eb6649ba",
|
||||
"path": "OneModifier",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "AddCredit",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "modifier",
|
||||
"id": "00cf67d9-ef62-4c49-a9ca-53afa7255cc0",
|
||||
"path": "<Gamepad>/dpad/left",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "AddCredit",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "binding",
|
||||
"id": "43c6b038-fbb8-4585-853b-f43bc8cd1273",
|
||||
"path": "<Gamepad>/rightShoulder",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "AddCredit",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
8
Assets/Resources/Audio.meta
Normal file
8
Assets/Resources/Audio.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24de3c5a0031e584ab71384b5d77244e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
143
Assets/Resources/Audio/Master.mixer
Normal file
143
Assets/Resources/Audio/Master.mixer
Normal file
@ -0,0 +1,143 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!244 &-3902009871899815571
|
||||
AudioMixerEffectController:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_EffectID: 889ce81b86755c040882cfc988492262
|
||||
m_EffectName: Attenuation
|
||||
m_MixLevel: 1db247ecc96499242b7ed19e5433c799
|
||||
m_Parameters: []
|
||||
m_SendTarget: {fileID: 0}
|
||||
m_EnableWetMix: 0
|
||||
m_Bypass: 0
|
||||
--- !u!243 &-496499518004457780
|
||||
AudioMixerGroupController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: SFX
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_GroupID: 3be00bcd5607507438c82ef262228106
|
||||
m_Children: []
|
||||
m_Volume: a3b50d40b0003814497bd8852988a7b2
|
||||
m_Pitch: d452543f92d9d474aab2d808f3854055
|
||||
m_Send: 00000000000000000000000000000000
|
||||
m_Effects:
|
||||
- {fileID: -3902009871899815571}
|
||||
m_UserColorIndex: 4
|
||||
m_Mute: 0
|
||||
m_Solo: 0
|
||||
m_BypassEffects: 0
|
||||
--- !u!244 &-271018402943269990
|
||||
AudioMixerEffectController:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_EffectID: 862e7507968483343ad2a663626ec77d
|
||||
m_EffectName: Attenuation
|
||||
m_MixLevel: 7920a99416b17464c85eded5bc8ca102
|
||||
m_Parameters: []
|
||||
m_SendTarget: {fileID: 0}
|
||||
m_EnableWetMix: 0
|
||||
m_Bypass: 0
|
||||
--- !u!241 &24100000
|
||||
AudioMixerController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Master
|
||||
m_OutputGroup: {fileID: 0}
|
||||
m_MasterGroup: {fileID: 24300002}
|
||||
m_Snapshots:
|
||||
- {fileID: 24500006}
|
||||
m_StartSnapshot: {fileID: 24500006}
|
||||
m_SuspendThreshold: -80
|
||||
m_EnableSuspend: 1
|
||||
m_UpdateMode: 0
|
||||
m_ExposedParameters:
|
||||
- guid: aeea8e463d035974b87c6895d9e3fdba
|
||||
name: MusicVolume
|
||||
- guid: a3b50d40b0003814497bd8852988a7b2
|
||||
name: SFXVolume
|
||||
m_AudioMixerGroupViews:
|
||||
- guids:
|
||||
- 2a3a16fafd41c1748838585b0e219678
|
||||
- 237f6931e4e5ae94d9af212cfcc14c8d
|
||||
- 3be00bcd5607507438c82ef262228106
|
||||
name: View
|
||||
m_CurrentViewIndex: 0
|
||||
m_TargetSnapshot: {fileID: 24500006}
|
||||
--- !u!243 &24300002
|
||||
AudioMixerGroupController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Master
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_GroupID: 2a3a16fafd41c1748838585b0e219678
|
||||
m_Children:
|
||||
- {fileID: 3793028766014410084}
|
||||
- {fileID: -496499518004457780}
|
||||
m_Volume: cdcb200ffbb7c1f4ab3ff6316084de72
|
||||
m_Pitch: 8672ed8ec37560243bb7aaa86c6081d6
|
||||
m_Send: 00000000000000000000000000000000
|
||||
m_Effects:
|
||||
- {fileID: 24400004}
|
||||
m_UserColorIndex: 0
|
||||
m_Mute: 0
|
||||
m_Solo: 0
|
||||
m_BypassEffects: 0
|
||||
--- !u!244 &24400004
|
||||
AudioMixerEffectController:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_EffectID: 48a6e9f98b14f6644bd97ee178ee34dd
|
||||
m_EffectName: Attenuation
|
||||
m_MixLevel: f36758954a86b804cadaf2d8c7e1c3d8
|
||||
m_Parameters: []
|
||||
m_SendTarget: {fileID: 0}
|
||||
m_EnableWetMix: 0
|
||||
m_Bypass: 0
|
||||
--- !u!245 &24500006
|
||||
AudioMixerSnapshotController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Snapshot
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_SnapshotID: 5af2efb0fc4b2af4cb11efcd2f7e5b14
|
||||
m_FloatValues:
|
||||
cdcb200ffbb7c1f4ab3ff6316084de72: 0
|
||||
m_TransitionOverrides: {}
|
||||
--- !u!243 &3793028766014410084
|
||||
AudioMixerGroupController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Music
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_GroupID: 237f6931e4e5ae94d9af212cfcc14c8d
|
||||
m_Children: []
|
||||
m_Volume: aeea8e463d035974b87c6895d9e3fdba
|
||||
m_Pitch: 74392233bc98220459e7ad1995ce3d17
|
||||
m_Send: 00000000000000000000000000000000
|
||||
m_Effects:
|
||||
- {fileID: -271018402943269990}
|
||||
m_UserColorIndex: 1
|
||||
m_Mute: 0
|
||||
m_Solo: 0
|
||||
m_BypassEffects: 0
|
||||
8
Assets/Resources/Audio/Master.mixer.meta
Normal file
8
Assets/Resources/Audio/Master.mixer.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f44de6a220ee7d4b8179285074ead2d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 24100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -14,6 +14,7 @@ GameObject:
|
||||
- component: {fileID: 4577859410310799261}
|
||||
- component: {fileID: -6245296037342915518}
|
||||
- component: {fileID: 6566017136146807207}
|
||||
- component: {fileID: 6824661392768214298}
|
||||
- component: {fileID: -3912971501865491093}
|
||||
m_Layer: 0
|
||||
m_Name: Globals
|
||||
@ -102,6 +103,30 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::PrizeSystem
|
||||
prizeErrorSound: {fileID: 0}
|
||||
--- !u!114 &6824661392768214298
|
||||
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: 60e317a2a046c5644ae8e9281284c244, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Managers.PaymentManager
|
||||
saveMoneyBetweenSessions: 0
|
||||
enableFreePlay: 0
|
||||
addCreditAction: {fileID: -2889265436170138120, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
OnCreditsChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
OnCreditAdded:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
OnCreditRemoved:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &-3912971501865491093
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -135,10 +160,14 @@ MonoBehaviour:
|
||||
dpadDirection: 1
|
||||
- pinIndex: 4
|
||||
target: 0
|
||||
button: 5
|
||||
button: 11
|
||||
dpadDirection: 0
|
||||
- pinIndex: 10
|
||||
target: 0
|
||||
button: 10
|
||||
dpadDirection: 1
|
||||
- pinIndex: 12
|
||||
target: 0
|
||||
button: 4
|
||||
dpadDirection: 1
|
||||
logCreateDevice: 1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -13,8 +13,8 @@ using UnityEngine.UI;
|
||||
public class ComWrapper : MonoBehaviour
|
||||
{
|
||||
//Настраиваем из инспектора
|
||||
[SerializeField] bool saveMoneyBetweenSessions;
|
||||
[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||||
[SerializeField] private bool saveMoneyBetweenSessions;
|
||||
[SerializeField] private bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||||
|
||||
|
||||
public static UnityEvent<int> OnCreditAdded;
|
||||
@ -165,7 +165,7 @@ public class ComWrapper : MonoBehaviour
|
||||
// if (SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл")
|
||||
// {
|
||||
// //Счетчики пробега
|
||||
PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||||
// PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||||
// PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||||
//
|
||||
// return true;
|
||||
@ -270,7 +270,7 @@ public class ComWrapper : MonoBehaviour
|
||||
|
||||
private static void ClosePort()
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper","Закрываю все порты");
|
||||
Debug.Log("Закрываю все порты");
|
||||
if (h > 0)
|
||||
{
|
||||
ComNativeWrapper.Serial_Close(h);
|
||||
@ -286,7 +286,7 @@ public class ComWrapper : MonoBehaviour
|
||||
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
|
||||
if (read > 0)
|
||||
{
|
||||
var s = Encoding.UTF8.GetString(buf, 0, read);
|
||||
string s = Encoding.UTF8.GetString(buf, 0, read);
|
||||
//GameManager.Logger.Log($"ReadLine len={read} data=[{s}]");
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -13,23 +13,25 @@ namespace RR.Common.Managers
|
||||
// ==== Singleton ====
|
||||
public static AudioManager Instance { get; private set; }
|
||||
|
||||
[Header("Music")] [SerializeField] private AudioSource musicSource; // child: MusicSource
|
||||
[Header("Music")]
|
||||
[SerializeField] private AudioSource musicSource; // child: MusicSource
|
||||
[SerializeField] private AudioSource tempMusicSource; // child: TempMusicSource
|
||||
|
||||
[Tooltip("Группа микшера для музыки (необязательно)")] [SerializeField]
|
||||
private AudioMixerGroup musicMixerGroup;
|
||||
[Tooltip("Группа микшера для музыки (необязательно)")]
|
||||
[SerializeField] private AudioMixerGroup musicMixerGroup;
|
||||
|
||||
[Tooltip("Целевая громкость музыки (после фейдов)")] [Range(0f, 1.5f)]
|
||||
public float musicTargetVolume = 0.2f;
|
||||
[Tooltip("Целевая громкость музыки (после фейдов)")]
|
||||
[Range(0f, 1.5f)] public float musicTargetVolume = 1f;
|
||||
|
||||
[Tooltip("Длительность фейда по умолчанию (сек)")] [SerializeField]
|
||||
private float defaultFadeDuration = 0.8f;
|
||||
[Tooltip("Длительность фейда по умолчанию (сек)")]
|
||||
[SerializeField] private float defaultFadeDuration = 0.8f;
|
||||
|
||||
[Header("SFX Pool")] [SerializeField] private int sfxPoolSize = 16;
|
||||
[Header("SFX Pool")]
|
||||
[SerializeField] private int sfxPoolSize = 16;
|
||||
[SerializeField] private Transform sfxPoolParent; // child: SFX Pool
|
||||
|
||||
[Tooltip("Группа микшера для SFX (необязательно)")] [SerializeField]
|
||||
private AudioMixerGroup sfxMixerGroup;
|
||||
[Tooltip("Группа микшера для SFX (необязательно)")]
|
||||
[SerializeField] private AudioMixerGroup sfxMixerGroup;
|
||||
|
||||
[Range(0f, 1.5f)] public float sfxDefaultVolume = 1f;
|
||||
|
||||
@ -44,7 +46,7 @@ namespace RR.Common.Managers
|
||||
|
||||
#region Unity Lifetime
|
||||
|
||||
public void Initialize()
|
||||
public void Initialize(AudioMixer mixer = null)
|
||||
{
|
||||
// Singleton init
|
||||
if (Instance != null && Instance != this)
|
||||
@ -56,6 +58,14 @@ namespace RR.Common.Managers
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
if (mixer != null)
|
||||
{
|
||||
if (musicMixerGroup == null)
|
||||
musicMixerGroup = mixer.FindMatchingGroups("Music")[0];
|
||||
if (sfxMixerGroup == null)
|
||||
sfxMixerGroup = mixer.FindMatchingGroups("SFX")[0];
|
||||
}
|
||||
|
||||
EnsureMusicSources();
|
||||
EnsureSfxPool();
|
||||
|
||||
@ -69,13 +79,14 @@ namespace RR.Common.Managers
|
||||
{
|
||||
if (musicSource == null)
|
||||
{
|
||||
var t = transform.Find("MusicSource");
|
||||
Transform t = transform.Find("MusicSource");
|
||||
if (t != null) musicSource = t.GetComponent<AudioSource>();
|
||||
|
||||
}
|
||||
|
||||
if (tempMusicSource == null)
|
||||
{
|
||||
var t = transform.Find("TempMusicSource");
|
||||
Transform t = transform.Find("TempMusicSource");
|
||||
if (t != null) tempMusicSource = t.GetComponent<AudioSource>();
|
||||
}
|
||||
|
||||
@ -167,12 +178,16 @@ namespace RR.Common.Managers
|
||||
|
||||
public void SetMusicVolume(float newVolume)
|
||||
{
|
||||
// TODO!!!!!
|
||||
float vol = Mathf.Clamp01(newVolume);
|
||||
vol = DbFrom01_Log(vol);
|
||||
musicMixerGroup.audioMixer.SetFloat("MusicVolume", vol);
|
||||
}
|
||||
|
||||
public void SetSfxVolume(float newVolume)
|
||||
{
|
||||
// TODO!!!!!
|
||||
float vol = Mathf.Clamp01(newVolume);
|
||||
vol = DbFrom01_Log(vol);
|
||||
sfxMixerGroup.audioMixer.SetFloat("SFXVolume", vol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -392,5 +407,13 @@ namespace RR.Common.Managers
|
||||
src.Play();
|
||||
}
|
||||
|
||||
float DbFrom01_Log(float t, float minDb = -80f, float maxDb = 20f)
|
||||
{
|
||||
t = Mathf.Clamp01(t);
|
||||
float minAmp = Mathf.Pow(10f, minDb / 20f); // 10^(-80/20) = 0.0001
|
||||
float maxAmp = Mathf.Pow(10f, maxDb / 20f); // 10^(+20/20) = 10
|
||||
float amp = Mathf.Lerp(minAmp, maxAmp, t); // линейная интерполяция по усилению
|
||||
return 20f * Mathf.Log10(Mathf.Max(1e-6f, amp));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using RR.Common.Behaviour.ScriptableObjects;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
@ -7,6 +8,7 @@ using RR.Common.Managers;
|
||||
using RR.Common.Utility;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Entities;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
namespace RR.Game2D.Core.Managers
|
||||
{
|
||||
@ -26,29 +28,30 @@ namespace RR.Game2D.Core.Managers
|
||||
public AudioManager Audio {get; private set;}
|
||||
|
||||
// Payment control for game
|
||||
|
||||
public PaymentManager Payment {get; private set;}
|
||||
|
||||
// Globals
|
||||
[Header("Globals")]
|
||||
private GameObject globals;
|
||||
[SerializeField] private string globalsPrefabResourcePath = "Prefabs/Global/Globals";
|
||||
[SerializeField] private string AudioMixerResourcePath = "Audio/Master";
|
||||
|
||||
[Header("Пауза")]
|
||||
public bool IsPaused { get; private set; }
|
||||
|
||||
[Header("Базовые настройки игры")]
|
||||
[SerializeField] private int maxLives = 3;
|
||||
public int StartingLives { get; private set; } = 3;
|
||||
public int MaxLives { get; private set; } = 3;
|
||||
public int LivesIncrement { get; private set; } = 0;
|
||||
|
||||
[Header("Информация для игры")]
|
||||
public int World { get; private set; } = 1;
|
||||
public int Stage { get; private set; } = 1;
|
||||
public int StagesByWorld { get; private set; } = 3;
|
||||
private int StagesByWorld { get; set; } = 3;
|
||||
public int Lives { get; set; } = 3;
|
||||
public int Coins { get; private set; } = 0;
|
||||
|
||||
[HideInInspector] public bool ResetLives { get; set; } = true;
|
||||
private float gameStartTime;
|
||||
private float _gameStartTime;
|
||||
|
||||
[Header("Игрок")]
|
||||
[SerializeField] private GameObject playerPrefab;
|
||||
@ -67,16 +70,16 @@ namespace RR.Game2D.Core.Managers
|
||||
public event Action<float> GameOverEvent; // Когда игрок проигрывает
|
||||
public event Action<float> GameWonEvent; // Когда игрок выигрывает
|
||||
|
||||
#region Unity Lifetime
|
||||
|
||||
// Singleton
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Bootstrap()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
GameObject go = new GameObject(nameof(GameManager));
|
||||
Instance = go.AddComponent<GameManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
if (Instance != null) return;
|
||||
GameObject go = new GameObject(nameof(GameManager));
|
||||
Instance = go.AddComponent<GameManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
@ -101,12 +104,14 @@ namespace RR.Game2D.Core.Managers
|
||||
}
|
||||
|
||||
// Initialize Audio control
|
||||
AudioMixer audioMixer = Resources.Load<AudioMixer>(AudioMixerResourcePath);
|
||||
if (Audio == null)
|
||||
{
|
||||
Audio = gameObject.AddComponent<AudioManager>();
|
||||
Audio.Initialize();
|
||||
Audio.Initialize(audioMixer);
|
||||
}
|
||||
|
||||
// Initialize Global Scripts from prefab
|
||||
if (globals == null)
|
||||
{
|
||||
GameObject prefab = Resources.Load<GameObject>(globalsPrefabResourcePath);
|
||||
@ -117,21 +122,27 @@ namespace RR.Game2D.Core.Managers
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Settings Manager from Globals
|
||||
if (Settings == null)
|
||||
{
|
||||
Settings = globals.GetComponent<SettingsManager>();
|
||||
}
|
||||
|
||||
Settings.OnSettingsLoaded += OnSettingsChanged;
|
||||
Settings.OnSettingsSaved += OnSettingsChanged;
|
||||
|
||||
// Initialize Payment control
|
||||
if (Payment == null)
|
||||
{
|
||||
Payment = gameObject.AddComponent<PaymentManager>();
|
||||
Payment.Initialize();
|
||||
Payment = globals.GetComponent<PaymentManager>();
|
||||
}
|
||||
|
||||
gameStartTime = Time.unscaledTime;
|
||||
Logger.Log(gameObject.name,$"Game start time = {gameStartTime}");
|
||||
Payment.OnCreditAdded.AddListener(OnCreditAdded);
|
||||
|
||||
_gameStartTime = Time.unscaledTime;
|
||||
|
||||
MaxLives = StartingLives;
|
||||
|
||||
if (SceneManager.GetActiveScene().name != "MainMenu") _shouldSpawnPlayer = true;
|
||||
}
|
||||
|
||||
@ -141,17 +152,6 @@ namespace RR.Game2D.Core.Managers
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
//private void Start()
|
||||
//{
|
||||
// //Application.targetFrameRate = 60;
|
||||
// //LoadScene("MainMenu", false);
|
||||
// //NewGame();
|
||||
// if (SceneManager.GetActiveScene().name != "MainMenu")
|
||||
// {
|
||||
// TrySpawnPlayerForActiveScene();
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск префаба игрока.
|
||||
/// Если не найдет префаб из инспектора, ищет в "Resources"
|
||||
@ -173,6 +173,71 @@ namespace RR.Game2D.Core.Managers
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Payment Event Handlers
|
||||
|
||||
private void OnCreditAdded(int newCount)
|
||||
{
|
||||
if (SceneManager.GetActiveScene().name == "MainMenu")
|
||||
{
|
||||
if (newCount > 1)
|
||||
{
|
||||
MaxLives += LivesIncrement;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Lives += LivesIncrement;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
}
|
||||
Debug.Log($"Max Lives: {MaxLives}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Settings Event Handlers
|
||||
|
||||
private void OnSettingsChanged()
|
||||
{
|
||||
GameSettings newSettings = Settings.GetCurrentGameSettings();
|
||||
int livesDiff = MaxLives - StartingLives;
|
||||
StartingLives = newSettings.startLivesCount;
|
||||
MaxLives = StartingLives + livesDiff;
|
||||
LivesIncrement = newSettings.livesIncreasedByCredits;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pause
|
||||
|
||||
/// <summary>
|
||||
/// Метод паузы игры.
|
||||
/// Ставит на паузу все SFX и саму игру
|
||||
/// </summary>
|
||||
/// <param name="pause">Ставить ли на паузу</param>
|
||||
public void SetPause(bool pause)
|
||||
{
|
||||
if (IsPaused == pause) return;
|
||||
IsPaused = pause;
|
||||
|
||||
Time.timeScale = pause ? 0f : 1f;
|
||||
Audio.SmoothPauseMusic(pause);
|
||||
|
||||
PauseStateChanged?.Invoke(IsPaused);
|
||||
}
|
||||
|
||||
// Можно заменить на другое поведение
|
||||
/// <summary>
|
||||
/// Метод переключателя паузы.
|
||||
/// </summary>
|
||||
public void TogglePause() => SetPause(!IsPaused);
|
||||
// --
|
||||
|
||||
#endregion
|
||||
|
||||
#region Levels and Scene management
|
||||
|
||||
/// <summary>
|
||||
/// Начало новой игры.
|
||||
@ -181,23 +246,25 @@ namespace RR.Game2D.Core.Managers
|
||||
/// </summary>
|
||||
public void NewGame()
|
||||
{
|
||||
Lives = maxLives;
|
||||
Lives = MaxLives;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
Coins = 0;
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
gameStartTime = Time.time;
|
||||
_gameStartTime = Time.time;
|
||||
Logger.Log(gameObject.name,$"Game start time = {_gameStartTime}");
|
||||
|
||||
if (!Payment.IsFreePlayEnabled()) Payment.ProcessPayment();
|
||||
|
||||
LoadLevel(1, 1);
|
||||
}
|
||||
|
||||
public void ContinueGame()
|
||||
{
|
||||
Lives = maxLives + 1;
|
||||
Lives = MaxLives;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
Coins = 0;
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
RespawnPlayer();
|
||||
// gameStartTime = Time.time;
|
||||
// LoadLevel(World, Stage);
|
||||
}
|
||||
|
||||
// Можно заменить на другое поведение
|
||||
@ -207,23 +274,50 @@ namespace RR.Game2D.Core.Managers
|
||||
/// </summary>
|
||||
private void GameOver() // => LoadScene("MainMenu", false);
|
||||
{
|
||||
// LoadScene("MainMenu", false);
|
||||
// Lives = maxLives;
|
||||
// LivesChanged?.Invoke(Lives);
|
||||
float totalTime = Time.unscaledTime - gameStartTime;
|
||||
float totalTime = Time.unscaledTime - _gameStartTime;
|
||||
GameOverEvent?.Invoke(totalTime);
|
||||
}
|
||||
// --
|
||||
|
||||
private void GameWin() // => LoadScene("MainMenu", false);
|
||||
{
|
||||
// LoadScene("MainMenu", false);
|
||||
// Lives = maxLives;
|
||||
// LivesChanged?.Invoke(Lives);
|
||||
float totalTime = Time.unscaledTime - gameStartTime;
|
||||
float totalTime = Time.unscaledTime - _gameStartTime;
|
||||
GameWonEvent?.Invoke(totalTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод переход на следующий уровень.
|
||||
/// Может загружать уровни и миры в прямой зависимости от
|
||||
/// Количества уровней на мир(StagesByWorld)
|
||||
/// </summary>
|
||||
public void NextLevel()
|
||||
{
|
||||
if (Stage >= StagesByWorld)
|
||||
{
|
||||
World++;
|
||||
Stage = 1;
|
||||
}
|
||||
else Stage++;
|
||||
LoadLevel(World, Stage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод сброса уровня.
|
||||
/// При сбросе также сбрасывает монеты и отнимает жизни
|
||||
/// </summary>
|
||||
[Obsolete("Используйте RespawnPlayer() для респавна игрока без сброса уровня и жизней.")]
|
||||
public void ResetLevel()
|
||||
{
|
||||
Lives--;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
Coins = 0;
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
|
||||
//print($"Lives: {lives}");
|
||||
if (Lives > 0) LoadLevel(World, Stage);
|
||||
else GameOver();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Загрузка уровня по данным.
|
||||
/// Загружает уровень по номерам мира и уровня
|
||||
@ -242,10 +336,6 @@ namespace RR.Game2D.Core.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
//NewGame();
|
||||
// Logger.Log("WIN");
|
||||
// LoadScene("MainMenu", false);
|
||||
|
||||
// Можно заменить на другое поведение
|
||||
|
||||
GameWin();
|
||||
@ -275,7 +365,32 @@ namespace RR.Game2D.Core.Managers
|
||||
|
||||
else _shouldSpawnPlayer = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод проверки сцены на существование в настройках билда
|
||||
/// </summary>
|
||||
/// <param name="sceneName">Строковое название сцены</param>
|
||||
/// <returns></returns>
|
||||
private static bool SceneInBuildSettingsExists(string sceneName)
|
||||
{
|
||||
var count = SceneManager.sceneCountInBuildSettings;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var path = SceneUtility.GetScenePathByBuildIndex(i);
|
||||
if (Path.GetFileNameWithoutExtension(path) == sceneName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод обработки события загрузки сцены.
|
||||
/// При загрузке сцены проверяет её название на наличие "-" (уровень формата "1-1")
|
||||
/// Если находит, спавнит игрока
|
||||
/// Также обновляет номера мира и уровня
|
||||
/// </summary>
|
||||
/// <param name="scene">Сцена</param>
|
||||
/// <param name="mode">Режим загрузки сцены</param>
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
string currentSceneName = SceneManager.GetActiveScene().name;
|
||||
@ -289,6 +404,10 @@ namespace RR.Game2D.Core.Managers
|
||||
Stage = Convert.ToInt32(levelInfo[1]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Player management
|
||||
|
||||
/// <summary>
|
||||
/// Метод для спавна игрока.
|
||||
/// Ищет первый спавнпоинт, спавнит игрока на его позиции.
|
||||
@ -313,7 +432,11 @@ namespace RR.Game2D.Core.Managers
|
||||
PlayerSpawned?.Invoke(PlayerObj);
|
||||
}
|
||||
|
||||
// Новый метод: респавн игрока без перезагрузки сцены
|
||||
/// <summary>
|
||||
/// Метод респавна игрока.
|
||||
/// Отнимает жизнь, если жизней не осталось — вызывает GameOver()
|
||||
/// Спавнит игрока на активном чекпоинте или на дефолтной позиции
|
||||
/// </summary>
|
||||
public void RespawnPlayer()
|
||||
{
|
||||
// отнимем жизнь (оставляем текущее поведение); монеты не сбрасываем
|
||||
@ -353,7 +476,7 @@ namespace RR.Game2D.Core.Managers
|
||||
if (PlayerObj == null)
|
||||
{
|
||||
// Создадим нового игрока, если объект был уничтожен
|
||||
var prefab = ResolvePlayerPrefab();
|
||||
GameObject prefab = ResolvePlayerPrefab();
|
||||
if (prefab == null) return;
|
||||
PlayerObj = Instantiate(prefab, respawnPos, Quaternion.identity);
|
||||
SceneManager.MoveGameObjectToScene(PlayerObj, SceneManager.GetActiveScene());
|
||||
@ -362,7 +485,7 @@ namespace RR.Game2D.Core.Managers
|
||||
else
|
||||
{
|
||||
// Остановим движение и телепортируем
|
||||
var rb = PlayerObj.GetComponent<Rigidbody2D>();
|
||||
Rigidbody2D rb = PlayerObj.GetComponent<Rigidbody2D>();
|
||||
if (rb != null)
|
||||
{
|
||||
rb.linearVelocity = Vector2.zero;
|
||||
@ -373,56 +496,26 @@ namespace RR.Game2D.Core.Managers
|
||||
}
|
||||
|
||||
// Повернём в нужную сторону (по X)
|
||||
if (PlayerObj != null)
|
||||
if (PlayerObj == null) return;
|
||||
if (respawnForward.x != 0)
|
||||
{
|
||||
if (respawnForward.x != 0)
|
||||
{
|
||||
PlayerObj.transform.localEulerAngles = respawnForward.x >= 0
|
||||
? Vector3.zero
|
||||
: new Vector3(0, 180f, 0);
|
||||
}
|
||||
|
||||
// Восстановим здоровье
|
||||
var hp = PlayerObj.GetComponent<Health>();
|
||||
if (hp != null)
|
||||
hp.ResetHealth();
|
||||
PlayerObj.transform.localEulerAngles = respawnForward.x >= 0
|
||||
? Vector3.zero
|
||||
: new Vector3(0, 180f, 0);
|
||||
}
|
||||
|
||||
// Восстановим здоровье
|
||||
Health playerHealth = PlayerObj.GetComponent<Health>();
|
||||
if (playerHealth != null)
|
||||
playerHealth.ResetHealth();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод переход на следующий уровень.
|
||||
/// Может загружать уровни и миры в прямой зависимости от
|
||||
/// Количества уровней на мир(StagesByWorld)
|
||||
/// </summary>
|
||||
public void NextLevel()
|
||||
{
|
||||
if (Stage >= StagesByWorld)
|
||||
{
|
||||
World++;
|
||||
Stage = 1;
|
||||
}
|
||||
else Stage++;
|
||||
LoadLevel(World, Stage);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Coins and Lives management
|
||||
|
||||
/// <summary>
|
||||
/// Метод сброса уровня.
|
||||
/// При сбросе также сбрасывает монеты и отнимает жизни
|
||||
/// </summary>
|
||||
public void ResetLevel()
|
||||
{
|
||||
Lives--;
|
||||
LivesChanged?.Invoke(Lives);
|
||||
Coins = 0;
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
|
||||
//print($"Lives: {lives}");
|
||||
if (Lives > 0) LoadLevel(World, Stage);
|
||||
else GameOver();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод добавления монеты
|
||||
/// Метод добавления внутриигровой монеты
|
||||
/// </summary>
|
||||
public void AddCoin(int count = 1)
|
||||
{
|
||||
@ -437,29 +530,6 @@ namespace RR.Game2D.Core.Managers
|
||||
CoinsChanged?.Invoke(Coins);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод паузы игры.
|
||||
/// Ставит на паузу все SFX и саму игру
|
||||
/// </summary>
|
||||
/// <param name="pause">Ставить ли на паузу</param>
|
||||
public void SetPause(bool pause)
|
||||
{
|
||||
if (IsPaused == pause) return;
|
||||
IsPaused = pause;
|
||||
|
||||
Time.timeScale = pause ? 0f : 1f;
|
||||
Audio.SmoothPauseMusic(pause);
|
||||
|
||||
PauseStateChanged?.Invoke(IsPaused);
|
||||
}
|
||||
|
||||
// Можно заменить на другое поведение
|
||||
/// <summary>
|
||||
/// Метод переключателя паузы.
|
||||
/// </summary>
|
||||
public void TogglePause() => SetPause(!IsPaused);
|
||||
// --
|
||||
|
||||
// Можно заменить на другое поведение
|
||||
/// <summary>
|
||||
/// Метод для добавления жизни.
|
||||
@ -471,21 +541,6 @@ namespace RR.Game2D.Core.Managers
|
||||
}
|
||||
// --
|
||||
|
||||
/// <summary>
|
||||
/// Метод проверки сцены на существование в настройках билда
|
||||
/// </summary>
|
||||
/// <param name="sceneName">Строковое название сцены</param>
|
||||
/// <returns></returns>
|
||||
private static bool SceneInBuildSettingsExists(string sceneName)
|
||||
{
|
||||
var count = SceneManager.sceneCountInBuildSettings;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var path = SceneUtility.GetScenePathByBuildIndex(i);
|
||||
if (Path.GetFileNameWithoutExtension(path) == sceneName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cbfc8b1557f401886b9e62505e9e6a6
|
||||
timeCreated: 1762846161
|
||||
@ -11,6 +11,7 @@ namespace RR.Common.Managers
|
||||
private ComWrapper _comWrapper;
|
||||
private In _in;
|
||||
private PrizeSystem _prizeSystem;
|
||||
private PaymentManager _paymentManager;
|
||||
private ComInputToInputSystemAdapter _adapter;
|
||||
|
||||
private void Awake()
|
||||
@ -20,12 +21,14 @@ namespace RR.Common.Managers
|
||||
_comWrapper = GetComponent<ComWrapper>();
|
||||
_in = GetComponent<In>();
|
||||
_prizeSystem = GetComponent<PrizeSystem>();
|
||||
_paymentManager = GetComponent<PaymentManager>();
|
||||
_adapter = GetComponent<ComInputToInputSystemAdapter>();
|
||||
|
||||
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 (_paymentManager == null) gameObject.AddComponent<PaymentManager>();
|
||||
if (_adapter == null) gameObject.AddComponent<ComInputToInputSystemAdapter>();
|
||||
|
||||
_settingsManager.Initialize();
|
||||
@ -40,6 +43,9 @@ namespace RR.Common.Managers
|
||||
_prizeSystem.Initialize();
|
||||
GameManager.Logger.Log(name, "PrizeSystem successfully initialized.");
|
||||
|
||||
_paymentManager.Initialize();
|
||||
GameManager.Logger.Log(name, "PaymentManager successfully initialized.");
|
||||
|
||||
_adapter.Initialize();
|
||||
GameManager.Logger.Log(name, "InputAdapter successfully initialized.");
|
||||
}
|
||||
|
||||
@ -1,12 +1,34 @@
|
||||
using RR.Common.Behaviour.ScriptableObjects;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace RR.Common.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Менеджер системы оплаты. Управляет кредитами и интегрируется с ComWrapper.
|
||||
/// </summary>
|
||||
public class PaymentManager : MonoBehaviour
|
||||
{
|
||||
public static PaymentManager Instance { get; private set; }
|
||||
|
||||
public int CreditsCount;
|
||||
[Header("Настройки")]
|
||||
[SerializeField] private bool saveMoneyBetweenSessions = false;
|
||||
[SerializeField] private bool enableFreePlay = false;
|
||||
|
||||
[Header("Управление")]
|
||||
[SerializeField] private InputActionReference addCreditAction;
|
||||
|
||||
[Header("Состояние")]
|
||||
public int CreditsCount { get; private set; }
|
||||
|
||||
// События для уведомления других систем
|
||||
public UnityEvent<int> OnCreditsChanged;
|
||||
public UnityEvent<int> OnCreditAdded;
|
||||
public UnityEvent<int> OnCreditRemoved;
|
||||
|
||||
#region Unity Lifetime
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
@ -18,21 +40,177 @@ namespace RR.Common.Managers
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
CreditsCount = 0;
|
||||
|
||||
ComWrapper.OnCreditAdded.AddListener(UpdateCredits);
|
||||
}
|
||||
|
||||
private void UpdateCredits(int newCreditsCount)
|
||||
{
|
||||
CreditsCount = newCreditsCount;
|
||||
}
|
||||
// Инициализация событий
|
||||
OnCreditsChanged ??= new UnityEvent<int>();
|
||||
OnCreditAdded ??= new UnityEvent<int>();
|
||||
OnCreditRemoved ??= new UnityEvent<int>();
|
||||
|
||||
// Загрузка кредитов из сохранений
|
||||
if (!saveMoneyBetweenSessions)
|
||||
{
|
||||
PlayerPrefs.SetInt("CoinsCount", 0);
|
||||
}
|
||||
|
||||
CreditsCount = PlayerPrefs.GetInt("CoinsCount", 0);
|
||||
|
||||
// Подписка на события ComWrapper
|
||||
ComWrapper.OnCreditAdded?.AddListener(HandleCreditAdded);
|
||||
ComWrapper.OnCreditRemoved?.AddListener(HandleCreditRemoved);
|
||||
|
||||
// Привязка действия добавления кредита (для тестирования)
|
||||
if (addCreditAction != null)
|
||||
{
|
||||
addCreditAction.action.performed += AddCreditsManually;
|
||||
addCreditAction.action.Enable();
|
||||
}
|
||||
|
||||
SettingsManager manager = SettingsManager.Instance;
|
||||
if (manager != null)
|
||||
{
|
||||
manager.OnSettingsLoaded += SetFreePlay;
|
||||
manager.OnSettingsSaved += SetFreePlay;
|
||||
}
|
||||
|
||||
Debug.Log($"Инициализирован. Кредиты: {CreditsCount}, Бесплатная игра: {enableFreePlay}", this);
|
||||
}
|
||||
|
||||
// Если не инициализируется в GameManager, инициализировать тут
|
||||
// private void Awake()
|
||||
// {
|
||||
// Initialize();
|
||||
// }
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Отписка от событий ComWrapper
|
||||
ComWrapper.OnCreditAdded?.RemoveListener(HandleCreditAdded);
|
||||
ComWrapper.OnCreditRemoved?.RemoveListener(HandleCreditRemoved);
|
||||
|
||||
// Отключение действия добавления кредита
|
||||
if (addCreditAction == null) return;
|
||||
addCreditAction.action.performed -= AddCreditsManually;
|
||||
addCreditAction.action.Disable();
|
||||
|
||||
SettingsManager manager = SettingsManager.Instance;
|
||||
if (manager == null) return;
|
||||
manager.OnSettingsLoaded -= SetFreePlay;
|
||||
manager.OnSettingsSaved -= SetFreePlay;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик добавления кредита из ComWrapper
|
||||
/// </summary>
|
||||
/// <param name="newCreditsCount">Новое количество кредитов</param>
|
||||
private void HandleCreditAdded(int newCreditsCount)
|
||||
{
|
||||
CreditsCount = newCreditsCount;
|
||||
OnCreditsChanged?.Invoke(CreditsCount);
|
||||
OnCreditAdded?.Invoke(CreditsCount);
|
||||
GameManager.Logger?.Log("PaymentManager", $"Кредит добавлен. Всего: {CreditsCount}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик удаления кредита из ComWrapper
|
||||
/// </summary>
|
||||
/// <param name="newCreditsCount">Новое количество кредитов</param>
|
||||
private void HandleCreditRemoved(int newCreditsCount)
|
||||
{
|
||||
CreditsCount = newCreditsCount;
|
||||
OnCreditsChanged?.Invoke(CreditsCount);
|
||||
OnCreditRemoved?.Invoke(CreditsCount);
|
||||
GameManager.Logger?.Log("PaymentManager", $"Кредит удален. Осталось: {CreditsCount}");
|
||||
}
|
||||
|
||||
private void SetFreePlay()
|
||||
{
|
||||
if (SettingsManager.Instance == null) return;
|
||||
GameSettings currentSettings = SettingsManager.Instance.GetCurrentGameSettings();
|
||||
enableFreePlay = currentSettings.isFreeGame;
|
||||
Debug.Log($"Бесплатная игра установлена из настроек: {enableFreePlay}", this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, может ли игрок начать игру (есть ли кредиты или включена бесплатная игра)
|
||||
/// </summary>
|
||||
/// <returns>true если игрок может начать игру</returns>
|
||||
public bool CanStartGame()
|
||||
{
|
||||
if (enableFreePlay)
|
||||
{
|
||||
GameManager.Logger?.Log("PaymentManager", "Бесплатная игра включена");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canStart = CreditsCount > 0;
|
||||
GameManager.Logger?.Log("PaymentManager", $"Проверка возможности старта: {canStart} (кредиты: {CreditsCount})");
|
||||
return canStart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет списание платы за запуск игры через ComWrapper.Pay()
|
||||
/// </summary>
|
||||
/// <returns>true если оплата прошла успешно</returns>
|
||||
public bool ProcessPayment()
|
||||
{
|
||||
if (enableFreePlay)
|
||||
{
|
||||
GameManager.Logger?.Log("PaymentManager", "Оплата пропущена - бесплатная игра");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool paymentSuccess = ComWrapper.Pay();
|
||||
|
||||
Debug.Log(paymentSuccess ? "Оплата успешна" : "Оплата не прошла - недостаточно кредитов", this);
|
||||
|
||||
return paymentSuccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет кредиты вручную (для тестирования)
|
||||
/// </summary>
|
||||
/// <param name="amount">Количество кредитов</param>
|
||||
public static void AddCreditsManually(int amount = 1)
|
||||
{
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
ComWrapper.AddCredit();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddCreditsManually(InputAction.CallbackContext context)
|
||||
{
|
||||
AddCreditsManually();
|
||||
// OnCreditAdded?.Invoke(CreditsCount);
|
||||
// OnCreditsChanged?.Invoke(CreditsCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Включает/выключает режим бесплатной игры
|
||||
/// </summary>
|
||||
/// <param name="enable">Включить/выключить</param>
|
||||
public void SetFreePlayMode(bool enable)
|
||||
{
|
||||
enableFreePlay = enable;
|
||||
GameManager.Logger?.Log("PaymentManager", $"Бесплатная игра: {enableFreePlay}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить текущее состояние режима бесплатной игры
|
||||
/// </summary>
|
||||
public bool IsFreePlayEnabled() => enableFreePlay;
|
||||
|
||||
public void SetFreePlay(bool freePlayEnabled)
|
||||
{
|
||||
enableFreePlay = freePlayEnabled;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ using UnityEngine;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
using RR.Common.Behaviour.ScriptableObjects;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace RR.Common.Managers
|
||||
{
|
||||
@ -17,8 +18,8 @@ namespace RR.Common.Managers
|
||||
private const string SETTINGS_FILE_NAME = "settings.json";
|
||||
private string SettingsFilePath => Path.Combine(Application.persistentDataPath, SETTINGS_FILE_NAME);
|
||||
|
||||
public Action OnSettingsLoaded;
|
||||
public Action OnSettingsSaved;
|
||||
public event Action OnSettingsLoaded;
|
||||
public event Action OnSettingsSaved;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
@ -103,6 +104,7 @@ namespace RR.Common.Managers
|
||||
return;
|
||||
}
|
||||
|
||||
gameSettings.ValidateSettings();
|
||||
try
|
||||
{
|
||||
string json = JsonUtility.ToJson(gameSettings, true);
|
||||
@ -159,12 +161,6 @@ namespace RR.Common.Managers
|
||||
Debug.Log($"{name}: Установлены жизни при пополнении баланса: {newLivesIncreasedByCredits}.");
|
||||
}
|
||||
|
||||
public void SetSetLivesToDefaultAfterFirstLose(bool newSetLivesToDefaultAfterFirstLose)
|
||||
{
|
||||
gameSettings.setLivesToDefaultAfterFirstLose = newSetLivesToDefaultAfterFirstLose;
|
||||
Debug.Log($"{name}: Установлен флаг 'set lives to default after first lose': {newSetLivesToDefaultAfterFirstLose}.");
|
||||
}
|
||||
|
||||
public GameSettings GetCurrentGameSettings() => gameSettings;
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,6 @@ namespace RR.Common.Behaviour.ScriptableObjects
|
||||
// Player settings
|
||||
public int startLivesCount;
|
||||
public int livesIncreasedByCredits;
|
||||
public bool setLivesToDefaultAfterFirstLose;
|
||||
|
||||
public void SetDefaultValues()
|
||||
{
|
||||
@ -31,17 +30,16 @@ namespace RR.Common.Behaviour.ScriptableObjects
|
||||
isFreeGame = false;
|
||||
isGivingPrizes = true;
|
||||
|
||||
setLivesToDefaultAfterFirstLose = true;
|
||||
startLivesCount = 3;
|
||||
livesIncreasedByCredits = 1;
|
||||
}
|
||||
|
||||
public void ValidateSettings()
|
||||
{
|
||||
if (musicVolume < 0f || musicVolume > 1f)
|
||||
if (musicVolume is < 0f or > 1f)
|
||||
musicVolume = Mathf.Clamp01(musicVolume);
|
||||
|
||||
if (sfxVolume < 0f || sfxVolume > 1f)
|
||||
if (sfxVolume is < 0f or > 1f)
|
||||
sfxVolume = Mathf.Clamp01(sfxVolume);
|
||||
|
||||
if (startLivesCount < 1)
|
||||
@ -58,9 +56,6 @@ namespace RR.Common.Behaviour.ScriptableObjects
|
||||
|
||||
if (GameManager.Instance != null)
|
||||
{
|
||||
if (!setLivesToDefaultAfterFirstLose)
|
||||
GameManager.Instance.ResetLives = true;
|
||||
|
||||
GameManager.Instance.Audio.SetMusicVolume(musicVolume);
|
||||
GameManager.Instance.Audio.SetSfxVolume(sfxVolume);
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ namespace RR.Common.UI
|
||||
|
||||
GetComponent<Image>().color = new Color(0, 0,0, 100f/255f);
|
||||
|
||||
winText.text = $"Время игры: {GameEndUIController.FormatTime(gameTime)}";
|
||||
winText.text = $"Время игры: {FormatTime(gameTime)}";
|
||||
SetVisible(winPanel, true);
|
||||
|
||||
EventSystem.current.firstSelectedGameObject = mainMenuButtonWin.gameObject;
|
||||
@ -136,7 +136,7 @@ namespace RR.Common.UI
|
||||
{
|
||||
TimeSpan interval = TimeSpan.FromSeconds(gameTime);
|
||||
|
||||
return interval.ToString(@"mm\:ss");
|
||||
return interval.ToString(@"mm\:ss\.fff");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,13 +76,13 @@ namespace RR.Common.UI
|
||||
}
|
||||
}
|
||||
|
||||
struct LevelEntry { public string name; public int world; public int stage; }
|
||||
private struct LevelEntry { public string name; public int world; public int stage; }
|
||||
|
||||
/// <summary>
|
||||
/// Метод для перебора уровней в игре
|
||||
/// </summary>
|
||||
/// <returns>LevelEntry - текущий мир при переборе</returns>
|
||||
IEnumerable<LevelEntry> EnumerateLevelScenesInBuild()
|
||||
private static IEnumerable<LevelEntry> EnumerateLevelScenesInBuild()
|
||||
{
|
||||
int n = SceneManager.sceneCountInBuildSettings;
|
||||
for (int i = 0; i < n; i++)
|
||||
@ -111,7 +111,7 @@ namespace RR.Common.UI
|
||||
/// <param name="to">Конечная прозрачнеость</param>
|
||||
/// <param name="time">Время перехода</param>
|
||||
/// <param name="onDone">Действие при выполнении перехода</param>
|
||||
IEnumerator Fade(float from, float to, float time, System.Action onDone = null)
|
||||
private IEnumerator Fade(float from, float to, float time, System.Action onDone = null)
|
||||
{
|
||||
if (!canvasGroup) { onDone?.Invoke(); yield break; }
|
||||
float t = 0f;
|
||||
@ -128,7 +128,7 @@ namespace RR.Common.UI
|
||||
onDone?.Invoke();
|
||||
}
|
||||
|
||||
void Update()
|
||||
private void Update()
|
||||
{
|
||||
// ESC закрыть
|
||||
if (gameObject.activeSelf && close.action.WasPressedThisFrame())
|
||||
|
||||
143
Assets/Scripts/UI/ScrollSelectionFollower.cs
Normal file
143
Assets/Scripts/UI/ScrollSelectionFollower.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections;
|
||||
|
||||
public class ScrollSelectionFollower : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private ScrollRect scrollRect;
|
||||
[SerializeField] private float padding = 8f; // отступы сверху/снизу/слева/справа
|
||||
[SerializeField] private float duration = 0.15f; // длительность анимации
|
||||
[SerializeField] private bool onlyWhenNotVisible = true; // если false — центрируем всегда
|
||||
|
||||
private GameObject _lastSelected;
|
||||
private Coroutine _anim;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
scrollRect = GetComponentInChildren<ScrollRect>();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
GameObject sel = EventSystem.current ? EventSystem.current.currentSelectedGameObject : null;
|
||||
if (!sel || sel == _lastSelected) return;
|
||||
|
||||
// Только если элемент внутри нашего Content
|
||||
if (!scrollRect || sel.transform is not RectTransform tr || !tr.IsChildOf(scrollRect.content)) return;
|
||||
ScrollTo(tr);
|
||||
_lastSelected = sel;
|
||||
}
|
||||
|
||||
public void ScrollTo(RectTransform target)
|
||||
{
|
||||
if (!scrollRect || !target) return;
|
||||
|
||||
// Сначала убедимся, что лейауты посчитались
|
||||
Canvas.ForceUpdateCanvases();
|
||||
|
||||
RectTransform content = scrollRect.content;
|
||||
RectTransform viewport = scrollRect.viewport != null ? scrollRect.viewport : (RectTransform)scrollRect.transform;
|
||||
|
||||
// Получаем мировые углы и преобразуем в локальное пространство viewport
|
||||
Vector3[] itemCorners = new Vector3[4];
|
||||
target.GetWorldCorners(itemCorners);
|
||||
|
||||
Vector3[] viewportCorners = new Vector3[4];
|
||||
viewport.GetWorldCorners(viewportCorners);
|
||||
|
||||
// Преобразуем в локальное пространство content
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
itemCorners[i] = content.InverseTransformPoint(itemCorners[i]);
|
||||
viewportCorners[i] = content.InverseTransformPoint(viewportCorners[i]);
|
||||
}
|
||||
|
||||
// Вычисляем границы
|
||||
float itemTop = itemCorners[1].y;
|
||||
float itemBottom = itemCorners[0].y;
|
||||
float itemLeft = itemCorners[0].x;
|
||||
float itemRight = itemCorners[2].x;
|
||||
|
||||
float viewTop = viewportCorners[1].y;
|
||||
float viewBottom = viewportCorners[0].y;
|
||||
float viewLeft = viewportCorners[0].x;
|
||||
float viewRight = viewportCorners[2].x;
|
||||
|
||||
Vector2 offset = Vector2.zero;
|
||||
|
||||
// Вертикальная прокрутка
|
||||
if (scrollRect.vertical)
|
||||
{
|
||||
float delta = 0f;
|
||||
bool above = itemTop > viewTop - padding;
|
||||
bool below = itemBottom < viewBottom + padding;
|
||||
|
||||
if (!onlyWhenNotVisible || above || below)
|
||||
{
|
||||
if (above && below)
|
||||
{
|
||||
// Элемент больше viewport - выравниваем по верхнему краю
|
||||
delta = itemTop - (viewTop - padding);
|
||||
}
|
||||
else if (above)
|
||||
{
|
||||
delta = itemTop - (viewTop - padding);
|
||||
}
|
||||
else if (below)
|
||||
{
|
||||
delta = itemBottom - (viewBottom + padding);
|
||||
}
|
||||
offset.y = delta;
|
||||
}
|
||||
}
|
||||
|
||||
// Горизонтальная прокрутка
|
||||
if (scrollRect.horizontal)
|
||||
{
|
||||
float delta = 0f;
|
||||
bool left = itemLeft < viewLeft + padding;
|
||||
bool right = itemRight > viewRight - padding;
|
||||
|
||||
if (!onlyWhenNotVisible || left || right)
|
||||
{
|
||||
if (left && right)
|
||||
{
|
||||
// Элемент больше viewport - выравниваем по левому краю
|
||||
delta = itemLeft - (viewLeft + padding);
|
||||
}
|
||||
else if (left)
|
||||
{
|
||||
delta = itemLeft - (viewLeft + padding);
|
||||
}
|
||||
else if (right)
|
||||
{
|
||||
delta = itemRight - (viewRight - padding);
|
||||
}
|
||||
offset.x = delta;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset == Vector2.zero) return;
|
||||
|
||||
Vector2 targetPos = content.anchoredPosition - offset;
|
||||
|
||||
if (_anim != null) StopCoroutine(_anim);
|
||||
_anim = StartCoroutine(AnimateTo(content, targetPos, duration));
|
||||
}
|
||||
|
||||
private static IEnumerator AnimateTo(RectTransform content, Vector2 target, float time)
|
||||
{
|
||||
Vector2 start = content.anchoredPosition;
|
||||
float t = 0f;
|
||||
while (t < 1f)
|
||||
{
|
||||
t += Time.unscaledDeltaTime / Mathf.Max(0.0001f, time);
|
||||
// лёгкая ease-in-out
|
||||
float s = t * t * (3f - 2f * t);
|
||||
content.anchoredPosition = Vector2.LerpUnclamped(start, target, s);
|
||||
yield return null;
|
||||
}
|
||||
content.anchoredPosition = target;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/ScrollSelectionFollower.cs.meta
Normal file
2
Assets/Scripts/UI/ScrollSelectionFollower.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 738ffd3a084a4114b918b316ca68c168
|
||||
@ -1,18 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.EventSystems;
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using RR.Common.Behaviour.ScriptableObjects;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
[RequireComponent(typeof(CanvasGroup), typeof(SettingsUIList))]
|
||||
public class SettingsUI : MonoBehaviour
|
||||
{
|
||||
[Header("Управление")]
|
||||
[SerializeField] private InputActionReference triggerKey;
|
||||
[SerializeField] private Button saveButton;
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
[SerializeField] private Transform content;
|
||||
[SerializeField] private float fadeDuration = 0.15f;
|
||||
|
||||
private SettingsUIList settingsUIList;
|
||||
|
||||
private SettingsManager settingsManager;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
settingsManager = SettingsManager.Instance;
|
||||
|
||||
if (triggerKey != null) triggerKey.action.started += Toggle;
|
||||
|
||||
if (settingsManager == null)
|
||||
{
|
||||
GameManager.Logger.LogError(name, "SettingsManager is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
settingsManager.OnSettingsLoaded += UpdateSettings;
|
||||
settingsManager.OnSettingsSaved += SaveSettingsHandler;
|
||||
|
||||
if (saveButton) saveButton.onClick.AddListener(SaveAndClose);
|
||||
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (settingsManager != null)
|
||||
{
|
||||
settingsManager.OnSettingsLoaded -= UpdateSettings;
|
||||
settingsManager.OnSettingsSaved -= SaveSettingsHandler;
|
||||
}
|
||||
|
||||
if (triggerKey != null) triggerKey.action.started -= Toggle;
|
||||
|
||||
if (saveButton) saveButton.onClick.RemoveListener(SaveAndClose);
|
||||
}
|
||||
|
||||
// private void Update()
|
||||
// {
|
||||
// if (!gameObject.activeSelf && triggerKey != null && triggerKey.action.WasPerformedThisFrame())
|
||||
// {
|
||||
// Open();
|
||||
// }
|
||||
// }
|
||||
|
||||
private void Toggle(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (gameObject.activeSelf)
|
||||
{
|
||||
SaveAndClose();
|
||||
}
|
||||
else
|
||||
{
|
||||
Open();
|
||||
}
|
||||
}
|
||||
|
||||
private void Open()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(Fade(0f, 1f, fadeDuration));
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
StartCoroutine(Fade(1f, 0f, fadeDuration, () => { gameObject.SetActive(false); }));
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
UpdateSettings();
|
||||
settingsUIList.SaveSettings();
|
||||
}
|
||||
|
||||
private void SaveAndClose()
|
||||
{
|
||||
Save();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void UpdateSettings()
|
||||
{
|
||||
if (settingsUIList == null) settingsUIList = content.GetComponent<SettingsUIList>();
|
||||
if (settingsUIList != null) settingsUIList.UpdateSettings(settingsManager.GetCurrentGameSettings());
|
||||
}
|
||||
|
||||
private void SaveSettingsHandler()
|
||||
{
|
||||
GameManager.Logger.Log(name, "Saved settings");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для плавного перехода (затухания)
|
||||
/// </summary>
|
||||
/// <param name="from">Начальная прозрачность</param>
|
||||
/// <param name="to">Конечная прозрачнеость</param>
|
||||
/// <param name="time">Время перехода</param>
|
||||
/// <param name="onDone">Действие при выполнении перехода</param>
|
||||
private IEnumerator Fade(float from, float to, float time, System.Action onDone = null)
|
||||
{
|
||||
if (!canvasGroup) { onDone?.Invoke(); yield break; }
|
||||
float t = 0f;
|
||||
canvasGroup.alpha = from;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
while (t < time)
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
canvasGroup.alpha = Mathf.Lerp(from, to, t / time);
|
||||
yield return null;
|
||||
}
|
||||
canvasGroup.alpha = to;
|
||||
canvasGroup.blocksRaycasts = to > 0.99f;
|
||||
onDone?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
207
Assets/Scripts/UI/SettingsUIList.cs
Normal file
207
Assets/Scripts/UI/SettingsUIList.cs
Normal file
@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RR.Common.Behaviour.ScriptableObjects;
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
public class SettingsUIList : MonoBehaviour
|
||||
{
|
||||
public List<Selectable> settingsList = new();
|
||||
|
||||
[Header("Настройки")]
|
||||
|
||||
// Volume
|
||||
[SerializeField] private Slider musicVolumeSlider;
|
||||
[SerializeField] private Slider sfxVolumeSlider;
|
||||
|
||||
// Payment
|
||||
[SerializeField] private Toggle freeGameToggle;
|
||||
[SerializeField] private Toggle givingPrizesToggle;
|
||||
|
||||
// Player
|
||||
[SerializeField] private Slider startLivesSlider;
|
||||
[SerializeField] private Slider increasedLivesSlider;
|
||||
|
||||
[Header("Текст в UI")]
|
||||
// Text
|
||||
[SerializeField] private TextMeshProUGUI volumePercentageText;
|
||||
[SerializeField] private TextMeshProUGUI sfxVolumePercentageText;
|
||||
[SerializeField] private TextMeshProUGUI startLivesCountText;
|
||||
[SerializeField] private TextMeshProUGUI increasedLivesCountText;
|
||||
|
||||
private SettingsManager settingsManager;
|
||||
private GameSettings currentSettings;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
settingsManager = SettingsManager.Instance;
|
||||
|
||||
if (settingsManager == null)
|
||||
{
|
||||
GameManager.Logger.LogError(name, "SettingsManager is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
settingsList.Add(musicVolumeSlider);
|
||||
settingsList.Add(sfxVolumeSlider);
|
||||
settingsList.Add(freeGameToggle);
|
||||
settingsList.Add(givingPrizesToggle);
|
||||
settingsList.Add(startLivesSlider);
|
||||
settingsList.Add(increasedLivesSlider);
|
||||
|
||||
currentSettings = settingsManager.GetCurrentGameSettings();
|
||||
|
||||
AddListeners();
|
||||
UpdateSettings(currentSettings);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
RemoveListeners();
|
||||
}
|
||||
|
||||
public void UpdateSettings(GameSettings newSettings = null)
|
||||
{
|
||||
if (newSettings != null) currentSettings = newSettings;
|
||||
|
||||
RemoveListeners();
|
||||
|
||||
musicVolumeSlider.value = currentSettings.musicVolume;
|
||||
sfxVolumeSlider.value = currentSettings.sfxVolume;
|
||||
freeGameToggle.isOn = currentSettings.isFreeGame;
|
||||
givingPrizesToggle.isOn = currentSettings.isGivingPrizes;
|
||||
startLivesSlider.value = currentSettings.startLivesCount;
|
||||
increasedLivesSlider.value = currentSettings.livesIncreasedByCredits;
|
||||
|
||||
AddListeners();
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
settingsManager.SaveSettings();
|
||||
}
|
||||
|
||||
private void AddListeners()
|
||||
{
|
||||
// Music Volume
|
||||
if (musicVolumeSlider != null)
|
||||
{
|
||||
musicVolumeSlider.onValueChanged.AddListener(settingsManager.SetMusicVolume);
|
||||
musicVolumeSlider.onValueChanged.AddListener(SetMusicVolumePercentage);
|
||||
musicVolumeSlider.onValueChanged.AddListener(GameManager.Instance.Audio.SetMusicVolume);
|
||||
}
|
||||
|
||||
// SFX Volume
|
||||
if (sfxVolumeSlider != null)
|
||||
{
|
||||
sfxVolumeSlider.onValueChanged.AddListener(settingsManager.SetSfxVolume);
|
||||
sfxVolumeSlider.onValueChanged.AddListener(SetSfxVolumePercentage);
|
||||
sfxVolumeSlider.onValueChanged.AddListener(GameManager.Instance.Audio.SetSfxVolume);
|
||||
}
|
||||
|
||||
// Free game
|
||||
if (freeGameToggle != null)
|
||||
{
|
||||
freeGameToggle.onValueChanged.AddListener(settingsManager.SetIsFreeGame);
|
||||
}
|
||||
|
||||
// Giving prizes
|
||||
if (givingPrizesToggle != null)
|
||||
{
|
||||
givingPrizesToggle.onValueChanged.AddListener(settingsManager.SetIsGivingPrizes);
|
||||
}
|
||||
|
||||
// Start lives
|
||||
if (startLivesSlider != null)
|
||||
{
|
||||
startLivesSlider.onValueChanged.AddListener(value =>
|
||||
settingsManager.SetStartLivesCount((int)value));
|
||||
startLivesSlider.onValueChanged.AddListener(SetStartLivesCountText);
|
||||
}
|
||||
|
||||
// Increased lives
|
||||
if (increasedLivesSlider != null)
|
||||
{
|
||||
increasedLivesSlider.onValueChanged.AddListener(value =>
|
||||
settingsManager.SetLivesIncreasedByCredits((int)value));
|
||||
increasedLivesSlider.onValueChanged.AddListener(SetIncreasedLivesCountText);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveListeners()
|
||||
{
|
||||
// Music Volume
|
||||
if (musicVolumeSlider != null)
|
||||
{
|
||||
musicVolumeSlider.onValueChanged.RemoveListener(settingsManager.SetMusicVolume);
|
||||
musicVolumeSlider.onValueChanged.AddListener(SetMusicVolumePercentage);
|
||||
}
|
||||
|
||||
// SFX Volume
|
||||
if (sfxVolumeSlider != null)
|
||||
{
|
||||
sfxVolumeSlider.onValueChanged.RemoveListener(settingsManager.SetSfxVolume);
|
||||
sfxVolumeSlider.onValueChanged.AddListener(SetSfxVolumePercentage);
|
||||
}
|
||||
|
||||
// Free game
|
||||
if (freeGameToggle != null)
|
||||
{
|
||||
freeGameToggle.onValueChanged.RemoveListener(settingsManager.SetIsFreeGame);
|
||||
}
|
||||
|
||||
// Giving prizes
|
||||
if (givingPrizesToggle != null)
|
||||
{
|
||||
givingPrizesToggle.onValueChanged.RemoveListener(settingsManager.SetIsGivingPrizes);
|
||||
}
|
||||
|
||||
// Start lives
|
||||
if (startLivesSlider != null)
|
||||
{
|
||||
startLivesSlider.onValueChanged.RemoveListener(value =>
|
||||
settingsManager.SetStartLivesCount((int)value));
|
||||
startLivesSlider.onValueChanged.AddListener(SetStartLivesCountText);
|
||||
}
|
||||
|
||||
// Increased lives
|
||||
if (increasedLivesSlider != null)
|
||||
{
|
||||
increasedLivesSlider.onValueChanged.RemoveListener(value =>
|
||||
settingsManager.SetLivesIncreasedByCredits((int)value));
|
||||
increasedLivesSlider.onValueChanged.AddListener(SetIncreasedLivesCountText);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMusicVolumePercentage(float value)
|
||||
{
|
||||
if (volumePercentageText == null) return;
|
||||
int volumePercent = Mathf.RoundToInt(value * 100);
|
||||
volumePercentageText.text = $"{volumePercent}%";
|
||||
}
|
||||
|
||||
private void SetSfxVolumePercentage(float value)
|
||||
{
|
||||
if (sfxVolumePercentageText == null) return;
|
||||
int sfxVolumePercent = Mathf.RoundToInt(value * 100);
|
||||
sfxVolumePercentageText.text = $"{sfxVolumePercent}%";
|
||||
}
|
||||
|
||||
private void SetStartLivesCountText(float value)
|
||||
{
|
||||
if (startLivesCountText == null) return;
|
||||
startLivesCountText.text = $"{(int)value}";
|
||||
}
|
||||
|
||||
private void SetIncreasedLivesCountText(float value)
|
||||
{
|
||||
if (increasedLivesCountText == null) return;
|
||||
increasedLivesCountText.text = $"{(int)value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/UI/SettingsUIList.cs.meta
Normal file
3
Assets/Scripts/UI/SettingsUIList.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98f414c07d2f47e794d25468e8519ba7
|
||||
timeCreated: 1762519929
|
||||
39
Assets/Scripts/UI/UICreditsTextController.cs
Normal file
39
Assets/Scripts/UI/UICreditsTextController.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
public class UICreditsTextController : MonoBehaviour
|
||||
{
|
||||
// UI elements
|
||||
[SerializeField] private TextMeshProUGUI creditsText;
|
||||
[SerializeField] private TextMeshProUGUI livesText;
|
||||
|
||||
// Strings to display
|
||||
[SerializeField] private string creditsString = "У вас {0} кредитов";
|
||||
[SerializeField] private string livesString = "У вас будет {0}";
|
||||
|
||||
private void Start()
|
||||
{
|
||||
UpdateCreditsText(0);
|
||||
UpdateLivesText(0);
|
||||
|
||||
// Subscribe to events
|
||||
PaymentManager.Instance?.OnCreditsChanged.AddListener(UpdateCreditsText);
|
||||
PaymentManager.Instance?.OnCreditsChanged.AddListener(UpdateLivesText);
|
||||
}
|
||||
|
||||
private void UpdateCreditsText(int creditsCount)
|
||||
{
|
||||
creditsText.text = string.Format(creditsString, creditsCount);
|
||||
}
|
||||
|
||||
private void UpdateLivesText(int creditsCount)
|
||||
{
|
||||
int totalLives = creditsCount > 1 ?
|
||||
GameManager.Instance.StartingLives + GameManager.Instance.LivesIncrement * (creditsCount - 1) :
|
||||
GameManager.Instance.StartingLives;
|
||||
livesText.text = string.Format(livesString, totalLives);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/UICreditsTextController.cs.meta
Normal file
2
Assets/Scripts/UI/UICreditsTextController.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 693c49fe51646a7419844c26fc01f369
|
||||
126
Assets/Scripts/UI/UIPointer.cs
Normal file
126
Assets/Scripts/UI/UIPointer.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
public class UIPointer : MonoBehaviour
|
||||
{
|
||||
[Header("Настройки")]
|
||||
[SerializeField] private GameObject pointer;
|
||||
[SerializeField] private RectTransform content; // Content ScrollView, где находятся элементы
|
||||
[SerializeField] private float smoothSpeed = 10f; // Скорость перемещения указателя
|
||||
[SerializeField] private Vector2 offset = Vector2.zero; // Смещение указателя относительно элемента
|
||||
|
||||
private RectTransform pointerRect;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (pointer != null)
|
||||
{
|
||||
pointerRect = pointer.GetComponent<RectTransform>();
|
||||
if (pointerRect == null)
|
||||
{
|
||||
Debug.LogError("UIPointer: pointer не имеет RectTransform!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("UIPointer: pointer не назначен!");
|
||||
}
|
||||
|
||||
// Если content не задан, пытаемся найти его автоматически
|
||||
if (content == null)
|
||||
{
|
||||
content = GetComponent<RectTransform>();
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!pointerRect || !content) return;
|
||||
|
||||
// Получаем текущий выбранный элемент из EventSystem
|
||||
GameObject selected = EventSystem.current?.currentSelectedGameObject;
|
||||
|
||||
// Если ничего не выбрано, скрываем указатель
|
||||
if (selected == null)
|
||||
{
|
||||
if (pointer.activeSelf)
|
||||
pointer.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что выбранный элемент находится внутри нашего content
|
||||
RectTransform selectedRect = selected.GetComponent<RectTransform>();
|
||||
if (!selectedRect || !selectedRect.IsChildOf(content))
|
||||
{
|
||||
if (pointer.activeSelf)
|
||||
pointer.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем указатель, если он был скрыт
|
||||
if (!pointer.activeSelf)
|
||||
pointer.SetActive(true);
|
||||
|
||||
// Вычисляем целевую позицию
|
||||
Vector2 targetPos = GetTargetPosition(selectedRect);
|
||||
|
||||
// Плавно перемещаем указатель
|
||||
if (smoothSpeed > 0)
|
||||
{
|
||||
pointerRect.anchoredPosition = Vector2.Lerp(
|
||||
pointerRect.anchoredPosition,
|
||||
targetPos,
|
||||
Time.unscaledDeltaTime * smoothSpeed
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
pointerRect.anchoredPosition = targetPos;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetTargetPosition(RectTransform target)
|
||||
{
|
||||
// Получаем позицию целевого элемента относительно content
|
||||
Vector2 targetAnchoredPos = target.anchoredPosition;
|
||||
|
||||
// Если элементы не прямые дети content, нужно пересчитать позицию
|
||||
if (target.parent == content)
|
||||
return new Vector2(
|
||||
pointerRect.anchoredPosition.x + offset.x, // X оставляем как есть (или применяем offset)
|
||||
targetAnchoredPos.y + offset.y // Y берем от целевого элемента
|
||||
);
|
||||
// Конвертируем мировую позицию в локальную позицию content
|
||||
Vector3[] corners = new Vector3[4];
|
||||
target.GetWorldCorners(corners);
|
||||
Vector3 center = (corners[0] + corners[2]) * 0.5f;
|
||||
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
content,
|
||||
RectTransformUtility.WorldToScreenPoint(null, center),
|
||||
null,
|
||||
out Vector2 localPos
|
||||
);
|
||||
targetAnchoredPos = localPos;
|
||||
|
||||
// Применяем смещение
|
||||
return new Vector2(
|
||||
pointerRect.anchoredPosition.x + offset.x, // X оставляем как есть (или применяем offset)
|
||||
targetAnchoredPos.y + offset.y // Y берем от целевого элемента
|
||||
);
|
||||
}
|
||||
|
||||
// Метод для ручного обновления позиции указателя (если нужно)
|
||||
public void UpdatePointerPosition()
|
||||
{
|
||||
if (pointerRect == null || content == null) return;
|
||||
|
||||
GameObject selected = EventSystem.current?.currentSelectedGameObject;
|
||||
if (selected == null) return;
|
||||
|
||||
RectTransform selectedRect = selected.GetComponent<RectTransform>();
|
||||
if (selectedRect == null || !selectedRect.IsChildOf(content)) return;
|
||||
|
||||
pointerRect.anchoredPosition = GetTargetPosition(selectedRect);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/UIPointer.cs.meta
Normal file
2
Assets/Scripts/UI/UIPointer.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4367909e6d008a41b067a1974733d6f
|
||||
36437
Assets/_Recovery/0 (5).unity
Normal file
36437
Assets/_Recovery/0 (5).unity
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/_Recovery/0 (5).unity.meta
Normal file
7
Assets/_Recovery/0 (5).unity.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa0cc7aabc3ce124e9fefec4607c9d6b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -12,7 +12,7 @@ PlayerSettings:
|
||||
targetDevice: 2
|
||||
useOnDemandResources: 0
|
||||
accelerometerFrequency: 60
|
||||
companyName: RR
|
||||
companyName: Robotic Retailers
|
||||
productName: Danro
|
||||
defaultCursor: {fileID: 0}
|
||||
cursorHotspot: {x: 0, y: 0}
|
||||
@ -144,7 +144,7 @@ PlayerSettings:
|
||||
loadStoreDebugModeEnabled: 0
|
||||
visionOSBundleVersion: 1.0
|
||||
tvOSBundleVersion: 1.0
|
||||
bundleVersion: 0.0.6a
|
||||
bundleVersion: 0.2.2b
|
||||
preloadedAssets:
|
||||
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
- {fileID: 11400000, guid: bf6b6fd5471a43d40b76a4625c2f3b62, type: 2}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user