Overview
In my SKScene
, I’ve a SKAudioNode
taking part in an audio file, declared as follows:
var backgroundMusic: SKAudioNode?
and initialized like this:
guard let musicURL = Bundle.principal.url(forResource: "MY_AUDIO_FILENAME", withExtension: "mp3") else { fatalError() }
backgroundMusic = SKAudioNode(url: musicURL)
backgroundMusic?.isPositional = false
For simplicity’s sake, take into consideration the audio performed as a voice counting loud from 1 to 1000:
“one, break, two, break, three, break, — [cut]”
Now, my objective is to have the audio taking part in and carry out stuff in my code when the audio reaches a particular half,, e.g. when the voice counts the quantity 45.
To attain that, I arrange a timer utilizing SKAction
as follows:
let wait = SKAction.wait(forDuration: 48)
let playAlarmSound = SKAction.run {
// carry out stuff
}
let timer = SKAction.sequence([wait, playAlarmSound])
I add them each to the scene on the similar time, in order that in principle, when the audio reaches that particular level, the timer fires.
run(timer, withKey: "Timer")
addChild(backgroundMusic!)
Points
- Each time the
SKScene
is paused after which resumed, theSKAudioNode
will leap a small portion of audio, within the instance case like a single quantity could be skipped circa. - After every pause&resume, the timer will fireplace a bit later. That is evident within the pattern mission I present down under, the place should you pause&resume round 10 occasions, the timer will fireplace round quantity 50 as a substitute of 45.
Tried options
- Pausing the
SKAudioNode
individually each time theSKScene
is paused, by sayingbackgroundMusic?.run(SKAction.pause())
.
Though this mounted problem 1. and reduces the lag of problem 2. by quite a bit, it’s nonetheless not an optimum resolution because the timer will nonetheless not be synchronized with the audio.
This time tho, it is going to fireplace simply round a second sooner than when it’s imagined to, as a substitute of a number of seconds later. - Utilizing
Timer
. That is NOT a viable resolution, for 2 causes: 1) it ignores a node’s, scene’s or the view’spaused
state, as acknowledged by a number of solutions right here [sources: 1. 2 ] 2) I’ve tried it and it introduces lag into my sport – by expertise, for my part, when utilizingSpriteKit
it is higher to not strive hybrid options.
I don’t know why this lag happens, so if anybody can shine a lightweight about it I might be grateful.
Furthermore, I am searching for a constant and optimum resolution for the problems described. I would like to have the ability to carry out stuff within the code precisely at a particular working time of the audio (which isn’t on completion).
Pattern Challenge
This can be a minimalistic pattern mission that highlights the problems I’ve identified. No .sks
information are wanted, simply copy&paste and construct&run.
The mission begins the audio file and the timer routinely as quickly as GameScene
is introduced. From there, you’ll be able to pause/resume the scene by tapping wherever on the purple display screen to breed the problems. With my settings and the audio I offered, the timer ought to fireplace when the rely reaches quantity 45.
Everytime you pause/resume the scene, and when the timer fires, the app will print on console.
Word that I used this resolution from one other query to maintain the paused
state constant when the app goes to background.
For this pattern, I recommend to obtain and use this audio file (Dropbox hyperlink) of a man counting numbers, because it makes the problems evident.
remaining class GameViewController: UIViewController {
override func viewDidLoad() {
tremendous.viewDidLoad()
let skView = SKView(body: view.body)
view = skView
skView.ignoresSiblingOrder = true
let gameScene = GameScene()
skView.presentScene(gameScene)
}
}
import SpriteKit
remaining class GameScene: SKScene {
var backgroundMusic: SKAudioNode?
var isGamePaused: Bool = false {
didSet {
self.isPaused = isGamePaused
// Uncomment the next strains to check my resolution, which isn't optimum. After a number of pause/resume, the timer will nonetheless not be synchronized with the audio and can fireplace simply round a second sooner than when it's imagined to.
//if isGamePaused {
// self.backgroundMusic?.run(SKAction.pause())
//} else {
// self.backgroundMusic?.run(SKAction.play())
//}
}
}
override var isPaused: Bool {
didSet {
if (self.isPaused == false && self.isGamePaused == true) {
self.isPaused = true
// Uncomment the next line to check my resolution, which isn't optimum. After a number of pause/resume, the timer will nonetheless not be synchronized with the audio and can fireplace simply round a second sooner than when it's imagined to.
//self.backgroundMusic?.run(SKAction.pause())
}
}
}
override func didMove(to view: SKView) {
tremendous.didMove(to: view)
view.isMultipleTouchEnabled = false
backgroundColor = .purple
startAudioAndTimer()
}
func startAudioAndTimer() {
// ISSUE: On pause&resume, particularly after a number of occasions, the timer will get unsynchronized with the audio and can fireplace many seconds later than when it's imagined to.
guard let musicURL = Bundle.principal.url(forResource: "Test_Song", withExtension: "mp3") else {
fatalError("Error: add a mp3 audio file to the mission")
}
backgroundMusic = SKAudioNode(url: musicURL)
backgroundMusic?.isPositional = false
let wait = SKAction.wait(forDuration: 48) // With this worth for length, should you're utilizing the audio file I offered, the alarm ought to ring on the finish of '45' within the tune
let playAlarmSound = SKAction.run {
print("n--- The alarm rings ---n")
}
let timer = SKAction.sequence([wait, playAlarmSound])
run(timer, withKey: "Timer")
addChild(backgroundMusic!)
}
override func touchesBegan(_ touches: Set, with occasion: UIEvent?) {
isGamePaused = !isGamePaused
let statusString = isGamePaused ? "paused" : "resumed"
print("nScene (statusString)n")
}
override func touchesMoved(_ touches: Set, with occasion: UIEvent?) { }
override func touchesEnded(_ touches: Set, with occasion: UIEvent?) { }
override func touchesCancelled(_ touches: Set, with occasion: UIEvent?) { }
}