SpriteKit: cannot change node position in contact callback - sprite-kit

I have a node with a dynamic physics body. And I would like to make it static and change its position when it comes in contact with another body.
I managed to make the body static with the solution provided in this question: Sprite Kit failing assertion: (typeA == b2_dynamicBody || typeB == b2_dynamicBody)
However if I change the position property of the node in one of the contact callback methods (e.g didBeginContact) the new position is not taken into account.
How could I achieve that?

I believe this is a bug in SpriteKit. (I was able to reproduce this problem with SpriteKit 7.1).
Here is a quick workaround:
- (void) didBeginContact:(SKPhysicsContact *)contact
{
contact.bodyB.node.position = CGPointMake(newX, newY);
contact.bodyB.node.physicsBody = contact.bodyB.node.physicsBody; // <-- Add this line
}

JKallio's solution did not work for me (Xcode 7.2.1 on OS X 10.10.5, targeting iOS 8.1). I figured out that I was able to change the position in update: method, so I got around the bug by setting a flag in didBeginContact and changing the position in update:.
#implementation GameScene {
SKNode *_victim;
CGPoint _target;
}
- (void)didMoveToView:(SKView *)view {
_victim = nil;
}
- (void)didBeginContact:(SKPhysicsContact *)contact {
_victim = contact.bodyB.node;
_target = CGPointMake(newX, newY);
}
- (void)update:(CFTimeInterval)currentTime {
if (_victim != nil) {
_victim.position = _target;
_victim = nil;
}
}
#end
Note that _victim serves as both the node and the flag.
My solution is more complicated that JKallio's; try that one before coming for this.

As another option for a workaround, I instead call a method in a SKSpriteNode subclass for the object I want to move, passing in that body. The position gets set correctly.
/* In GameScene.swift */
func didBegin(_ contact: SKPhysicsContact) {
let dotBody: SKPhysicsBody
if contact.bodyA.categoryBitMask == 0b1 {
dotBody = contact.bodyB
} else {
dotBody = contact.bodyA
}
if let dot = dotBody.node as? Dot {
dot.move()
}
}
Here is the Dot class:
import SpriteKit
class Dot : SKSpriteNode {
let dotTex = SKTexture(imageNamed: "dot")
init() {
super.init(texture: dotTex, color: .clear, size: dotTex.size())
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.categoryBitMask = 0b1 << 1
self.physicsBody?.contactTestBitMask = 0b1
}
func move() {
let reset = SKAction.run {
self.position.x = 10000
}
self.run(reset)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Related

Entity-Component in Swift

I am trying to build a simple iOS game using entity-component architecture similar to what is described here.
What I would like to achieve in my game is when a user touches the screen, detect where the touch occurred and move all entities of one type towards a specific direction (direction depends on where the user touched, right of screen = up, left of screen = down).
So far, the game is really simple and I am only getting started, but I am stuck in this simple functionality:
My issue is that an SKAction is supposed to run on all entities of a type, but happens at all.
Before I redesigned my game to an ECS approach, this worked fine.
Here is the GKEntity subclass that I declared in Lines.swift:
class Lines: GKEntity {
override init() {
super.init()
let LineSprite = SpriteComponent(color: UIColor.white, size: CGSize(width: 10.0, height: 300))
addComponent(LineSprite)
// Set physics body
if let sprite = component(ofType: SpriteComponent.self)?.node {
sprite.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: sprite.size.width, height: sprite.size.height))
sprite.physicsBody?.isDynamic = false
sprite.physicsBody?.restitution = 1.0
sprite.physicsBody?.friction = 0.0
sprite.physicsBody?.linearDamping = 0.0
sprite.physicsBody?.angularDamping = 0.0
sprite.physicsBody?.mass = 0.00
sprite.physicsBody?.affectedByGravity = false
sprite.physicsBody?.usesPreciseCollisionDetection = true
sprite.physicsBody?.categoryBitMask = 0b1
sprite.zPosition = 10
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In TouchesBegan I am calling the function Move(XAxisPoint: t.location(in: self)) which is declared in GameScene and here is what Move() does:
///Determines direction of movement based on touch location, calls MoveUpOrDown for movement
func move(XAxisPoint: CGPoint){
let Direction: SKAction
let Key: String
if XAxisPoint.x >= 0 {
Direction = SKAction.moveBy(x: 0, y: 3, duration: 0.01)
Key = "MovingUp"
} else {
Direction = SKAction.moveBy(x: 0, y: -3, duration: 0.01)
Key = "MovingDown"
}
moveUpOrDown(ActionDirection: Direction, ActionKey: Key)
}
///Moves sprite on touch
func moveUpOrDown(ActionDirection: SKAction, ActionKey: String) {
let Line = Lines()
if let sprite = Line.component(ofType: SpriteComponent.self)?.node {
if sprite.action(forKey: ActionKey) == nil {
stopMoving()
let repeatAction = SKAction.repeatForever(ActionDirection)
sprite.run(repeatAction, withKey: ActionKey)
}
}
}
///Stops movement
func stopMoving() {
let Line = Lines()
if let sprite = Line.component(ofType: SpriteComponent.self)?.node {
sprite.removeAllActions()
}
}
I am guessing there is some issue with this line of code Line.component(ofType: SpriteComponent.self)?.node but the compiler doesn't throw any errors and I am not sure where my mistake is.
Any help/guidance will be greatly appreciated!
The issue is the following line in MoveUpOrDown and StopMoving
let Line = Lines()
It's creating a new Lines object then telling it to run an action. Since it's new, it hasn't been added to the scene so it isn't drawn or acted on.
You should be getting an existing Lines object and modifying that instead of creating a new one.
As a side note, the common convention for naming methods and variables is to use camelCase which means MoveUpOrDown should be moveUpOrDown. On the other hand SnakeCase is used For classes structs and protocols so SpriteComponent is current. That allows you to know at a glance whether your working with a type or a variable.

Inconsistent contact detection in Swift 3 using SpriteKit

I'm having an issue with contact detection in Swift 3 using SpriteKit. The contact detection is working...sometimes. It seems purely random as to when it fires and when it doesn't.
I have a yellow "bullet" that moves up on the screen to hit a red sprite named targetSprite. The desired behavior is to have the bullet removed when it hits the target, but sometimes it just passes through underneath.
I've found many questions about contact detection not working at all, but I haven't found any dealing with inconsistent detection.
What can I do to fix this?
Here's the code:
import SpriteKit
import GameplayKit
enum PhysicsCategory:UInt32 {
case bullet = 1
case sprite1 = 2
case targetSprite = 4
// each new value should double the previous
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create sprites
let sprite1 = SKSpriteNode(color: SKColor.blue, size: CGSize(width:100,height:100))
let targetSprite = SKSpriteNode(color: SKColor.red, size: CGSize(width:100,height:100))
let bullet = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 20, height: 20))
// show the bullet?
var isShowingBullet = true
// Timers
//var timer:Timer? = nil
var fireBulletTimer:Timer? = nil
// set up bullet removal:
var bulletShouldBeRemoved = false
let bulletMask = PhysicsCategory.bullet.rawValue
override func didMove(to view: SKView) {
// Physics
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
targetSprite.physicsBody?.affectedByGravity = false
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.centerRect.size)
bullet.physicsBody?.affectedByGravity = false
// Contact Detection:
targetSprite.physicsBody?.categoryBitMask = PhysicsCategory.targetSprite.rawValue
targetSprite.physicsBody?.contactTestBitMask =
//PhysicsCategory.sprite1.rawValue |
PhysicsCategory.bullet.rawValue
targetSprite.physicsBody?.collisionBitMask = 0 // no collision detection
// bullet physics
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet.rawValue
bullet.physicsBody?.contactTestBitMask =
PhysicsCategory.targetSprite.rawValue
bullet.physicsBody?.collisionBitMask = 0 // no collision detection
// execute once:
fireBulletTimer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.fireBullet),
userInfo: nil,
repeats: false)
// Add sprites to the scene:
self.addChild(sprite1)
self.addChild(bullet)
self.addChild(targetSprite)
// Positioning
targetSprite.position = CGPoint(x:0, y:300)
// Note: bullet and sprite1 are at 0,0 by default
// Delegate
self.physicsWorld.contactDelegate = self
}
func didBegin(_ contact: SKPhysicsContact) {
print("didBegin(contact:))")
//let firstBody:SKPhysicsBody
// let otherBody:SKPhysicsBody
// Use 'bitwise and' to see if both bits are 1:
if contact.bodyA.categoryBitMask & bulletMask > 0 {
//firstBody = contact.bodyA
//otherBody = contact.bodyB
print("if contact.bodyA....")
bulletShouldBeRemoved = true
}
else {
//firstBody = contact.bodyB
//otherBody = contact.bodyA
print("else - if not contacted?")
}
/*
// Find the type of contact:
switch otherBody.categoryBitMask {
case PhysicsCategory.targetSprite.rawValue: print(" targetSprite hit")
case PhysicsCategory.sprite1.rawValue: print(" sprite1 hit")
case PhysicsCategory.bullet.rawValue: print(" bullet hit")
default: print(" Contact with no game logic")
}
*/
} // end didBegin()
func didEnd(_ contact: SKPhysicsContact) {
print("didEnd()")
}
func fireBullet() {
let fireBulletAction = SKAction.move(to: CGPoint(x:0,y:500), duration: 1)
bullet.run(fireBulletAction)
}
func showBullet() {
// Toggle to display or not, every 1 second:
if isShowingBullet == true {
// remove (hide) it:
bullet.removeFromParent()
// set up the toggle for the next call:
isShowingBullet = false
// debug:
print("if")
}
else {
// show it again:
self.addChild(bullet)
// set up the toggle for the next call:
isShowingBullet = true
// debug:
print("else")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bulletShouldBeRemoved {
bullet.removeFromParent()
}
}
}
Sorry for the inconsistent indentation, I can't seem to find an easy way to do this...
EDIT:
I have found that using 'frame' instead of 'centerRect' makes the collision area the size of the sprite. For example:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
should be:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.frame.size)
First advice - Do not use NSTimer (aka Timer) in SpriteKit. It is not paired with a game loop and can cause different issues in a different situations. Read more here ( answer posted by LearnCocos2D)
So, do this:
let wait = SKAction.wait(forDuration: 1)
run(wait, completion: {
[unowned self] in
self.fireBullet()
})
What I have noticed is that if I run your code in Simulator, I get the behaviour you have described. didBegin(contact:) is being fired randomly. Still, this is not happening on a device for me, and device testing is what matters.
Now, when I have removed Timer and did the same thing with SKAction(s) everything worked, means contact were detected every time.
Have you tried adding
.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true
SpriteKit physics engine will calculate collision correctly if you do following:
1) set "usesPreciseCollisionDetection" property to true for bullet's physics body. This will change collision detection algorithm for this body. You can found more information about this property here, chapter "Working with Collisions and Contacts".
2) move your bullet using applyImpulse or applyForce methods. Collision detection will not woking correctly if you move body by changing it's position manually. You can find more information here, chapter "Making Physics Bodies Move".

How to remove node from parent if touched more than once?

I am making a game in which bullets are shot to the bad guy when the button is pressed. I made a function in which whenever it is called it adds more bad guys.
Here is the code: (This method is called multiple times)
func BadGuyPosition()
let BadGuyCircle = SKSpriteNode(imageNamed: "CircleBadGuy")
BadGuyCircle.zPosition = 1
//var mininmum = self.size.width / 600
let TooMuch = self.size.height - 60
let PointToShow = UInt32(TooMuch)
BadGuyCircle.position = CGPointMake(CGRectGetMaxX(self.frame) + 20 , CGFloat(arc4random_uniform(PointToShow) ))
let action2 = SKAction.moveToX(-100, duration: 5.0)
let remove = SKAction.removeFromParent()
BadGuyCircle.runAction(SKAction.sequence([action2,remove]))
//Physics BadGuy
BadGuyCircle.physicsBody = SKPhysicsBody(rectangleOfSize: BadGuyCircle.size)
BadGuyCircle.physicsBody?.categoryBitMask = Numbering.Badguy
BadGuyCircle.physicsBody?.contactTestBitMask = Numbering.Laser
BadGuyCircle.physicsBody?.affectedByGravity = false
BadGuyCircle.physicsBody?.dynamic = true
self.addChild(BadGuyCircle)
I want it so that the bad guy is removed from the parent if 2 bullets are made in contact with the bad guy.
I got it so that when 1 bullet makes contact with the enemy, it is removed from the parent. (here is the code)
func didBeginContact(contact: SKPhysicsContact) {
let A : SKPhysicsBody = contact.bodyA
let B : SKPhysicsBody = contact.bodyB
if (A.categoryBitMask == Numbering.Badguy) && (B.categoryBitMask == Numbering.Laser) || (A.categoryBitMask == Numbering.Laser) && (B.categoryBitMask == Numbering.Badguy)
{
runAction(BadGuyLostSound)
bulletsTouchedBadGuy(A.node as! SKSpriteNode, Laser: B.node as! SKSpriteNode)
}
}
func bulletsTouchedBadGuy(BadGuy: SKSpriteNode, Laser: SKSpriteNode){
Laser.removeFromParent()
BadGuy.removeFromParent()
}
Can Anyone Please Tell me how can I make so that it would take more than one bullet to make the enemy be removed from parent.
The best way to remove your collided nodes is using the method didFinishUpdate, if you remove or launch a method to remove your node from didBeginContact your game could crash searching a collided node that meanwhile is in the process of being removed..
class BadGuy: SKSpriteNode {
var badGuyBulletCollisionsCounter: Int = 0
init() {
let texture = SKTexture(imageNamed: "CircleBadGuy")
super.init(texture: texture, color: nil, size: texture.size())
...
// fill this part with your BadGuy code
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Declare a global var :
var nodesToRemove = [SKNode]()
In the didBeginContact method:
func didBeginContact(contact: SKPhysicsContact) {
let A : SKPhysicsBody = contact.bodyA
let B : SKPhysicsBody = contact.bodyB
if (A.categoryBitMask == Numbering.Badguy) && (B.categoryBitMask == Numbering.Laser) || (A.categoryBitMask == Numbering.Laser) && (B.categoryBitMask == Numbering.Badguy)
{
badGuy = A.node as! BadGuy
badGuy.badGuyBulletCollisionsCounter += 1
runAction(BadGuyLostSound)
bulletsTouchedBadGuy(badGuy, Laser: B.node as! SKSpriteNode)
}
}
In the bulletsTouchedBadGuy method :
func bulletsTouchedBadGuy(badGuy: BadGuy, laser: SKSpriteNode){
nodesToRemove.append(laser)
if badGuy.badGuyBulletCollisionsCounter == 2 {
nodesToRemove.append(badGuy)
}
}
Finally:
override func didFinishUpdate()
{
nodesToRemove.forEach(){$0.removeFromParent()}
nodesToRemove = [SKNode]()
}
Use the nodes' userData to track it's hit status. When the bullet hits BadGuy, check the nodes' userData to see if it has been hit already. If it has, remove it; if not, indicate that it has :
//Add this when creating BadGuy:
BagGuy.userData = ["HasBeenHit" : false]
func bulletsTouchedBadGuy(BadGuy: SKSpriteNode, Laser: SKSpriteNode){
Laser.removeFromParent()
if BadGuy.userData["HasBeenHit"] == true {
BadGuy.removeFromParent()
} else {
BadGuy.userData["HasBeenHit"] = true
}
or even:
if BadGuy.userData["HasBeenHit"] == true ? BadGuy.removeFromParent() : BadGuy.userData["HasBeenHit"] = true
If you want to move the removeFromParent() to didFinishUpdate() , then simply change the action as follows:
if BadGuy.userData["HasBeenHit"] == true {
BadGuy.userData["HasBeenHitTwice"]
} else {
BadGuy.userData["HasBeenHit"] = true
}
and then in didFinishUpdate() remove all nodes with this value set, or construct an array of nodes to be removed as per the other answer.

Swift SpriteKit Contact check inside the PhysicsBody

I am working on PhysicsBody and get some troubles.
First I don't really now if this is with PhysicsBody possible or only with pixel check.
The problem is: Contact check works pretty well but if a node is inside a PhysicsBody it won't be able to see if it collides.
in the Pictures it is pretty well explained.
Picture 1: This works
Picture 2: This won't
Some ideas how it works?
Maybe PhysicsBody(Green Line) fill with the SKNode?
Here some Code:
(Some explain: if Human moves inside Object and Touched is false nothing happened and it is okay but if Human is moved inside Object and Touched will be True nothing happened too and this is my problem.)
enum myContacts: UInt32 {
case None = 0
case All = 0xFFFFFFFF
case Object = 0b001
case Human = 0b010
}
class whatever...{
let Object = SKSpriteNode(imageNamed: "xyz")
Object.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
Object.physicsBody = SKPhysicsBody(circleOfRadius: Object.size.width/2)
Object.physicsBody?.dynamic = true
Object.physicsBody?.categoryBitMask = myContacts. Object.rawValue
Object.physicsBody?.contactTestBitMask = myContacts.Human.rawValue
Object.physicsBody?.collisionBitMask = 0
Object.physicsBody?.usesPreciseCollisionDetection = true
addChild(Object)
let Human = SKSpriteNode(imageNamed: "zyx")
Human.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
Human.physicsBody = SKPhysicsBody(circleOfRadius: Human.size.width/2)
Human.physicsBody?.dynamic = true
Human.physicsBody?.categoryBitMask = myContacts. Human.rawValue
Human.physicsBody?.contactTestBitMask = myContacts.Object.rawValue
Human.physicsBody?.collisionBitMask = 0
Human.physicsBody?.usesPreciseCollisionDetection = true
addChild(Human)
}
func didBeginContact(contact: SKPhysicsContact) {
if touched {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case myContacts.Object.rawValue | myContacts.Human.rawValue:
print("Human touched")
default:
break
}
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
touched = true
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
touched = false
}
Not sure if I completely understand what you are asking, but if you just want to detect a contact when node is spawned inside the space "taken" by another node, this code will work:
#import "GameScene.h"
static const uint32_t playerCategory = 0x1 << 0;
static const uint32_t obstacleCategory = 0x1 << 1;
#interface GameScene()<SKPhysicsContactDelegate>
#property (nonatomic, strong) SKSpriteNode *player;
#property (nonatomic, strong) SKSpriteNode *obstacle;
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
self.physicsWorld.contactDelegate = self;
[self initializeNodes];
}
-(void)initializeNodes{
self.player = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(50,50)];
self.player.name = #"player";
self.player.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.player.size];
self.player.physicsBody.collisionBitMask = 0;
self.player.physicsBody.categoryBitMask = playerCategory;
self.player.physicsBody.affectedByGravity = NO;
self.player.physicsBody.contactTestBitMask = obstacleCategory;
self.player.zPosition = 10;
[self addChild:self.player];
self.obstacle = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:CGSizeMake(100,100)];
self.obstacle.name = #"obstacle";
self.obstacle.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.obstacle.size];
self.obstacle.physicsBody.collisionBitMask = 0;
self.obstacle.physicsBody.categoryBitMask = obstacleCategory;
self.obstacle.physicsBody.affectedByGravity = NO;
self.obstacle.physicsBody.contactTestBitMask = playerCategory;
self.obstacle.zPosition = 8;
[self addChild:self.obstacle];
}
-(void)didBeginContact:(SKPhysicsContact *)contact{
NSLog(#"Contact detected!");
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
Here, everything is pretty much straight forward:
I make two nodes and assign physics bodies to them
I place those two nodes at the same position
As a result, a contact is detected
If you copy & paste this code and run it, what you are going to see in the console is the message which says : "Contact detected".
I guess that is the same situation from the second picture (where you are saying that contact is not detected).
Also, if you continue to move the player (while a player is inside of an obstacle's bounds), the further contacts will not be detected. Player have to leave the bounds of an obstacle first. But still, you can check the return value of allContactBodies method which returns an array of SKPhysicsBody objects that current body (player's physics body) is in contact with.

Sprite Kit physicsBody.resting behavior

I am using Swift and Sprite Kit to develop a game on XCode Beta 6.
In order to detect if all nodes are sleeping, i check their physicsBody.resting property.
In update method i print out the result.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var hero:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFromRect:self.frame)
hero = SKSpriteNode(imageNamed: "Spaceship")
hero.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
hero.zPosition = 10.0
hero.physicsBody = SKPhysicsBody(circleOfRadius: hero.size.width/2)
hero.physicsBody.allowsRotation = false
hero.physicsBody.linearDamping = 0.5
self.addChild(hero)
}
override func update(currentTime: CFTimeInterval) {
if hero.physicsBody.resting {
println("resting")
} else {
println("moving")
}
}
}
To my surprise, the results are:
moving
resting
moving
(n times the same)
moving
resting
So why the hero is moving, although i didn't do anything. The node moves N times and takes a break(resting), after that goes on moving.
Can anyone explain that behaviour? Is that a bug or do i miss something? Thanks in advance.
If you examine the velocity of a physics body, you'll see that it is indeed moving but at a rate that is not perceivable. That's why the resting property is not set. A more reliable way to check if a SKPhysicsBody is at rest is to test if its linear and angular speeds are nearly zero. Here's an example of how to do that:
func speed(velocity:CGVector) -> Float {
let dx = Float(velocity.dx);
let dy = Float(velocity.dy);
return sqrtf(dx*dx+dy*dy)
}
func angularSpeed(velocity:CGFloat) -> Float {
return abs(Float(velocity))
}
// This is a more reliable test for a physicsBody at "rest"
func nearlyAtRest(node:SKNode) -> Bool {
return (self.speed(node.physicsBody.velocity)<self.verySmallValue
&& self.angularSpeed(node.physicsBody.angularVelocity) < self.verySmallValue)
}
override func update(_ currentTime: TimeInterval) {
/* Enumerate over child nodes with names starting with "circle" */
enumerateChildNodesWithName("circle*") {
node, stop in
if (node.physicsBody.resting) {
println("\(node.name) is resting")
}
if (self.nearlyAtRest(node)) {
println("\(node.name) is nearly resting")
}
}
}