SpriteKit SKShapeNode stops prematurely when colliding due to gravity - ios

This is the setup
// Create a scene
scene = SKScene(size: self.view.frame.size)
let sceneHeight = scene.frame.height
let sceneWidth = scene.frame.width
// Create nodes
// Rectangle
let shapeWidth: CGFloat = 100
let shapeHeight = shapeWidth
let shapeNode = SKShapeNode(rect: CGRectMake(0, 0, shapeWidth, 100))
shapeNode.position = CGPoint(x: sceneWidth/2 - shapeWidth/2, y: 300)
shapeNode.fillColor = UIColor.redColor()
shapeNode.strokeColor = UIColor.redColor()
// Floor
let floorWidth: CGFloat = sceneWidth
let floorHeight: CGFloat = 10
let floorNode = SKShapeNode(rect: CGRectMake(0, 0, floorWidth, floorHeight))
floorNode.position = CGPoint(x: 0, y: 100)
floorNode.fillColor = UIColor.greenColor()
floorNode.strokeColor = UIColor.greenColor()
// Create the node tree
scene.addChild(floorNode)
scene.addChild(shapeNode)
Then I define and add two physics bodies to my two nodes.
// Create physics
let shapePhysicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: shapeWidth, height: shapeHeight))
shapePhysicsBody.dynamic = true
shapePhysicsBody.categoryBitMask = PhysicsCategory.Shape
shapePhysicsBody.contactTestBitMask = PhysicsCategory.Floor
shapePhysicsBody.collisionBitMask = PhysicsCategory.None
shapePhysicsBody.usesPreciseCollisionDetection = true
let floorPhysicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: floorWidth, height: floorHeight))
floorPhysicsBody.dynamic = true
floorPhysicsBody.categoryBitMask = PhysicsCategory.Floor
floorPhysicsBody.contactTestBitMask = PhysicsCategory.Shape
floorPhysicsBody.collisionBitMask = PhysicsCategory.None
floorPhysicsBody.usesPreciseCollisionDetection = true
floorPhysicsBody.affectedByGravity = false
// Add physics
shapeNode.physicsBody = shapePhysicsBody
floorNode.physicsBody = floorPhysicsBody
scene.physicsWorld.gravity = CGVector(dx: 0, dy: -1)
scene.physicsWorld.contactDelegate = self
This sort of works, that is the rectangle shape does fall from it's initial position and the collision delegate is called when a collision happens.
The contents of my didBeginContact: method.
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody
var secondBody : SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & PhysicsCategory.Floor != 0) && (secondBody.categoryBitMask & PhysicsCategory.Shape != 0)) {
secondBody.dynamic = false
println("collision")
}
}
But the shape node stops way before there's a visual contact. I hope these images give an impression of how that looks.
Starts here
This is where the contact happens
Why is there such a gap between them? What am I missing? I want them to visually touch each other before the rectangle stops moving.

SKShapeNode(rect:) draws the SKShapeNode with the node.position as origin. For the SKPhysicsBody position corresponds to the center of the rectangle. This creates an offset between the physics body and the actual node. You can see that if you enable
scene.view?.showsPhysics = true.
You can correct the offset by creating the SKShapeNode using following code.
let shapeNode = SKShapeNode(rect: CGRectMake(-shapeWidth/2, -50, shapeWidth, 100))
// Other code
let floorNode = SKShapeNode(rect: CGRectMake(-floorWidth/2, -floorHeight/2, floorWidth, floorHeight))

Related

SKShapeNode collision detection in Sprite kit not detecting collision

I am trying to achieve collision detection between a SKShapeNode(a circlular node) and SKShapeNode(a square node), just like the app "Snake VS Blocks".
Below is my code snippet of my node creations
Snake Creation
private func addSnake() {
snake = SKShapeNode(circleOfRadius: Constants.snakeRadius)
snake.fillColor = Constants.snakeColor
let snakeXPosition = (Helper.getScreenWidth() / 2.0) - Constants.snakeRadius
snake.position = CGPoint(x: snakeXPosition, y: size.height * 0.3)
snake.physicsBody = SKPhysicsBody(rectangleOf: snake.frame.size)
snake.physicsBody?.isDynamic = false
snake.physicsBody?.categoryBitMask = PhysicsCategory.snake
snake.physicsBody?.contactTestBitMask = PhysicsCategory.block
snake.physicsBody?.collisionBitMask = PhysicsCategory.none
snake.name = "Snake"
addChild(snake)
}
Block Creation
func addBlocks() {
for index in 0...(blockCount-1) {
let hasBlock = getRandomBool()
if hasBlock {
let block = getBlock(for: index)
block.fillColor = .red
addChild(block)
let actionMove = getBlockMoveAction(for: block, atIndex: index)
let actionMoveDone = SKAction.removeFromParent()
block.run(SKAction.sequence([actionMove, actionMoveDone]))
}
}
}
func getBlockMoveAction(for block: SKShapeNode, atIndex: Int) -> SKAction {
return SKAction.move(to: CGPoint(x: CGFloat(atIndex) * width, y: -block.frame.size.height),
duration: TimeInterval(blockMovementDuration))
}
func getBlock(for index: Int) -> SKShapeNode {
let rect = CGRect(x: 0, y: 0, width: width, height: width)
let block = SKShapeNode(rect: rect, cornerRadius: Constants.blockCornerRadius)
block.name = "Block"
block.position = CGPoint(x: CGFloat(index) * width, y: (Helper.getScreenHeight() + width))
block.physicsBody = SKPhysicsBody(rectangleOf: block.frame.size)
block.physicsBody?.isDynamic = false
block.physicsBody?.categoryBitMask = PhysicsCategory.block
block.physicsBody?.contactTestBitMask = PhysicsCategory.snake
block.physicsBody?.collisionBitMask = PhysicsCategory.none
return block
}
func getRandomBool() -> Bool {
return arc4random_uniform(2) == 0
}
Below is the image of the outcome of above code:
My first problem is, I don't know why, but when I enabled, skview to show physics,
view.showsPhysics = true
i found out my view is not in correct frame
Secondly, I have confirmed to contact delegate as well
physicsWorld.contactDelegate = self
But my
didBegin(_ contact: SKPhysicsContact)
is not getting triggered
collision detection code:
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA //snake
secondBody = contact.bodyB //block
} else {
firstBody = contact.bodyB //block
secondBody = contact.bodyA //snake
}
if ((firstBody.categoryBitMask & PhysicsCategory.snake != 0) &&
(secondBody.categoryBitMask & PhysicsCategory.block != 0)) {
if let snake = firstBody.node as? SKShapeNode,
let block = secondBody.node as? SKShapeNode {
snakeDidCollideWithBlock(snake: snake, block: block)
}
}
}
func snakeDidCollideWithBlock(snake: SKShapeNode, block: SKShapeNode) {
print("Hit")
block.removeFromParent()
}
}
I went through other questions in stackoverflow searching for similar problem, but didnt find any solution.
Either one or both "isDynamic" should be true. Otherwise, they will behave like a wall and never participate in a physical simulation.

Struggling with collision detection in Swift/SpriteKit

I'm having a play with game programming in Swift/Xcode and am struggling getting collision to work.
I'm using Xcode 8 Beta 3 (because I couldn't use Xcode 7 on MacOS Sierra) so its using the new Swift 3.0 Syntax. The game has a little puppy (defined in a SK Scene file) that jumps and collects thrown items from the right. The thrown item is created in a function and removed when a new item is thrown.
I need contact with the thrown item comes into contact with the puppy. I've got physics turned on and the item bounces off the puppy but no contact is registered.
Heres my code. Appologies for the mess of code but its very first attempt at Swift and everything just thrown in for now. I can't see what I'm doing wrong :(
UPDATE: As cocoajoe suggested, I've tried setting the masks to 1 and have simplified the code to remove references to the gamescene.sks file and created everything programmatically but its still not working. I've updated the code below to reflect the latest version of the code...
import SpriteKit
import AVFoundation
class GameScene: SKScene, SKPhysicsContactDelegate {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder) is not used in this app")
}
override init(size: CGSize) {
super.init(size: size)
anchorPoint = CGPoint(x: 0.0, y: 0.0)
}
var isJumping = false
var gamePaused = false
var gameRunning = false
var playerScore = 0
var lastSpawnTime = 0
var lastTick = 0
var spawnDelay = 4
let PuppyCategory : UInt32 = 0x1 << 0
let ThrowableCategory : UInt32 = 0x1 << 1
let GroundCategory : UInt32 = 0x1 << 2
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0.0, dy: -9.8)
physicsWorld.contactDelegate = self
let background = SKSpriteNode(imageNamed: "bg")
background.anchorPoint = CGPoint(x: 0.0, y:0.0)
background.size = size
addChild(background)
// Set up Ground Object
let Ground = SKSpriteNode()
Ground.name = "Ground"
Ground.size = CGSize(width:frame.width, height: frame.height / 10)
Ground.position = CGPoint(x: frame.midX, y: Ground.frame.height / 2 )
Ground.zPosition = -20
Ground.color = SKColor.white()
// Set up the Physics
Ground.physicsBody = SKPhysicsBody(rectangleOf: Ground.size)
Ground.physicsBody!.categoryBitMask = GroundCategory
Ground.physicsBody!.contactTestBitMask = PuppyCategory | ThrowableCategory
Ground.physicsBody!.collisionBitMask = PuppyCategory | ThrowableCategory
Ground.physicsBody?.affectedByGravity = false
Ground.physicsBody?.allowsRotation = false
Ground.physicsBody?.isDynamic = false
Ground.physicsBody?.mass = 1.99999
// Initialise the Node
addChild(Ground)
// Create a Puppy
let Puppy = SKSpriteNode(imageNamed: "puppy1")
Puppy.name = "Puppy"
Puppy.position = CGPoint(x: frame.width / 10, y: frame.height / 2)
Puppy.size = CGSize(width: frame.width / 7, height: frame.height / 5)
Puppy.zPosition = 3
// Set up the Physics
Puppy.physicsBody = SKPhysicsBody(rectangleOf: Puppy.size)
Puppy.physicsBody!.categoryBitMask = PuppyCategory
Puppy.physicsBody!.contactTestBitMask = ThrowableCategory | GroundCategory
Puppy.physicsBody!.collisionBitMask = ThrowableCategory | GroundCategory
Puppy.physicsBody?.affectedByGravity = true
Puppy.physicsBody?.allowsRotation = false
Puppy.physicsBody?.isDynamic = true
Puppy.physicsBody?.mass = 1.99999
// Set up the Textures/Animation Frames
var TextureAtlas = SKTextureAtlas()
var TextureArray = [SKTexture]()
TextureAtlas = SKTextureAtlas(named: "puppy")
for i in 1...TextureAtlas.textureNames.count {
let Name = "puppy\(i).png"
TextureArray.append(SKTexture(imageNamed: Name))
}
Puppy.run(SKAction.repeatForever(SKAction.animate(with:TextureArray, timePerFrame: 0.1)))
// Add the Sprite
addChild(Puppy)
let gameMessage = SKSpriteNode(imageNamed: "TapToPlay")
gameMessage.name = "GameMessage"
gameMessage.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
gameMessage.zPosition = 30
gameMessage.setScale(0.0)
addChild(gameMessage)
let gameLogo = SKSpriteNode(imageNamed: "pjlogo")
gameLogo.name = "GameLogo"
gameLogo.size = CGSize(width: frame.width / 3, height: frame.height / 5)
gameLogo.position = CGPoint(x: frame.midX, y: (frame.maxY / 100) * 80)
gameLogo.zPosition = 30
addChild(gameLogo)
let scale = SKAction.scale(to: 1.0, duration: 1)
scene?.childNode(withName: "GameMessage")!.run(scale)
if let musicURL = Bundle.main.urlForResource("bgmusic", withExtension: "mp3") {
var backgroundMusic: SKAudioNode!
backgroundMusic = SKAudioNode(url: musicURL)
addChild(backgroundMusic)
}
startNewGame()
}
func puppyJump() {
if gameRunning {
let Puppy = childNode(withName: "Puppy")
if !isJumping {
Puppy?.physicsBody?.applyImpulse(CGVector(dx: 0,dy: 1400))
isJumping = true
}
}
}
func generateThrowable() {
print("new enemy")
let oldItem = childNode(withName: "throwableItem")
oldItem?.removeFromParent()
let throwableItem = SKSpriteNode(imageNamed: "puppy1")
throwableItem.name = "throwableItem"
throwableItem.position = CGPoint(x: frame.maxX, y: frame.midY)
throwableItem.zPosition = 3
throwableItem.setScale(0.1)
throwableItem.physicsBody = SKPhysicsBody(rectangleOf: throwableItem.size)
throwableItem.physicsBody!.categoryBitMask = ThrowableCategory
throwableItem.physicsBody!.contactTestBitMask = PuppyCategory | GroundCategory
throwableItem.physicsBody!.collisionBitMask = PuppyCategory | GroundCategory
throwableItem.physicsBody?.friction = 0.1
throwableItem.physicsBody?.restitution = 0.1
throwableItem.physicsBody?.mass = 0.01
throwableItem.physicsBody?.affectedByGravity = true
throwableItem.physicsBody?.allowsRotation = true
throwableItem.physicsBody?.isDynamic = true
addChild(throwableItem)
throwableItem.physicsBody?.applyImpulse(CGVector(dx: -7,dy: 4))
let throwableTrail = SKEmitterNode(fileNamed: "particleTrail.sks")
throwableTrail?.name = "throwableTrail"
throwableTrail?.targetNode = scene
throwableItem.addChild(throwableTrail!)
}
func startNewGame() {
let logo = childNode(withName: "GameLogo")
let gameMessage = childNode(withName: "GameMessage")
logo?.removeFromParent()
gameMessage?.removeFromParent()
let gameScore = SKLabelNode(fontNamed: "Arial")
gameScore.name = "GameScore"
gameScore.text = "Score:0"
gameScore.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.right
gameScore.fontSize = 20
gameScore.position = CGPoint(x: frame.maxX - 20, y: frame.maxY - 26)
gameScore.zPosition = 30
addChild(gameScore)
let pauseButton = SKLabelNode(fontNamed: "Arial")
pauseButton.name = "pauseButton"
pauseButton.text = "PAUSE"
pauseButton.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.left
pauseButton.fontSize = 20
pauseButton.position = CGPoint(x: 20, y: frame.maxY - 26)
pauseButton.zPosition = 30
addChild(pauseButton)
gameRunning = true
}
func didBeginContact(contact: SKPhysicsContact) {
print("contact")
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
// 2
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
// 3
if firstBody.categoryBitMask == PuppyCategory && secondBody.categoryBitMask == GroundCategory {
print("Hit bottom. First contact has been made.")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if gameRunning {
let gameScore = childNode(withName: "GameScore") as? SKLabelNode
gameScore?.text = "Score:\(playerScore)"
let currentTick:Int = Int(ceil(currentTime))
if lastTick < currentTick {
lastTick = currentTick
}
if (lastSpawnTime + spawnDelay) < currentTick {
// Time to Spawn new Bad Guy
generateThrowable()
lastSpawnTime = currentTick
}
}
}
}
I haven't checked other parts. (Your code still needs plenty of resources...)
But this line:
func didBeginContact(contact: SKPhysicsContact) {
needs to be changed as:
func didBegin(_ contact: SKPhysicsContact) {
in Swift 3. (Which is suggested by Xcode, with just typing didBegin.)
Please try.

Contact method for a frame

I am trying to implement a contact method to know when my player block has touched the yellow frame. Below is the code I have so far. When i drag the player block to the yellow frames nothing happens.
import SpriteKit
import iAd
struct BitMask {
static let player: UInt32 = 0x1 << 0
static let obstacle: UInt32 = 0x1 << 1
static let frame: UInt32 = 0x1 << 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
let player = SKShapeNode(rectOfSize: CGSize(width: 30, height: 30))
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
// setup scene's physics body (setup the walls)
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
// get current screen size
let screenSize: CGRect = UIScreen.mainScreen().bounds
print("screenWidth: \(screenSize.width) screenHeight: \(screenSize.height)")
// setup play scene
let playScreen = SKSpriteNode(color: .clearColor(), size: CGSize(width: 370, height: 370))
// y position adjustment
let yFrame = 65
playScreen.position = CGPoint(x: frame.midX, y: frame.midY - CGFloat(yFrame))
// create the rectangle which will represent physics body.
let rect = CGRect(origin: CGPoint(x: -playScreen.size.width/2, y: -playScreen.size.height/2), size: playScreen.size)
// apply physics conditions to the play scene
playScreen.physicsBody = SKPhysicsBody(edgeLoopFromRect: rect)
playScreen.physicsBody?.friction = 0
// add play scene to the frame
addChild(playScreen)
let playerScreenFrame = SKShapeNode(rectOfSize: CGSize(width: playScreen.size.width, height: playScreen.size.height))
playerScreenFrame.position = CGPoint(x: frame.midX, y: frame.midY - CGFloat(yFrame))
playerScreenFrame.fillColor = .clearColor()
playerScreenFrame.strokeColor = UIColor(rgba: "#E9BD00")
playerScreenFrame.lineWidth = 3;
addChild(playerScreenFrame)
let bottomRect = CGRectMake(frame.midX, frame.midY - CGFloat(yFrame), playScreen.size.width, playScreen.size.height)
let bottom = SKNode()
bottom.physicsBody = SKPhysicsBody(edgeLoopFromRect: bottomRect)
addChild(bottom)
bottom.physicsBody!.categoryBitMask = BitMask.frame
bottom.physicsBody!.contactTestBitMask = BitMask.player
bottom.physicsBody!.collisionBitMask = BitMask.player
// set up player block
player.name = "player"
player.fillColor = UIColor(rgba: "#E9BD00")
player.strokeColor = .blackColor()
player.position = CGPoint(x:frame.size.width/2, y: frame.size.height/2 - CGFloat(yFrame))
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 30, height: 30))
player.physicsBody!.dynamic = false
player.physicsBody!.friction = 0
player.physicsBody!.affectedByGravity = false
player.physicsBody!.allowsRotation = false
addChild(player)
player.physicsBody!.categoryBitMask = BitMask.player
player.physicsBody!.contactTestBitMask = BitMask.obstacle | BitMask.frame
player.physicsBody!.collisionBitMask = BitMask.obstacle | BitMask.frame
// set gravity to 0
physicsWorld.gravity = CGVectorMake(0,0)
// set the scene as the delegate to be notified when two bodies collide
physicsWorld.contactDelegate = self
}
my contact method is as follows:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask == BitMask.player) && (secondBody.categoryBitMask == BitMask.obstacle) {
// do your thing
print("Player contacted with obstacle")
self.view?.paused = true
}
if (firstBody.categoryBitMask == BitMask.player) && (secondBody.categoryBitMask == BitMask.frame) {
// do your thing
print("Player contacted with frame")
self.view?.paused = true
}
}
Currently you are trying to check for contact between two static bodies, but SpriteKit will not generate any contact events unless at least one body is dynamic.
In your case, the reason for this is related to the fact that edge-based physics bodies are static by default, and your player is static as well, means no contacts.
To fix this, you should set dynamic property on player's physics body to true, like this:
player.physicsBody!.dynamic = true

Contact physics Gap in SpriteKit (Swift)

I am working on my first swift iOS sprite kit project but I've run into a problem that I just can't find a solution to. My goal is to have a ball land on a moving platform and the ball and platform both stop upon contact.
The issue I am running into is that there is consistently a gap between the platform and ball when the code recognizes they are in contact and makes them stop. See the attached photo:
Image Here
Ball:
//Ball Node
func createPlayer() -> SKNode {
let playerNode = SKNode()
playerNode.position = CGPoint(x: self.size.width / 2, y: self.size.height * 0.25)
let sprite = SKSpriteNode(imageNamed: "ball")
sprite.setScale(scaleFactor)
playerNode.addChild(sprite)
playerNode.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width / 2)
playerNode.physicsBody?.dynamic = false
playerNode.physicsBody?.allowsRotation = false
playerNode.physicsBody?.restitution = 0.0
playerNode.physicsBody?.friction = 0.0
playerNode.physicsBody?.angularDamping = 0.0
playerNode.physicsBody?.linearDamping = 0.0
playerNode.physicsBody?.usesPreciseCollisionDetection = true
playerNode.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Player
playerNode.physicsBody?.collisionBitMask = 0
playerNode.physicsBody?.contactTestBitMask = CollisionCategoryBitmask.Platform | CollisionCategoryBitmask.Vortex
return playerNode
Platform:
//Platform
sprite = SKSpriteNode(imageNamed: "platform")
let node = PlatformNode()
let thePosition = CGPoint(x: position.x * scaleFactor - (sprite.size.width / 1.999), y: position.y + (self.size.height * 0.50))
sprite.setScale(scaleFactor)
node.addChild(sprite)
node.physicsBody?.usesPreciseCollisionDetection = true
node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
node.physicsBody?.dynamic = false
node.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Platform
node.physicsBody?.collisionBitMask = 0
return node
didBeginContact:
func didBeginContact(contact: SKPhysicsContact) {
var updateHUD = true
maxPlayerY = Int(player.position.y)
let whichNode = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
//If the ball is falling
if player.physicsBody?.velocity.dy < 0 {
let other = whichNode as! GameObjectNode
//If the player hits the Platform
if player.physicsBody?.velocity.dy < 0 && whichNode?.name == "NODE_PLATFORM" {
let joint = SKPhysicsJointFixed.jointWithBodyA(player.physicsBody, bodyB:whichNode!.physicsBody, anchor:CGPoint(x: player.position.x, y: ballBottom))
let moveScreen = SKAction.moveToY(-(player.position.y - 200), duration: 0.5)
let moveVortex = SKAction.moveToY((player.position.y - scaleFactor*300), duration: 0.5)
physicsWorld.addJoint(joint
player.physicsBody?.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: 0)
player.physicsBody?.affectedByGravity = false
}
}
}
I suspect you can resolve this issue by waiting until after the physics simulation has completed (for the current frame) before connecting the objects. One way to do that is to a set a flag when the ball is in contact with the platform in didBeginContact and then connect the two bodies with a joint in didSimulatePhysics.

Problems with contact/collision in spritekit

I am developing an app where contact plays a big role. I'm my game the "shoe" rest on the ground. I need to be able to know when the shoe is on the ground so it doesn't multi-jump. I also need to know when the shoe hits a hurdle so the game will end. My problem is that it thinks that the ground is a hurdle along with the actual hurdles.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var shoeGround = SKSpriteNode()
var hurdleTexture = SKTexture()
var hurdlesMoveAndRemove = SKAction()
var hurdlesStopAndRemove = SKAction()
var jump = false
//collision bitmask
let shoeGroundCategory:UInt32 = 0x1 << 0
let hurdleCategory:UInt32 = 0x1 << 28
let groundSensorCategory: UInt32 = 0x1 << 3
override func didMoveToView(view: SKView) {
//Physics
self.physicsWorld.gravity = CGVectorMake(0.0, -8.0)
self.physicsWorld.contactDelegate = self
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//ground
var groundTexture = SKTexture(imageNamed:"ground")
var sprite = SKSpriteNode(texture: groundTexture)
//scale it
sprite.setScale(2.1)
//position it
sprite.position = CGPointMake(self.size.width / 2, sprite.size.height / 2)
//add it to the scene
self.addChild(sprite)
//ground variable for the node
var ground = SKSpriteNode()
//set the position of the node
ground.position = CGPointMake(0, groundTexture.size().height)
ground.zPosition = 1000
//set the physics body to equal the size of the image.
ground.physicsBody = SKPhysicsBody(rectangleOfSize:CGSizeMake(self.frame.size.width, groundTexture.size().height * 1.85))
//physics bodies
ground.physicsBody?.dynamic = false
ground.physicsBody?.restitution = CGFloat(0.0)
//add the object to the scene
self.addChild(ground)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//ground
var groundSensorTexture = SKTexture(imageNamed:"ground")
var spriteSensor = SKSpriteNode(texture: groundSensorTexture)
//scale it
spriteSensor.setScale(2.1)
//position it
spriteSensor.position = CGPointMake(self.size.width / 2, spriteSensor.size.height / 2)
//add it to the scene
self.addChild(sprite)
//ground variable for the node
var groundSensor = SKSpriteNode()
//set the position of the node
groundSensor.position = CGPointMake(0, groundSensorTexture.size().height)
groundSensor.zPosition = 1000
//set the physics body to equal the size of the image.
groundSensor.physicsBody = SKPhysicsBody(rectangleOfSize:CGSizeMake(self.frame.size.width, groundSensorTexture.size().height * 1.85))
//physics bodies
groundSensor.physicsBody?.dynamic = false
groundSensor.physicsBody?.restitution = CGFloat(0.0)
groundSensor.physicsBody?.categoryBitMask = groundSensorCategory
groundSensor.physicsBody?.contactTestBitMask = shoeGroundCategory | hurdleCategory
//add the object to the scene
self.addChild(ground)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//shoe
var shoeGroundTexture = SKTexture(imageNamed:"default_shoe")
//change texture filtering mode.
shoeGroundTexture.filteringMode = SKTextureFilteringMode.Nearest
//Make the object.
shoeGround = SKSpriteNode(texture: shoeGroundTexture)
//set scale
shoeGround.setScale(0.35)
//position it.
shoeGround.position = CGPointMake(self.frame.size.width * 0.35, ((groundSensorTexture.size().height * 2.0) + (shoeGround.frame.size.height/2)))
shoeGround.zPosition = 100
//give it the collision collider of a circle.
shoeGround.physicsBody = SKPhysicsBody(circleOfRadius: shoeGround.size.height / 2)
shoeGround.physicsBody?.dynamic = true
shoeGround.physicsBody?.allowsRotation = false
shoeGround.physicsBody?.restitution = CGFloat(0.0)
shoeGround.physicsBody?.categoryBitMask = shoeGroundCategory
shoeGround.physicsBody?.contactTestBitMask = groundSensorCategory | hurdleCategory
//add it to the scene
self.addChild(shoeGround)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Hurdles
//Create the Hurdles.
hurdleTexture = SKTexture(imageNamed:"hurdle")
//Spawn the Hurdles.
let spawn = SKAction.runBlock({() in self.spawnHurdles()})
var time = arc4random() % 3
time += 2
let delay = SKAction.waitForDuration(2.0, withRange: 2.0)
let spawnThenDelay = SKAction.sequence([spawn, delay])
let spawnThenDelayForever = SKAction.repeatActionForever(spawnThenDelay)
self.runAction(spawnThenDelayForever)
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func didBeginContact(contact: SKPhysicsContact) {
var firstBody, secondBody, thirdBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & shoeGroundCategory) != 0 && (secondBody.categoryBitMask & hurdleCategory != 0)) {
//secondBody.node?.removeFromParent()
println("Hurdle")
}
if ((firstBody.categoryBitMask & shoeGroundCategory != 0) && (secondBody.categoryBitMask & groundSensorCategory != 0)) {
//secondBody.node?.removeFromParent()
jump = true
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func didEndContact(contact: SKPhysicsContact) {
var firstBody, secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & shoeGroundCategory != 0) &&
(secondBody.categoryBitMask & groundSensorCategory != 0)) {
//secondBody.node?.removeFromParent()
jump = false
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
moveHurdles()
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if jump == true {
shoeGround.physicsBody?.velocity = CGVectorMake(0, 0)
shoeGround.physicsBody?.applyImpulse(CGVectorMake(0, 82))
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func spawnHurdles() {
let hurdle = SKSpriteNode(texture: hurdleTexture)
hurdle.setScale(2.0)
hurdle.position = CGPointMake(0, 175)
hurdle.physicsBody = SKPhysicsBody(rectangleOfSize:hurdle.size)
hurdle.physicsBody?.dynamic = false
hurdle.physicsBody?.categoryBitMask = hurdleCategory
hurdle.physicsBody?.contactTestBitMask = shoeGroundCategory | groundSensorCategory
hurdle.runAction(hurdlesMoveAndRemove)
self.addChild(hurdle)
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//movement of Hurdles.
func moveHurdles() {
let distanceToMove = CGFloat(self.frame.size.width + 10.0 * hurdleTexture.size().width)
let moveHurdles = SKAction.moveByX(-distanceToMove, y: 0, duration: NSTimeInterval(0.00185 * distanceToMove))
let removeHurdles = SKAction.removeFromParent()
hurdlesMoveAndRemove = SKAction.sequence([moveHurdles, removeHurdles])
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func stopHurdles() {
let distanceToMove = CGFloat(self.frame.size.width + 10.0 * hurdleTexture.size().width)
let stopHurdles = SKAction.moveByX(0, y: 0, duration: NSTimeInterval(0.00185 * distanceToMove))
let removeHurdles = SKAction.removeFromParent()
hurdlesStopAndRemove = SKAction.sequence([stopHurdles, removeHurdles])
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
One way to check if your shoe is on the ground is to implement a condition at the Update function.
The condition would be to check the shoe's y coordinate and if it's below a certain threshold depending on your hurdles and ground.
The second method would be to check the sprite's velocity if it's close to zero.

Resources