app crashing after trying to load an skspritenode twice? - swift

I asked a question before and i wound up deleting it as i though i figured it out, but i actually just narrowed it down.
When one of my nodes hits into an obstacle, the game presents a score label and a restart button. But if my node hits a different object after the initial hit, the app crashes. If i run the game with // in front of the the label and restart to take them out of the equation the game runs fine, but if i run the game with them, it crashes.
This leads me to believe its crashing because its trying to load the restart button and score label twice. I might be totally wrong but how can i correct this?

I think you're probably right.
Trying to addchild is how I presume you're adding things to the scene. If you try to add something to a scene or object that's already added to a something, you get a crash.
So you're probably trying to add your restart and score label to something, as a child, when they already have a parent, and this is causing the crash.

SOLVED IT!
I added if statements to make the app only run my collision code after 1 collision and not 2
var collision = Int()
func didBegin(_ contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody = contact.bodyA
var secondBody : SKPhysicsBody = contact.bodyB
if collision == 0{
if ((firstBody.categoryBitMask == physicsCatagory.bird) && (secondBody.categoryBitMask == physicsCatagory.obstacle)) {
collisionWithObstacle(bird: firstBody.node as! SKSpriteNode)
}
}
else if collision != 0{
if ((firstBody.categoryBitMask == physicsCatagory.bird) && (secondBody.categoryBitMask == physicsCatagory.obstacle)) {
}
}
}
func collisionWithObstacle(bird:SKSpriteNode){
collision = collision+1
scoreTimer.invalidate()
obstacleTimer.invalidate()
addChild(restart)
scoreLabel2.text = "Score: \(score1)"
addChild(scoreLabel2)
}
hope this can help someone else because although it seems trivial in hindsight, it was a head scratcher for a few nights after work.

Related

How do I choose which node I want to contact with, when a collision happens with two nodes at once with SpriteKit?

I have a an iOS app coded in Swift 3 where a ball is shot and bounces off of bricks on the screen. If I have the brick being one PhysicsBody (a rectangle), I can't easily determine which side/corner of the brick is being hit. What I decided to do instead of this, is have each side of the brick be its own separate node. The issue I am having now, is that a ball can't be in contact with two nodes (say the left and bottom) at once. I am decreasing the value of the brick after every contact with the ball, which in turn is decreasing the value by 2 for this one hit. How can I make it so that if a ball hits two nodes, only execute code for one contact?
Sometimes the below code gets executed twice, with the ball contacting with two brickNodes both times.
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
let countPoint = true
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & ballCategory) != 0 {
if (firstBody.node != nil && secondBody.node != nil){
if (secondBody.categoryBitMask & brickCategory) != 0 {
ballDidHitBrick(ballNode: firstBody.node as! SKShapeNode, brickNode: secondBody.node as! SKShapeNode, decreasePoint: countPoint)
} else if (secondBody.categoryBitMask & roofCategory) != 0 || (secondBody.categoryBitMask & rightWallCategory) != 0 || (secondBody.categoryBitMask & leftWallCategory) != 0 || (secondBody.categoryBitMask & bottomCategory) != 0 {
ballDidHitWall(ballNode: firstBody.node as! SKShapeNode, wallNode: secondBody.node as! SKShapeNode)
} else {
//Nothing as of yet
}
}
}
}
So going along with what Steve has said above, I implemented the code below and I am no longer having dual contacts per update:
if !bricksHit.contains("\(secondBody.node?.name ?? ""), \(firstBody.node?.name ?? "")") {
//If ball hasnt hit the object more than once
bricksHit.append("\(secondBody.node?.name ?? ""), \(firstBody.node?.name ?? "")")
ballDidHitBrick(ballNode: firstBody.node as! SKShapeNode, brickNode: secondBody.node as! SKShapeNode, decreasePoint: countPoint, contact: contact)
}
I also added in the below to my code, which clears the bircksHit after every update:
override func didFinishUpdate() {
bricksHit.removeAll()
}
I would scrap the multiple nodes with multiple bodies, that would yield terrible performance if you have many blocks.
Instead, you should process your work in steps.
During your didBegin phase, you need to keep track of where the contact point is. This can be done with userData
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
let countPoint = true
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & ballCategory) != 0, (secondBody.categoryBitMask & brickCategory) != 0 {
let userData = firstBody.node!.userData ?? [String,AnyObject]()
let contactPoints = userData["contactPoints"] as? [CGPoint] ?? [CGPoint]()
contactPoints.append(contact.contactPoint) //if need be add other info like the vector or relative to the node
userData["contactPoints"] = contactPoints
}
}
Then in a process later on, like didSimulatePhysics You can evaluate the nodes that were contacted, determine the priority of contact (like the bottom would supersede the sides, or if the velocity x > velocity y, sides, whatever you need to do) and react in this manner.
Note this is only sample code, it will not work verbatim. You will need to structure it into your code to get it to properly work.
Yep - this happens. The consensu of opinion seems to be that if there are multiple simultaneous contact points between 2 physics bodies, SK will call didBegin for every contact point, resulting in multiple calls (in the sam game loop) for the same pair of physics bodies.
The way to handle it (you can't get sprite-kit to NOT call didBegin multiple times in some circumstances) is to make sure that your contact code accommodates this and that handling the contract multiple times does not cause a problem (such as adding to the score multiple times, removing multiple lives, trying to access a node or physicsBody that has been removed etc).
Some things you can do include:
If you remove a node that is contacted, check for it being nil before
you remove it (for the duplicate contacts)
Add the node to a set and then remove all the nodes in the set in didFinishUpdate
Add an 'inactive' flag' to the node's userData
Make the node a subclass of SKSpriteNode and add an 'inactive' property (or similar)
Etc etc.
See this question and answer about SK calling didBegin multiple times for a single contact:
Sprite-Kit registering multiple collisions for single contact
Also SKPhysicsContact contains not only details of the 2 physics bodies that have collided, but also the contact point. From this, and the position properties of the 2 nodes involved, you could indeed calculate which side/corner of the brick is being hit.

3 different collision bodies? Swift3 + Spritekit

Okay I've searched around and I cannot find an answer to this problem.
I have 3 different node types that I want to handle collisions with. A meteor, a shot, and a station. I have the code for the collision handling between the shots and the meteors working perfectly, but I cannot for the life of me figure out how to respond to collisions (yes the collision is being detected but the response code isn't executing) between the station and a meteor. Here's my didBegin func:
func didBegin(_ contact: SKPhysicsContact){
print("contact")
let shot = (contact.bodyA.categoryBitMask == shotCategory) ? contact.bodyA : contact.bodyB
let stationBody = (contact.bodyA.categoryBitMask == stationCategory) ? contact.bodyA : contact.bodyB
let otherObject = (shot == contact.bodyA) ? contact.bodyB : contact.bodyA
if ((contact.bodyA == stationBody) && (contact.bodyB == otherObject)) {
print("collision!")
}
}
"contact" is being printed when a meteor collides with the station, but "collision!" is not being printed. I know its got something to do with the way the code is worded but i can't seem to get it to work no matter how I write/rewrite it
Try considering the order...
if (contact.bodyA == stationBody && contact.bodyB == otherObject) || (contact.bodyB == stationBody && contact.bodyA == otherObject)
Also you are setting the other object according to shot, so if shot is not one of the objects in the collision it might be a problem
if bodyA is the other object and bodyB the station for example, shot would be set to bodyB (because the categoryBitMask != shotCategory) and then the otherObject would be set to bodyA. Therefore contact.bodyB won't equal otherObject.

Changing the location of a node once the location is already set.(SpriteKit)

I am working on a game that involves objects(fish) that move across the screen that you need to catch with another object(hook) that you can move up and down. Once the objects make contact the fish's position is supposed to become the same as the hook's. So in did begin contact (which does work, Im sure) I said, when the hook and fish touch make the fishes position become the same as the hooks.
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == physicsCategory.fishHook && secondBody.categoryBitMask == physicsCategory.greenFish || firstBody.categoryBitMask == physicsCategory.greenFish && secondBody.categoryBitMask == physicsCategory.fishHook{
CollisionWithfishHook(firstBody.node as! SKSprit
eNode, greenFish: secondBody.node as! SKSpriteNode)
}
}
func CollisionWithfishHook (fishHook: SKSpriteNode, greenFish: SKSpriteNode){
greenFish.position = CGPointMake(fishHook.position.x, fishHook.position.y)
}
For some reason when I run it and they make contact nothing happens. I know that did begin contact is working correctly because when I told it to remove the fish when it touches the hook it works. The problem is I am unable to change the location of the green fish. Why is this and what can I do to fix it?
Changing the position of the fish in the didBeginContact method is only going to change the fish's position once - the moment the fish and hook touch. You need to continue updating the fish's position in the update method.
I would create a class variable in your GameScene class called fishToUpdate and when you 'hook' one, set fishToUpdate = greenFish (or whatever was caught) in your didBeginContact method. Then in the update method, update the fish's position by fishToUpdate.position = fishHook.position.

Stop an action until all gameobjects have stopped colliding with barrier

So far in my code there is a collision detection method that sences when a certain sprite has hit a barrier, and there is also an action sequence I have implemented. In the action sequence I want there to be a wait period where the action stops until a certain amount of sprites have hit the ground (barrier). I've so far got the rotation and cubes falling and the main cube sprite, but the problem I'm trying to solve is getting the cube to stop rotating until all the cubes has hit the barrier. Basically stopping an action until a certain amount of projectile objects has collided with something, then continuing the action. Also the spawning of the cubes is part of the action sequence, so idk what to do without creating clutter in my code.
Please find the below code for reference.
class GameScene:SKScene {
var wait:NSTimeInterval = 0
var blocks = 0
var cube = SKSpriteNode(imageNamed: "cube")
func didBeginContact(contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == barrierCategory && secondBody.categoryBitMask == objectCategory {
secondBody.node?.removeFromParent()
counterTest++
print("hello")
}
if (firstBody.categoryBitMask == blockCategory && secondBody.categoryBitMask == barrierCategory) {
blocks++
}
}
override func didMoveToView(view: SKView) {
NSLog("Start") //Using NSLog just to print the current time
NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval!), target: self, selector: Selector("spawnBlocks"), userInfo: nil, repeats: false)
cube.runAction(recursive())
}
}
func recursive(){
let recursive = SKAction.sequence([
//SHOULD SPAWN ABOUT 10 BLOCKS
SKAction.spawnBlocks,
//WAIT UNTIL THE COLLISION HAS DETECTED 10 BLOCKS HIT THE BARRIER
SKAction.waitForDuration(waitTime),
//CONTINUE THE ROTATION OF THE CUBE SPRITE
SKAction.rotate,
SKAction.runBlock({[unowned self] in NSLog("Block executed"); self.recursive()})
])
runAction(recursive, withKey: "aKey")
}
}
You don't need your action to be recursive. this makes the logic hard to flow and is unnecessary.
If your cube has a continuous and autonomous behaviour, it should manage it itself in a subclass of SKSprite.
I would suggest you create a rotating action and run it "forever" on your cube. Use removeActionWithName when you want it to stop. Add it again when you want the rotation to resume. This will enable you to implement an easy to use "startRotating" / "stopRotating" pair of functions that you can call whenever appropriate.
Same thing with the cube's continuous behaviour, place it in a runBlock action that repeats forever and start/stop other actions when conditions are met.
This will keep all the cube's behaviour within its own class (preferably in a separate file) and avoid you SKView from becoming a big unmanageable blob of code.

How to end a game when hitting an object from below?

Hey so I am making this project in which the player has to jump platforms all the way to the top. Some monsters spawn randomly throughout the game. So the idea is to lose the game when you hit them from below, but can go on if you jump on them. I already did the part in which the player jumps on it and you destroy the monster but I am still stuck on that part to lose the game when you hit it from below. Any ideas on how I can manage to do this? For this project I followed Ray Wenderlich's tutorial on How To Make a Game Like Mega Jump.
So on my GameScene, I have the didBeginContact method:
func didBeginContact(contact: SKPhysicsContact) {
var updateHUD = false
let whichNode = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
let other = whichNode as GameObjectNode
updateHUD = other.collisionWithPlayer(player)
if updateHUD {
lblStars.text = String(format: "X %d", GameState.sharedInstance.stars)
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
}
Which then calls the method from the GameObjectNode Scene.
class MonsterNode: GameObjectNode {
var monsterType: MonsterType!
override func collisionWithPlayer(player: SKNode) -> Bool {
if player.physicsBody?.velocity.dy < 0 {
player.physicsBody?.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: 450.0)
if monsterType == .Normal {
self.removeFromParent()
}
}
When the player jumps on top of the monster, the monster is removed from the parent. I was trying to set that if the player's velocity is greater than 0 when colliding with the monster, then the player is removed from parent. Then when I go back to my GameScene, I could declare something in my update method so that when the player is removed from the parent call the endGame() method.
override func update(currentTime: NSTimeInterval) {
if gameOver {
return
}
if Int(player.position.y) > endLevelY {
endGame()
}
if Int(player.position.y) < maxPlayerY - 500 {
endGame()
}
}
Of course I wasn't able to make that work, and I still can't. So if anyone could help me out on how I can manage to do this, or probably some tutorial which could guide me into doing this I would really appreciate it! Thank you in advance.
First you use the didBeginContact method to establish if a contact between player and monster has been made. Next you compare the y positions of the player and monster. If the monster's y position is greater than than the player's... BANG, player dies.
The code sample assumes you have multiple monsters, each with a unique name, and have them all stored in an array.
- (void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryMonster | CategoryPlayer)) {
for(SKSpriteNode *object in monsterArray) {
if(([object.name isEqualToString:contact.bodyB.node.name]) || ([object.name isEqualToString:contact.bodyA.node.name])) {
if(object.position.y > player.position.y) {
// moster is above player
}
}
}
}
Couple of notes... If you have more than one monster active at any one time, you will need to have a unique name for each one. Reason being that you will need to know which monster contacted the player and that can only happen if you can differentiate between monsters. Hence the unique name for each one.
The if check for the y position is a simple one and only needs +1 y to be fulfilled. If it is possible for your player to make side contact with a monster and not die, you can change the if condition to be something like if(object.position.y > player.position.y+50) to make sure that the contact was actually from the bottom.
(I am not all too proficient in Swift yet so code sample is in Obj-C.)