Using Apple's CoreMotion APIs with Godot
May 13, 2026 · View on GitHub
This is a guide on using the CoreMotion APIs in this Godot addon. For an overview of what you can do with CoreMotion, check Apple's CoreMotion Documentation.
As with the rest of GodotApplePlugins, the binding surfaces the same class names that Apple uses for their own data types to simplify looking things up and finding resources online. Method names follow the Godot naming scheme (snake_case instead of camelCase).
Table of Contents
- Available Types
- Info.plist and Permissions
- Choosing Between Raw Sensors and Device Motion
- Platform Notes
- Examples
Available Types
The CoreMotion binding is documented in doc_classes/ and in the
published API reference. The surface includes:
Motion (handheld device)
CMMotionManager— entry point for accelerometer, gyroscope, magnetometer, and fused device motionCMAccelerometerData— a raw accelerometer sampleCMGyroData— a raw gyroscope sampleCMMagnetometerData— a raw magnetometer sampleCMDeviceMotion— fused attitude, gravity, user acceleration, rotation rate, and magnetic field
Headphone Motion
CMHeadphoneMotionManager— head motion from AirPods and other compatible headphones (re-usesCMDeviceMotion)
Pedometer
CMPedometer— step, distance, floor, pace, and cadence countsCMPedometerData— a single pedometer reading covering a time window
Altimeter
CMAltimeter— barometric pressure-based altitudeCMAltitudeData— a relative altitude / pressure sampleCMAbsoluteAltitudeData— a sea-level-referenced altitude sample (iOS 15+)
Activity Classification
CMMotionActivityManager— classifies the user's current activityCMMotionActivity— one classification (walking, running, automotive, cycling, stationary, unknown) with a confidence level
Info.plist and Permissions
Most CoreMotion APIs are permission-gated. You need at least:
<key>NSMotionUsageDescription</key>
<string>This game uses motion data to ...</string>
The NSMotionUsageDescription key covers CMMotionManager (accelerometer/gyroscope/magnetometer/device motion), CMPedometer, CMAltimeter, CMMotionActivityManager, and CMHeadphoneMotionManager. The first time your project starts updates on any of these, iOS prompts the user; the user's decision is reported via the authorization_status() static methods (where applicable).
Choosing Between Raw Sensors and Device Motion
The raw start_accelerometer_updates() / start_gyro_updates() / start_magnetometer_updates() deliver the device's unprocessed sensor readings — accelerometer values include gravity, gyroscope values include drift bias, and magnetometer values include local magnetic interference.
start_device_motion_updates() runs Apple's sensor fusion pipeline and gives you:
- Attitude as both a
Quaternionand roll/pitch/yaw Euler angles - Gravity and user acceleration separated from one another
- Bias-corrected rotation rate
- Bias-corrected magnetic field with a calibration-accuracy enum
For almost all gameplay use cases (head/device tilt, shake detection, motion-controlled cameras), CMDeviceMotion is what you want. Reach for the raw streams only when you need very high sample rates or you're doing custom sensor fusion.
Platform Notes
- iOS — Full support for every type in this binding.
- macOS —
CMHeadphoneMotionManagerworks on macOS 14+ (returns motion from connected AirPods). The other managers are surfaced but theiris_*_availablestatic methods returnfalseand starting updates emits a*_failedsignal with a "not available" message — this matches Apple's own coverage, since CoreMotion sensors require an iPhone or Apple Watch. - visionOS — All managers are surfaced as non-operational stubs. Use the visionOS-native ARKit
WorldTrackingProvider(viaARSessionin this package) for head pose instead. - Linux / Windows — All managers are surfaced as non-operational stubs so a single GDScript file can compile and run across platforms; calls emit
*_failedsignals.
Examples
Tilt-controlled camera with device motion
var motion := CMMotionManager.new()
func _ready() -> void:
if not motion.is_device_motion_available:
return
motion.device_motion_update_interval = 1.0 / 60.0
motion.device_motion_updated.connect(_on_device_motion)
motion.update_failed.connect(func(msg): push_warning(msg))
motion.start_device_motion_updates(CMMotionManager.XARBITRARY_Z_VERTICAL)
func _on_device_motion(m: CMDeviceMotion) -> void:
# Use the quaternion directly to orient a Node3D
$Camera3D.transform.basis = Basis(m.attitude_quaternion)
Step counter
var pedometer := CMPedometer.new()
func _ready() -> void:
if not CMPedometer.is_step_counting_available():
return
pedometer.pedometer_updated.connect(_on_pedometer_updated)
pedometer.pedometer_failed.connect(func(msg): push_warning(msg))
pedometer.start_updates(Time.get_unix_time_from_system())
func _on_pedometer_updated(data: CMPedometerData) -> void:
print("Steps since start: ", data.number_of_steps)
Querying historical activity
var activity := CMMotionActivityManager.new()
func dump_last_hour() -> void:
if not CMMotionActivityManager.is_activity_available():
return
var now := Time.get_unix_time_from_system()
activity.query_activity(now - 3600.0, now, func(activities, error):
if error:
push_warning(error)
return
for a in activities:
print(a.start_date, " walking=", a.walking, " automotive=", a.automotive)
)
Head-tracked audio with AirPods
var headphones := CMHeadphoneMotionManager.new()
func _ready() -> void:
headphones.device_motion_updated.connect(_on_head_motion)
headphones.connected.connect(func(): print("AirPods connected"))
headphones.disconnected.connect(func(): print("AirPods disconnected"))
headphones.update_failed.connect(func(msg): push_warning(msg))
headphones.start_device_motion_updates()
func _on_head_motion(m: CMDeviceMotion) -> void:
$AudioListener.transform.basis = Basis(m.attitude_quaternion)