Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 27 additions & 15 deletions engine/Orbit.Input/GameControllers/GameController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
namespace Orbit.Input;
using System.Collections.Concurrent;

namespace Orbit.Input;

/// <summary>
/// Represents a physical game controller that is connected to a device.
/// </summary>
public partial class GameController
{
private readonly WeakEventManager weakEventManager = new();
public string Name { get; private set; } = string.Empty;

/// <summary>
/// Gets the <see cref="Stick"/> that represents the D-pad on the game controller.
Expand Down Expand Up @@ -61,34 +63,44 @@ public partial class GameController
/// Gets the <see cref="Shoulder"/> that represents the right hand shoulder on the controller.
/// </summary>
public Shoulder RightShoulder { get; }

/// <summary>
/// Event that is raised when a button on the game controller is detected as being pressed or released.
/// U
/// </summary>
public event EventHandler<GameControllerButtonChangedEventArgs> ButtonChanged
public IReadOnlyList<ButtonValue<bool>> UnmappedButtons => unmappedButtons.Values.ToList().AsReadOnly();

private ConcurrentDictionary<string, ButtonValue<bool>> 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<bool>(this, buttonName);
unmappedButtons.TryAdd(buttonName, button);
}

button.Value = isPressed;
}


/// <summary>
/// Event that is raised when a button on the game controller is detected as being pressed or released.
/// </summary>
public event EventHandler<GameControllerButtonChangedEventArgs>? ButtonChanged;

/// <summary>
/// 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.
/// </summary>
public event EventHandler<GameControllerValueChangedEventArgs> ValueChanged
{
add => weakEventManager.AddEventHandler(value);
remove => weakEventManager.RemoveEventHandler(value);
}
public event EventHandler<GameControllerValueChangedEventArgs>? ValueChanged;

internal void RaiseButtonValueChanged(ButtonValue buttonValue)
{
switch (buttonValue)
{
case ButtonValue<float> floatValue:
weakEventManager.HandleEvent(this, new GameControllerValueChangedEventArgs(buttonValue.Name, floatValue.Value), nameof(ValueChanged));
ValueChanged?.Invoke(this, new GameControllerValueChangedEventArgs(buttonValue.Name, floatValue.Value));
break;
case ButtonValue<bool> boolValue:
weakEventManager.HandleEvent(this, new GameControllerButtonChangedEventArgs(buttonValue.Name, boolValue.Value), nameof(ButtonChanged));
ButtonChanged?.Invoke(this, new GameControllerButtonChangedEventArgs(buttonValue.Name, boolValue.Value));
break;
}
}
Expand Down
11 changes: 10 additions & 1 deletion engine/Orbit.Input/Platforms/Android/GameController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}

Expand Down
19 changes: 17 additions & 2 deletions engine/Orbit.Input/Platforms/MacCatalyst/GameController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Diagnostics;
using System.Runtime.Intrinsics.Arm;

using Foundation;
using GameController;

Expand All @@ -24,8 +26,10 @@ public GameController(GCController controller)
East = new ButtonValue<bool>(this, nameof(East));
West = new ButtonValue<bool>(this, nameof(West));
Pause = new ButtonValue<bool>(this, nameof(Pause));

if (OperatingSystem.IsMacOSVersionAtLeast(16))

Name = controller.VendorName ?? "Unknown";

if (OperatingSystem.IsMacCatalystVersionAtLeast(16))
{
controller.PhysicalInputProfile.ValueDidChangeHandler += Changed;
}
Expand Down Expand Up @@ -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;
}
}
}
8 changes: 8 additions & 0 deletions engine/Orbit.Input/Platforms/iOS/GameController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public GameController(GCController controller)
West = new ButtonValue<bool>(this, nameof(West));
Pause = new ButtonValue<bool>(this, nameof(Pause));

Name = controller.VendorName ?? "Unknown";

if (OperatingSystem.IsIOSVersionAtLeast(16))
{
controller.PhysicalInputProfile.ValueDidChangeHandler += Changed;
Expand Down Expand Up @@ -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;
}
}
}
11 changes: 11 additions & 0 deletions games/Platformer/ChangeViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Platformer;

public class ChangeViewModel
{
public ChangeViewModel(string description)
{
Description = description;
}

public string Description { get; }
}
36 changes: 36 additions & 0 deletions games/Platformer/GameControllerPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>

<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Platformer"
x:Class="Platformer.GameControllerPage"
x:DataType="local:GameControllerPageViewModel">

<Grid
RowDefinitions="Auto,*"
ColumnDefinitions="*,Auto">
<Picker
ItemsSource="{Binding GameControllers}"
ItemDisplayBinding="{Binding Name, x:DataType={x:Null}}"
SelectedItem="{Binding SelectedGameController}" />

<Button
Text="Refresh"
Command="{Binding RefreshCommand}"
Grid.Column="1" />

<CollectionView
ItemsSource="{Binding Changes}"
Grid.Row="1"
Grid.ColumnSpan="2">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:ChangeViewModel">
<Label Text="{Binding Description}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

</Grid>

</ContentPage>
16 changes: 16 additions & 0 deletions games/Platformer/GameControllerPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
106 changes: 106 additions & 0 deletions games/Platformer/GameControllerPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -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<Orbit.Input.GameController> GameControllers { get; } = [];
public ObservableCollection<ChangeViewModel> 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<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
9 changes: 9 additions & 0 deletions games/Platformer/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
Pressed="OnJumpButtonPressed"
Released="OnJumpButtonReleased"
Grid.Row="1" />

<Button
Text="Controllers"
HorizontalOptions="End"
VerticalOptions="Start"
Width="40"
Height="40"
Clicked="Button_OnClicked"
Grid.Row="1" />
</Grid>

</ContentPage>
5 changes: 5 additions & 0 deletions games/Platformer/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
4 changes: 4 additions & 0 deletions games/Platformer/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public static MauiApp CreateMauiApp()
builder.Services.AddSingleton<SettingsService>();
builder.Services.AddSingleton(GameControllerManager.Current);
builder.Services.AddSingleton(KeyboardManager.Current);

builder.Services.AddTransient<GameControllerPage>();
builder.Services.AddTransient<GameControllerPageViewModel>();
Routing.RegisterRoute(nameof(GameControllerPage), typeof(GameControllerPage));

return builder.Build();
}
Expand Down
Loading