VR-Engage  2.2
Loading...
Searching...
No Matches
Input Device Example

Overview

Purpose: This example integrates custom input devices with VR-Engage through implementation of the DtInputDevice interface. It covers the complete pattern for receiving device events, translating them to VR-Engage input data, and integrating with the input mapping system.

Observable Behavior: When running this example, you will see a virtual on-screen gamepad appear in the VR-Engage UI. Touch or mouse interaction with gamepad buttons and analog sticks generates input events, with input from the virtual gamepad mapped to player controls via standard input configuration. Gamepad visibility is managed automatically during state transitions.

Prerequisites: Understanding of Input and Control and familiarity with frontend input patterns. Knowledge of event-driven programming and callback patterns is required, along with basic Qt/QML understanding for UI integration.

Related Examples: Input mapping examples in VR-Engage documentation provide additional context.


Key Concepts Demonstrated

This example implements custom input device integration through the DtInputDevice interface for non-standard hardware. It serves as a template for integrating any custom device using serial, network, or proprietary APIs. For complete input system details, see Input and Control.

The example covers the input device factory pattern for registering custom devices with VR-Engage's input system. The device lifecycle is managed automatically by the input manager with integration into input mapping configuration files.

Event queue processing collects asynchronous device events and processes them in sync with frame ticks. Events are gathered between ticks and processed during the tick, demonstrating proper timing for input processing.

The implementation includes QML-C++ integration using Qt Quick for custom input UI. The virtual gamepad is implemented in QML while C++ receives events via Q_INVOKABLE methods.


Code Walkthrough

Input Device Factory Registration

The plugin registers the custom input device class with VR-Engage's factory:

// File: examples/inputDevice/plugin.cxx
{
// Register custom input device with factory
app->inputDeviceFactory()->addCreator<DtQmlGamepadInputDevice>("DtQmlGamepadInputDevice");
return true;
}
Custom input device adapter for VR-Engage's input mapping system.
Definition qmlGamepadInputDevice.h:67
void addCreator(std::string name="")
Registers a creator for a specific type.
Definition factory.h:112
Top-level class representing the VR-Engage application.
Definition playerStationApp.h:163
virtual DtInputDeviceFactory * inputDeviceFactory()
Gets the input device factory.
VRECOMMONCOMPONENTS_DLL bool initPlayerStationModule(makVre::DtPlayerStationApp *app)
Module initialization function declarations using C linkage.

The device is automatically instantiated during app startup, with the tick() method called by the input manager at the appropriate time. No manual device lifecycle management is required.

Device Initialization and Event Callback

The input device initializes and registers for events from the virtual gamepad:

// File: examples/inputDevice/qmlGamepadInputDevice.cxx
bool DtQmlGamepadInputDevice::init(DtVreInputManager& mgr)
{
// Save pointer to InputManager for later use in tick()
myManager = &mgr;
// Register callback to receive events from virtual gamepad
return true;
}
{
// Unregister callback to prevent dangling references
}
virtual void shutdown() override
Clean up device resources and unregister event handlers.
virtual bool init(makVre::DtVreInputManager &mgr) override
Initialize input device and register for device events.
void deviceEventCallback(std::string eventType, int eventId, double eventValue)
Callback receiving events from the virtual gamepad device.
makVre::DtQmlGampad myVirtualGamepad
The custom device instance (virtual on-screen gamepad in this example) For a real hardware device,...
Definition qmlGamepadInputDevice.h:167
DtVreInputManager * myManager
Pointer to the input manager that owns this device.
Definition inputDevice.h:75
void addEventCallback(T *object, Method method)
Register a callback to receive virtual gamepad input events.
Definition qmlGamepad.h:81
void removeEventCallback(T *object, Method method)
Remove a previously registered event callback.
Definition qmlGamepad.h:97

The input manager reference is saved during initialization since it's needed later to send processed events. Callback registration enables event-driven device integration, while proper cleanup in shutdown() prevents resource leaks. This pattern works for any device that provides a callback or event-based API.

Event Collection and Translation

Device events are collected asynchronously and queued for processing:

// File: examples/inputDevice/qmlGamepadInputDevice.cxx
void DtQmlGamepadInputDevice::deviceEventCallback(std::string eventType, int eventId, double eventValue)
{
// Generate InputData structure for every device event
DtInputData data;
// Identify the device
data.myDeviceType = DtDeviceType::DtTouchController;
data.myDeviceName = "Virtual Gamepad";
data.myDeviceIndex = 0;
data.myTimestamp = myManager->simulationTime();
// Translate event type, id, and value to VR-Engage input data
data.myInputType = stringToInputType(eventType); // axis, button, etc.
data.myInputID = eventId; // which axis/button
data.myInputValue = eventValue; // current value
// Queue for processing in tick()
myEventQueue.push_back(data);
}
std::list< makVre::DtInputData > myEventQueue
Event queue to gather device events between tick calls Events received via callbacks are queued here,...
Definition qmlGamepadInputDevice.h:162
virtual double simulationTime() const
Gets the current simulation time.

Key Points:

  • Events arrive asynchronously from device (callback-driven)
  • Queue decouples event arrival from processing timing
  • Timestamp ensures correct temporal ordering
  • Device identification allows input mapping to differentiate sources

Frame-Synchronized Processing

Queued events are processed during the frame tick:

// File: examples/inputDevice/qmlGamepadInputDevice.cxx
{
// Process all events collected since last tick
for (auto data : myEventQueue)
{
// Send each event to InputManager for immediate processing
}
// Clear queue after processing
myEventQueue.clear();
}
virtual void tick(double dt) override
Process queued input events and submit to input manager.
virtual bool processInput(const DtInputData &data)
Processes raw input data from a device.

Input processing is synchronized with the simulation frame, ensuring consistent behavior. The input manager matches events to active action map layers, processing them in arrival order (FIFO). All pending input is handled before the next frame begins.

QML Virtual Gamepad Implementation

The example includes a QML-based virtual gamepad for demonstration:

// File: examples/inputDevice/qmlGamepad.cxx
void DtQmlGampad::qmlEvent(QString type, int id, qreal value)
{
// Called from QML when button pressed or axis changed
std::string eventType = type.toStdString();
// Distribute event to all registered callbacks
for (auto& pair : myEventCallbacks)
{
pair.second(eventType, id, value);
}
}

The Q_INVOKABLE macro makes the method callable from QML, enabling the QML UI to provide a touch-friendly gamepad interface. This pattern demonstrates how to wrap any custom device API—in real implementations, you would replace the virtual gamepad calls with actual device driver interactions.


Deployment and Testing

Installation

Build the example (see Environment Setup & Build Guide):

cd examples\build
cmake --build . --config RelWithDebInfo --target inputDevice

Install the plugin to the VR-Engage installation:

cmake --install . --config RelWithDebInfo

This copies the plugin to <VR-Engage-Install-Dir>\plugins64\vrEngage\release\exampleInputDevice.dll

Verify installation:

dir "<VR-Engage-Install-Dir>\plugins64\vrEngage\release\exampleInputDevice.dll"

Support Files

The toolkit installer (or cmake --install when building from source) deploys the required support files into the VR-Engage installation. These files must be present for the example to function correctly:

QML UI files (<VR-Engage-Install-Dir>\data\UI\virtualGamepad\): The virtual gamepad QML files define the on-screen gamepad interface. The main.qml file and its supporting assets are loaded at runtime by DtQtQuickRenderer when the input device initializes.

Input mapping configuration (<VR-Engage-Install-Dir>\data\simulationModelSets\examples\inputDevice\input\human\touch.lua): This Lua configuration file defines how events from the "Virtual Gamepad" device are mapped to player actions (movement, combat, parachuting, and related controls). It is loaded as part of the humanInput.lua configuration in the example simulation model set.

Configuration

The input device is automatically instantiated when the plugin loads. The example simulation model set enables the virtual gamepad by loading input/humanInput.lua, which in turn includes input/human/touch.lua. That Lua file defines the action mappings for the "Virtual Gamepad" device.

When you use the example custom settings file exampleInputDevice.lua, two application-level settings are overridden in the playerStationApp table:

  • playerStationApp.defaultSimulationModelSets is set to:
    • "$(DATA_DIR)/simulationModelSets/examples/inputDevice.sms"
    • This selects the inputDevice example simulation model set at startup.
  • playerStationApp.inputSettingsPath is set to:
    • "$(DATA_DIR)/simulationModelSets/examples/inputDevice/input/"
    • This directs VR-Engage to load its input configuration (including humanInput.lua and human/touch.lua) from the example's input directory instead of the default VR-Engage input configuration.

For reference, the touch.lua configuration includes mappings such as:

inputDevices ={
{
DeviceType="touch-controller";
DeviceName="Virtual Gamepad";
Ordinal=0;
MappingGroups={
{
Group="general";
Mappings = {
{type="axis"; id=1; action="move"; ...};
{type="axis"; id=0; action="strafe"; ...};
{type="button"; id=3; action="sprint"};
{type="axis"; id=3; action="pitch"; ...};
{type="axis"; id=2; action="yaw"; ...};
{type="button"; id=2; action="posture-select"};
};
};
{
Group="combat";
Mappings={
{type="button"; id=1; action="aim"};
{type="button"; id=0; action="fire"};
};
};
};
};
}

This means that, when the example simulation model set is selected and inputSettingsPath is pointed at the example's input directory, the virtual gamepad is already wired to standard human movement and combat actions without additional configuration. For more details on input mapping configuration, see Input and Control.

Launching with the Example Custom Settings File

The recommended way to run this example is to use the provided custom settings script exampleInputDevice.lua, which sets both the default simulation model set and the input settings path as described above.

After installing the example, this file is available at:

  • <VR-Engage-Install-Dir>\appData\scripts\exampleInputDevice.lua

From the VR-Engage bin64 directory, you can launch with:

vrEngage.exe -c -n 3 --customSettingsFile "..\appData\scripts\exampleInputDevice.lua"

Alternatively, you can specify an absolute path:

vrEngage.exe -c -n 3 --customSettingsFile "<VR-Engage-Install-Dir>\appData\scripts\exampleInputDevice.lua"

Using the custom settings file ensures that both playerStationApp.defaultSimulationModelSets and playerStationApp.inputSettingsPath are configured consistently for the inputDevice example.

If you prefer to override these settings directly on the command line instead of using the Lua file, you can pass them via --setting:

vrEngage.exe -c -n 3 --setting "playerStationApp.defaultSimulationModelSets = {'<VR-Engage-Install-Dir>/data/simulationModelSets/examples/inputDevice.sms'}; playerStationApp.inputSettingsPath = '<VR-Engage-Install-Dir>/data/simulationModelSets/examples/inputDevice/input/';"

Testing Procedure

  1. Launch VR-Engage with the inputDevice plugin loaded
  2. Enter engaged state to activate input processing
  3. Virtual gamepad appears as overlay in UI
  4. Interact with gamepad controls:
    • Touch/click analog stick to generate axis events
    • Press buttons to generate button events
    • Observe input values in debug logs (if logging enabled)
  5. Expected Behavior:
    • Input from virtual gamepad triggers mapped actions
    • Player controls respond to gamepad input
    • Input device appears in input device list

Verification:

  • Check the VR-Engage log (the most recent *.log file in the MAK log directory, typically C:/MAK/logs) for device registration:
    [Example Input Device] Initializing...
    [Input Device Factory] Registered DtQmlGamepadInputDevice
  • Enable input logging to see processed events:
    [InputManager] Processing input: device=Virtual Gamepad, type=axis, id=0, value=0.543

Troubleshooting

Plugin not loading:

  • Verify DLL is in <VR-Engage-Install-Dir>\plugins64\vrEngage\release\
  • Check Qt dependencies (Qt5Qml.dll, Qt5Quick.dll)
  • Review the VR-Engage log (most recent *.log file in the MAK log directory) for load errors

Virtual gamepad not visible:

  • Symptom: Plugin loads but no gamepad UI appears
  • Cause: QML files not deployed or path incorrect
  • Solution: Verify QML files copied to data/UI/ directory

Input not mapped to controls:

  • Symptom: Gamepad input generated but no effect on player
  • Cause: Input action maps not configured for "Virtual Gamepad" device
  • Solution: Add or adjust input mappings in the Lua configuration files under <VR-Engage-Install-Dir>\data\simulationModelSets\examples\inputDevice\input\\ (for example, human/touch.lua)

Events not processed:

  • Symptom: Gamepad interaction logged but not reaching action handlers
  • Cause: Input action map layer not active
  • Solution: Verify role activates appropriate input layers

Technical Reference

File Structure

examples/inputDevice/
├── CMakeLists.txt # Build configuration for exampleInputDevice plugin
├── README.md # This documentation
├── plugin.h/cxx # Plugin entry point and device registration
├── qmlGamepadInputDevice.h/.cxx # Custom input device implementation
├── qmlGamepad.h/.cxx # QML virtual gamepad wrapper
└── export.h # DLL export macros
<VR-Engage-Install-Dir>/data/
├── UI/virtualGamepad/ # QML virtual gamepad UI files
└── simulationModelSets/examples/inputDevice/
├── input/human/touch.lua # Virtual gamepad → action mappings
├── input/humanInput.lua # Includes touch.lua for human roles
└── inputDevice.sms # Simulation model set referencing this data

Key Classes

Class Base Class Purpose Header
DtQmlGamepadInputDevice DtInputDevice Custom input device implementation qmlGamepadInputDevice.h
DtQmlGampad QObject QML virtual gamepad interface qmlGamepad.h

API Methods Used

  • DtInputDevice::init() - Device initialization with input manager
  • DtInputDevice::tick() - Frame-synchronized input processing
  • DtInputDevice::shutdown() - Device cleanup
  • DtInputDeviceFactory::addCreator() - Register custom device class
  • DtVreInputManager::processInput() - Submit input events for processing
  • DtVreInputManager::simulationTime() - Get current simulation timestamp

Input Data Structure

struct DtInputData
{
DtDeviceType myDeviceType; // Device category (joystick, gamepad, etc.)
std::string myDeviceName; // Human-readable device name
int myDeviceIndex; // Instance index for multiple devices
double myTimestamp; // Simulation time of event
DtInputType myInputType; // Input type (axis, button, hat, etc.)
int myInputID; // Specific control ID on device
double myInputValue; // Current value (-1.0 to 1.0 for axes, 0/1 for buttons)
};

Build Targets

  • Plugin: exampleInputDevice.dll (Windows)
  • Install Location: plugins64/vrEngage/release/

Related Documentation: