Swift SpriteKit Contact check inside the PhysicsBody - swift

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.

Related

Why my node is invisible

I'm currently trying to make a node that can change it's texture, but it's not visible on the simulator. I know it's there, cause I set showNodesCount to true and it displays that 2 nodes are on scene. It's displaying a cannon node, but not rgyBox node, that should change it's texture if I tap on cannon node. Here is my code:
class GameScene: SKScene {
var gameOver = false
let cannon = SKSpriteNode(imageNamed: "cannon")
let rgyArray = ["redBox", "greenBox", "yellowBox"]
var rgyBlock = SKSpriteNode()
func changeRgyColor() {
var randomRGY = Int(arc4random_uniform(3))
rgyBlock.texture = SKTexture(imageNamed: rgyArray[randomRGY])
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
cannon.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addChild(cannon)
rgyBlock.texture = SKTexture(imageNamed: rgyArray[1])
rgyBlock.position = CGPointMake(self.cannon.position.x, self.cannon.position.y + 20)
self.addChild(rgyBlock)
rgyBlock.zPosition = 10
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.cannon {
changeRgyColor()
}
}
}
What am I doing wrong?
You need to recreate the rgyBlock by calling rgyBlock = SKSpriteNode(imageNamed: rgyArray[1]). Where it is defined at the top, it is defined as nothing. You then need to recreate it in didMoveToView. You can remove the texture set in didMoveToView for the rgyBlock.
You could do it a different way, and put imageNamed: "aTextureNameHere" into the define of rgyBlock. This will give rgyBlock a value. Then you can set the texture (as you already do) in didMoveToView. This way may be quicker and easier.

swift - Jump only when landed

I'm looking to restrict my character (cat), to only jump when it's either on the ground (dummy SKNode), or when on the tree (treeP SKNode).
Currently I don't have any restrictions to touchesBegan and as a result the cat is able to fly through the air if the user clicks in quick succession, whilst this could be useful in other games it's not welcome here.
If anyone could help me I'd be really happy.
What i would like to do but have no experience would be to enable a click (jump), if the cat was in contact with either dummy or tree and likewise disable clicks if not in contact with either dummy or tree.
Here is everything that may be of use....
class GameScene: SKScene, SKPhysicsContactDelegate {
let catCategory: UInt32 = 1 << 0
let treeCategory: UInt32 = 1 << 1
let worldCategory: UInt32 = 1 << 1
override func didMoveToView(view: SKView) {
// part of cat code
cat = SKSpriteNode(texture: catTexture1)
cat.position = CGPoint(x: self.frame.size.width / 2.2, y: self.frame.size.height / 7.0 )
cat.physicsBody?.categoryBitMask = catCategory
cat.physicsBody?.collisionBitMask = crowCategory | worldCategory
cat.physicsBody?.contactTestBitMask = crowCategory | contact2Category
// part of the ground code
var dummy = SKNode()
dummy.position = CGPointMake(0, groundTexture.size().height / 2)
dummy.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, groundTexture.size().height))
dummy.physicsBody!.dynamic = false
dummy.physicsBody?.categoryBitMask = worldCategory
dummy.physicsBody?.collisionBitMask = 0
dummy.physicsBody?.contactTestBitMask = 0
moving.addChild(dummy)
// part of the tree code
func spawnTrees() {
var treeP = SKNode()
treeP.position = CGPointMake( self.frame.size.width + treeTexture1.size().width * 2, 0 );
treeP.zPosition = -10;
var height = UInt32( self.frame.size.height / 4 )
var y = arc4random() % height;
var tree1 = SKSpriteNode(texture: treeTexture1)
tree1.position = CGPointMake(0.0, CGFloat(y))
tree1.physicsBody = SKPhysicsBody(rectangleOfSize: tree1.size)
tree1.physicsBody?.dynamic = false
tree1.physicsBody?.categoryBitMask = treeCategory;
tree1.physicsBody?.collisionBitMask = 0
tree1.physicsBody?.contactTestBitMask = 0
treeP.addChild(tree1)
treeP.runAction(moveAndRemoveTrees)
trees.addChild(treeP)
}
// all of touchesBegan
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
if (moving.speed > 0){
cat.physicsBody!.velocity = CGVectorMake(0, 0)
cat.physicsBody!.applyImpulse(CGVectorMake(0, 20))
} else if (canRestart) {
self.resetScene()
}
}
You can use some custom boolean variable called isOnTheGround in order to restrict jumping while in the air. This variable need to be updated by you.Note that character is not necessarily on the ground just if it is in contact with "platform" (eg. side contact can occur). So, it's up to you to define what means "on the ground". When you define that, then it's easy:
Hint: You can compare character's y position with platform's y position to see is character is really on the platform.
Assuming that both character's and platform's anchor point are unchanged (0.5,0.5), to calculate character's legs y position you can do something like this (pseudo code):
characterLegsY = characterYPos - characterHeight/2
And to to get platform's surace:
platformSurfaceYPos = platformYPos + platformHeight/2
Preventing jumping while in the air
In touchesBegan:
if isOnTheGround -> jump
In didEndContact:
here you set isOnTheGround to false (character ended contact with ground/platform)
In didBeginContact:
check if character is really on the ground
set isOnTheGround variable to true
Here you can find an example of something similar...
I recently had this problem. This is how I fixed it. In the game scene create this variable:
var ableToJump = true
Then in the update method put this code:
if cat.physicsBody?.velocity.dy == 0 {
ableToJump = true
}
else {
ableToJump = false
}
The above code will work because the update method is ran every frame of the game. Every frame it checks if your cat is moving on the y axis. If you do not have an update method, just type override func update and it should autocomplete.
Now the last step is put this code in the touchesBegan:
if ableToJump == true {
cat.physicsBody!.applyImpulse(CGVectorMake(0,40))
}
Note: You may have to tinker with the impulse to get desired jump height

How to pause an SKSpriteNode, Swift

I created this game using sprite kit. During the game sprite nodes are moving. When the "Game Over" Label pops up, I would like the monster sprite node to stop moving or pause, but the rest of the scene to still move on. I know where to put the code, I just don't know how to write it. This is my monster code.
func addMonster() {
// Create sprite
let monster = SKSpriteNode(imageNamed: "box")
monster.setScale(0.6)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.dynamic = true
monster.physicsBody?.categoryBitMask = UInt32(monsterCategory)
monster.physicsBody?.contactTestBitMask = UInt32(laserCategory)
monster.physicsBody?.collisionBitMask = 0
monster.name = "box"
var random : CGFloat = CGFloat(arc4random_uniform(320))
monster.position = CGPointMake(random, self.frame.size.height + 20)
self.addChild(monster)
}
EDIT
override func update(currentTime: CFTimeInterval) {
if isStarted == true {
if currentTime - self.lastMonsterAdded > 1 {
self.lastMonsterAdded = currentTime + 3.0
self.addMonster()
}
self.moveObstacle()
} else {
}
self.moveBackground()
if isGameOver == true {
}
}
If I understand your question correctly, you want to pause a single (or small number of) nodes. In that case...
monster.paused = true
Is probably what you want.

Swift SpriteKit Physics Collision Issue

I am making a simple physics based game. Everything is working normally with exception to collision detection. It feels like the didBeginContact method is being ignored.
I have tried several ways of configuring the "PhysicsCategory" struct (even using enum) and several formations of the bodyA/bodyB contact statements.
I am all out of ideas. I can get the 2 objects to collide but they just bounce off each other. There are no errors and nothing logged to the console. I hope that I have made a trivial mistake that I am overlooking.
Below is all the pertinent code. In case it matters... setupPhysics() is being called in didMoveToView
PhysicsCategory Struct
struct PhysicsCategory {
static let None: UInt32 = 0
static let Fish: UInt32 = 0b1
static let Bird: UInt32 = 0b10
static let BottomEdge: UInt32 = 0b100}
Physics Setup Method
//MARK: - Physics Methods
func setupPhysics() {
/* Physics World */
physicsWorld.gravity = CGVectorMake(0, -9.8)
physicsWorld.contactDelegate = self
physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
/* Bottom Collision Rect */
let bEdge = CGRect(x: CGPointZero.x, y: CGPointZero.y, width: size.width, height: size.height * 0.005)
let bottomEdge = SKShapeNode(rect: bEdge)
bottomEdge.physicsBody = SKPhysicsBody(edgeLoopFromRect: bEdge)
bottomEdge.physicsBody!.categoryBitMask = PhysicsCategory.BottomEdge
bottomEdge.physicsBody!.collisionBitMask = PhysicsCategory.Fish
bottomEdge.physicsBody!.dynamic = false
gameLayer.addChild(bottomEdge)
/* Fish */
fish.physicsBody = SKPhysicsBody(circleOfRadius: blowfish.size.height / 2.1)
fish.physicsBody!.allowsRotation = false
fish.physicsBody!.categoryBitMask = PhysicsCategory.Fish
fish.physicsBody!.collisionBitMask = PhysicsCategory.Bird | PhysicsCategory.BottomEdge
/* Left Random Bird */
randomLeftBird.physicsBody = SKPhysicsBody(rectangleOfSize: randomLeftBird.size)
randomLeftBird.physicsBody!.affectedByGravity = false
randomLeftBird.physicsBody!.allowsRotation = false
randomLeftBird.physicsBody!.categoryBitMask = PhysicsCategory.Bird
randomLeftBird.physicsBody!.collisionBitMask = PhysicsCategory.Fish
/* Random Right Bird */
randomRightBird.physicsBody = SKPhysicsBody(rectangleOfSize: randomRightBird.size)
randomRightBird.physicsBody!.affectedByGravity = false
randomRightBird.physicsBody!.allowsRotation = false
randomRightBird.physicsBody!.categoryBitMask = PhysicsCategory.Bird
randomRightBird.physicsBody!.collisionBitMask = PhysicsCategory.Fish
}
didBeginContact Setup
func didBeginContact(contact: SKPhysicsContact!) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == PhysicsCategory.Fish | PhysicsCategory.Bird {
println("COLLISON WITH BIRD!")
updateLives()
} else if collision == PhysicsCategory.Fish | PhysicsCategory.BottomEdge {
println("EPIC FAIL!")
}
}
I don't see any use of contactTestBitMask in your code. That one controls whether you get contact delegate messages — collisionBitMask just controls whether they collide (bounce off).
These are separate so that you can get contact delegate messages even for categories that don't bounce off each other, but it means you also can have collisions that don't send messages. (That can be a good thing if you don't want game logic for every kind of collision.) Any that you do want contact messages for you need to explicitly request.

SpriteKit: cannot change node position in contact callback

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