using System; using System.Collections.Generic; using UnityEngine; public class COMinput : InputSource { //Количество = цифровые + 2 датчика. //На момент написания количество цифровых входов 7*N. //Настроить количество пинов под свою настройку платы private const int PIN_COUNT = 16; 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]; // Словарь для маппинга ButtonPinMapping -> физический пин private Dictionary pinMappings = new Dictionary(); // Обратный словарь для быстрого доступа private Dictionary reverseMappings = new Dictionary(); //Свойство для определения текущего игрока //Должно возвращать 1 или 2 //Используется для получения состояния кнопок с пометкой ...CurrentPlayer private int currentPlayer => gm.currentPlayer == Team.Player1 ? 1 : 2;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ. private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите. public override bool Initialize() { try { gm = GameManager.Instance; // Загружаем конфигурацию пинов LoadPinMappingConfiguration(); ComWrapper.InputRecieved += UpdateInputState; } catch (Exception e) { Debug.Log($"Что то в инициализациии COMinput {e.ToString()}"); return false; } return true; } /// /// Загружает конфигурацию маппинга пинов из файла elektro_version.rr /// private void LoadPinMappingConfiguration() { int elektroVersion = ConfigManager.Config.ElectronicVersion; Debug.Log($"Определена версия электроники: {elektroVersion}"); // Создаем маппинг в зависимости от версии if (elektroVersion == 1) { // Версия 1 (старая распиновка) pinMappings[InSignal.up1] = 0; pinMappings[InSignal.right1] = 1; pinMappings[InSignal.down1] = 2; pinMappings[InSignal.left1] = 3; pinMappings[InSignal.action1] = 4; pinMappings[InSignal.up2] = 5; pinMappings[InSignal.right2] = 6; pinMappings[InSignal.down2] = 7; pinMappings[InSignal.left2] = 8; pinMappings[InSignal.action2] = 9; pinMappings[InSignal.startOnePlayer] = 10; pinMappings[InSignal.startTwoPlayers] = 11; pinMappings[InSignal.secret] = 12; pinMappings[InSignal.extra] = 13; pinMappings[InSignal.hasPrize] = 14; pinMappings[InSignal.prizeGiven] = 15; Debug.Log($"Используется версия 1 распиновки. Полученная версия: {elektroVersion}"); } else { // Версия 2 (Дефолт) pinMappings[InSignal.up1] = 0; pinMappings[InSignal.right1] = 1; pinMappings[InSignal.down1] = 2; pinMappings[InSignal.left1] = 3; pinMappings[InSignal.action1] = 4; pinMappings[InSignal.up2] = 10; pinMappings[InSignal.right2] = 7; pinMappings[InSignal.down2] = 8; pinMappings[InSignal.left2] = 9; pinMappings[InSignal.action2] = 11; pinMappings[InSignal.startOnePlayer] = 5; pinMappings[InSignal.startTwoPlayers] = 6; pinMappings[InSignal.secret] = 12; pinMappings[InSignal.extra] = 13; pinMappings[InSignal.hasPrize] = 14; pinMappings[InSignal.prizeGiven] = 15; Debug.Log("Используется версия 2 распиновки (новая)"); } // Создаем обратный маппинг для быстрого поиска foreach (var mapping in pinMappings) { reverseMappings[mapping.Value] = mapping.Key; } } /// /// Получает физический номер пина для указанной кнопки /// private int GetPhysicalPin(InSignal button) { if (pinMappings.TryGetValue(button, out int pin)) return pin; // Возвращаем значение по умолчанию (для обратной совместимости) return (int)button; } /// /// Получает ButtonPinMapping для физического пина /// private InSignal? GetButtonForPhysicalPin(int physicalPin) { if (reverseMappings.TryGetValue(physicalPin, out InSignal button)) return button; // Если пин не найден в маппинге, пытаемся конвертировать напрямую if (Enum.IsDefined(typeof(InSignal), physicalPin)) return (InSignal)physicalPin; return null; } #region PUBLIC_API //ЭТА ГРУППА МЕТОДОВ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ ЛЮБОЙ КНОПКИ ПО ПИНУ (enum ButtonPinMapping) /// /// Получает состояние конкретной кнопки по её физическому пину. /// Возвращает true если кнопка в данный момент нажата. /// /// Пин кнопки на периферийной плате /// True если кнопка нажата в текущем кадре public override bool GetButton(InSignal button) => buttonState[GetPhysicalPin(button)]; /// /// Проверяет, была ли кнопка нажата в начале текущего кадра. /// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает. /// /// Пин кнопки на периферийной плате /// True если кнопка была нажата в этом кадре public override bool GetButtonDown(InSignal button) => ConsumeButtonEvent(buttonDown, GetPhysicalPin(button)); /// /// Проверяет, была ли кнопка отпущена в начале текущего кадра. /// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает. /// /// Пин кнопки на периферийной плате /// True если кнопка была отпущена в этом кадре public override bool GetButtonUp(InSignal button) => ConsumeButtonEvent(buttonUp, GetPhysicalPin(button)); /// /// Получает состояние кнопки по числовому индексу пина напрямую из буфера состояний. /// Обходит перечисление ButtonPinMapping для прямого доступа к пинам по индексу. /// /// Числовой индекс пина (0-15) /// true если кнопка нажата в текущий момент /// /// Используется когда необходим прямой доступ к пинам без приведения к перечислению. /// Подходит для сценариев где количество пинов может превышать размер перечисления. /// public override bool GetButton(int pinIndex) => buttonState[pinIndex]; /// /// Проверяет, была ли кнопка нажата в начале текущего кадра по числовому индексу пина. /// Возвращает true только в первом кадре после нажатия и сбрасывает флаг события. /// /// Числовой индекс пина (0-15) /// true если кнопка была нажата в этом кадре /// /// Метод "потребляет" событие - после чтения флаг сбрасывается. /// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий. /// public override bool GetButtonDown(int pinIndex) => ConsumeButtonEvent(buttonDown, pinIndex); /// /// Проверяет, была ли кнопка отпущена в начале текущего кадра по числовому индексу пина. /// Возвращает true только в первом кадре после отпускания и сбрасывает флаг события. /// /// Числовой индекс пина (0-15) /// true если кнопка была отпущена в этом кадре /// /// Метод "потребляет" событие - после чтения флаг сбрасывается. /// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий. /// public override bool GetButtonUp(int pinIndex) => ConsumeButtonEvent(buttonUp, pinIndex); // ------------------------------------------------------------------------------------ //ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК АКТИВНОГО ИГРОКА (проверка по enum PlayerAction) //Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются /// /// Получает состояние действия для текущего игрока. /// Возвращает true если действие активно (кнопка нажата) у текущего игрока. /// /// Действие игрока (up, down, left, right, action, extra) /// True если действие активно у текущего игрока public override bool GetButtonCurrentPlayer(PlayerAction button) => GetButton(GetButtonForCurrentPlayer(button)); /// /// Проверяет, было ли действие начато текущим игроком в этом кадре. /// Возвращает true только в первом кадре после начала действия. /// /// Действие игрока /// True если действие было начато в этом кадре public override bool GetButtonDownCurrentPlayer(PlayerAction button) => GetButtonDown(GetButtonForCurrentPlayer(button)); /// /// Проверяет, было ли действие завершено текущим игроком в этом кадре. /// Возвращает true только в первом кадре после завершения действия. /// /// Действие игрока /// True если действие было завершено в этом кадре public override bool GetButtonUpCurrentPlayer(PlayerAction button) => GetButtonUp(GetButtonForCurrentPlayer(button)); // ------------------------------------------------------------------------------------ //ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК ЛЮБОГО ИГРОКА (проверка по enum PlayerAction) //Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются /// /// Получает состояние действия для любого из игроков. /// Возвращает true если действие активно у первого ИЛИ второго игрока. /// /// Действие игрока /// True если действие активно у любого игрока public override bool GetButtonAnyPlayer(PlayerAction button) => CheckBothPlayers(button, GetButton); /// /// Проверяет, было ли действие начато любым игроком в этом кадре. /// Возвращает true если первый ИЛИ второй игрок начали действие в этом кадре. /// /// Действие игрока /// True если действие было начато любым игроком в этом кадре public override bool GetButtonDownAnyPlayer(PlayerAction button) => CheckBothPlayers(button, GetButtonDown); /// /// Проверяет, было ли действие завершено любым игроком в этом кадре. /// Возвращает true если первый ИЛИ второй игрок завершили действие в этом кадре. /// /// Действие игрока /// True если действие было завершено любым игроком в этом кадре public override bool GetButtonUpAnyPlayer(PlayerAction button) => CheckBothPlayers(button, GetButtonUp); #endregion //==========ХЕЛПЕРЫ========== // Проверка нажатия с обоих контроллеров игроков private bool CheckBothPlayers(PlayerAction action, Func buttonFunc) { var buttons = _buttonByAction(action); return buttonFunc(buttons.player1button) || buttonFunc(buttons.player2button); } // Проверка нажатия с контроллера текущего игрока private InSignal GetButtonForCurrentPlayer(PlayerAction button) { var buttons = _buttonByAction(button); return currentPlayer == 1 ? buttons.player1button : buttons.player2button; } // Маппер из действий игрока => пины платы private (InSignal player1button, InSignal player2button) _buttonByAction(PlayerAction playerAction) => playerAction switch { PlayerAction.up => (InSignal.up1, InSignal.up2), PlayerAction.down => (InSignal.down1, InSignal.down2), PlayerAction.left => (InSignal.left1, InSignal.left2), PlayerAction.right => (InSignal.right1, InSignal.right2), PlayerAction.action => (InSignal.action1, InSignal.action2), PlayerAction.extra => (InSignal.extra, InSignal.extra), // одинаковая кнопка _ => throw new ArgumentException($"Unknown player action: {playerAction}") }; // Обновляет состояния инпута когда приходит новый ввод с платы. Дергается событием. private void UpdateInputState(string input) { input = input.Substring(0, PIN_COUNT); for (int i = 0; i < input.Length; 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; } //Проверка прошел ли приз через сенсор int prizeSensorPin = GetPhysicalPin(InSignal.hasPrize); char sensorPositive = ConfigManager.Config.SensorPositiveValue.ToString()[0]; if (prizeSensorPin < input.Length && input[prizeSensorPin] == sensorPositive) { Debug.Log($"Считано положительное значени датчика приза ({sensorPositive}). Устанавливаю значение 1 в буфер"); PlayerPrefs.SetInt("PrizeGivenBuffer", 1); Debug.Log($"Установлено значение буфера {PlayerPrefs.GetInt("PrizeGivenBuffer")}"); } } // Потребление "Нажатия"/"Отжатия". Возвращает что там было и чистит флаг из буфера private bool ConsumeButtonEvent(bool[] buffer, InSignal button) { int physicalPin = GetPhysicalPin(button); bool result = buffer[physicalPin]; buffer[physicalPin] = false; return result; } /// /// Вспомогательный метод для обработки одноразовых событий кнопок по числовому индексу. /// Возвращает текущее значение флага и сбрасывает его для предотвращения повторной обработки. /// /// Буфер событий (buttonDown или buttonUp) /// Числовой индекс пина /// Состояние флага события до сброса private bool ConsumeButtonEvent(bool[] buffer, int pinIndex) { bool result = buffer[pinIndex]; buffer[pinIndex] = false; return result; } /// /// Метод для отладки - печатает текущую конфигурацию маппинга /// public void PrintPinMappingConfiguration() { Debug.Log("Текущая конфигурация маппинга пинов:"); foreach (var mapping in pinMappings) { Debug.Log($"{mapping.Key} -> Физический пин {mapping.Value}"); } } public override bool AnyButton() { //отсекаем датчики for (int i = 0; i < pinMappings.Count - 2; i++) { if (buttonState[i]) { return true; } } return false; } }