How to remove a SKSpriteNode correctly - swift

I'm doing a small game in Swift 3 and SpriteKit. I want to do a collision with my character and a special object that increases my score in 1, but for some reason, when I detect the collision, the score increases in 2 or 3.
I'm removing from parent my SpriteKitNode but it seems that it doesn't work.
Here's my code:
func checkCollisionsObject(){
enumerateChildNodes(withName: "objeto") {node, _ in
let objeto = node as! SKSpriteNode
if objeto.frame.intersects(self.personaje.frame){
objeto.removeFromParent()
self.actualizarPoints()
//self.labelNivel.text = "Level: \(self.nivel)"
}
}
}
func actualizarPoints() {
self.pointsCounter += 1
points.text = "Points: \(pointsCounter)"
}

The problem is that the collision detection is happening at 60fps (pretty fast). So in that time multiple collision detections are occurring. You are just handling the first one.
I usually like to have a property on the object that I can trigger so that I know whether or not the object has collided, and set it so that it doesn't detect anymore collisions.
In your case the object is just a SKSpriteNode so you would have to set the property in userData or make the object a custom object and have the property in the custom object class
func checkCollisionsObject(){
enumerateChildNodes(withName: "objeto") {node, _ in
let objeto = node as! CustomObject
if objeto.frame.intersects(self.personaje.frame) && objeto.hasCollided == false {
objeto.hasCollided = true
objeto.removeFromParent()
self.actualizarPoints()
}
}
}
func actualizarPoints() {
self.pointsCounter += 1
points.text = "Points: \(pointsCounter)"
}

Related

remove nodes from scene after use ARSCNView

I am making an app using ARKit to measure between two points. The goal is to be able to measure length, store that value, then measure width and store.
The problem I am having is disposing of the nodes after I get the measurement.
Steps so far:
1) added a button with a restartFunction. this worked to reset the measurements but did not remove the spheres from scene, and also made getting the next measurement clunky.
2) set a limit on > 2 nodes. This functionaly works best. But again the spheres just stay floating in the scene.
Here is a screen shot of the best result I have had.
#objc func handleTap(sender: UITapGestureRecognizer) {
let tapLocation = sender.location(in: sceneView)
let hitTestResults = sceneView.hitTest(tapLocation, types: .featurePoint)
if let result = hitTestResults.first {
let position = SCNVector3.positionFrom(matrix: result.worldTransform)
let sphere = SphereNode(position: position)
sceneView.scene.rootNode.addChildNode(sphere)
let tail = nodes.last
nodes.append(sphere)
if tail != nil {
let distance = tail!.position.distance(to: sphere.position)
infoLabel.text = String(format: "Size: %.2f inches", distance)
if nodes.count > 2 {
nodes.removeAll()
}
} else {
nodes.append(sphere)
}
}
}
I am new to Swift (coding in general) and most of my code has come from piecing together tutorials.
I think the issue here is that your are not actually removing the SCNNodes you have added to hierarchy.
Although you are removing the nodes from what I assume is an array of SCNNodes by calling: nodes.removeAll(), you first need to actually remove them from the scene hierarchy.
So what you need to do is call the following function on any node you wish to remove:
removeFromParentNode()
Which simply:
Removes the node from its parent’s array of child nodes.
As such you would do something like this which first removes the nodes from the hierarchy, and then removes them from the array:
for nodeAdded in nodesArray{
nodeAdded.removeFromParentNode()
}
nodesArray.removeAll()
So based on the code provided you could do the following:
if nodes.count > 2 {
for nodeAdded in nodes{
nodeAdded.removeFromParentNode()
}
nodes.removeAll()
}
For future reference, if you want to remove all SCNNodes from you hierarchy you can also call:
self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (existingNode, _) in
existingNode.removeFromParentNode()
}
Whereby self.augmentedRealityView refers to the variable:
var augmentedRealityView: ARSCNView!
Here is a very basic working example based on (and modified from) the code you have provided:
/// Places A Marker Node At The Desired Tap Point
///
/// - Parameter sender: UITapGestureRecognizer
#objc func handleTap(_ sender: UITapGestureRecognizer) {
//1. Get The Current Tap Location
let currentTapLocation = sender.location(in: sceneView)
//2. Check We Have Hit A Feature Point
guard let hitTestResult = self.augmentedRealityView.hitTest(currentTapLocation, types: .featurePoint).first else { return }
//3. Get The World Position From The HitTest Result
let worldPosition = positionFromMatrix(hitTestResult.worldTransform)
//4. Create A Marker Node
createSphereNodeAt(worldPosition)
//5. If We Have Two Nodes Then Measure The Distance
if let distance = distanceBetweenNodes(){
print("Distance == \(distance)")
}
}
/// Creates A Marker Node
///
/// - Parameter position: SCNVector3
func createSphereNodeAt(_ position: SCNVector3){
//1. If We Have More Than 2 Nodes Remove Them All From The Array & Hierachy
if nodes.count >= 2{
nodes.forEach { (nodeToRemove) in
nodeToRemove.removeFromParentNode()
}
nodes.removeAll()
}
//2. Create A Marker Node With An SCNSphereGeometry & Add It To The Scene
let markerNode = SCNNode()
let markerGeometry = SCNSphere(radius: 0.01)
markerGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
markerNode.geometry = markerGeometry
markerNode.position = position
sceneView.scene.rootNode.addChildNode(markerNode)
//3. Add It To The Nodes Array
nodes.append(markerNode)
}
/// Converts A matrix_float4x4 To An SCNVector3
///
/// - Parameter matrix: matrix_float4x4
/// - Returns: SCNVector3
func positionFromMatrix(_ matrix: matrix_float4x4) -> SCNVector3{
return SCNVector3(matrix.columns.3.x, matrix.columns.3.y, matrix.columns.3.z)
}
/// Calculates The Distance Between 2 Nodes
///
/// - Returns: Float?
func distanceBetweenNodes() -> Float? {
guard let firstNode = nodes.first, let endNode = nodes.last else { return nil }
let startPoint = GLKVector3Make(firstNode.position.x, firstNode.position.y, firstNode.position.z)
let endPoint = GLKVector3Make(endNode.position.x, endNode.position.y, endNode.position.z)
let distance = GLKVector3Distance(startPoint, endPoint)
return distance
}
For an example of a measuringApp which might help your development you can look here: ARKit Measuring Example
Hope it helps...
This looks like a logic issue. You're assigning nodes.last to tail just before checking if tail is not nil. So it will never be != nil so you'll never execute the nodes.append(sphere) in the else.
I agree with #dfd. Set a breakpoint to make sure the code is being executed before continuing.

iOS Sprites Contact Detection

I'm working on a platform game where the character can climb ladders. Since the total length of each ladder varies, I have several ladders stacked on top of each other in SceneEditor. Upon entering a ladder, the contact delegate works fine and my code allows the character to move up the ladder. The problem I'm having is that as soon as the character moves off the first ladder segment, the didEnd method fires even though the character has entered the next ladder segment. I've gotten around it by given the ladder segments different category masks. Is this the only way to do it?
try overlapping the ladders by 1 rung, and then set a count whenever didBegin contact is fired increase the value by 1 whenever didEnd is called decrease the value by 1. at the end of the didEnd func check if onladderCount == 0. if it is, trigger whatever code is supposed to fire when the player is not on a ladder.
this assumes that you have a ladder class, and will have to put a property in the ladder class for onLadder to ensure that the += 1 isn't called multiple times.
var onladderCount: Int = 0
func didBegin(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if !ladder.onLadder {
ladder.onLadder = true
onladderCount += 1
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if ladder.onLadder {
ladder.onLadder = false
onladderCount -= 1
}
}
if onladderCount == 0 {
//trigger whatever code happens when they leave a ladder
}
}

Play a sound loop while at least one sprite of its class is on stage

I'm working on a game with some monsters of different kinds (one kind = one subclass of SKSpriteNode) crossing the scene.
To each kind is given a specific sound.
One example is I can have at a specific point in time:
5 monsters A
2 monsters B
0 monster C
What I would like at any time, is to loop a sound for each class which is part of the scene (A, B) (and not for each sprite !) , and to stop playing a sound for each class absent from the scene(C).
My first idea was to store a specific sound into each class of monster and to add a SKAction to each sprite that would play loop the sound of its class.
But this would play loop as many sounds as sprites on scene, and this doesn't match with my expectations.
Do you have an idea on the best approach to adopt for this ?
Is there a possibility to implement a kind of observer that would be able to notify when an instance of class is on the scene and when it is not ?
Many thanks for your help !
As #Knight0fDragon said, "SKAction is not designed to play looped music"
Maybe you can do: (I diden't test it, now I'm on windows machine)
Class MonsterA : SKSpriteNode {
static var monsterAInScene = Set<Monster>()
static let audioAction: SKAction = SKAction.playSoundFileNamed("MonsterA-audio") //Change to AVAudio for loop
static let audioNode: SKNode()
init (scene:SKScene) {
//Your init
if let MonsterA.monsterAInScene.count == 0 {
//play audio
scene.addChild(MonsterA.audioNode)
MonsterA.playAudio()
}
MonsterA.monsterAInScene.insert(self)
}
static func playAudio() {
//Play audio code
if MonsterA.audioNode.actionFroKey("audio") != nil {return}
MonsterA.audioNode.runaction(MonsterA.audioAction, forKey "audio")
}
override removeFromParent() {
MonsterA.monsterAInScene.remove(self)
if let MonsterA.monsterAInScene.count == 0 {
//stop audio code
MonsterA.audioNode.removeActionWithKey("audio")
MonsterA.audioNode.removeFromParent()
}
super.removeFromParent()
}
}
#Knight0fDragon and #Alessandro Ornano are more expert than me, they can suggest if is the best way to do.
I have finally adopted SKAudioNode as advised bu KnightOfDragon.
At the beginning of the GameScene class, I have added an observer to check the actual number of monster of each kind (Here with kind A):
var monsterASound = SKAudioNode()
var currentNumberOfMonsterA = 0 {
didSet {
if currentNumberOfMonsterA > 0 {
monsterASound.runAction(SKAction.play())
} else{
monsterASound.runAction(SKAction.stop())
}
}
}
currentNumberOfMonsterA is updated each time I add/remove a monster
//Add a monster A
self.currentNumberOfMonsterA += 1
//Removing a monster A
self.currentNumberOfMonsterA -= 1
//Destroying a monster A
self.currentNumberOfMonsterA -= 1
Then in the didMoveToView, I have added the SKAudioNode to the scene
self.monsterASound = SKAudioNode(URL: NSBundle.mainBundle().URLForResource("monsterA", withExtension: "wav")!)
self.monsterASound.runAction(SKAction.stop())
addChild(helicoSound)

GKMinmaxStrategist doesn't return any moves

I have the following code in my main.swift:
let strategist = GKMinmaxStrategist()
strategist.gameModel = position
strategist.maxLookAheadDepth = 1
strategist.randomSource = nil
let move = strategist.bestMoveForActivePlayer()
...where position is an instance of my GKGameModel subclass Position. After this code is run, move is nil. bestMoveForPlayer(position.activePlayer!) also results in nil (but position.activePlayer! results in a Player object).
However,
let moves = position.gameModelUpdatesForPlayer(position.activePlayer!)!
results in a non-empty array of possible moves. From Apple's documentation (about bestMoveForPlayer(_:)):
Returns nil if the player is invalid, the player is not a part of the game model, or the player has no valid moves available.
As far as I know, none of this is the case, but the function still returns nil. What could be going on here?
If it can be of any help, here's my implementation of the GKGameModel protocol:
var players: [GKGameModelPlayer]? = [Player.whitePlayer, Player.blackPlayer]
var activePlayer: GKGameModelPlayer? {
return playerToMove
}
func setGameModel(gameModel: GKGameModel) {
let position = gameModel as! Position
pieces = position.pieces
ply = position.ply
reloadLegalMoves()
}
func gameModelUpdatesForPlayer(thePlayer: GKGameModelPlayer) -> [GKGameModelUpdate]? {
let player = thePlayer as! Player
let moves = legalMoves(ofPlayer: player)
return moves.count > 0 ? moves : nil
}
func applyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
let move = gameModelUpdate as! Move
playMove(move)
}
func unapplyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
let move = gameModelUpdate as! Move
undoMove(move)
}
func scoreForPlayer(thePlayer: GKGameModelPlayer) -> Int {
let player = thePlayer as! Player
var score = 0
for (_, piece) in pieces {
score += piece.player == player ? 1 : -1
}
return score
}
func isLossForPlayer(thePlayer: GKGameModelPlayer) -> Bool {
let player = thePlayer as! Player
return legalMoves(ofPlayer: player).count == 0
}
func isWinForPlayer(thePlayer: GKGameModelPlayer) -> Bool {
let player = thePlayer as! Player
return isLossForPlayer(player.opponent)
}
func copyWithZone(zone: NSZone) -> AnyObject {
let copy = Position(withPieces: pieces.map({ $0.1 }), playerToMove: playerToMove)
copy.setGameModel(self)
return copy
}
If there's any other code I should show, let me know.
You need to change the activePlayer after apply or unapply a move.
In your case that would be playerToMove.
The player whose turn it is to perform an update to the game model. GKMinmaxStrategist assumes that the next call to the applyGameModelUpdate: method will perform a move on behalf of this player.
and, of course:
Function applyGameModelUpdate Applies a GKGameModelUpdate to the game model, potentially resulting in a new activePlayer. GKMinmaxStrategist will call this method on a copy of the primary game model to speculate about possible future moves and their effects. It is assumed that calling this method performs a move on behalf of the player identified by the activePlayer property.
func applyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
let move = gameModelUpdate as! Move
playMove(move)
//Here change the current Player
let player = playerToMove as! Player
playerToMove = player.opponent
}
The same goes for your unapplyGameModelUpdate implementation.
Also, keep special attention to your setGameModelimplementation as it should copy All data in your model. This includes activePlayer
Sets the data of another game model. All data should be copied over, and should not maintain any pointers to the copied game state. This is used by the GKMinmaxStrategist to process permutations of the game without needing to apply potentially destructive updates to the primary game model.
I had the same problem. Turns out, .activePlayer has to return one of the instances returned by .players. It's not enough to return a new instance with matching .playerId.
simple checklist:
GKMinmaxStrategist's .bestMove(for:) is called
GKMinmaxStrategist's .gameModel is set
GKGameModel's isWin(for:) does not return true before move
GKGameModel's isLoss(for:) does not return true before move
GKGameModel's gameModelUpdates(for:) does not return nil all the time
GKGameModel's score(for:) is implemented

Target Multiple Sprites Based on their Name?

The problem with this code is that it's only targeting 1 SKNode named "coin", I want to target all SKNodes named "coin". I'm not sure how to do this...
Heres what I have:
func killCoins(){
let skcoin: SKNode = self.childNodeWithName("coin")!
// Looks for a SKNode named "coin".
let coinsprite: SKSpriteNode = (skcoin as? SKSpriteNode)!
// Converts the SKNode into a SKSpriteNode
coinsprite.removeFromParent()
// Kills Sprite
}
To remove multiple SKSpriteNodes, loop through all of the children and remove those SKSpriteNodes with the name "coin".
func killCoins() {
for child in self.children where child is SKSpriteNode {
if child.name == "coin" {
child.removeFromParent()
}
}
}
As an addition to vacawama's answer, you can use enumerateChildNodesWithName:usingBlock: for more advanced searches:
self.enumerateChildNodesWithName("coin") {
node, stop in
// do something with node or stop
}
From the docs:
This method enumerates the child array in order, searching for nodes
whose names match the search parameter. The block is called once for
each node that matches the name parameter.
Solution 1
You can look for all the children SKSpriteNode(s) having coin as name. The following code is totally Functiona Programming oriented
func killCoins() {
self
.children
.filter { $0 is SKSpriteNode && $0.name == "coin" }
.forEach { $0.removeFromParent() }
}
The time of this operation is O(n) where n is the total number of children of the current node/scene.
We can do better.
Solution 2
Just create a special node that will be the parent of all the coins
override func didMoveToView(view: SKView) {
let coins = SKNode()
coins.name = "coins"
self.addChild(coins)
let coin0 = SKSpriteNode(imageNamed: "coin")
coins.addChild(coin0)
let coin1 = SKSpriteNode(imageNamed: "coin")
coins.addChild(coin1)
}
Now the killCoins becomes
func killCoins() {
self.childNodeWithName("coins")?.removeAllChildren()
}
The time complexity of this operation is O(m) where m is the number of the coins.