1

Making a simple spritekit game. There I have sounds like button touched sound and etc. did by AVFoundation sound library. But have trouble with this. My frame rate always drops when hit any button, which sounds. But if I mute all my sound, there is no lag. Here is my code:

import AVFoundation

class GameAudio {
    static let shared = GameAudio()

    private var buttonClickSound = AVAudioPlayer()
    private var completionSound = AVAudioPlayer()
    private var swishSound = AVAudioPlayer()
    private var gridSwishSound = AVAudioPlayer()
    private let Volume: Float = 0.8

    func setupSounds() {
        print("GameAudio setupSounds()")
        if let path = Bundle.main.path(forResource: SoundNames.ButtonClickSoundName, ofType: "mp3") {
            let url = URL(fileURLWithPath: path )
            do {
                buttonClickSound = try AVAudioPlayer(contentsOf: url)
            } catch {
                print(error)
            }
        }
        if let path = Bundle.main.path(forResource: SoundNames.CompletionSoundName, ofType: "mp3") {
            let url = URL(fileURLWithPath: path)
            do {
                completionSound = try AVAudioPlayer(contentsOf: url)
            } catch {
                print(error)
            }
        }
        if let path = Bundle.main.path(forResource: SoundNames.SwishSoundName, ofType: "mp3") {
            let url = URL(fileURLWithPath: path )
            do {
                swishSound = try AVAudioPlayer(contentsOf: url)
            } catch {
                print(error)
            }
        }
        if let path = Bundle.main.path(forResource: SoundNames.GridSwishSoundName, ofType: "mp3") {
            let url = URL(fileURLWithPath: path)
            do {
                gridSwishSound = try AVAudioPlayer(contentsOf: url)
            } catch {
                print(error)
            }
        }
        buttonClickSound.volume = Volume
        completionSound.volume = Volume
        swishSound.volume = Volume
        gridSwishSound.volume = Volume
    }

    func playButtonClickSound() {
        buttonClickSound.play()
    }

    func playCompletionSound() {
        completionSound.play()
    }

    func playSwishSound() {
        swishSound.play()
    }

    func playGridSwishSound() {
        gridSwishSound.play()
    }
}

In my GameViewController I call GameAudio.shared.setupSounds() for preload all my sounds.

public struct Actions {
    static func buttonIsTouched(_ button: SKNode, sound: Bool, completion: @escaping () -> ()) {
        if button.hasActions() { return }
        if sound {
            GameAudio.shared.playButtonClickSound()
        }
        button.run(touchedButtonAction(scaleFactor: button.returnScaleFactor()), completion: completion)
    }
}

class MenuNode: SKNode {
    private let settings: Settings

    var delegate: MenuNodeDelegate?

    private let playButton = SKSpriteNode(imageNamed: SpriteNames.PlayButtonName)

    private let MenuNodeTreshold: CGFloat = 12.0

    init(settings: Settings) {
        self.settings = settings
        super.init()

        setupButtons()

        isUserInteractionEnabled = false
    }

    private func setupButtons() {
        playButton.position = CGPoint(x: 0.0 - playButton.frame.size.width / 2.0 - MenuNodeTreshold / 2.0, y: 0.0)
        addChild(playButton)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in touches {
            let location = touch.location(in: self)

            if playButton.contains(location) {
                Actions.buttonIsTouched(playButton as SKNode, sound: settings.sound, completion: {
                    self.delegate?.playButtonTouched()
                })
            }
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

I use Actions struct for all my actions in my game. There I have buttonTouchedAction(), I use it in different nodes. I put some code from my menu with one "play" button. So, for example, I run my game on device, it shows up menu node, wait just few seconds and tap the button, after what my frame rate drops, I have a lag in a second and then scene changes to game scene.

Nurassyl Nuridin
  • 444
  • 4
  • 13
  • Can you post the code which executes when your button is "hit"? – Stoyan Mar 27 '17 at 20:12
  • Done it Stoyan. Edited with some additional code. – Nurassyl Nuridin Mar 28 '17 at 03:36
  • Is there a bad code implementation in `GameAudio`, in things like using four `AVAudioPlayer()` for my sounds, instead of one? Or this is ok? – Nurassyl Nuridin Mar 28 '17 at 03:44
  • Your GameAudio is fine. Use as many players as you like - it only affects memory usage. I think your FPS drop is due to switching the scene, not coming from playing the actual sound. In order to verify, just remove the code for switching the scene and observe the FPS while the sound plays. It's normal to expect FPS drop while switching scenes, I haven't found a way to work around it. – Stoyan Mar 28 '17 at 07:40
  • @Stoyan but I have just one scene and switch between two nodes - `MenuNode` and `GameNode`, the last one is where lies my all game content. I have even this problem in my `GameNode`, where I tap shapes, and that lag is in his place, without switching any nodes. – Nurassyl Nuridin Mar 29 '17 at 06:00
  • From your code it doesn't become clear what happens in the delegate. Perhaps you could try what happens without using the completion closure. The code looks okay otherwise. – Stoyan Mar 29 '17 at 13:47
  • The answer with `OperationQueue()` helped me! Thanks @Stoyan! – Nurassyl Nuridin Mar 29 '17 at 16:22

2 Answers2

1

I had similar issues. I ended up using SKAction for playing short sounds.

SKAction.playSoundFileNamed("Sounds/Click.wav", waitForCompletion: false)

See SKAction reference for details.

Adam Eri
  • 889
  • 7
  • 13
  • I've tried this out, but I had [this](http://stackoverflow.com/questions/42874601/my-app-crashes-because-of-unfinished-sounds-in-my-sprite-kit-game) issue. – Nurassyl Nuridin Mar 28 '17 at 03:38
1

If you want to use AVAudioPlayer (instead of SpriteKit's sound actions), you should make the sounds play in a background thread so that its operation won't interfere with the main thread where all of your visual stuff goes:

 let soundQueue = OperationQueue()
 soundQueue.qualityOfService = QualityOfService.background 
 soundQueue.addOperation{self.buttonClickSound.play()}
Piotr Byzia
  • 3,363
  • 7
  • 42
  • 62
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • But if the game goes background I don't want to play sounds. – Nurassyl Nuridin Mar 28 '17 at 03:40
  • 1
    @NurassylNuridin, he means a background thread. This is different than an application in the background. Your app usually runs at several threads without you even noticing. For instance most of the SKActions run in separate threads. Using a background thread help separate non-time-critical code from the one that FPS depends on. However, as I already pointed out, I believe the problem is related to switching scenes rather than to playing sounds. I believe playing sounds is not too expensive compared to setting up AVAudioPlayers for instance. – Stoyan Mar 28 '17 at 07:46
  • Wether the game plays sound when the application is no longer the active one is controlled by your AVSession. I did have short rendering hic-ups in my own game before I made the AVAudioPlayer play in a background thread (mostly on the older iPads) so you should try it just in case you have the same issue as I did. – Alain T. Mar 28 '17 at 21:25
  • I'll try this out! – Nurassyl Nuridin Mar 29 '17 at 12:13
  • Thanks @AlainT. it helps me! – Nurassyl Nuridin Mar 29 '17 at 16:21