Sample CodeiOS, iPadOS, Mac Catalyst, macOS, visionOSReviewed 2026-11-01View on Apple Developer

Creating a navigation mesh with Reality Composer Pro

At a glance

Item Summary
Purpose Move a character around a game level by defining its walkable surface and driving motion with a behavior tree.
App architecture A two-file SwiftUI shell. RCPNavigationMeshApp starts the RealityKitScripting runtime and opens an immersive space (visionOS) or a window (other platforms). ContentView loads one exported scene into a RealityView and forwards input to the scene’s script graphs. Gameplay is not written in Swift.
Main patterns No named design pattern. The Swift code only wires the app to the asset; the navigation mesh, behavior tree, and script graphs are Reality Composer Pro data.
Project style One application target backed by a file-system-synchronized RCP-NavigationMesh/ folder, plus one remote Swift package product (RealityKitScripting). The Reality Composer Pro project sits beside the Xcode project, and the app folder holds the exported .reality file.
Execution model The target defaults to MainActor isolation. The only suspension point is try await Entity(named:) inside the RealityView make closure. The source contains no Task, dispatch queue, or Swift System.
State/event model The Swift code has no app state. Taps and drags reach the script graphs through .enableRealityKitScriptingInput().
Key frameworks/packages SwiftUI, RealityKit, RealityKitScripting imports. The project manifest declares RealityKitScripting as a remote package product.

Project structure

RCP-NavigationMesh.xcodeproj/        one app target, one remote package product
RCP-NavigationMesh/                  synchronized root group for the app target
├── RCPNavigationMeshApp.swift       @main App: runtime setup + scene selection
├── ContentView.swift                RealityView: load "world", add camera, input
├── rcp-navigationmesh.reality       exported scene the app loads at run time
└── Assets.xcassets
rcp-navigationmesh.realitycomposerpro/   authoring project (not in the target)
├── Entities/        world, Player, NavigationTargetMarker
├── Behaviors/       Player_Behavior behavior tree
└── Script Graphs/   TapTerrain, PlayerLogic, PlayerModelLogic, ...
Info.plist                           visionOS-only scene manifest
Configuration/SampleCode.xcconfig

Structure observations

  • The target has a single app product. Its only package product dependency is RealityKitScripting (RCP-NavigationMesh.xcodeproj/project.pbxproj:90), resolved from a remote repository with an up-to-next-minor requirement starting at 1.4.0 (RCP-NavigationMesh.xcodeproj/project.pbxproj:387). There is no local Swift package and no Package.swift in the tree.
  • RCP-NavigationMesh/ is a PBXFileSystemSynchronizedRootGroup (RCP-NavigationMesh.xcodeproj/project.pbxproj:23). The exported rcp-navigationmesh.reality file sits in that folder next to the Swift files. Inference: that placement is what lets Entity(named: "world") find the scene in the app bundle at run time.
  • The rcp-navigationmesh.realitycomposerpro authoring project is not referenced by the Xcode project. Its asset files hold the navigation behavior. Entities/world.tm_entity contains a tm_navigation_mesh_component. Entities/Player.tm_entity contains tm_navigation_component and tm_re_behavior_tree_component. Behaviors/Player_Behavior.tm_behavior_tree uses a BTMoveOnNavMeshNodeDefinition node. This is a deliberate split: code handles hosting, assets handle gameplay.
  • The root Info.plist applies only to visionOS SDKs (INFOPLIST_FILE[sdk=xros*]). It makes an immersive space the preferred default scene role, with mixed immersion (Info.plist:8, Info.plist:16).

Platform boundary

Apple’s documentation lists iOS, iPadOS, Mac Catalyst, macOS, and visionOS. The project file lists iphoneos, macosx, and xros as supported platforms and sets SUPPORTS_MACCATALYST = NO (RCP-NavigationMesh.xcodeproj/project.pbxproj:185). The downloaded project therefore builds native macOS rather than Mac Catalyst. The front matter keeps Apple’s documented platform list.

Overall architecture

Reference code

RCP-NavigationMesh/RCPNavigationMeshApp.swift:19 — RCPNavigationMeshApp.init and body

@main
struct RCPNavigationMeshApp: App {
    // ...
    init() {
        // The scene doesn't use hand tracking or world sensing, so it skips
        // the ARKit input provider.
        ScriptingRuntime.setup(inputOptions: .all.subtracting(.ar))
    }

    var body: some Scene {
        #if os(visionOS)
        // An immersive space is unbounded, so the level isn't clipped to a box
        // and the person wearing the device can walk around it.
        ImmersiveSpace {
            ContentView()
        }
        #else
        WindowGroup {
            ContentView()
        }
        .defaultSize(width: Self.windowSize, height: Self.windowSize)
        #endif
    }
}

Interpretation

Observed: the app entry point does two things. It configures the scripting runtime synchronously in init, and it picks a scene type at compile time. Both branches host the same ContentView, so platform differences stay in the scene declaration and a single #if !os(visionOS) block in the view.

Inference: running ScriptingRuntime.setup in App.init ensures the runtime is configured before any RealityView loads entities that carry script graphs. The source comment explains only why AR input is excluded, not the ordering, so treat the ordering rationale as inference.

Ownership and state

Ownership evidence

RCP-NavigationMesh/ContentView.swift:15 — RealityView make closure

struct ContentView: View {

    var body: some View {
        RealityView { content in
            // The Reality Composer Pro project exports a scene named "world".
            let world: Entity
            do {
                world = try await Entity(named: "world")
            } catch {
                print("Failed to load the scene named \"world\": \(error)")
                return
            }
            content.add(world)
            // ...
        }
        // Sends taps and drags to the script graphs on the scene's entities.
        .enableRealityKitScriptingInput()
    }
}
Owner Object or state Relationship Mutation authority
RCPNavigationMeshApp ContentView Constructs the view value inside ImmersiveSpace or WindowGroup SwiftUI scene system
ContentView (make closure) world: Entity A local constant. Handed to content.add(_:) and not stored by the view RealityKit scene graph and the script graphs inside the asset
ContentView (make closure, non-visionOS) PerspectiveCamera Created locally, positioned from world.visualBounds, and added to the content (RCP-NavigationMesh/ContentView.swift:32) RealityKit scene graph
RCPNavigationMeshApp windowSize A static let constant, compiled only for non-visionOS platforms (RCP-NavigationMesh/RCPNavigationMeshApp.swift:16) Immutable

Observed: ContentView has no stored properties, no @State, and no references to entities after the make closure returns. After content.add, the Swift code never mutates the scene. Inference: the entities’ runtime state belongs to the scripting runtime and RealityKit. The source does not show which framework object retains them.

Concurrency, scheduling, and thread safety

Evidence limit: actor isolation, async/await, or Task creation does not by itself prove background-thread execution; Sendable conformance alone does not prove thread-safe mutation.

Concern Source mechanism Verified placement or handoff Evidence
Main-actor isolation Build setting SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, with SWIFT_APPROACHABLE_CONCURRENCY = YES and Swift 5 language mode Declarations in the app target that carry no annotation default to main-actor isolation. No source file writes @MainActor or MainActor.run explicitly RCP-NavigationMesh.xcodeproj/project.pbxproj:187
Main-queue scheduling None No DispatchQueue appears in the source RCP-NavigationMesh/ContentView.swift:15
Task or worker execution RealityView async make closure The closure suspends at try await Entity(named: "world"). The task that runs the closure is created by RealityView, not by sample code. The source does not establish its executor or any cancellation RCP-NavigationMesh/ContentView.swift:19
Synchronous setup ScriptingRuntime.setup(inputOptions:) in App.init A synchronous, non-throwing call made before the scene body is evaluated RCP-NavigationMesh/RCPNavigationMeshApp.swift:22
Shared-state safety Not applicable The Swift code has no shared mutable state to protect RCP-NavigationMesh/ContentView.swift:12
Per-frame scheduling Not visible in Swift The source declares no RealityKit System and no update(context:) method. Per-frame navigation runs inside the RealityKitScripting runtime and the asset’s behavior tree, and the sample source does not show how they are scheduled RCP-NavigationMesh/RCPNavigationMeshApp.swift:22

Task retention and cancellation: the sample creates no task, so it has nothing to retain or cancel. If loading fails, the closure logs with print and returns before adding anything (RCP-NavigationMesh/ContentView.swift:21).

Reference code

RCP-NavigationMesh.xcodeproj/project.pbxproj:186 — target concurrency settings

SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;

State propagation, frameworks, and dependencies

Evidence limit: an import proves a source-level compilation dependency at the cited line; it does not prove runtime use, architectural adoption, or whether a Swift package is a direct application dependency.

Category Mechanism or module Verified role Evidence
Input propagation .enableRealityKitScriptingInput() view modifier Forwards taps and drags on the RealityView to script graphs on the scene’s entities. The source comment states this purpose RCP-NavigationMesh/ContentView.swift:40
Swift state propagation None No property wrappers, Observation, Combine, or notifications are used RCP-NavigationMesh/ContentView.swift:12
Framework import SwiftUI Imported in both Swift files RCP-NavigationMesh/RCPNavigationMeshApp.swift:8
Framework import RealityKit Imported in ContentView.swift, which uses RealityView, Entity, and PerspectiveCamera RCP-NavigationMesh/ContentView.swift:9
Framework import RealityKitScripting Imported in both files, which use ScriptingRuntime and .enableRealityKitScriptingInput() RCP-NavigationMesh/ContentView.swift:10
Package declaration XCRemoteSwiftPackageReference for realitykitscripting A direct package product dependency of the app target. It is listed in the target’s packageProductDependencies and linked in its Frameworks phase RCP-NavigationMesh.xcodeproj/project.pbxproj:399

Reference code

RCP-NavigationMesh.xcodeproj/project.pbxproj:387 — remote package requirement

repositoryURL = "https://github.com/apple/realitykitscripting";
requirement = {
    kind = upToNextMinorVersion;
    minimumVersion = 1.4.0;
};

Class and protocol design

The sample declares only two types, both value types that conform to SwiftUI protocols. It declares no local protocols, classes, RealityKit Component types, or System types. A class diagram would not add information.

RCP-NavigationMesh/ContentView.swift:12 — ContentView

struct ContentView: View {

    var body: some View {
        RealityView { content in
            // ...
        }
        // Sends taps and drags to the script graphs on the scene's entities.
        .enableRealityKitScriptingInput()
    }
}
Type Responsibility Depends on
RCPNavigationMeshApp Configures the scripting runtime and chooses the scene type for each platform App, ScriptingRuntime, ImmersiveSpace/WindowGroup
ContentView Loads the exported scene, adds a camera when there is no device viewpoint, and enables scripting input View, RealityView, Entity, PerspectiveCamera

Access control

Symbol Access Verified effect Likely rationale
RCPNavigationMeshApp implicit internal Visible across the app module. No public or private modifier appears anywhere in the source Inference: a single app target exports no API
ContentView implicit internal Visible across the app module, so the App body can construct it Inference: same as above
RCPNavigationMeshApp.windowSize implicit internal static let, conditionally compiled Exists only when !os(visionOS). The visionOS build has no such symbol Inference: an immersive space is unbounded, so a window size would be meaningless there

Reference code

RCP-NavigationMesh/RCPNavigationMeshApp.swift:14 — conditional constant

@main
struct RCPNavigationMeshApp: App {

    #if !os(visionOS)
    /// The width and height of the window that holds the scene, in points.
    static let windowSize = 800.0
    #endif
    // ...
}

The sample uses conditional compilation rather than access modifiers to separate platform concerns. It has no private or fileprivate members because neither type holds mutable state.

Logic ownership and placement

Logic Owning type or file Placement rationale
Scripting runtime configuration RCPNavigationMeshApp.init Runs once at app startup, before any scene is built
Scene type per platform RCPNavigationMeshApp.body Compile-time #if os(visionOS) selects ImmersiveSpace or WindowGroup
Asset loading and error handling ContentView make closure Loads asynchronously, logs on failure, and adds the root entity
Framing the camera on non-visionOS platforms ContentView make closure Derives the camera position from world.visualBounds(relativeTo: nil), so the asset’s placement in the Reality Composer Pro project doesn’t matter (RCP-NavigationMesh/ContentView.swift:30)
Navigation mesh, pathfinding, tap-to-move behavior Reality Composer Pro assets (Entities/, Behaviors/, Script Graphs/) Authored as data and executed by the scripting runtime. No Swift code is involved

Design patterns

No named design pattern is supported. The Swift code composes framework types directly. The project does show one structural decision worth studying: gameplay is data-driven and lives outside the code. The Swift layer is a thin host that installs the runtime, loads the asset, and routes input. The navigation component, navigation mesh, and behavior tree are defined in the Reality Composer Pro project. This is described here as a project-structure observation, not as a named pattern (RCP-NavigationMesh/ContentView.swift:40).

Main application flow

The sequence-diagram checks are not all satisfied. The app has one async load, and the order of tap handling inside the scripting runtime is not visible in Swift. The flow is therefore described in prose:

  1. App.init calls ScriptingRuntime.setup (RCP-NavigationMesh/RCPNavigationMeshApp.swift:22).
  2. The scene body builds ContentView inside the scene type chosen for the platform.
  3. The RealityView make closure awaits Entity(named: "world") and adds the result (RCP-NavigationMesh/ContentView.swift:24).
  4. Taps reach the script graphs through .enableRealityKitScriptingInput(). From there, the asset’s TapTerrain and PlayerLogic script graphs and the Player_Behavior tree move the player. That last step is visible only in asset names, not in Swift.

Naming conventions

  • Types: RCPNavigationMeshApp follows the <ProductName>App convention, with the RCP prefix taken from the target name RCP-NavigationMesh. ContentView is the Xcode-template root view name.
  • Asset identifiers: the Swift code refers to the exported scene by the string "world", which must match the root entity name in the Reality Composer Pro project (Entities/world.tm_entity). The coupling is a single literal, not a named constant.
  • Asset folders: Reality Composer Pro groups its assets by role (Entities, Behaviors, Script Graphs). Script graphs are named <Entity>Logic (PlayerLogic, NavigationTargetMarkerLogic) or after an action (TapTerrain).
  • Protocols: none declared.
  • Files: one primary type per file, and each file is named after its type.

Architecture takeaways

  • A thin Swift host can run a full gameplay loop. The navigation mesh, navigation component, and behavior tree are assets, and Swift only installs the runtime and loads the scene.
  • Global framework runtimes are configured in App.init, before any RealityView loads content that depends on them.
  • Platform divergence is pushed to two seams: the scene type in App.body, and a #if !os(visionOS) camera block in the view. The rest of the code is shared.
  • Concurrency is minimal and framework-owned. RealityView creates the only async context, and the target’s default MainActor isolation covers everything else.
  • The coupling between code and asset is a single string, "world". Renaming the root entity in Reality Composer Pro breaks loading at run time, not at compile time.

Source map

Source file Relevant symbols
RCP-NavigationMesh/RCPNavigationMeshApp.swift RCPNavigationMeshApp, init, ScriptingRuntime.setup, ImmersiveSpace, WindowGroup, windowSize
RCP-NavigationMesh/ContentView.swift ContentView, RealityView, Entity(named:), PerspectiveCamera, enableRealityKitScriptingInput()
RCP-NavigationMesh.xcodeproj/project.pbxproj App target, synchronized root group, RealityKitScripting package product, concurrency build settings, supported platforms
Info.plist Immersive-space default scene role, mixed immersion style