I have a moving platform, but when the node is above the platform it doesnt move with the platform horizontally
In this article, the problem is explained: Moving Platform Hell
http://www.learn-cocos2d.com/2013/08/physics-engine-platformer-terrible-idea/
And in comment there is solutions for Box2D: Kinematic body
But what about SpriteKit ?
Update
I'm moving the platform using
let moveHPart1 = SKAction.moveByX(origW, y: 0, duration: moveDuration);
let moveHPart2 = SKAction.moveByX(-origW, y: 0, duration: moveDuration);
platform(SKAction.repeatActionForever(SKAction.sequence([moveHPart1, moveHPart2])));
Personally I am against of using physics for moving platforms because moving platform physics body has to be dynamic.
Static platforms
For static platforms setting physics body dynamic property to false is perfect solution. And this is how it is meant to be. Static bodies are not affected by forces but still give you a collision response. So, the problem is solved.
But you can't change position of static physics bodies by using forces. You can do this by using actions or manually setting its position. But, then you are removing a platform out of physics simulation.
In order to do all with physics, you have to keep the platform dynamic. But this can lead in other problems. For example when player lands on platform, he will push the platform down, because player has a mass.
Even if platform has big mass it will go down as time passing. Remember, we cant just update platforms x position manually, because this can make a mess with physics simulation.
"Moving platform hell" as stated in that nice article of LearnCocos2d is probably the best description what can happen when using physics for this task :-)
Moving platform example
To show you some possibilities I made an simple example on how you can move a platform with applying a force to it, and make a character to stay on it.There are few things I've done in order to make this to work:
Changed a mass of platform. This will prevent platform from moving when player bumps in it from below.
Made an edge based physics body to prevent platform falling when player lands on it.
Played with properties like allows rotation and friction to get desired effect.
Here is the code :
import SpriteKit
class GameScene: SKScene,SKPhysicsContactDelegate
{
let BodyCategory : UInt32 = 0x1 << 1
let PlatformCategory : UInt32 = 0x1 << 2
let WallCategory : UInt32 = 0x1 << 3
let EdgeCategory : UInt32 = 0x1 << 4 // This will prevent a platforom from falling down
let PlayerCategory : UInt32 = 0x1 << 5
let platformSpeed: CGFloat = 40.0
let body = SKShapeNode(circleOfRadius: 20.0)
let player = SKShapeNode(circleOfRadius: 20.0)
let platform = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width:100, height:20))
let notDynamicPlatform = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width:100, height:20))
override func didMoveToView(view: SKView)
{
//Setup contact delegate so we can use didBeginContact and didEndContact methods
physicsWorld.contactDelegate = self
//Setup borders
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody?.categoryBitMask = WallCategory
self.physicsBody?.collisionBitMask = BodyCategory | PlayerCategory
//Setup some physics body object
body.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
body.fillColor = SKColor.greenColor()
body.physicsBody = SKPhysicsBody(circleOfRadius: 20)
body.physicsBody?.categoryBitMask = BodyCategory
body.physicsBody?.contactTestBitMask = PlatformCategory
body.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
body.physicsBody?.allowsRotation = false
body.physicsBody?.dynamic = true
self.addChild(body)
//Setup player
player.position = CGPoint(x: CGRectGetMidX(self.frame), y:30)
player.fillColor = SKColor.greenColor()
player.physicsBody = SKPhysicsBody(circleOfRadius: 20)
player.physicsBody?.categoryBitMask = PlayerCategory
player.physicsBody?.contactTestBitMask = PlatformCategory
player.physicsBody?.collisionBitMask = PlatformCategory | WallCategory | BodyCategory
player.physicsBody?.allowsRotation = false
player.physicsBody?.friction = 1
player.physicsBody?.dynamic = true
self.addChild(player)
//Setup platform
platform.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) - 100)
platform.physicsBody = SKPhysicsBody(rectangleOfSize: platform.size)
platform.physicsBody?.categoryBitMask = PlatformCategory
platform.physicsBody?.contactTestBitMask = BodyCategory
platform.physicsBody?.collisionBitMask = BodyCategory | EdgeCategory | PlayerCategory
platform.physicsBody?.allowsRotation = false
platform.physicsBody?.affectedByGravity = false
platform.physicsBody?.dynamic = true
platform.physicsBody?.friction = 1.0
platform.physicsBody?.restitution = 0.0
platform.physicsBody?.mass = 20
//Setup edge
let edge = SKNode()
edge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPoint(x: 0, y:-platform.size.height/2), toPoint: CGPoint(x: self.frame.size.width, y:-platform.size.height/2))
edge.position = CGPoint(x:0, y: CGRectGetMidY(self.frame) - 100)
edge.physicsBody?.categoryBitMask = EdgeCategory
edge.physicsBody?.collisionBitMask = PlatformCategory
self.addChild(edge)
self.addChild(platform)
}
override func update(currentTime: NSTimeInterval) {
if(platform.position.x <= platform.size.width/2.0 + 20.0 && platform.physicsBody?.velocity.dx < 0.0 ){
platform.physicsBody?.velocity = CGVectorMake(platformSpeed, 0.0)
}else if((platform.position.x >= self.frame.size.width - platform.size.width/2.0 - 20.0) && platform.physicsBody?.velocity.dx >= 0.0){
platform.physicsBody?.velocity = CGVectorMake(-platformSpeed, 0.0)
}else if(platform.physicsBody?.velocity.dx > 0.0){
platform.physicsBody?.velocity = CGVectorMake(platformSpeed, 0.0)
}else{
platform.physicsBody?.velocity = CGVectorMake(-platformSpeed, 0.0)
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch: AnyObject? = touches.anyObject()
let location = touch?.locationInNode(self)
if(location?.x > 187.5){
player.physicsBody?.applyImpulse(CGVector(dx: 3, dy: 50))
}else{
player.physicsBody?.applyImpulse(CGVector(dx: -3, dy: 50))
}
}
}
Here is the result :
Related
I have a small test project (my first using Swift / XCode) which is designed to move me away from HTML5 and Canvas for game production.
The code compiles and runs fine. I use my iPhone as the test device rather than the built in simulator.
The symptoms of the problem are
that the lasers being repeatedly fired from the player's ship appear to occasionally bend around the aliens
the names being pulled out from the nodes are being shown as their default names not the names I assigned to them at creation
In some cases the collision works fine and the alien explosion is generated and the alien sprite node is removed from the scene.
I have named the alien nodes "alien" and the laser nodes "laser".
Both have their contactTestBitMask set to the same value.
Here is my GameScene.swift code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var lastUpdateTime: TimeInterval = 0
var delta: TimeInterval = 0
var sp_player: SKSpriteNode!
var stars: SKSpriteNode!
var deeperstars: SKSpriteNode!
var laser: SKSpriteNode!
var alien: SKSpriteNode!
var explosionSplat1: SKSpriteNode!
var playerScore: UInt32!
struct PhysicsCategory {
static let base:UInt32 = 0x1 << 0
static let alien:UInt32 = 0x1 << 1
static let laser:UInt32 = 0x1 << 2
static let player:UInt32 = 0x1 << 3
}
override func didMove(to view: SKView) { // called when the scene is presented into view (happens only once)
playerScore = 0
physicsWorld.contactDelegate = self
physicsWorld.gravity = .zero
// BACKGROUND
backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 48/255, alpha: 1.0)
print("Background color is set")
// WRAP THE STARFIELDS
// Front most layer of stars
let starsTexture = SKTexture(imageNamed: "stars.png")
let bgAnimation = SKAction.move(by: CGVector(dx: 0, dy: -starsTexture.size().height), duration: 5)
let bgReset = SKAction.move(by: CGVector(dx: 0, dy: starsTexture.size().height), duration: 0)
let bgConstantMotion = SKAction.repeatForever(SKAction.sequence([bgAnimation,bgReset]))
// Back layer of slower stars
let deeperStarsTexture = SKTexture(imageNamed: "stars-deeper.png")
let deeperStarsbgAnimation = SKAction.move(by: CGVector(dx: 0, dy: -deeperStarsTexture.size().height), duration: 8)
let deeperStarsbgReset = SKAction.move(by: CGVector(dx: 0, dy: deeperStarsTexture.size().height), duration: 0)
let deeperStarsbgConstantMotion = SKAction.repeatForever(SKAction.sequence([deeperStarsbgAnimation,deeperStarsbgReset]))
var i: CGFloat = 0
while i < 3
{
stars = SKSpriteNode(texture: starsTexture)
stars.position = CGPoint(x: frame.midX, y: starsTexture.size().height * i)
stars.size.height = frame.height
stars.run(bgConstantMotion)
stars.zPosition = -1
addChild(stars)
deeperstars = SKSpriteNode(texture: deeperStarsTexture)
deeperstars.position = CGPoint(x: frame.midX, y: deeperStarsTexture.size().height * i)
deeperstars.size.height = frame.height
deeperstars.run(deeperStarsbgConstantMotion)
deeperstars.zPosition = -1
addChild(deeperstars)
i += 1
}
// PLAYER
let playerTexture1 = SKTexture(imageNamed: "player-1.png")
let playerTexture2 = SKTexture(imageNamed: "player-2.png")
let playerAnimation = SKAction.animate(with: [playerTexture1, playerTexture2], timePerFrame: 0.2)
let constantAnimation = SKAction.repeatForever(playerAnimation)
sp_player = SKSpriteNode(texture: playerTexture1)
sp_player.position = CGPoint(x: frame.midX, y: (sp_player.size.height * 2))
sp_player.physicsBody = SKPhysicsBody(rectangleOf: sp_player.size)
sp_player.physicsBody!.isDynamic = false
sp_player.name = "player"
sp_player.run(constantAnimation)
addChild(sp_player)
// PLACE ALIENS
let alienTexture1 = SKTexture(imageNamed: "alien-1a.png")
let alienTexture2 = SKTexture(imageNamed: "alien-1b.png")
let alienAnimation = SKAction.animate(with: [alienTexture1, alienTexture2], timePerFrame: 0.4)
let constantAlienAnimation = SKAction.repeatForever(alienAnimation)
var x: CGFloat = 0, y: CGFloat = 0
while y < 6
{
while x < 6
{
alien = SKSpriteNode(texture: alienTexture1)
alien.position = CGPoint(x: 32 + (x * alien.size.width), y: (frame.size.height - (alien.size.height * 1.5) - (alien.size.height * y)))
print("Setting y to \(frame.size.height - (alien.size.height * y))")
alien.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: alien.size.width, height: alien.size.height))
alien.physicsBody!.isDynamic = false
alien.name = "alien"
alien.physicsBody!.contactTestBitMask = PhysicsCategory.laser
alien.run(constantAlienAnimation)
addChild(alien)
x += 1
}
y += 1
x = 0
}
print("Sprites added to scene")
spawnLasers()
}
func spawnLasers()
{
let delay1 = SKAction.wait(forDuration: 0.5)
let spawn = SKAction.run {
let laserTexture = SKTexture(imageNamed: "laser-1.png")
self.laser = SKSpriteNode(texture: laserTexture)
self.laser.position = CGPoint(x: self.sp_player.position.x, y: self.sp_player.position.y + self.sp_player.size.height)
self.laser.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.laser.size.width, height: self.laser.size.height))
self.laser.physicsBody!.isDynamic = true
self.laser.physicsBody!.linearDamping = 0
self.laser.physicsBody!.allowsRotation = false
self.laser.physicsBody!.contactTestBitMask = PhysicsCategory.laser
self.laser.name = "laser"
self.addChild(self.laser)
let shoot = SKAction.moveTo(y: self.frame.size.height, duration: 1)
let killLaser = SKAction.removeFromParent()
let handleLaser = SKAction.sequence([shoot,killLaser])
self.laser.run(handleLaser)
}
let action = SKAction.sequence([delay1,spawn])
let constantLasers = SKAction.repeatForever(action)
self.run(constantLasers)
}
func didBegin(_ contact: SKPhysicsContact) {
var check: UInt32 = 0
if contact.bodyA.node != nil
{
check += 1
}
if contact.bodyB.node != nil
{
check += 1
}
if check == 2
{
if contact.bodyA.node!.name == "alien" && contact.bodyB.node!.name == "laser"
{
// EXPLOSION
let explosionSplatTexture1 = SKTexture(imageNamed: "explosion-1a.png")
let explosionSplatTexture2 = SKTexture(imageNamed: "explosion-1b.png")
let explosionSplatTexture3 = SKTexture(imageNamed: "explosion-1c.png")
let explosionSplatTexture4 = SKTexture(imageNamed: "explosion-1d.png")
let explosionSplatAnimation = SKAction.animate(with: [explosionSplatTexture1, explosionSplatTexture2, explosionSplatTexture3, explosionSplatTexture4], timePerFrame: 0.1)
let killExplosion = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([explosionSplatAnimation,killExplosion])
explosionSplat1 = SKSpriteNode(texture: explosionSplatTexture1)
explosionSplat1.name = "explosion"
explosionSplat1.position = CGPoint(x: contact.bodyA.node!.position.x, y: contact.bodyA.node!.position.y)
addChild(explosionSplat1)
explosionSplat1.run(explosionSequence)
self.playerScore += 1
print("Score: \(self.playerScore!)")
contact.bodyA.node?.removeFromParent()
print("Alien named \(contact.bodyA.node?.name ?? "defaultAlienName") from scene")
contact.bodyB.node?.removeFromParent()
print("Laser named \(contact.bodyB.node?.name ?? "defaultLaserName") from scene")
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
//print("Contact ended between \(contact.bodyA) and \(contact.bodyB)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
// if let touch = touches.first {
// let position = touch.location(in: view)
// storedTouch = position
// }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first {
let position = touch.location(in: view)
var playerpos: CGPoint!
playerpos = sp_player.position
let pl_move = SKAction.move(to: CGPoint(x: position.x, y: playerpos.y), duration: 0.1)
sp_player.run(pl_move)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
/*
Need to figure out how to use storedTouch properly
to move player relative to the screen touch co-ordinates
*/
// if let touch = touches.first {
// let position = touch.location(in: view)
// }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if (lastUpdateTime > 0)
{
delta = currentTime - lastUpdateTime
} else {
delta = 0
}
lastUpdateTime = currentTime
}
}
When the game runs it produces this screen:
You can see the unexpected laser behavior here:
In my diags I get the following output from the collision function:
Score: 1
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 2
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 3
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 4
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
Score: 5
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
Score: 6
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
This is most likely my complete lack of understanding for optionals and how collision actually works. I'd be super grateful for any insights.
In your alien loop, as well as spawnLasers(), you are not giving the sprite nodes an actual PhysicsBody category. For sprites to be able to detect contact between one another, they need a category name and a contact name.
So in your while loop (building the aliens), you need to have this:
alien.physicsBody!.categoryBitMask = PhysicsCategory.alien
alien.physicsBody!.contactTestBitMask = PhysicsCategory.laser
And in spawnLasers(), you want this added:
self.laser.physicsBody!.categoryBitMask = PhysicsCategory.laser
and change the contactTestBitMask to alien, not laser:
self.laser.physicsBody!.contactTestBitMask = PhysicsCategory.alien
Hopefully you can see that the alien category wants to know when lasers touch, and the laser category wants to know when the aliens touch.
To visually help you, turn on the show physics option, this way you can see the actual physics bodies you are dealing with.
To do this, in your GameViewController (or similar), find:
showsFPS = true
showsNodeCount = true
You want to add the following:
showsPhysics = true
This will help with seeing the actual physics bodies on screen.
In:
func didBegin(_ contact: SKPhysicsContact)
you are only testing for BodyA being alien and BodyB being laser.
This is the contact test:
if contact.bodyA.node!.name == "alien" && contact.bodyB.node!.name == "laser"
I believe BodyA could be laser and BodyB be alien. Basically the physics engine contact events could be "A hitting B", or "B hitting A".
Therefore, a quick and dirty solution is to create another if statement below the current one, but changing the body names, so:
if contact.bodyA.node!.name == "laser" && contact.bodyB.node!.name == "alien" {
and duplicate the code from your existing if statement, and changing the two print statements.
This isn't the ideal way to do it, but hopefully when you tidy it up you'll get an understanding of what the physics contact is doing.
I am hoping once you have implmented the above, you will be in a much better shape.
I'm building a pong/breaker game with a ball and non-static blocks. I'd like the ball to never stop moving, but whenever it hits a block it loses velocity.
I have the restitusion = 1 for all sprites involved, I've tried setting the mass equal to each other and the density and the friction = 0. But, the ball still loses velocity on a bounce.
When the ball hits a block I'm removing it in the didBegin(contact:) function. I've also tried delaying the removal and it didn't help.
I'd like for the ball to have a constant velocity, but still be able to interact with the blocks as later I'd like to add blocks that can be hit without immediately being broken. So, the blocks can't be static but the ball needs to have a constant velocity.
My code for creating the ball node:
func ballNode(_ position: CGPoint?) -> SKSpriteNode {
let node = SKSpriteNode()
node.position = position == nil ? CGPoint(x: size.width/2, y: 100) : position!
node.size = CGSize(width: 17, height: 17)
//background
let background = SKShapeNode(circleOfRadius: 8.5)
background.fillColor = UIColor.white
node.addChild(background)
//physics
node.physicsBody = SKPhysicsBody(circleOfRadius: 8.5)
node.physicsBody?.allowsRotation = true
node.physicsBody?.friction = 0
node.physicsBody?.restitution = 1
node.physicsBody?.linearDamping = 0
node.physicsBody?.angularDamping = 0
node.physicsBody?.categoryBitMask = BallCategory
node.physicsBody?.contactTestBitMask = AddBlockBorderCategory | PaddleCategory
node.physicsBody?.collisionBitMask = PaddleCategory | BlockCategory | BorderCategory
return node
}
My code for creating the block node:
func createBlockNode() -> BlockNode {
let width = (size.width-CGFloat(6*layout[0].count))/CGFloat(layout[0].count)
let height = width*0.5
let nodeSize = CGSize(width: width, height: height)
let node = BlockNode(texture: nil, size: nodeSize)
let background = SKShapeNode(rectOf: nodeSize)
background.fillColor = .darkGray
background.strokeColor = .lightGray
//physics
node.physicsBody = SKPhysicsBody(rectangleOf: nodeSize)
node.physicsBody?.restitution = 1
node.physicsBody?.allowsRotation = true
node.physicsBody?.friction = 0
node.physicsBody?.categoryBitMask = BlockCategory
node.physicsBody?.contactTestBitMask = BallCategory
node.addChild(background)
return node
}
And a screen recording:
screen recording of the ball losing velocity
I'm starting the ball using this:
ball!.physicsBody?.applyForce(CGVector(dx: 0, dy: 50))
I'm using a repeat action. When the app first loads the sprites do spawn in front of the background and so you can see them. But then when you restart the game from the score scene, the nodes spawn behind the background and I can't get them to come to the front. Does anyone know how I can fix this?
override init(size: CGSize) {
super.init(size: size)
//Background
for var index = 0; index < 2; ++index {
let bg = SKSpriteNode(imageNamed: "background")
bg.position = CGPoint(x: -100, y: index * Int(bg.size.height))
bg.anchorPoint = CGPointZero
bg.name = "background"
self.addChild(bg)
}
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(callEnemy), SKAction.waitForDuration(1.0)])))
The runAction is the code to repeat an action that doesn't seem to be working
//Player functions
foreground = SKNode()
addChild(foreground)
player = createPlayer()
foreground.addChild(player)
//Game hud
gameHud = SKNode()
addChild(gameHud)
}
func callEnemy() {
if player.physicsBody?.dynamic == true {
spawnEnemy()
}
}
func spawnEnemy() -> SKNode{
let enemy = SKSpriteNode(imageNamed: "Enemy1")
enemy.position = CGPoint(x: frame.size.width * random(min: 0, max: 1), y: 690 )
addChild(enemy)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.width / 2)
enemy.physicsBody?.dynamic = true
enemy.physicsBody?.allowsRotation = false
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.velocity = CGVector(dx: enemy.physicsBody!.velocity.dx, dy: -200.0)
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.angularDamping = 0.0
enemy.physicsBody?.linearDamping = 0.0
if enemy.position.y <= CGFloat(0) {
enemy.removeFromParent()
}
return enemy
}
In order to have the nodes be on top of the background you need to change their zPosition. If you specify:
bg.zPosition = 0
and
foreground.zPosition = 1
the foreground will now be on top of the background. The zPosition specifies the location of each node on the z-axis.
I have a 40x40 rectangle node, and I want to detect when the bottom touches a platform.
I tried this
let feet = SKPhysicsBody(rectangleOfSize: CGSize(width: hero.frame.size.width, height: 1), center: CGPoint(x: 0, y: -(hero.frame.size.height/2 - 0.5)))
then set the categoryBitMask, collisionBitMask, contactTestBitMaskand added it to the hero
hero.physicsBody = SKPhysicsBody(bodies: [feet])
But in didBeginContact the println() doesn't print.
I need a body for the bottom of the rectangle and one for the top, because if hero hits a platform from below the collision should push him down.
Update
Here is how I set the bit masks
let heroFeetCategory: UInt32 = 1 << 0
let edgeCategory: UInt32 = 1 << 1
let groundCategory: UInt32 = 1 << 2
let feet = SKPhysicsBody(rectangleOfSize: CGSize(width: hero.frame.size.width, height: 10), center: CGPoint(x: 0, y: -(hero.frame.size.height/2 - 5)))
feet.collisionBitMask = edgeCategory | groundCategory
feet.contactTestBitMask = groundCategory
feet.categoryBitMask = heroFeetCategory
hero.physicsBody = SKPhysicsBody(bodies: [feet])
hero.physicsBody?.usesPreciseCollisionDetection = true
hero.physicsBody?.velocity = CGVectorMake(0, 0)
hero.physicsBody?.restitution = 0.0
hero.physicsBody?.friction = 0.0
hero.physicsBody?.angularDamping = 0.0
hero.physicsBody?.linearDamping = 1.0
hero.physicsBody?.allowsRotation = false
hero.physicsBody?.mass = 0.0641777738928795
world.addChild(hero)
and for the ground
let ground = SKSpriteNode(color: UIColor.whiteColor(), size: CGSizeMake(38, 38))
ground.name = "groundName"
ground.position = CGPoint(x: 0, y: -(self.frame.size.height/2 - ground.frame.size.height/2))
ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.size)
ground.physicsBody?.collisionBitMask = edgeCategory | heroFeetCategory
ground.physicsBody?.contactTestBitMask = heroFeetCategory
ground.physicsBody?.categoryBitMask = groundCategory
world.addChild(ground)
And how I detect if they touch
func didBeginContact(contact: SKPhysicsContact) {
var notTheHero: SKPhysicsBody!;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
notTheHero = contact.bodyB;
} else {
notTheHero = contact.bodyA;
}
println(notTheHero.node) // print "heroName"
if (notTheHero.categoryBitMask == groundCategory) {
println("touch began?"); // is never called
}
}
collision is fine, but when you print the node's name through the physics body with notTheHero.node you only access the SKNode, and not the NSString property of its name. Instead, try something like println(notTheHero.node.name);
I can’t find any help or solution for my problem. I have 4 SKSpriteNodes named: bottomGoalGreen, topGoalGreen, bottomGoalBlue, and topGoalBlue. I also have a ball that is a SKSpriteNode named ball. My first question/problem is when I have my ball collide with, for example, topGoalGreen or bottomGoalGreen, I want the topGoalGreen to be removed as well as bottomGoalGreen and then topGoalBlue and bottomGoalBlue to appear and vice versa. My other problem is with my ball and the collision. I have two SKAction.moveToY so the ball can move up and down the screen. I was wondering if the SKActions could be the culprit to why the collision will not happen. I hope I improved my question. If not, I will try again to clarify.
import Foundation
import SpriteKit
import UIKit
struct PhysicsCatagory {
static let bottomGoalGreen : UInt32 = 1
static let topGoalGreen : UInt32 = 2
static let bottomGoalBlue : UInt32 = 4
static let topGoalBlue : UInt32 = 8
static let ball : UInt32 = 16
}
class GamePlayScene: SKScene, SKPhysicsContactDelegate {
var topGoalGreen = SKSpriteNode(imageNamed: "green goal (top).png")
var bottomGoalGreen = SKSpriteNode(imageNamed: "green goal (bottom).png")
var topGoalBlue = SKSpriteNode(imageNamed: "blue goal (top).png")
var bottomGoalBlue = SKSpriteNode(imageNamed: "blue goal (bottom).png")
var ball = SKSpriteNode(imageNamed: "green ball.png")
override func didMoveToView(view: SKView) {
//setup scene
physicsWorld.gravity = CGVector.zeroVector
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
self.scene?.backgroundColor = UIColor.blackColor()
self.scene?.size = CGSize(width: 640, height: 1136)
//Top goal green code
topGoalGreen.position = CGPoint (x: self.size.width * 0.5, y: self.size.width * 1.52)
topGoalGreen.physicsBody = SKPhysicsBody(rectangleOfSize: topGoalGreen.size)
topGoalGreen.size = CGSize (width: 300, height: 309)
topGoalGreen.physicsBody?.dynamic = false
topGoalGreen.physicsBody?.categoryBitMask = PhysicsCatagory.topGoalGreen
topGoalGreen.physicsBody?.collisionBitMask = 0
topGoalGreen.physicsBody?.contactTestBitMask = PhysicsCatagory.ball
self.addChild(topGoalGreen)
//Bottom goal code
bottomGoalGreen.position = CGPoint (x: self.size.width * 0.5, y: self.size.width * 0.252)
bottomGoalGreen.size = CGSize (width: 300, height: 309)
bottomGoalGreen.physicsBody?.dynamic = false
bottomGoalGreen.physicsBody?.categoryBitMask = PhysicsCatagory.bottomGoalGreen
bottomGoalGreen.physicsBody?.contactTestBitMask = PhysicsCatagory.ball
self.addChild(bottomGoalGreen)
//Ball code
ball.position = CGPoint (x: self.size.width * 0.5, y: self.size.width * 0.9)
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.frame.size.width / 2)
ball.size = CGSize (width: 80, height: 82)
ball.physicsBody?.dynamic = true
ball.physicsBody?.categoryBitMask = PhysicsCatagory.ball
ball.physicsBody?.collisionBitMask = 0
ball.physicsBody?.contactTestBitMask = PhysicsCatagory.topGoalGreen
ball.physicsBody?.categoryBitMask = PhysicsCatagory.bottomGoalGreen
ball.physicsBody?.collisionBitMask = PhysicsCatagory.bottomGoalGreen
ball.physicsBody?.contactTestBitMask = PhysicsCatagory.bottomGoalGreen
let moveBallUp = SKAction.moveToY(1040, duration: 2)
let moveBallDown = SKAction.moveToY(90, duration: 2)
let moveUpAndDown = SKAction.sequence([moveBallUp, moveBallDown])
let moveUpAndDownForever = SKAction.repeatActionForever(moveUpAndDown)
ball.runAction(moveUpAndDownForever)
self.addChild(ball)
}
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody = contact.bodyA
var secondBody : SKPhysicsBody = contact.bodyB
if (((firstBody.categoryBitMask == PhysicsCatagory.topGoalGreen) && (secondBody.categoryBitMask == PhysicsCatagory.ball)) ||
((firstBody.categoryBitMask == PhysicsCatagory.ball) && (secondBody.categoryBitMask == PhysicsCatagory.topGoalGreen))){
CollisionWithBall(firstBody.node as! SKSpriteNode, ball: secondBody.node as! SKSpriteNode)
NSLog("Collision!")
}
}
func CollisionWithBall(topGoalGreen : SKSpriteNode, ball : SKSpriteNode) {
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
I figured out what I was missing. I was missing a SKPhysicsBody for both goals and ball. Thanks to the other commenters for trying to help me.
//Top goal green code
topGoalGreen.position = CGPoint (x: self.size.width * 0.5, y: self.size.width * 1.67)
topGoalGreen.size = CGSize (width: 400, height: 80)
topGoalGreen.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize (width: 10, height: 10))
topGoalGreen.physicsBody?.dynamic = false
topGoalGreen.physicsBody?.categoryBitMask = PhysicsCatagory.topGoalGreen
topGoalGreen.physicsBody?.contactTestBitMask = PhysicsCatagory.ball
self.addChild(topGoalGreen)
In order to register contact you should set contact delegate.
physicsWorld.contactDelegate = self
Otherwise , you can't use methods like didBeginContact or didEndContact. Also you have to set category, contact and collision bitmasks properly in order to make everything to work. So, next thing would be to set category and contact bit masks properly.
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
//Contact will be registered when ball makes a contact with top or bottom goal.
ball.physicsBody?.contactTestBitMask = PhysicsCategory.topGoal | PhysicsCategory.bottomGoal
You have to follow this principle as well for top and bottom goal nodes.
Check out this great answer by rickster in order to understand how things works when using physics engine in SpriteKit.