Bringing your SceneKit projects to RealityKit
At a glance
| Item | Summary |
|---|---|
| Purpose | Adapt a platformer game for RealityKit’s powerful ECS and modularity. |
| App architecture | PyroPandaRealityKitApp and AppModel coordinate the migrated game while feature packages split controller input, character movement, agents, attacks, and other behavior into RealityKit components and systems. |
| Main patterns | SwiftUI scene composition, UI–RealityKit bridge, Observable state owner, Entity-component-system, Delegate callback adapter, Feature-package ECS migration |
| Project style | Code-rich sample with 79 scanned Swift file(s) and 8518 implementation line(s). |
Project structure
Source bundle/
├── Pyro Panda RealityKit/Pyro Panda RealityKit/PyroPandaRealityKitApp.swift # PyroPandaRealityKitApp
├── Pyro Panda RealityKit/Pyro Panda RealityKit/AppModel.swift # AppModel, ImmersiveSpaceState
├── Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentSystem.swift # AgentSystem
├── Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentComponent.swift # AgentComponent, AgentType, AgentState
├── Pyro Panda RealityKit/Pyro Panda RealityKit/Views/ContentView.swift # ContentView
├── Pyro Panda SceneKit/Swift/Shared/UI/Menu.swift # MenuDelegate, Menu
├── Pyro Panda SceneKit/Swift/iOS/ButtonOverlay.swift # ButtonOverlayDelegate, ButtonOverlay
└── Pyro Panda RealityKit/Packages/PyroPanda/Package.realitycomposerpro/ProjectData/main.json # authored RealityKit content
Structure observations
- The runtime boundary is WindowGroup + ImmersiveSpace + SwiftUI/UIKit host; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
- The source is Swift; role-named types and feature folders separate the major responsibilities.
- Authored
.reality,.rkassets, or Reality Composer Pro content is an implementation boundary; Swift loads or drives it rather than reproducing its entity graph.
Overall architecture
flowchart LR
PyroPandaRealityKitApp_1["PyroPandaRealityKitApp"]
WindowGroup___ImmersiveSpace___SwiftUI_UIKit_host_2["WindowGroup + ImmersiveSpace + SwiftUI/UIKit host"]
ContentView_3["ContentView"]
AppModel_4["AppModel"]
AgentSystem_5["AgentSystem"]
RealityKit_entity_graph_6["RealityKit entity graph"]
RealityKit_ECS_feature_packages_7["RealityKit ECS feature packages"]
PyroPandaRealityKitApp_1 --> WindowGroup___ImmersiveSpace___SwiftUI_UIKit_host_2
WindowGroup___ImmersiveSpace___SwiftUI_UIKit_host_2 --> ContentView_3
ContentView_3 --> AppModel_4
AppModel_4 --> AgentSystem_5
AgentSystem_5 --> RealityKit_entity_graph_6
RealityKit_entity_graph_6 --> RealityKit_ECS_feature_packages_7
Reference code
Pyro Panda RealityKit/Pyro Panda RealityKit/PyroPandaRealityKitApp.swift:13 — executable or app-lifecycle anchor.
struct PyroPandaRealityKitApp: App {
// ...
}The diagram is a responsibility flow, not a claim that every adjacent node directly calls the next. It separates lifecycle, presentation, stable state, framework/session work, and the RealityKit entity graph.
Ownership and state
classDiagram
PyroPandaRealityKitApp *-- AppModel : appModel
AppModel --> Entity : gameRoot
ContentView o-- AppModel : appModel
AppModel --> ImmersiveSpaceState : immersiveSpaceState
Ownership evidence
Pyro Panda RealityKit/Pyro Panda RealityKit/PyroPandaRealityKitApp.swift:15 — representative stored state or nearest verified lifecycle anchor.
@main
struct PyroPandaRealityKitApp: App {
// ...
@State private var appModel = AppModel()
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
PyroPandaRealityKitApp |
AppModel as appModel |
creates and retains | The declaring type controls writes |
AppModel |
Entity as gameRoot |
stores and coordinates | The owning type coordinates writes |
ContentView |
AppModel as appModel |
receives a shared or non-owning reference | The upstream owner controls lifetime; this scope may invoke the exposed mutable API |
AppModel |
ImmersiveSpaceState as immersiveSpaceState |
owns value state | The owning type coordinates writes |
Ownership is intentionally narrow: environment, bindings, observed objects, weak links, and delegate callbacks do not prove lifetime ownership. Initialized stored services and wrapper-managed state do; entities added inside a RealityKit content closure belong to that entity graph.
Class and protocol design
| Type | Responsibility | Depends on or conforms to |
|---|---|---|
PyroPandaRealityKitApp (Pyro Panda RealityKit/Pyro Panda RealityKit/PyroPandaRealityKitApp.swift:13) |
Declares app lifecycle and top-level dependency lifetime. | App |
ContentView (Pyro Panda RealityKit/Pyro Panda RealityKit/Views/ContentView.swift:13) |
Presents UI and forwards gestures or lifecycle events. | View |
AppModel (Pyro Panda RealityKit/Pyro Panda RealityKit/AppModel.swift:15) |
Owns feature state and domain transitions. | Concrete framework collaborators |
AgentSystem (Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentSystem.swift:12) |
Updates entities matching a RealityKit component query. | System |
MenuDelegate (Pyro Panda SceneKit/Swift/Shared/UI/Menu.swift:10) |
Declares a replaceable capability or collaboration contract. | NSObjectProtocol |
ButtonOverlayDelegate (Pyro Panda SceneKit/Swift/iOS/ButtonOverlay.swift:10) |
Declares a replaceable capability or collaboration contract. | NSObjectProtocol |
PadOverlayDelegate (Pyro Panda SceneKit/Swift/iOS/PadOverlay.swift:11) |
Declares a replaceable capability or collaboration contract. | NSObjectProtocol |
AgentComponent (Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentComponent.swift:14) |
Stores RealityKit entity data. | Component |
The reviewed source defines no local substitution protocol. Its App, View, delegate, renderer, ARKit, and RealityKit conformances are framework-facing contracts, so this document does not label the whole app protocol-oriented.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
private static func registerSystem() { (Pyro Panda RealityKit/Packages/PyroPanda/Sources/PyroPanda/RunAwayComponent.swift:35) |
private |
Use stays inside the declaration and same-file extensions permitted by Swift. | Inference: Hide lifecycle-sensitive state or an implementation step. |
fileprivate func realityKitCameraEntity(context: SceneUpdateContext) ->... (Pyro Panda RealityKit/Packages/CharacterMovement/Sources/CharacterMovement/CharacterMovementComponent.swift:119) |
fileprivate |
Use is limited to declarations in this source file. | Inference: Share with same-file helpers without making the symbol module-wide. |
public var state: AgentState? (Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentComponent.swift:33) |
public |
The symbol is available to importing modules, subject to its containing type’s visibility. | Inference: Expose an intentional package or cross-target surface. |
internal var wanderSpeed: Float = 5.0 (Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentComponent.swift:86) |
internal |
With no narrower modifier, Swift keeps the declaration internal to the module. | Inference: Allow app-target collaboration without exporting a library API. |
No reviewed Swift access example uses private(set), open; declarations without a modifier are internal.
Reference code
Pyro Panda RealityKit/Packages/PyroPanda/Sources/PyroPanda/RunAwayComponent.swift:35 — representative visibility boundary.
private static func registerSystem() {
Task {
await RunAwaySystem.registerSystem()
}
}Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application/command lifecycle | PyroPandaRealityKitApp |
The executable boundary determines app, controller, scene, or command lifetime. |
| Presentation and user intent | ContentView |
The view/controller forwards input and owns only presentation-local work. |
| Shared feature state and commands | AppModel |
A stable owner prevents view recomputation or callbacks from duplicating transitions. |
| Framework/session/render coordination | AgentSystem |
The role-named collaborator isolates long-lived or per-frame work from presentation code. |
| Per-entity data and repeated behavior | Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentSystem.swift:12 |
Components carry data; the system queries and updates matching entities. |
| GPU and low-level resource work | Pyro Panda RealityKit/Pyro Panda RealityKit/AppModel.swift:38 |
Buffer, texture, shader, or frame-resource mutation stays in a rendering/compute boundary. |
| Authored scene composition | Pyro Panda RealityKit/Packages/PyroPanda/Package.realitycomposerpro/ProjectData/main.json |
Reality Composer Pro owns hierarchy, materials, animation, or behavior that Swift loads and coordinates. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| SwiftUI scene composition | Pyro Panda RealityKit/Pyro Panda RealityKit/PyroPandaRealityKitApp.swift:13 |
Keeps windows, volumes, and immersive presentation visible at the app boundary. |
| UI–RealityKit bridge | Pyro Panda RealityKit/Pyro Panda RealityKit/Views/PyroPandaView.swift:32 |
Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle. |
| Observable state owner | Pyro Panda RealityKit/Pyro Panda RealityKit/AppModel.swift:15 |
Keeps shared feature state in a stable owner rather than in transient view values. |
| Entity-component-system | Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentSystem.swift:12 |
Stores per-entity data in components and advances repeated behavior in registered systems. |
| Delegate callback adapter | Pyro Panda SceneKit/Swift/iOS/AppDelegate.swift:11 |
Inverts imperative framework callbacks into the sample’s owning controller, coordinator, or model. |
| Feature-package ECS migration | Pyro Panda RealityKit/Pyro Panda RealityKit/PyroPandaRealityKitApp.swift:110 |
Splits migrated game behavior into feature packages and component/system pairs instead of one scene delegate. |
Naming conventions
- Role suffixes are evidence, not decoration: App:
PyroPandaRealityKitApp; Model:AppModel; Controller:GameController,WASDController; System:AgentSystem,CharacterMovementSystem,ControllerInputSystem,FollowSystem,MoveBetweenSystem; Component:AgentComponent,AttackComponent,BaseComponent,CharacterMovementComponent,CharacterStateComponent. - ECS names pair data and behavior: components
AgentComponent,CharacterMovementComponent,CharacterStateComponent,ControllerInputReceiver,RunAwayComponent; systemsAgentSystem,CharacterMovementSystem,ControllerInputSystem,RunAwaySystem,SpinSystem. - Protocols:
MenuDelegate,ButtonOverlayDelegate,PadOverlayDelegate. - Commands use verb-led methods:
reset,didConnectController,didDisconnectController,update,constrainPosition,updateAgentState,fearingUpdate,chasingUpdate. - Files and folders use primary-type or responsibility names such as
Views,Models,Rendering,Components,Systems,Packages, or feature names where those boundaries exist.
Architecture takeaways
- Treat
PyroPandaRealityKitAppas the owner of executable or scene lifecycle, not automatically as the owner of every entity created later. - Keep view/controller input near presentation, but move sessions, reconstruction, capture, rendering, shared game state, or documents into stable owners when their lifetime exceeds one callback or render pass.
- Use components for per-entity data and systems for repeated simulation instead of centralizing every update in a view model or controller.
- Count authored Reality Composer Pro assets as implementation; the Swift shell is not the complete architecture.
Source map
| Source file | Relevant symbols |
|---|---|
Pyro Panda RealityKit/Pyro Panda RealityKit/PyroPandaRealityKitApp.swift:13 |
PyroPandaRealityKitApp |
Pyro Panda RealityKit/Pyro Panda RealityKit/AppModel.swift:15 |
AppModel, ImmersiveSpaceState |
Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentSystem.swift:12 |
AgentSystem |
Pyro Panda RealityKit/Packages/AgentComponent/Sources/AgentComponent/AgentComponent.swift:14 |
AgentComponent, AgentType, AgentState, ConstraintValue, PositionConstraints |
Pyro Panda RealityKit/Pyro Panda RealityKit/Views/ContentView.swift:13 |
ContentView |
Pyro Panda SceneKit/Swift/Shared/UI/Menu.swift:10 |
MenuDelegate, Menu |
Pyro Panda SceneKit/Swift/iOS/ButtonOverlay.swift:10 |
ButtonOverlayDelegate, ButtonOverlay |