Search code examples
sprite-kitavaudioplayerframe-rate

SpriteKit + AVAudioPlayer in touchesMoved: kills fps


In a SpriteKit app, I am playing a click sound when the user moves a block on the screen, but this causes terrible lagging (fps dropping to near zero).

Code:

self.audioPlayers is a strong NSMutableArray which holds currently playing AVAudioPlayers and removes them when the audioPlayerDidFinishPlaying: delegate method is called.

• The blockShouldMove method compares the touch location to its previous location and only returns YES if the user has moved the cursor enough distance so we only play a maximum of around 8 sounds simultaneously.

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *oneTouch = [touches anyObject];
    CGPoint location = [oneTouch locationInNode:self];

    if (![self blockShouldMove:location]) {
        return;
    }

    NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:@"click_01.mp3" ofType:nil];
    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:soundFilePath] error:nil];
    [audioPlayer prepareToPlay];
    audioPlayer.volume = 1.0;
    audioPlayer.numberOfLoops = 0;
    audioPlayer.delegate = self;
    [audioPlayer play];
    [self.audioPlayers addObject:audioPlayer];
}

On both simulators and real devices (iOS and tvOS), if I make circles with my finger, the fps drops to almost nothing until well after I have even released my finger.

If I remove the whole AVAudioPlayer and use [SKAction playSoundFileNamed:@"click_01.mp3" waitForCompletion:NO], everything works fine. But unfortunately, SKAction sound handling is terrible for my purpose because the volume cannot be set.

Is there anything that can be done to make this better?


Solution

  • This is an indirect sort of "answer", more a comment but it's too long to write it in that form.

    I ran into something like this issue with a SpriteKit game. I solved it by dropping the AVAudioPlayers and instead using the scene's audio engine. I connected a bunch of AVAudioPlayerNodes to the main mixer node and started them playing (nothing). Whenever I'd want a sound effect of some sort, I'd grab the next audio player node (round robin fashion, and there were enough of them so that I was sure it would be idle) and schedule a preloaded sound buffer for it.

    The point is that for more complicated audio in SpriteKit, you may need to go through the audio engine and build a sound graph that's appropriate for what you want to do. Multiple AVAudioPlayers existing at once seem to be stepping on each other somehow and causing lag.

    However if you didn't try the SKAudioNode functionality yet, I'd say do that first. You should be able to run an action on the audio node to adjust volume. I didn't go that route since I couldn't get the stereo panning to work the way I wanted with that setup. If that works for what you want to do, it's probably simpler than setting up your own sound graph.

    If you want to see the solution I eventually used, you can look at https://github.com/bg2b/RockRats/blob/master/Asteroids/Sounds.swift