How to determine which physics object ended contact in sprite kit - ios

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.
}
}

Related

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.

Wait until a tap gesture from user?

In my spritekit game I would like users to choose starting positions on a map by tapping and then adding the sprite to the appropriate place. I am struggling to figure out how to make the game "wait" for the user input without advancing.
My initial thought was an infinite while loop, but that seems to simply freeze the game.
if self.settingsInstance.startPositionMode == "custom" {
for player in self.settingsInstance.gamePlayers {
if player.isPlayerHuman == true {
//Allow human to choose
player.isCurrentlyActive = true
while player.hasStartAssigned == false {
~ -> Wait until a tap gesture occurs
}
print("success!")
if player.hasStartAssigned == true{
player.isCurrentlyActive = false
}
}
}
}
In my tap handling function I first check for player.isCurrentlyActive == true and then set the .hasStartAssigned to true, but so far this method isnt working.
Does anyone have some pointers for how to "wait" for user input?
Thanks!

Why is my collision registered when there is no collision?

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
}
}

Why does the following code get triggered only after I set a breakpoint elsewhere, and then resume?

I have created a real-time multiplayer game using game kit. Creating the match etc proceeds normally on both devices. Then the if block :if (!self.matchStarted && match.expectedPlayerCount == 0)never gets triggered, unless I place a break point in the update method of SKScene. While the game is running I place a break point. And once the debugger breaks, I "continue" (^ + CMD + Y). Immediately the code in the if block gets triggered.
-(void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state
{
switch (state)
{
case GKPlayerStateConnected:
// Handle a new player connection.
break;
case GKPlayerStateDisconnected:
// A player just disconnected.
break;
}
//TODO We have to keep track of whether a match has started or not.
if (!self.matchStarted && match.expectedPlayerCount == 0)
{
self.matchStarted = YES;
self.gameState = _proposingBestHostState;
}
}
My SKScene update: method is effectively a NOP, since it calls a method on self.gameState which is nil until it is set in the if block.
self.matchStarted is a property with the following attributes:
#property (nonatomic)bool matchStarted;

AR With Printed Buttons

I made an AR Application that detects the printed marker & plays the video on it.
I'd like to have a button printed on paper, which triggers an event when its blocked by a finger.
IMAGE LINK: http://tinypic.com/view.php?pic=23le2h2&s=7
The application development is to have an AR Quiz that works with simple printed paper & Webcam only. What is the best solution to have the button event triggered by hiding the marker?
Operating System: Windows PC
Framework: FLARManager
You just need to listen when and what combination of markers is active and make if statments like:
private function onEnterFrame (evt:Event) :void
{
if (this.activeMarker_1 && this.activeMarker_2)
{
trace("no button is being pressed");
}
else if (this.activeMarker_1 && this.activeMarker_2 == null)
{
trace("answer: no");
}
else if (this.activeMarker_1 == null && this.activeMarker_2)
{
trace("answer: yes");
}
else if (this.activeMarker_1 == null && this.activeMarker_2 == null)
{
trace("both buttons are being pressed");
}
}

Resources