extension for SKPhysicsContact crashing - ios

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.

Related

Small lag/jitter when tapping the screen

So my game is almost complete... but there's this little glitch or jitter that occurs when I press and hold my finger on the screen which, now I've noticed, I can't un-notice...
It happens really fast, and only happens when a function is called to handle tap&holds (long press). This happens after 0.2seconds have passed using a timer.
I've tried breakpointing it to pin down where exactly the jitter happens in the code, but it seems I can not fine tune it enough to locate it.
My update method is typical:
override func update(currentTime: CFTimeInterval) {
//calc delta time
if lastUpdateTime > 0 {
dt = currentTime - lastUpdateTime
} else {
dt = 0
}
lastUpdateTime = currentTime
//timer for handleLongPress
if touched {
longTouchTimer += dt
}
if longTouchTimer >= 0.2 && !canLongPressNow {
canLongPressNow = true
handleLongPress()
} else {
canLongPressNow = false
}
...
//switch GameSate
//switch CharacterState
}
My function to handleLongPress is this:
func handleLongPress() {
//switch gameState
//if in gameplay gamestate
if canLongPressNow {
//rotate player
//change character state
startPlayerAnimation_Sliding()
}
touched = false
longTouchTimer = 0
}
The startPlayerAnimation_Sliding() just iterates a texture array of the playerNode.
func startPlayerAnimation_Sliding() {
var textures: Array<SKTexture> = []
for i in 0..<KNumSlideFrames{
textures.append(SKTexture(imageNamed: "slide\(i)"))
}
let playerAnimation = SKAction.animateWithTextures(textures, timePerFrame: 0.3)
player.runAction(SKAction.repeatActionForever(playerAnimation), withKey: "sliding")
}
Is there anything noticeable that may be causing this?
update
I've removed this from my update(..) method, and it seems smooth again... and I have no idea why...? Maybe because it's removing a key (explosion) that hasn't been created yet? or the fact it's removing these keys every frame... Doesn't make sense though... But I'm calling it a night, and looking at this again tomorrow. Thanks for your help so far. Have a good evening. (will update tomorrow)
//for animations
switch characterState {
case .Running:
player.removeActionForKey("exploding")
player.removeActionForKey("sliding")
break
case .Sliding:
player.removeActionForKey("running")
player.removeActionForKey("exploding")
break
case .Exploding:
player.removeActionForKey("running")
player.removeActionForKey("sliding")
break
}
Yikes, how you create textures is what is slowing you down a lot, you are creating new textures every time a touch happens, this is not needed. Instead do:
var textures: Array<SKTexture> = []
var playerAnimation : SKAction?
func loadingPhase() //however this is defined for you
{
for i in 0..<KNumSlideFrames{
textures.append(SKTexture(imageNamed: "slide\(i)"))
}
playerAnimation = SKAction.repeatActionForever(SKAction.animateWithTextures(textures, timePerFrame: 0.3))
}
func startPlayerAnimation_Sliding() {
player.runAction(playerAnimation!, withKey: "sliding")
}

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]))

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

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

iOS Motion Detection: Motion Detection Sensitivity Levels

I have a simple question. I'm trying to detect when a user shakes the iPhone. I have the standard code in place to detect the motion and this works no problem. However, in testing this on my actual phone, I've realized that you have to shake the device quite hard to get the motion detection to trigger. I would like to know if there is a way to implement a level of sensitivity checking. For example, a way to detect if a user lightly shakes the device or somewhere between light and hard shake. This will be targeted towards iOS 7 so any tips or advice that is not deprecated from older iOS version would be greatly appreciated. I've done my googling but have yet to find any good solutions to this problem (If there are any.)
Thanks!
-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if(motion == UIEventSubtypeMotionShake)
{
//Detected motion, do something about it
//at this point.
}
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
-(void)viewWillDisappear:(BOOL)animated
{
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
Here is the solution I found. This works well but you do have to play with the deviceMotionUpdateInterval time value as well as the accelerationThreshold which can be tricky to get a fine balancing act for a actual "light shake" vs "picking up the phone and moving it closer to your face etc..." There might be better ways but here is one to start. Inside of my view didLoad I did something like this:
#import <CoreMotion/CoreMotion.h> //do not forget to link the CoreMotion framework to your project
#define accelerationThreshold 0.30 // or whatever is appropriate - play around with different values
-(void)viewDidLoad
{
CMMotionManager *motionManager;
motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 1;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error)
{
[self motionMethod:motion];
}];
}
-(void)motionMethod:(CMDeviceMotion *)deviceMotion
{
CMAcceleration userAcceleration = deviceMotion.userAcceleration;
if (fabs(userAcceleration.x) > accelerationThreshold
|| fabs(userAcceleration.y) > accelerationThreshold
|| fabs(userAcceleration.z) > accelerationThreshold)
{
//Motion detected, handle it with method calls or additional
//logic here.
[self foo];
}
}
This is a swift version based on zic10's answer, with the addition of a flag that prevents getting a few extra calls to your motion handler even when the first line in that handler is motionManager.stopDeviceMotionUpdates().
Also, a value of around 3.0 can be useful if you want to ignore the shake, but detect a bump. I found 0.3 to be way too low as it ended up being more like "detect move". In my tests, the ranges were more like:
0.75 - 2.49 is a better range for shake sensitivity
2.5 - 5.0 is a good range for "ignore shake, detect bump"
Here is the complete view controller for an Xcode single VC template:
import UIKit
import CoreMotion
class ViewController: UIViewController {
lazy var motionManager: CMMotionManager = {
return CMMotionManager()
}()
let accelerationThreshold = 3.0
var handlingShake = false
override func viewWillAppear(animated: Bool) {
handlingShake = false
motionManager.startDeviceMotionUpdatesToQueue(NSOperationQueue.currentQueue()!) { [weak self] (motion, error) in
if
let userAcceleration = motion?.userAcceleration,
let _self = self {
print("\(userAcceleration.x) / \(userAcceleration.y)")
if (fabs(userAcceleration.x) > _self.accelerationThreshold
|| fabs(userAcceleration.y) > _self.accelerationThreshold
|| fabs(userAcceleration.z) > _self.accelerationThreshold)
{
if !_self.handlingShake {
_self.handlingShake = true
_self.handleShake();
}
}
} else {
print("Motion error: \(error)")
}
}
}
override func viewWillDisappear(animated: Bool) {
// or wherever appropriate
motionManager.stopDeviceMotionUpdates()
}
func handleShake() {
performSegueWithIdentifier("showShakeScreen", sender: nil)
}
}
And the storyboard I used for this test looks like this:
It's also worth noting that CoreMotion is not testable in the simulator. Because of this constraint you may still find it worthwhile to additionally implement the UIDevice method of detecting motion shake. This would allow you to manually test shake in the simulator or give UITests access to shake for testing or tools like fastlane's snapshot. Something like:
class ViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
if TARGET_OS_SIMULATOR != 0 {
if event?.subtype == .MotionShake {
// do stuff
}
}
}
}
And then use Ctrl-Cmd-Z to test shake in the simulator.
Use core motion.
Link your binary with the CoreMotion framework.
Include
#import <CoreMotion/CoreMotion.h>
in your class.
Create an instance of CMMotionManager.
Set the deviceMotionUpdateInterval property to a suitable value.
Then call startDeviceMotionUpdatesToQueue.
You will get continuous updates inside the block, which include acceleration, magnetic field, rotation, etc.
You will get the data you require.
One thing to be taken care of is that the update shall be so rapid if
the interval is too small, and hence you will have to employ suitable
logic to handle the same.
Heres how I did this using Swift 3.
Import CoreMotion and create an instance
import CoreMotion
let motionManager = CMMotionManager()
On ViewDidLoad or wherever you want to start checking for updates:
motionManager.startDeviceMotionUpdates(to: OperationQueue.current!, withHandler:{
deviceManager, error in
if(error == nil){
if let mgr = deviceManager{
self.handleMotion(rate: mgr.rotationRate)
}
}
})
This function takes the rotation rate and gets a sum for the absolute values for x,y and z movements
func handleMotion(rate: CMRotationRate){
let totalRotation = abs(rate.x) + abs(rate.y) + abs(rate.z)
if(totalRotation > 20) {//Play around with the number 20 to find the optimal level for your case
start()
}else{
print(totalRotation)
}
}
func start(){
//The function you want to trigger when the device is rotated
}

Resources