using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using RR.Common.Controls;
using RR.Common.Settings;
using RR.Game2D.Core.Managers;
namespace RR.Common.Managers
{
///
/// Менеджер отслеживания бездействия игрока.
/// При отсутствии ввода в течение заданного времени выкидывает игрока в главное меню.
///
public class InactivityManager : MonoBehaviour
{
public static InactivityManager Instance { get; private set; }
[Header("Настройки бездействия")]
[SerializeField] private float inactivityTimeout = 300f; // 5 минут в секундах
[SerializeField] private bool enableInactivityCheck = true;
[Header("Отладка")]
[SerializeField] private bool showDebugInfo;
private float lastInputTime;
private bool isInMainMenu = true;
private bool isGameActive;
private InputSnapshot lastInputSnapshot;
private bool hasInputSnapshot;
private string lastComInputState = "11111111111111111111111111";
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
Initialize();
}
private void Initialize()
{
lastInputTime = Time.unscaledTime;
// Подписываемся на все возможные источники ввода
SubscribeToInputEvents();
// Подписываемся на события смены сцены
UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
// Подписываемся на изменения настроек
var settingsManager = SettingsManager.Instance;
if (settingsManager != null)
{
settingsManager.OnSettingsLoaded += LoadSettingsValues;
settingsManager.OnSettingsSaved += LoadSettingsValues;
LoadSettingsValues();
}
Debug.Log("[InactivityManager] Инициализирован");
}
private void OnDestroy()
{
UnsubscribeFromInputEvents();
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
var settingsManager = SettingsManager.Instance;
if (settingsManager != null)
{
settingsManager.OnSettingsLoaded -= LoadSettingsValues;
settingsManager.OnSettingsSaved -= LoadSettingsValues;
}
}
///
/// Загружает значения из настроек
///
private void LoadSettingsValues()
{
var settings = SettingsManager.Instance;
if (settings == null)
{
Debug.LogWarning("[InactivityManager] SettingsManager is null!");
return;
}
inactivityTimeout = settings.GetInt("gameplay.inactivityTimeout");
if (inactivityTimeout == 0)
{
Debug.Log("[InactivityManager] Отслеживание бездействия отключено (таймаут = 0)");
}
else
{
Debug.Log($"[InactivityManager] Таймаут бездействия: {inactivityTimeout}s");
}
}
private void SubscribeToInputEvents()
{
// Подписываемся на события ComSystemManager (кнопки автомата)
if (ComSystemManager.OnInputReceived != null)
{
ComSystemManager.OnInputReceived.AddListener(OnComInput);
}
// InputSystem.onAnyButtonPress - это событие, которое срабатывает каждый раз
// когда нажимается любая кнопка. Мы просто слушаем его в Update через CheckForAnyInput
}
private void UnsubscribeFromInputEvents()
{
if (ComSystemManager.OnInputReceived != null)
{
ComSystemManager.OnInputReceived.RemoveListener(OnComInput);
}
}
private void Update()
{
// Проверяем, изменилось ли состояние ввода в этом кадре
CheckForInputStateChanges();
// Проверяем таймер бездействия
if (enableInactivityCheck && inactivityTimeout > 0f && !isInMainMenu && isGameActive)
{
float timeSinceLastInput = Time.unscaledTime - lastInputTime;
if (showDebugInfo && Time.frameCount % 60 == 0)
{
Debug.Log($"[InactivityManager] Время бездействия: {timeSinceLastInput:F1}s / {inactivityTimeout}s");
}
if (timeSinceLastInput >= inactivityTimeout)
{
OnInactivityTimeout();
}
}
}
///
/// Проверяет, изменилось ли состояние ввода с прошлого кадра.
/// Зажатая кнопка без изменений считается бездействием.
///
private void CheckForInputStateChanges()
{
if (isInMainMenu || !isGameActive)
return;
InputSnapshot currentSnapshot = BuildInputSnapshot();
if (!hasInputSnapshot)
{
lastInputSnapshot = currentSnapshot;
hasInputSnapshot = true;
return;
}
if (!currentSnapshot.Equals(lastInputSnapshot))
{
lastInputSnapshot = currentSnapshot;
ResetInactivityTimer();
}
}
private void OnComInput(string input)
{
lastComInputState = string.IsNullOrWhiteSpace(input)
? "11111111111111111111111111"
: input;
}
///
/// Сбрасывает таймер бездействия
///
public void ResetInactivityTimer()
{
lastInputTime = Time.unscaledTime;
if (showDebugInfo)
{
Debug.Log("[InactivityManager] Таймер сброшен");
}
}
///
/// Вызывается при достижении таймаута бездействия
///
private void OnInactivityTimeout()
{
Debug.Log("[InactivityManager] Таймаут бездействия! Возврат в главное меню...");
enableInactivityCheck = false; // Отключаем проверку до следующей игры
// Останавливаем игру
if (GameManager.Instance != null)
{
GameManager.Instance.SetPause(false);
GameManager.Instance.LoadScene("MainMenu", false);
}
ResetInactivityTimer();
}
///
/// Обработчик загрузки сцены
///
private void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
{
isInMainMenu = scene.name == "MainMenu";
if (isInMainMenu)
{
enableInactivityCheck = false;
isGameActive = false;
hasInputSnapshot = false;
Debug.Log("[InactivityManager] Главное меню загружено - проверка отключена");
}
ResetInactivityTimer();
}
///
/// Включает проверку бездействия (вызывается при старте игры)
///
public void EnableInactivityCheck()
{
if (inactivityTimeout <= 0f)
{
enableInactivityCheck = false;
isGameActive = false;
ResetInactivityTimer();
Debug.Log("[InactivityManager] Проверка бездействия не включена: таймаут = 0");
return;
}
enableInactivityCheck = true;
isGameActive = true;
lastComInputState = GetCurrentComInputState();
lastInputSnapshot = BuildInputSnapshot();
hasInputSnapshot = true;
ResetInactivityTimer();
Debug.Log("[InactivityManager] Проверка бездействия включена");
}
///
/// Отключает проверку бездействия
///
public void DisableInactivityCheck()
{
enableInactivityCheck = false;
isGameActive = false;
hasInputSnapshot = false;
Debug.Log("[InactivityManager] Проверка бездействия отключена");
}
///
/// Устанавливает время таймаута бездействия.
/// Если установлено 0, отслеживание бездействия отключается.
///
public void SetInactivityTimeout(float timeoutSeconds)
{
inactivityTimeout = Mathf.Max(0f, timeoutSeconds); // 0 = отключено, минимум 0
if (inactivityTimeout == 0f)
{
enableInactivityCheck = false;
isGameActive = false;
hasInputSnapshot = false;
Debug.Log("[InactivityManager] Отслеживание бездействия отключено (таймаут = 0)");
}
else
{
Debug.Log($"[InactivityManager] Таймаут установлен: {inactivityTimeout}s");
}
}
private InputSnapshot BuildInputSnapshot()
{
return new InputSnapshot(
BuildKeyboardStateHash(),
BuildMouseStateHash(),
BuildGamepadStateHash(),
BuildComStateHash());
}
private static int BuildKeyboardStateHash()
{
Keyboard keyboard = Keyboard.current;
if (keyboard == null)
{
return 0;
}
int hash = 17;
foreach (KeyControl key in keyboard.allKeys)
{
if (key.isPressed)
{
hash = hash * 31 + (int)key.keyCode;
}
}
return hash;
}
private static int BuildMouseStateHash()
{
Mouse mouse = Mouse.current;
if (mouse == null)
{
return 0;
}
Vector2 position = mouse.position.ReadValue();
Vector2 scroll = mouse.scroll.ReadValue();
int hash = 17;
hash = hash * 31 + (mouse.leftButton.isPressed ? 1 : 0);
hash = hash * 31 + (mouse.rightButton.isPressed ? 1 : 0);
hash = hash * 31 + (mouse.middleButton.isPressed ? 1 : 0);
hash = hash * 31 + Mathf.RoundToInt(position.x);
hash = hash * 31 + Mathf.RoundToInt(position.y);
hash = hash * 31 + Mathf.RoundToInt(scroll.x);
hash = hash * 31 + Mathf.RoundToInt(scroll.y);
return hash;
}
private static int BuildGamepadStateHash()
{
Gamepad gamepad = Gamepad.current;
if (gamepad == null)
{
return 0;
}
int hash = 17;
foreach (InputControl control in gamepad.allControls)
{
if (control is ButtonControl button && button.isPressed)
{
hash = hash * 31 + control.name.GetHashCode();
}
}
Vector2 leftStick = QuantizeStick(gamepad.leftStick.ReadValue());
Vector2 rightStick = QuantizeStick(gamepad.rightStick.ReadValue());
float leftTrigger = QuantizeAxis(gamepad.leftTrigger.ReadValue());
float rightTrigger = QuantizeAxis(gamepad.rightTrigger.ReadValue());
hash = hash * 31 + Mathf.RoundToInt(leftStick.x * 10f);
hash = hash * 31 + Mathf.RoundToInt(leftStick.y * 10f);
hash = hash * 31 + Mathf.RoundToInt(rightStick.x * 10f);
hash = hash * 31 + Mathf.RoundToInt(rightStick.y * 10f);
hash = hash * 31 + Mathf.RoundToInt(leftTrigger * 10f);
hash = hash * 31 + Mathf.RoundToInt(rightTrigger * 10f);
return hash;
}
private int BuildComStateHash()
{
string currentState = GetCurrentComInputState();
if (string.IsNullOrWhiteSpace(currentState))
{
return 0;
}
int hash = 17;
for (int i = 0; i < currentState.Length; i++)
{
hash = hash * 31 + currentState[i];
}
return hash;
}
private string GetCurrentComInputState()
{
string currentState = ComSystemManager.GetLastInputState();
if (!string.IsNullOrWhiteSpace(currentState))
{
lastComInputState = currentState;
}
return string.IsNullOrWhiteSpace(lastComInputState)
? "11111111111111111111111111"
: lastComInputState;
}
private static Vector2 QuantizeStick(Vector2 value)
{
return new Vector2(QuantizeAxis(value.x), QuantizeAxis(value.y));
}
private static float QuantizeAxis(float value)
{
if (Mathf.Abs(value) < 0.15f)
return 0f;
return Mathf.Round(value * 10f) / 10f;
}
private readonly struct InputSnapshot : System.IEquatable
{
private readonly int _keyboardHash;
private readonly int _mouseHash;
private readonly int _gamepadHash;
private readonly int _comHash;
public InputSnapshot(int keyboardHash, int mouseHash, int gamepadHash, int comHash)
{
_keyboardHash = keyboardHash;
_mouseHash = mouseHash;
_gamepadHash = gamepadHash;
_comHash = comHash;
}
public bool Equals(InputSnapshot other)
{
return _keyboardHash == other._keyboardHash
&& _mouseHash == other._mouseHash
&& _gamepadHash == other._gamepadHash
&& _comHash == other._comHash;
}
}
///
/// Получает текущее время таймаута
///
public float GetInactivityTimeout() => inactivityTimeout;
///
/// Получает время с момента последнего ввода
///
public float GetTimeSinceLastInput() => Time.unscaledTime - lastInputTime;
}
}