
scenekit
Maintain and extend existing SceneKit 3D scenes and visualizations. Use when working with SCNView, SCNScene, SCNNode scene graphs, SceneKit geometry/materials/lights/cameras, SCNAction animation, SCNPhysicsBody physics, SCNParticleSystem effects, .scn/.dae/.abc SceneKit assets, shader modifiers, or SwiftUI SceneView. SceneKit is soft-deprecated and in maintenance mode; route new apps, significant updates, USD/USDZ pipelines, and migration planning toward RealityKit.
- Installs
- 3.1K
- Last 24h
- +18
- Trending rank
- #6299
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill scenekit- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads scenekit automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
SceneKit
Maintain existing SceneKit scenes only. Apple deprecated SceneKit at WWDC 2025 and limits it to maintenance; route new projects, major modernization, and USD/USDZ pipelines to RealityKit. Existing apps continue to work.
Contents
- Scene Setup
- Nodes and Geometry
- Materials
- Lighting
- Cameras
- Animation
- Physics
- Particle Systems
- Loading Models
- SwiftUI Integration
- Common Mistakes
- Review Checklist
- References
Scene Setup
SCNView in UIKit
import SceneKit
let sceneView = SCNView(frame: view.bounds)
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = .black
view.addSubview(sceneView)
allowsCameraControl adds built-in orbit, pan, and zoom gestures. Typically
disabled in production where custom camera control is needed.
Creating an SCNScene
let scene = SCNScene() // Empty
guard let scene = SCNScene(named: "art.scnassets/ship.scn") // .scn in .scnassets
else { fatalError("Missing scene asset") }
let url = Bundle.main.url(forResource: "ship", withExtension: "dae")!
let scene = try SCNScene(url: url, options: [.checkConsistency: true])
Nodes and Geometry
Every scene has a rootNode. All content exists as descendant nodes. Nodes
define position, orientation, and scale in their parent's coordinate system.
SceneKit uses a right-handed coordinate system: +X right, +Y up, +Z toward
the camera.
let parentNode = SCNNode()
scene.rootNode.addChildNode(parentNode)
let childNode = SCNNode()
childNode.position = SCNVector3(0, 1, 0) // 1 unit above parent
parentNode.addChildNode(childNode)
Transforms
node.position = SCNVector3(x: 0, y: 2, z: -5)
node.eulerAngles = SCNVector3(x: 0, y: .pi / 4, z: 0) // 45-degree Y rotation
node.scale = SCNVector3(2, 2, 2)
node.simdPosition = SIMD3<Float>(0, 2, -5) // Prefer simd for performance
Built-in Primitives
SCNBox, SCNSphere, SCNCylinder, SCNCone, SCNTorus, SCNCapsule,
SCNTube, SCNPlane, SCNFloor, SCNText, SCNShape (extruded Bezier path).
let node = SCNNode(geometry: SCNSphere(radius: 0.5))
Finding Nodes
let maxNode = scene.rootNode.childNode(withName: "Max", recursively: true)
let enemies = scene.rootNode.childNodes { node, _ in
node.name?.hasPrefix("enemy") == true
}
Materials
SCNMaterial defines surface appearance. Use firstMaterial for single-material
geometries or the materials array for multi-material.
Color and Texture
let material = SCNMaterial()
material.diffuse.contents = UIColor.systemBlue // Solid color
material.diffuse.contents = UIImage(named: "brick") // Texture
material.normal.contents = UIImage(named: "brick_normal")
sphere.firstMaterial = material
Physically Based Rendering (PBR)
let pbr = SCNMaterial()
pbr.lightingModel = .physicallyBased
pbr.diffuse.contents = UIImage(named: "albedo")
pbr.metalness.contents = 0.8 // Scalar or texture
pbr.roughness.contents = 0.2 // Scalar or texture
pbr.normal.contents = UIImage(named: "normal")
pbr.ambientOcclusion.contents = UIImage(named: "ao")
Lighting Models
.physicallyBased (metalness/roughness), .blinn (default), .phong,
.lambert (diffuse-only), .constant (unlit), .shadowOnly.
Each material property is an SCNMaterialProperty accepting UIColor,
UIImage, CGFloat scalar, SKTexture, CALayer, or AVPlayer.
Transparency
material.transparency = 0.5
material.transparencyMode = .dualLayer
material.isDoubleSided = true
Lighting
Attach an SCNLight to a node. The light's direction follows the node's
negative Z-axis.
Light Types
// Ambient: uniform, no direction
let ambient = SCNLight()
ambient.type = .ambient
ambient.color = UIColor(white: 0.3, alpha: 1)
// Directional: parallel rays (sunlight)
let directional = SCNLight()
directional.type = .directional
directional.castsShadow = true
// Omni: point light, all directions
let omni = SCNLight()
omni.type = .omni
omni.attenuationEndDistance = 20
// Spot: cone-shaped
let spot = SCNLight()
spot.type = .spot
spot.spotInnerAngle = 20
spot.spotOuterAngle = 60
Attach to a node:
let lightNode = SCNNode()
lightNode.light = directional
lightNode.eulerAngles = SCNVector3(-Float.pi / 3, 0, 0)
lightNode.position = SCNVector3(0, 10, 10)
scene.rootNode.addChildNode(lightNode)
Shadows
light.castsShadow = true
light.shadowMapSize = CGSize(width: 2048, height: 2048)
light.shadowSampleCount = 8
light.shadowRadius = 3.0
light.shadowColor = UIColor(white: 0, alpha: 0.5)
Category Bit Masks
light.categoryBitMask = 1 << 1 // Category 2
node.categoryBitMask = 1 << 1 // Only lit by category-2 lights
SceneKit renders a maximum of 8 lights per node. Use attenuationEndDistance
on point/spot lights so SceneKit skips them for distant nodes.
Cameras
Attach an SCNCamera to a node to define a viewpoint.
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
cameraNode.look(at: SCNVector3Zero)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
Configuration
camera.fieldOfView = 60 // Degrees
camera.zNear = 0.1
camera.zFar = 500
camera.automaticallyAdjustsZRange = true
// Orthographic
camera.usesOrthographicProjection = true
camera.orthographicScale = 10
Depth-of-field (wantsDepthOfField, focusDistance, fStop) and HDR effects
(wantsHDR, bloomIntensity, bloomThreshold, screenSpaceAmbientOcclusionIntensity)
are configured directly on SCNCamera.
Animation
SceneKit provides three animation approaches.
SCNAction (Declarative, Game-Oriented)
Reusable, composable animation objects attached to nodes.
let move = SCNAction.move(by: SCNVector3(0, 2, 0), duration: 1)
let rotate = SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 1)
node.runAction(.group([move, rotate]))
// Sequential
node.runAction(.sequence([.fadeOut(duration: 0.3), .removeFromParentNode()]))
// Infinite loop
let pulse = SCNAction.sequence([
.scale(to: 1.2, duration: 0.5),
.scale(to: 1.0, duration: 0.5)
])
node.runAction(.repeatForever(pulse))
SCNTransaction (Implicit Animation)
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.0
node.position = SCNVector3(5, 0, 0)
node.opacity = 0.5
SCNTransaction.completionBlock = { print("Done") }
SCNTransaction.commit()
Explicit Animations (Core Animation)
let animation = CABasicAnimation(keyPath: "rotation")
animation.toValue = NSValue(scnVector4: SCNVector4(0, 1, 0, Float.pi * 2))
animation.duration = 2
animation.repeatCount = .infinity
node.addAnimation(animation, forKey: "spin")
Physics
Physics Bodies
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) // Forces + collisions
floor.physicsBody = SCNPhysicsBody(type: .static, shape: nil) // Immovable
platform.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) // Code-driven
When shape is nil, SceneKit derives it from geometry. For performance, use
simplified shapes:
let shape = SCNPhysicsShape(
geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
options: nil
)
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
node.physicsBody?.mass = 2.0
node.physicsBody?.restitution = 0.3
Applying Forces
node.physicsBody?.applyForce(SCNVector3(0, 10, 0), asImpulse: false) // Continuous
node.physicsBody?.applyForce(SCNVector3(0, 5, 0), asImpulse: true) // Instant
node.physicsBody?.applyTorque(SCNVector4(0, 1, 0, 2), asImpulse: true)
Collision Detection
struct PhysicsCategory {
static let player: Int = 1 << 0
static let enemy: Int = 1 << 1
static let ground: Int = 1 << 2
}
playerNode.physicsBody?.categoryBitMask = PhysicsCategory.player
playerNode.physicsBody?.collisionBitMask = PhysicsCategory.ground | PhysicsCategory.enemy
playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.enemy
scene.physicsWorld.contactDelegate = self
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
handleCollision(between: contact.nodeA, and: contact.nodeB)
}
Gravity
scene.physicsWorld.gravity = SCNVector3(0, -9.8, 0)
node.physicsBody?.isAffectedByGravity = false
Particle Systems
SCNParticleSystem creates effects like fire, smoke, rain, and sparks.
let particles = SCNParticleSystem()
particles.birthRate = 100
particles.particleLifeSpan = 2
particles.particleSize = 0.1
particles.particleColor = .orange
particles.emitterShape = SCNSphere(radius: 0.5)
particles.particleVelocity = 2
particles.isAffectedByGravity = true
particles.blendMode = .additive
let emitterNode = SCNNode()
emitterNode.addParticleSystem(particles)
scene.rootNode.addChildNode(emitterNode)
Load from Xcode particle editor with
SCNParticleSystem(named: "fire.scnp", inDirectory: nil). Particles can
collide with geometry via colliderNodes.
Loading Models
SceneKit's documented scene-source formats are .scn, .dae, and .abc.
For bundled assets, place scene files in a .scnassets folder and texture
images in asset catalogs so Xcode can optimize them for target devices.
USD/USDZ is the RealityKit migration path, not the default SceneKit loading path. For new projects, significant updates, or SCN-to-USD asset conversion, handoff to the RealityKit skill.
enum SceneAssetError: Error { case missingResource, missingNode(String) }
func loadCheckedScene() throws -> SCNScene {
guard let url = Bundle.main.url(forResource: "model", withExtension: "dae")
else { throw SceneAssetError.missingResource }
let scene = try SCNScene(url: url, options: [.checkConsistency: true])
guard scene.rootNode.childNode(withName: "mesh", recursively: true) != nil
else { throw SceneAssetError.missingNode("mesh") }
return scene
}
Use this as an authoring/import gate: stop on a consistency or required-node
failure, fix the source asset or import options, then repeat the same check.
For generated .scn files, load
Scene Serialization and
require both export success and a checked reload before commit.
Use SCNReferenceNode with .onDemand loading policy for large models. For
import-time unit conversion, use SCNSceneSource.LoadingOption:
let source = SCNSceneSource(url: url, options: nil)!
let scene = try source.scene(options: [.convertUnitsToMeters: 1.0])
Do not use SCNScene.Attribute.unit or UnitMetersPerUnit. SCNScene.Attribute
is metadata only: .startTime, .endTime, .frameRate, and .upAxis.
SwiftUI Integration
SceneView embeds SceneKit in SwiftUI:
import SwiftUI
import SceneKit
struct SceneKitView: View {
let scene: SCNScene = {
let scene = SCNScene()
let sphere = SCNNode(geometry: SCNSphere(radius: 1))
sphere.geometry?.firstMaterial?.lightingModel = .physicallyBased
sphere.geometry?.firstMaterial?.diffuse.contents = UIColor.systemBlue
sphere.geometry?.firstMaterial?.metalness.contents = 0.8
scene.rootNode.addChildNode(sphere)
return scene
}()
var body: some View {
SceneView(scene: scene,
options: [.allowsCameraControl, .autoenablesDefaultLighting])
}
}
Options: .allowsCameraControl, .autoenablesDefaultLighting,
.jitteringEnabled, .temporalAntialiasingEnabled.
For render loop control, wrap SCNView in UIViewRepresentable with an
SCNSceneRendererDelegate coordinator. See references/scenekit-patterns.md.
Common Mistakes
Not adding a camera or lights
// DON'T: Scene renders blank or black -- no camera, no lights
sceneView.scene = scene
// DO: Add camera + lights, or use convenience flags
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
sceneView.autoenablesDefaultLighting = true
Using exact geometry for physics shapes
// DON'T
node.physicsBody = SCNPhysicsBody(type: .dynamic,
shape: SCNPhysicsShape(geometry: complexMesh, options: nil))
// DO: Simplified primitive
node.physicsBody = SCNPhysicsBody(type: .dynamic,
shape: SCNPhysicsShape(
geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
options: nil))
Modifying transforms on dynamic bodies
// DON'T: Resets physics simulation
dynamicNode.position = SCNVector3(5, 0, 0)
// DO: Use forces/impulses
dynamicNode.physicsBody?.applyForce(SCNVector3(10, 0, 0), asImpulse: true)
Review Checklist
- Scene has at least one camera node set as
pointOfView - Scene has appropriate lighting (or
autoenablesDefaultLightingfor prototyping) - Physics shapes use simplified geometry, not full mesh detail
-
contactTestBitMaskset for bodies that need collision callbacks -
SCNPhysicsContactDelegateassigned toscene.physicsWorld.contactDelegate - Dynamic body transforms changed via forces/impulses, not direct position
- Lights limited to 8 per node;
attenuationEndDistanceset on point/spot lights - Materials use
.physicallyBasedlighting model for realistic rendering - SceneKit assets use documented
.scn,.dae, or.abcscene-source formats - Imported and exported assets pass consistency and required-node checks before commit
- Bundled SceneKit textures/images use asset catalogs or Xcode-optimized resources
- Scene metadata/import options use documented API; no invented
SCNScene.Attribute.unit - New USD/USDZ pipelines or significant updates are routed to RealityKit
- Game Center authentication, leaderboards, achievements, or multiplayer are handed off to GameKit
-
SCNReferenceNodeused for large models to enable lazy loading - Particle
birthRateandparticleLifeSpanbalanced to control particle count -
categoryBitMaskused to scope lights and cameras to relevant nodes - SwiftUI scenes use
SceneVieworUIViewRepresentable-wrappedSCNView - Deprecation acknowledged; RealityKit evaluated for new projects
References
- See references/scenekit-patterns.md for custom geometry, shader modifiers, constraints, morph targets, hit testing, scene serialization, render loop delegates, performance, SpriteKit overlay, LOD, and Metal shaders.
- SceneKit documentation, SCNSceneSource, SCNView, SceneView
- SCNPhysicsShape, SCNShadable
- WWDC 2025 session 288: Bring your SceneKit project to RealityKit
FAQ
What does the scenekit skill do?
Maintain and extend existing SceneKit 3D scenes and visualizations. Use when working with SCNView, SCNScene, SCNNode scene graphs, SceneKit geometry/materials/lights/cameras, SCNAction animation, SCNPhysicsBody physics, SCNParticleSystem effects, .scn/.dae/.abc SceneKit assets, shader modifiers, or SwiftUI SceneView. SceneKit is soft-deprecated and in maintenance mode; route new apps, significant updates, USD/USDZ pipelines, and migration planning toward RealityKit.
How do I install scenekit?
Run npx skills add https://github.com/dpearson2699/swift-ios-skills --skill scenekit in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/scenekit/ (personal) or .claude/skills/scenekit/ (project).
Which AI agents can use scenekit?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is scenekit safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains scenekit?
It is published by dpearson2699 in dpearson2699/swift-ios-skills. It has 3,086 recorded installs.
More from dpearson2699/swift-ios-skills
swiftui-animation
dpearson2699/swift-ios-skills
Implement, diagnose, or review SwiftUI motion using explicit and scoped implicit animations, springs, transitions, PhaseAnimator, KeyframeAnimator, matched geometry or navigation zoom, SF Symbol effects, and custom Animation types. Use when views should animate on state changes, insertion, removal, navigation, or multi-step choreography, or when motion must respect Reduce Motion and Swift concurrency.
ios-accessibility
dpearson2699/swift-ios-skills
Build and audit SwiftUI, UIKit, and AppKit accessibility for VoiceOver, Voice Control, Switch Control, Full Keyboard Access, Dynamic Type, focus restoration, labels/traits/actions, traversal, custom rotors, NSAccessibility, XCTest checks, adaptive system preferences, and App Store accessibility declarations. Use when implementing accessible UI, fixing an accessibility audit, testing assistive-technology behavior, or substantiating App Store Accessibility Nutrition Labels.
swiftui-performance
dpearson2699/swift-ios-skills
Profile, diagnose, and remediate SwiftUI runtime performance using code review, Instruments, and repeatable measurements. Use when a SwiftUI screen renders slowly, scrolling or animations hitch, view bodies update excessively, list identity churns, layout work spikes, or broad Observation dependencies raise CPU cost. Covers evidence-based triage, SwiftUI Instruments lanes, lazy-container guardrails, state lifetime, and before/after verification.
swiftui-patterns
dpearson2699/swift-ios-skills
Builds and reviews SwiftUI views with modern MV architecture, state, composition, isolated previews, and migration guidance. Covers @Observable ownership, @State/@Bindable/@Environment wiring, view decomposition, ViewModifiers, environment values, .task loading, iOS 26+ handoffs, Writing Tools, clipboard availability, and performance. Use when structuring SwiftUI state, managing @Observable, composing views, previewing meaningful UI states, or correcting SwiftUI patterns.
app-store-review
dpearson2699/swift-ios-skills
Audits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.
ios-networking
dpearson2699/swift-ios-skills
Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps.
Popular mobile skills
design-mobile-apps
designed-by-ai/skills
Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.
ios-design
uizze.sh
Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.
vercel-react-native-skills
vercel-labs/agent-skills
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
animate-expo
emilkowalski/skills
Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
firebase-remote-config-basics
firebase/agent-skills
Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.