374 lines
20 KiB
C#
374 lines
20 KiB
C#
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.onInputRecieved.AddListener(UpdateInputState);
|
||
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.Log($"Что то в инициализациии COMinput {e.ToString()}");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Загружает конфигурацию маппинга пинов из файла elektro_version.rr
|
||
/// </summary>
|
||
private void LoadPinMappingConfiguration()
|
||
{
|
||
string elektroVersion = ExternalFilesGetter.GetElektroVersion();
|
||
|
||
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)
|
||
{
|
||
//DebugWindow.Message($"В инпут пришло: {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;
|
||
}
|
||
|
||
//Проверка прошел ли приз через сенсор
|
||
int prizeSensorPin = GetPhysicalPin(ButtonPinMapping.hasPrize);
|
||
char sensorPositive = ExternalFilesGetter.GetSensorPositiveValue();
|
||
if (prizeSensorPin < input.Length && input[prizeSensorPin] == sensorPositive)
|
||
{
|
||
PlayerPrefs.SetInt("PrizeGivenBuffer", 1);
|
||
}
|
||
|
||
}
|
||
|
||
// Потребление "Нажатия"/"Отжатия". Возвращает что там было и чистит флаг из буфера
|
||
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}");
|
||
}
|
||
}
|
||
} |