using System; using RR.Game2D.Core.Managers; using UnityEngine; namespace RR.Common.Controls { 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]; //Свойство для определения текущего игрока //Должно возвращать 1 или 2 //Используется для получения состояния кнопок с пометкой ...CurrentPlayer private int currentPlayer => 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ. private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите. public override bool Initialize() { try { gm = GameManager.Instance; //ОБЯЗАТЕЛЬНЫЙ БЛОК ComWrapper.OnInputReceived.AddListener(UpdateInputState); } catch (Exception e) { GameManager.Logger.Log("COMInput", $"Что то в COMInput {e.ToString()}"); return false; } return true; } #region PUBLIC_API //ЭТА ГРУППА МЕТОДОВ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ ЛЮБОЙ КНОПКИ ПО ПИНУ (enum ButtonPinMapping) /// /// Получает состояние конкретной кнопки по её физическому пину. /// Возвращает true если кнопка в данный момент нажата. /// /// Пин кнопки на периферийной плате /// True если кнопка нажата в текущем кадре public override bool GetButton(ButtonPinMapping button) => buttonState[(int)button]; /// /// Проверяет, была ли кнопка нажата в начале текущего кадра. /// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает. /// /// Пин кнопки на периферийной плате /// True если кнопка была нажата в этом кадре public override bool GetButtonDown(ButtonPinMapping button) => ConsumeButtonEvent(buttonDown, button); /// /// Проверяет, была ли кнопка отпущена в начале текущего кадра. /// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает. /// /// Пин кнопки на периферийной плате /// True если кнопка была отпущена в этом кадре public override bool GetButtonUp(ButtonPinMapping button) => ConsumeButtonEvent(buttonUp, 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 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" 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; } if (input[^1] == POSITIVE) PlayerPrefs.SetInt("PrizeGivenBuffer", 1); //пишем в буфер чтобы между кадрами и порядком вызова не проебать // DebugWindow.Message("Инпуты: " + string.Join("", buttonState));//можно вырезать } // Потребление "Нажатия"/"Отжатия". Возвращает что там было и чистит флаг из буфера private bool ConsumeButtonEvent(bool[] buffer, ButtonPinMapping button) { bool result = buffer[(int)button]; buffer[(int)button] = false; return result; } /// /// Вспомогательный метод для обработки одноразовых событий кнопок по числовому индексу. /// Возвращает текущее значение флага и сбрасывает его для предотвращения повторной обработки. /// /// Буфер событий (buttonDown или buttonUp) /// Числовой индекс пина /// Состояние флага события до сброса private bool ConsumeButtonEvent(bool[] buffer, int pinIndex) { bool result = buffer[pinIndex]; buffer[pinIndex] = false; return result; } } }