Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
329 lines
10 KiB
C#
329 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.InputSystem;
|
||
using UnityEngine.InputSystem.Layouts;
|
||
using UnityEngine.InputSystem.LowLevel;
|
||
|
||
namespace RR.Common.Controls
|
||
{
|
||
/// <summary>
|
||
/// Адаптер между системой ввода COM и Unity Input System.
|
||
/// Создаёт виртуальный Gamepad и транслирует состояние кнопок из:
|
||
/// - действий игроков (PlayerAction)
|
||
/// - физических пинов (int индексы)
|
||
/// </summary>
|
||
[DefaultExecutionOrder(-100)]
|
||
public class ComInputToInputSystemAdapter : MonoBehaviour
|
||
{
|
||
private const string VirtualGamepadProduct = "COM Bridge Gamepad";
|
||
private const string VirtualGamepadManufacturer = "Robotic Retailers";
|
||
|
||
[Header("Источник состояния")]
|
||
[Tooltip("Если true — использовать In.GetButtonAnyPlayer, иначе — In.GetButtonCurrentPlayer")]
|
||
[SerializeField] private bool useAnyPlayer = true;
|
||
|
||
[Header("Маппинг действий (PlayerAction -> Gamepad)")]
|
||
[SerializeField] private List<ActionBinding> actionBindings = new();
|
||
|
||
[Header("Маппинг пинов (индекс пина -> Gamepad)")]
|
||
[SerializeField] private List<PinBinding> pinBindings = new();
|
||
|
||
[Header("Отладка")]
|
||
[SerializeField] private bool logCreateDevice = true;
|
||
|
||
private static Gamepad _virtualPad;
|
||
private static bool _deviceCreated;
|
||
|
||
private GamepadState _currentState;
|
||
|
||
#region Структуры маппинга
|
||
|
||
[Serializable]
|
||
public struct ActionBinding
|
||
{
|
||
public PlayerAction action;
|
||
public BindingTarget target;
|
||
public GamepadButton button;
|
||
public DpadDirection dpadDirection;
|
||
}
|
||
|
||
[Serializable]
|
||
public struct PinBinding
|
||
{
|
||
public int pinIndex;
|
||
public BindingTarget target;
|
||
public GamepadButton button;
|
||
public DpadDirection dpadDirection;
|
||
}
|
||
|
||
public enum BindingTarget { Button, Dpad }
|
||
public enum DpadDirection { Up, Down, Left, Right }
|
||
|
||
#endregion
|
||
|
||
#region Unity Lifecycle
|
||
|
||
// Если нет общего инициализатора, можно раскомментировать Awake
|
||
// private void Awake()
|
||
// {
|
||
// Initialize();
|
||
// }
|
||
|
||
private void OnEnable()
|
||
{
|
||
EnsureVirtualGamepadExists();
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (_virtualPad == null) return;
|
||
|
||
UpdateGamepadState();
|
||
InputSystem.QueueStateEvent(_virtualPad, _currentState);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Initialization
|
||
|
||
public void Initialize()
|
||
{
|
||
EnsureVirtualGamepadExists();
|
||
InitializeDefaultBindings();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region State Update
|
||
|
||
private void UpdateGamepadState()
|
||
{
|
||
_currentState = default;
|
||
|
||
ProcessActionBindings();
|
||
ProcessPinBindings();
|
||
}
|
||
|
||
private void ProcessActionBindings()
|
||
{
|
||
foreach (ActionBinding binding in actionBindings)
|
||
{
|
||
bool isPressed = useAnyPlayer
|
||
? In.GetButtonAnyPlayer(binding.action)
|
||
: In.GetButtonCurrentPlayer(binding.action);
|
||
|
||
if (isPressed)
|
||
{
|
||
ApplyBinding(ref _currentState, binding.target, binding.button, binding.dpadDirection);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ProcessPinBindings()
|
||
{
|
||
foreach (PinBinding binding in pinBindings)
|
||
{
|
||
if (In.GetButton(binding.pinIndex))
|
||
{
|
||
ApplyBinding(ref _currentState, binding.target, binding.button, binding.dpadDirection);
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Binding Logic
|
||
|
||
private static void ApplyBinding(
|
||
ref GamepadState state,
|
||
BindingTarget target,
|
||
GamepadButton button,
|
||
DpadDirection direction)
|
||
{
|
||
if (target == BindingTarget.Button)
|
||
{
|
||
state = state.WithButton(button);
|
||
}
|
||
else // Dpad
|
||
{
|
||
GamepadButton dpadButton = direction switch
|
||
{
|
||
DpadDirection.Up => GamepadButton.DpadUp,
|
||
DpadDirection.Down => GamepadButton.DpadDown,
|
||
DpadDirection.Left => GamepadButton.DpadLeft,
|
||
DpadDirection.Right => GamepadButton.DpadRight,
|
||
_ => GamepadButton.DpadUp
|
||
};
|
||
state = state.WithButton(dpadButton);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Virtual Gamepad Management
|
||
|
||
private void EnsureVirtualGamepadExists()
|
||
{
|
||
if (_deviceCreated && _virtualPad != null)
|
||
return;
|
||
|
||
// Проверяем, не создан ли уже геймпад
|
||
if (TryFindExistingVirtualGamepad(out Gamepad existingGamepad))
|
||
{
|
||
_virtualPad = existingGamepad;
|
||
_deviceCreated = true;
|
||
return;
|
||
}
|
||
|
||
CreateVirtualGamepad();
|
||
}
|
||
|
||
private static bool TryFindExistingVirtualGamepad(out Gamepad gamepad)
|
||
{
|
||
gamepad = null;
|
||
|
||
foreach (InputDevice device in InputSystem.devices)
|
||
{
|
||
if (device is not Gamepad { description: { product: VirtualGamepadProduct } } gp) continue;
|
||
gamepad = gp;
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void CreateVirtualGamepad()
|
||
{
|
||
InputDeviceDescription description = new()
|
||
{
|
||
interfaceName = "Virtual",
|
||
deviceClass = "Gamepad",
|
||
product = VirtualGamepadProduct,
|
||
manufacturer = VirtualGamepadManufacturer
|
||
};
|
||
|
||
_virtualPad = (Gamepad)InputSystem.AddDevice(description);
|
||
_deviceCreated = true;
|
||
|
||
if (logCreateDevice)
|
||
{
|
||
Debug.Log($"[ComInputAdapter] Виртуальный Gamepad создан: {_virtualPad.name}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Default Bindings
|
||
|
||
private void InitializeDefaultBindings()
|
||
{
|
||
if (actionBindings.Count > 0)
|
||
return;
|
||
|
||
AddDefaultActionBindings();
|
||
}
|
||
|
||
private void AddDefaultActionBindings()
|
||
{
|
||
// Маппинг стрелок на D-Pad
|
||
TryAddActionBinding(PlayerAction.up, BindingTarget.Dpad, GamepadButton.North, DpadDirection.Up);
|
||
TryAddActionBinding(PlayerAction.down, BindingTarget.Dpad, GamepadButton.South, DpadDirection.Down);
|
||
TryAddActionBinding(PlayerAction.left, BindingTarget.Dpad, GamepadButton.West, DpadDirection.Left);
|
||
TryAddActionBinding(PlayerAction.right, BindingTarget.Dpad, GamepadButton.East, DpadDirection.Right);
|
||
|
||
// Кнопки действий
|
||
TryAddActionBinding(PlayerAction.action, BindingTarget.Button, GamepadButton.South, DpadDirection.Up);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Синхронизирует pinBindings с ButtonPinMapping.
|
||
/// Вызывать из контекстного меню в инспекторе.
|
||
/// </summary>
|
||
[ContextMenu("Синхронизировать pinBindings с ButtonPinMapping")]
|
||
private void SyncPinBindingsWithButtonPinMapping()
|
||
{
|
||
pinBindings.Clear();
|
||
|
||
// Left
|
||
pinBindings.Add(new PinBinding
|
||
{
|
||
pinIndex = (int)ButtonPinMapping.left1,
|
||
target = BindingTarget.Dpad,
|
||
button = GamepadButton.DpadLeft,
|
||
dpadDirection = DpadDirection.Left
|
||
});
|
||
|
||
// Right
|
||
pinBindings.Add(new PinBinding
|
||
{
|
||
pinIndex = (int)ButtonPinMapping.right1,
|
||
target = BindingTarget.Dpad,
|
||
button = GamepadButton.DpadRight,
|
||
dpadDirection = DpadDirection.Right
|
||
});
|
||
|
||
// Up
|
||
pinBindings.Add(new PinBinding
|
||
{
|
||
pinIndex = (int)ButtonPinMapping.up1,
|
||
target = BindingTarget.Dpad,
|
||
button = GamepadButton.DpadUp,
|
||
dpadDirection = DpadDirection.Up
|
||
});
|
||
|
||
// Down
|
||
pinBindings.Add(new PinBinding
|
||
{
|
||
pinIndex = (int)ButtonPinMapping.down1,
|
||
target = BindingTarget.Dpad,
|
||
button = GamepadButton.DpadDown,
|
||
dpadDirection = DpadDirection.Down
|
||
});
|
||
|
||
// Action
|
||
pinBindings.Add(new PinBinding
|
||
{
|
||
pinIndex = (int)ButtonPinMapping.action1,
|
||
target = BindingTarget.Button,
|
||
button = GamepadButton.South,
|
||
dpadDirection = DpadDirection.Up
|
||
});
|
||
|
||
// Start
|
||
pinBindings.Add(new PinBinding
|
||
{
|
||
pinIndex = (int)ButtonPinMapping.startOnePlayer,
|
||
target = BindingTarget.Button,
|
||
button = GamepadButton.Start,
|
||
dpadDirection = DpadDirection.Up
|
||
});
|
||
|
||
Debug.Log($"[ComInputAdapter] pinBindings синхронизированы с ButtonPinMapping. Всего: {pinBindings.Count}");
|
||
}
|
||
|
||
private void TryAddActionBinding(
|
||
PlayerAction action,
|
||
BindingTarget target,
|
||
GamepadButton button,
|
||
DpadDirection direction)
|
||
{
|
||
try
|
||
{
|
||
actionBindings.Add(new ActionBinding
|
||
{
|
||
action = action,
|
||
target = target,
|
||
button = button,
|
||
dpadDirection = direction
|
||
});
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogWarning($"[ComInputAdapter] Не удалось добавить привязку для {action}: {e.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |