VR-Engage  2.2
Loading...
Searching...
No Matches
QML HUD Example

Overview

Purpose: This example demonstrates how to create custom heads-up display (HUD) overlays using Qt Quick/QML technology for VR-Engage player stations. It shows the pattern for binding VR-Engage state attributes to QML properties, processing simulation messages to extract entity information, and creating responsive animated HUD elements.

Observable Behavior: When running this example, you will see a vehicle status indicator in the lower-left corner showing a tank silhouette. When mobility damage is applied, the tank treads flash red, and when firepower damage is applied, the tank turret flashes red. For human roles, an animated antenna icon appears when the radio is transmitting, with smooth color animations using sinusoidal easing.

Prerequisites: Understanding of the Player Station Framework and familiarity with the Player Attribute Store. Basic Qt/QML knowledge (properties, signals, bindings) and knowledge of the VR-Engage message system are required.

Related Examples: The State Example provides more comprehensive state and QML integration, while Notification Example covers message system and user feedback patterns. The Vehicle Blinker Example demonstrates frontend/backend state synchronization.


Key Concepts Demonstrated

This example implements QML overlay integration using DtQtQuickOverlay for 2D UI over 3D graphics. The framework-provided component handles QML loading and rendering with automatic data binding between C++ attributes and QML properties. It also demonstrates both vehicle and human role QML overlays.

The example covers state attribute extension by creating custom attributes beyond the standard set. The DtDriverQmlHudComponent manages vehicle damage state attributes and demonstrates hierarchical attribute structure (vehicle-status.mobility-kill) with attribute initialization in postInitialize().

Message-driven updates process simulation messages for HUD data by listening for SimulationStateMessage containing entity appearance bits and using the DtAppearance helper to extract semantic damage information. The data binding pattern then provides automatic UI updates from state changes with QML properties bound to attributes configured in the role.


Code Walkthrough

Component Registration

The plugin registers the custom HUD component with VR-Engage's factory system:

// File: examples/qmlHud/plugin.cxx
extern "C"
{
{
LOG_VERBOSE("QML HUD") << "Initializing QML HUD components" << std::endl;
return true;
}
}
Example component demonstrating QML overlay integration with vehicle damage state.
Definition driverQmlHudComponent.h:46
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 DtComponentFactory & componentFactory()
Gets the component factory.
constexpr auto DtDriverQmlHudComponentType
Component type identifier for factory registration and role configuration.
Definition driverQmlHudComponent.h:22
VRECOMMONCOMPONENTS_DLL bool initPlayerStationModule(makVre::DtPlayerStationApp *app)
Module initialization function declarations using C linkage.
#define LOG_VERBOSE(channel)
Macro to log a verbose message to log files.
Definition logger.h:79

The component type string DtDriverQmlHudComponentType is "DtDriverQmlHudComponent", and logging provides visibility during plugin loading. The extern "C" declaration ensures proper symbol export for dynamic loading.

Message Handler Registration

The component monitors simulation state messages to extract damage information:

// File: examples/qmlHud/driverQmlHudComponent.cxx
bool DtDriverQmlHudComponent::initialize(DtPlayerStation* player, DtInitTable& config)
{
if (!DtPlayerComponent::initialize(player, config))
return false;
// Register handler for simulation state updates
DtVreMessageManager::instance().addHandler(SimulationStateMessage::theType(),
DtVreMessageDelegate(this, &DtDriverQmlHudComponent::handleSimState));
return true;
}
virtual makVre::DtVreMessageResult handleSimState(makVre::DtVreMessage *msg)
Handles simulation state messages to update vehicle damage status.
virtual bool initialize(makVre::DtPlayerStation *player, makVre::DtInitTable &config) override
Initializes the component with player station and configuration.
virtual DtPlayerStation & player()
Gets the player station.

Messages are the primary mechanism for receiving entity state updates, while DtVreMessageDelegate provides type-safe callback registration. The handler must be removed in shutdown() to prevent dangling references.

Attribute Hierarchy Creation

The component creates nested attributes that QML will bind to:

{
DtPlayerComponent::postInitialize();
// Create hierarchical attribute structure
myVehicleDamageState["mobility-kill"]->set<bool>(false);
myVehicleDamageState["firepower-kill"]->set<bool>(false);
return true;
}
virtual bool postInitialize() override
Completes initialization after all components are initialized.
makVre::DtAttributeHandle myVehicleDamageState
Handle to the vehicle damage state attribute hierarchy.
Definition driverQmlHudComponent.h:130
bool set(const T &value)
Sets the value of the attribute.
virtual DtAttributeHandle & playerAttributeStore()
Gets the player attribute store.

The postInitialize() method runs after all components exist, making it safe to access the attribute store. The bracket operator [] creates attributes if they don't exist, while initial values prevent undefined behavior before the first message arrives. The QML overlay component will bind to these exact attribute paths.

Message Processing

The component extracts damage state from simulation messages:

DtVreMessageResult DtDriverQmlHudComponent::handleSimState(DtVreMessage* msg)
{
const SimulationStateMessage* smsg = static_cast<SimulationStateMessage*>(msg);
// Filter for our entity only
if (smsg->getId() == myPlayer->entityId().string())
{
DtEntityType entityType = smsg->getType();
unsigned int appearanceBits = smsg->getAppearanceBits();
// Use helper class to extract semantic information
DtAppearance appearance(entityType);
appearance.setAppearance(appearanceBits);
// Update attributes (automatically propagates to QML)
myVehicleDamageState["mobility-kill"]->set<bool>(appearance.immobilized());
myVehicleDamageState["firepower-kill"]->set<bool>(appearance.firePowerDisabled());
}
return HANDLED;
}
DtPlayerStation * myPlayer
Pointer to the owning player station.
Definition playerComponent.h:182
virtual const DtEntityIdentifier & entityId() const
Gets the entity ID for the controlled entity.

The entity ID check prevents processing other entities' state, while DtAppearance abstracts DIS appearance bit field complexity. Attribute updates automatically trigger QML property updates without manual notification, and returning HANDLED allows other components to process the message.

Role Configuration with QML Overlay

The role Lua file configures both the QML overlay and supporting component:

-- File: data/simulationModelSets/examples/qmlHud/roles/driverOverlay.lua
inherits = "@(vre-roles-dir)/driver.lua";
components = {
["indicatorOverlay"] = {
componentType = "DtQtQuickOverlay";
priority = 10;
qmlFilename = "examples/exampleQmlHudDriver.qml";
qmlDataItemObjectName = "stateItem";
bindQmlPropertyToAttribute = {
mobilityKill = "vehicle-status.mobility-kill";
firePowerKill = "vehicle-status.firepower-kill";
};
};
["indicatorOverlayInfo"] = {
componentType = "DtDriverQmlHudComponent";
priority = 4;
};
};

The role inherits from the standard driver role and adds custom components. Priority numbers determine initialization order, with lower values initializing first (4 before 10). The bindQmlPropertyToAttribute table maps QML property names to attribute paths, while qmlDataItemObjectName identifies which QML Item receives the bound properties. The two components work together: one manages state, one renders UI.

QML Overlay Structure

The QML file defines the visual overlay with data binding:

// File: data/UI/examples/exampleQmlHudDriver.qml
Item {
id: root
// Data item that receives bound properties from C++
Item {
id: state
objectName: "stateItem" // Must match qmlDataItemObjectName in config
// Properties automatically updated by VR-Engage framework
property bool mobilityKill: false
property bool firePowerKill: false
// Called each frame after property updates
signal tick(double dt)
// Trigger animations when damage state changes
onMobilityKillChanged: {
if (state.mobilityKill)
mobilityKillFlash.restart();
else
mobilityKillFlash.complete();
}
}
// Visual elements (SVG images with color overlays)
// Sequential animations for smooth color transitions
// ...
}

The state item serves as the central object for all bound properties. Property change handlers react to C++ attribute updates and trigger animations declaratively, with QML handling timing and interpolation. The hudScale() function adapts element sizes to different screen resolutions.


Architecture

Component Interaction Diagram

sequenceDiagram
    participant SM as Simulation Message
    participant HC as HUD Component
    participant AS as Attribute Store
    participant QO as Qt Quick Overlay
    participant QML as QML Interface

    SM->>HC: SimulationStateMessage
    HC->>HC: Extract appearance bits
    HC->>AS: Update vehicle-status.mobility-kill
    HC->>AS: Update vehicle-status.firepower-kill
    AS->>QO: Attribute change notification
    QO->>QML: Bind attributes to QML properties
    QML->>QML: Update UI & trigger animations

Data Flow

  1. Initialization Phase:
  2. Runtime Phase:
    • Simulation sends SimulationStateMessage
    • DtDriverQmlHudComponent::handleSimState() filters and processes
    • Attributes updated with damage state
    • Framework automatically propagates changes to QML properties
    • QML property change handlers trigger animations
  3. Shutdown Phase:
    • Components shut down in reverse priority order
    • Message handlers unregistered
    • QML overlay closed

Deployment and Testing

Installation

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

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

Install the plugin to the VR-Engage installation:

cmake --install . --config RelWithDebInfo

This copies the plugin to the appropriate location:

  • Frontend plugin: <VR-Engage-Install-Dir>\plugins64\vrEngage\release\

Verify installation:

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

Launching with the Example Simulation Model Set (optional)

For quick standalone testing without creating a separate VR-Forces scenario, you can run VR-Engage with the qmlHud.sms example simulation model set as the default. This loads the example roles and data installed under data/simulationModelSets/examples/qmlHud:

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

When connecting to a separate VR-Forces exercise instead, use the scenario-based configuration described below.

Configuration

Create a test scenario:

  1. Launch VR-Forces and create a new scenario using the examples/qmlHud.sms simulation model set
  2. Add the following entities:
    • LAV III APC (armored personnel carrier) - for vehicle HUD testing
    • US Army M4 (human soldier) - for human radio HUD testing

Testing Procedure

  1. Launch VR-Forces with your test scenario and Launch VR-Engage to connect to the exercise
  2. Vehicle HUD Testing:
    • Engage with the LAV III APC using the "Driver" role
    • Expected Behavior: Vehicle status icon appears in lower-left corner showing tank silhouette
    • Damage Testing: In VR-Forces, apply damage to test HUD animations:
      • Apply mobility kill damage → vehicle treads flash red
      • Apply firepower kill damage → turret flashes red
      • Verify smooth color transitions and animation timing
  3. Human Radio HUD Testing:
    • Engage with the US Army M4 using the "Human Player" role
    • Expected Behavior: Animated antenna icon appears when transmitting
    • Radio Testing: Transmit on radio → verify antenna icon appears with smooth animations
  4. Console Verification:
    • Check console output shows: "Initializing QML HUD components"
    • Verify HUD elements are properly positioned over the 3D scene

Verification: The HUD demonstrates real-time data binding between C++ component state and QML visual elements, with automatic property updates and smooth visual transitions for both vehicle damage states and human radio transmission states.


Troubleshooting

Common Issues

Symptom Diagnosis Solution
HUD not visible QML file not found or wrong entity type Verify entity is LAV III APC with "Driver" role selected
Properties not updating Attribute name mismatch Check exact paths in Lua config match C++ code
Wrong role available Entity not configured for QML HUD Use LAV III APC entity with "Driver" role only
Crash on shutdown Handler not removed Verify removeHandler() in shutdown()
Animation not playing Property not triggering Add console.log() in QML to verify property changes

Debug Logging

Add diagnostic output to component:

// In handleSimState()
LOG_INFO("QmlHud") << "Entity " << smsg->getId()
<< " mobility=" << appearance.immobilized()
<< " firepower=" << appearance.firePowerDisabled()
<< std::endl;
#define LOG_INFO(channel)
Macro to log an informational message to log files.
Definition logger.h:74

Plugin not loading:

  • Verify DLL is in correct plugins directory: <VR-Engage-Install-Dir>\plugins64\vrEngage\release\
  • Check dependencies with Dependency Walker or dumpbin /dependents
  • Review the VR-Engage log (most recent *.log file in the MAK log directory, typically C:/MAK/logs) for error messages

Technical Reference

File Structure

examples/qmlHud/
├── CMakeLists.txt # Build configuration with debug setup
├── README.md # This documentation
├── plugin.h/.cxx # Plugin entry point and factory registration
└── driverQmlHudComponent.h/.cxx # Component implementation for vehicle damage HUD

Key Classes

Class Base Class Purpose Header
DtDriverQmlHudComponent DtPlayerComponent Vehicle damage state tracking and attribute publishing driverQmlHudComponent.h
DtQtQuickOverlay DtPlayerComponent Framework-provided QML overlay rendering (VR-Engage framework)

API Methods Used

  • DtVreMessageManager::addHandler() - Register message handler callbacks
  • DtVreMessageManager::removeHandler() - Unregister message handlers
  • DtPlayerStation::playerAttributeStore() - Access centralized attribute repository
  • DtAttributeHandle::set<T>() - Type-safe attribute value assignment
  • DtAppearance::immobilized() / firePowerDisabled() - DIS appearance bit interpretation
  • DtPlayerStationApp::componentFactory().addCreator<T>() - Component factory registration

Attribute Details

Attribute Path Type Description Updated By
vehicle-status.mobility-kill bool True when vehicle is immobilized DtDriverQmlHudComponent
vehicle-status.firepower-kill bool True when vehicle weapons are disabled DtDriverQmlHudComponent

Dependencies

Category Libraries
VR-Engage vrePlayerStation, vreMessageManager, vreMessages, vreCommonComponents, vreUtil
Qt Qt5::Core, Qt5::Qml, Qt5::Quick
VR-Link vlutil (includes DtAppearance helper)

Build Targets

  • Plugin: exampleQmlHud.dll (Windows)
  • Install Location: plugins64/vrEngage/release/
  • Debug Configuration: Automatically loads qmlHud.sms simulation model set

Runtime Files

File Type Location
QML overlays <VR-Engage-Install-Dir>/data/UI/examples/exampleQmlHud*.qml
Role configs <VR-Engage-Install-Dir>/data/simulationModelSets/examples/qmlHud/roles/*.lua
SMS file <VR-Engage-Install-Dir>/data/simulationModelSets/examples/qmlHud.sms

Related Documentation: