Why is my collision registered when there is no collision? - ios

Please bear with me, I've been working with XCode/IOS for a day, so you may need to explain things...
I have a collision method:
func didBeginContact(contact: SKPhysicsContact!) {
if (contact != nil && contact.bodyA != nil && contact.bodyB != nil)
{
var firstBody:SKPhysicsBody
var seconBody:SKPhysicsBody
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA
seconBody = contact.bodyB
}
else
{
firstBody = contact.bodyB
seconBody = contact.bodyA
}
if (firstBody.categoryBitMask & torpedoCategory != 0 && seconBody.categoryBitMask & alienCategory != 0)
{
if firstBody.node != nil && seconBody.node != nil {
torpedodidCollideWithAlien(firstBody.node as SKSpriteNode, alien: seconBody.node as SKSpriteNode)
}
}
}
}
Which is triggered by this:
var alien:SKSpriteNode = SKSpriteNode(imageNamed: "Alien")
alien.physicsBody = SKPhysicsBody(texture: alien.texture, size: alien.size)
alien.physicsBody.categoryBitMask = alienCategory
alien.physicsBody.contactTestBitMask = torpedoCategory
alien.physicsBody.collisionBitMask = 0
alien.zPosition = -100000
I'm trying to use the pixel collision available in XCode 6. The issue is that the didBeginContact method is triggered when there is no collision, and it is triggered multiple times for one collision.
Am I using the physics system incorrectly?
Here is a link to the full project: https://www.dropbox.com/s/1npctvb99vw2l7x/BubbleBurst.zip
Values for the masks:
let alienCategory:UInt32 = 0x1 << 1
let torpedoCategory:UInt32 = 0x1 << 0

I know this is not really your answer, but it might help you to find out the actual answer.
I'm trying to use the pixel collision available in XCode 6. The issue is that the didBeginContact method is triggered when there is no collision, and it is triggered multiple times for one collision.
Collision and contact are different things. Use contactTestBitMask for detecting contacts (what you're doing) and collisionBitMask to enable the physics related to those contacts (collision itself). In more practical terms, contactTestBitMask should be used to handle something using your code, and collisionBitMask is used by SpriteKit to make, for example, an object bounce when it hits another object.
If you use the collisionBitMask, you'll probably get rid of your second problem ("[didBeginContact] triggered multiple times for one collision").
For your first issue ("didBeginContact [...] triggered when there is no collision"), I'd try to change your alien.physicsBody. I'm gonna totally guess in this part, but maybe your image is like 300x300 and you want your alien to have a pixel size of 150x150. Maybe your alien.physicsBody is larger than it looks, and therefore its physics body is touching the other object's physics body before the visual representation does the same.

In your viewController where you set up your SKView I recommend turning this on while you get this sorted out.
skView.showsPhysics = true
As for contact happening multiple times, that is completely normal. If two nodes you are telling SpriteKit to test for contact overlap it will read as contact each run of the loop (didSimulatePhysics). Let's say you want a torpedo to destroy an alien, one or both need to be removed from the scene to stop contact from being read again (optionally hidden or moved). Or for example if you want it to take multiple torpedoes to destroy the alien, the torpedo needs to be removed/moved on contact.
As Bruno mentioned, collision in SpriteKit means two objects can act as solid bodies that get in each others way. A player might collide with a wall (can't pass through it), but the player may be able to pass through monsters.
Also your didBeginContact code could be a lot more simple. Here is an example that tests if a player has hit one of three other physics body contactBitMasks.
func didBeginContact(contact: SKPhysicsContact) {
var other:SKPhysicsBody = contact.bodyA.categoryBitMask == Contact.Player ? contact.bodyB : contact.bodyA
if other.categoryBitMask == Contact.Scene {
// Do stuff for player hitting scene edge
} else if other.categoryBitMask == Contact.Object {
// Do stuff for player hitting the hazards
} else if other.categoryBitMask == Contact.Score {
// Do stuff for player scoring points
}
}

Related

How do I only allow one instance of an SKSpriteNode on screen at one time?

This is my first ever post - I have searched for a long time and could not find the answer.
I am making a game with SpriteKit and want the player to be able to only launch one bomb at a time- i.e they can't fire again until the previous bomb has exploded or gone off screen. Currently when the player taps the screen, they can launch as many bombs as they want.
Any help would be greatly appreciated!
Thanks,
Iain
Steve's idea works out well and is better than mine, but here is a more novice-friendly explanation IMO... Put this in your gamescene :)
var canFireMissile = true
func fireMissile() {
guard canFireMissile else { return }
canFireMissile = false // So you can't fire anymore missiles until 0.5secs later
let wait = SKAction.wait(forDuration: 0.5) // the duration of the missile animation (example)
let reset = SKAction.run { canFireMissile = true } // lets us refire the missile
let sequence = SKAction.sequence([wait, reset])
run(sequence)
}
override func mouseDown(with event: NSEvent) { // touchesBegan on iOS
fireMissile()
}
Create a SKSpriteNode property for your misssile.
Create an SKAction for the movement of the missile and give the action a key so you can refer to it by name).
When the fire button is pressed, check to see if the named action is already running; if it is, do nothing, otherwise run the ‘fireMissile’ action.

extension for SKPhysicsContact crashing

I'm creating a game with SpriteKit, that has collision between 2 bodies. After setting up the bodies, I've implemented the didBegin(_contact:) moethod as shown below:
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
gameOver()
}
}
and it worked perfectly.
Later, while inspecrting the documentation for this method, I found the following:
The two physics bodies described in the contact parameter are not passed in a guaranteed order.
So to be on the safe side, I've extended the SKPhysicsContact class with a function the swaps the categoryBitMask between both bodies, as following:
extension SKPhysicsContact {
func bodiesAreFromCategories(_ a: UInt32, and b: UInt32) -> Bool {
if self.bodyA.categoryBitMask == a && self.bodyB.categoryBitMask == b { return true }
if self.bodyA.categoryBitMask == b && self.bodyB.categoryBitMask == a { return true }
return false
}
}
The problem is that when the function gets called, the app crashes, and I get the following error:
2017-07-18 13:44:18.548 iSnake Retro[17606:735367] -[PKPhysicsContact bodiesAreFromCategories:and:]: unrecognized selector sent to instance 0x60000028b950
2017-07-18 13:44:18.563 iSnake Retro[17606:735367] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PKPhysicsContact bodiesAreFromCategories:and:]: unrecognized selector sent to instance 0x60000028b950'
This apparently is a bug, as answered here:
https://stackoverflow.com/a/33423409/6593818
The problem is, the type of contact is PKPhysicsContact (as you've noticed), even when you explicitly tell it to be an SKPhysicsContact, and the extension is on SKPhysicsContact. You'd have to be able to make an extension to PKPhysicsContact for this to work. From this logic, we can say that no instance methods will work in SKPhysicsContact extensions at the moment. I'd say it's a bug with SpriteKit, and you should file a radar. Class methods still work since you call them on the class itself.
In the meantime, you should be able to move that method into your scene or another object and call it there successfully.
For the record, this is not a Swift-specific problem. If you make the same method in an Objective-C category on SKPhysicsContact you'll get the same crash.
You can submit a bug report to apple:
https://developer.apple.com/bug-reporting/
And report it to the community:
https://openradar.appspot.com/search?query=spritekit
However, what you really want to do with your code is to add the category masks together. And then check for the sum (2 + 4 and 4 + 2 always equals 6, regardless of bodyA and bodyB order).
This is how you get unique contacts, if you set up your masks correctly in powers of two (2, 4, 8, 16, etc)
SKPhysicsContact is a wrapper class to PKPhysicsContact, you are extending SKPhysicsContact but in reality you need to extend PKPhysicsContact (Which you can't do)
To preserve order in your contact methods, just do:
let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
This way when you need to check for a specific node, you know what node to hit, so
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
gameOver()
}
}
Becomes
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
gameOver()
}
}
You can then add to your code since you now know the individual bodies.
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
gameOver()
//since I know bodyB is 1, let's add an emitter effect on bodyB.node
}
}
BTW, for people who see this answer, categoryBitMask 0 should not be firing any contacts, you need some kind of value in it to work. This is a bug that goes beyond the scope of the authors question, so I left it at 0 and 1 to since that is what his/her code is doing and (s)he is claiming it works.

Resetting Whole Scene (Swift, SpriteKit)

I'm trying to create a replay button for my game, but whenever I go back to the game scene, It seems as if nothing ever stopped. The time is a negative number and the game will just crash. I tried...
if timeInt < 0 {
//////////////
let retryScene = RetryScene(size: self.frame.size)
self.view?.presentScene(retryScene)
self.removeAllChildren()
self.removeAllActions()
///// end game
timeInt = 45
}
I figured removing all children would work and resetting the time would work too. I used a function that updates every second to make the time work. So all functions keep going as if the scene never ended. What should I do?
All the time i whant to restart game I'am presenting Game scene. (starting game scene from begining)
It should look like this
if (node.name == "ReplayButton") {
var gameScene = GameScene(size: self.size)
var transition = SKTransition.doorsCloseHorizontalWithDuration(0.5)
gameScene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(gameScene, transition: transition)
}
I fixed it by stopping the timer that makes the update function.
if time < 0 {
timer.invalidate()
}

Is there a function for no collisionTest in swift?

I know this may sound weird, but I'm wondering if there is a way I can set a method for is my sprite doesn't make contact with anything. For example; if my sprite doesn't touch an object the game is over. I'm very sorry I can't provide any sample codes because I have absolutely no idea how to do what I just described. Usually for my CollisionTests I would simply import this:
didBeginContact(contact: SKPhysicsContact) {
let firstnode = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(firstnode){
case Body.faceMask.rawValue | Body.redPoint.rawValue:
//gameOver = true
sampleMethod()
default:
print("a")
}
I don't think it's possible to set an else method inside there because I tried and it gave me errors.
This should really be a comment but I don't have enough rep yet!
What is your sprite trying to do? For example is it like a projectile that gets fired from somewhere? And if it misses you loose?
When you give your sprite an action, can you give it an action to complete after that?
For example:
let actionMove = SKAction.moveTo(CGPoint(10, 10), duration: 1.0)
let actionMoveDone = SKAction.rubBlock() {
// Code here to run after the object finishes moving
}
sprite.runAction(SKAction.sequence([actionMove, actionMoveDone]))

How to determine which physics object ended contact in sprite kit

I've been trying to call a method in the method didEndContact:contact when a player jumps off a certain physics object called "triFloor", how would I run a method in didEndContact:contact only when the players contact with "trifFloor" ends?
You don't run didEndContact: just between player and trifFoor (unless those are the only two with physics body), you run the method every time and in the method, find who it made contact with and do the appropriate actions.
- (void)didEndContact:(SKPhysicsContact *)contact {
if ((contact.bodyA == player && contact.bodyB == trifFoor) ||
(contact.bodyA == trifFoor && contact.bodyB == player)) {
//Do what you want here.
}
}

Resources