home / skills / charleswiltgen / axiom / axiom-spritekit-ref
This skill helps you quickly reference SpriteKit APIs, node types, physics, actions, and textures to accelerate iOS game development.
npx playbooks add skill charleswiltgen/axiom --skill axiom-spritekit-refReview the files below or copy the command above to add this skill to your agents.
---
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
```
## 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
This skill is a compact, battle-tested SpriteKit API reference for modern xOS development. It summarizes node types, physics body creation, the action catalog, texture atlas usage, constraints, scene setup, particles, and SKRenderer-related guidance. Use it as a quick lookup to pick correct APIs and performance patterns when building games or interactive UI with SpriteKit.
The reference organizes SpriteKit by category: node hierarchy and performance notes, physics bodies and joints, action primitives and timing, texture/atlas handling, constraint APIs, scene and SKView configuration, and particle/field nodes. Each section lists common constructors, key properties, and recommended usage patterns so you can find exact signatures and behavior quickly. It highlights performance tradeoffs (draw calls, batching, GPU vs CPU cost) and practical defaults to speed development.
When should I use texture-based physics bodies?
Use texture-based bodies only when collision accuracy matters for a small number of objects; they are significantly more expensive than primitive shapes.
How do I keep draw calls low?
Batch by using SKSpriteNode with the same SKTextureAtlas, enable ignoresSiblingOrder, and avoid nodes that force separate draws like SKShapeNode and SKEffectNode.