Regulating the volume of SKAction playSoundFileNamed: - ios

Is there a way to regulate the volume of sound played via SKAction playSoundFileNamed:waitForCompletion:.
I would like to implement a simple music & sound effects slider in my game. I can easily control background music since i play it via AVAudioPlayer, but all sound effects are played via SKAction.

Here is my code for how i handled this problem
NSError *error;
NSURL *soundURL = [[NSBundle mainBundle] URLForResource:#"pew-pew-lei" withExtension:#"caf"];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
[player setVolume:masterVolume];
[player prepareToPlay];
SKAction* playAction = [SKAction runBlock:^{
[player play];
}];
SKAction *waitAction = [SKAction waitForDuration:player.duration+1];
SKAction *sequence = [SKAction sequence:#[playAction, waitAction]];
[self runAction:sequence];
The masterVolume variable is just some preset variable i have that the user can change from 0.0-1.0
The waitAction ensures that the player doesn't get removed before it has played the entire sound
Hope this helps!

Unfortunately you can't modify the volume using SKAction, so you have to use AVAudioPlayer for your effects too.
You could implement a custom playSoundFileNamed:waitForCompletion:volume: using runBlock as you already thought, so your code won't be very different then using playSoundFileNamed:waitForCompletion:.

Swift:
Since iOS 9.0 you can change the volume to your node running another action if you have already runned your audio (remember that this one obviusly cannot work if you have something that run with repeatForever):
let changeVolumeAction = SKAction.changeVolume(to: 0.3, duration: 0.3)
node.run(changeVolume)
Or you can create a group and launching your audio with the correct volume:
let effectAudioAction = SKAction.playSoundFileNamed("electricshockLow.mp3", waitForCompletion: false)
let changeVolumeAction = SKAction.changeVolume(to: 0.3, duration: 0.3)
let effectAudioGroup = SKAction.group([effectAudioAction,changeVolumeAction])
node.run(effectAudioGroup)

Related

SceneKit AVPlayer only audio is playing

I am trying to play a video in SceneKit. I can hear the audio but video is not rendering. The way i am using the scene is as follows. Please help.
SCNScene *scene = [SCNScene sceneNamed:#"art.scnassets/plane.scn"];
SCNNode *plane = [scene.rootNode childNodeWithName:#"plane" recursively:YES];
AVPlayer *player = [AVPlayer playerWithURL:[NSURL URLWithString:#"https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_5mb.mp4"]];
plane.geometry.firstMaterial.diffuse.contents = player;
[player play];
SCNView *scnView = (SCNView *)self.view;
scnView.scene = scene;
scnView.allowsCameraControl = YES;
scnView.showsStatistics = YES;
scnView.backgroundColor = [UIColor blackColor];
You need to use a real iPhone not simulator to enjoy.
Another reason is the shading in the material panel should not be physical based. You may try Blinn. The code has no problem.
If using a physical based, you have to add an extra light to make it work.
you can keep PBR and get glass reflections with a non-rough material. use the emission instead of diffuse for the the video though
plane.geometry.firstMaterial.emission.contents = player

SKAudioNode won't play sound

I have a small game that needs some background music. However I cannot figure out how to make this happen! I've tried using SKAudioNode with this code.
override func didMoveToView(view: SKView) {
/* Setup your scene here */
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(sprite)
//music
let music = SKAudioNode(fileNamed: "music")
self.addChild(music)
}
I have a Data Set in Assets.xcassets called "music" with only one file in it called "music.mp3". I've tried changing the initialization of the music constant to SKAudioNode(fileNamed: "music.mp3") but that had no effect. Any help would be highly appreciated.
I have my doubts that you can use music from the XCAssets file. Instead drag and drop the file into your folder. Then use it.
Secondly, just "music" will not work. It requires an extension!
You can even play music using the SKAction Class also!
[SKAction repeatForever:[SKAction playSoundFileNamed:#"yourFile.mp3" waitForCompletion:YES]];

Objective c SpriteKit node

i want to make simple game in SpriteKit, basically what i want do do is make node what shows up on touch and disappear after like 5 sec. here are sample of code that is for board:
SKSpriteNode* board = [[SkSpriteNode alloc] initWithImageNamed:#"board"];
board.name = boardCategoryName;
board.position = CGPointMake (CGRectGetMaxX(self.frame),board.frame.seize.height *4.6f);
[self.addChild:board];
board.physicsBody = [SKphysicsBody bodyWithRectangleOFSize:board.frame.size];
board.physicsBody.restitution =0.1f;
board.physicsBody.friction = 0.4f;
board.physicsBody.dynamic = No;
Mr T does present 1 option, but personally I would use the SKActions sequence, waitForDuration, and removeFromParent() when dealing with nodes, like this:
let waitDuration = SKAction.waitForDuration(5)
let killAction = SKAction.removeFromParent()
let seqAction = SKAction.sequence([waitDuration,killAction])
....
board.runAction(seqAction)
There are many ways you can do it. One way is to call [board removeFromParent] after 5 seconds. This could be a call by NSTimer or you can use the update method which runs continuously,and check for the flag if node is added on the screen or not, and then remove the node after 5 seconds. You might need to set the flag in the touchesBegan method.
Edit:
You can also make use of SKAction waitForDuration
Sample code:
SKAction *delay = [SKAction waitForDuration:5];
SKAction *remove = [SKAction removeFromParent];
SKAction *actionSequence = [SKAction sequence:#[delay,remove]];
[board runAction:actionSequence];
So that after the wait is done, the node will be removed.

Sprite kit detect sound completion

I have used sequence of action and one of the action plays sound for me.
SKAction *wait = [SKAction waitForDuration:1];
NSString* completion_sound = self.gameData[#"level_data"][#"completion_sound"];
SKAction *playSound = [SKAction playSoundFileNamed:completion_sound waitForCompletion:NO];
SKAction *completion = [SKAction runBlock:^{
// do some code here.
}];
SKAction *sequence = [SKAction sequence:#[wait, playSound, completion]];
[self runAction:sequence];
But I want to detect a completion of sound. seems like sound is still playing while block is invoking.
It appears you are passing NO for the waitForCompletion: argument and it should be YES.
If YES, the duration of this action is the same as the length of the audio playback. If NO, the action is considered to have completed immediately.
So change to YES in this line of code...
SKAction *playSound = [SKAction playSoundFileNamed:completion_sound waitForCompletion:YES];
...and the playSound action duration will be the length of the sound, therefor the 'completion block' SKAction *completion will not be executed until the sound is finished.
Your sequence is wrong. The correct order should be:
SKAction *sequence = [SKAction sequence:#[playSound, wait, completion]];

iOS - How to make a UISlider into a "Scrubber / Seekbar" for video playback?

I have an instance of AVAudioPlayer that plays a local audio file from disk. The playback progress is reflected with a UIProgressView. I'm trying to replace that with a UISlider. To accomplish the task, the slider will:
move in response to timer event to indicate the current playback position
respond to manual touch to allow for video scrubbing
I'm not sure how to make the UISlider smart enough to know that when human touch began, it should stop responding to timer events, and when the touch is released, it should resume the audio player at a new position and start updating with timer events again.
Which "Send event" outlets in storyboard would I use to create the behavior
above?
Currently I have a generic "value changed" callback.
- (IBAction)sliderChanged:(id)sender {
if(self.backgroundMusicPlayer == nil)
{
NSError* error = nil;
NSData* data = [self.audioFile data];
self.backgroundMusicPlayer = [[AVAudioPlayer alloc]initWithData:data error:&error];
[self.backgroundMusicPlayer prepareToPlay];
}
self.backgroundMusicPlayer.currentTime = self.slider.value;
[self.backgroundMusicPlayer play];
}
What you have is right. To make your code respond to the user adjusting the slider, use the Value Changed event. If you want to be notified that the slider has been touched, use the Touch Down event, or add a gesture recognizer to the slider.
I would keep things simple and just check the isTracking property of UISlider which it inherits from UIControl.

Resources