Skip to content

Instantly share code, notes, and snippets.

@AchrafKassioui
Last active August 28, 2025 02:44
Show Gist options
  • Save AchrafKassioui/cecc4f84fffa9340f27125e56ee4df35 to your computer and use it in GitHub Desktop.
Save AchrafKassioui/cecc4f84fffa9340f27125e56ee4df35 to your computer and use it in GitHub Desktop.
Code sample for manual camera setup in SpriteKit.
/**
Code sample for manual camera setup in SpriteKit.
Achraf Kassioui
Created 27 August 2025
Updated 28 August 2025
*/
import SpriteKit
import SwiftUI
// MARK: Live Preview
struct ManualCameraView: View {
var body: some View {
SpriteView(
scene: ManualCameraScene()
)
.ignoresSafeArea()
}
}
#Preview {
ManualCameraView()
}
// MARK: Layers
enum SceneLayer: CGFloat, Codable, CaseIterable {
case base = 0
case content = 10_000
case ui = 20_000
var isScalable: Bool {
return [.content].contains(self)
}
}
// MARK: Scene
class ManualCameraScene: SKScene {
var parentNodes: [SceneLayer: SKNode] = [:]
let cameraNode = SKNode()
// MARK: didMove
override func didMove(to view: SKView) {
size = view.bounds.size
scaleMode = .resizeFill
backgroundColor = .darkGray
anchorPoint = CGPoint(x: 0.5, y: 0.5)
createLayers()
createCamera()
createContent(view: view)
animateCamera()
}
// MARK: Layer Nodes
func createLayers() {
for layer in SceneLayer.allCases {
let node = SKNode()
node.name = "\(layer) layer"
addChild(node)
node.zPosition = layer.rawValue
parentNodes[layer] = node
}
}
func parentNode(forLayer layer: SceneLayer) -> SKNode {
guard let node = parentNodes[layer] else {
print("Layer node for \(layer) not found. Returning the scene node instead.")
return self
}
return node
}
// MARK: Camera
func createCamera() {
parentNode(forLayer: .base).addChild(cameraNode)
}
/// A sample animation that controls the camera node.
/// We could use anything to control the camera, including physics.
/// If the camera node is controlled with physics, make sure to reset its scale to 1 in update,
/// then set it back to its scale value after the physics step.
func animateCamera() {
let action = SKAction.sequence([
.group([
.scale(to: 0.25, duration: 1),
.move(to: CGPoint(x: 0, y: 0), duration: 1),
]),
.wait(forDuration: 0.5),
.group([
.scale(to: 1.5, duration: 1),
.move(to: CGPoint(x: 0, y: 0), duration: 1),
]),
.wait(forDuration: 0.5)
])
action.timingMode = .easeInEaseOut
cameraNode.run(SKAction.repeatForever(action))
}
// MARK: Content
func createContent(view: SKView) {
let UILabel = SKLabelNode(text: "UI Layer")
UILabel.fontName = "Menlo-Bold"
UILabel.fontSize = 24
UILabel.position = CGPoint(x: 0 , y: -view.bounds.height/2 + 100)
parentNode(forLayer: .ui).addChild(UILabel)
let contentSquare = SKShapeNode(rectOf: CGSize(width: 200, height: 100), cornerRadius: 4)
contentSquare.fillColor = .systemYellow
contentSquare.lineWidth = 3
contentSquare.strokeColor = .black
parentNode(forLayer: .content).addChild(contentSquare)
let contentLabel = SKLabelNode(text: "Scalable Layer")
contentLabel.verticalAlignmentMode = .center
contentLabel.fontName = "Menlo-Bold"
contentLabel.fontSize = 16
contentLabel.fontColor = .black
contentSquare.addChild(contentLabel)
}
// MARK: Loop
override func update(_ currentTime: TimeInterval) {
/// All nodes that scale and move with camera are reset,
/// so physics and any other processing happen in scene coordinates.
for layer in SceneLayer.allCases where layer.isScalable {
parentNode(forLayer: layer).position = .zero
parentNode(forLayer: layer).setScale(1)
parentNode(forLayer: layer).zRotation = 0
}
/// If the camera is controlled with physics, store its scale, and set its scale to 1 for now.
/// ... run frame logic
}
override func didFinishUpdate() {
/// Restore camera scale if needed.
/// Apply the camera transforms to all scalable layers.
for layer in SceneLayer.allCases where layer.isScalable {
parentNode(forLayer: layer).position = cameraNode.position
parentNode(forLayer: layer).scaleAsPoint = cameraNode.scaleAsPoint
parentNode(forLayer: layer).zRotation = cameraNode.zRotation
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment