SKAudioNode positional audio seems to not work - ios

I'm trying to implement the "3d" audio Apple has added to SpriteKit, but the sounds really don't seem to be positioned at all, even with headphones on. Here's my relevant code.
Play Sound function:
func playTapSound(node: SKNode) {
let tapSound = SKAudioNode(fileNamed: "glassNote")
tapSound.positional = true
tapSound.autoplayLooped = false
node.addChild(tapSound)
tapSound.runAction(SKAction.play())
}
Game Scene:
let listenerNode = SKNode()
...
override func didMoveToView(view: SKView) {
listenerNode.position = view.center
listener = listenerNode
}
playTapSound gets called if the user taps a game piece node.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touchedNode = scene.nodeAtPoint(sceneTouchPoint)
playTapSound(touchedNode)
}
I've checked the positions of all the nodes including the listener node and they're all correct. Listener node is in the center of the screen and the game pieces are basically a checkerboard layout.
When I tap a piece, I really can't tell there's any sort of positioning on the audio. Is it just extremely subtle? Can't find much info out there about it except Apple saying it's part of SpriteKit..

Turns out my mistake was not adding my listenerNode to the scene before setting it to the listener.
From the docs on listener:
If a non-nil value is specified, it must be a node in the scene.
Game Scene updated and verified:
let listenerNode = SKNode()
...
override func didMoveToView(view: SKView) {
listenerNode.position = view.center
addChild(listenerNode)
listener = listenerNode
}

Related

SpriteKit not detecting physics bodies when using alpha mask but does when using bounding circle

I am pretty new to SpriteKit so I may be missing something quite obvious.
I am attempting to create an interactive map of the US. I have loaded PNG images for each state and placed a couple of them into the main SKScene using the scene editor.
My goal is wanting to detect when each state is tapped by the user. I initially wrote some code that would find the nodes within a touch location however, this meant that the user could tap the frame and it would be counted as a valid tap. I only want to register touches that happen within the state texture and not the frame. Reading online it was suggested to use SKPhysicsBody to register when a tap takes place. So I changed my code to the following.
class GameScene: SKScene {
override func didMove(to view: SKView) {}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location: CGPoint = self.convertPoint(fromView: touch.location(in: self.view))
let body = self.physicsWorld.body(at: location)
if let state = body?.node, let name = state.name {
state.run(SKAction.run({
var sprite = self.childNode(withName: name) as! SKSpriteNode
sprite.color = UIColor.random()
sprite.colorBlendFactor = 1
}))
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
Now, if I choose the Bounding circle body type everything works as expected (shown above in the screenshot). When I click within the boudning circle it runs the SKAction otherwise it does nothing. However, when I change the body type to Alpha mask (the body type I want) it suddenly stops detecting the state. In fact, it returns the SKPhysicsBody for the MainScene entity.
Any advice on how to fix this would be greatly appreciated.
Thanks.
i can reproduce this behavior (bug?) when using the scene editor. however it goes away if you skip the sks file and initialize your sprites in code. (i acknowledge that setting locations for 50 states is more tedious this way.)
class GameScene: SKScene {
let ok = SKSpriteNode(imageNamed: "ok")
let nm = SKSpriteNode(imageNamed: "nm")
let tx = SKSpriteNode(imageNamed: "tx")
override func didMove(to view: SKView) {
tx.name = "Texas"
nm.name = "New Mexico"
ok.name = "Oklahoma"
//set up physics bodies w/ alpha mask
tx.physicsBody = SKPhysicsBody(texture: tx.texture!, size: tx.texture!.size())
tx.physicsBody?.affectedByGravity = false
nm.physicsBody = SKPhysicsBody(texture: nm.texture!, size: nm.texture!.size())
nm.physicsBody?.affectedByGravity = false
ok.physicsBody = SKPhysicsBody(texture: ok.texture!, size: ok.texture!.size())
ok.physicsBody?.affectedByGravity = false
//then position your sprites and add them as children
}
}

Why is SpriteKit's showFields property not working properly?

Building a game in SpriteKit, I used SKFieldNode.radialGravityField() with negative strength to simulate a positive charge's electric field. To visualize the fields and their interaction, I used SKView.showFields property and it was working fine.
Now, trying to implement a negative charge, I tried switching all fields to SKFieldNode.electricField(). The behavior of the fields and particles (attraction and repulsion) continued to work perfectly, but the visual representation created by showFields stopped working.
Investigating, the visual representation worked only for radialGravityField and did nothing for all other SKFieldNode options. I tried changing the fields' strength but couldn't reach anything. In addition to the fields physics working fine, I looked for the fields magnitude using SKPhysicsWorld.sampleFields(at:) and they were correct.
Example
Based on XCode's default project for iOS game, this GameScene below can reproduce the behavior. Changing from SKFieldNode.radialGravityField() to SKFieldNode.electricField() makes the fields not show up.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var fieldNode : SKFieldNode?
override func didMove(to view: SKView) {
view.showsFields = true
self.fieldNode = SKFieldNode.radialGravityField() // Change field type here
if let fieldNode = self.fieldNode {
fieldNode.strength = 2
}
}
func touchDown(atPoint pos : CGPoint) {
if let n = self.fieldNode?.copy() as! SKFieldNode? {
n.position = pos
self.addChild(n)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
}

Detect a Tap within the first Scene : SpriteKit Swift iOS, having small problem

so I have a menu screen in my app. Swift, SpriteKit, iOS 7-10,11,12,13..
All I want is a user to tap the button on my screen, and have it move them to another screen. This works in other references in my code. However All I did was change the first loading scene in the GameViewController to my own swift file, and this is how the code looks:
import Foundation
import SpriteKit
import GameplayKit
import UIKit
class FrontMenu : SKScene {
var play = SKSpriteNode(imageNamed: "pbut")
var credit = SKSpriteNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "play" {
print("play tapped.")
// this is where I would have placed the transition, but this did not work.
}
else if node.name == "cred" {
NSLog("credits tapped")
}
if location.x <= -90 && location.y >= -160 {
NSLog("Area hit for play")
// This area is still not detected within the app, and there is no button function.
let gameScene = GameScene(fileNamed: "GameScene")
gameScene?.scaleMode = .aspectFill
self.view?.presentScene(gameScene!, transition: SKTransition.fade(withDuration: 0.86))
Basically that does that. You can see where I implemented the transition. Am I missing an import? Am I missing something to change to the overall Plist file? or Maybe something else within GameViewController.swift, I changed the first loading swift file, so that it does load this code first in the app. However it does not detect the touches.
Thank you for your help!
It looks like you are missing code that would set the name for your node. Make sure that in your didMove function that you are setting the name of the node and adding it to the scene.
//inside scene
override func didMove(to view: SKView) {
play.name = "play"
self.addChild(play)
}
The other thing you have to make sure of is that you created a SpriteKit scene file in your project and set the custom class to be FrontMenu
As for switching the file in your GameViewController, you should just be using the name of the file and not the file extension for fileNamed::
//inside GameViewController
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'MneuScene.sks'
if let scene = SKScene(fileNamed: "MenuScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .resizeFill
// Present the scene
view.presentScene(scene)
}
}
}

SKSpriteNode does not let touches pass through when isUserInteractionEnabled false

I'm attempting to create an overlay in SpriteKit which by using an SKSpriteNode. However, I want the touches to pass through the overlay, so I set isUserInteractionEnabled to false. However, when I do this, the SKSpriteNode still seems to absorb all the touches and does not allow the underlying nodes to do anything.
Here's an example of what I'm talking about:
class
TouchableSprite: SKShapeNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touch began")
}
}
class GameScene: SKScene {
override func sceneDidLoad() {
// Creat touchable
let touchable = TouchableSprite(circleOfRadius: 100)
touchable.zPosition = -1
touchable.fillColor = SKColor.red
touchable.isUserInteractionEnabled = true
addChild(touchable)
// Create overlay
let overlayNode = SKSpriteNode(imageNamed: "Fade")
overlayNode.zPosition = 1
overlayNode.isUserInteractionEnabled = false
addChild(overlayNode)
}
}
I've tried subclassing SKSpriteNode and manually delegating the touch events to the appropriate nodes, but that results in a lot of weird behavior.
Any help would be much appreciated. Thanks!
Reading the actual sources you find:
/**
Controls whether or not the node receives touch events
*/
open var isUserInteractionEnabled: Bool
but this refers to internal node methods of touches.
By default isUserInteractionEnabled is false then the touch on a child like your overlay SKSpriteNode is, by default, a simple touch handled to the main (or parent) class (the object is here, exist but if you don't implement any action, you simply touch it)
To go through your overlay with the touch you could implement a code like this below to your GameScene.Remember also to not using -1 as zPosition because means it's below your scene.
P.S. :I've added a name to your sprites to recall it to touchesBegan but you can create global variables to your GameScene:
override func sceneDidLoad() {
// Creat touchable
let touchable = TouchableSprite(circleOfRadius: 100)
touchable.zPosition = 0
touchable.fillColor = SKColor.red
touchable.isUserInteractionEnabled = true
touchable.name = "touchable"
addChild(touchable)
// Create overlay
let overlayNode = SKSpriteNode(imageNamed: "fade")
overlayNode.zPosition = 1
overlayNode.name = "overlayNode"
overlayNode.isUserInteractionEnabled = false
addChild(overlayNode)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("GameScene Touch began")
for t in touches {
let touchLocation = t.location(in: self)
if let overlay = self.childNode(withName: "//overlayNode") as? SKSpriteNode {
if overlay.contains(touchLocation) {
print("overlay touched")
if let touchable = self.childNode(withName: "//touchable") as? TouchableSprite {
if touchable.contains(touchLocation) {
touchable.touchesBegan(touches, with: event)
}
}
}
}
}
}
}
You need to be careful with zPosition. It is possible to go behind the scene, making things complicated.
What is worse is the order of touch events. I remember reading that it is based on the node tree, not the zPosition. Disable the scene touch and you should be seeing results.
This is what it says now:
The Hit-Testing Order Is the Reverse of Drawing Order In a scene, when
SpriteKit processes touch or mouse events, it walks the scene to find
the closest node that wants to accept the event. If that node doesn’t
want the event, SpriteKit checks the next closest node, and so on. The
order in which hit-testing is processed is essentially the reverse of
drawing order.
For a node to be considered during hit-testing, its
userInteractionEnabled property must be set to YES. The default value
is NO for any node except a scene node. A node that wants to receive
events needs to implement the appropriate responder methods from its
parent class (UIResponder on iOS and NSResponder on OS X). This is one
of the few places where you must implement platform-specific code in
SpriteKit.
But this may not have always been the case, so you will need to check between 8 , 9, and 10 for consistency

SKLabelNode will disappear but is still clickable

I am making a game using SpriteKit and Swift, running Xcode 6. I have an SKLabelNode, let's call it myLabelNode for this example. When I call myLabelNode.removeFromParent() it removes the node from the scene, as it should. The node count drops by 1, and it isn't visible anywhere on the screen. However, when I click the spot where myLabelNode previously was, my program will still call out the function that should only happen when myLabelNode is touched. I also tried combining myLabelNode.removeFromParent() with myLabelNode.hidden = true, but it is still touchable, and calls the function even though it shouldn't. How should I fix this? Is there a different method I should be using? Is this supposed to happen?
Edit:
let lemonadeLabel = SKLabelNode(fontNamed: "Optima-ExtraBlack")
override func didMoveToView(view: SKView) {
lemonadeLabel.text = "Lemonade Stand"
lemonadeLabel.fontSize = 24
lemonadeLabel.fontColor = SKColor.yellowColor()
lemonadeLabel.position = CGPoint(x: size.width/2, y: size.height*0.66)
lemonadeLabel.zPosition = 2.0
addChild(lemonadeLabel)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
if lemonadeLabel.containsPoint(location) {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
/*lemonadeLabel is now be removed,
however if I click the area where it
used to be, "lemonadeLabel pressed"
will print to the console*/
}
}
You are trying to determine if the constrainPoints' location are being touched. Even if you remove the label from the scene, it is still an object in memory, i.e: you could re-ad it later.. it still has all it's properties including position, etc..
I would try this instead:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
if nodeAtPoint(touch.locationInNode(self)) == lemonadeLabel {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
}
}
}
You basically determine if the lemonadeLabel is the node at that position, if yes you remove it. Since you compare with the added node in the scene, if it's gone, it will not be there for comparison ;)
Your labelNode may not be inside the SKScene anymore. This does not mean that it will not respond to the containsPoint function. The labelNode still has a position assigned to it and it can calculate if a point falls inside it using containsPoint function.
Instead you can try this.
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) === lemonadeLabel {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
}
}

Resources