Skip to content

Commit

Permalink
feat: Gamepad API
Browse files Browse the repository at this point in the history
resolve #11
  • Loading branch information
EbiseLutica committed Feb 9, 2024
1 parent e3c7c83 commit 0315d77
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 1 deletion.
1 change: 1 addition & 0 deletions Promete.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.Use<Keyboard>()
.Use<Mouse>()
.Use<ConsoleLayer>()
.Use<Gamepads>()
.BuildWithOpenGLDesktop();

app.Run<MainScene>();
35 changes: 35 additions & 0 deletions Promete.Example/examples/input/gamepad.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Promete.Example.Kernel;
using Promete.Input;
using Silk.NET.Input;

namespace Promete.Example.examples.experimental;

[Demo("input/gamepad.demo", "ゲームパッドの入力確認")]
public class GamepadExampleScene(ConsoleLayer console, Keyboard keyboard, Gamepads pads) : Scene
{
private Gamepad? CurrentPad => pads[0];

public override void OnUpdate()
{
console.Clear();
if (CurrentPad is null || !CurrentPad.IsConnected)
{
console.Print("Connect a gamepad!");
return;
}

console.Print($"Name: {CurrentPad.Name}");
console.Print($"Supports Motor: {(CurrentPad.IsVibrationSupported ? "Yes" : "No")}");
console.Print($"Left Stick: {CurrentPad.LeftStick}");
console.Print($"Right Stick: {CurrentPad.RightStick}");


console.Print($"\n{"Button Type",-12} Pressed Down Up");
foreach (var btn in CurrentPad.AllButtons)
{
console.Print($"{btn.Type.ToString(),-12} {btn.IsPressed,-7} {btn.IsButtonDown,-5} {btn.IsButtonUp,-5}");
}

if (keyboard.Escape.IsKeyDown) App.LoadScene<MainScene>();
}
}
2 changes: 1 addition & 1 deletion Promete.Example/examples/sample6.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ namespace Promete.Example.examples;
public class SpriteRotateTestScene(ConsoleLayer console, Keyboard keyboard, Mouse mouse) : Scene
{
private ITexture tSolid;
private Sprite sprite;
private Container wrapper;
private Sprite sprite;
private float angle;

protected override Container Setup()
Expand Down
150 changes: 150 additions & 0 deletions Promete/Input/Gamepad.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Promete.Input.Internal;
using Promete.Windowing;
using Silk.NET.Input;

namespace Promete.Input;

public sealed class Gamepad : IDisposable
{
public bool IsConnected => pad.IsConnected;

public string Name => pad.Name;
public bool IsVibrationSupported => pad.VibrationMotors.Any();

public Vector LeftStick => pad.Thumbsticks.Count >= 1 ? (pad.Thumbsticks[0].X, pad.Thumbsticks[0].Y) : (0, 0);

public Vector RightStick => pad.Thumbsticks.Count >= 2 ? (pad.Thumbsticks[1].X, pad.Thumbsticks[1].Y) : (0, 0);

public GamepadButton this[int index] => buttons[index];

public GamepadButton this[GamepadButtonType type] => buttonMap[type];

public IEnumerable<GamepadButton> AllButtons => buttons.AsEnumerable();

/// <summary>
/// 現在押されている全てのボタンを列挙します。
/// </summary>
public IEnumerable<GamepadButton> AllPressedButtons => buttons.Where(c => c.IsPressed);

/// <summary>
/// このフレームで押された全てのボタンを列挙します。
/// </summary>
public IEnumerable<GamepadButton> AllDownButtons => buttons.Where(c => c.IsButtonDown);

/// <summary>
/// このフレームで離された全てのボタンを列挙します。
/// </summary>
public IEnumerable<GamepadButton> AllUpButtons => buttons.Where(c => c.IsButtonUp);

private readonly GamepadButton[] buttons;
private readonly Dictionary<GamepadButtonType, GamepadButton> buttonMap = new();
private readonly IWindow window;
private readonly IGamepad pad;

private const int TriggerCount = 2;

public Gamepad(IGamepad pad, IWindow window)
{
this.pad = pad;
this.window = window;
buttons = new GamepadButton[pad.Buttons.Count + TriggerCount];
for (var i = 0; i < pad.Buttons.Count; i++)
{
buttons[i] = new GamepadButton(pad.Buttons[i].Name.ToPromete());
buttonMap[buttons[i].Type] = buttons[i];
}

// Note: XInputの仕様に従い、Prometeでは最大2つのトリガーをL2, R2として解釈する
buttonMap[GamepadButtonType.L2] = buttons[pad.Buttons.Count + 0] = new GamepadButton(GamepadButtonType.L2);
buttonMap[GamepadButtonType.R2] = buttons[pad.Buttons.Count + 1] = new GamepadButton(GamepadButtonType.R2);

pad.ButtonDown += OnButtonDown;
pad.ButtonUp += OnButtonUp;
pad.TriggerMoved += OnTriggerMove;

window.PreUpdate += OnPreUpdate;
window.PostUpdate += OnPostUpdate;
}

public void Vibrate(float value)
{
if (!IsVibrationSupported) return;

foreach (var motor in pad.VibrationMotors)
{
motor.Speed = value;
}
}

public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}

private void OnPreUpdate()
{
for (var i = 0; i < buttons.Length; i++)
{
var isPressed = i < buttons.Length - 2
? pad.Buttons[i].Pressed
: pad.Triggers[i - buttons.Length + 2].Position >= 1;
buttons[i].IsPressed = isPressed;
buttons[i].ElapsedFrameCount = isPressed ? buttons[i].ElapsedFrameCount + 1 : 0;
buttons[i].ElapsedTime = isPressed ? buttons[i].ElapsedTime + window.DeltaTime : 0;
}
}

private void OnPostUpdate()
{
foreach (var t in buttons)
{
t.IsButtonDown = false;
t.IsButtonUp = false;
}
}

private void OnButtonDown(IGamepad pad, Button button)
{
buttons[button.Index].IsButtonDown = true;
}

private void OnButtonUp(IGamepad pad, Button button)
{
buttons[button.Index].IsButtonUp = true;
}

private void OnTriggerMove(IGamepad pad, Trigger trigger)
{
var button = buttons[buttons.Length - 2 + trigger.Index];
if (trigger.Position >= 1)
{
button.IsButtonDown = true;
}
else
{
button.IsButtonUp = true;
}
}

private void ReleaseUnmanagedResources()
{
foreach (var motor in pad.VibrationMotors)
{
motor.Speed = 0;
}
pad.ButtonDown -= OnButtonDown;
pad.ButtonUp -= OnButtonUp;
pad.TriggerMoved -= OnTriggerMove;
window.PreUpdate -= OnPreUpdate;
window.PostUpdate -= OnPostUpdate;
}

~Gamepad()
{
ReleaseUnmanagedResources();
}
}
40 changes: 40 additions & 0 deletions Promete/Input/GamepadButton.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Silk.NET.Input;

namespace Promete.Input;

public class GamepadButton
{
public GamepadButtonType Type { get; }

/// <summary>
/// このボタンが押されているかどうかを取得します。
/// </summary>
public bool IsPressed { get; internal set; }

/// <summary>
/// このボタンが押されてからの経過フレーム数を取得します。
/// </summary>
/// <value></value>
public int ElapsedFrameCount { get; internal set; }

/// <summary>
/// このボタンが押されてからの経過時間を取得します。
/// </summary>
/// <value></value>
public float ElapsedTime { get; internal set; }

/// <summary>
/// このボタンがこのフレームで押されたかどうかを取得します。
/// </summary>
public bool IsButtonDown { get; internal set; }

/// <summary>
/// このボタンがこのフレームで離されたかどうかを取得します。
/// </summary>
public bool IsButtonUp { get; internal set; }

internal GamepadButton(GamepadButtonType type)
{
Type = type;
}
}
23 changes: 23 additions & 0 deletions Promete/Input/GamepadButtonType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Promete.Input;

public enum GamepadButtonType
{
DpadUp,
DpadDown,
DpadLeft,
DpadRight,
A,
B,
X,
Y,
L1,
R1,
L2,
R2,
Plus,
Minus,
LeftStick,
RightStick,
Home,
Unknown,
}
42 changes: 42 additions & 0 deletions Promete/Input/Gamepads.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using Promete.Windowing;
using Silk.NET.Input;

namespace Promete.Input;

/// <summary>
/// 接続されたゲームパッドの入力を取得する Promete プラグインです。このクラスは継承できません。
/// </summary>
public sealed class Gamepads
{
private IInputContext input;
private List<Gamepad> pads = [];

private readonly IWindow window;

public Gamepad? this[int index] => index < pads.Count ? pads[index] : null;

public Gamepads(IWindow window)
{
this.window = window;
input = window._RawInputContext!;
UpdateGamepads();

input.ConnectionChanged += OnConnectionChanged;
}

private void OnConnectionChanged(IInputDevice device, bool isConnected)
{
if (device is IGamepad) UpdateGamepads();
}

private void UpdateGamepads()
{
pads.ForEach(p => p.Dispose());
pads.Clear();
foreach (var silkGamepad in input.Gamepads)
{
pads.Add(new Gamepad(silkGamepad, window));
}
}
}
51 changes: 51 additions & 0 deletions Promete/Input/Internal/GamepadButtonTypeEnumConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using Silk.NET.Input;

namespace Promete.Input.Internal;

internal static class GamepadButtonTypeEnumConverter
{
public static ButtonName ToSilk(this GamepadButtonType type) =>
type switch
{
GamepadButtonType.DpadUp => ButtonName.DPadUp,
GamepadButtonType.DpadDown => ButtonName.DPadDown,
GamepadButtonType.DpadLeft => ButtonName.DPadLeft,
GamepadButtonType.DpadRight => ButtonName.DPadRight,
GamepadButtonType.A => ButtonName.A,
GamepadButtonType.B => ButtonName.B,
GamepadButtonType.X => ButtonName.X,
GamepadButtonType.Y => ButtonName.Y,
GamepadButtonType.L1 => ButtonName.LeftBumper,
GamepadButtonType.R1 => ButtonName.RightBumper,
GamepadButtonType.L2 => throw new NotSupportedException("L2 is not a button in Silk.NET"),
GamepadButtonType.R2 => throw new NotSupportedException("R2 is not a button in Silk.NET"),
GamepadButtonType.Plus => ButtonName.Start,
GamepadButtonType.Minus => ButtonName.Back,
GamepadButtonType.LeftStick => ButtonName.LeftStick,
GamepadButtonType.RightStick => ButtonName.RightStick,
GamepadButtonType.Home => ButtonName.Home,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};

public static GamepadButtonType ToPromete(this ButtonName type) =>
type switch
{
ButtonName.DPadUp => GamepadButtonType.DpadUp,
ButtonName.DPadDown => GamepadButtonType.DpadDown,
ButtonName.DPadLeft => GamepadButtonType.DpadLeft,
ButtonName.DPadRight => GamepadButtonType.DpadRight,
ButtonName.A => GamepadButtonType.A,
ButtonName.B => GamepadButtonType.B,
ButtonName.X => GamepadButtonType.X,
ButtonName.Y => GamepadButtonType.Y,
ButtonName.LeftBumper => GamepadButtonType.L1,
ButtonName.RightBumper => GamepadButtonType.R1,
ButtonName.Start => GamepadButtonType.Plus,
ButtonName.Back => GamepadButtonType.Minus,
ButtonName.LeftStick => GamepadButtonType.LeftStick,
ButtonName.RightStick => GamepadButtonType.RightStick,
ButtonName.Home => GamepadButtonType.Home,
_ => GamepadButtonType.Unknown,
};
}

0 comments on commit 0315d77

Please sign in to comment.