388 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<ButtonPinMapping, int> pinMappings = new Dictionary<ButtonPinMapping, int>();
// Обратный словарь для быстрого доступа
private Dictionary<int, ButtonPinMapping> reverseMappings = new Dictionary<int, ButtonPinMapping>();
//Свойство для определения текущего игрока
//Должно возвращать 1 или 2
//Используется для получения состояния кнопок с пометкой ...CurrentPlayer
private int currentPlayer => gm.currentPlayer == CellState.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;
}
/// <summary>
/// Загружает конфигурацию маппинга пинов из файла elektro_version.rr
/// </summary>
private void LoadPinMappingConfiguration()
{
int elektroVersion = ConfigManager.Config.ElectronicVersion;
Debug.Log($"Определена версия электроники: {elektroVersion}");
// Создаем маппинг в зависимости от версии
if (elektroVersion == 1)
{
// Версия 1 (старая распиновка)
pinMappings[ButtonPinMapping.up1] = 0;
pinMappings[ButtonPinMapping.right1] = 1;
pinMappings[ButtonPinMapping.down1] = 2;
pinMappings[ButtonPinMapping.left1] = 3;
pinMappings[ButtonPinMapping.action1] = 4;
pinMappings[ButtonPinMapping.up2] = 5;
pinMappings[ButtonPinMapping.right2] = 6;
pinMappings[ButtonPinMapping.down2] = 7;
pinMappings[ButtonPinMapping.left2] = 8;
pinMappings[ButtonPinMapping.action2] = 9;
pinMappings[ButtonPinMapping.startOnePlayer] = 10;
pinMappings[ButtonPinMapping.startTwoPlayers] = 11;
pinMappings[ButtonPinMapping.secret] = 12;
pinMappings[ButtonPinMapping.extra] = 13;
pinMappings[ButtonPinMapping.hasPrize] = 14;
pinMappings[ButtonPinMapping.prizeGiven] = 15;
Debug.Log($"Используется версия 1 распиновки. Полученная версия: {elektroVersion}");
}
else
{
// Версия 2 (Дефолт)
pinMappings[ButtonPinMapping.up1] = 0;
pinMappings[ButtonPinMapping.right1] = 1;
pinMappings[ButtonPinMapping.down1] = 2;
pinMappings[ButtonPinMapping.left1] = 3;
pinMappings[ButtonPinMapping.action1] = 4;
pinMappings[ButtonPinMapping.up2] = 10;
pinMappings[ButtonPinMapping.right2] = 7;
pinMappings[ButtonPinMapping.down2] = 8;
pinMappings[ButtonPinMapping.left2] = 9;
pinMappings[ButtonPinMapping.action2] = 11;
pinMappings[ButtonPinMapping.startOnePlayer] = 5;
pinMappings[ButtonPinMapping.startTwoPlayers] = 6;
pinMappings[ButtonPinMapping.secret] = 12;
pinMappings[ButtonPinMapping.extra] = 13;
pinMappings[ButtonPinMapping.hasPrize] = 14;
pinMappings[ButtonPinMapping.prizeGiven] = 15;
Debug.Log("Используется версия 2 распиновки (новая)");
}
// Создаем обратный маппинг для быстрого поиска
foreach (var mapping in pinMappings)
{
reverseMappings[mapping.Value] = mapping.Key;
}
}
/// <summary>
/// Получает физический номер пина для указанной кнопки
/// </summary>
private int GetPhysicalPin(ButtonPinMapping button)
{
if (pinMappings.TryGetValue(button, out int pin))
return pin;
// Возвращаем значение по умолчанию (для обратной совместимости)
return (int)button;
}
/// <summary>
/// Получает ButtonPinMapping для физического пина
/// </summary>
private ButtonPinMapping? GetButtonForPhysicalPin(int physicalPin)
{
if (reverseMappings.TryGetValue(physicalPin, out ButtonPinMapping button))
return button;
// Если пин не найден в маппинге, пытаемся конвертировать напрямую
if (Enum.IsDefined(typeof(ButtonPinMapping), physicalPin))
return (ButtonPinMapping)physicalPin;
return null;
}
#region PUBLIC_API
//ЭТА ГРУППА МЕТОДОВ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ ЛЮБОЙ КНОПКИ ПО ПИНУ (enum ButtonPinMapping)
/// <summary>
/// Получает состояние конкретной кнопки по её физическому пину.
/// Возвращает true если кнопка в данный момент нажата.
/// </summary>
/// <param name="button">Пин кнопки на периферийной плате</param>
/// <returns>True если кнопка нажата в текущем кадре</returns>
public override bool GetButton(ButtonPinMapping button) => buttonState[GetPhysicalPin(button)];
/// <summary>
/// Проверяет, была ли кнопка нажата в начале текущего кадра.
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
/// </summary>
/// <param name="button">Пин кнопки на периферийной плате</param>
/// <returns>True если кнопка была нажата в этом кадре</returns>
public override bool GetButtonDown(ButtonPinMapping button) => ConsumeButtonEvent(buttonDown, GetPhysicalPin(button));
/// <summary>
/// Проверяет, была ли кнопка отпущена в начале текущего кадра.
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
/// </summary>
/// <param name="button">Пин кнопки на периферийной плате</param>
/// <returns>True если кнопка была отпущена в этом кадре</returns>
public override bool GetButtonUp(ButtonPinMapping button) => ConsumeButtonEvent(buttonUp, GetPhysicalPin(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)
{
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(ButtonPinMapping.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, ButtonPinMapping button)
{
int physicalPin = GetPhysicalPin(button);
bool result = buffer[physicalPin];
buffer[physicalPin] = false;
return result;
}
/// <summary>
/// Вспомогательный метод для обработки одноразовых событий кнопок по числовому индексу.
/// Возвращает текущее значение флага и сбрасывает его для предотвращения повторной обработки.
/// </summary>
/// <param name="buffer">Буфер событий (buttonDown или buttonUp)</param>
/// <param name="pinIndex">Числовой индекс пина</param>
/// <returns>Состояние флага события до сброса</returns>
private bool ConsumeButtonEvent(bool[] buffer, int pinIndex)
{
bool result = buffer[pinIndex];
buffer[pinIndex] = false;
return result;
}
/// <summary>
/// Метод для отладки - печатает текущую конфигурацию маппинга
/// </summary>
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;
}
}