Creating a Spaceship game
At a glance
| Item | Summary |
|---|---|
| Purpose | Build an immersive game using RealityKit audio, simulation, and rendering features. |
| App architecture | SpaceshipApp owns shared AppModel; view models coordinate hangar and immersive phases while RealityKit components and systems handle ships, asteroids, hand control, lighting, and spatial-audio simulation. |
| Main patterns | SwiftUI scene composition, UI–RealityKit bridge, Observable state owner, Entity-component-system, Session owner boundary, Game-state plus ECS split |
| Project style | Code-rich sample with 57 scanned C/Objective-C header, Objective-C++, Swift file(s) and 4639 implementation line(s). |
Project structure
Source bundle/
├── Spaceship/SpaceshipApp.swift # SpaceshipApp
├── Spaceship/AppModel.swift # AppModel, GamePhase
├── Spaceship/ECS/SpaceshipControl/HandsShipControlProvider.swift # HandTrackingComponent, Location, ThrottleLabelPlacementComponent
├── Spaceship/ViewModels/HangarViewModel.swift # HangarViewModel
├── Spaceship/Views/ImmersiveView.swift # ImmersiveView
├── Spaceship/ECS/AudioMaterial.swift # AudioMaterialComponent, AudioMaterialLookupComponent, AudioMaterial
├── Spaceship/ECS/Closure.swift # ClosureComponent, ClosureSystem
└── Packages/RealityKitContent/Package.realitycomposerpro/ProjectData/main.json # authored RealityKit content
Structure observations
- The runtime boundary is WindowGroup + volumetric window + ImmersiveSpace; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
- The source is C/Objective-C header, Objective-C++, 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
SpaceshipApp_1["SpaceshipApp"]
WindowGroup___volumetric_window___ImmersiveSpace_2["WindowGroup + volumetric window + ImmersiveSpace"]
ImmersiveView_3["ImmersiveView"]
AppModel_4["AppModel"]
HandsShipControlProviderSystem_5["HandsShipControlProviderSystem"]
RealityKit_entity_graph_6["RealityKit entity graph"]
RealityKit_components_and_systems_7["RealityKit components and systems"]
SpaceshipApp_1 --> WindowGroup___volumetric_window___ImmersiveSpace_2
WindowGroup___volumetric_window___ImmersiveSpace_2 --> ImmersiveView_3
ImmersiveView_3 --> AppModel_4
AppModel_4 --> HandsShipControlProviderSystem_5
HandsShipControlProviderSystem_5 --> RealityKit_entity_graph_6
RealityKit_entity_graph_6 --> RealityKit_components_and_systems_7
Reference code
Spaceship/SpaceshipApp.swift:12 — executable or app-lifecycle anchor.
struct SpaceshipApp: 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
SpaceshipApp *-- AppModel : appModel
ImmersiveView *-- ImmersiveViewModel : viewModel
HandsShipControlProviderSystem *-- SpatialTrackingSession : session
ImmersiveView o-- AppModel : appModel
Ownership evidence
Spaceship/SpaceshipApp.swift:14 — representative stored state or nearest verified lifecycle anchor.
@main
struct SpaceshipApp: App {
// ...
@State var appModel = AppModel()
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
SpaceshipApp |
AppModel as appModel |
creates and retains | The owning type coordinates writes |
ImmersiveView |
ImmersiveViewModel as viewModel |
creates and retains | The declaring type controls writes |
HandsShipControlProviderSystem |
SpatialTrackingSession as session |
creates and retains | The owning type coordinates writes |
ImmersiveView |
AppModel as appModel |
receives a shared or non-owning reference | The upstream owner controls lifetime; this scope may invoke the exposed mutable API |
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 |
|---|---|---|
SpaceshipApp (Spaceship/SpaceshipApp.swift:12) |
Declares app lifecycle and top-level dependency lifetime. | App |
ImmersiveView (Spaceship/Views/ImmersiveView.swift:12) |
Presents UI and forwards gestures or lifecycle events. | View |
AppModel (Spaceship/AppModel.swift:12) |
Owns feature state and domain transitions. | Concrete framework collaborators |
HandsShipControlProviderSystem (Spaceship/ECS/SpaceshipControl/HandsShipControlProvider.swift:46) |
Updates entities matching a RealityKit component query. | System |
AudioMaterialComponent (Spaceship/ECS/AudioMaterial.swift:10) |
Stores RealityKit entity data. | Component |
AudioMaterialLookupComponent (Spaceship/ECS/AudioMaterial.swift:14) |
Stores RealityKit entity data. | Component |
ClosureComponent (Spaceship/ECS/Closure.swift:10) |
Stores RealityKit entity data. | Component |
ClosureSystem (Spaceship/ECS/Closure.swift:20) |
Updates entities matching a RealityKit component query. | System |
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 func addMeshAnchor(_ meshAnchor: MeshAnchor, shape: ShapeResour... (Spaceship/Utility/SceneReconstruction.swift:52) |
private |
Use stays inside the declaration and same-file extensions permitted by Swift. | Inference: Hide lifecycle-sensitive state or an implementation step. |
public let realityKitContentBundle = Bundle.module (Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.swift:11) |
public |
The symbol is available to importing modules, subject to its containing type’s visibility. | Inference: Expose an intentional package or cross-target surface. |
@property (nonatomic, assign) float throttle; (Spaceship/AudioUnitTurbine/AudioUnitTurbine.h:26) |
header-visible |
Translation units importing the header can use the declaration. | Inference: Publish the C/Objective-C or Metal contract needed across files. |
No reviewed Swift access example uses fileprivate, private(set), open; declarations without a modifier are internal.
Objective-C/C header and implementation visibility is documented separately from Swift lexical access control.
Reference code
Spaceship/Utility/SceneReconstruction.swift:52 — representative visibility boundary.
private func addMeshAnchor(_ meshAnchor: MeshAnchor, shape: ShapeResource) {
// ...
}Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application/command lifecycle | SpaceshipApp |
The executable boundary determines app, controller, scene, or command lifetime. |
| Presentation and user intent | ImmersiveView |
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 | HandsShipControlProviderSystem |
The role-named collaborator isolates long-lived or per-frame work from presentation code. |
| Per-entity data and repeated behavior | Spaceship/ECS/Closure.swift:20 |
Components carry data; the system queries and updates matching entities. |
| Authored scene composition | Packages/RealityKitContent/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 | Spaceship/SpaceshipApp.swift:12 |
Keeps windows, volumes, and immersive presentation visible at the app boundary. |
| UI–RealityKit bridge | Spaceship/Views/HangarView.swift:27 |
Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle. |
| Observable state owner | Spaceship/AppModel.swift:12 |
Keeps shared feature state in a stable owner rather than in transient view values. |
| Entity-component-system | Spaceship/ECS/Closure.swift:20 |
Stores per-entity data in components and advances repeated behavior in registered systems. |
| Session owner boundary | Spaceship/ViewModels/ImmersiveViewModel.swift:16 |
Keeps authorization, session lifetime, and framework callbacks in a stable model, manager, or controller. |
| Game-state plus ECS split | Spaceship/ECS/SpaceshipControl/HandsShipControlProvider.swift:46 |
Keeps application and game phases in models and repeated ship, hand, asteroid, lighting, and audio behavior in components and systems. |
Naming conventions
- Role suffixes are evidence, not decoration: App:
SpaceshipApp; ViewModel:HangarViewModel,ImmersiveViewModel; Model:AppModel; System:ClosureSystem,EnvironmentLightingFadeSystem,HandsShipControlProviderSystem,PlanetVisualsSystem,ShipAudioSystem; Component:AsteroidComponent,AudioMaterialComponent,AudioMaterialLookupComponent,CargoComponent,ClosureComponent. - ECS names pair data and behavior: components
AudioMaterialComponent,AudioMaterialLookupComponent,ClosureComponent,PlanetVisualsComponent,AsteroidComponent; systemsClosureSystem,PlanetVisualsSystem,EnvironmentLightingFadeSystem,ShipAudioSystem,ShipFlightSystem. - Protocols: no app-defined protocol in the reviewed source.
- Commands use verb-led methods:
updateImmersion,computeTransformWith,update,updateShipControlParameters,interpolateThrottle,computeTargetThrottle,computePitchAndRoll,interpolate. - 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
SpaceshipAppas 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.
- Tie asynchronous session/output consumers to an explicit owner so cancellation and teardown follow the same lifecycle.
- Count authored Reality Composer Pro assets as implementation; the Swift shell is not the complete architecture.
Source map
| Source file | Relevant symbols |
|---|---|
Spaceship/SpaceshipApp.swift:12 |
SpaceshipApp |
Spaceship/AppModel.swift:12 |
AppModel, GamePhase |
Spaceship/ECS/SpaceshipControl/HandsShipControlProvider.swift:12 |
HandTrackingComponent, Location, ThrottleLabelPlacementComponent, PitchRollLabelPlacementComponent, HandsShipControlProviderSystem |
Spaceship/ViewModels/HangarViewModel.swift:14 |
HangarViewModel |
Spaceship/Views/ImmersiveView.swift:12 |
ImmersiveView |
Spaceship/ECS/AudioMaterial.swift:10 |
AudioMaterialComponent, AudioMaterialLookupComponent, AudioMaterial |
Spaceship/ECS/Closure.swift:10 |
ClosureComponent, ClosureSystem |