Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Рефакторинг системы подсветки: вместо длительности в секундах теперь используется количество циклов (float). Добавлена таблица длительности циклов эффектов `LightCycleDurations` для пересчёта циклов в секунды. Префикс команд подсветки изменён с `85` на `86`. - `LightSequence`: добавлены `Pause()` / `Resume()` с запоминанием текущего шага последовательности; возобновление продолжает с сохранённой позиции. - `LightController`: добавлены `PauseAll()` / `ResumeAll()` для приостановки и восстановления подсветки и мигания шляпы при отключении/переподключении платы. Свойства `IsSequencePaused`, `IsBlinkPaused`. - `ComDeviceClient`: после успешной авторизации отправляются команды `890003` и `8600`. Добавлен запрос MAC-адреса через команду `06` (ответ — 12 hex-символов без префикса). Новое событие `ConnectionLost` при начале KeepAlive-реконнекта. - `ComHub`: очередь `_disconnectedQueue` с типом `DeviceDisconnectMessage` (DeviceId + Role + Mac) — сохраняет идентификатор устройства до его удаления из словаря. Подписка на `ConnectionLost`. Добавлен `GetDeviceMac()`. - `ComSystemManager`: при инициализации, если плата не обнаружена, подсветка сразу ставится на паузу. При отвале дефолтного устройства — `PauseAll()`, при переподключении — сравнение по MAC и роли через `IsSameDevice()`: та же плата → `ResumeAll()`, другая → `Blink()` с нуля. Добавлены событие `OnDeviceDisconnected` и метод `GetDeviceMac()`. - `COMInput`: при событии `OnDeviceDisconnected` сбрасываются все состояния кнопок через `ResetAllButtons()`, генерируя `buttonUp` для зажатых кнопок. Устраняет баг залипания управления после переподключения платы. - `GameManager`: расширена последовательность световых эффектов в меню (добавлены `DiagonalWave3`, `WaveUp`, `OrangeWave`); вызовы `SetLight` для game over/won переведены на float-циклы. - Обновлены `ARCHITECTURE.md` и `README.md`: переписана документация на актуальную структуру проекта и текущие возможности игры.
287 lines
17 KiB
C#
287 lines
17 KiB
C#
using System;
|
||
using RR.Game2D.Core.Managers;
|
||
using UnityEngine;
|
||
|
||
namespace RR.Common.Controls
|
||
{
|
||
public class COMInput : InputSource
|
||
{
|
||
//Количество = цифровые + 2 датчика.
|
||
//На момент написания количество цифровых входов соответствует разрядности платы
|
||
//Настроить количество пинов под свою настройку платы
|
||
private const int PIN_COUNT = 26;
|
||
|
||
private const char POSITIVE = '0';
|
||
private const char NEGATIVE = '1';
|
||
|
||
private bool[] buttonState = new bool[PIN_COUNT];
|
||
private bool[] buttonDown = new bool[PIN_COUNT];
|
||
private bool[] buttonUp = new bool[PIN_COUNT];
|
||
|
||
//Свойство для определения текущего игрока
|
||
//Должно возвращать 1 или 2
|
||
//Используется для получения состояния кнопок с пометкой ...CurrentPlayer
|
||
private int currentPlayer => 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ.
|
||
private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите.
|
||
|
||
public override bool Initialize()
|
||
{
|
||
try
|
||
{
|
||
gm = GameManager.Instance;
|
||
|
||
//ОБЯЗАТЕЛЬНЫЙ БЛОК
|
||
ComSystemManager.OnInputReceived.AddListener(UpdateInputState);
|
||
ComSystemManager.OnDeviceDisconnected?.AddListener(OnDeviceDisconnected);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
GameManager.Logger.Log("COMInput", $"Что то в COMInput {e.ToString()}");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Сбрасывает все состояния кнопок в «не нажато».
|
||
/// Вызывается при потере связи с платой.
|
||
/// </summary>
|
||
private void ResetAllButtons()
|
||
{
|
||
for (int i = 0; i < PIN_COUNT; i++)
|
||
{
|
||
if (buttonState[i])
|
||
{
|
||
buttonUp[i] = true;
|
||
}
|
||
buttonState[i] = false;
|
||
buttonDown[i] = false;
|
||
}
|
||
}
|
||
|
||
private void OnDeviceDisconnected(string deviceId)
|
||
{
|
||
ResetAllButtons();
|
||
}
|
||
|
||
#region PUBLIC_API
|
||
|
||
//ЭТА ГРУППА МЕТОДОВ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ ЛЮБОЙ КНОПКИ ПО ПИНУ (enum ButtonPinMapping)
|
||
|
||
/// <summary>
|
||
/// Получает состояние конкретной кнопки по её физическому пину.
|
||
/// Возвращает true если кнопка в данный момент нажата.
|
||
/// </summary>
|
||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||
/// <returns>True если кнопка нажата в текущем кадре</returns>
|
||
public override bool GetButton(ButtonPinMapping button) => buttonState[(int)button];
|
||
|
||
/// <summary>
|
||
/// Проверяет, была ли кнопка нажата в начале текущего кадра.
|
||
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
|
||
/// </summary>
|
||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||
/// <returns>True если кнопка была нажата в этом кадре</returns>
|
||
public override bool GetButtonDown(ButtonPinMapping button) => ConsumeButtonEvent(buttonDown, button);
|
||
|
||
/// <summary>
|
||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра.
|
||
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
|
||
/// </summary>
|
||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||
/// <returns>True если кнопка была отпущена в этом кадре</returns>
|
||
public override bool GetButtonUp(ButtonPinMapping button) => ConsumeButtonEvent(buttonUp, button);
|
||
|
||
|
||
/// <summary>
|
||
/// Получает состояние кнопки по числовому индексу пина напрямую из буфера состояний.
|
||
/// Обходит перечисление ButtonPinMapping для прямого доступа к пинам по индексу.
|
||
/// </summary>
|
||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||
/// <returns>true если кнопка нажата в текущий момент</returns>
|
||
/// <remarks>
|
||
/// Используется когда необходим прямой доступ к пинам без приведения к перечислению.
|
||
/// Подходит для сценариев где количество пинов может превышать размер перечисления.
|
||
/// </remarks>
|
||
public override bool GetButton(int pinIndex) => buttonState[pinIndex];
|
||
|
||
/// <summary>
|
||
/// Проверяет, была ли кнопка нажата в начале текущего кадра по числовому индексу пина.
|
||
/// Возвращает true только в первом кадре после нажатия и сбрасывает флаг события.
|
||
/// </summary>
|
||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||
/// <returns>true если кнопка была нажата в этом кадре</returns>
|
||
/// <remarks>
|
||
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
|
||
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
|
||
/// </remarks>
|
||
public override bool GetButtonDown(int pinIndex) => ConsumeButtonEvent(buttonDown, pinIndex);
|
||
|
||
/// <summary>
|
||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра по числовому индексу пина.
|
||
/// Возвращает true только в первом кадре после отпускания и сбрасывает флаг события.
|
||
/// </summary>
|
||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||
/// <returns>true если кнопка была отпущена в этом кадре</returns>
|
||
/// <remarks>
|
||
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
|
||
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
|
||
/// </remarks>
|
||
public override bool GetButtonUp(int pinIndex) => ConsumeButtonEvent(buttonUp, pinIndex);
|
||
// ------------------------------------------------------------------------------------
|
||
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК АКТИВНОГО ИГРОКА (проверка по enum PlayerAction)
|
||
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
|
||
|
||
/// <summary>
|
||
/// Получает состояние действия для текущего игрока.
|
||
/// Возвращает true если действие активно (кнопка нажата) у текущего игрока.
|
||
/// </summary>
|
||
/// <param name="button">Действие игрока (up, down, left, right, action, extra)</param>
|
||
/// <returns>True если действие активно у текущего игрока</returns>
|
||
public override bool GetButtonCurrentPlayer(PlayerAction button)
|
||
=> GetButton(GetButtonForCurrentPlayer(button));
|
||
|
||
/// <summary>
|
||
/// Проверяет, было ли действие начато текущим игроком в этом кадре.
|
||
/// Возвращает true только в первом кадре после начала действия.
|
||
/// </summary>
|
||
/// <param name="button">Действие игрока</param>
|
||
/// <returns>True если действие было начато в этом кадре</returns>
|
||
public override bool GetButtonDownCurrentPlayer(PlayerAction button)
|
||
=> GetButtonDown(GetButtonForCurrentPlayer(button));
|
||
|
||
/// <summary>
|
||
/// Проверяет, было ли действие завершено текущим игроком в этом кадре.
|
||
/// Возвращает true только в первом кадре после завершения действия.
|
||
/// </summary>
|
||
/// <param name="button">Действие игрока</param>
|
||
/// <returns>True если действие было завершено в этом кадре</returns>
|
||
public override bool GetButtonUpCurrentPlayer(PlayerAction button)
|
||
=> GetButtonUp(GetButtonForCurrentPlayer(button));
|
||
|
||
// ------------------------------------------------------------------------------------
|
||
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК ЛЮБОГО ИГРОКА (проверка по enum PlayerAction)
|
||
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
|
||
|
||
/// <summary>
|
||
/// Получает состояние действия для любого из игроков.
|
||
/// Возвращает true если действие активно у первого ИЛИ второго игрока.
|
||
/// </summary>
|
||
/// <param name="button">Действие игрока</param>
|
||
/// <returns>True если действие активно у любого игрока</returns>
|
||
public override bool GetButtonAnyPlayer(PlayerAction button)
|
||
=> CheckBothPlayers(button, GetButton);
|
||
|
||
/// <summary>
|
||
/// Проверяет, было ли действие начато любым игроком в этом кадре.
|
||
/// Возвращает true если первый ИЛИ второй игрок начали действие в этом кадре.
|
||
/// </summary>
|
||
/// <param name="button">Действие игрока</param>
|
||
/// <returns>True если действие было начато любым игроком в этом кадре</returns>
|
||
public override bool GetButtonDownAnyPlayer(PlayerAction button)
|
||
=> CheckBothPlayers(button, GetButtonDown);
|
||
|
||
/// <summary>
|
||
/// Проверяет, было ли действие завершено любым игроком в этом кадре.
|
||
/// Возвращает true если первый ИЛИ второй игрок завершили действие в этом кадре.
|
||
/// </summary>
|
||
/// <param name="button">Действие игрока</param>
|
||
/// <returns>True если действие было завершено любым игроком в этом кадре</returns>
|
||
public override bool GetButtonUpAnyPlayer(PlayerAction button)
|
||
=> CheckBothPlayers(button, GetButtonUp);
|
||
#endregion
|
||
|
||
|
||
//==========ХЕЛПЕРЫ==========
|
||
// Проверка нажатия с обоих контроллеров игроков
|
||
private bool CheckBothPlayers(PlayerAction action, Func<ButtonPinMapping, bool> buttonFunc)
|
||
{
|
||
var buttons = _buttonByAction(action);
|
||
return buttonFunc(buttons.player1button) || buttonFunc(buttons.player2button);
|
||
}
|
||
|
||
// Проверка нажатия с контроллера текущего игрока
|
||
private ButtonPinMapping GetButtonForCurrentPlayer(PlayerAction button)
|
||
{
|
||
var buttons = _buttonByAction(button);
|
||
return currentPlayer == 1 ? buttons.player1button : buttons.player2button;
|
||
}
|
||
|
||
// Маппер из действий игрока => пины платы
|
||
private (ButtonPinMapping player1button, ButtonPinMapping player2button) _buttonByAction(PlayerAction playerAction) =>
|
||
playerAction switch
|
||
{
|
||
PlayerAction.up => (ButtonPinMapping.up1, ButtonPinMapping.up2),
|
||
PlayerAction.down => (ButtonPinMapping.down1, ButtonPinMapping.down2),
|
||
PlayerAction.left => (ButtonPinMapping.left1, ButtonPinMapping.left2),
|
||
PlayerAction.right => (ButtonPinMapping.right1, ButtonPinMapping.right2),
|
||
PlayerAction.action => (ButtonPinMapping.action1, ButtonPinMapping.action2),
|
||
PlayerAction.extra => (ButtonPinMapping.extra, ButtonPinMapping.extra), // одинаковая кнопка
|
||
_ => throw new ArgumentException($"Unknown player action: {playerAction}")
|
||
|
||
};
|
||
|
||
// Обновляет состояния инпута когда приходит новый ввод с платы. Дергается событием.
|
||
private void UpdateInputState(string input)
|
||
{
|
||
// Будем принимать в нативном формате by Pasha. 0 - нажато 1 - не нажато (чтобы преобразовывать циклом)
|
||
// В параметрприходит строка вида "1111011111011111"
|
||
|
||
// Ограничиваем количество обрабатываемых символов размером массива
|
||
int maxIndex = Math.Min(input.Length, PIN_COUNT);
|
||
|
||
for (int i = 0; i < maxIndex; i++)
|
||
{
|
||
// Получаем новое состояние пина
|
||
bool newState = input[i] == POSITIVE;
|
||
|
||
// Состояние пина изменилось => пишем в буферы
|
||
if (newState != buttonState[i])
|
||
{
|
||
if (newState) // Пин был зажат
|
||
{
|
||
buttonDown[i] = true;
|
||
buttonUp[i] = false;
|
||
}
|
||
else // Пин был отжат
|
||
{
|
||
buttonDown[i] = false;
|
||
buttonUp[i] = true;
|
||
}
|
||
}
|
||
|
||
buttonState[i] = newState;
|
||
|
||
}
|
||
|
||
// Проверяем последний пин только если он в пределах массива
|
||
if (input.Length > 0 && input.Length <= PIN_COUNT && input[^1] == POSITIVE)
|
||
PlayerPrefs.SetInt("PrizeGivenBuffer", 1); //пишем в буфер чтобы между кадрами и порядком вызова не проебать
|
||
|
||
// DebugWindow.Message("Инпуты: " + string.Join("", buttonState));//можно вырезать
|
||
}
|
||
|
||
// Потребление "Нажатия"/"Отжатия". Возвращает что там было и чистит флаг из буфера
|
||
private static bool ConsumeButtonEvent(bool[] buffer, ButtonPinMapping button)
|
||
{
|
||
bool result = buffer[(int)button];
|
||
buffer[(int)button] = false;
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Вспомогательный метод для обработки одноразовых событий кнопок по числовому индексу.
|
||
/// Возвращает текущее значение флага и сбрасывает его для предотвращения повторной обработки.
|
||
/// </summary>
|
||
/// <param name="buffer">Буфер событий (buttonDown или buttonUp)</param>
|
||
/// <param name="pinIndex">Числовой индекс пина</param>
|
||
/// <returns>Состояние флага события до сброса</returns>
|
||
private static bool ConsumeButtonEvent(bool[] buffer, int pinIndex)
|
||
{
|
||
bool result = buffer[pinIndex];
|
||
buffer[pinIndex] = false;
|
||
return result;
|
||
}
|
||
}
|
||
}
|