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
Related
I have 2 physicsBodies and don't want them to collide (ie they must pass through each other) but still want to be notified in case of contact, however when setting the collisionBitMask to 0, the contact event is not called anymore. What am I doing wrong ?
let stoneCategory : UInt32 = 0x1 << 1
let birdCategory : UInt32 = 0x1 << 2
physicsWorld.contactDelegate = self
stoneNode = SKSpriteNode(imageNamed: "Stone");
self.addChild(stoneNode)
stoneNode.physicsBody = SKPhysicsBody(rectangleOf: stoneNode.size);
stoneNode.physicsBody?.categoryBitMask = stoneCategory
stoneNode.physicsBody?.contactTestBitMask = birdCategory
stoneNode.physicsBody?.collisionBitMask = 0
birdNode = SKSpriteNode();
<...Set texture...>
self.addChild(birdNode)
birdNode.physicsBody = SKPhysicsBody(rectangleOf: birdNode.size);
birdNode.physicsBody?.categoryBitMask = birdCategory
birdNode.physicsBody?.contactTestBitMask = stoneCategory
birdNode.physicsBody?.collisionBitMask = 0
birdNode.physicsBody?.isDynamic = false;
func didBegin(_ contact: SKPhysicsContact) {
print("Contact between "+contact.bodyA.node!.name!+" and "+contact.bodyB.node!.name!); // --> Never called
}
You might have a different problem in your code elsewhere, because this is working for me. The bodies do not collide and the contact happens.
Sample playground code:
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var stoneNode: SKShapeNode!
var birdNode: SKShapeNode!
override func didMove(to view: SKView) {
let stoneCategory : UInt32 = 0x1 << 1
let birdCategory : UInt32 = 0x1 << 2
physicsWorld.contactDelegate = self
stoneNode = SKShapeNode(circleOfRadius: 30)
stoneNode.name = "stone"
stoneNode.position = CGPoint(x: 0, y: 100)
addChild(stoneNode)
stoneNode.physicsBody = SKPhysicsBody(circleOfRadius: 30)
stoneNode.physicsBody?.categoryBitMask = stoneCategory
stoneNode.physicsBody?.contactTestBitMask = birdCategory
stoneNode.physicsBody?.collisionBitMask = 0
birdNode = SKShapeNode(circleOfRadius: 40)
birdNode.name = "bird"
birdNode.position = CGPoint(x: 0, y: -100)
addChild(birdNode)
birdNode.physicsBody = SKPhysicsBody(circleOfRadius: 40);
birdNode.physicsBody?.categoryBitMask = birdCategory
birdNode.physicsBody?.contactTestBitMask = stoneCategory
birdNode.physicsBody?.collisionBitMask = 0
birdNode.physicsBody?.isDynamic = false;
}
func didBegin(_ contact: SKPhysicsContact) {
print("Contact between "+contact.bodyA.node!.name!+" and "+contact.bodyB.node!.name!); // --> Never called
}
}
// Load the SKScene from 'GameScene.sks'
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
And in the console, when the first body drops, being affected by gravity, it's printed:
Contact between stone and bird
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.
So I am playing around with Swift and SpriteKit. I decided to create a simple game where a character will walk to a chest of coins and get all the treasure.
I am trying to detect when the two objects contact eachother, however nothing seems to be happening.
What I did:
1) I set the delegate:
SKPhysicsContactDelegate
2) Created the scene:
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
physicsWorld.contactDelegate = self
let sceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sceneBody.friction = 0
background.zPosition = -20
background.anchorPoint = CGPointZero
background.position = CGPointZero
backgroundRepeated.zPosition = -20
backgroundRepeated.anchorPoint = CGPointZero
backgroundRepeated.position = CGPointMake(background.size.width-1,0)
addChest()
addCharacter()
addChild(background)
addChild(backgroundRepeated)
}
3) Created the items:
func addChest() {
chestOfCoins.anchorPoint = CGPointZero
chestOfCoins.size = CGSize(width: 200,height: 200)
chestOfCoins.position = CGPoint(x: self.frame.width-1, y: 300)
chestOfCoins.zPosition = 20
chestOfCoins.physicsBody = SKPhysicsBody(rectangleOfSize: chestOfCoins.size)
chestOfCoins.physicsBody?.affectedByGravity = false
chestOfCoins.physicsBody?.dynamic = false
chestOfCoins.physicsBody?.categoryBitMask = physicsCategory.chests
chestOfCoins.physicsBody?.collisionBitMask = 0
chestOfCoins.physicsBody?.contactTestBitMask = physicsCategory.character
addChild(chestOfCoins)
}
func addCharacter() {
character.anchorPoint = CGPointZero
character.position = CGPoint(x: 300, y: 300)
character.zPosition = 20
character.size = CGSize(width: 200, height: 200)
character.physicsBody = SKPhysicsBody(rectangleOfSize: character.size)
character.physicsBody?.affectedByGravity = false
character.physicsBody?.dynamic = false
character.physicsBody?.categoryBitMask = physicsCategory.character
character.physicsBody?.contactTestBitMask = physicsCategory.chests
character.physicsBody?.collisionBitMask = 0
addChild(character)
}
4) created didBeginContact:
func didBeginContact(contact: SKPhysicsContact) {
var contactBody1: SKPhysicsBody
var contactBody2: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
contactBody1 = contact.bodyA
contactBody2 = contact.bodyB
} else {
contactBody1 = contact.bodyB
contactBody2 = contact.bodyA
}
if((contactBody1.categoryBitMask == 1) && (contactBody2.categoryBitMask == 2)) {
print("touched")
} else {
print("not these two")
}
}
FYI, this IF if((contactBody1.categoryBitMask == 1) && (contactBody2.categoryBitMask == 2)) does not output Either the main if block or the else.
5) I created the category physics :
struct physicsCategory {
static let none: UInt32 = 0 //0
static let character: UInt32 = 0b1 //1
static let chests: UInt32 = 0b10 //2
static let snake: UInt32 = 0b100 //4
}
Full GameScene.Swift:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
struct physicsCategory {
static let none: UInt32 = 0 //0
static let character: UInt32 = 0b1 //1
static let chests: UInt32 = 0b10 //2
static let snake: UInt32 = 0b100 //4
static let all: UInt32 = UInt32.max
}
let background = SKSpriteNode(imageNamed: "Backgrounds")
let backgroundRepeated = SKSpriteNode(imageNamed: "Backgrounds")
let chestOfCoins = SKSpriteNode(imageNamed: "chest")
let character = SKSpriteNode(imageNamed: "character")
let gameActions = NSUserDefaults.standardUserDefaults()
func addChest() {
chestOfCoins.anchorPoint = CGPointZero
chestOfCoins.size = CGSize(width: 200,height: 200)
chestOfCoins.position = CGPoint(x: self.frame.width-1, y: 300)
chestOfCoins.zPosition = 20
chestOfCoins.physicsBody = SKPhysicsBody(rectangleOfSize: chestOfCoins.size)
chestOfCoins.physicsBody?.affectedByGravity = false
chestOfCoins.physicsBody?.dynamic = false
chestOfCoins.physicsBody?.categoryBitMask = physicsCategory.chests
chestOfCoins.physicsBody?.collisionBitMask = 0
chestOfCoins.physicsBody?.contactTestBitMask = physicsCategory.character
addChild(chestOfCoins)
}
func addCharacter() {
character.anchorPoint = CGPointZero
character.position = CGPoint(x: 300, y: 300)
character.zPosition = 20
character.size = CGSize(width: 200, height: 200)
character.physicsBody = SKPhysicsBody(rectangleOfSize: character.size)
character.physicsBody?.affectedByGravity = false
character.physicsBody?.dynamic = false
character.physicsBody?.categoryBitMask = physicsCategory.character
character.physicsBody?.contactTestBitMask = physicsCategory.chests
character.physicsBody?.collisionBitMask = 0
addChild(character)
}
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
physicsWorld.contactDelegate = self
let sceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sceneBody.friction = 0
background.zPosition = -20
background.anchorPoint = CGPointZero
background.position = CGPointZero
backgroundRepeated.zPosition = -20
backgroundRepeated.anchorPoint = CGPointZero
backgroundRepeated.position = CGPointMake(background.size.width-1,0)
addChest()
addCharacter()
addChild(background)
addChild(backgroundRepeated)
}
func didBeginContact(contact: SKPhysicsContact) {
var contactBody1: SKPhysicsBody
var contactBody2: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
contactBody1 = contact.bodyA
contactBody2 = contact.bodyB
} else {
contactBody1 = contact.bodyB
contactBody2 = contact.bodyA
}
if((contactBody1.categoryBitMask == 1) && (contactBody2.categoryBitMask == 2)) {
print("touched")
} else {
print("not these two")
}
}
override func update(currentTime: NSTimeInterval) {
let startWalking = gameActions.boolForKey("Walking")
if (startWalking == true) {
chestOfCoins.position = CGPoint(x: chestOfCoins.position.x - 5, y: chestOfCoins.position.y)
background.position = CGPoint(x: background.position.x - 5, y: background.position.y)
backgroundRepeated.position = CGPoint(x: backgroundRepeated.position.x - 5, y: backgroundRepeated.position.y)
if (background.position.x < -background.size.width) {
background.position = CGPointMake(backgroundRepeated.position.x + backgroundRepeated.size.width, background.position.y)
}
if (backgroundRepeated.position.x < -backgroundRepeated.size.width) {
backgroundRepeated.position = CGPointMake(background.position.x + background.size.width, backgroundRepeated.position.y)
}
}
}
}
Question
Can anyone tell me why, when the screen starts scrolling and those two items come into contact nothing happens and tell me how to fix it?
One also seems to go behind even though I set the zPosition of both to the same
Here is a pic, Don't judge about images :(
set either your chest or your other thingies property to dynamic = true
two non-dynamic physics bodies wont register contacts
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.
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))