| 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
SKSpriteNode(imageNamed: "player")
SKSpriteNode(texture: texture)
SKSpriteNode(texture: texture, size: size)
SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))
sprite.anchorPoint = CGPoint(x: 0.5, y: 0)
sprite.colorBlendFactor = 0.5
sprite.color = .red
sprite.normalTexture = normalMap
sprite.lightingBitMask = 0x1
sprite.shadowCastBitMask = 0x1
sprite.shader = customShader
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
label.preferredMaxLayoutWidth = 200
label.lineBreakMode = .byWordWrapping
Part 2: Physics API
SKPhysicsBody Creation
SKPhysicsBody(circleOfRadius: 20)
SKPhysicsBody(rectangleOf: CGSize(width: 40, height: 60))
SKPhysicsBody(polygonFrom: path)
SKPhysicsBody(texture: texture, size: size)
SKPhysicsBody(texture: texture, alphaThreshold: 0.5, size: size)
SKPhysicsBody(bodies: [body1, body2])
SKPhysicsBody(edgeLoopFrom: rect)
SKPhysicsBody(edgeLoopFrom: path)
SKPhysicsBody(edgeFrom: pointA, to: pointB)
SKPhysicsBody(edgeChainFrom: path)
Physics Body Properties
body.categoryBitMask = 0x1
body.collisionBitMask = 0x2
body.contactTestBitMask = 0x4
body.mass = 1.0
body.density = 1.0
body.friction = 0.2
body.restitution = 0.3
body.linearDamping = 0.1
body.angularDamping = 0.1
body.isDynamic = true
body.affectedByGravity = true
body.allowsRotation = true
body.pinned = false
body.usesPreciseCollisionDetection = false
body.velocity (dx: , dy: )
body.angularVelocity
body.applyForce((dx: , dy: ))
body.applyImpulse((dx: , dy: ))
body.applyTorque()
body.applyAngularImpulse()
body.applyForce((dx: , dy: ), at: point)
SKPhysicsWorld
scene.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
scene.physicsWorld.speed = 1.0
scene.physicsWorld.contactDelegate = self
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
}
Physics Joints
let pin = SKPhysicsJointPin.joint(
withBodyA: bodyA, bodyB: bodyB,
anchor: anchorPoint
)
let fixed = SKPhysicsJointFixed.joint(
withBodyA: bodyA, bodyB: bodyB,
anchor: anchorPoint
)
let spring = SKPhysicsJointSpring.joint(
withBodyA: bodyA, bodyB: bodyB,
anchorA: pointA, anchorB: pointB
)
spring.frequency = 1.0
spring.damping = 0.5
let slide = SKPhysicsJointSliding.joint(
withBodyA: bodyA, bodyB: bodyB,
anchor: point, axis: CGVector(dx: 1, dy: 0)
)
let limit = SKPhysicsJointLimit.joint(
withBodyA: bodyA, bodyB: bodyB,
anchorA: pointA, anchorB: pointB
)
scene.physicsWorld.add(joint)
Physics Fields
let gravity = SKFieldNode.linearGravityField(withVector: vector_float3(0, -9.8, 0))
let radial = SKFieldNode.radialGravityField()
radial.strength = 5.0
let electric = SKFieldNode.electricField()
let noise = SKFieldNode.noiseField(withSmoothness: 0.5, animationSpeed: 1.0)
let vortex = SKFieldNode.vortexField()
let drag = SKFieldNode.dragField()
field.region = SKRegion(radius: 100)
field.strength = 1.0
field.falloff = 0.0
field.minimumRadius = 10
field.isEnabled = true
field.categoryBitMask = 0xFFFFFFFF
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)
SKAction.rotate(toAngle: .pi / 2, duration: 0.5)
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])
SKAction.group([action1, action2])
SKAction.repeat(action, count: 5)
SKAction.repeatForever(action)
action.reversed()
SKAction.wait(forDuration: 1.0)
SKAction.wait(forDuration: 1.0, withRange: 0.5)
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
}
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
action.timingMode = .easeIn
action.timingMode = .easeOut
action.timingMode = .easeInEaseOut
action.speed = 2.0
Part 4: Textures and Atlases
SKTexture
let tex = SKTexture(imageNamed: "player")
let atlas = SKTextureAtlas(named: "Characters")
let tex = atlas.textureNamed("player_run_1")
let sub = SKTexture(rect: CGRect(x: 0, y: 0, width: 0.25, height: 0.5), in: sheetTexture)
let tex = SKTexture(cgImage: cgImage)
tex.filteringMode = .nearest
tex.filteringMode = .linear
SKTexture.preload([tex1, tex2]) { }
SKTextureAtlas
let atlas = SKTextureAtlas(named: "Characters")
let textureNames = atlas.textureNames
atlas.preload { }
SKTextureAtlas.preloadTextureAtlases([atlas1, atlas2]) { }
let frames = (1...8).map { atlas.textureNamed("run_\($0)") }
let animate = SKAction.animate(with: frames, timePerFrame: 0.1)
Part 5: Constraints
let orient = SKConstraint.orient(to: targetNode, offset: SKRange(constantValue: 0))
let orient = SKConstraint.orient(to: point, offset: SKRange(constantValue: 0))
let xRange = SKConstraint.positionX(SKRange(lowerLimit: 0, upperLimit: 400))
let yRange = SKConstraint.positionY(SKRange(lowerLimit: 50, upperLimit: 750))
let dist = SKConstraint.distance(SKRange(lowerLimit: 50, upperLimit: 200), to: targetNode)
let rot = SKConstraint.zRotation(SKRange(lowerLimit: -.pi/4, upperLimit: .pi/4))
node.constraints = [orient, xRange, yRange]
node.constraints?.first?.isEnabled = false
SKRange
SKRange(constantValue: 100)
SKRange(lowerLimit: 50, upperLimit: 200)
SKRange(lowerLimit: 0)
SKRange(upperLimit: 500)
SKRange(value: 100, variance: 20)
Part 6: Scene Setup
SKView Configuration
let skView = SKView(frame: view.bounds)
skView.showsFPS = true
skView.showsNodeCount = true
skView.showsDrawCount = true
skView.showsPhysics = true
skView.showsFields = true
skView.showsQuadCount = true
skView.ignoresSiblingOrder = true
skView.shouldCullNonVisibleNodes = true
skView.isAsynchronous = true
skView.allowsTransparency = false
skView.preferredFramesPerSecond = 60
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)
SKTransition(ciFilter: filter, duration: 0.5)
Part 7: Particles
SKEmitterNode Key Properties
let emitter = SKEmitterNode(fileNamed: "Spark")!
emitter.particleBirthRate = 100
emitter.numParticlesToEmit = 0
emitter.particleLifetime = 2.0
emitter.particleLifetimeRange = 0.5
emitter.particlePosition = .zero
emitter.particlePositionRange = CGVector(dx: 10, dy: 10)
emitter.emissionAngle = .pi / 2
emitter.emissionAngleRange = .pi / 4
emitter.particleSpeed = 100
emitter.particleSpeedRange = 50
emitter.xAcceleration = 0
emitter.yAcceleration = -100
emitter.particleTexture = SKTexture(imageNamed: "spark")
emitter.particleSize (width: , height: )
emitter.particleColor .white
emitter.particleColorAlphaSpeed
emitter.particleBlendMode .add
emitter.particleAlpha
emitter.particleAlphaSpeed
emitter.particleScale
emitter.particleScaleRange
emitter.particleScaleSpeed
emitter.particleRotation
emitter.particleRotationSpeed
emitter.targetNode scene
emitter.particleRenderOrder .dontCare
emitter.fieldBitMask
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
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)
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
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)
]
Part 7: SwiftUI Integration
SpriteView
import SpriteKit
import SwiftUI
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
}
}
SpriteView(
scene: scene,
transition: .fade(withDuration: 0.5),
isPaused: false,
preferredFramesPerSecond: 60,
options: [.ignoresSiblingOrder, .shouldCullNonVisibleNodes],
debugOptions: [.showsFPS, .showsNodeCount]
)
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
class GameState {
var score = 0
var isPaused = false
var lives = 3
}
class GameScene: SKScene {
var gameState: GameState?
override func update(_ currentTime: TimeInterval) {
guard let state = gameState, !state.isPaused else { return }
}
}
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
s
}()
body: {
{
()
(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