Stopping an SKAction - ios

I have a sprite kit game with different scenes: the main menu ("MainMenuScene") and the game scene ("MyScene"). While the user is playing the game, I have an endless playing background music. But when the player wants to stop the game and return to main menu, the background music keeps playing. What should I do to make it stop? I have tried [self removeAllActions] but it didn't work.
MyScene:
#implementation MyScene
{
SKAction *_backgroundMusic;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.5 blue:0.3 alpha:1.0];
}
//Here I make the endless background music
_backgroundMusic = [SKAction playSoundFileNamed:#"Background 2.m4a" waitForCompletion:YES];
SKAction * backgroundMusicRepeat = [SKAction repeatActionForever:_backgroundMusic];
[self runAction:backgroundMusicRepeat];
return self;
}
- (void)selectNodeForTouch:(CGPoint)touchLocation
{
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
if ([_MainMenuButton isEqual:touchedNode]) {
SKScene *mainMenuScene = [[MainMenuScene alloc]initWithSize:self.size];
[self.view presentScene:mainMenuScene];
//Here is where the music should stop, when the player presses the 'return to main menu' button
}
}

I would not recommend using SKAction to play background music. Instead use AVAudioPlayer.
To use the AVAudioPlayer:
Add the AVFoundation to your project.
#import <AVFoundation/AVFoundation.h> into your .m file.
Add AVAudioPlayer *_backgroundMusicPlayer; in #implementation
Use this code snippet to run your audio:
- (void)playBackgroundMusic:(NSString *)filename
{
NSError *error;
NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
_backgroundMusicPlayer.numberOfLoops = -1;
_backgroundMusicPlayer.volume = 0.8;
_backgroundMusicPlayer.delegate = self;
[_backgroundMusicPlayer prepareToPlay];
[_backgroundMusicPlayer play];
}
Also read up on the AVAudioPlayer Class Reference so you know what all the properties do such as setting volume, number of loops, etc...

try this to play the music:
[self runAction:backgroundMusicRepeat withKey:#"bgmusic"];
and this to stop:
[self removeActionForKey:#"bgmusic"];
update
SKAction * backgroundMusicRepeat = [SKAction playSoundFileNamed:#"Background 2.m4a" waitForCompletion:YES];
backgroundMusicRepeat = [SKAction repeatActionForever:backgroundMusicRepeat];
[self runAction:backgroundMusicRepeat];
I have run those code in my own project and seems work.
but not your way, it only stop when I quit the view, I don't even need removeActionForKey . [self removeActionForKey:#"bgmusic"]; won't work in the scene.
So if you want stop sound while switch between diferent scenes of same view, I suggest you using AVAudioPlayer.
I also found some other questions in stackoverflow has the same problem as you, like this:
how to pause the sound when use SpriteKit
and this:
Spritekit stopping sound
they both go for the AVAudioPlayer.
as one of the comment in those links said: you should use the playSoundFileNamed method for playing sound effects... something short 1 or 2 seconds, like explosion sound --don't use it for background sound.

Related

How can I countdown before I resume a SKScene?

I have a paused SKScene. When the user asks to do so, I would like to resume the scene. However, I would like to give the user a couple of seconds to prepare before the game begins. To do so, when the user asks to resume the game, I would like to first countdown from 3, and then resume the scene.
I currently have a SKLabel to indicate the count. When the user clicks on resume, I use an NSTimer to countdown from 3, updating the contents of the label every second, and resuming the game when the count is up.
However, because the game is paused, the SKLabel doesn't update every second; it only updates once at the very end, once the game is resumed. I am looking for a way around this.
Use a common variable in your GameScene that indicates if the game is paused, for example isGamePaused. In your update: method, you will have:
if(!isGamePaused){
//Do all game logic
}
You can use isGamePaused to pause and unpause the game. Now, let's make the countdown. I would make a subclass of SKNode, add the SKLabel there and set a delegate so we know when the CountDown finished. For example, in CountDown.h:
#protocol SKCountDownDelegate <NSObject>
-(void)countDown:(id)countDown didFinishCounting:(BOOL) didFinishCounting;
#end
#interface SKCountDown : SKNode
#property (assign, atomic) id<SKCountDownDelegate> delegate;
-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *) color andFontName:(NSString *)fontName;
#end
And in CountDown.m:
#import "SKCountDown.h"
#interface SKCountDown()
#property int secondsToGo;
#property SKLabelNode *numberLabel;
#property NSTimer *countTimer;
#end
#implementation SKCountDown
-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *)color andFontName:(NSString *)fontName{
if (self = [super init]) {
self.numberLabel = [[SKLabelNode alloc] initWithFontNamed:fontName];
[self.numberLabel setFontColor:color];
[self.numberLabel setFontSize:110];
[self.numberLabel setText:#"3"];
self.secondsToGo = seconds;
[self addChild:self.numberLabel];
self.countTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(count) userInfo:nil repeats:YES];
}
return self;
}
-(void)count{
if (self.secondsToGo > 1) {
self.secondsToGo -= 1;
self.numberLabel.text = [NSString stringWithFormat:#"%i", self.secondsToGo];
}else{
[self.countTimer invalidate];
self.countTimer = nil;
self.numberLabel.text = #"GO!";
[self performSelector:#selector(finish) withObject:nil afterDelay:1];
}
}
-(void)finish{
[self removeFromParent];
[self.delegate countDown:self didFinishCounting:YES];
}
#end
So, anywhere you want to add this CountDown, you can do the following:
-(void)startCountDown{
self.countDown = [[SKCountDown alloc] initCountDownWithSeconds:3 andColor:self.countdownFontColor andFontName:self.countdownFontName];
self.countDown.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.countDown.delegate = self;
self.countDown.zPosition = 20;
[self addChild:self.countDown];
}
-(void)countDown:(id)countDown didFinishCounting:(BOOL)didFinishCounting{
isGamePaused = NO;
}
This is an example of a way to do it. Hope it helps!
Pausing an entire SKScene during gameplay seems a bit too blunt for me, because you typically want to update something in the node hierarchy.
A simple solution would be to make a distinction between game node and UI nodes. You can simply add two nodes (game and ui) to the scene and whenever the scene would call [self addChild] choose which node to add them to.
Later, you can pause the game node and still be able to update the UI.
SKScene
- SKNode "game" // <-- pause this one
- SKSpriteNode "goodGuy"
- SKSpriteNode "badGuy"
- SKSpriteNode "mehGuy"
- ...
- SKNode "ui"
- SKLabelNode "countdownLabel"
- SKSpriteNode "resumeButton"
- SKSpriteNode "pauseButton"
- ...
Do take note that the paused property only determines if actions are processed on the node and its descendants. If you are performing physics this will not suffice.
Pausing physics can be done by pausing the SKView but that is definitely not what you're looking for. You can always try setting the speed of the physicsWorld to 0 (I haven't tested this, though).
I prefer to simply pause sections of the update method instead of pausing the whole scene. Create a BOOL property:
#property (nonatomic) BOOL runUpdateMethod;
Structure your update method to determine what pieces get paused:
-(void)update {
if(runUpdateMethod) {
// code to be paused
}
// code not to be paused
}
You can use a SKAction block to run a countdown and resume regular updates:
-(void)countdown {
__block SKLabelNode *labelNode0;
SKAction *wait0 = [SKAction waitForDuration:1.0];
SKAction *block0 = [SKAction runBlock:^{
labelNode0 = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
labelNode0.text = #"5";
labelNode0.fontSize = 100;
labelNode0.fontColor = [SKColor redColor];
labelNode0.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
labelNode0.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
labelNode0.position = CGPointMake(self.screenWidth/2, self.screenHeight/2);
labelNode0.zPosition = 955;
labelNode0.alpha = 0.4;
[self addChild:labelNode0];
}];
SKAction *block1 = [SKAction runBlock:^{
labelNode0.text = #"4";
}];
SKAction *block2 = [SKAction runBlock:^{
labelNode0.text = #"3";
}];
SKAction *block3 = [SKAction runBlock:^{
labelNode0.text = #"2";
}];
SKAction *block4 = [SKAction runBlock:^{
labelNode0.text = #"1";
}];
SKAction *block5 = [SKAction runBlock:^{
[labelNode0 removeFromParent];
self.runUpdateMethod = YES;
}];
[self runAction:[SKAction sequence:#[block0, wait0, block1, wait0, block2, wait0, block3, wait0, block4, wait0, block5]]];
}
Instead of pausing the game, I set SKScene's speed property to 0, along with setting SKPhysicsWorld's speed property to 0. I also had to modify my update function slightly by only executing certain portions of the code if the game isn't paused.
This created the same effect as the entire game being paused, while also allowing me to update the content of labels.

Overlaying background sound

we are having problem with our background sound. If the sound is on when we change view and then go back to the menu it adds another loop of sound. If the sound is muted when we go back it starts again. Please help. This is my code.
// Meny.m
#import "Meny.h"
#import <AVFoundation/AVFoundation.h>
#interface Meny () {
AVAudioPlayer *audioPlayerBG;
}
#end
#implementation Meny
- (void)viewDidLoad {
[super viewDidLoad];
NSString *music = [[NSBundle mainBundle]pathForResource:#"TestSwoong" ofType:#"wav"];
audioPlayerBG = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
audioPlayerBG.numberOfLoops = -1;
audioPlayerBG.volume = 0.5;
[audioPlayerBG play];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
// LjudKnapp stop
- (IBAction)stopBG:(id)sender {
[playBG setHidden:NO];
[pauseBG setHidden:YES];
[audioPlayerBG stop];
}
// LjudKnapp play
- (IBAction)playBG:(id)sender {
[pauseBG setHidden:NO];
[playBG setHidden:YES];
[audioPlayerBG play];
}
It appears that you need to brush up on what classes and instances are. Understanding classes and instances is fundamental if you're going to do object-oriented programming, which is what you do when you use Objective-C.
we change view and then go back to the menu it adds another loop of sound
That phrase suggests that when you "go back", you are not actually going back to the already existing Meny instance - instead, you are creating another Meny instance. So now, you see, you have two of them! Well, each Meny instance has its own AVAudioPlayer, which starts playing when the Meny instance is created — so now you have two audio players, and that's why you hear two loops playing simultaneously.

Why does my audio play before the Sprite Kit scene transition?

In this code, Audio is a class where all things concerning AVAudioPlayer are handled. Currently there is some music playing. I want to have this fade out completely in 1 second and then play another song. Simultaneously, I want a new scene to appear. So I want the new song to start right as the transition (also lasting 1 second) ends. However, I find myself always waiting for the music to stop, before the scene transition starts. I've tried many ways, with and without selectors, with and without the various sleep functions, but without any luck. I've also tried to use stopMusic instead of transitionMusic and then use playMusic: in the new scene, but I always find myself waiting for stopMusic to finish before the transition starts.
Here's a part of the first scene, which is run if a certain button on the screen is pressed:
Audio *musicPlayer = [[Audio alloc] init];
NewScene *newScene = [NewScene sceneWithSize:self.size];
SKTransition *reveal = [SKTransition pushWithDirection:SKTransitionDirectionLeft duration:1.0];
reveal.pausesOutgoingScene = NO;
reveal.pausesIncomingScene = NO;
[self.view presentScene:newScene transition:reveal];
[musicPlayer transitionMusic:#"NewSong"];
The method transitionMusic: is as follows:
-(void)transitionMusic:(NSString *)fileName {
[self performSelectorOnMainThread:#selector(stopMusic) withObject:nil waitUntilDone:YES];
[self playMusic:fileName];
}
And stopMusic and playMusic: are:
-(void)stopMusic {
while(BGM.volume > 0.05) {
[BGM setVolume:(BGM.volume - 0.05)];
[NSThread sleepForTimeInterval:0.05];
}
[BGM stop];
}
-(void)playMusic:(NSString *)fileName {
NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:#"%#",fileName] withExtension:#"caf"];
BGM = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:nil];
[BGM setNumberOfLoops:-1];
[BGM setVolume:1.0];
[BGM prepareToPlay];
[BGM play];
}
Also, BGM is a static audioplayer declared in Audio.h. Can anyone point out what I'm doing wrong?
Your musicPlayer player object is dependent on your old scene. You need to create a singleton to handle your transition music independently from one scene to another.
BoxesOfBabies.com has a good tutorial on how to set up a singleton and use it to Control Music Across Multiple SKScene.
EDIT:
In your SKScene:
#import "MySingleton"
#implementation MainScene
{
MySingleton *_mySingleton;
}
In your init method:
_mySingleton = [MySingleton sharedInstance];
To start playing audio in any of your scenes:
[_mySingleton startPlayingSong1];
or
[_mySingleton startPlayingSong2];
whatever methods you have in your singleton.

DTMF sounds for custom Dialer iOS

I am trying to add dtmf sounds to my custom dialer pad.
I managed reproducing a sound for each number on touch down. Now I am trying to achieve the natural phone behaviour for when the person who is dialing does a "longpress" on a number.
The problem is that the sound ends. How could I make it lasting as long as the touch ends?
In my code for now I am trying to add a long sound even if I am using the touch down event, just to test if it works. If it does I will add the gesture recognizer for long press.
Here is my code:
#interface ViewController : UIViewController<AVAudioPlayerDelegate>{
//SystemSoundID toneSSIDs[12];
AVAudioPlayer *toneSSIDs[12];
}
#end
#implementation ViewController
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
for(int count = 0; count < 12; count++){
NSString *toneFilename = [NSString stringWithFormat:#"dtmf-%d", count];
NSURL *toneURLRef = [[NSBundle mainBundle] URLForResource:toneFilename
withExtension:#"mp3"];
player = [[AVAudioPlayer alloc]initWithContentsOfURL:toneURLRef error:NULL ];
player.volume = 0.4f;
toneSSIDs[count] = player;
}
}
return self;
}
-(void)playSoundForKey:(int)key {
_sound = toneSSIDs[key];
_sound.delegate = self;
_sound.numberOfLoops = -1;
[_sound prepareToPlay];
[_sound play];
}
- (IBAction)touchNum:(id)sender {
[self playSoundForKey:[sender tag]];
}
I tried using both .wav and .mp3 sounds and also looping the sound but I get multiple sounds, not a long one as I need. Somebody help, please!

Spritekit animation load time

I have a simple game that I've been working on, a side scrolling space adventure where you save cats. A very simple game, but I'm running across an issue with. My game starts up normally, showing the main menu scene. Hit play, and it transititons to the game scene without issue, however, I'm finding that the first time I press start to begin the game, I get a bit of loading lag. This lag only appears the very first time you load the game.
Upon hitting start, the following method is called:
-(void)nonplayerSprites:(StartReason)endReason{
if (endReason == kStartGame){
// [self playMusic:#"Electrodoodle.mp3"];
[self spawnPlayer];
[self setupUI];
[self runAction:[SKAction repeatActionForever: [SKAction sequence:#[[SKAction
performSelector:#selector(enemySpawn) onTarget:self],
[SKAction waitForDuration:2.0]]]]];
[self runAction:[SKAction repeatActionForever: [SKAction sequence:#[[SKAction
performSelector:#selector(spawnCat) onTarget:self],
[SKAction waitForDuration:1.5]]]]];
[self runAction:[SKAction repeatActionForever: [SKAction sequence:#[[SKAction
performSelector:#selector(powerUpSpawnCheck) onTarget:self],[SKAction
waitForDuration:2.0]]]]];
} else if (endReason == kEndGame){
[self removeAllActions];
} else if (endReason == kRestartGame){
MainGame * myScene =
[[MainGame alloc] initWithSize:self.size];
SKTransition *reveal =
[SKTransition fadeWithDuration:0.5];
[self.view presentScene: myScene transition: reveal];
}
}
I have four sprite nodes that it calls, a UFO, a cat, the player rocket and a randomized power up. Two SKLabelNodes for player score and life total are called via the spawnUI method, and Additionally, music will play. Although I currently have it disabled.
The enemy and friendly nodes are all called using a similar method:
-(void)enemySpawn
{
int enemyChoice = [self getRandomNumberBetween:0 to:1];
if (enemyChoice == 0){
_enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
[_enemy setScale:1.0];
}else if (enemyChoice == 1){
_enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy_alt"];
[_enemy setScale:0.50];
}
_enemy.anchorPoint = CGPointZero;
_enemy.zPosition = 2.0;
_enemy.name = #"rocket";
_enemy.position = CGPointMake(self.size.width + _enemy.size.width, [self
getRandomNumberBetween:0 to:250]);
SKAction *enemyMove = [SKAction sequence:#[[SKAction moveToX:-40 duration:[self
getRandomNumberBetween:2 to: 5]],
[SKAction removeFromParent]]];
[_worldNode addChild:_enemy];
[_enemy runAction:enemyMove];
}
I feel like I know what the issue is, the game data is being loaded into ram for the first time, but how do I avoid the pause while this loads? I wonder if I'm doing something wrong, because it doesn't seem like I'm loading enough to cause a noticable 1 to 3 second pause upon hitting the start button. The second time through, say after the player dies and restarts, the pause is gone and everything plays out smoothly. I apologize if I'm leaving out any important information, I'm pretty new at SpriteKit.
Anyone have some thoughts?
Thanks in advance.
I had the same problem.
It seems that when you use [SKSpriteNode spriteNodeWithImageNamed:#"enemy"] or *[SKSpriteNode spriteNodeWithImageNamed:#"enemy_alt"]* or any other texture loading method, texture is not stored in buffers for quick access so first call of those methods is giving you lags.
The solution is next :
Add properties for your game objects textures
#property(nonatomic,strong) SKTexture *enemyTexture;
.
.
.
add method to your scene subclass:
-(void)preloadTextures
{
self.enemyTexture = [SKTexture textureWithImageNamed:#"enemy"];
.
.
.
.
[SKTexture preloadTextures:#[self.enemyTexture,...] withCompletionHandler:^{
NSLog(#"Textures preloaded");
}];
}
Now call that method before presenting
MainGame * myScene =
[[MainGame alloc] initWithSize:self.size];
SKTransition *reveal =
[SKTransition fadeWithDuration:0.5];
[myScene preloadTextures];
[self.view presentScene: myScene transition: reveal];
Hope it will do the trick for you too.

Resources