I'm trying to make a game and in the main menu, there is a label to start music, and another to stop music.
let backgroundMusic = SKAction.playSoundFileNamed("background.mp3", waitForCompletion: false)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
if touchedNode.name == self.BEGIN_MUSIC {
// start music
self.run(backgroundMusic)
}
if touchedNode.name == self.STOP_MUSIC {
// stop music
SKAction.stop()
}
}
}
the music starts fine and works on all scenes which is what i wanted. But when I press the stop music, the music keeps playing. How do I stop the music.
I've tried SKAction.pause(), i've tried using withKey: "music" and then self.removeAction(forKey: "music").
I also tried making backgroundMusic: SKAudioNode and this worked but once i stopped the music i couldn't turn it back on, also it doesn't work on any other scene just the main menu.
How do I stop the sound?
I've had the same issue a while ago: This is how I got it working. This is specific for background music.
var backgroundMusic: AVAudioPlayer!
func playMusic() {
if let musicURL = Bundle.main.url(forResource: "musicSource", withExtension: "wav") {
if let audioPlayer = try? AVAudioPlayer(contentsOf: musicURL) {
backgroundMusic = audioPlayer
backgroundMusic.numberOfLoops = -1
backgroundMusic.play()
backgroundMusic.volume = 0.2
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
let musicOff = UserDefaults.standard.bool(forKey: "musicOff")
if node == musicButton && musicOff == false {
backgroundMusic.stop()
UserDefaults.standard.set(true, forKey: "musicOff")
}
if node == musicButton && musicOff == true {
backgroundMusic.play()
backgroundMusic.volume = 0.3
UserDefaults.standard.set(false, forKey: "musicOff")
}
}
}
Related
So I have a background music looping in the background in my GameViewController. The pause music button is available in the GameScene where a user can mute or unmute the game music.
I have two global variables:
var muteButton = SKSpriteNode(imageNamed: "pause")
var mute: Bool = false
Inside my GameScene I've added, things work like they are suppose to (the print responses are triggered).
class GameScene: SKScene{
override func didMove(to view: SKView){
...
muteButton.position = CGPoint(x: self.size.width*0.2, y: self.size.height*0.90)
muteButton.name = "Mute Button"
muteButton.zPosition = 10
self.addChild(muteButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let nodeITapped = atPoint(pointOfTouch)
if nodeITapped.name == "Mute Button"{
if mute == false {
print("music will now turn OFF")
mute = true
}
else{
print("music will now turn ON")
mute = false
}
}
}
}
}
I suspect the mute variable is only being called once in the GameViewController viewDidLoad, and thus the if statement is being checked only once. Since I have multiple senses connected that all need to have music playing, the best place for me to put the backgroundAudio would be here.
In my GameViewController:
class GameViewController: UIViewController{
var backgroundAudio = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Background Audio plays throughout the game
let filePath = Bundle.main.path(forResource: "Track1",ofType:"mp3")
let audioNS_URL = NSURL(fileURLWithPath: filePath!)
if mute == false{
do{ backgroundAudio = try AVAudioPlayer(contentsOf: audioNS_URL as URL)}
catch { return print("No Audio Found")}
// audio will loop forever
backgroundAudio.numberOfLoops = -1
backgroundAudio.play()
}
else{
backgroundAudio.pause()
}
}
}
Add an observer inside GameViewController:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(switchBackgroundAudio), name: NSNotification.Name.init("switchBackgroundAudio"), object: nil)
//... other stuff here ...//
}
then add a function for switching the sound on/off:
#objc func switchBackgroundAudio() {
if mute == false {
backgroundAudio.play()
} else {
backgroundAudio.pause()
}
}
finally whenever inside your GameScene you touch the button, you may launch an event:
if nodeITapped.name == "Mute Button"{
//... stuff here ...//
NotificationCenter.default.post(Notification(name: NSNotification.Name("switchBackgroundAudio")))
}
I want a sound to play with negligible delay when a user taps down on a view. I've tried this with both AVAudioPlayer and AudioServices, but I'm experiencing the same issue with both: a slight delay if there is more than about a second between taps. In other words, here's what happens:
Tap many times in quick succession and only the first tap is delayed,
Pause for about a second or longer,
Tap many times again (or just once) and again only the first tap is delayed.
The delay we're talking about is short, maybe 100 ms or so, but enough so that the playback doesn't feel/sound instantaneous like the others. Here's the AVAudioPlayer version of the code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
player = try! AVAudioPlayer(contentsOf: soundUrl)
player.volume = 0 // "Prime the pump"
player.play()
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
player.volume = 1
player.play()
}
}
And here's the AudioServices version. In this case, there are other posts suggesting that a silent sound could be played during initialization to "prime the pump" but that would only address the first sound. This issue affects all sounds that occur after a 1+ second delay.
import UIKit
import AudioToolbox
class ViewController: UIViewController {
var soundID:SystemSoundID = 0
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundID)
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
AudioServicesPlaySystemSound(soundID)
}
}
In both cases, this "touch down" recognizer (by #le-sang) is used:
import Foundation
import UIKit.UIGestureRecognizerSubclass
class TouchDownGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if self.state == .possible {
self.state = .recognized
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
}
I've also tried using a UILongPressGestureRecognizer with a minimumPressDuration=0 and the same thing happens. Also, same thing if I replace the UIView with a UIControl and setup a target/action. Any insights would be much appreciated, thanks for reading.
Here's the fix I found. I'm not totally happy with this solution (still not sure why this was happening), but it does indeed work for both players.
The idea is simple: if you leave either player alone for a second or so, it starts to nap and then takes a moment to wake up when you need it. So, the solution is to "nudge" the player at regular intervals so it doesn't start napping.
For the AVAudioPlayer case:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVAudioPlayer!
var ghostPlayer:AVAudioPlayer!
var timer:Timer!
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
player = try! AVAudioPlayer(contentsOf: soundUrl)
ghostPlayer = try! AVAudioPlayer(contentsOf: soundUrl)
ghostPlayer.volume = 0
ghostPlayer.play()
timer = Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(nudgeAudio),
userInfo: nil,
repeats: true)
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
player.play()
}
func nudgeAudio() {
ghostPlayer.play()
}
}
And for AudioServices:
import UIKit
import AudioToolbox
class ViewController: UIViewController {
var soundID:SystemSoundID = 0
var ghostID:SystemSoundID = 1
var timer:Timer!
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundID)
let ghostUrl = Bundle.main.url(forResource: "silence", withExtension: "wav")!
AudioServicesCreateSystemSoundID(ghostUrl as CFURL, &ghostID)
timer = Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(nudgeAudio),
userInfo: nil,
repeats: true)
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
AudioServicesPlaySystemSound(soundID)
}
func nudgeAudio() {
AudioServicesPlaySystemSound(ghostID)
}
}
I am making a game for fun and I want to make my sound effect loop every time it ends, these are my codes so far.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
if atPoint(touchLocation).name == "startGame"{
let gameScene = SKScene(fileNamed: "GameScene")!
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: SKTransition.doorsOpenHorizontal(withDuration: TimeInterval(2)))
self.run(SKAction.playSoundFileNamed("mainmenu.wav", waitForCompletion: false))
You can't play background music over the scenes with using SKActions. Use AVAudioPlayer instead:
if let filePath = Bundle.main.path(forResource: "mainmenu", ofType: "wav") {
let url = URL(fileURLWithPath: filePath)
do {
let player = try AVAudioPlayer(contentsOf: url)
player.numberOfLoops = -1
player.play()
} catch {
// catch error if necessary
}
}
original post:
https://stackoverflow.com/a/23268462/6846532
In gamescene, or any other scene you want the music to loop in use this
let music = SKAudioNode(fileNamed: "backgroundmusic.mp3")
music.autoplayLooped = true
addChild(music)
Personally I would go with AVAudioPlayer or SKAudioNode because playSoundFileNamed turned being buggy in many SpriteKit releases, but I will show you how you can do it with actions:
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
run(SKAction.repeatForever(SKAction.playSoundFileNamed("sound", waitForCompletion: true)))
}
}
I have background music which starts when the app is launched in GameViewController.swift using the following code:
class GameViewController: UIViewController {
// VARIABLES
var backgroundMusicPlayer : AVAudioPlayer!
// AUDIO PLAYER
func playBackgroundMusic(filename: String) {
let url = NSBundle.mainBundle().URLForResource(filename, withExtension: nil)
var error : NSError? = nil
do {
backgroundMusicPlayer = try AVAudioPlayer(contentsOfURL: url!)
} catch let error1 as NSError {
error = error1
backgroundMusicPlayer = nil
}
if backgroundMusicPlayer == nil {
print("Could not create audio player: \(error!)")
return
}
backgroundMusicPlayer.numberOfLoops = -1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play()
}
func stopBackgroundMusic() {
backgroundMusicPlayer.stop()
}
override func viewDidLoad() {
super.viewDidLoad()
playBackgroundMusic("MainTheme.mp3")
<< Various irrelevant code >>
}
Because this is run in the viewController, it persists through changing scenes on the menu (i.e. opening the "shop" scene) and creates a seamless track. When I click the "Play" button on the menu scene I want the music to then stop, and transition to the game. I have the stopBackgroundMusic() method in the GameViewController but I don't know how to call it on on the menu scene. IN THE MENU SCENE I tried this:
// TOUCH
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch?
let touchLocation = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
if touchedNode.name == "startGame" {
GameViewController.stopBackgroundMusic()
let transitionType = SKTransition.fadeWithDuration(2)
let viewSize = self.view?.bounds.size
let scene = GameScene(size: viewSize!)
self.view?.presentScene(scene, transition: transitionType)
}
}
But I get an error saying I'm missing parameter #1 in call for stopBackgroundMusic() which shouldn't require any parameters. Am I calling this method wrong? Thanks!
You are referring to your class by using GameViewController but your function is at the object instance level.
If you declare the variable and function at the class level, your code in the touchesBegan function should work fine.
static var backgroundMusicPlayer : AVAudioPlayer!
class func playBackgroundMusic(filename: String) ...
class func stopBackgroundMusic()
override func viewDidLoad() {
super.viewDidLoad()
GameViewController.playBackgroundMusic("MainTheme.mp3")
<< Various irrelevant code >>
}
Im testing out my game that is using multiplayer real time with game center and when I press play now and it finds the match it doesnt go past Starting game. It just keeps loading and doesnt go to the gameplay. Why does this happen? Heres the code I have: BTW Im using my iPhone 5s and the simulator for iPhone 5s in xcode to test the multiplayer....is that okay?
func findMatchPlease() {
let matchRequest = GKMatchRequest()
matchRequest.minPlayers = 2
matchRequest.maxPlayers = 2
let mmvc = GKMatchmakerViewController(matchRequest: matchRequest)
mmvc.matchmakerDelegate = self
let viewController = self.scene?.view?.window?.rootViewController
viewController?.presentViewController(mmvc, animated: true, completion: nil)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
var touch: UITouch = touches.first as! UITouch
var location = touch.locationInNode(self)
var node = self.nodeAtPoint(location)
if node.name == "find" {
var goToMatch = GamePlay(size: self.size)
var transitionToMatch = SKTransition.fadeWithDuration(1.0)
goToMatch.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(goToMatch, transition: transitionToMatch)
findMatchPlease()
}
}
}