--- name: axiom-spritekit-ref description: SpriteKit API reference — all node types, physics body creation, action catalog, texture atlases, constraints, scene setup, particles, SKRenderer license: MIT compatibility: [iOS 13+, macOS 10.15+, tvOS 13+, watchOS 6+] metadata: version: "1.0.0" --- # SpriteKit API Reference Complete API reference for SpriteKit organized by category. ## When to Use This Reference Use this reference when: - Looking up specific SpriteKit API signatures or properties - Checking which node types are available and their performance characteristics - Finding the right physics body creation method - Browsing the complete action catalog - Configuring SKView, scale modes, or transitions - Setting up particle emitter properties - Working with SKRenderer or SKShader ## Part 1: Node Hierarchy ### All Node Types | Node | Purpose | Batches? | Performance Notes | |------|---------|----------|-------------------| | `SKNode` | Container, grouping | N/A | Zero rendering cost | | `SKSpriteNode` | Textured sprites | Yes (same atlas) | Primary gameplay node | | `SKShapeNode` | Vector paths | **No** | 1 draw call each — avoid in gameplay | | `SKLabelNode` | Text rendering | No | 1 draw call each | | `SKEmitterNode` | Particle systems | N/A | GPU-bound, limit birth rate | | `SKCameraNode` | Viewport control | N/A | Attach HUD as children | | `SKEffectNode` | Core Image filters | No | Expensive — cache with `shouldRasterize` | | `SKCropNode` | Masking | No | Mask + content = 2+ draw calls | | `SKTileMapNode` | Tile-based maps | Yes (same tileset) | Efficient for large maps | | `SKVideoNode` | Video playback | No | Uses AVPlayer | | `SK3DNode` | SceneKit content | No | Renders SceneKit scene | | `SKReferenceNode` | Reusable .sks files | N/A | Loads archive at runtime | | `SKLightNode` | Per-pixel lighting | N/A | Limits: 8 lights per scene | | `SKFieldNode` | Physics fields | N/A | Gravity, electric, magnetic, etc. | | `SKAudioNode` | Positional audio | N/A | Uses AVAudioEngine | | `SKTransformNode` | 3D rotation wrapper | N/A | xRotation, yRotation for perspective | ### SKSpriteNode Properties ```swift // Creation SKSpriteNode(imageNamed: "player") // From asset catalog SKSpriteNode(texture: texture) // From SKTexture SKSpriteNode(texture: texture, size: size) // Custom size SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50)) // Solid color // Key properties sprite.anchorPoint = CGPoint(x: 0.5, y: 0) // Bottom-center sprite.colorBlendFactor = 0.5 // Tint strength (0-1) sprite.color = .red // Tint color sprite.normalTexture = normalMap // For lighting sprite.lightingBitMask = 0x1 // Which lights affect this sprite.shadowCastBitMask = 0x1 // Which lights cast shadows sprite.shader = customShader // Per-pixel effects ``` ### SKLabelNode Properties ```swift let label = SKLabelNode(text: "Score: 0") label.fontName = "AvenirNext-Bold" label.fontSize = 24 label.fontColor = .white label.horizontalAlignmentMode = .left label.verticalAlignmentMode = .top label.numberOfLines = 0 // Multi-line (iOS 11+) label.preferredMaxLayoutWidth = 200 label.lineBreakMode = .byWordWrapping ``` --- ## Part 2: Physics API ### SKPhysicsBody Creation ```swift // Volume bodies (have mass, respond to forces) SKPhysicsBody(circleOfRadius: 20) // Cheapest SKPhysicsBody(rectangleOf: CGSize(width: 40, height: 60)) SKPhysicsBody(polygonFrom: path) // Convex only SKPhysicsBody(texture: texture, size: size) // Pixel-perfect (expensive) SKPhysicsBody(texture: texture, alphaThreshold: 0.5, size: size) SKPhysicsBody(bodies: [body1, body2]) // Compound // Edge bodies (massless boundaries) SKPhysicsBody(edgeLoopFrom: rect) // Rectangle boundary SKPhysicsBody(edgeLoopFrom: path) // Path boundary SKPhysicsBody(edgeFrom: pointA, to: pointB) // Single edge SKPhysicsBody(edgeChainFrom: path) // Open path ``` ### Physics Body Properties ```swift // Identity body.categoryBitMask = 0x1 // What this body IS body.collisionBitMask = 0x2 // What it bounces off body.contactTestBitMask = 0x4 // What triggers didBegin/didEnd // Physical characteristics body.mass = 1.0 // kg body.density = 1.0 // kg/m^2 (auto-calculates mass) body.friction = 0.2 // 0.0 (ice) to 1.0 (rubber) body.restitution = 0.3 // 0.0 (no bounce) to 1.0 (perfect bounce) body.linearDamping = 0.1 // Air resistance (0 = none) body.angularDamping = 0.1 // Rotational damping // Behavior body.isDynamic = true // Responds to forces body.affectedByGravity = true // Subject to world gravity body.allowsRotation = true // Can rotate from physics body.pinned = false // Pinned to parent position body.usesPreciseCollisionDetection = false // For fast objects // Motion (read/write) body.velocity = CGVector(dx: 100, dy: 0) body.angularVelocity = 0.0 // Force application body.applyForce(CGVector(dx: 0, dy: 100)) // Continuous body.applyImpulse(CGVector(dx: 0, dy: 50)) // Instant body.applyTorque(0.5) // Continuous rotation body.applyAngularImpulse(1.0) // Instant rotation body.applyForce(CGVector(dx: 10, dy: 0), at: point) // Force at point ``` ### SKPhysicsWorld ```swift scene.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) scene.physicsWorld.speed = 1.0 // 0 = paused, 2 = double speed scene.physicsWorld.contactDelegate = self // Ray casting let body = scene.physicsWorld.body(at: point) let bodyInRect = scene.physicsWorld.body(in: rect) scene.physicsWorld.enumerateBodies(alongRayStart: start, end: end) { body, point, normal, stop in // Process each body the ray intersects } ``` ### Physics Joints ```swift // Pin joint (pivot) let pin = SKPhysicsJointPin.joint( withBodyA: bodyA, bodyB: bodyB, anchor: anchorPoint ) // Fixed joint (rigid connection) let fixed = SKPhysicsJointFixed.joint( withBodyA: bodyA, bodyB: bodyB, anchor: anchorPoint ) // Spring joint let spring = SKPhysicsJointSpring.joint( withBodyA: bodyA, bodyB: bodyB, anchorA: pointA, anchorB: pointB ) spring.frequency = 1.0 // Oscillations per second spring.damping = 0.5 // 0 = no damping // Sliding joint (linear constraint) let slide = SKPhysicsJointSliding.joint( withBodyA: bodyA, bodyB: bodyB, anchor: point, axis: CGVector(dx: 1, dy: 0) ) // Limit joint (distance constraint) let limit = SKPhysicsJointLimit.joint( withBodyA: bodyA, bodyB: bodyB, anchorA: pointA, anchorB: pointB ) // Add joint to world scene.physicsWorld.add(joint) // Remove: scene.physicsWorld.remove(joint) ``` ### Physics Fields ```swift // Gravity (directional) let gravity = SKFieldNode.linearGravityField(withVector: vector_float3(0, -9.8, 0)) // Radial gravity (toward/away from point) let radial = SKFieldNode.radialGravityField() radial.strength = 5.0 // Electric field (charge-dependent) let electric = SKFieldNode.electricField() // Noise field (turbulence) let noise = SKFieldNode.noiseField(withSmoothness: 0.5, animationSpeed: 1.0) // Vortex let vortex = SKFieldNode.vortexField() // Drag let drag = SKFieldNode.dragField() // All fields share: field.region = SKRegion(radius: 100) // Area of effect field.strength = 1.0 // Intensity field.falloff = 0.0 // Distance falloff field.minimumRadius = 10 // Inner dead zone field.isEnabled = true field.categoryBitMask = 0xFFFFFFFF // Which bodies affected ``` --- ## Part 3: Action Catalog ### Movement ```swift SKAction.move(to: point, duration: 1.0) SKAction.move(by: CGVector(dx: 100, dy: 0), duration: 0.5) SKAction.moveTo(x: 200, duration: 1.0) SKAction.moveTo(y: 300, duration: 1.0) SKAction.moveBy(x: 50, y: 0, duration: 0.5) SKAction.follow(path, asOffset: true, orientToPath: true, duration: 2.0) ``` ### Rotation ```swift SKAction.rotate(byAngle: .pi, duration: 1.0) // Relative SKAction.rotate(toAngle: .pi / 2, duration: 0.5) // Absolute SKAction.rotate(toAngle: angle, duration: 0.5, shortestUnitArc: true) ``` ### Scaling ```swift SKAction.scale(to: 2.0, duration: 0.5) SKAction.scale(by: 1.5, duration: 0.3) SKAction.scaleX(to: 2.0, y: 1.0, duration: 0.5) SKAction.resize(toWidth: 100, height: 50, duration: 0.5) ``` ### Fading ```swift SKAction.fadeIn(withDuration: 0.5) SKAction.fadeOut(withDuration: 0.5) SKAction.fadeAlpha(to: 0.5, duration: 0.3) SKAction.fadeAlpha(by: -0.2, duration: 0.3) ``` ### Composition ```swift SKAction.sequence([action1, action2, action3]) // Sequential SKAction.group([action1, action2]) // Parallel SKAction.repeat(action, count: 5) // Finite repeat SKAction.repeatForever(action) // Infinite action.reversed() // Reverse SKAction.wait(forDuration: 1.0) // Delay SKAction.wait(forDuration: 1.0, withRange: 0.5) // Random delay ``` ### Texture & Color ```swift SKAction.setTexture(texture) SKAction.setTexture(texture, resize: true) SKAction.animate(with: [tex1, tex2, tex3], timePerFrame: 0.1) SKAction.animate(with: textures, timePerFrame: 0.1, resize: false, restore: true) SKAction.colorize(with: .red, colorBlendFactor: 1.0, duration: 0.5) SKAction.colorize(withColorBlendFactor: 0, duration: 0.5) ``` ### Sound ```swift SKAction.playSoundFileNamed("explosion.wav", waitForCompletion: false) ``` ### Node Tree ```swift SKAction.removeFromParent() SKAction.run(block) SKAction.run(block, queue: .main) SKAction.customAction(withDuration: 1.0) { node, elapsed in // Custom per-frame logic } ``` ### Physics ```swift SKAction.applyForce(CGVector(dx: 0, dy: 100), duration: 0.5) SKAction.applyImpulse(CGVector(dx: 50, dy: 0), duration: 1.0/60.0) // ~1 frame SKAction.applyTorque(0.5, duration: 1.0) SKAction.changeCharge(to: 1.0, duration: 0.5) SKAction.changeMass(to: 2.0, duration: 0.5) ``` ### Timing Modes ```swift action.timingMode = .linear // Constant speed action.timingMode = .easeIn // Slow → fast action.timingMode = .easeOut // Fast → slow action.timingMode = .easeInEaseOut // Slow → fast → slow action.speed = 2.0 // 2x speed ``` --- ## Part 4: Textures and Atlases ### SKTexture ```swift // From image let tex = SKTexture(imageNamed: "player") // From atlas let atlas = SKTextureAtlas(named: "Characters") let tex = atlas.textureNamed("player_run_1") // Subrectangle (for manual sprite sheets) let sub = SKTexture(rect: CGRect(x: 0, y: 0, width: 0.25, height: 0.5), in: sheetTexture) // From CGImage let tex = SKTexture(cgImage: cgImage) // Filtering tex.filteringMode = .nearest // Pixel art (no smoothing) tex.filteringMode = .linear // Smooth scaling (default) // Preload SKTexture.preload([tex1, tex2]) { /* Ready */ } ``` ### SKTextureAtlas ```swift // Create in Xcode: Assets.xcassets → New Sprite Atlas // Or .atlas folder in project bundle let atlas = SKTextureAtlas(named: "Characters") let textureNames = atlas.textureNames // All texture names in atlas // Preload entire atlas atlas.preload { /* Atlas ready */ } // Preload multiple atlases SKTextureAtlas.preloadTextureAtlases([atlas1, atlas2]) { /* All ready */ } // Animation from atlas let frames = (1...8).map { atlas.textureNamed("run_\($0)") } let animate = SKAction.animate(with: frames, timePerFrame: 0.1) ``` --- ## Part 5: Constraints ```swift // Orient toward another node let orient = SKConstraint.orient(to: targetNode, offset: SKRange(constantValue: 0)) // Orient toward a point let orient = SKConstraint.orient(to: point, offset: SKRange(constantValue: 0)) // Position constraint (keep X in range) let xRange = SKConstraint.positionX(SKRange(lowerLimit: 0, upperLimit: 400)) // Position constraint (keep Y in range) let yRange = SKConstraint.positionY(SKRange(lowerLimit: 50, upperLimit: 750)) // Distance constraint (stay within range of node) let dist = SKConstraint.distance(SKRange(lowerLimit: 50, upperLimit: 200), to: targetNode) // Rotation constraint let rot = SKConstraint.zRotation(SKRange(lowerLimit: -.pi/4, upperLimit: .pi/4)) // Apply constraints (processed in order) node.constraints = [orient, xRange, yRange] // Toggle node.constraints?.first?.isEnabled = false ``` ### SKRange ```swift SKRange(constantValue: 100) // Exactly 100 SKRange(lowerLimit: 50, upperLimit: 200) // 50...200 SKRange(lowerLimit: 0) // >= 0 SKRange(upperLimit: 500) // <= 500 SKRange(value: 100, variance: 20) // 80...120 ``` --- ## Part 6: Scene Setup ### SKView Configuration ```swift let skView = SKView(frame: view.bounds) // Debug overlays skView.showsFPS = true skView.showsNodeCount = true skView.showsDrawCount = true skView.showsPhysics = true skView.showsFields = true skView.showsQuadCount = true // Performance skView.ignoresSiblingOrder = true // Enables batching optimizations skView.shouldCullNonVisibleNodes = true // Auto-hide offscreen (manual is faster) skView.isAsynchronous = true // Default: renders asynchronously skView.allowsTransparency = false // Opaque is faster // Frame rate skView.preferredFramesPerSecond = 60 // Or 120 for ProMotion // Present scene skView.presentScene(scene) skView.presentScene(scene, transition: .fade(withDuration: 0.5)) ``` ### Scale Mode Matrix | Mode | Aspect Ratio | Content | Best For | |------|-------------|---------|----------| | `.aspectFill` | Preserved | Fills view, crops edges | Most games | | `.aspectFit` | Preserved | Fits in view, letterboxes | Exact layout needed | | `.resizeFill` | Distorted | Stretches to fill | Almost never | | `.fill` | Varies | Scene resizes to match view | Adaptive scenes | ### SKTransition Types ```swift SKTransition.fade(withDuration: 0.5) SKTransition.fade(with: .black, duration: 0.5) SKTransition.crossFade(withDuration: 0.5) SKTransition.flipHorizontal(withDuration: 0.5) SKTransition.flipVertical(withDuration: 0.5) SKTransition.reveal(with: .left, duration: 0.5) SKTransition.moveIn(with: .right, duration: 0.5) SKTransition.push(with: .up, duration: 0.5) SKTransition.doorway(withDuration: 0.5) SKTransition.doorsOpenHorizontal(withDuration: 0.5) SKTransition.doorsOpenVertical(withDuration: 0.5) SKTransition.doorsCloseHorizontal(withDuration: 0.5) SKTransition.doorsCloseVertical(withDuration: 0.5) // Custom with CIFilter: SKTransition(ciFilter: filter, duration: 0.5) ``` --- ## Part 7: Particles ### SKEmitterNode Key Properties ```swift let emitter = SKEmitterNode(fileNamed: "Spark")! // Emission control emitter.particleBirthRate = 100 // Particles per second emitter.numParticlesToEmit = 0 // 0 = infinite emitter.particleLifetime = 2.0 // Seconds emitter.particleLifetimeRange = 0.5 // ± random // Position emitter.particlePosition = .zero emitter.particlePositionRange = CGVector(dx: 10, dy: 10) // Movement emitter.emissionAngle = .pi / 2 // Direction (radians) emitter.emissionAngleRange = .pi / 4 // Spread emitter.particleSpeed = 100 // Points per second emitter.particleSpeedRange = 50 // ± random emitter.xAcceleration = 0 emitter.yAcceleration = -100 // Gravity-like // Appearance emitter.particleTexture = SKTexture(imageNamed: "spark") emitter.particleSize = CGSize(width: 8, height: 8) emitter.particleColor = .white emitter.particleColorAlphaSpeed = -0.5 // Fade out emitter.particleBlendMode = .add // Additive for fire/glow emitter.particleAlpha = 1.0 emitter.particleAlphaSpeed = -0.5 // Scale emitter.particleScale = 1.0 emitter.particleScaleRange = 0.5 emitter.particleScaleSpeed = -0.3 // Shrink over time // Rotation emitter.particleRotation = 0 emitter.particleRotationSpeed = 2.0 // Target node (for trails) emitter.targetNode = scene // Particles stay in world space // Render order emitter.particleRenderOrder = .dontCare // .oldestFirst, .oldestLast, .dontCare // Physics field interaction emitter.fieldBitMask = 0x1 ``` ### Common Particle Presets | Effect | Key Settings | |--------|-------------| | Fire | `blendMode: .add`, fast `alphaSpeed`, orange→red color, upward speed | | Smoke | `blendMode: .alpha`, slow speed, gray color, scale up over time | | Sparks | `blendMode: .add`, high speed + range, short lifetime, small size | | Rain | Downward `emissionAngle`, narrow range, long lifetime, thin texture | | Snow | Slow downward speed, wide position range, slight x acceleration | | Trail | Set `targetNode` to scene, narrow emission angle, medium lifetime | | Explosion | High birth rate, short `numParticlesToEmit`, high speed range | --- ## Part 8: SKRenderer and Shaders ### SKRenderer (Metal Integration) ```swift import MetalKit let device = MTLCreateSystemDefaultDevice()! let renderer = SKRenderer(device: device) renderer.scene = gameScene renderer.ignoresSiblingOrder = true // In Metal render loop: func draw(in view: MTKView) { guard let commandBuffer = commandQueue.makeCommandBuffer(), let rpd = view.currentRenderPassDescriptor else { return } renderer.update(atTime: CACurrentMediaTime()) renderer.render( withViewport: CGRect(origin: .zero, size: view.drawableSize), commandBuffer: commandBuffer, renderPassDescriptor: rpd ) commandBuffer.present(view.currentDrawable!) commandBuffer.commit() } ``` ### SKShader (Custom GLSL ES Effects) ```swift // Fragment shader for per-pixel effects let shader = SKShader(source: """ void main() { vec4 color = texture2D(u_texture, v_tex_coord); // Desaturate float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); gl_FragColor = vec4(vec3(gray), color.a) * v_color_mix.a; } """) sprite.shader = shader // With uniforms let shader = SKShader(source: """ void main() { vec4 color = texture2D(u_texture, v_tex_coord); color.rgb *= u_intensity; gl_FragColor = color; } """) shader.uniforms = [ SKUniform(name: "u_intensity", float: 0.8) ] // Built-in uniforms: // u_texture — sprite texture // u_time — elapsed time // u_path_length — shape node path length // v_tex_coord — texture coordinate // v_color_mix — color/alpha mix // SKAttribute for per-node values ``` ## Part 7: SwiftUI Integration ### SpriteView ```swift import SpriteKit import SwiftUI // Basic embedding struct GameView: View { var body: some View { SpriteView(scene: makeScene()) .ignoresSafeArea() } func makeScene() -> SKScene { let scene = GameScene(size: CGSize(width: 1024, height: 768)) scene.scaleMode = .aspectFill return scene } } // With options SpriteView( scene: scene, transition: .fade(withDuration: 0.5), // Scene transition isPaused: false, // Pause control preferredFramesPerSecond: 60, // Frame rate options: [.ignoresSiblingOrder, .shouldCullNonVisibleNodes], debugOptions: [.showsFPS, .showsNodeCount] // Debug overlays ) ``` ### SpriteView Options | Option | Purpose | |--------|---------| | `.ignoresSiblingOrder` | Enable draw order batching optimization | | `.shouldCullNonVisibleNodes` | Auto-hide offscreen nodes | | `.allowsTransparency` | Allow transparent background (slower) | ### Debug Options | Option | Shows | |--------|-------| | `.showsFPS` | Frames per second | | `.showsNodeCount` | Total visible nodes | | `.showsDrawCount` | Draw calls per frame | | `.showsPhysics` | Physics body outlines | | `.showsFields` | Physics field regions | | `.showsQuadCount` | Quad subdivisions | ### Communicating Between SwiftUI and SpriteKit ```swift // Observable model shared between SwiftUI and scene @Observable class GameState { var score = 0 var isPaused = false var lives = 3 } // Scene reads/writes the shared model class GameScene: SKScene { var gameState: GameState? override func update(_ currentTime: TimeInterval) { guard let state = gameState, !state.isPaused else { return } // Game logic updates state.score, state.lives, etc. } } // SwiftUI view owns the model struct GameContainerView: View { @State private var gameState = GameState() @State private var scene: GameScene = { let s = GameScene(size: CGSize(width: 1024, height: 768)) s.scaleMode = .aspectFill return s }() var body: some View { VStack { Text("Score: \(gameState.score)") SpriteView(scene: scene, isPaused: gameState.isPaused) .ignoresSafeArea() } .onAppear { scene.gameState = gameState } } } ``` **Key pattern**: Use `@Observable` model as bridge. Scene mutates it; SwiftUI observes changes. Avoid recreating scenes in view body — use `@State` to persist the scene instance. --- ## Resources **WWDC**: 2014-608, 2016-610, 2017-609 **Docs**: /spritekit/skspritenode, /spritekit/skphysicsbody, /spritekit/skaction, /spritekit/skemitternode, /spritekit/skrenderer **Skills**: axiom-spritekit, axiom-spritekit-diag