$npx -y skills add dpearson2699/swift-ios-skills --skill spritekitBuild 2D games and animations using SpriteKit. Use when creating game scenes with SKScene and SKView, adding sprites with SKSpriteNode, animating with SKAction sequences, simulating physics with SKPhysicsBody and contact detection, creating particle effects with SKEmitterNode, bu
| 1 | # SpriteKit |
| 2 | |
| 3 | Build 2D games and interactive animations for iOS 26+ using SpriteKit and |
| 4 | Swift 6.3. Covers scene lifecycle, node hierarchy, actions, physics, particles, |
| 5 | camera, touch handling, and SwiftUI integration. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | - [Scene Setup](#scene-setup) |
| 10 | - [Nodes and Sprites](#nodes-and-sprites) |
| 11 | - [Actions and Animation](#actions-and-animation) |
| 12 | - [Physics](#physics) |
| 13 | - [Touch Handling](#touch-handling) |
| 14 | - [Camera](#camera) |
| 15 | - [Particle Effects](#particle-effects) |
| 16 | - [SwiftUI Integration](#swiftui-integration) |
| 17 | - [Common Mistakes](#common-mistakes) |
| 18 | - [Review Checklist](#review-checklist) |
| 19 | - [References](#references) |
| 20 | |
| 21 | ## Scene Setup |
| 22 | |
| 23 | SpriteKit renders content through `SKView`, which presents an `SKScene` -- the |
| 24 | root node of a tree that the framework animates and renders each frame. |
| 25 | |
| 26 | ### Creating a Scene |
| 27 | |
| 28 | Subclass `SKScene` and override lifecycle methods. The coordinate system |
| 29 | origin is at the bottom-left by default. |
| 30 | |
| 31 | ```swift |
| 32 | import SpriteKit |
| 33 | |
| 34 | final class GameScene: SKScene { |
| 35 | override func didMove(to view: SKView) { |
| 36 | backgroundColor = .darkGray |
| 37 | physicsWorld.contactDelegate = self |
| 38 | physicsBody = SKPhysicsBody(edgeLoopFrom: frame) |
| 39 | setupNodes() |
| 40 | } |
| 41 | |
| 42 | override func update(_ currentTime: TimeInterval) { |
| 43 | // Called once per frame before actions are evaluated. |
| 44 | } |
| 45 | } |
| 46 | ``` |
| 47 | |
| 48 | ### Presenting a Scene (UIKit) |
| 49 | |
| 50 | ```swift |
| 51 | guard let skView = view as? SKView else { return } |
| 52 | skView.ignoresSiblingOrder = true |
| 53 | |
| 54 | let scene = GameScene(size: skView.bounds.size) |
| 55 | scene.scaleMode = .resizeFill |
| 56 | skView.presentScene(scene) |
| 57 | ``` |
| 58 | |
| 59 | ### Scale Modes |
| 60 | |
| 61 | Use `.resizeFill` when the scene should adapt to view size changes (rotation, |
| 62 | multitasking). Use `.aspectFill` for fixed-design game scenes. `.aspectFit` |
| 63 | letterboxes; `.fill` stretches and may distort. |
| 64 | |
| 65 | ### Frame Cycle |
| 66 | |
| 67 | Each frame follows this order: |
| 68 | |
| 69 | 1. `update(_:)` -- game logic |
| 70 | 2. Evaluate actions |
| 71 | 3. `didEvaluateActions()` -- post-action logic |
| 72 | 4. Simulate physics |
| 73 | 5. `didSimulatePhysics()` -- post-physics adjustments |
| 74 | 6. Apply constraints |
| 75 | 7. `didApplyConstraints()` |
| 76 | 8. `didFinishUpdate()` -- final adjustments before rendering |
| 77 | |
| 78 | Override only the callbacks where work is needed. |
| 79 | |
| 80 | ## Nodes and Sprites |
| 81 | |
| 82 | Use `SKNode` (without a visual) as an invisible container or layout group. |
| 83 | Child nodes inherit parent position, scale, rotation, alpha, and speed. |
| 84 | `SKSpriteNode` is the primary visual node. |
| 85 | |
| 86 | ### Common Node Types |
| 87 | |
| 88 | | Class | Purpose | |
| 89 | |-------|---------| |
| 90 | | `SKSpriteNode` | Textured image or solid color | |
| 91 | | `SKLabelNode` | Text rendering | |
| 92 | | `SKShapeNode` | Vector paths (expensive per draw call) | |
| 93 | | `SKEmitterNode` | Particle effects | |
| 94 | | `SKCameraNode` | Viewport control | |
| 95 | | `SKTileMapNode` | Grid-based tiles | |
| 96 | | `SKAudioNode` | Positional audio | |
| 97 | | `SKCropNode` / `SKEffectNode` | Masking / CIFilter | |
| 98 | | `SK3DNode` | Embedded SceneKit content | |
| 99 | |
| 100 | ### Creating Sprites |
| 101 | |
| 102 | ```swift |
| 103 | let player = SKSpriteNode(imageNamed: "hero") |
| 104 | player.position = CGPoint(x: frame.midX, y: frame.midY) |
| 105 | player.name = "player" |
| 106 | addChild(player) |
| 107 | ``` |
| 108 | |
| 109 | ### Drawing Order |
| 110 | |
| 111 | Set `ignoresSiblingOrder = true` on `SKView` for better performance; SpriteKit |
| 112 | then uses `zPosition` to determine order. Without it, nodes draw in tree order. |
| 113 | |
| 114 | ```swift |
| 115 | background.zPosition = -1 |
| 116 | player.zPosition = 0 |
| 117 | foregroundUI.zPosition = 10 |
| 118 | ``` |
| 119 | |
| 120 | ### Naming and Searching |
| 121 | |
| 122 | Assign `name` to find nodes without instance variables. Use `childNode(withName:)`, |
| 123 | `enumerateChildNodes(withName:using:)`, or `subscript`. Patterns: `//` searches |
| 124 | the entire tree, `*` matches any characters, `..` refers to the parent. |
| 125 | |
| 126 | ```swift |
| 127 | player.name = "player" |
| 128 | if let found = childNode(withName: "player") as? SKSpriteNode { /* ... */ } |
| 129 | ``` |
| 130 | |
| 131 | ## Actions and Animation |
| 132 | |
| 133 | `SKAction` objects define changes applied to nodes over time. Actions are |
| 134 | immutable and reusable. Run with `node.run(_:)`. |
| 135 | |
| 136 | ### Basic Actions |
| 137 | |
| 138 | ```swift |
| 139 | let moveUp = SKAction.moveBy(x: 0, y: 100, duration: 0.5) |
| 140 | let grow = SKAction.scale(to: 1.5, duration: 0.3) |
| 141 | let spin = SKAction.rotate(byAngle: .pi * 2, duration: 1.0) |
| 142 | let fadeOut = SKAction.fadeOut(withDuration: 0.3) |
| 143 | let remove = SKAction.removeFromParent() |
| 144 | ``` |
| 145 | |
| 146 | ### Combining Actions |
| 147 | |
| 148 | ```swift |
| 149 | // Sequential: run one after another |
| 150 | let dropAndRemove = SKAction.sequence([ |
| 151 | SKAction.moveBy(x: 0, y: -500, duration: 1.0), |
| 152 | SKAction.removeFromParent() |
| 153 | ]) |
| 154 | |
| 155 | // Parallel: run simultaneously |
| 156 | let scaleAndFade = SKAction.group([ |
| 157 | SKAction.scale(to: 0.0, duration: 0.3), |
| 158 | SKAction.fadeOut(withDuration: 0.3) |
| 159 | ]) |
| 160 | |
| 161 | // Repeat |
| 162 | let pulse = SKAction.repeatForever( |
| 163 | SKAction.sequence([ |
| 164 | SKAction.scale(to: 1.2, duration: 0.5), |