Sensors

Microsoft::Devices & Microsoft::Devices::Sensors — Accelerometer, Gyroscope, Compass, Motion, VibrateController

Implementation status: Accelerometer and Gyroscope use the real SDL sensor API; Compass and Motion are real on Android and report NotSupported elsewhere; VibrateController uses SDL haptics. The tag contains 36 Devices/Devices-EXT test sources with 525 statically discoverable GoogleTest-family definitions, including injectable backends for headless CI. Hardware-dependent tests self-skip when the required device is absent.

Build flag: the Microsoft::Devices layer is gated behind the CNA_DEVICES CMake option, which defaults to OFF. Configure with -DCNA_DEVICES=ON to build and link it. The path-filtered devices-tests.yml workflow builds both Microsoft::Devices and CNA::Devices tests with sanitizers on relevant pushes and pull requests, and is also manually dispatchable; that is desktop/headless evidence, not a physical-sensor laboratory.

Overview

XNA 4.0 introduced these sensor classes as part of its Windows Phone support, providing a straightforward polling API for tilt, rotation, heading and shake detection. CNA maps them onto two platform integrations: the SDL sensor subsystem for acceleration and angular velocity, and, on Android, the NDK <android/sensor.h> interface for magnetometer-based heading and fused orientation. This is separate from the selected graphics renderer.

Like all XNA input classes, the sensors follow a polling model: call GetState() once per frame, store the result in a state snapshot, and read its fields. No callbacks or event subscriptions are required.

Where a sensor genuinely has no hardware or platform backing, the API is still present and callable — it reports NotSupported rather than fabricating data, so code that guards on IsSupported degrades gracefully without preprocessor guards.

Sensor classes at a glance

Class Renderer Where it is real Status
Accelerometer SDL3 sensor API Android and desktop (Linux, Windows, macOS) Functional
Gyroscope SDL3 sensor API Android and desktop (Linux, Windows, macOS) Functional
Compass Android NDK <android/sensor.h> Android only; NotSupported elsewhere Android-only
Motion Android NDK fusion of 5 sensors Android only; NotSupported elsewhere Android-only
VibrateController SDL haptics Any device with an SDL haptic device (gamepads excluded by design) Functional

The Compass/Motion restriction is a platform limitation, not an unimplemented stub: there is a complete Android implementation, and no equivalent magnetometer/fusion source is wired on other platforms, so those platforms answer honestly instead of returning zeros.

Accelerometer static class

Accelerometer is a static class with no instances. It exposes two members that cover all common use cases:

Member Type Description
GetState() AccelerometerState Returns a snapshot of the current accelerometer reading. Safe to call every frame.
IsSupported bool (static property) true wherever SDL3 reports an accelerometer device — Android and desktop (Linux, Windows, macOS) machines with sensor hardware. false when SDL3 enumerates no such device.

AccelerometerState members

AccelerometerState is a value type (struct) returned by GetState(). It carries two fields:

Member Type Description
Acceleration Vector3 Current acceleration in G-forces. See axis breakdown below.
IsActive bool true when the sensor is running and returning live data; false on unsupported platforms.

Acceleration axis conventions

The Acceleration vector uses the same coordinate conventions as XNA 4.0 on Windows Phone, measured in G-forces (1 G ≈ 9.81 m/s²). SDL3 reports raw acceleration in m/s², and CNA applies the m/s²→G conversion so that readings match XNA's units rather than SDL's:

Component Physical meaning Typical value (device flat)
Acceleration.X Left/right tilt — positive toward the right edge of the device 0.0
Acceleration.Y Forward/back tilt — positive toward the top edge of the device 0.0
Acceleration.Z Up/down (gravity component) — approximately 1.0 when the screen faces up and the device is stationary ~1.0

When the device is held upright (portrait orientation), gravity shifts from Z into the Y axis, so Acceleration.Y approaches −1.0 and Acceleration.Z approaches 0.0. A shake produces transient spikes across all three axes.

Platform support

Platform Accelerometer / Gyroscope Compass / Motion VibrateController
Android Live data via the SDL3 sensor API Live data via the NDK sensor fusion path SDL haptic rumble
Desktop (Linux / Windows) Live data wherever SDL3 enumerates a sensor device NotSupported SDL haptic rumble on a real haptic device

Android is wired through CMake (the NDK toolchain, with the sensor code linking android), but there is no Android CI job and no Android CMake preset — Android builds are a manual step today.

Concurrency hardening

The SDL3 sensor path is not a thin wrapper. Sensor open/close and state reads are guarded by a process-wide mutex, and the implementation issues dispatch tokens that protect against use-after-free when Dispose() is re-entered from a callback. This matters because the same underlying SDL sensor device can be reached from more than one sensor object.

VibrateController

VibrateController is a real implementation rather than a no-op: it initialises rumble with SDL_InitHapticRumble and plays it with SDL_PlayHapticRumble, and additionally drives a genuine SDL_HAPTIC_LEFTRIGHT effect where the device supports one.

It deliberately excludes gamepads from the devices it will open. That is intentional: gamepad rumble belongs to GamePad::SetVibration(), and letting VibrateController claim the same device would mean two APIs fighting over one motor. If you want a controller to rumble, use GamePad::SetVibration().

Known gap

Compass reports TrueHeading as equal to MagneticHeading. CNA has no magnetic-declination data source, so the two headings cannot be distinguished. If your game needs true north, apply your own declination correction.

Code examples

Checking support before use

Always guard sensor use with Accelerometer::IsSupported so that your game degrades gracefully on desktop builds:

// In Game::Initialize() or your platform detection pass
if (Accelerometer::IsSupported) {
    // Sensor is available; enable tilt controls
    tiltControlsEnabled = true;
} else {
    // Fall back to keyboard/gamepad controls
    tiltControlsEnabled = false;
}

Reading the accelerometer each frame (tilt-to-move)

A typical tilt-to-move mechanic reads the state once in Update() and maps the X/Y axes directly to horizontal and vertical forces on a physics body or velocity vector:

// Declared in your Game class
AccelerometerState previousAccel;

// In Update(GameTime gameTime)
AccelerometerState currentAccel = Accelerometer::GetState();

if (currentAccel.IsActive) {
    // Map tilt to movement — dead-zone to filter hand tremor
    const float deadZone = 0.1f;

    float tiltX = currentAccel.Acceleration.X;
    float tiltY = currentAccel.Acceleration.Y;

    if (std::abs(tiltX) < deadZone) tiltX = 0.0f;
    if (std::abs(tiltY) < deadZone) tiltY = 0.0f;

    // Apply as velocity (scale to your game's units per second)
    float speed = 200.0f;
    playerVelocity.X = tiltX * speed;
    playerVelocity.Y = tiltY * speed;  // positive Y = forward on the device
}

previousAccel = currentAccel;

Shake detection

A shake produces a spike in the acceleration magnitude above the normal gravity baseline of ~1 G. Compare the length of the vector against a threshold:

AccelerometerState state = Accelerometer::GetState();

if (state.IsActive) {
    float magnitude = state.Acceleration.Length();
    const float shakeThreshold = 2.5f;  // 2.5 G — adjust per game feel

    if (magnitude > shakeThreshold) {
        // Player shook the device — trigger action
        OnShakeDetected();
    }
}

Gyroscope

Gyroscope is implemented on the same real SDL3 sensor path as Accelerometer, with the same concurrency hardening, and is available on Android and desktop (Linux, Windows, macOS). It reports angular velocity around each axis, which enables more precise orientation tracking and complements the accelerometer for robust tilt controls. It follows the same polling model — call GetState() once per frame.

Compass and Motion

Compass exposes a magnetic heading and Motion exposes a fused device attitude. Both are backed on Android by a real NDK implementation that fuses five underlying sensors through <android/sensor.h>. Both follow the same polling model as the other sensors.

On every non-Android platform they report NotSupported. Check IsSupported before use and provide a fallback if your game needs a heading on desktop or web.