Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Building an immersive experience with RealityKit

At a glance

Item Summary
Purpose Use systems and postprocessing effects to create a realistic underwater scene.
App architecture The UIKit GameViewController owns the AR experience, GameManager coordinates gameplay, and custom components, InputSystem, and MetalRenderer create the underwater simulation and postprocessing path.
Main patterns UIKit lifecycle composition, UI–RealityKit bridge, Session owner boundary, Protocol capability boundary, Delegate callback adapter, Underwater postprocess pipeline
Project style Code-rich sample with 59 scanned C/Objective-C header, Metal, Swift file(s) and 7988 implementation line(s).

Project structure

Source bundle/
└── BugBreakAR/
    ├── GameViewController.swift  # GameViewController
    ├── Rendering/
    │   └── MetalRenderer.swift  # MetalRenderer
    ├── Components/
    │   ├── EntitySwitcherComponent.swift  # EntitySwitcherAnimationType, EntitySwitcherComponent, ChildEntityInfo
    │   ├── AudioComponent.swift  # AudioComponent, HasAudioComponent
    │   └── InteractionComponent.swift  # InteractionComponent, InteractionComponentDelegate, HasInteraction
    ├── AppDelegate.swift  # AppDelegate
    └── GameManager.swift  # GameManager, GameState

Structure observations

  • The runtime boundary is UIApplicationDelegate + UIViewController; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
  • The source is C/Objective-C header, Metal, Swift; role-named types and feature folders separate the major responsibilities.
  • This archive extracts to the same source-tree SHA-256 as Creating a game with scene understanding; this document focuses on underwater systems and postprocessing.

Overall architecture

Reference code

BugBreakAR/AppDelegate.swift:13 — executable or app-lifecycle anchor.

class AppDelegate: UIResponder, UIApplicationDelegate {
    // ...
}

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

Ownership evidence

BugBreakAR/GameManager.swift:20 — representative stored state or nearest verified lifecycle anchor.

    weak var viewController: GameViewController?
    var inputSystemInstance: InputSystem?
    var voxels: Voxels?
    weak var assets: GameAssets?

    // Game state related
    var currentState = GameState.applicationLaunched
    var assetsLoaded = false
Owner Object or state Relationship Mutation authority
GameManager GameViewController as viewController receives a shared or non-owning reference The upstream owner controls lifetime; this scope may invoke the exposed mutable API
GameManager AnchorEntity as creaturesAnchor creates and retains The owning type coordinates writes
GameManager AnchorEntity as audioAnchor stores and coordinates The owning type coordinates writes
GameManager InputSystem as inputSystemInstance stores and coordinates 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
AppDelegate (BugBreakAR/AppDelegate.swift:13) Declares app lifecycle and top-level dependency lifetime. UIResponder, UIApplicationDelegate
GameViewController (BugBreakAR/GameViewController.swift:14) Coordinates long-lived framework or cross-view work. UIViewController, UIGestureRecognizerDelegate
GameManager (BugBreakAR/GameManager.swift:11) Coordinates long-lived framework or cross-view work. Concrete framework collaborators
MetalRenderer (BugBreakAR/Rendering/MetalRenderer.swift:12) Wraps a framework, input, rendering, or session capability. Concrete framework collaborators
HasAudioComponent (BugBreakAR/Components/AudioComponent.swift:16) Declares a replaceable capability or collaboration contract. Concrete framework collaborators
HasEntitySwitcher (BugBreakAR/Components/EntitySwitcherComponent.swift:66) Declares a replaceable capability or collaboration contract. Concrete framework collaborators
EntitySwitcherComponentDelegate (BugBreakAR/Components/EntitySwitcherComponent.swift:68) Declares a replaceable capability or collaboration contract. AnyObject
InteractionComponentDelegate (BugBreakAR/Components/InteractionComponent.swift:25) Declares a replaceable capability or collaboration contract. AnyObject

Verified protocol-oriented seams: DebugSettingBoolDebugSettingProtocol, DebugSettingIntDebugSettingProtocol, DebugSettingFloatDebugSettingProtocol, DebugSettingEnumDebugSettingProtocol, CreatureEntityHasEntitySwitcher, CreatureEntityEntitySwitcherComponentDelegate. Framework conformances remain adapters, not proof of an app-wide protocol architecture.

Access control

Symbol Access Verified effect Likely rationale
private let log = OSLog(subsystem: appSubsystem, category: "EntitySwitc... (BugBreakAR/Components/EntitySwitcherComponent.swift:15) private Use stays inside the declaration and same-file extensions permitted by Swift. Inference: Hide lifecycle-sensitive state or an implementation step.
fileprivate var childEntityInfoList = [ChildEntityInfo]() (BugBreakAR/Components/EntitySwitcherComponent.swift:48) fileprivate Use is limited to declarations in this source file. Inference: Share with same-file helpers without making the symbol module-wide.
public var audioComponent: AudioComponent { (BugBreakAR/Components/AudioComponent.swift:19) 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 func configurePathfinding() { (BugBreakAR/CreatureEntity+Pathfinding.swift:12) 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.

Objective-C/C header and implementation visibility is documented separately from Swift lexical access control.

Reference code

BugBreakAR/Components/EntitySwitcherComponent.swift:15 — representative visibility boundary.

private let log = OSLog(subsystem: appSubsystem, category: "EntitySwitchComponent")

Logic ownership and placement

Logic Owning type or file Placement rationale
Application/command lifecycle AppDelegate The executable boundary determines app, controller, scene, or command lifetime.
Presentation and user intent GameViewController The view/controller forwards input and owns only presentation-local work.
Shared feature state and commands GameManager A stable owner prevents view recomputation or callbacks from duplicating transitions.
Framework/session/render coordination MetalRenderer The role-named collaborator isolates long-lived or per-frame work from presentation code.
GPU and low-level resource work BugBreakAR/Rendering/MetalRenderer.swift:12 Buffer, texture, shader, or frame-resource mutation stays in a rendering/compute boundary.

Design patterns

Pattern Source evidence Purpose or tradeoff
UIKit lifecycle composition BugBreakAR/AppDelegate.swift:13 Uses the application delegate and view controller as the explicit lifecycle and scene-ownership boundary.
UI–RealityKit bridge BugBreakAR/MainMenuViewController.swift:24 Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle.
Session owner boundary BugBreakAR/GameViewController+ARSessionDelegate.swift:12 Keeps authorization, session lifetime, and framework callbacks in a stable model, manager, or controller.
Protocol capability boundary BugBreakAR/DebugSettings/DebugSettings.swift:27 Defines a local capability with concrete conformers; this is the verified protocol-oriented seam.
Delegate callback adapter BugBreakAR/AppDelegate.swift:13 Inverts imperative framework callbacks into the sample’s owning controller, coordinator, or model.
Underwater postprocess pipeline BugBreakAR/Rendering/MetalRenderer.swift:12 Keeps the underwater frame effect in a dedicated renderer while the game manager owns gameplay state.

Naming conventions

  • Role suffixes are evidence, not decoration: Model: MetalModel; Controller: DebugSettingsViewController, GameViewController, MainMenuViewController; Manager: GameManager; Renderer: MetalRenderer; System: InputSystem.
  • ECS names pair data and behavior: components AudioComponent, CustomCameraComponent, EntitySwitcherComponent, InteractionComponent, PathfindingComponent; systems none.
  • Protocols: HasAudioComponent, HasEntitySwitcher, EntitySwitcherComponentDelegate, InteractionComponentDelegate, HasInteraction, PathfindingComponentDelegate.
  • Commands use verb-led methods: viewWillAppear, viewDidAppear, prepare, configureView, resetTracking, setupIBL, gestureRecognizer, nearbyFaceWithClassification.
  • 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 AppDelegate as 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.
  • A shared source tree can support different documentation questions; keep each page focused on its specific architecture question rather than duplicating generic analysis.

Source map

Source file Relevant symbols
BugBreakAR/GameViewController.swift:14 GameViewController
BugBreakAR/Rendering/MetalRenderer.swift:12 MetalRenderer
BugBreakAR/Components/EntitySwitcherComponent.swift:18 EntitySwitcherAnimationType, EntitySwitcherComponent, ChildEntityInfo, HasEntitySwitcher, EntitySwitcherComponentDelegate
BugBreakAR/AppDelegate.swift:13 AppDelegate
BugBreakAR/GameManager.swift:11 GameManager, GameState
BugBreakAR/Components/AudioComponent.swift:12 AudioComponent, HasAudioComponent
BugBreakAR/Components/InteractionComponent.swift:12 InteractionComponent, InteractionComponentDelegate, HasInteraction