diff --git a/engine/Orbit.Input/GameControllers/GameController.cs b/engine/Orbit.Input/GameControllers/GameController.cs
index cb71c59..dc78fd3 100644
--- a/engine/Orbit.Input/GameControllers/GameController.cs
+++ b/engine/Orbit.Input/GameControllers/GameController.cs
@@ -1,11 +1,13 @@
-namespace Orbit.Input;
+using System.Collections.Concurrent;
+
+namespace Orbit.Input;
///
/// Represents a physical game controller that is connected to a device.
///
public partial class GameController
{
- private readonly WeakEventManager weakEventManager = new();
+ public string Name { get; private set; } = string.Empty;
///
/// Gets the that represents the D-pad on the game controller.
@@ -61,34 +63,44 @@ public partial class GameController
/// Gets the that represents the right hand shoulder on the controller.
///
public Shoulder RightShoulder { get; }
-
+
///
- /// Event that is raised when a button on the game controller is detected as being pressed or released.
+ /// U
///
- public event EventHandler ButtonChanged
+ public IReadOnlyList> UnmappedButtons => unmappedButtons.Values.ToList().AsReadOnly();
+
+ private ConcurrentDictionary> unmappedButtons = new ();
+
+ internal void RaiseUnmappedButtonChange(string buttonName, bool isPressed)
{
- add => weakEventManager.AddEventHandler(value);
- remove => weakEventManager.RemoveEventHandler(value);
+ if (unmappedButtons.TryGetValue(buttonName, out var button) is false)
+ {
+ button = new ButtonValue(this, buttonName);
+ unmappedButtons.TryAdd(buttonName, button);
+ }
+
+ button.Value = isPressed;
}
-
+
+ ///
+ /// Event that is raised when a button on the game controller is detected as being pressed or released.
+ ///
+ public event EventHandler? ButtonChanged;
+
///
/// Event that is raised when a button that supports a varying value on the game controller is detected as being pressed or released to some degree.
///
- public event EventHandler ValueChanged
- {
- add => weakEventManager.AddEventHandler(value);
- remove => weakEventManager.RemoveEventHandler(value);
- }
+ public event EventHandler? ValueChanged;
internal void RaiseButtonValueChanged(ButtonValue buttonValue)
{
switch (buttonValue)
{
case ButtonValue floatValue:
- weakEventManager.HandleEvent(this, new GameControllerValueChangedEventArgs(buttonValue.Name, floatValue.Value), nameof(ValueChanged));
+ ValueChanged?.Invoke(this, new GameControllerValueChangedEventArgs(buttonValue.Name, floatValue.Value));
break;
case ButtonValue boolValue:
- weakEventManager.HandleEvent(this, new GameControllerButtonChangedEventArgs(buttonValue.Name, boolValue.Value), nameof(ButtonChanged));
+ ButtonChanged?.Invoke(this, new GameControllerButtonChangedEventArgs(buttonValue.Name, boolValue.Value));
break;
}
}
diff --git a/engine/Orbit.Input/Platforms/Android/GameController.cs b/engine/Orbit.Input/Platforms/Android/GameController.cs
index a82b34b..3838db4 100644
--- a/engine/Orbit.Input/Platforms/Android/GameController.cs
+++ b/engine/Orbit.Input/Platforms/Android/GameController.cs
@@ -7,9 +7,10 @@ public partial class GameController
{
private readonly int deviceId;
- public GameController(int deviceId)
+ public GameController(int deviceId, string name)
{
this.deviceId = deviceId;
+ Name = name;
Dpad = new Stick(this, nameof(Dpad));
LeftStick = new Stick(this, nameof(LeftStick));
@@ -100,6 +101,10 @@ public bool OnKeyDown(InputEvent inputEvent)
case KeyEvent { KeyCode: Keycode.ButtonR1 }:
RightShoulder.Button.Value = true;
break;
+
+ case KeyEvent keyEvent:
+ RaiseUnmappedButtonChange(keyEvent.KeyCode.ToString(), false);
+ break;
}
return true;
@@ -151,6 +156,10 @@ public bool OnKeyUp(InputEvent inputEvent)
case KeyEvent { KeyCode: Keycode.ButtonR1 }:
RightShoulder.Button.Value = false;
break;
+
+ case KeyEvent keyEvent:
+ RaiseUnmappedButtonChange(keyEvent.KeyCode.ToString(), false);
+ break;
}
return true;
diff --git a/engine/Orbit.Input/Platforms/Android/GameControllerManager.cs b/engine/Orbit.Input/Platforms/Android/GameControllerManager.cs
index e57b70a..c9836e7 100644
--- a/engine/Orbit.Input/Platforms/Android/GameControllerManager.cs
+++ b/engine/Orbit.Input/Platforms/Android/GameControllerManager.cs
@@ -51,7 +51,7 @@ public partial Task StartDiscovery()
if (sources.HasFlag(InputSourceType.Gamepad) || sources.HasFlag(InputSourceType.Joystick))
{
- OnGameControllerConnected(new GameController(deviceId));
+ OnGameControllerConnected(new GameController(deviceId, device.Name ?? "Unknown"));
}
}
diff --git a/engine/Orbit.Input/Platforms/MacCatalyst/GameController.cs b/engine/Orbit.Input/Platforms/MacCatalyst/GameController.cs
index a7f4077..c7f4d78 100644
--- a/engine/Orbit.Input/Platforms/MacCatalyst/GameController.cs
+++ b/engine/Orbit.Input/Platforms/MacCatalyst/GameController.cs
@@ -1,4 +1,6 @@
using System.Diagnostics;
+using System.Runtime.Intrinsics.Arm;
+
using Foundation;
using GameController;
@@ -24,8 +26,10 @@ public GameController(GCController controller)
East = new ButtonValue(this, nameof(East));
West = new ButtonValue(this, nameof(West));
Pause = new ButtonValue(this, nameof(Pause));
-
- if (OperatingSystem.IsMacOSVersionAtLeast(16))
+
+ Name = controller.VendorName ?? "Unknown";
+
+ if (OperatingSystem.IsMacCatalystVersionAtLeast(16))
{
controller.PhysicalInputProfile.ValueDidChangeHandler += Changed;
}
@@ -82,6 +86,17 @@ private void Changed(GCPhysicalInputProfile gamepad, GCControllerElement element
RightStick.XAxis.Value = directionPad.XAxis.Value;
RightStick.YAxis.Value = directionPad.YAxis.Value;
break;
+
+ case GCControllerDirectionPad directionPad when directionPad.Aliases.Contains(new NSString("Direction Pad")):
+ Dpad.XAxis.Value = directionPad.XAxis.Value;
+ Dpad.YAxis.Value = directionPad.YAxis.Value;
+ break;
+
+ case GCControllerButtonInput buttonInput:
+ var buttonName = buttonInput.LocalizedName ?? "Unknown";
+
+ RaiseUnmappedButtonChange(buttonName, buttonInput.IsPressed);
+ break;
}
}
}
\ No newline at end of file
diff --git a/engine/Orbit.Input/Platforms/iOS/GameController.cs b/engine/Orbit.Input/Platforms/iOS/GameController.cs
index 3e4c379..e7c0f83 100644
--- a/engine/Orbit.Input/Platforms/iOS/GameController.cs
+++ b/engine/Orbit.Input/Platforms/iOS/GameController.cs
@@ -29,6 +29,8 @@ public GameController(GCController controller)
West = new ButtonValue(this, nameof(West));
Pause = new ButtonValue(this, nameof(Pause));
+ Name = controller.VendorName ?? "Unknown";
+
if (OperatingSystem.IsIOSVersionAtLeast(16))
{
controller.PhysicalInputProfile.ValueDidChangeHandler += Changed;
@@ -86,6 +88,12 @@ private void Changed(GCPhysicalInputProfile gamepad, GCControllerElement element
RightStick.XAxis.Value = directionPad.XAxis.Value;
RightStick.YAxis.Value = directionPad.YAxis.Value;
break;
+
+ case GCControllerButtonInput buttonInput:
+ var buttonName = buttonInput.LocalizedName ?? "Unknown";
+
+ RaiseUnmappedButtonChange(buttonName, buttonInput.IsPressed);
+ break;
}
}
}
\ No newline at end of file
diff --git a/games/Platformer/ChangeViewModel.cs b/games/Platformer/ChangeViewModel.cs
new file mode 100644
index 0000000..3a7702e
--- /dev/null
+++ b/games/Platformer/ChangeViewModel.cs
@@ -0,0 +1,11 @@
+namespace Platformer;
+
+public class ChangeViewModel
+{
+ public ChangeViewModel(string description)
+ {
+ Description = description;
+ }
+
+ public string Description { get; }
+}
\ No newline at end of file
diff --git a/games/Platformer/GameControllerPage.xaml b/games/Platformer/GameControllerPage.xaml
new file mode 100644
index 0000000..09689f1
--- /dev/null
+++ b/games/Platformer/GameControllerPage.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/games/Platformer/GameControllerPage.xaml.cs b/games/Platformer/GameControllerPage.xaml.cs
new file mode 100644
index 0000000..0cdc2a4
--- /dev/null
+++ b/games/Platformer/GameControllerPage.xaml.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Platformer;
+
+public partial class GameControllerPage : ContentPage
+{
+ public GameControllerPage(GameControllerPageViewModel viewModel)
+ {
+ InitializeComponent();
+ BindingContext = viewModel;
+ }
+}
\ No newline at end of file
diff --git a/games/Platformer/GameControllerPageViewModel.cs b/games/Platformer/GameControllerPageViewModel.cs
new file mode 100644
index 0000000..3fdc871
--- /dev/null
+++ b/games/Platformer/GameControllerPageViewModel.cs
@@ -0,0 +1,106 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Input;
+
+using Orbit.Input;
+
+namespace Platformer;
+
+public class GameControllerPageViewModel : INotifyPropertyChanged
+{
+ private Orbit.Input.GameController? selectedGameController;
+ private readonly Orbit.Input.GameControllerManager gameControllerManager;
+
+ public ObservableCollection GameControllers { get; } = [];
+ public ObservableCollection Changes { get; } = [];
+
+ public Orbit.Input.GameController? SelectedGameController
+ {
+ get => selectedGameController;
+ set
+ {
+ if (selectedGameController is not null)
+ {
+ selectedGameController.ButtonChanged -= SelectedGameControllerOnButtonChanged;
+ selectedGameController.ValueChanged -= SelectedGameControllerOnValueChanged;
+ }
+
+ if (SetField(ref selectedGameController, value))
+ {
+ if (selectedGameController is not null)
+ {
+ AddChange($"Connected to controller '{selectedGameController.Name}'");
+ selectedGameController.ButtonChanged += SelectedGameControllerOnButtonChanged;
+ selectedGameController.ValueChanged += SelectedGameControllerOnValueChanged;
+ }
+ }
+ }
+ }
+
+ private void SelectedGameControllerOnValueChanged(object? sender, GameControllerValueChangedEventArgs e)
+ {
+ AddChange($"'{e.ButtonName}' button changed to {e.Value}");
+ }
+
+ private void SelectedGameControllerOnButtonChanged(object? sender, GameControllerButtonChangedEventArgs e)
+ {
+ AddChange($"'{e.ButtonName}' was {(e.IsPressed ? "Pressed" : "Released")}");
+ }
+
+ private void AddChange(string description)
+ {
+ Changes.Add(new ChangeViewModel($"{DateTime.Now:O} - {description}"));
+ }
+
+ public ICommand RefreshCommand { get; }
+
+ public GameControllerPageViewModel(Orbit.Input.GameControllerManager gameControllerManager)
+ {
+ this.gameControllerManager = gameControllerManager;
+
+ this.gameControllerManager.GameControllerConnected += GameControllerManagerOnGameControllerConnected;
+
+ RefreshCommand = new Command(OnRefresh);
+ }
+
+ private void GameControllerManagerOnGameControllerConnected(object? sender, GameControllerConnectedEventArgs e)
+ {
+ GameControllers.Add(e.GameController);
+ }
+
+ private void OnRefresh()
+ {
+ try
+ {
+ GameControllers.Clear();
+
+ _ = this.gameControllerManager.StartDiscovery();
+
+ foreach (var gameController in gameControllerManager.GameControllers)
+ {
+ GameControllers.Add(gameController);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ private bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/games/Platformer/MainPage.xaml b/games/Platformer/MainPage.xaml
index 5b13b93..c0a2ed7 100644
--- a/games/Platformer/MainPage.xaml
+++ b/games/Platformer/MainPage.xaml
@@ -53,6 +53,15 @@
Pressed="OnJumpButtonPressed"
Released="OnJumpButtonReleased"
Grid.Row="1" />
+
+
diff --git a/games/Platformer/MainPage.xaml.cs b/games/Platformer/MainPage.xaml.cs
index 046e09b..a5419da 100644
--- a/games/Platformer/MainPage.xaml.cs
+++ b/games/Platformer/MainPage.xaml.cs
@@ -198,4 +198,9 @@ private void OnShowDebugCheckedChanged(object? sender, CheckedChangedEventArgs e
{
this.settingsService.ShowDebug = e.Value;
}
+
+ private async void Button_OnClicked(object? sender, EventArgs e)
+ {
+ await Shell.Current.GoToAsync(nameof(GameControllerPage));
+ }
}
diff --git a/games/Platformer/MauiProgram.cs b/games/Platformer/MauiProgram.cs
index 7935e6d..f3df3d8 100644
--- a/games/Platformer/MauiProgram.cs
+++ b/games/Platformer/MauiProgram.cs
@@ -42,6 +42,10 @@ public static MauiApp CreateMauiApp()
builder.Services.AddSingleton();
builder.Services.AddSingleton(GameControllerManager.Current);
builder.Services.AddSingleton(KeyboardManager.Current);
+
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ Routing.RegisterRoute(nameof(GameControllerPage), typeof(GameControllerPage));
return builder.Build();
}