"Attemped to add a SKNode which already has a parent" error when trying to spawn a node - ios

The error that I keep getting is this one: Attemped to add a SKNode which already has a parent. I have tried to take out the spawn function and it works without it. Can someone please help me ?
func spawnTrolls(){
let minValue = self.size.width / 10
let maxValue = self.size.width - 30
let spawnpoint = UInt32(maxValue - minValue)
Troll.position = CGPointMake(CGFloat(arc4random_uniform(spawnpoint)), self.size.height)
self.addChild(Troll)
let action = SKAction.moveToY(-70, duration: 2)
let actionDone = SKAction.removeFromParent()
Troll.runAction(SKAction.sequence([action, actionDone]))
Troll.physicsBody? = SKPhysicsBody(rectangleOfSize: Troll.size)
Troll.physicsBody?.categoryBitMask = physicsCategory.Troll
Troll.physicsBody?.contactTestBitMask = physicsCategory.Rocket
Troll.physicsBody?.affectedByGravity = false
Troll.physicsBody?.dynamic = true
}
_ = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("spawnTrolls"), userInfo: nil, repeats: true)

Related

"lldb" crash with XCODE & ARKIT : 0 __ UpdateAudioTransform

I have written a game using ARKIT that pops up random nodes (with drones) in the 3d space around the player, using ARKIT, and I get an incomprehensible crash, as only thing in the console is "lldb" , and the rest of the crash details are on the screenshot attached (I am still a newbie in Swift so not able to debug it).
The crash happens when there's a lot of nodes on the screen (maybe 20-30) and the FPS drops - ie happens "mid - game" and the FPS drops a lot.
Can someone point me to the right direction for this crash?
The part of the code that is in my opinion relevant is the function that spawns the random 3d nodes (they also have SCNActions attached that play sounds when they're tapped - perhaps this could be relevant as the left hand side debugger opens with that line highlighted when the crash occurs, as per the photo attached). In case it is also relevant, the program uses some SCNParticleSystem calls as well. Attaching relevant code, and also the snapshot of the crash screen:
var droneSound : SCNAudioSource()
override func viewDidLoad() {
super.viewDidLoad()
droneSound = SCNAudioSource(named: "Sounds/drone1.wav")!
playDroneSound = SCNAction.playAudio(self.droneSound, waitForCompletion: true)
}
func startGame() {
DispatchQueue.main.async {
self.spawnTimer = Timer.scheduledTimer(timeInterval: TimeInterval(self.randomFloat(min: 2.5, max: 5)), target: self, selector: #selector(self.spawnDronesInterim), userInfo: nil, repeats: true)
}
}
#objc func spawnDronesInterim() {
for _ in 0...5 {
spawnDrone()
}
}
#objc func spawnDrone() {
let newDroneScene = SCNScene(named: "Ar.scnassets/DroneScene.scn")!
var newDroneNode = newDroneScene.rootNode.childNode(withName: "Drone", recursively: false)!
newDroneNode.name = "Drone\(self.droneCounter)"
newDroneNode = newDroneScene.rootNode.childNode(withName: "Drone\(self.droneCounter)", recursively: false)!
newDroneNode.position.x = newDroneNode.presentation.position.x + self.randomFloat(min: -10, max: 10)
newDroneNode.position.y = newDroneNode.presentation.position.y + self.randomFloat(min: -1, max: 5)
newDroneNode.position.z = newDroneNode.presentation.position.z + self.randomFloat(min: -10, max: 10)
let move1 = SCNAction.move(to: SCNVector3((self.randomFloat(min: -7, max: 7)), (self.randomFloat(min: 1, max: 5)), (self.randomFloat(min: -7, max: 7))), duration: 15)
let disappearMove = SCNAction.move(to: SCNVector3((self.randomFloat(min: -10, max: 10)), (self.randomFloat(min: 1, max: 5)), (self.randomFloat(min: -10, max: 10))), duration: 3)
let rotateAction = SCNAction.run { (SCNNode) in
let rotate = SCNAction.rotateBy(x: 0, y: CGFloat(360.degreesToRadians), z: 0, duration: 2)
newDroneNode.runAction(rotate)
}
let removeIt = SCNAction.removeFromParentNode()
let sequence = SCNAction.sequence([waitFive,move1,rotateAction,waitFive,disappearMove,waitTwo,removeIt])
newDroneNode.runAction(sequence)
self.sceneView.scene.rootNode.addChildNode(newDroneNode)
if self.droneCounter >= 5 {
self.sceneView.scene.rootNode.childNode(withName: "Drone\(self.droneCounter)", recursively: true)!.runAction(SCNAction.repeatForever(self.playDroneSound))
}
self.droneCounter += 1
}
when user taps on one of the 3d nodes, this gets called:
func handleExplosion (node : SCNNode) {
self.sceneView.scene.rootNode.childNode(withName: node.name!, recursively: true)!.runAction(self.playExplosionSound)
node.opacity = 0
print (self.sceneView.scene.rootNode.position)
let confetti = SCNParticleSystem(named: "Ar.scnassets/confetti.scnp", inDirectory: nil)
confetti?.loops = false
confetti?.particleLifeSpan = 1.5
confetti?.emitterShape = node.geometry
let confettiNode = SCNNode()
confettiNode.addParticleSystem(confetti!)
confettiNode.position = node.presentation.position
self.sceneView.scene.rootNode.addChildNode(confettiNode)
let fire = SCNParticleSystem(named: "Ar.scnassets/fire", inDirectory: nil)
fire?.loops = false
fire?.particleLifeSpan = 0.1
fire?.emitterShape = node.geometry
let fireNode = SCNNode()
fireNode.addParticleSystem(fire!)
fireNode.position = node.presentation.position
self.sceneView.scene.rootNode.addChildNode(fireNode)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
node.removeFromParentNode()
self.killedLabel.text = "Killed: \(self.killedCount)"
self.dronesOut -= 1
}
}
Any pointer to the right direction for solving this crash would be greatly appreciated]1

how to add a delay when you tap when firing a bullet on my game

i need help on this issue, in my game when a node is tapped a bullet is fired, but the problem is that i can tap continually and a lot of bullets fire, i would like to add some kind of delay to the shot.
here is my touches began code
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in:self)
if player1.contains(pointOfTouch) {
fireBullet1()
}
if player2.contains(pointOfTouch) {
fireBullet2()
}
if player3.contains(pointOfTouch) {
fireBullet3()
}
}
}
}
func fireBullet1() {
let bullet = SKSpriteNode(imageNamed: "b")
bullet.position = player1.position
bullet.zPosition = 1
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
func fireBullet2 () {
let bullet2 = SKSpriteNode(imageNamed: "b")
bullet2.position = player2.position
bullet2.zPosition = 1
self.addChild(bullet2)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet2.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet2.run(bulletSequence)
}
func fireBullet3() {
let bullet3 = SKSpriteNode(imageNamed: "b")
bullet3.position = player3.position
bullet3.zPosition = 1
self.addChild(bullet3)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet3.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet3.run(bulletSequence)
}
Declare flags to disable multiple firing immediatly
let minFireDelay = 0.5
var allowsFire1 = true
var allowsFire2 = true
var allowsFire3 = true
Update touches began
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in:self)
if allowsFire1 && player1.contains(pointOfTouch) {
fireBullet1()
// disable firing temporarily
allowsFire1 = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + minFireDelay) {
allowsFire1 = true
}
}
if allowsFire2 && player2.contains(pointOfTouch) {
fireBullet2()
// disable firing temporarily
allowsFire2 = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + minFireDelay) {
allowsFire2 = true
}
}
if allowsFire3 && player3.contains(pointOfTouch) {
fireBullet3()
// disable firing temporarily
allowsFire3 = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + minFireDelay) {
allowsFire3 = true
}
}
}
Look up Timer (NSTimer in Swift 2 and Objective-C.)
The idea is as follows:
Have a gunXEnabled Bool for each player's gun. Set each Bool to true initially.
Have your fireBullet1() method check gun1Enabled. If false, do nothing.
If gun1Enabled == true, fire the gun, set gun1Enabled = false, and start a timer that will re-enable the gun once it fires:
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) {
(timer) -> Void) in
gun1Enabled = true
}
I gave you the unfamiliar part, creating the timer. See if you can work out the rest, and if not, post your code with info about what's not working.

timeInterval Variable won't work

I'm trying to change the timeInterval in a scheduledTimer. I'm trying to do this by changing a variable to the interval and than setting the timeInterval to this variable. I don't get any errors but the timeInterval won't change. Can someone help me?
var enemyTimer = Timer()
var playTime = 0
var enemySpawnTime: Double = 3
enemyTimer = Timer.scheduledTimer(timeInterval: Double(enemySpawnTime), target: self, selector: #selector(GameScene.enemySpawn), userInfo: nil, repeats: true)
playTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(GameScene.ingameTimer), userInfo: nil, repeats: true)
func enemySpawn() {
let enemy = SKSpriteNode(imageNamed: "Enemy")
let minValue = self.size.width / 8
let maxValue = self.size.width - 20
let spawnPoint = UInt32(maxValue - minValue)
enemy.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)), y: self.size.height)
let action = SKAction.moveTo(y: -70, duration: 5)
enemy.run(action)
self.addChild(enemy)
}
func ingameTimer() {
playTime += 1
if(playTime >= 10 && playTime < 30){
enemySpawnTime = 2
print(enemySpawnTime)
}else
if(playTime >= 30 && playTime < 60){
enemySpawnTime = 1
print(enemySpawnTime)
}else
if(playTime >= 60 && playTime < 120){
enemySpawnTime = 0.75
print(enemySpawnTime)
}else
if(playTime >= 120 && playTime < 180){
enemySpawnTime = 0.5
print(enemySpawnTime)
}else
if(playTime >= 180 && playTime < 240){
enemySpawnTime = 0.25
print(enemySpawnTime)
}
}
I hope someone can help me!
Thanks!
The reason why your code doesn't work is because the Timer object doesn't know that its interval needs to be in sync with your enemySpawnTime. The solution is simple, just recreate the timer when you change the enemy spawn time.
But...
You should NEVER use Timer (NSTimer prior to Swift 3) or GCD to delay stuff when you're using SpriteKit. See this for more details.
The correct way to do this is to create a sequence of SKActions.
Assuming self is a subclass of SKScene, you can do this:
override func didMove(to view: SKView) {
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
}
where somePlaceholderNode is just a node that does nothing but run the action. I'll explain this later.
And you should do this for the other timer as well.
Now whenever you change the timer interval, also do this:
somePlaceholderNode.removeAllActions()
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
Here I first remove the action that the node was running, and tell it to run almost the same action, but with a different time interval. You can add this block of code to the didSet block of enemySpawnTime:
var enemySpawnTime: Double = 3 {
didSet {
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
}
}
Now your code should work!
The reason why we want a placeholder node here is because when we remove the action by calling removeAllActions, we don't want to remove all the actions that is running.

Declaring SKSpriteNode outside spawn function returns SIGABRT in Swift

I have a game that I am making where I have a player and an unlimited number of enemies that's spawn at a given position in a certain amount of time. These enemies are shooting bullets at the player. I need to be able to access the Enemy variable outside the SpawnEnemies() function so that I can use it in my SpawnBullets() function. I tried declaring the Enemy variable outside the SpawnEnemies() function but it returned Sigabrt and I don't know how to access the Enemy variable outside the function without getting this error.
Enemy declaration:
var Enemy = SKSpriteNode(imageNamed: "Enemy.png")
SpawnEnemies function:
func SpawnEnemies() {
let MinValue = self.size.width/8
let MaxValue = self.size.width-20
let SpawnPoint = UInt32(MaxValue-MinValue)
self.Enemy.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
let action = SKAction.moveToY(-70, duration: 3.0)
self.Enemy.runAction(SKAction.repeatActionForever(action))
self.addChild(Enemy)
}
SpawnBullets function:
func SpawnBullets(){
let Bullet = SKSpriteNode(imageNamed: "bullet.png")
Bullet.zPosition = -5
Bullet.position = CGPointMake(Enemy.position.x, Enemy.position.y)
let action = SKAction.moveToY(self.size.height + 30, duration: 1.0)
Bullet.runAction(SKAction.repeatActionForever(action))
self.addChild(Bullet)
}
Call SpawnEnemies function in didMoveToView():
var EnemyTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("SpawnEnemies"),userInfo: nil, repeats: true)
Call SpawnBullets function in didMoveToView():
var BulletTimer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("SpawnBullets"),userInfo: nil, repeats: true)
I get this error which is a sigabrt but I used a breakpoint to figure out exactly where the error was and what it was:
Attemped to add a SKNode which already has a parent: <SKSpriteNode> name:'(null)' texture:[<SKTexture> 'Enemy.png' (60 x 80)] position:{132, 1024} scale:{1.00, 1.00} size:{60, 80} anchor:{0.5, 0.5} rotation:0.00
Adding children to a scene already is "infinite", you just need to use it to your advantage.
First rework your bullet function like this so that you are passing in an enemy.
func SpawnBullets(enemy : SKSpriteNode){
let Bullet = SKSpriteNode(imageNamed: "bullet.png")
Bullet.zPosition = -5
Bullet.position = CGPointMake(enemy.position.x, enemy.position.y)
let action = SKAction.moveToY(-self.size.height - 70, duration: 1.0)
Bullet.runAction(SKAction.repeatActionForever(action))
self.addChild(Bullet)
}
Then rework your enemy function so that overtime you call it you spawn a new enemy.
func SpawnEnemies() {
var Enemy = SKSpriteNode(imageNamed: "Enemy.png")
Enemy.name = "enemy";
let MinValue = self.size.width/8
let MaxValue = self.size.width-20
let SpawnPoint = UInt32(MaxValue-MinValue)
Enemy.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
let action = SKAction.moveToY(-70, duration: 3.0)
Enemy.runAction(SKAction.repeatActionForever(action))
self.addChild(Enemy)
}
In the function didMoveToView, lets add actions to the scene that handles our timing so that we do not use NSTimer
{
...
let spawnEnemy = SKAction.sequence([SKAction.runBlock(
{
[unowned self] in
self.SpawnEnemies();
}),SKAction.waitForDuration(1)]);
let spawnBullet = SKAction.sequence([SKAction.runBlock(
{
[unowned self] in
self.enumerateChildNodesWithName("enemy", usingBlock:
{
(enemy : SKNode,stop: UnsafeMutablePointer <ObjCBool>) in
  self.SpawnBullets(enemy as! SKSpriteNode);
});
}),SKAction.waitForDuration(0.2)]);
let group = SKAction.group([spawnEnemy, spawnBullet])
self.runAction(SKAction.repeatActionForever(group))
}
This code is not tested so let me know if I need to update anything.

Unexpectedly found nil while unwrapping an Optional value - boxNode.addChildNode(importedBox!)

I have some code that runs a for loop to create 2 objects and stuffs them into the boxArray then adds them to the scene. The first time through the code works fine to create one box but during the addBox(num: Int) function to create the second box it crashes with the error in the subject line. Its this line that is responsible "boxNode.addChildNode(importedBox!)" but Im at a loss as to why since it works the first time.
importedSceneBox = SCNScene(named: "art.scnassets/boxObject.dae")
for var i = 0; i <= 1; ++i {
let boxNode = self.addBox(i)
boxArray.append(boxNode)
theScene.rootNode.addChildNode(boxArray[i])
}
func addBox(num: Int) -> SCNNode{
let boxNode = SCNNode()
let importedBox = importedSceneBox.rootNode.childNodeWithName("pCube4", recursively: false)
importedBox?.scale = SCNVector3Make(70, 70, 70)
boxNode.addChildNode(importedBox!)
boxNode.position = SCNVector3Make(5, 100, 3)
let collisionBox = SCNBox(width: 5.0, height: 5.0, length: 5.0, chamferRadius: 0)
boxNode.physicsBody?.physicsShape = SCNPhysicsShape(geometry: collisionBox, options: nil)
boxNode.physicsBody = SCNPhysicsBody.dynamicBody()
boxNode.physicsBody?.mass = 1
boxNode.physicsBody?.restitution = 0.8
boxNode.physicsBody?.damping = 0.5
boxNode.name = "dice" + String(num)
boxNode.physicsBody?.allowsResting = true
return boxNode
}
hi your importedBox is an optional meaning it can have a value or it can be nil.
you are using "!"(IUO implicitly unwrapped optional) behind importedBox which means that your telling the compiler that your sure its not be nil. How ever in this case it WAS nil thus the crash "found nil when unwrapping optional"
So recap importedBox is nil but you marked it with ! so the compiler think it will never have a nil value but a nil was found thus the crash.
to fix this you can use guard or optional binding
Guard example
func addBox(num: Int) -> SCNNode{
let boxNode = SCNNode()
let importedBox = importedSceneBox.rootNode.childNodeWithName("pCube4", recursively: false)
importedBox?.scale = SCNVector3Make(70, 70, 70)
guard let unwrapBox = importedBox else {
//do your error handling here
return SCNNode()
}
boxNode.addChildNode(unwrapBox)
boxNode.position = SCNVector3Make(5, 100, 3)
let collisionBox = SCNBox(width: 5.0, height: 5.0, length: 5.0, chamferRadius: 0)
boxNode.physicsBody?.physicsShape = SCNPhysicsShape(geometry: collisionBox, options: nil)
boxNode.physicsBody = SCNPhysicsBody.dynamicBody()
boxNode.physicsBody?.mass = 1
boxNode.physicsBody?.restitution = 0.8
boxNode.physicsBody?.damping = 0.5
boxNode.name = "dice" + String(num)
boxNode.physicsBody?.allowsResting = true
return boxNode
}
optional binding example
func addBox(num: Int) -> SCNNode{
let boxNode = SCNNode()
let importedBox = importedSceneBox.rootNode.childNodeWithName("pCube4", recursively: false)
importedBox?.scale = SCNVector3Make(70, 70, 70)
if let unwrapBox = importedBox else {
boxNode.addChildNode(unwrapBox)
boxNode.position = SCNVector3Make(5, 100, 3)
let collisionBox = SCNBox(width: 5.0, height: 5.0, length: 5.0, chamferRadius: 0)
boxNode.physicsBody?.physicsShape = SCNPhysicsShape(geometry: collisionBox, options: nil)
boxNode.physicsBody = SCNPhysicsBody.dynamicBody()
boxNode.physicsBody?.mass = 1
boxNode.physicsBody?.restitution = 0.8
boxNode.physicsBody?.damping = 0.5
boxNode.name = "dice" + String(num)
boxNode.physicsBody?.allowsResting = true
return boxNode
}else{
//do your error handling here
return SCNNode()
}
}
also as a side note in the error handling cases we are creating a default value since your function returns a SCNNode and not a SCNNode optional.
Alternatively you can return nil if you change your function signature to be
func addBox(num: Int) -> SCNNode? {
if let unwrappedBox = importedBox {
//do stuff
}else{
return nil
}
}

Resources