Back to Blog

Godot on XREAL AURA: A Practical Guide to Native Spatial Development

Published on November 3, 2026
Godot on XREAL AURA: A Practical Guide to Native Spatial Development Generated with the help of AI

In a nutshell

Learn Godot XREAL AURA XR development with native editor setup, OpenXR spatial containers, Android XR export steps, and real code for spatial panels.

Your XR build pipeline currently means tethering to a desktop, exporting an APK, sideloading it over ADB, putting on the headset, discovering a typo, and starting over. XREAL AURA removes that loop entirely, and Godot is in the launch lineup with a full editor that runs on the glasses themselves. That changes what "iterate" means for spatial development.

This guide walks through the actual workflow: getting the editor running on AURA, structuring a spatial scene with OpenXR Spatial Containers, handling the passthrough/see-through model that makes optical glasses different from VR headsets, and the pitfalls that will bite you on your first export.

Why Optical See-Through Changes Your Design Assumptions

XREAL AURA is the first optical see-through device on Android XR. That single sentence breaks assumptions baked into most Godot XR tutorials.

In a VR headset, the headset camera renders everything. The user sees your environment and nothing else. On optical see-through glasses, the user sees through the lenses at the real world, and your rendered content is composited on top. This is not the same as video passthrough on a Quest — video passthrough captures reality with cameras and re-displays it, which introduces latency and light-loss. Optical see-through has zero compositor latency for the real world.

Practical consequences for your rendering budget:

  • You cannot darken the real world. Black on a transparent display is transparent. You have no opaque backdrop, so UI that relies on contrast against dark panels will fail in a bright room.
  • Additive-looking content wins. Bright emissive content reads well; dark, desaturated content disappears. Design your palette at 2-3x the luminance you would use in VR.
  • Field of view is narrower than VR. Content placed at extreme angles falls outside the display area. Keep primary interaction targets within roughly ±30 degrees of forward center.

If you have shipped VR before, expect to re-tune every material and every UI panel. Budget a full day for it per scene.

Setting Up the Godot Editor on the Glasses

The editor is distributed on Google Play, not as a sideloaded APK. That matters because it means you can update it like any other app.

The setup sequence:

  1. Install the Godot editor on XREAL AURA from the Play Store.
  2. Install the companion Gradle build environment app. This ships the Android export toolchain, so you are not building a JDK and Android SDK from scratch on the device.
  3. Sign in to an Android developer identity if you plan to sign and publish builds from the device.
  4. Launch the editor — it opens in a multi-window spatial workspace rather than a single flat window.

That fourth point is the interesting one. The editor gives you the desktop-style feature set, but arranged as floating windows in space. You can keep the Scene tree in one volume and the 2D viewport in another.

What the On-Device Workflow Actually Gets You

The honest answer: fast iteration on feel, slower iteration on assetry.

Editing shaders, tweaking node transforms, and testing input mappings on the device you are targeting eliminates the entire export-test cycle. That cycle is typically 3-8 minutes once you factor in build time, transfer, and relaunch. Ten iterations is an hour gone.

What it does not help with: importing large texture atlases, running long lightmap bakes, or anything CPU-heavy. Do that on a workstation. The realistic split is asset creation on desktop, scene assembly and tuning on the glasses.

Building Spatial Content with OpenXR Spatial Containers

Godot's Android XR support includes OpenXR Spatial Containers — system-managed volumes where your app places spatial content. The analogy from the platform itself is close to accurate: traditional apps draw 2D content inside windows on a desktop; spatial apps draw 3D content inside containers in a room.

There are two container types worth understanding before you write code:

Container type Best for Trade-off
Volume 3D objects with real depth, world-locked placement System may clamp the volume bounds
Panel UI surfaces, text, 2D games Rendered at a fixed plane; depth cues are flat

The mistake most developers make is choosing volume for UI. Text in a volume container gets scaled and moved by the system in ways you cannot fully predict, and your carefully positioned radar widget drifts. Put UI in a panel container and let the system handle placement.

Working With the Spatial Container API

Godot exposes spatial containers through OpenXR extension classes. The exact class names depend on your Godot version, so check the engine's list of features for the current spatial container bindings before writing production code. The pattern below reflects how container-based XR content is typically structured.

extends Node3D

signal container_created(handle: int)

# Spatial container setup for Android XR.
# Class names are version-dependent; verify against the
# OpenXR extension bindings shipped with your Godot build.

var volume_container: SpatialContainer
var panel_container: SpatialContainer

func _ready() -> void:
    var xr_interface: XRInterface = XRServer.find_interface("OpenXR")
    if xr_interface == null or not xr_interface.is_initialized():
        push_error("OpenXR interface unavailable on this device.")
        return

    # Volume container for the 3D board.
    volume_container = SpatialContainer.new()
    volume_container.container_type = SpatialContainer.TYPE_VOLUME
    volume_container.extent = Vector3(0.6, 0.4, 0.6)  # 60cm x 40cm x 60cm
    if volume_container.request_container() != OK:
        push_error("Volume container request failed.")
        return

    # Panel container for the HUD.
    panel_container = SpatialContainer.new()
    panel_container.container_type = SpatialContainer.TYPE_PANEL
    panel_container.extent = Vector3(0.4, 0.4, 0.02)
    panel_container.request_container()

    add_child(volume_container)
    add_child(panel_container)

    container_created.emit(volume_container.get_container_handle())

func _process(_delta: float) -> void:
    # Containers can be rejected or resized by the system.
    if volume_container and not volume_container.is_active():
        _handle_container_loss()

func _handle_container_loss() -> void:
    push_warning("Volume container lost - repositioning.")
    # Never assume the container survives; re-request on loss.
    volume_container.request_container()

The critical line is _handle_container_loss(). System-managed containers are, by definition, managed by the system. The user walks away, the assistant repositions content, or the device reclaims space. If your input handling does not survive a container loss, your app will sit there with an invisible board and a player mashing buttons.

The Pitfall Almost Everyone Hits: Anchor Drift

Here is the specific problem, and it is the one that will cost you the most debugging time.

You place a 3D board in a volume container. You hit Play. Everything looks right. You take off the glasses, put them back on two minutes later, and the board is now half a meter to the left and rotated 20 degrees.

Why? Because you anchored your content to world origin rather than to the container. World origin on a spatial device is not a stable place — it is defined relative to the session start pose. When the session re-anchors, world origin moves.

The fix is to keep every piece of your scene as a child of the container transform, and never read or write global position for spatial content:

# WRONG: assumes world origin is stable
func place_piece(piece: Node3D, board_pos: Vector3) -> void:
    piece.global_position = board_pos

# RIGHT: position relative to the container transform
func place_piece(piece: Node3D, board_pos: Vector3) -> void:
    piece.position = board_pos   # local to container
    piece.rotation = Vector3.ZERO

Do a grep across your project for .global_position and .global_transform before you ship. Every hit in spatial content is a potential drift bug. This class of bug shows up in conventional 3D and multiplayer work too — anchor and ownership mistakes on a replicated transform produce the exact same "object teleported for some clients" symptom, which is why treating transforms as container-local rather than absolute is a habit worth carrying into any networked Godot project.

Verifying the Fix

To confirm anchoring is correct:

  1. Launch your scene and note the board's position.
  2. Remove the device, wait 60 seconds, re-enter the app.
  3. The board should appear in the same physical location relative to the room, not the same offset from wherever you are standing.
  4. Repeat with the device at a different orientation — sit, then stand.

If it drifts, your content tree still has absolute references. If it jumps but stays consistent after the jump, the container is being repositioned and your content is following correctly — that is expected behavior, not a bug.

Handling Player Identity and Persistent State

Spatial apps on Android XR are Android apps. They suspend, they resume, the user may have multiple signed-in identities on the device, and your save data lives in the same account ecosystem as everything else.

The failure mode is subtle: a player plays spatially on the glasses, then opens the same game on their phone. If your save state is device-local, they now have two divergent worlds and no way to reconcile them. Fixing this is not a rendering problem, it is an account-identity problem.

Getting it right yourself means implementing sign-in flows, a server-side save store, and — the part everyone underestimates — conflict handling when two devices write to the same save slot. A revision-aware read/write model where the client compares versions and resolves conflicts explicitly is the only version that survives contact with real users. Building that from scratch is a multi-week job; horizOn provides account-bound cloud save with revision-aware reads and writes exactly for this case, along with email, anonymous, Google, and Apple authentication so the same player identity carries across the glasses and the phone.

Whatever backend you use, decide the conflict policy before you ship. Last-write-wins silently destroys progress. Prompting the player is better than losing data.

Best Practices for Shipping on XREAL AURA

  1. Design for ambient light. Test every UI panel outdoors at noon and in a dim room. If your text becomes unreadable in either, raise contrast and add a subtle bright outline rather than a dark shadow — shadows do nothing on a transparent display.
  2. Keep containers small and re-requestable. A 60cm volume is easier for the system to place reliably than a 2m one. Assume every container can be lost at any time and write the recovery path first, not last.
  3. Cap your render budget aggressively. These are glasses, not a desktop GPU. Target a frame time under 11ms (90Hz-class) with temporal anti-aliasing off in favor of MSAA, which costs less at spatial resolutions.
  4. Put UI in panels, content in volumes. Do not fight the system's placement logic for text. Reserve volumes for things with genuine depth that the player inspects from multiple angles.
  5. Verify on device before every milestone. The on-glasses editor makes this cheap. Simulator results will not reveal anchor drift, luminance failures, or container loss behavior.

Taking the Next Step

The on-device editor is the real headline here. It compresses the spatial iteration loop from minutes to seconds, and that is the difference between tuning a design and guessing at one.

Start by installing the editor and companion Gradle app on AURA, then rebuild a small existing project as a spatial scene rather than starting fresh — you will surface the anchoring and luminance issues immediately, on content you already understand. From there, add a 3D volume container and a UI panel and get both stable across a suspend/resume cycle. Once that holds, you are ready to design for the platform instead of against it.

If persistent cross-device player state is on your list, horizOn handles account-bound cloud save with client-side conflict resolution so your spatial session and your flat-screen session stay in agreement.


Source: Ready for XREAL AURA