Sony DualSense Controller Integration for O3DE
A prototype gem showcasing GamepadCore integration with the O3DE Input System
o3de-dualsense is a prototype gem that demonstrates how to integrate Sony DualSense (PS5) controllers into O3DE using GamepadCore. This project serves as a starting point and reference implementation for developers looking to add custom gamepad support to their O3DE projects.
โ ๏ธ Note: This is a prototype for validating GamepadCore use cases. It provides a solid foundation but is not a production-ready implementation.
| Feature | Status |
|---|---|
| ๐ Plug & Play device detection | โ |
| ๐น๏ธ Face buttons (Cross, Circle, Square, Triangle) | โ |
| ๐ฏ Analog triggers (L2/R2) | โ |
| ๐น๏ธ Analog sticks (Left/Right) | โ |
| ๐ก Lightbar color control | โ |
| ๐ณ Haptic vibration | โ |
| ๐ซ Adaptive triggers | โ |
| ๐ฅ๏ธ Windows platform support | โ |
| ๐ง Linux platform support | ๐ง Planned |
o3de-dualsense/
โโโ Code/
โ โโโ Include/ # Public headers
โ โโโ Source/
โ โ โโโ Adapters/ # GamepadCore โ O3DE bridge
โ โ โ โโโ DeviceRegistry.h # Device management & EBus integration
โ โ โโโ Clients/ # System component implementation
โ โ โโโ Devices/ # O3DE InputDevice implementation
โ โ โ โโโ DualSenseInputDevice.cpp
โ โ โโโ Platforms/
โ โ โ โโโ Windows/ # Platform-specific policies
โ โ โโโ ThirdParty/
โ โ โโโ GamepadCore/ # GamepadCore library
โ โโโ Platform/ # Platform build configs
โโโ Registry/ # Asset processor settings
The gem creates a bridge between GamepadCore (low-level controller communication) and O3DE's Input System (high-level game input).
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ DualSense โโโโโโถโ GamepadCore โโโโโโถโ Adapter โโโโโโถโ O3DE Input โ
โ Hardware โ โ Library โ โ Layer โ โ System โ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
The core of the input translation happens in DualSenseInputDevice::ProcessRawInput():
void DualSenseInputDevice::ProcessRawInput(FDeviceContext* Context) {
if (!Context) return;
const auto& state = Context->GetInputState();
// Face Buttons โ O3DE Digital Channels
if (buttonCross) buttonCross->SimulateRawInput(state.bCross);
if (buttonCircle) buttonCircle->SimulateRawInput(state.bCircle);
if (buttonSquare) buttonSquare->SimulateRawInput(state.bSquare);
if (buttonTriangle) buttonTriangle->SimulateRawInput(state.bTriangle);
// Analog Triggers โ O3DE Analog Channels
if (L2) L2->SimulateRawInput(state.LeftTriggerAnalog);
if (R2) R2->SimulateRawInput(state.RightTriggerAnalog);
// Analog Sticks โ O3DE Axis2D Channels
AZ::Vector2 leftStick(state.LeftAnalog.X, state.LeftAnalog.Y);
if (AnalogLeft) AnalogLeft->SimulateRawInput2D(leftStick.GetX(), leftStick.GetY());
AZ::Vector2 rightStick(state.RightAnalog.X, state.RightAnalog.Y);
if (AnalogRight) AnalogRight->SimulateRawInput2D(rightStick.GetX(), rightStick.GetY());
}Key Points:
FDeviceContextprovides the raw controller state from GamepadCoreSimulateRawInput()feeds values into O3DE's input channel system- Buttons map to standard gamepad buttons (A/B/X/Y)
- Triggers provide analog 0.0-1.0 values
- Sticks provide 2D axis values
The DeviceRegistry manages controller lifecycle using a policy-based design:
struct DeviceRegistryPolicy {
using EngineIdType = AZ::u32;
std::unordered_map<EngineIdType, std::unique_ptr<DualSenseInputDevice>> CustomDevicesMap;
void DispatchNewGamepad(EngineIdType GamepadId) {
InputDeviceId deviceName(AZStd::string_view("dualsense"), GamepadId);
auto DualInputDevice = std::make_unique<DualSenseInputDevice>(deviceName);
CustomDevicesMap[GamepadId] = std::move(DualInputDevice);
CustomDevicesMap[GamepadId]->SetConnected(true);
}
void DisconnectDevice(EngineIdType GamepadId) {
CustomDevicesMap.erase(GamepadId);
}
};This pattern allows:
- ๐ Automatic device detection and registration
- ๐ฎ Multiple controller support
- ๐ Hot-plug/unplug handling
- ๐ก O3DE EBus integration
Platform-specific code is isolated using policy classes:
struct WindowsHardwarePolicy {
void Read(FDeviceContext* Context) {
WindowsDeviceInfo::Read(Context);
}
void Write(FDeviceContext* Context) {
WindowsDeviceInfo::Write(Context);
}
void Detect(std::vector<FDeviceContext>& Devices) {
WindowsDeviceInfo::Detect(Devices);
}
void ProcessAudioHaptic(FDeviceContext* Context) {
WindowsDeviceInfo::ProcessAudioHapitc(Context);
}
};Copy the gem to your O3DE project's Gems/ directory or register it globally.
Add to your project's project.json:
{
"gem_dependencies": [
"o3de-dualsense"
]
}cmake --build build/windows --target YourProject.GameLauncher --config profilePlug in your DualSense controller via USB. The gem automatically detects and registers it.
Once connected, DualSense inputs are available through O3DE's standard input system:
| DualSense Button | O3DE Input Channel |
|---|---|
| โ Cross | gamepad_button_a |
| โ Circle | gamepad_button_b |
| โก Square | gamepad_button_x |
| โณ Triangle | gamepad_button_y |
| L2 | gamepad_trigger_l2 |
| R2 | gamepad_trigger_r2 |
| Left Stick | gamepad_stick_l |
| Right Stick | gamepad_stick_r |
gamepad->SetLightbar({255, 0, 0}); // Set to redgamepad->SetVibration(10, 30); // Left motor: 10, Right motor: 30IGamepadTrigger* Trigger = gamepad->GetIGamepadTrigger();
// Bow effect on L2
Trigger->SetBow22(0xf8, 0x3f, EDSGamepadHand::Left);
// Machine gun effect on R2
Trigger->SetMachine27(0x80, 0x02, 0x03, 0x0f, 0x19, 0x02, EDSGamepadHand::Right);| Directory | Purpose |
|---|---|
Code/Include/ |
Public API headers |
Code/Source/Adapters/ |
GamepadCore โ O3DE bridge layer |
Code/Source/Clients/ |
System component implementation |
Code/Source/Devices/ |
O3DE InputDevice wrapper |
Code/Source/Platforms/ |
Platform-specific implementations |
Code/Source/ThirdParty/ |
GamepadCore library |
Code/Platform/ |
CMake platform configurations |
This gem provides a solid foundation. Here's how to extend it:
- Add new
InputChannel*members inDualSenseInputDevice.h - Create and register them in the constructor
- Process them in
ProcessRawInput()
- Create
Source/Platforms/Linux/DualSenseLinuxPolicy.h - Implement the policy interface using Linux input subsystem
- Register the policy in the system component
- Extend
WindowsDeviceInfoto detect Bluetooth connections - Handle different report formats for USB vs Bluetooth
- O3DE 23.10 or later
- GamepadCore library (included in
ThirdParty/) - Windows 10/11 (for Windows builds)
- DualSense Controller (PS5 controller)
Contributions are welcome! This is a prototype meant to evolve.
- Fork the repository
- Create your feature branch
- Implement your changes
- Submit a pull request
This project is licensed under the MIT License - see the gem.json for details.
This software is an independent and unofficial project. It is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Sony Interactive Entertainment Inc., Microsoft Corporation, Apple Inc., Epic Games, Unity Technologies, the Godot Engine project, or the Open 3D Foundation.
Trademarks belong to their respective owners:
- Sony: "PlayStation", "PlayStation Family Mark", "PS5 logo", "PS5", "DualSense", and "DualShock" are registered trademarks or trademarks of Sony Interactive Entertainment Inc. "SONY" is a registered trademark of Sony Corporation.
- Microsoft: "Windows" and "Xbox" are registered trademarks of Microsoft Corporation.
- Apple: "Mac" and "macOS" are registered trademarks of Apple Inc.
- Linux: "Linux" is the registered trademark of Linus Torvalds in the U.S. and other countries.
- Epic Games: "Unreal" and "Unreal Engine" are trademarks or registered trademarks of Epic Games, Inc. in the United States of America and elsewhere.
- Unity: "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere.
- Godot: "Godot" and the Godot logo are trademarks of the Godot Engine project.
- O3DE: "O3DE" and the O3DE logo are trademarks of the Open 3D Foundation.
