PhysicsBody is nil using SpriteKit with Swift - ios
I have a SpriteKit game I am building and I am loading a level from a multidimensional array. The loadlevel function works the first time. It does fail if I do a println of the physicsBody above the physicsBody assignment (after the initialization of the physicBody). When I remove all the tiles with removeChildrenInArray the secondtime I run load level it throws an error saying fatal error: unexpectedly found nil while unwrapping an Optional and it points to line right below the println below. And the println indicates that it is the physicsBody that is nil. In my mind there is no reason a freshly initialize PhysicsBody should ever be nil. The println prints physicsBody nil. I have no idea why the physicsBody would be nil. I am just trying to reset the level by removing all block nodes and adding new ones in the original place according to the level map.
func loadLevel() {
var levels = Levels().data
var frameSize = view.frame.size
var thisLevel = levels[currentLevel]
println("running load level")
for (rowIndex,row) in enumerate(thisLevel) {
for (colIndex,col) in enumerate(row) {
if col == 4 {
continue
}
println("COL: \(col)")
var tile = SKSpriteNode(texture: SKTexture(imageNamed: "brick_\(tileMap[col])"))
tile.name = "tile_\(rowIndex)_\(colIndex)"
tile.position.y = frameSize.height - (tile.size.height * CGFloat(rowIndex)) - (tile.size.height/2)
tile.position.x = tile.size.width * CGFloat(colIndex) + (tile.size.width/2)
var physicsBody = SKPhysicsBody(rectangleOfSize: tile.size)
tile.physicsBody = physicsBody
tile.physicsBody.affectedByGravity = false
tile.physicsBody.categoryBitMask = ColliderType.Block.toRaw()
tile.physicsBody.contactTestBitMask = ColliderType.Ball.toRaw()
tile.physicsBody.collisionBitMask = ColliderType.Ball.toRaw()
scene.addChild(tile)
tileCount++
}
}
}
Here is my ColliderType
enum ColliderType:UInt32 {
case Paddle = 1
case Block = 2
case Wall = 3
case Ball = 4
}
This is my reset function contents:
func reset() {
tileCount = 0
var removeTiles = [SKSpriteNode]()
// remove all the tiles
for child in scene.children {
var a_tile = child as SKSpriteNode
if a_tile.name.hasPrefix("tile_") {
a_tile.removeFromParent()
a_tile.name = ""
removeTiles.append(a_tile)
}
}
removeTiles.removeAll(keepCapacity: false)
ball!.position = CGPoint(x: 200, y: 200)
ballVel = CGPoint(x: 0, y: -5)
currentLevel++
loadLevel()
lost = false
won = false
}
Here is my Level structs
struct Tile {
let map = ["blue","green","purple","red"]
}
struct Levels {
let data = [
[
[4,4,0,4,0,4,0,4,0,4,0,4,0,0,4,4],
[4,4,1,4,1,4,1,4,1,4,1,4,1,1,4,4],
[4,4,2,4,2,4,2,4,2,4,2,4,2,2,4,4],
[4,4,3,4,3,4,3,4,3,4,3,4,3,3,4,4]
],
[
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2],
[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]
]
]
}
If this is a bug in Swift I am trying to figure out a way around so I can just make this work.
Looks like SKPhysicsBody is instantiated with an empty size. Try to create the physics body object with an explicit size, like so:
var physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(100, 100))
Alternatively, you can set the size directly on the SKSpriteNode or use one of its constructors that take a CGSize construct like initWithTexture:color:size:
Related
detecting if a spritenode exists in swift
I am trying to detect if one of the nodes that I have made through a subclass of SKNode (called Achievements) exists and if it doesn't exist then i'm trying to turn off a boolean variable. What I use to locate the SKShapeNode (called "Indicator") func checkIndicatorStatus() { moveableArea.enumerateChildNodes(withName: "Achievement") { (node, stop) in let Indicate = node Indicate.enumerateChildNodes(withName: "Indicator") { node, stop in if let Achievement = node as? Achievements { menuAchieveNotificationOn = false } } } } I have enumerated through the nodes specifically and tried searching for it but it doesn't seem to do anything. what am I doing wrong? Here is my subclass. I have many of them named achievement displayed in my scene. class Achievements: SKNode { //Nodes used throughout the SKNode class var achievementLabel = SKLabelNode() var achievementTitleLabel = SKLabelNode() var achievementNode = SKSpriteNode() var notifyCircle = SKShapeNode() //Amount Variables used as Achievement Properties var image: String = "" var information: String = "" var title: String = "" var amount = 0 var neededAmount = 0 var notification:Bool = false var stage = 0 func getachievementData(AchName: String) { let getDataRequest:NSFetchRequest<Achievement> = Achievement.fetchRequest() getDataRequest.predicate = NSPredicate(format: "theSearchName == %#" , AchName) do { let searchResults = try CoreDatabaseContoller.getContext().fetch(getDataRequest) //print("number of results: \(searchResults.count)") for result in searchResults as [Achievement] { title = result.theName! information = result.theDescription! image = result.theImage! amount = Int(result.aAmount!) neededAmount = Int(result.aNeededAmount!) stage = Int(result.aStage!) if result.aHasBeenAchieved!.intValue == 1 { notification = true } } } catch { print("ERROR: \(error)") } createAchievement() } func createAchievement() { let tex:SKTexture = SKTexture(imageNamed: image) achievementNode = SKSpriteNode(texture: tex, color: SKColor.black, size: CGSize(width: 85, height: 85)) //frame.maxX / 20, height: frame.maxY / 20)) achievementNode.zPosition = -10 achievementSprites.append(achievementNode) self.name = "Achievement" self.addChild(achievementNode) self.zPosition = -11 createAchievementLabels() } func createAchievementLabels() { achievementTitleLabel = SKLabelNode(fontNamed: "Avenir-Black") achievementTitleLabel.fontColor = UIColor.black; achievementTitleLabel.fontSize = 15 //self.frame.maxY/30 achievementTitleLabel.position = CGPoint (x: 0, y: 50) achievementTitleLabel.text = "\(title)" achievementTitleLabel.zPosition = -9 addChild(achievementTitleLabel) achievementLabel = SKLabelNode(fontNamed: "Avenir-Black") achievementLabel.fontColor = UIColor.black; achievementLabel.fontSize = 13 //self.frame.maxY/30 achievementLabel.position = CGPoint (x: 0, y: -55) achievementLabel.text = ("\(amount) / \(neededAmount)") achievementLabel.zPosition = -9 addChild(achievementLabel) if notification == true { notifyCircle = SKShapeNode(circleOfRadius: 10) notifyCircle.fillColor = .red notifyCircle.position = CGPoint(x: 30 , y: 35) notifyCircle.zPosition = 1000 notifyCircle.name = "Indicator" addChild(notifyCircle) } } } EDIT 1 As you can see from the image below, there are a number of different achievement nodes each with their individual names and unlock criteria, when an achievement becomes unlocked it makes an indicator and changes the graphic, which is seen for the two X nodes here with the coloured red circle in the top right corners of each of them which is the "indicator" (if you look at the subclass at the bottom of it the creation of the indicator is there) Now as you can seen the big red button in the bottom right hand corner of the picture is the achievement menu button which also has an indicator which is controlled by the bool variable (menuAchieveNotificationOn) what i'm trying to achieve is once the achievements with the indicators have each been pressed they are removed from the node. what i'm trying to do is search each of the nodes to see if the indicator still exists if not I want to turn the variable (menuAchieveNotificationOn) to false.
You should be able to use this: if let indicatorNode = childNode(withName: "//Indicator") as! SKShapeNode? { menuAchieveNotificationOn = false } else { menuAchieveNotificationOn = true } EDIT: to run some code for EVERY "indicator" node in the scene. If any are found, achievementsFound is set to true: achievementsFound = false enumerateChildNodes(withName: "//Indicator") { indicatorNode, _ in // Do something with indicatorNode achievementsFound = true } Although this seems too simple, so I might have misunderstood your aim.
SpriteKit reference nodes from level editor
I'm using the scene editor in SpriteKit to place color sprites and assign them textures using the Attributes Inspector. My problem is trying to figure out how to reference those sprites from my GameScene file. For example, I'd like to know when a sprite is a certain distance from my main character. Edit - code added I'm adding the code because for some reason, appzYourLife's answer worked great in a simple test project, but not in my code. I was able to use Ron Myschuk's answer which I also included in the code below for reference. (Though, as I look at it now I think the array of tuples was overkill on my part.) As you can see, I have a Satellite class with some simple animations. There's a LevelManager class that replaces the nodes from the scene editor with the correct objects. And finally, everything gets added to the world node in GameScene.swift. Satellite Class func spawn(parentNode:SKNode, position: CGPoint, size: CGSize = CGSize(width: 50, height: 50)) { parentNode.addChild(self) createAnimations() self.size = size self.position = position self.name = "satellite" self.runAction(satAnimation) self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2) self.physicsBody?.affectedByGravity = false self.physicsBody?.categoryBitMask = PhysicsCategory.satellite.rawValue self.physicsBody?.contactTestBitMask = PhysicsCategory.laser.rawValue self.physicsBody?.collisionBitMask = 0 } func createAnimations() { let flyFrames:[SKTexture] = [textureAtlas.textureNamed("sat1.png"), textureAtlas.textureNamed("sat2.png")] let flyAction = SKAction.animateWithTextures(flyFrames, timePerFrame: 0.14) satAnimation = SKAction.repeatActionForever(flyAction) let warningFrames:[SKTexture] = [textureAtlas.textureNamed("sat8.png"), textureAtlas.textureNamed("sat1.png")] let warningAction = SKAction.animateWithTextures(warningFrames, timePerFrame: 0.14) warningAnimation = SKAction.repeatActionForever(warningAction) } func warning() { self.runAction(warningAnimation) } Level Manager Class import SpriteKit class LevelManager { let levelNames:[String] = ["Level1"] var levels:[SKNode] = [] init() { for levelFileName in levelNames { let level = SKNode() if let levelScene = SKScene(fileNamed: levelFileName) { for node in levelScene.children { switch node.name! { case "satellite": let satellite = Satellite() satellite.spawn(level, position: node.position) default: print("Name error: \(node.name)") } } } levels.append(level) } } func addLevelsToWorld(world: SKNode) { for index in 0...levels.count - 1 { levels[index].position = CGPoint(x: -2000, y: index * 1000) world.addChild(levels[index]) } } } GameScene.swift - didMoveToView world = SKNode() world.name = "world" addChild(world) physicsWorld.contactDelegate = self levelManager.addLevelsToWorld(self.world) levelManager.levels[0].position = CGPoint(x:0, y: 0) //This does not find the satellite nodes let satellites = children.flatMap { $0 as? Satellite } //This does work self.enumerateChildNodesWithName("//*") { node, stop in if (node.name == "satellite") { self.satTuple.0 = node.position self.satTuple.1 = (node as? SKSpriteNode)! self.currentSatellite.append(self.satTuple) } }
The Obstacle class First of all you should create an Obstacle class like this. class Obstacle: SKSpriteNode { } Now into the scene editor associate the Obstacle class to your obstacles images The Player class Do the same for Player, create a class class Player: SKSpriteNode { } and associate it to your player sprite. Checking for collisions Now into GameScene.swift change the updated method like this override func update(currentTime: CFTimeInterval) { /* Called before each frame is rendered */ let obstacles = children.flatMap { $0 as? Obstacle } let player = childNodeWithName("player") as! Player let obstacleNearSprite = obstacles.contains { (obstacle) -> Bool in let distance = hypotf(Float(player.position.x) - Float(obstacle.position.x), Float(player.position.y) - Float(obstacle.position.y)) return distance < 100 } if obstacleNearSprite { print("Oh boy!") } } What does it do? The first line retrieves all your obstacles into the scene. the second line retrieves the player (and does crash if it's not present). Next it put into the obstacleNearSprite constant the true value if there is at least one Obstacle at no more then 100 points from Player. And finally use the obstacleNearSprite to print something. Optimizations The updated method gets called 60 times per second. We put these 2 lines into it let obstacles = children.flatMap { $0 as? Obstacle } let player = childNodeWithName("player") as! Player in order to retrieves the sprites we need. With the modern hardware it is not a problem but you should save references to Obstacle and Player instead then searching for them in every frame. Build a nice game ;)
you will have to loop through the children of the scene and assign them to local objects to use in your code assuming your objects in your SKS file were named Obstacle1, Obstacle2, Obstacle3 Once in local objects you can check and do whatever you want with them let obstacle1 = SKSpriteNode() let obstacle2 = SKSpriteNode() let obstacle3 = SKSpriteNode() let obstacle3Location = CGPointZero func setUpScene() { self.enumerateChildNodesWithName("//*") { node, stop in if (node.name == "Obstacle1") { self.obstacle1 = node } else if (node.name == "Obstacle2") { self.obstacle2 = node } else if (node.name == "Obstacle3") { self.obstacle3Location = node.position } } }
using flatMap to enumerate SpriteKit nodes
I tried using the following to get all nodes of a certain class: let enemies = children.flatMap { $0 as? SomeEnemyClass } The result is an empty array. I would like to understand why I'm not getting any results. enumerateChildNodesWithName works, but it doesn't list the parent/child relationships which I thought might shed some light. Thanks! Here is the code: There is a simple class for the enemy, a level manager that replaces nodes in the scene editor with enemy objects, and GameScene.swift. Everything gets assigned to a world node. Enemy Class class Enemy: SKSpriteNode { func spawn(parentNode:SKNode, position: CGPoint, size: CGSize = CGSize(width: 50, height: 50)) { parentNode.addChild(self) self.size = size self.position = position self.name = "enemy" self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2) self.physicsBody?.affectedByGravity = false self.physicsBody?.categoryBitMask = PhysicsCategory.enemy.rawValue self.physicsBody?.contactTestBitMask = PhysicsCategory.hero.rawValue self.physicsBody?.collisionBitMask = 0 } } Level Manager class LevelManager { let levelNames:[String] = ["Level1"] var levels:[SKNode] = [] init() { for levelFileName in levelNames { let level = SKNode() if let levelScene = SKScene(fileNamed: levelFileName) { for node in levelScene.children { switch node.name! { case "enemy": let enemy = Enemy() enemy.spawn(level, position: node.position) default: print("Name error: \(node.name)") } } levels.append(level) } } func addLevelsToWorld(world: SKNode) { for index in 0...levels.count - 1 { levels[index].position = CGPoint(x: -2000, y: index * 1000) world.addChild(levels[index]) } } } GameScene.swift - didMoveToView world = SKNode() world.name = "world" addChild(world) levelManager.addLevelsToWorld(self.world) levelManager.levels[0].position = CGPoint(x:0, y: 0)
map and flatMap transform one array into another, what you are really trying to do is just filtering your child nodes for enemies. Use filter instead. let enemines = children.filter { $0 is SomeEnemyClass } That said, I don't see any reason why your sample code would not work. Here is an example of this code working. This code will only work if you have real classes derived from SKNode. It is much more common to use node name strings to identify types of nodes. This would work if you had a constant string name for your enemies. let enemies = children.filter { $0.name == "SomeEnemy" }
You said let enemies = children.flatMap { $0 as? SomeEnemyClass } The result is an empty array. I would like to understand why I'm not getting any results. enumerateChildNodesWithName works First of all this code let enemies = children.flatMap { $0 as? Enemy } is correct and it does returns an array of Enemy. Specifically all objects inside children that are Enemy or subclasses of Enemy. Look Why is not working for you? You said that enumerateChildNodesWithName si working for you. This makes me think that you don't have Enemy objects into children. You just have SKNode(s) you assigned the name enemy.
What I learned was that the node tree is really a tree and you have to start looking on the right branch to get results. The answer to my question is: let enemies = childNodeWithName("//level")!.children.flatMap { ($0 as? SomeEnemyClass) } or let enemies = childNodeWithName("//level")!.children.filter { $0 is SomeEnemyClass } This is not an answer to my question, but it works too... self.enumerateChildNodesWithName("//*") { node, stop in if (node.name == "enemy") { print(node) } } Thank you to everyone who offered their input. It got me pointed in the right direction.
unexpectedly found nil while unwrapping an Optional value - Sprite Kit
Hey guys im having a huge problem with my Sprite Kit game. Im new to it and im trying to do a kind of asteroids game just to do exercise what i learnt. My problem is, my asteroids are infinite, they don't stop unless the player is hit. My code is the follows: For the meteor: func addMeteor() { let bigMeteor = SKSpriteNode(imageNamed: "meteor1") let bigMeteorAux = bigMeteor let sizeX = UInt32(CGRectGetMaxX(self.frame)) let randomX = CGFloat(arc4random_uniform(sizeX)) var impulse : CGVector! bigMeteorAux.position = CGPoint(x: randomX, y: size.height + 100) impulse = CGVector(dx: 0, dy: -30) bigMeteorAux.physicsBody = SKPhysicsBody(texture: bigMeteor.texture, size: bigMeteor.size); bigMeteor.physicsBody?.affectedByGravity = false bigMeteor.physicsBody?.allowsRotation = false bigMeteorAux.physicsBody?.friction = 0.0 bigMeteorAux.physicsBody!.categoryBitMask = CollisionCategoryAsteroids bigMeteorAux.name = "Asteroid" foregroundNode!.addChild(bigMeteorAux) bigMeteorAux.physicsBody!.applyImpulse(impulse) } Im calling the function with an action: runAction(SKAction.repeatActionForever( SKAction.sequence([ SKAction.runBlock(addMeteor), SKAction.waitForDuration(1.0) ]) )) Perfect until now, the game works just fine. This is the player code: playerNode = SKSpriteNode(imageNamed: "spaceshipv2") playerNode!.physicsBody = SKPhysicsBody(texture: playerNode!.texture, size: playerNode!.size); playerNode!.xScale = 0.5 playerNode!.yScale = 0.5 playerNode!.position = CGPoint(x: size.width / 2.0, y: 220.0) playerNode!.physicsBody!.linearDamping = 1.0 playerNode!.physicsBody!.allowsRotation = false playerNode!.physicsBody?.affectedByGravity = false playerNode!.physicsBody!.categoryBitMask = CollisionCategoryPlayer playerNode!.physicsBody!.contactTestBitMask = CollisionCategoryAsteroids playerNode!.name = "Player" //addChild(playerNode!) foregroundNode!.addChild(playerNode!) And for the last the contact func: func didBeginContact(contact: SKPhysicsContact) { var nodeB = contact.bodyB!.node! if nodeB.name == "Asteroid" { println("Touched") nodeB.removeFromParent() } } So my problem start with the nodeB, for some reason sometimes when the Asteroid hit the player this code works just fine but other times when the asteroid hit the player the game crashes and i get fatal error: unexpectedly found nil while unwrapping an Optional value After the program enters the contact function. Any ideas or solution about how to fix this? Thx a lot! =)
The crash comes from one of the two forced unwrap calls: bodyB! and node!. By writing it like this, you are asserting that you are sure they will never be nil. The way to find the case where one of these is nil, break up the code into lines that you can check with a breakpoint. var bodyB = contact.bodyB if bodyB == nil { // breakpoint here } var nodeB = bodyB!.node if nodeB == nil { // breakpoint here } if nodeB!.name = "Asteroid" // etc. Once the code stops you can examine the objects and try to find out why they are nil and fix the problem.
I'm not sure why the node is coming up nil, but you can avoid the crash by not force unwrapping the nil values: if let nodeB = contact.bodyB?.node? if nodeB.name == "Asteroid" { println("Touched") nodeB.removeFromParent() } } Then that bit of code will only run if those optionals can be successfully unwrapped.
How do I make my enemySprite respawn as new bad guy once its been killed?
I am having trouble with my game, I've managed to have my enemySprites all shoot at the hero in a synchronized matter and I've gotten them to play the "killed" animation once they've been hit. Although I've run into a rather small matter which I was really hoping you guys could help me with. The problem I have is that when my badGuy is killed they move off the screen and I don't know how I can program it so that a 'new' badGuy appears forever until the Hero sprite is killed. This is my function to spawn my enemy: func spawnEnemy(targetSprite: SKNode) -> SKSpriteNode { if !gamePaused{ // create a new enemy sprite let main = GameScene() newEnemy = SKSpriteNode(imageNamed:"BNG1_1.png") enemySprite.append(newEnemy)//ADD TO THE LIBRARY OF BADGUYS newEnemy.xScale = 1.2 newEnemy.yScale = 0.6 newEnemy.physicsBody?.dynamic = true newEnemy.physicsBody = SKPhysicsBody(texture: newEnemy.texture, size: newEnemy.size) newEnemy.physicsBody?.affectedByGravity = false newEnemy.physicsBody?.categoryBitMask = BodyType.badguyCollision.rawValue newEnemy.physicsBody?.contactTestBitMask = BodyType.beamCollison.rawValue newEnemy.physicsBody?.collisionBitMask = 0x0 let muv : UInt32 = (200 + (arc4random()%500)) let actualDuration = NSTimeInterval(random(min: CGFloat(3.0), max: CGFloat(4.0))) let randomNum = CGPoint(x:Int (muv), y:Int (arc4random()%500)) // Create the actions var actionMove = SKAction.moveTo(randomNum, duration: NSTimeInterval(actualDuration)) newEnemy.runAction(SKAction.sequence([actionMove])) // position new sprite at a random position on the screen var posX = arc4random_uniform(UInt32(sizeRect.size.width)) var posY = arc4random_uniform(UInt32(sizeRect.size.height)) newEnemy.position = CGPoint(x: screenSize.width*2 + newEnemy.size.width, y: random(min: newEnemy.size.height, max: screenSize.height - newEnemy.size.height)) let atlas = SKTextureAtlas(named: "BG1.atlas") let anime = SKAction.animateWithTextures([atlas.textureNamed("BG1_1.png"), atlas.textureNamed("BG1_2.png"), atlas.textureNamed("BG1_3.png"), atlas.textureNamed("BG1_2.png"), atlas.textureNamed("BG1_1.png")], timePerFrame: 0.1) dinoRun = SKAction.repeatActionForever(anime) newEnemy.runAction(dinoRun) } return newEnemy } And this is my function for once they are hit (this function is called when the collisionTest between my hero's laser and the badguy is recognized): func deadBadGuy(){ //animation var dinoRun:SKAction var newdes = CGPoint(x: Int(arc4random()%500), y:0) var actionMoov = SKAction.moveTo(newdes, duration: 3) var goaway = SKAction.removeFromParent() let aTlas = SKTextureAtlas(named: "dedBG.atlas") let anime = SKAction.animateWithTextures([aTlas.textureNamed("deadBG1.png"), aTlas.textureNamed("deadBG2.png"), aTlas.textureNamed("deadBG3.png"), aTlas.textureNamed("deadBG2.png"), aTlas.textureNamed("deadBG1.png")], timePerFrame: 0.1) dinoRun = SKAction.repeatActionForever(anime) newEnemy.runAction(dinoRun) newEnemy.runAction(SKAction.sequence([actionMoov,goaway])) score++ self.scoreNode.text = String(score) enemySprites.newPlace(neewEnemy) dead = true //i created this Boolean because the sprite keeps shooting even if its dead } This is the way I called for the collisionTest in my program in case you need it for more information: func didBeginContact(contact:SKPhysicsContact){ if !gamePaused { let firstNode = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask switch(firstNode){ case BodyType.beamCollison.rawValue | BodyType.badguyCollision.rawValue: deadBadGuy() //enemySprites.spawnEnemy(sprite) default: print("hit") } Note: I tried having the func spawnEnemy being called during the collisionTest but that results in the BadGuy staying off screen shooting at my hero. Update : I found out how to add a new enemy sprite once the other is dead all i had to do was newEnemy.runAction(SKAction.sequence([actionMoov,goaway]), completion: { self.addChild(self.enemySprites.spawnEnemy(sprite)) }) in the deadBadGuy function(the spawnEnemy function is in another class which I named enemySprites as a variable). However now I've run into a new issue and that is that it adds 10 enemySprites instead of one. How can I change that? Update 2 : I figured out that issue too, I just needed to remove to dead Boolean method in the deadBadGuy function.
I found my answer and all I had to do was add this line to my deadBadGuy function and remove the dead boolean methods newEnemy.runAction(SKAction.sequence([actionMoov,goaway]), completion: { self.addChild(self.enemySprites.spawnEnemy(sprite)) })