Detect collision between sprites - ios

I have a problem in my code.
I'm creating a Game and I need to have a sprite that can appear multiple times at the same time, to do so I created a class so I can do addChild(obstacle) multiple times and it will spawn one SKSpriteNode exactly similar to another.
My problem is that I want to check collision between my player and the obstacle but because it's from the same SKSpriteNode the computer can't know of which obstacle I'm talking about.
Here's how I created the player and the obstacle:
import SpriteKit
class Obstacle: SKSpriteNode {
init() {
let obstacleTexture = SKTexture(imageNamed: "obstacle")
super.init(texture: obstacleTexture, color: UIColor.clearColor(), size: CGSize(width: 80, height: 80))
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameScene: SKScene {
var player:SKSpriteNode!
override func didMoveToView(view: SKView) {
//player setup
let playerTexture = SKTexture(imageNamed: "player")
player = SKSpriteNode(texture: playerTexture)
player.position = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.2)
}
//how I spawn an obstacle
func spawnObstacle() {
let obstacle = Obstacle()
//obstacle position setup
obstacle.position.x = CGFloat(arc4random()) % self.frame.size.width
obstacle.position.y = self.frame.size.height + 200
//random spin action setup
var rotateObstacle = SKAction.rotateByAngle(CGFloat(M_PI), duration: Double((drand48() + 1) * 0.75))
if random() % 2 == 0 {
rotateObstacle = SKAction.rotateByAngle(CGFloat(M_PI), duration: Double((drand48() + 1) * 0.75))
}else{
rotateObstacle = SKAction.rotateByAngle(-CGFloat(M_PI), duration: Double((drand48() + 1) * 0.75))
}
let rotateObstacleForever = SKAction.repeatActionForever(rotateObstacle)
//random move action setup
let moveObstacle = SKAction.moveTo(CGPointMake(CGFloat(arc4random()) % self.frame.size.width, -200), duration: Double((drand48() + 1) * 1.5))
//running the actions
obstacle.runAction(rotateObstacleForever)
obstacle.runAction(moveObstacle)
addChild(obstacle)
}
}
}
How to detect when the player collide with any obstacle?

To detect collisions you could use the SpriteKit physics.
Here you have 3 elements involved to hypothetical collisions:
The boundaries (or the field where live your player and where you
have your obstacles)
The player
The obstacles
Advice
Set this parameter to your debug phases to see physics objects boundaries :
skView.showsPhysics = true
An example of code (warning:- This code would be only a point to start to realize your physics, I don't know the rest of your project so your job will be to correct as you believe it's better for your objects):
enum CollisionTypes: UInt32 {
case Field = 1
case Player = 2
case Obstacle = 4
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, 0) // set your gravity value
self.physicsWorld.contactDelegate = self
let fieldBody = SKPhysicsBody.init(edgeLoopFromRect: self.frame)
self.physicsBody = fieldBody
self.physicsBody!.affectedByGravity = false
self.physicsBody!.usesPreciseCollisionDetection = true
self.physicsBody!.dynamic = true
self.physicsBody!.mass = 0.8
self.physicsBody!.friction = 0
self.physicsBody!.linearDamping = 0
self.physicsBody!.angularDamping = 0
self.physicsBody!.restitution = 0
self.physicsBody!.categoryBitMask = CollisionTypes.Field.rawValue
self.physicsBody!.contactTestBitMask = CollisionTypes.Player.rawValue
// Prepare the player
player.physicsBody = SKPhysicsBody(circleOfRadius: player.frame.width/2)
player.physicsBody!.affectedByGravity = false
player.physicsBody!.restitution = 0.0
player.physicsBody!.linearDamping = 0
player.physicsBody!.friction = 0.3
player.physicsBody!.dynamic = true
player.physicsBody!.mass = 0.2
player.physicsBody!.allowsRotation = false
player.physicsBody!.categoryBitMask = CollisionTypes.Player.rawValue
player.physicsBody!.contactTestBitMask = CollisionTypes.Field.rawValue | CollisionTypes.Obstacles.rawValue
player.physicsBody!.collisionBitMask = CollisionTypes.Field.rawValue | CollisionTypes.Obstacles.rawValue
//Prepare the obstacles (you must do it in your obstacle class)
obstacle.physicsBody = SKPhysicsBody(circleOfRadius: obstacle.frame.width/2)
obstacle.physicsBody!.affectedByGravity = false
obstacle.physicsBody!.restitution = 0.0
obstacle.physicsBody!.linearDamping = 0
obstacle.physicsBody!.friction = 0.3
obstacle.physicsBody!.dynamic = true
obstacle.physicsBody!.mass = 0.8
obstacle.physicsBody!.allowsRotation = true
obstacle.physicsBody!.categoryBitMask = CollisionTypes.Obstacle.rawValue
obstacle.physicsBody!.contactTestBitMask = CollisionTypes.Player.rawValue
obstacle.physicsBody!.collisionBitMask = CollisionTypes.Player.rawValue
}
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == CollisionTypes.Player.rawValue &&
contact.bodyB.categoryBitMask == CollisionTypes.Obstacle.rawValue) {
print("contact between Player and Obstacle")
}
if (contact.bodyA.categoryBitMask == CollisionTypes.Player.rawValue &&
contact.bodyB.categoryBitMask == CollisionTypes.Field.rawValue) {
print("contact between Player and Field")
}
}
}

Related

Setting position of custom SKSpriteNode from GameScene

I am making a custom SKSpriteNode, when I step through calling createShip the heroShip constant in the class is correct, but when I jump back to the gameScene, the heroShip constant there does not have the properties I assigned when calling createShip, I am not sure what I am doing wrong. I have tried using class function but that doesn't work using the height and width properties.
Custom SKSpriteNode class
class hero: SKSpriteNode {
var width: CGFloat = 0.0
var height: CGFloat = 0.0
func createShip() -> SKSpriteNode {
let heroShip = SKSpriteNode(imageNamed: "heroShip")
heroShip.anchorPoint = CGPointMake(1.0, 0.5)
heroShip.physicsBody = SKPhysicsBody(rectangleOfSize: heroShip.size)
heroShip.physicsBody?.usesPreciseCollisionDetection = true
heroShip.zPosition = 1.0
heroShip.physicsBody?.mass = 0.02
heroShip.physicsBody?.dynamic = true
heroShip.physicsBody?.affectedByGravity = false
heroShip.physicsBody?.categoryBitMask = ObjectCategory.collisionHeroCategory.rawValue
heroShip.physicsBody?.contactTestBitMask = ObjectCategory.sceneCategory.rawValue
heroShip.physicsBody?.collisionBitMask = 0x0 | ObjectCategory.sceneCategory.rawValue
//heroShip.position = CGPointMake((scene?.frame.size.width)!/6.0, (scene?.frame.size.height)!/2.0)
heroShip.position = CGPointMake(width, height)
return heroShip
}
}
My GameScene
class GameScene: SKScene,SKPhysicsContactDelegate{
let background = SKSpriteNode(imageNamed: "background")
var score:Int = 0
let scoreLabel = SKLabelNode(fontNamed: "Courier")
let MotionManager = CMMotionManager()
var heroShip = hero()
override func didMoveToView(view: SKView) {
heroShip.width = self.size.width/6.0
heroShip.height = self.size.height/2.0
heroShip.createShip()
let enemyShip = SKSpriteNode(imageNamed: "enemyShip")
/* Setup your scene here */
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRectMake(0,heroShip.size.width/1.25,frame.width,frame.height - heroShip.size.width*1.6))
scene?.physicsBody?.contactTestBitMask = ObjectCategory.sceneCategory.rawValue
scene?.physicsBody?.categoryBitMask = ObjectCategory.sceneCategory.rawValue
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
scoreLabel.fontColor = SKColor.whiteColor()
scoreLabel.text = String(format: "Score: %01u",score)
scoreLabel.position = CGPointMake(frame.size.width/2, frame.size.height - scoreLabel.frame.size.width/1.2)
scoreLabel.zPosition = 1.0
enemyShip.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
enemyShip.zPosition = 1.0
enemyShip.physicsBody = SKPhysicsBody(rectangleOfSize: heroShip.size)
enemyShip.physicsBody?.usesPreciseCollisionDetection = true
enemyShip.physicsBody?.mass = 0.02
enemyShip.physicsBody?.dynamic = true
enemyShip.physicsBody?.affectedByGravity = false
enemyShip.physicsBody?.categoryBitMask = ObjectCategory.collisionEnemyCategory.rawValue
enemyShip.physicsBody?.contactTestBitMask = ObjectCategory.collisionBulletCategory.rawValue
enemyShip.physicsBody?.collisionBitMask = 0x0
self.addChild(enemyShip)
self.addChild(background)
self.addChild(self.heroShip)
self.addChild(scoreLabel)
if MotionManager.accelerometerAvailable{
MotionManager.startAccelerometerUpdates()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = CGPointMake(heroShip.position.x, heroShip.position.y)
bullet.zPosition = 1.0
// Add physics body for collision detection
bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.frame.size)
bullet.physicsBody?.dynamic = true
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.categoryBitMask = ObjectCategory.collisionBulletCategory.rawValue
bullet.physicsBody?.contactTestBitMask = ObjectCategory.collisionHeroCategory.rawValue
bullet.physicsBody?.collisionBitMask = 0x0;
let action = SKAction.moveToX(CGRectGetMaxX(self.frame) + bullet.size.width, duration: 0.75)
self.addChild(bullet)
bullet.runAction(action, completion: {
bullet.removeAllActions()
bullet.removeFromParent()
})
}
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyB.categoryBitMask == ObjectCategory.collisionBulletCategory.rawValue && contact.bodyA.categoryBitMask == ObjectCategory.collisionEnemyCategory.rawValue{
score++
}
}
override func update(currentTime: CFTimeInterval) {
let data = MotionManager.accelerometerData
if data?.acceleration.x == nil{
print("nil")
}
else if fabs((data?.acceleration.x)!) > 0.2 {
heroShip.physicsBody?.applyForce(CGVectorMake(0.0, CGFloat(40 * (data?.acceleration.x)!)))
}
scoreLabel.text = String(format: "Score: %01u",score)
}
}
You run
heroShip.createShip()
and then never do anything with the SKSpriteNode that is returned. From what I can tell, the hero class is the hero ship. I am going off of this assumption for the rest of this answer.
Going from the top of the GameScene, you should do some refactoring.
var heroShip = hero() Is going to be replaced to init using the SKSpriteNode structure:
var heroShip = hero(imageNamed: "heroShip")
Moving to hero class, func createShip() -> SKSpriteNode { should be turned into func createShip() {. As you already have the node set up with the image texture now, and are using the hero class as the node, there is no need to return a SKSpriteNode.
Delete let heroShip = SKSpriteNode(imageNamed: "heroShip") as the hero class is going to be our heroShip.
Replace any usage of the heroShip.whatever variable with self.whatever. At the end, delete the return heroShip.
You seem to not be understanding what is happening with your code.
As of right now createShip() is creating a hero ship, but you never use it.
By looking at your code, it seems like createShip() is not even needed. From looking at it, hero IS heroShip, so do the code in your init.
convenience init(imageNamed: String,sceneSize:CGSize) {
let heroTexture = SKTexture(imageNamed: imageNamed)
self.init(texture: heroTexture, color: UIColor.whiteColor(), size: heroTexture.size()) //This may need to be tweeked, my mac is dead right now to verify.
self.anchorPoint = CGPointMake(1.0, 0.5)
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size)
self.physicsBody?.usesPreciseCollisionDetection = true
self.zPosition = 1.0
self.physicsBody?.mass = 0.02
self.physicsBody?.dynamic = true
self.physicsBody?.affectedByGravity = false
heroShip.physicsBody?.categoryBitMask = ObjectCategory.collisionHeroCategory.rawValue
heroShip.physicsBody?.contactTestBitMask = ObjectCategory.sceneCategory.rawValue
heroShip.physicsBody?.collisionBitMask = 0x0 | ObjectCategory.sceneCategory.rawValue
//self.position = CGPointMake((scene?.frame.size.width)!/6.0, (scene?.frame.size.height)!/2.0)
self.position = CGPointMake(sceneSize.width, sceneSize.height)
}
You now come across another problem, and that is your SKPhysicsBody will not align to your anchor point.
To fix this, create the SKPhysicsBody like this:
let centerPoint = CGPointMake(self.size.width / 2 - (self.size.width * self.anchorPoint.x), self.size.height / 2 - (self.size.height * self.anchorPoint.y))
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size, center: centerPoint)

Sprite Kit Game Scene changes when it is not supposed to

In a game that I am currently building a person is supposed to catch balls that are falling from the sky. If the ball goes off the screen it means he didn't catch the ball, and so the scene is supposed to change to a game over scene. The problem is that even if the ball doesn't go below the screen the screen will change. But the screen will change to a blank screen so instead of the GameOverScene().
Here is the code for the GameScene()...
//
// GameScene.swift
// catch balls
//
// Created by Ankith Udupa on 8/10/15.
// Copyright (c) 2015 UdupaLabs. All rights reserved.
//
import SpriteKit
var score = 0
var lossFlag = false
class GameScene: SKScene, SKPhysicsContactDelegate {
var person = SKSpriteNode(imageNamed: "guyLeft_1.png")
var left = true
let kScoreHudName = "scoreHud"
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let Ball : UInt32 = 0b1
static let Person: UInt32 = 0b10
}
override func didMoveToView(view: SKView) {
var content = false
//set up screen
setUpScreen()
//set up the physics
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
//add ball
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addBall),
SKAction.waitForDuration(1.0)
])
))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
left = !left
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if ((person.position.x > person.size.width/2) && (person.position.x < size.width-(person.size.width/2))){
if left {
var leftMove = SKAction.moveByX(5, y: 0, duration: 0.1)
person.runAction(leftMove)
}
if !left { // or use an if-else construct
var rightMove = SKAction.moveByX(-5, y: 0, duration: 0.1)
person.runAction(rightMove)
}
}
}
//random number gen functions
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
//add ball function
func addBall(){
//create ball sprite
var ball = SKSpriteNode(imageNamed: "ball.png")
//create physics for ball
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size) // 1
ball.physicsBody?.dynamic = true // 2
ball.physicsBody?.categoryBitMask = PhysicsCategory.Ball // 3
ball.physicsBody?.contactTestBitMask = PhysicsCategory.Person // 4
ball.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
//generate random postion along x axis for ball to spawn
let actualX = random(min:ball.size.width/2+1, max: size.width - ball.size.width/2-1)
//set balls positon
ball.position = CGPoint(x: actualX, y: size.height - ball.size.width/2)
//add ball to scene
addChild(ball)
//determine speed of ball
let actualDuration = random(min: CGFloat(3.0), max: CGFloat(5.0))
//create movement actions and run them
let actionMove = SKAction.moveTo(CGPoint(x:actualX, y: -ball.size.width/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let Loss = SKAction.runBlock() {
let reveal = SKTransition.crossFadeWithDuration(0.1)
let gameOverScene = GameOverScene()
self.view?.presentScene(GameOverScene(), transition: reveal)
}
ball.runAction(SKAction.sequence([actionMove, Loss, actionMoveDone]))
}
//setUpScreen
func setUpScreen(){
self.backgroundColor = SKColor.whiteColor()
var ground = SKShapeNode(rectOfSize: CGSizeMake(self.frame.size.width, self.frame.size.height * 0.2))
ground.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height * 0.1)
ground.fillColor = SKColor.blueColor()
self.addChild(ground)
person.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height * 0.2)
setUpPersonPhysics()
self.addChild(person)
}
//set up person physics
func setUpPersonPhysics(){
person.physicsBody = SKPhysicsBody(rectangleOfSize: person.size)
person.physicsBody?.dynamic = true
person.physicsBody?.categoryBitMask = PhysicsCategory.Person
person.physicsBody?.contactTestBitMask = PhysicsCategory.Ball
person.physicsBody?.collisionBitMask = PhysicsCategory.None
person.physicsBody?.usesPreciseCollisionDetection = true
}
func didBeginContact(contact: SKPhysicsContact) {
// 1
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
}
// 2
if ((firstBody.categoryBitMask & PhysicsCategory.Ball != 0) &&
(secondBody.categoryBitMask & PhysicsCategory.Person != 0)) {
personDidCollideWithBall(secondBody.node as! SKSpriteNode, ball: firstBody.node as! SKSpriteNode)
}
}
//called when person collides with ball
func personDidCollideWithBall(person:SKSpriteNode, ball:SKSpriteNode) {
println("hit")
ball.removeFromParent()
score++
}
}
and here is the code for the gameOverScene()...
//
// gameOverScene.swift
// catch babies
//
// Created by Ankith Udupa on 8/12/15.
// Copyright (c) 2015 UdupaLabs. All rights reserved.
//
import Foundation
import SpriteKit
class GameOverScene: SKScene {
var message = "Game Over"
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.whiteColor()
setUpTextOutPut()
}
func setUpTextOutPut(){
let gameOverLabel = SKLabelNode(fontNamed: "Superclarendon-Black")
gameOverLabel.text = message
gameOverLabel.fontSize = 40
gameOverLabel.fontColor = SKColor.orangeColor()
gameOverLabel.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(gameOverLabel)
let scoreLabel = SKLabelNode(fontNamed: "Superclarendon-Black")
scoreLabel.text = "\(score)"
scoreLabel.fontSize = 40
scoreLabel.fontColor = SKColor.orangeColor()
scoreLabel.position = CGPoint(x: size.width/2, y: size.height/2-50)
addChild(scoreLabel)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
}
}
The error is with your addBall method,
//add ball function
func addBall(){
//create ball sprite
var ball = SKSpriteNode(imageNamed: "ball.png")
//create physics for ball
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size) // 1
ball.physicsBody?.dynamic = true // 2
ball.physicsBody?.categoryBitMask = PhysicsCategory.Ball // 3
ball.physicsBody?.contactTestBitMask = PhysicsCategory.Person // 4
ball.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
//generate random postion along x axis for ball to spawn
let actualX = random(min:ball.size.width/2+1, max: size.width - ball.size.width/2-1)
//set balls positon
ball.position = CGPoint(x: actualX, y: size.height - ball.size.width/2)
//add ball to scene
addChild(ball)
//determine speed of ball
let actualDuration = random(min: CGFloat(3.0), max: CGFloat(5.0))
//create movement actions and run them
let actionMove = SKAction.moveTo(CGPoint(x:actualX, y: -ball.size.width/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let Loss = SKAction.runBlock() {
let reveal = SKTransition.crossFadeWithDuration(0.1)
let gameOverScene = GameOverScene()
self.view?.presentScene(GameOverScene(), transition: reveal)
}
ball.runAction(SKAction.sequence([actionMove, Loss, actionMoveDone]))
}
If you look at the method properly, you ask to run the sequence to the sprite and inside runBlock, you move to another scene. Do you need to check if the ball is outside bounds inside this block and only then present your game over scene ?
Should it be something like this,
let Loss = SKAction.runBlock() {
if ball.position.x > self.size.width + ball.frame.size.width * 0.5 || ball.position.y < ball.frame.size.height * 0.5 {
let reveal = SKTransition.crossFadeWithDuration(0.1)
let gameOverScene = GameOverScene()
self.view?.presentScene(GameOverScene(), transition: reveal)
}
}

Sprite Kit set Min. and Max. for Jump

I want to move a SKSpriteNode on the Y-Axis. The SKSpriteNode called Player has no Velocity.The Player can only jump if a Platform is in contact.
Everytime the Screen is touched, I want to give the Player an impulse with a minimum impulse or a maximum impulse
If the Screen is tapped shortly, the Minimum impulse should be e.g. y = 50.
If the Screen is hold, that means the finger is on the Screen long, the Maximum should be e.g. y = 100.
But the Player should be also able to jump between the Minimum and Maximum height, if for e.g. the Screen is not long but also not shortly pressed, the Player should only get an impulse of y = 70.
If the Screen is hold, the Player should jump to his max height, fall down, and if it is in contact with the Platform again, it should jump, because you still hold the Screen.
I have already tried this with the suggested answer in this Thread:StackOverFlow
But this does not give the Minimum jump, also no Press jump.
For clarity: The impulse should not be after the tap is done, but while it is tapped. The longer you hold, the longer the jump is.
import SpriteKit
import GameKit
struct Constants {
static let minimumJumpForce:CGFloat = 40.0
static let maximumJumpForce:CGFloat = 60.0
static let characterSideSpeed:CGFloat = 18.0
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var Player: SKSpriteNode!
var Platform0: SKSpriteNode!
var World: SKNode!
var Camera: SKNode!
var force: CGFloat = 40.0
var pressed = false
var isCharacterOnGround = false
.....
func SpawnPlatforms() {
Platform0 = SKSpriteNode (color: SKColor.greenColor(), size: CGSize(width: self.frame.size.width , height: 25))
Platform0.position = CGPoint(x: self.frame.size.width / 2, y: -36)
Platform0.zPosition = 1
Platform0.physicsBody = SKPhysicsBody(rectangleOfSize:Platform0.size)
Platform0.physicsBody?.dynamic = false
Platform0.physicsBody?.allowsRotation = false
Platform0.physicsBody?.restitution = 0
Platform0.physicsBody?.usesPreciseCollisionDetection = true
Platform0.physicsBody?.categoryBitMask = Platform0Category
Platform0.physicsBody?.collisionBitMask = PlayerCategory
Platform0.physicsBody?.contactTestBitMask = PlayerCategory
World.addChild(Platform0)
}
func SpawnPlayer(){
Player = SKSpriteNode (imageNamed: "Image.png")
Player.size = CGSize(width: 64, height: 64)
Player.position = CGPoint(x: self.frame.size.width / 2, y: 0)
Player.zPosition = 2
Player.physicsBody = SKPhysicsBody(rectangleOfSize:CGSize(width: 35, height: 50))
Player.physicsBody?.dynamic = true
Player.physicsBody?.allowsRotation = false
Player.physicsBody?.restitution = 0.1
Player.physicsBody?.usesPreciseCollisionDetection = true
Player.physicsBody?.categoryBitMask = PlayerCategory
Player.physicsBody?.collisionBitMask = Platform0Category
Player.physicsBody?.contactTestBitMask = Platform0Category | Platform1Category | Platform2Category | Platform3Category | Platform4Category | Platform5Category
World.addChild(Player)
}
func jump(force : CGFloat){
if(self.isCharacterOnGround){
self.Player.physicsBody?.applyImpulse(CGVectorMake(0, force))
self.isCharacterOnGround = false
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
self.pressed = true
let timerAction = SKAction.waitForDuration(0.0)
let update = SKAction.runBlock({
if(self.force < Constants.maximumJumpForce){
self.force += 2.0
}else{
self.jump(Constants.maximumJumpForce)
self.force = Constants.maximumJumpForce
}
})
let sequence = SKAction.sequence([timerAction, update])
let repeat = SKAction.repeatActionForever(sequence)
self.runAction(repeat, withKey:"repeatAction")
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
self.removeActionForKey("repeatAction")
self.jump(self.force)
self.force = Constants.minimumJumpForce
self.pressed = false
}
}
func didBeginContact(contact: SKPhysicsContact) {
//this gets called automatically when two objects begin contact with each other
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(contactMask) {
case PlayerCategory | Platform0Category:
//either the contactMask was the bro type or the ground type
println("Contact Made0")
Green = true
self.isCharacterOnGround = true
default:
return
}
}
Here is an working example on how to make something like:
long pressed jump based on duration of press
short (one tap jump)
restrict character to jump while in the air
keep character jumping while finger is on screen
Code (Swift 4.x)
import SpriteKit
struct Constants {
static let minimumJumpForce:CGFloat = 15.0
static let maximumJumpForce:CGFloat = 30.0
static let characterSideSpeed:CGFloat = 18.0
}
class GameScene: SKScene,SKPhysicsContactDelegate
{
let CharacterCategory : UInt32 = 0x1 << 1
let PlatformCategory : UInt32 = 0x1 << 2
let WallCategory : UInt32 = 0x1 << 3
var force: CGFloat = 16.0 //Initial force
var pressed = false
var isCharacterOnGround = false // Use this to prevent jumping while in the air
let character = SKSpriteNode(color: .green, size: CGSize(width: 30, height:30))
let debugLabel = SKLabelNode(fontNamed: "Geneva")
override func didMove(to view: SKView)
{
//Setup contact delegate so we can use didBeginContact and didEndContact methods
physicsWorld.contactDelegate = self
physicsWorld.speed = 0.5
//Setup borders so character can't escape from us :-)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody?.categoryBitMask = WallCategory
self.physicsBody?.collisionBitMask = CharacterCategory
//Setup character
character.position = CGPoint(x: 150, y: 150)
character.physicsBody = SKPhysicsBody(rectangleOf: character.size)
character.physicsBody?.categoryBitMask = CharacterCategory
character.physicsBody?.contactTestBitMask = PlatformCategory
character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
character.physicsBody?.allowsRotation = false
character.physicsBody?.isDynamic = true
character.physicsBody?.restitution = 0.1
self.addChild(character)
generatePlatforms()
debugLabel.text = " DEBUG: "
debugLabel.fontColor = .white
debugLabel.fontSize = 12.0
debugLabel.position = CGPoint(x: frame.midX, y: frame.midY+100)
self.addChild(debugLabel)
}
func generatePlatforms(){
for i in 1...4
{
let position = CGPoint(x: frame.midX, y: CGFloat(i)*140.0 - 100)
let platform = createPlatformAtPosition(position: position)
self.addChild(platform)
}
}
func createPlatformAtPosition(position : CGPoint)->SKSpriteNode{
let platform = SKSpriteNode(color: .green, size: CGSize(width: frame.size.width, height:20))
platform.position = position
platform.physicsBody = SKPhysicsBody(
edgeFrom: CGPoint(x: -platform.size.width/2.0, y:platform.size.height/2.0),
to:CGPoint(x: platform.size.width/2.0, y: platform.size.height/2.0))
platform.physicsBody?.categoryBitMask = PlatformCategory
platform.physicsBody?.contactTestBitMask = CharacterCategory
platform.physicsBody?.collisionBitMask = CharacterCategory
platform.physicsBody?.allowsRotation = false
platform.name = "platform"
platform.physicsBody?.isDynamic = false
platform.physicsBody?.restitution = 0.0
return platform
}
func jump(force : CGFloat){
if(self.isCharacterOnGround){
self.character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: force))
self.character.physicsBody?.collisionBitMask = WallCategory
self.isCharacterOnGround = false
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.pressed = true
let timerAction = SKAction.wait(forDuration: 0.05)
let update = SKAction.run({
if(self.force < Constants.maximumJumpForce){
self.force += 2.0
}else{
self.jump(force: Constants.maximumJumpForce)
self.force = Constants.maximumJumpForce
}
})
let sequence = SKAction.sequence([timerAction, update])
let repeat_seq = SKAction.repeatForever(sequence)
self.run(repeat_seq, withKey:"repeatAction")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.removeAction(forKey: "repeatAction")
self.jump(force: self.force)
self.force = Constants.minimumJumpForce
self.pressed = false
}
override func update(_ currentTime: TimeInterval) {
debugLabel.text = "DEBUG: onTheGround : \(isCharacterOnGround), force \(force)"
if(character.position.x <= character.size.width/2.0 + 5.0 && character.physicsBody!.velocity.dx < 0.0 ){
character.physicsBody?.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
}else if((character.position.x >= self.frame.size.width - character.size.width/2.0 - 5.0) && character.physicsBody!.velocity.dx >= 0.0){
character.physicsBody?.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
}else if(character.physicsBody!.velocity.dx > 0.0){
character.physicsBody!.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
}else{
character.physicsBody!.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
}
}
func didBegin(_ 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 & CharacterCategory) != 0 &&
(secondBody.categoryBitMask & PlatformCategory != 0)) {
let platform = secondBody.node! as! SKSpriteNode
// platform.color = UIColor.redColor()
let platformSurfaceYPos = platform.position.y + platform.size.height/2.0
let player = contact.bodyB.node! as! SKSpriteNode
let playerLegsYPos = player.position.y - player.size.height/2.0
if((platformSurfaceYPos <= playerLegsYPos)){
character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
self.isCharacterOnGround = true
if(self.pressed){
let characterDx = character.physicsBody?.velocity.dx
character.physicsBody?.velocity = CGVector(dx: characterDx!, dy: 0.0)
self.jump(force: Constants.maximumJumpForce)
}
}
}
}
func didEnd(_ 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 & CharacterCategory) != 0 &&
(secondBody.categoryBitMask & PlatformCategory != 0)) {
let platform = secondBody.node as! SKSpriteNode
let platformSurfaceYPos = platform.position.y + platform.size.height/2.0
let player = contact.bodyB.node as! SKSpriteNode
let playerLegsYPos = player.position.y - player.size.height/2.0
if((platformSurfaceYPos <= playerLegsYPos) && ((character.physicsBody?.velocity.dy)! > CGFloat(0.0))){
character.physicsBody?.collisionBitMask = WallCategory
self.isCharacterOnGround = false
}
}
}
}
Note that this is simple example, and in real application you will probably have to handle states like isOnTheGround in a different way. Right now, to determine if character is on the ground you just set isOnTheGround = true when character make a contact with platform, and set it to false in didEndContact...But there are situations when character can be in contact with platform while in the air (eg. side contact)...
EDIT:
I changed the code to let the player jump while pressed. Here is the result:
Important:
Actual platform implementation and contact handling is up to you and this is not tested. The only purpose of this example is to show you how to jump while pressed. Currently, physicsWorld.speed is set to 0.5 to make animation slower because its easier to debug like that, but you can change this to default value (1.0).
So, as you can see from the image, while player is on the first platform some small jumps are presented (by simple tapping, or short pressing). Then (player is still on first platform) long press has been made, and player has jumped on second platform. After that, another long press is done, but this time without releasing, and player starts jumping from one platform to another using maximum force.
This needs a lot of tweaking and proper platform & contact detection, but it can give you an idea about how to implement jumping you asked about.

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.

Implementing collision detections

Basically the game consists of a basket that the player moves across the screen, the aim of the game is for the player to catch balls falling from the top of the screen. I am currently trying to add collision detection between the balls and the basket, but am facing difficulties namely, implementing this collision detection. I am new to swift, sprite kit and app development, so please help. Any help would be appreciated. Another problem I am facing is that all the balls are falling in the centre of the screen. A line of code is supposed to execute when, the ball hits the basket and following that the ball should disappear, please help as I am new to Spritekit.
import SpriteKit
class GameScene: SKScene {
var basket = SKSpriteNode()
let actionMoveRight = SKAction.moveByX(50, y: 0, duration: 0.2)
let actionMoveLeft = SKAction.moveByX(-50, y: 0, duration: 0.2)
//let physicsBody = SKPhysicsBody(texture: , size: 3500)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, -0.5)
self.backgroundColor = SKColor.whiteColor()
basket = SKSpriteNode(imageNamed: "basket")
basket.setScale(0.5)
basket.position = CGPointMake(self.size.width/2, self.size.height/8)
basket.size.height = 50
basket.size.width = 75
self.addChild(basket)
let updateAction = SKAction.runBlock {
var choice = arc4random_uniform(3)
switch choice {
case 1 :
var ball1 = SKSpriteNode(imageNamed: "redBall")
ball1.position = CGPointMake(self.size.width/3, self.size.height)
ball1.setScale(0.5)
ball1.size.height = 20
ball1.size.width = 30
ball1.physicsBody = SKPhysicsBody(circleOfRadius: ball1.size.height / 2.75)
ball1.physicsBody!.dynamic = true
self.addChild(ball1)
println("0")
case 0 :
var ball2 = SKSpriteNode(imageNamed: "redBall")
ball2.position = CGPointMake(self.size.width/5, self.size.height)
ball2.setScale(0.5)
ball2.size.height = 20
ball2.size.width = 30
ball2.physicsBody = SKPhysicsBody(circleOfRadius: ball2.size.height / 2.75)
ball2.physicsBody!.dynamic = true
self.addChild(ball2)
println("1")
case 2 :
var ball3 = SKSpriteNode(imageNamed: "redBall")
ball3.position = CGPointMake(self.size.width*4/5, self.size.height)
ball3.setScale(0.5)
ball3.size.height = 20
ball3.size.width = 30
ball3.physicsBody = SKPhysicsBody(circleOfRadius: ball3.size.height / 2.75)
ball3.physicsBody!.dynamic = true
self.addChild(ball3)
println("2")
default :
println("Problem")
}
}
let waitDuration : NSTimeInterval = 1.0
let updateAndWaitAction = SKAction.sequence([updateAction,SKAction.waitForDuration(waitDuration)])
let repeatForeverAction = SKAction.repeatActionForever(updateAndWaitAction)
self.runAction(repeatForeverAction)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if location.x > basket.position.x {
if basket.position.x < self.frame.maxX {
basket.runAction(actionMoveRight)
}
}
else {
if basket.position.x > self.frame.minX {
basket.runAction(actionMoveLeft)
}
}
}
}
override func update(currentTime: CFTimeInterval) {
}
}
For now you have a code that typically used in situations where user is taping something. You need to use BodyA & BodyB and assign a bitmasks to your nodes.
self.basket.physicsBody?.categoryBitMask = ColliderType.basket.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball1.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball1.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball2.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball2.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball3.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball3.rawValue
And do that for every ball too. And then in func didBeginContact you should say to Xcode what to do, if you have an animation or something:
if (contact.bodyA.categoryBitMask == ColliderType.ball1.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball1.rawValue) {
yourGameOverFunc()
}
if (contact.bodyA.categoryBitMask == ColliderType.ball2.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball2.rawValue) {
yourGameOverFunc()
}
if (contact.bodyA.categoryBitMask == ColliderType.ball3.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball3.rawValue) {
yourGameOverFunc()
}

Resources