Back to skills
extension
Category: Development & EngineeringNo API key required

axiom-spritekit-ref

SpriteKit API reference — all node types, physics body creation, action catalog, texture atlases, constraints, scene setup, particles, SKRenderer

personAuthor: jakexiaohubgithub

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

// 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

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

// 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

// 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

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

// 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

// 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

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

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

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

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

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

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

SKAction.playSoundFileNamed("explosion.wav", waitForCompletion: false)

Node Tree

SKAction.removeFromParent()
SKAction.run(block)
SKAction.run(block, queue: .main)
SKAction.customAction(withDuration: 1.0) { node, elapsed in
    // Custom per-frame logic
}

Physics

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

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

// 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

// 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

// 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

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

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

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

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)

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)

// 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

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

// 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