I have published a number of games that use SpriteKit for everything important. Since the release of macOS Tahoe, I've had a lot of end user reports saying that sound effects have stopped working in many (but not all) of my titles.
I'm not doing anything unusual here – typical code is:
sndGameOver = [SKAction playSoundFileNamed:@"Audio/GameOver.wav" waitForCompletion:YES];
Then at the appropriate time:
[self runAction:sndGameOver];
Has anyone else encountered this? The code still works fine on previous operating systems, and appears to be fine on iOS too. Has something changed in macOS Tahoe?
I'm at a bit of a loss. There's nothing obviously different between the titles that do work and the titles that don't.
Suggestions welcomed!
Thanks
SpriteKit
RSS for tagDrawing shapes, particles, text, images, and video in two dimensions using SpriteKit.
Posts under SpriteKit tag
14 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I've had no issue calling image files in my .swift files, but they are causing crashes when used in my .SKS files. When I set a sprite texture to an image in the inspector/ editor bar, at runtime when that sprite is being called I get the error: "Cannot get value with size 16. The type encoded as {CGRect={CGPoint=dd}{CGSize=dd}} is expected to be 32 bytes." From my research it has something to do with Apple switching from 32 to 64 bite machines. From chatGPT “SpriteKit under the hood uses NSKeyedUnarchiver to load your .sks file. That unarchiver decodes each archived property by reading a fixed‑size blob of bytes and mapping it into a C struct. In your case it ran into a mismatch”. I am using a 64-bite machine to write my code and 64-bite simulators and physical devices, so there isn't a clear cause of the mismatch. My scenes play fine in Xcode 16's preview window and my code builds, it just crashes at runtime.
When I don’t use image textured assets in the SKS file it works fine. It loads animated labels, and plain color squares. I’ve been able to work around this for static things like a sprite with a background texture by. in a normal non-game swift file, writing code like:
if let scene = SKScene(fileNamed: "GameScene2") {
let bg = SKSpriteNode(imageNamed: "YourBackgroundImage")
bg.position = CGPoint(x: scene.frame.midX, y: scene.frame.midY)
bg.zPosition = -1
scene.addChild(bg)
}
The issue now is I want to make a particle emitter and other non static sprites, but my understanding of their properities isn’t deep enough to create them without the editor. Also when I set SKTexture in a swift file that causes the same runtime crash with the 16/32 error. Could you help me figure out how to fix the bug so I can use the editor again? Otherwise could you help me figure out how to write a workaround like I do for background images? I have a feeling the answer is in writing my own NSKeyedUnarchiver but I don’t know how to make sure it’s called instead of the default one. I've already tried cleaning my code multiple times and deleting and reading sprite nodes. Thank you.
Hi,
Toggling a SwiftUI menu in iOS 26 significantly reduces the framerate of an underlying SKView or ARView.
Below are test cases for SpriteKit and RealityKit. I ran these tests on iOS 26.1 Beta using an iPhone 13 (A15 chip). Results were similar on iOS 26.0.1.
Both scenes consist of circles and balls bouncing on the ground. The restitution of the physics bodies is set for near-perfect elasticity, so they keep bouncing indefinitely.
In both SKView and ARView, the framerate drops significantly whenever the SwiftUI menu is toggled. The menu itself is simple and uses standard SwiftUI animations and styling.
SpriteKit
import SpriteKit
import SwiftUI
class SKRestitutionScene: SKScene {
override func didMove(to view: SKView) {
view.contentMode = .center
size = view.bounds.size
scaleMode = .resizeFill
backgroundColor = .darkGray
anchorPoint = CGPoint(x: 0.5, y: 0.5)
let groundWidth: CGFloat = 300
let ground = SKSpriteNode(color: .gray, size: CGSize(width: groundWidth, height: 10))
ground.physicsBody = SKPhysicsBody(rectangleOf: ground.size)
ground.physicsBody?.isDynamic = false
addChild(ground)
let circleCount = 5
let spacing: CGFloat = 60
let totalWidth = CGFloat(circleCount - 1) * spacing
let startX = -totalWidth / 2
for i in 0..<circleCount {
let circle = SKShapeNode(circleOfRadius: 18)
circle.fillColor = .systemOrange
circle.lineWidth = 0
circle.physicsBody = SKPhysicsBody(circleOfRadius: 18)
circle.physicsBody?.restitution = 1
circle.physicsBody?.linearDamping = 0
let x = startX + CGFloat(i) * spacing
circle.position = CGPoint(x: x, y: 150)
addChild(circle)
}
}
override func willMove(from view: SKView) {
self.removeAllChildren()
}
}
struct SKRestitutionView: View {
var body: some View {
ZStack {
SpriteView(scene: SKRestitutionScene(), preferredFramesPerSecond: 120)
.ignoresSafeArea()
VStack {
Spacer()
Menu {
Button("Edit", systemImage: "pencil") {}
Button("Share", systemImage: "square.and.arrow.up") {}
Button("Delete", systemImage: "trash") {}
} label: {
Text("Menu")
}
.buttonStyle(.glass)
}
.padding()
}
}
}
#Preview {
SKRestitutionView()
}
RealityKit
import RealityKit
import SwiftUI
struct ARViewPhysicsRestitution: UIViewRepresentable {
let arView = ARView()
func makeUIView(context: Context) -> some ARView {
arView.contentMode = .center
arView.cameraMode = .nonAR
arView.automaticallyConfigureSession = false
arView.environment.background = .color(.gray)
// MARK: Root
let anchor = AnchorEntity()
arView.scene.addAnchor(anchor)
// MARK: Camera
let camera = Entity()
camera.components.set(PerspectiveCameraComponent())
camera.position = [0, 1, 4]
camera.look(at: .zero, from: camera.position, relativeTo: nil)
anchor.addChild(camera)
// MARK: Ground
let groundWidth: Float = 3.0
let ground = Entity()
let groundMesh = MeshResource.generateBox(width: groundWidth, height: 0.1, depth: groundWidth)
let groundModel = ModelComponent(mesh: groundMesh, materials: [SimpleMaterial(color: .white, roughness: 1, isMetallic: false)])
ground.components.set(groundModel)
let groundShape = ShapeResource.generateBox(width: groundWidth, height: 0.1, depth: groundWidth)
let groundCollision = CollisionComponent(shapes: [groundShape])
ground.components.set(groundCollision)
let groundPhysicsBody = PhysicsBodyComponent(
material: PhysicsMaterialResource.generate(friction: 0, restitution: 0.97),
mode: .static
)
ground.components.set(groundPhysicsBody)
anchor.addChild(ground)
// MARK: Balls
let ballCount = 5
let spacing: Float = 0.4
let totalWidth = Float(ballCount - 1) * spacing
let startX = -totalWidth / 2
let radius: Float = 0.12
let ballMesh = MeshResource.generateSphere(radius: radius)
let ballMaterial = SimpleMaterial(color: .systemOrange, roughness: 1, isMetallic: false)
let ballShape = ShapeResource.generateSphere(radius: radius)
for i in 0..<ballCount {
let ball = Entity()
let ballModel = ModelComponent(mesh: ballMesh, materials: [ballMaterial])
ball.components.set(ballModel)
let ballCollision = CollisionComponent(shapes: [ballShape])
ball.components.set(ballCollision)
var ballPhysicsBody = PhysicsBodyComponent(
material: PhysicsMaterialResource.generate(friction: 0, restitution: 0.97), /// 0.97 for near perfect elasticity
mode: .dynamic
)
ballPhysicsBody.linearDamping = 0
ballPhysicsBody.angularDamping = 0
ball.components.set(ballPhysicsBody)
let shadow = GroundingShadowComponent(castsShadow: true)
ball.components.set(shadow)
let x = startX + Float(i) * spacing
ball.position = [x, 1, 0]
anchor.addChild(ball)
}
return arView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
struct PhysicsRestitutionView: View {
var body: some View {
ZStack {
ARViewPhysicsRestitution()
.ignoresSafeArea()
.background(.black)
VStack {
Spacer()
Menu {
Button("Edit", systemImage: "pencil") {}
Button("Share", systemImage: "square.and.arrow.up") {}
Button("Delete", systemImage: "trash") {}
} label: {
Text("Menu")
}
.buttonStyle(.glass)
}
.padding()
}
}
}
#Preview {
PhysicsRestitutionView()
}
Hello,
I have noticed a performance drop on SpriteKit-based projects running on iOS 26.0 (23A341).
Below is a SpriteKit scene used to test framerate on different devices:
import SpriteKit
import SwiftUI
class BareboneScene: SKScene {
override func didMove(to view: SKView) {
size = view.bounds.size
anchorPoint = CGPoint(x: 0.5, y: 0.5)
backgroundColor = .darkGray
let roundedSquare = SKShapeNode(rectOf: CGSize(width: 150, height: 75), cornerRadius: 12)
roundedSquare.fillColor = .systemRed
roundedSquare.strokeColor = .black
roundedSquare.lineWidth = 3
addChild(roundedSquare)
let action = SKAction.rotate(byAngle: .pi, duration: 1)
roundedSquare.run(.repeatForever(action))
}
}
struct BareboneSceneView: View {
var body: some View {
SpriteView(
scene: BareboneScene(),
debugOptions: [.showsFPS]
)
.ignoresSafeArea()
}
}
#Preview {
BareboneSceneView()
}
The scene is very simple, yet framerate drops to ~40 fps as shown by the Metal HUD. Tested on:
iPhone 13, iOS 26.0: framerate drops to 40 fps. Sometimes it runs at near 60fps. But if the screen is touched repeatedly, the framerate drops to 40-50 fps again.
iPhone 11 Pro, iOS 26.0: ~40fps.
iPad 9th Gen, iOS 18.6.2: 60fps, no issues.
See screenshots attached. These numbers were observed by me and members of our beloved SpriteKit Discord server.
Thank you for your attention.
Hello!
Bare with me here, as there is a lot to explain!
I am working on implementing a Game Center high score leaderboard into my game. I have looked around for examples of how to properly implement this code, but have come up short on finding much material. Therefore, I have tried implementing it myself based off information I found on apples documentation.
Long story short, I am getting success printed when I update my score, but no scores are actually being posted (or at-least no scores are showing up on the Game Center leaderboard when opened).
Before I show the code, one thing I have questioned is the fact that this game is still in development. In AppStoreConnect, the status of the leaderboard is "Not Live". Does this affect scores being posted?
Onto the code. I have created a GameCenter class which handles getting the leaderboards and posting scores to a specific leaderboard. I will post the code in whole, and will discuss below what is happening.
PLEASE VIEW ATTACHED TEXT TO SEE THE GAMECENTER CLASS!
GameCenter class - https://developer.apple.com/forums/content/attachment/0dd6dca8-8131-44c8-b928-77b3578bd970
In a different GameScene, once the game is over, I request to post a new high score to Game Center with this line of code:
GameCenter.shared.submitScore(id: GameCenterLeaderboards.HighScore.rawValue)
Now onto the logic of my code. For the longest time I struggled to figure out how to submit a score. I figured out that in Xcode 12, they deprecated a lot of functions that previously worked for me. Not is seems that we have to load all leaderboards (or the ones we want). That is the purpose behind the leaderboards private variable in the Game Center class.
On the start up of the app, I call authenticate player. Once this callback is reached, I call loadLeaderboards which will load the leaderboards for each string id in an enum that I have elsewhere. Each of these leaderboards will be created as a Leaderboard object, and saved in the private leaderboard array. This is so I have access to these leaderboards later when I want to submit a score.
Once the game is over, I am calling submitScore with the leaderboard id I want to post to. Right now, I only have a high score, but in the future I may add a parameter to this with the value so it works for other leaderboards as well. Therefore, no value is passed in since I am pulling from local storage which holds the high score.
submitScore will get the leaderboard from the private leaderboard array that has the same id as the one passed in. Once I get the correct leaderboard, I submit a score to that leaderboard. Once the callback is hit, I receive the output "Successfully submitted score to leaderboard". This looks promising, except for the fact that no score is actually posted.
At startup, I am calling updatePlayerHighScore, which is not complete - but for the purpose of my point, retrieves the high score of the player from the leaderboard and is printing it out to the console. It is printing out (0), meaning that no score was posted.
The last thing I have questions about is the context when submitting a score. According to the documentation, this seems to just be metadata that GameCenter does not care about, but rather something the developer can use. Therefore, I think I can cross this off as causing the problem.
I believe I implemented this correctly, but for some reason, nothing is posting to the leaderboard. This was ALOT, but I wanted to make sure I got all my thoughts down.
Any help on why this is NOT posting would be awesome! Thanks so much!
Mark
Hi,
I've just moved my SpriteKit-based game from UIView to SwiftUI + SpriteView and I'm getting this mesage
Adding 'GCControllerView' as a subview of UIHostingController.view is not supported and may result in a broken view hierarchy. Add your view above UIHostingController.view in a common superview or insert it into your SwiftUI content in a UIViewRepresentable instead.
Here's how I'm doing this
struct ContentView: View {
@State var alreadyStarted = false
let initialScene = GKScene(fileNamed: "StartScene")!.rootNode as! SKScene
var body: some View {
ZStack {
SpriteView(scene: initialScene, transition: .crossFade(withDuration: 1), isPaused: false , preferredFramesPerSecond: 60)
.edgesIgnoringSafeArea(.all)
.onAppear {
if !self.alreadyStarted {
self.alreadyStarted.toggle()
initialScene.scaleMode = .aspectFit
}
}
VirtualControllerView()
.onAppear {
let virtualController = BTTSUtilities.shared.makeVirtualController()
BTTSSharedData.shared.virtualGameController = virtualController
BTTSSharedData.shared.virtualGameController?.connect()
}
.onDisappear {
BTTSSharedData.shared.virtualGameController?.disconnect()
}
}
}
}
struct VirtualControllerView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let result = PassthroughView()
return result
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
class PassthroughView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subview in subviews.reversed() {
let convertedPoint = convert(point, to: subview)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return nil
}
}
Can I use them in SK and do the animations work?
Thanks, Patrick
Hello,
I'm getting this error when launching a SpriteKit Swift game in iOS 18+ on an iPhone 11 Pro, whose shell is partly damaged in the back:
CHHapticEngine.mm:1206 -[CHHapticEngine doStartWithCompletionHandler:]_block_invoke: ERROR: Player start failed: The operation couldn’t be completed. (com.apple.CoreHaptics error 1852797029.)
Haptics do not work on this device, due to the damaged shell, so some error — which obviously occurs when calling start(completionHandler:) — is definitely expected; what is not expected is the main thread sometimes blocking for up to 5 seconds — although the method is not called from the main thread... the error itself is always displayed from some other secondary (system) thread. During this time, the main thread does not access the haptics engine at all; on average, it blocks once every four or five launches. In each launch (blocking or not), the 'nope' error is displayed ~5 seconds after trying to start the engine.
After going nuts with all kinds of breakpoints and instrumentation, I'm at a loss as to why the main thread would sometimes block...
Ideas, anyone?
Thank you,
D.
Hi 👋! We have a SpriteKit-based app where we play AVAudio sounds in three different ways:
Effects (incl. UI sounds) with AVAudioPlayer.
Long looping tracks with AVAudioPlayer.
Short animation effects on the timeline of SpriteKit's SKScene files (effectively SKAudioNode nodes).
We've found that when you exit the app or otherwise interrupt audio plays, future audio plays often fail. For example, there's a WebKit-based video trailer inside the app, and if you play it, our looping background music track (2.) will stop playing, and won't resume as you close the trailer (return from WebKit). This is probably due to us not manually restarting the track (so may well be easily fixed). Periodically played AVAudioPlayer audio (1.) are not affected.
However, the more concerning thing is that the audio tracks on SKScene file timelines (3.) will no longer play. My hypothesis is that AVAudioEngine gets interrupted, and needs to be restarted for those AVAudioNode elements to regain functionality. Thing is, we don't deal with AVAudioEngine at all currently in the app, meaning it is never initiated to begin with.
Obviously things return to normal when you remove the app from short-term memory and restart it. However, it seems many of our users aren't doing this, and often report audio failing presumably due to some interruption in the past without the app ever being cleared from memory.
Any idea why timeline-run SKAudioNodes would fail like this? Should the app react to app backgrounding/foregrounding regarding audio?
Any help would be very much appreciated ✌️!
I've just started working on my first SpriteKit game that will eventually run on both tvOS and iOS and am looking at how to build a "button". So far, I've got a custom node that looks like:
class MyButton: SKSpriteNode {
...
#if os(tvOS)
override var canBecomeFocused: Bool {
true
}
override func didUpdateFocus(...) {
...
}
#endif
}
The above let me nicely handle focus changes in tvOS and now I'm looking at reacting to selecting the button.
Searching around, all the articles/questions/posts are from 2015-2016 - which is a LOOOONG time ago. Most of the guidance appears to be to add a tap gesture recognizer in the owning scene and getting the scene to hand it off to the button. That seems pretty brittle and I'd much prefer if the button itself is responsible for its own tap management.
So, I guess my question is whether I should just add a gesture recognizer to my custom button class? Is this inefficient if I end up having 7-8 buttons on the screen and each one has its own gesture recognizer?
Somewhat related, all of the 10-year-old advice is that if we add recognizers to scenes, then they need to be removed from the view controller... however, in the modern day world with SwiftUI, my project doesn't even have a view controller (yet, anyway)... what gesture recognizer lifecycle management do I need in a SpriteKit scene that is presented within a SpriteKitView?
Or, is there a better way? I was kind of hoping that overriding pressesBegan() (or something similar) in my custom button might have been triggered on tvOS (like touchesBegan() lets me manage touches for the iOS variant of my app)
Any pointers or suggestions would be gladly received. Thanks.
So I'm trying to use SpriteKit to make the background of my game. The walls have alpha 1.0, and the safe area alpha 0 and fully transparent. (e.g. a big black square with a smaller transparent square in the middle of it). Yet sprite kit always assume the entire image is either fully opaque or fully transparent. That defies its purpose isn't it? Is there a way to make this work?
Hello, and an early "Merry Christmas" to all,
I'm building a SwiftUI app, and one of my Views is a fullscreen UIViewRepresentable (SpriteView) beneath a SwiftUI interface.
Whenever the user interacts with any SwiftUI element, the UIView registers a hit in touchesBegan(). For example, my UIView has logic for pinching (not implemented via UIGestureRecognizer), so whenever the user holds down a SwiftUI element while touching the UIView, that counts as two touches to the UIView which invokes the pinching logic.
Things I've tried to block SwiftUI from passing the gesture down to the UIView:
Adding opaque elements beneath control elements
Adding gestures to the elements above
Adding gesture masks to the gestures above
Converting eligible elements to Buttons (since those seem immune)
Adding SpriteViews beneath those elements to absorb gestures
So far nothing has worked. As long as the UIView is beneath SwiftUI elements, any interactions with those elements will be registered as a hit.
The obvious solution is to track each SwiftUI element's size and coordinates with respect to the UIView's coordinate space, then use exclusion areas, but this is both a pain and expensive, and I find it hard to believe this is the best fix for such a seemingly basic problem.
I'm probably overlooking something basic, so any suggestions will be greatly appreciated
I am making a SpriteKit game and I am trying to change the cursor image from the default pointer to a png image that I have imported into the project, but it’s not really working. when I run the project I can see the cursor image change for a brief second and then return to the default image. Here is my code:
print(NSCursor.current)
if let image = NSImage(named: customImage) {
print("The image exists")
cursor = NSCursor(image: image, hotSpot: .zero)
cursor.push()
print(cursor)
}
print(NSCursor.current)
The above code is all contained in the didMove(:) function in GameScene. From the print statements I can see that the memory address of the NSCursor.current changes to that of cursor. HOWEVER, in the mouseMoved(:) call back function I print out the mouse location and the current cursor. I can see from these print stamens that the cursor memory address has again changed and no longer matches the custom cursor address… so I am not sure what is going on…
Also, fyi the cursor is a global property within game scene so it should persist. Also, image is not nil. This is verified by the print statements I see
Thanks
I plan to create a simple motion graphics software for macOS that animates text, basic shapes, and handles audio. I'll use SwiftUI for the UI.
What are the commonly used technologies for rendering animated graphics? Core Animation is suitable for UI animations but not for exporting and controlling UI animations.
Basic requirements:
Timeline user interface
Animation of text and basic shapes
Viewer in SwiftUI GUI with transport control (play, pause, scrub, …)
Export to video file
Is Metal or Core Graphics typically used directly? I want to keep it as simple as possible.