Mobile Game Development with SceneKit (iOS)
SceneKit—Apple's native 3D framework on Metal. Appeared iOS 8, grew significantly iOS 12–16. Not Unity or Unreal: no visual editor pipeline, no Asset Store, fewer ready solutions. But not raw Metal either: physics engine, animations, shader modifiers, AR integration via ARKit—all in SDK without third-party dependencies. For iOS-exclusive 3D games moderate complexity—workable option, especially team deep in Swift/Objective-C.
Scene, Nodes, Rendering
Base unit—SCNNode. Scene—node tree: root SCNScene.rootNode, nodes have attached geometry (SCNGeometry), light (SCNLight), camera (SCNCamera), physics body (SCNPhysicsBody). Difference from SpriteKit: SceneKit works in 3D with coordinates SCNVector3, transforms via simd_float4x4 (or convenient properties position, eulerAngles, simdTransform).
Load scene from .scn file (editable in Xcode Scene Editor):
guard let scene = SCNScene(named: "GameLevel.scn") else {
fatalError("Scene not found")
}
let scnView = SCNView(frame: view.bounds)
scnView.scene = scene
scnView.allowsCameraControl = false
scnView.rendersContinuously = true // important for animations
view.addSubview(scnView)
rendersContinuously = true—if not set, SCNView renders only on scene change. For games with constant motion need continuous render. Downside—battery consumption. For menus with rare changes keep false.
Physics and Collisions in 3D
SCNPhysicsBody three types: .static (immobile, collider doesn't move), .dynamic (physics-controlled), .kinematic (code-moved, ignores forces, participates in collisions). Player character usually kinematic—we move via code, walls and floor stop.
Collider shape affects performance more than 2D:
// Expensive: SCNPhysicsShape(geometry: complexMesh) — convex hull for 10k vertices
// OK: bounding box or capsule for character
let capsule = SCNCapsule(capRadius: 0.3, height: 1.8)
let physicsShape = SCNPhysicsShape(geometry: capsule, options: nil)
player.physicsBody = SCNPhysicsBody(type: .kinematic, shape: physicsShape)
Collisions via SCNPhysicsContactDelegate:
func physicsWorld(_ world: SCNPhysicsWorld,
didBegin contact: SCNPhysicsContact) {
let nodeA = contact.nodeA
let nodeB = contact.nodeB
// Handle collision
}
categoryBitMask, collisionBitMask, contactTestBitMask—same bitmask system as SpriteKit. Rule: set contactTestBitMask only for pairs actually needing handling, otherwise didBegin fires too often.
Animations: CAAnimation and SCNAnimationPlayer
SceneKit supports animations from .dae (Collada) and .usdz files. For skeletal character animation—import from .dae:
let idleScene = SCNScene(named: "character_idle.dae")!
// Extract animation from scene
if let animationKey = idleScene.rootNode.animationKeys.first,
let animation = idleScene.rootNode.animationPlayer(forKey: animationKey) {
characterNode.addAnimationPlayer(animation, forKey: "idle")
animation.play()
}
Switch animations (idle → run → jump) via SCNAnimationPlayer with blendInDuration for smooth transition:
func transition(to key: String, blendDuration: CGFloat = 0.3) {
guard let player = characterNode.animationPlayer(forKey: key) else { return }
player.blendInDuration = blendDuration
player.play()
// Stop current
currentAnimationKey.flatMap { characterNode.animationPlayer(forKey: $0) }?.stop(blendOutDuration: blendDuration)
currentAnimationKey = key
}
Without blendInDuration, character "jumps" between poses—first thing player notices.
Shaders and Post Effects
SceneKit allows plugging GLSL/Metal shaders via SCNMaterial.shaderModifiers. Four attachment points: .geometry, .surface, .lightingModel, .fragment. Typical use—dissolve object on death:
let dissolveShader = """
#pragma transparent
#pragma body
float threshold = u_dissolveAmount;
float noise = ... // noise from coordinates
if (noise < threshold) discard_fragment();
_output.color.a = smoothstep(threshold - 0.05, threshold, noise);
"""
material.shaderModifiers = [.fragment: dissolveShader]
SCNTechnique—for full-screen post-processing (bloom, outline, depth of field). Configured via plist dictionary describing passes and render targets. More complex than Unity, but works on Metal without extra libraries.
ARKit Integration
SceneKit—first and main renderer for ARKit:
let arView = ARSCNView(frame: view.bounds)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal, .vertical]
arView.session.run(config)
ARSCNView automatically syncs SCNScene with AR coordinate space. ARSCNViewDelegate allows reacting to plane detection, add anchors. This is foundation for AR games—ARKit + SceneKit market well covered by Apple examples.
Performance: Metal Under Hood
SCNView defaults to Metal on iOS 9+. Several rules with real fps impact:
Batching: SceneKit automatically combines draw calls for nodes with same material. Therefore "1000 trees with one material" much cheaper than "100 trees with 100 different materials". Use SCNMaterial instances, don't create new material object per node.
Level of Detail via SCNLevelOfDetail:
let lods = [
SCNLevelOfDetail(geometry: mediumDetailMesh, worldSpaceDistance: 20),
SCNLevelOfDetail(geometry: lowDetailMesh, worldSpaceDistance: 50)
]
node.geometry?.levelsOfDetail = lods
Occlusion culling: SceneKit does frustum culling automatically, but occlusion culling (hiding objects behind others)—no. For complex scenes need manual via isHidden = true from ray cast or level logic.
Profiling tool: Xcode → Metal System Trace + Render Graph (Instruments). Shows draw call count, GPU usage, tile memory on Apple Silicon. Goal—not more 30–50 draw calls for stable 60 fps on iPhone X.
When SceneKit Not Best Choice
Multi-platform (iOS + Android + PC)—Unity or Godot. Complex deformable body physics—Unity with Havok. Massive open worlds—same Unity/Unreal. SceneKit good for iOS exclusive moderate 3D complexity, AR apps, games where native integration matters (Game Center, CloudKit, Apple Silicon optimizations).
Work Stages
TZ audit: game type, AR needed, target devices, iOS minimum, assets (3D models, animations).
Gameplay prototype: character movement, physics, basic camera—1–2 weeks.
Core gameplay: levels, enemies / obstacles, UI (SwiftUI over SCNView or UIKit overlay).
Audio: AVAudioEngine for 3D sound via AVAudio3DMixing.
Game Center, IAP (if needed), ARKit (if AR component).
Performance polishing, test on weak devices.
Timeline
| Project Type | Timeline |
|---|---|
| Prototype / proof of concept | 2–3 weeks |
| Casual 3D game without AR | 1.5–2 months |
| 3D game with AR + Game Center | 2–3 months |
| Complex project (open world, multiplayer) | Discussed separately |
Cost calculated individually after TZ analysis, ready asset availability, and platform requirements.







