Overview
In my SKScene
, I’ve a SKAudioNode
enjoying an audio file, declared as follows:
var backgroundMusic: SKAudioNode?
and initialized like this:
guard let musicURL = Bundle.primary.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 aim is to have the audio enjoying and carry out stuff in my code when the audio reaches a selected 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 identical time, in order that in idea, 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 soar a small portion of audio, within the instance case like a single quantity could be skipped circa. - After every pause&resume, the timer will hearth a bit later. That is evident within the pattern venture I present down beneath, the place if you happen to pause&resume round 10 instances, the timer will hearth 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 fastened subject 1. and reduces the lag of subject 2. by lots, 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 hearth 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 said by a number of solutions right here [sources: 1. 2 ] 2) I’ve tried it and it introduces lag into my sport – by expertise, in my view, when utilizingSpriteKit
it is higher to not strive hybrid options.
I do not know why this lag happens, so if anybody can shine a lightweight about it I might be grateful.
Furthermore, I am in search of 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 selected working time of the audio (which isn’t on completion).
Pattern Venture
It is a minimalistic pattern venture that highlights the problems I’ve identified. No .sks
recordsdata are wanted, simply copy&paste and construct&run.
The venture begins the audio file and the timer mechanically as quickly as GameScene
is introduced. From there, you possibly can pause/resume the scene by tapping anyplace on the purple display to breed the problems. With my settings and the audio I offered, the timer ought to hearth when the depend reaches quantity 45.
Everytime you pause/resume the scene, and when the timer fires, the app will print on console.
Observe 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 counsel to obtain and use this audio file (Dropbox hyperlink) of a man counting numbers, because it makes the problems evident.
last 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
last class GameScene: SKScene {
var backgroundMusic: SKAudioNode?
var isGamePaused: Bool = false {
didSet {
self.isPaused = isGamePaused
// Uncomment the next traces 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 hearth 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 hearth 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 instances, the timer will get unsynchronized with the audio and can hearth many seconds later than when it's imagined to.
guard let musicURL = Bundle.primary.url(forResource: "Test_Song", withExtension: "mp3") else {
fatalError("Error: add a mp3 audio file to the venture")
}
backgroundMusic = SKAudioNode(url: musicURL)
backgroundMusic?.isPositional = false
let wait = SKAction.wait(forDuration: 48) // With this worth for period, if you happen to're utilizing the audio file I offered, the alarm ought to ring on the finish of '45' within the track
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<UITouch>, with occasion: UIEvent?) {
isGamePaused = !isGamePaused
let statusString = isGamePaused ? "paused" : "resumed"
print("nScene (statusString)n")
}
override func touchesMoved(_ touches: Set<UITouch>, with occasion: UIEvent?) { }
override func touchesEnded(_ touches: Set<UITouch>, with occasion: UIEvent?) { }
override func touchesCancelled(_ touches: Set<UITouch>, with occasion: UIEvent?) { }
}