|
VR-Engage
2.2
|
The VR-Engage input system provides a flexible framework for handling user input from keyboards, mice, game controllers, joysticks, and specialized simulation hardware. This section covers the architecture from a developer's perspective, including how to create custom input handlers, define new actions, and integrate with the backend simulation.
The input system follows a layered architecture that separates physical device handling from logical action processing. This separation enables flexible input remapping, multi-device support, and runtime configuration without code changes.
flowchart TB
subgraph Devices["Physical Devices"]
KB[Keyboard]
Mouse[Mouse]
Gamepad[Gamepad]
HOTAS[HOTAS/Flight Stick]
Custom[Custom Hardware]
end
subgraph InputLayer["Input Layer"]
DeviceManager[Device Manager]
RawEvents[Raw Input Events]
end
subgraph MappingLayer["Mapping Layer"]
ConfigFiles[Lua config files]
ActionMapper[Action Mapper]
LayerStack[Layer Stack]
end
subgraph ActionLayer["Action Layer"]
ActionDispatcher[Action Dispatcher]
Handlers[Action Handlers]
end
subgraph Components["Components"]
ControlLogic[Control Logic]
UIComponents[UI Components]
end
KB --> DeviceManager
Mouse --> DeviceManager
Gamepad --> DeviceManager
HOTAS --> DeviceManager
Custom --> DeviceManager
DeviceManager --> RawEvents
RawEvents --> ActionMapper
ConfigFiles --> ActionMapper
ActionMapper --> LayerStack
LayerStack --> ActionDispatcher
ActionDispatcher --> Handlers
Handlers --> ControlLogic
Handlers --> UIComponents
The device manager polls connected devices and generates raw input events. The action mapper transforms these events into named actions based on configuration files. The layer stack manages priority when multiple mapping configurations are active simultaneously. Finally, action handlers in components respond to the dispatched actions.
Input processing spans both frontend and backend processes. The frontend captures physical input and generates actions, while the backend validates and applies those actions to the simulation state.
sequenceDiagram
participant Device as Input Device
participant Frontend as Frontend Process
participant Message as Message Layer
participant Backend as Backend Process
participant Sim as Simulation State
Device->>Frontend: Raw Input Event
Frontend->>Frontend: Map to Action
Frontend->>Frontend: Local Feedback (UI)
Frontend->>Message: Control Command
Message->>Backend: Receive Command
Backend->>Backend: Validate Command
Backend->>Sim: Apply to Simulation
Sim->>Message: State Update
Message->>Frontend: Receive Update
Frontend->>Frontend: Update Display
The frontend provides immediate feedback for responsive user interaction, while the backend maintains authoritative simulation state. This separation produces consistent behavior across networked simulations while preserving responsiveness.
Here are the essential steps for the most common input customizations:
driver/keyboard.lua): initialize() method: Modify or add entries in the appropriate device config file. No C++ changes are required—the action name binds the config to the existing handler.
DtInputDevice and implement init(), tick(), and shutdown()DtInputDeviceFactory::addCreator<>()Input mappings are defined in Lua configuration files within the simulation model set. These files specify how physical inputs map to logical actions and support features like dead zones, scaling, and inversion.
Input configuration files are loaded from the path specified by playerStationApp.inputSettingsPath in the application's Lua startup script. The default VR-Engage configuration sets this to:
Custom applications can point to their own input directory. For example, the vehicle blinker example uses:
To customize input mappings, either modify files in your simulation model set's input directory or create a custom model set with its own input configuration path.
The following device type strings are recognized in configuration files:
| DeviceType | Description |
|---|---|
"keyboard" | Standard keyboard |
"mouse" | Mouse buttons and movement |
"gamepad" | Xbox-style game controllers |
"joystick" | Generic joystick/flight stick |
"hotas" | HOTAS (Hands On Throttle-And-Stick) devices |
"touchController" | VR touch controllers |
For gamepads and joysticks, axis and button IDs in configuration files correspond to SDL (Simple DirectMedia Layer) indices. VR-Engage includes a Game Controller Tool (gameControllerTool.exe) to help identify these values for your hardware.
List all connected devices:
Example output:
Get verbose information about a specific device, including axis and button names:
This shows the mapping between numeric IDs and named controls (for recognized game controllers).
Monitor live input events to see exactly which axis or button corresponds to each physical control:
This mode prints events as you move axes or press buttons:
Use the axis and button IDs from this output directly in your Lua configuration files.
Top-level role input files list one or more per-device mapping files. For example, the driver role input file loads keyboard, gamepad, and joystick mappings:
Each per-device file defines one or more inputDevices entries with mapping groups and actions. For example, driver keyboard mappings:
Analog axes and other continuous inputs use valueTransforms to adapt device characteristics to application requirements. Supported transform types are registered in DtVreInputManager::valueTransformFactory() and include:
dead-zone – ignore small input magnitudes (field: value)gain – amplify or attenuate input (field: value)invert – flip the sign of the inputscale – multiply input by a constant factor (field: value)remap – remap one numeric range into another (fields: inputMin, inputMax, outputMin, outputMax)Example gamepad steering axis with dead zone and gain:
Example joystick throttle and brake remapped from device range to 0–1:
Components typically use DtInputLogic (or a subclass) to load input mappings and register action handlers during initialization.
Important: The action name strings (e.g.,
"steering","throttle") must match exactly between the Lua configuration and the C++ handler registration. These strings are case-sensitive. If a Lua config defines an action with no registered handler, the input is silently ignored. If a handler is registered for an action not defined in any active config, it simply never fires.
Action handlers are registered via DtActionDelegate, which is defined as:
Handlers therefore have a single float parameter:
| Parameter | Type | Description |
|---|---|---|
value | float | Normalized action value (typically -1.0 to 1.0 for axes, 0.0 or 1.0 for buttons) |
Press/release semantics for buttons are represented by the value (for example, 0.0 for released, 1.0 for pressed); axis actions use the full continuous range supplied by the input mappings.
The input system supports multiple active mapping layers. Each call to DtInputLogic::loadInputConfig adds a mapping layer backed by a Lua config file and a mapping group. Layers are stored in a stack inside DtVreInputManager, with the front of the stack processed first.
DtVreInputManager::addMappingLayer takes an alwaysOnTop flag as well as an alwaysEnabled flag. Layers marked alwaysOnTop stay at the top of the stack; otherwise, layers are inserted below any existing alwaysOnTop layers. When multiple layers define the same action, the first layer in the stack that handles the input wins, enabling context-sensitive remapping.
Consider a scenario where both the application layer and the driver layer define an "escape" action:
When the user presses Escape, layer [1] handles the input first. If the app layer's handler consumes the event (returns without propagating), the driver layer never sees it. This enables modal UI contexts like menus to intercept inputs that would otherwise control the simulation.
For specialized hardware not supported by the built-in devices, implement a custom device class that inherits from DtInputDevice and register it with DtInputDeviceFactory.
When do you need a custom device? The built-in device types handle standard keyboards, mice, gamepads, and joysticks automatically. You only need a custom device for:
- Hardware with proprietary SDKs (motion platforms, custom cockpit panels)
- Virtual input sources (touch screen overlays, network-based remote controls)
- Devices requiring special initialization or polling logic
Register the custom device type during plugin initialization:
Common issues and diagnostic approaches for input problems:
Enable input system logging by setting the log level for the vreInput category:
This logs each config file as it loads and reports parsing errors with line numbers.
Add temporary logging in your action handler to verify it receives events:
| Symptom | Likely Cause | Solution |
|---|---|---|
| Action never fires | Typo in action name (Lua vs C++) | Check case-sensitive spelling in both locations |
| Wrong device responds | Incorrect DeviceType in config | Verify device type string matches your hardware |
| Axis inverted or scaled wrong | Missing or incorrect valueTransforms | Add invert or adjust gain/scale values |
| Config changes ignored | File in wrong directory | Verify inputSettingsPath in your startup script |
| Lua syntax error | Missing semicolons or braces | Check log output for parser errors with line numbers |
| Unknown axis/button ID | Using wrong numeric ID | Run gameControllerTool.exe --debug to identify correct IDs |
| Device not detected | Device not connected or recognized | Run gameControllerTool.exe --listDevices to verify detection |
For end-user documentation on supported controllers, default key mappings, and controller configuration procedures, see "Chapter 12. Mouse, Keypad, and Controller Mappings" in the VR-Engage User Guide.
See also