I'm working with collision detection with SpriteKit and Swift. I have a SKScene that responds to a collision with the didBeginContact function:
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == ColliderType.Food && contact.bodyB.categoryBitMask == ColliderType.Head) {
placeFoodInRandomGridLocation()
}
}
func placeFoodInRandomGridLocation() {
let randomX = arc4random_uniform(UInt32(myGrid.columnCount))
let randomY = arc4random_uniform(UInt32(myGrid.rowCount))
foodSpriteHolder.position = CGPoint(x: colLines[Int(randomX)], y: rowLines[Int(randomY)])
}
The problem is that I can easily adjust the position of the foodSpriteHolder before this didBeginContact function fires. It simply will not move the foodSpriteHolder when the placeFoodInRandomGridLocation is called.
It seems like a scope issue. I'm just not sure how to isolate why the position won't update. I can even make the foodSpriteHolder visibility hidden in this flow...So, I know i can access it.
For reference here is how the physics body is setup for the Food class item that is within the foodSpriteHolder:
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size, center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Food
Lastly the placeFoodInRandomGridLocation function definitely gets called...The position just won't update.
Thanks for any ideas,
Josh
The answer in this case was to remove the node when the collision is detected:
contact.bodyA.node?.removeFromParent()
Now I can create a new node in a new position. Since affectedByGravity is set to false the node wouldn't update its position.
Related
I'm currently working on a small iOS game. In its current iteration, 20 targets spawn and move across the screen space-invaders style, and you control a little ship to shoot and destroy them. The code for my targets, the player ship's bullets, and a simple collision detection function I've written in the interim are as follows:
class Red_Target: SKSpriteNode{
var game_scene: GameScene!
private var ship_texture: SKTexture!
convenience init(scale: CGFloat, game_world: GameScene){
self.init(texture: SKTexture(imageNamed: "Proto Target"))
self.ship_texture = SKTexture(imageNamed: "Proto Target")
self.setScale(scale)
game_scene = game_world
game_scene.addChild(self)
self.position = CGPoint(x: game_scene.view!.bounds.width/10, y: 9 * game_scene.view!.bounds.height/10)
//self.physicsBody = SKPhysicsBody(texture: ship_texture, size: self.size)
self.physicsBody = SKPhysicsBody(circleOfRadius: 13)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.categoryBitMask = CollisionType.Enemy.rawValue
self.physicsBody!.contactTestBitMask = CollisionType.Player_Bullet.rawValue
}
func move() {
self.run(space_invaders(scene: game_scene))
}
}
class PC_Bullet: SKSpriteNode{
convenience init(scale: CGFloat){
self.init(imageNamed: "Goodbullet")
self.setScale(scale)
self.physicsBody = SKPhysicsBody(circleOfRadius: 3)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.categoryBitMask = CollisionType.Player_Bullet.rawValue
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.contactTestBitMask = CollisionType.Enemy.rawValue
}
}
func didBegin(_ contact: SKPhysicsContact) {
contact.bodyA.node!.removeFromParent()
contact.bodyB.node!.removeFromParent()
}
}
This code, in its current iteration, works just fine. However, if the line defining the target's physicsbody as its texture is uncommented and the line defining physicsbody as circleOfRadius is removed, the game will consistently crash after the 5th target is destroyed, claiming that didBegin unwraps a nil value. Why does this only happen from physics bodies with textures? Is there any way I could change the code for this to work? I would love to be able to use the physics body from texture function later on, when working with more irregular shapes.
You are pulling a classic nooby mistake. You are removing your bodies too early. If you browse Stackoverflow you will find a plethera of ways to solve it.
The basic idea is do not remove your sprites until the end of the physics phase because your 1 sprite could have multiple contact points to handle. So come up with a way to flag sprites that need to be deleted, and remove them during the didSimulatePhysics function.
I'm having an issue with contact detection in Swift 3 using SpriteKit. The contact detection is working...sometimes. It seems purely random as to when it fires and when it doesn't.
I have a yellow "bullet" that moves up on the screen to hit a red sprite named targetSprite. The desired behavior is to have the bullet removed when it hits the target, but sometimes it just passes through underneath.
I've found many questions about contact detection not working at all, but I haven't found any dealing with inconsistent detection.
What can I do to fix this?
Here's the code:
import SpriteKit
import GameplayKit
enum PhysicsCategory:UInt32 {
case bullet = 1
case sprite1 = 2
case targetSprite = 4
// each new value should double the previous
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create sprites
let sprite1 = SKSpriteNode(color: SKColor.blue, size: CGSize(width:100,height:100))
let targetSprite = SKSpriteNode(color: SKColor.red, size: CGSize(width:100,height:100))
let bullet = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 20, height: 20))
// show the bullet?
var isShowingBullet = true
// Timers
//var timer:Timer? = nil
var fireBulletTimer:Timer? = nil
// set up bullet removal:
var bulletShouldBeRemoved = false
let bulletMask = PhysicsCategory.bullet.rawValue
override func didMove(to view: SKView) {
// Physics
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
targetSprite.physicsBody?.affectedByGravity = false
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.centerRect.size)
bullet.physicsBody?.affectedByGravity = false
// Contact Detection:
targetSprite.physicsBody?.categoryBitMask = PhysicsCategory.targetSprite.rawValue
targetSprite.physicsBody?.contactTestBitMask =
//PhysicsCategory.sprite1.rawValue |
PhysicsCategory.bullet.rawValue
targetSprite.physicsBody?.collisionBitMask = 0 // no collision detection
// bullet physics
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet.rawValue
bullet.physicsBody?.contactTestBitMask =
PhysicsCategory.targetSprite.rawValue
bullet.physicsBody?.collisionBitMask = 0 // no collision detection
// execute once:
fireBulletTimer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.fireBullet),
userInfo: nil,
repeats: false)
// Add sprites to the scene:
self.addChild(sprite1)
self.addChild(bullet)
self.addChild(targetSprite)
// Positioning
targetSprite.position = CGPoint(x:0, y:300)
// Note: bullet and sprite1 are at 0,0 by default
// Delegate
self.physicsWorld.contactDelegate = self
}
func didBegin(_ contact: SKPhysicsContact) {
print("didBegin(contact:))")
//let firstBody:SKPhysicsBody
// let otherBody:SKPhysicsBody
// Use 'bitwise and' to see if both bits are 1:
if contact.bodyA.categoryBitMask & bulletMask > 0 {
//firstBody = contact.bodyA
//otherBody = contact.bodyB
print("if contact.bodyA....")
bulletShouldBeRemoved = true
}
else {
//firstBody = contact.bodyB
//otherBody = contact.bodyA
print("else - if not contacted?")
}
/*
// Find the type of contact:
switch otherBody.categoryBitMask {
case PhysicsCategory.targetSprite.rawValue: print(" targetSprite hit")
case PhysicsCategory.sprite1.rawValue: print(" sprite1 hit")
case PhysicsCategory.bullet.rawValue: print(" bullet hit")
default: print(" Contact with no game logic")
}
*/
} // end didBegin()
func didEnd(_ contact: SKPhysicsContact) {
print("didEnd()")
}
func fireBullet() {
let fireBulletAction = SKAction.move(to: CGPoint(x:0,y:500), duration: 1)
bullet.run(fireBulletAction)
}
func showBullet() {
// Toggle to display or not, every 1 second:
if isShowingBullet == true {
// remove (hide) it:
bullet.removeFromParent()
// set up the toggle for the next call:
isShowingBullet = false
// debug:
print("if")
}
else {
// show it again:
self.addChild(bullet)
// set up the toggle for the next call:
isShowingBullet = true
// debug:
print("else")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bulletShouldBeRemoved {
bullet.removeFromParent()
}
}
}
Sorry for the inconsistent indentation, I can't seem to find an easy way to do this...
EDIT:
I have found that using 'frame' instead of 'centerRect' makes the collision area the size of the sprite. For example:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
should be:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.frame.size)
First advice - Do not use NSTimer (aka Timer) in SpriteKit. It is not paired with a game loop and can cause different issues in a different situations. Read more here ( answer posted by LearnCocos2D)
So, do this:
let wait = SKAction.wait(forDuration: 1)
run(wait, completion: {
[unowned self] in
self.fireBullet()
})
What I have noticed is that if I run your code in Simulator, I get the behaviour you have described. didBegin(contact:) is being fired randomly. Still, this is not happening on a device for me, and device testing is what matters.
Now, when I have removed Timer and did the same thing with SKAction(s) everything worked, means contact were detected every time.
Have you tried adding
.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true
SpriteKit physics engine will calculate collision correctly if you do following:
1) set "usesPreciseCollisionDetection" property to true for bullet's physics body. This will change collision detection algorithm for this body. You can found more information about this property here, chapter "Working with Collisions and Contacts".
2) move your bullet using applyImpulse or applyForce methods. Collision detection will not woking correctly if you move body by changing it's position manually. You can find more information here, chapter "Making Physics Bodies Move".
I'm new with sprite kit. I have tried simple ball bouncing game with 2 player, another is tracking the ball slowly. But I have discovered a problem. When I move the line to ball (with edge) ball disappearing from the screen. Another times not a problem, ball bouncing. What is the problem?
I have one GameScene, sks and ViewController. My sprite nodes coming from sks. If someone explain this case. It would be better. I have attached what I did below.
My GameScene:
class GameScene: SKScene {
var ball = SKSpriteNode()
var enemy = SKSpriteNode()
var main = SKSpriteNode()
override func didMove(to view: SKView) {
ball = self.childNode(withName: "ball") as! SKSpriteNode
enemy = self.childNode(withName: "enemy") as! SKSpriteNode
main = self.childNode(withName: "main") as! SKSpriteNode
ball.physicsBody?.applyImpulse(CGVector(dx: -20, dy: -20))
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.angularDamping = 0
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
self.physicsBody = border
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 0.5))
}
View controller:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
}
}
override var prefersStatusBarHidden: Bool {
return true
}
Pad settings:
Ball settings:
Some updates
I have tried some messages in update function, then encountered with same case ball goes outside from left side of the device (using iPhone 6S)
2016-12-08 14:27:54.436485 Pong[14261:3102941] fatal error: ball out of left bounds: file
You're pinching the ball against the wall, with the enemy. This means that the force is eventually enough to create enough speed of ball movement/force to overcome the physics system, so it pops through the wall. If you make your enemy stop before it pinces the ball against the wall, you should be fine.
This 'pincing' is occurring because of this line of code:
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 0.5))
This is making the enemy chase the ball, which is a good idea for a ball game, but for the way it's being moved is wrong. Using an Action means the enemy has infinite force applied to it, and is aiming for the middle of the ball.
So when the ball gets to the wall, it's stopped against a physics object with infinite static force, then this enemy comes along and applies infinite force from the other side... and the ball either pops inside the bounds of the enemy, or over the other side of the wall, because it's being crushed by infinite forces.
So you either need to take very good care of how you control the enemy with Actions, or use forces to control the enemy, as these won't be infinite, and the physics system will be able to push back on the enemy.
How easy is it to reproduce the problem? In update(), print the ball's position to see where it is when it has 'disappeared'. (this will produce a lot of output, so be warned).
From what you've posted, it doesn't look like the ball is set to collide with the border, meaning the ball will not react (i.e. bounce off) the border and the border itself is immobile (as it's an edge-based physics body). This, combined with a high ball velocity (from a hard hit) might make it possible that you have hit the ball so hard with the 'main' sprite that it's gone through the border - using preciseCollisionDetection=true might resolve this but give the border a category first and add this to the ball's collisionBitMask.
here is an example of what Steve is saying (in your .update())
if ball.position.x > frame.maxX { fatalError(" ball out of right bounds") }
if ball.position.x < frame.minX { fatalError(" ball out of left bounds") }
if ball.position.y > frame.maxY { fatalError(" ball out of top bounds") }
if ball.position.y < frame.minY { fatalError(" ball out of bottom bounds) }
you could also just spam your debug window:
print(ball.position)
This will help you to find out what is going on--if your ball is flying through the boundary, or if it's getting destroyed somewhere, or some other possible bug.
As a workaround (for now) I would just replace the above "fatalError" with "ball.position = CGPoint(x: 0, y: 0)" or some other position to "reset" the ball in case of it getting lost.
You could even store it's last position in a variable, then restore it to that should the above if-statements trigger.
var lastBallLocation = CGPoint(x: 0, y: 0) // Just to initialize
override func update( prams ) {
if ball.position.x > frame.maxX { ball.position = lastBallLocation }
// .. copy the other three cases
lastBallLocation = ball.position // update only on successful position
Or, you could try making the walls thicker (use a shape node or spritenode and lay them on the outside of the frame such as the walls of a house, and your view on screen is the "room")
each wall also has a physics body for bouncing:
So I have two objects that should lose health points at an collision.
func addPlayer(xPos: CGFloat, yPos: CGFloat){
playerNode = SKSpriteNode(imageNamed: "player")
playerNode.physicsBody = SKPhysicsBody(circleOfRadius: width/2)
playerNode.physicsBody!.affectedByGravity = false
playerNode.physicsBody!.categoryBitMask = PhysicsCategory.Player
playerNode.physicsBody!.contactTestBitMask = PhysicsCategory.Wall | PhysicsCategory.Zombie
playerNode.physicsBody!.collisionBitMask = PhysicsCategory.Wall | PhysicsCategory.Zombie
playerNode.name = "Player"
player = Player(node: playerNode, healthPoints: 100, attack: 10)
playerNode.position.x = xPos
playerNode.position.y = yPos
playerNode.size = CGSize(width: width, height: width)
addChild(playerNode)
}
func addZombie(xPos: CGFloat, yPos: CGFloat){
zombieNode = SKSpriteNode(imageNamed: "zombie")
zombieNode.physicsBody = SKPhysicsBody(circleOfRadius: width/2)
zombieNode.physicsBody!.affectedByGravity = false
zombieNode.physicsBody!.categoryBitMask = PhysicsCategory.Zombie
zombieNode.physicsBody!.contactTestBitMask = PhysicsCategory.Zombie | PhysicsCategory.Player | PhysicsCategory.Wall
zombieNode.physicsBody!.collisionBitMask = PhysicsCategory.Zombie | PhysicsCategory.Player | PhysicsCategory.Wall
zombieNode.name = "Zombie"
zombie = Zombie(node: zombieNode, healthPoints: 50, attack: 5)
Zombies.append(zombie!)
zombieNode.position.x = xPos
zombieNode.position.y = yPos
zombieNode.size = CGSize(width: width, height: width)
addChild(zombieNode)
}
When a collision appears this function get activated:
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if(firstBody.name == "Player" && secondBody.name == "Zombie"){
changeHealthPointsForZombieWithNode(secondBody, points: player!.attack)
} else if(firstBody.name == "Zombie" && secondBody.name == "Player"){
changeHealthPointsForPlayer(secondBody, points: zombie!.attack)
print(player!.healthPoints)
}
}
func changeHealthPointsForZombieWithNode(node: SKSpriteNode, points: Int) {
for zombie in Zombies {
if zombie.node == node {
zombie.healthPoints -= points
print(zombie.healthPoints)
if(zombie.healthPoints <= 0){
zombieNode.removeFromParent()
}
return
}
}
}
func changeHealthPointsForPlayer(node: SKSpriteNode, points: Int) {
player!.healthPoints -= points
if(player!.healthPoints <= 0){
playerNode.removeFromParent()
gameOver = true
}
}
I want to subtract the health points of the zombie depending on the attack of the player and other way around. When the player hits the zombie the zombie should lose life points. When the zombie hits the player the player should lose life points. Every player/zombie got health points and an attack value. The Problem is that some zombies are killable and lose health and other (normally 1-3) are not able to lose health. These zombies who aren't able to lose health are the only one able to kill the player. Zombies that lose health can't deal damage(why?)? So there only able to do one thing(attack or lose health) although they should be able to do two things(attack and lose health).
Keep in mind that the two physics bodies described in the contact parameter are not passed in a guaranteed order. Inside the delegate function there is not such a thing as "A is the one colliding with B, not the other way around". There is only "A and B are colliding". That said, you have two options at least, depending on your game mechanics:
Make player AND zombie deal damage to each other every time they collide.
Make for player and zombie a subclass of SKSpriteNode with a property isAttacking, a boolean to determine when the entity should deal damage when detecting a contact with it. For the player this boolean might get "activated" every button/tap press. For the zombie, every once in a while, if close enough to the player.
Additionally, the way you implemented you will be getting doubled contact notifications. SpriteKit will detect that the player is in contact with the zombie, by the player contact mask and zombie category mask, and that the zombie is in contact with the player, by the zombie contact mask and the player category mask. You usually don't want this to happen. You should make only one of them detect it. I would suggest the player. This way there is no need to set the zombie contact mask. As long as you set zombie in the player's contact mask and the zombie's category mask, you will already have the detection (and once).
Alright, so I have contact detection set up between 2 nodes - savior and chicken1. This is set up here:
//This is within GameScene class
var screenTouches = Bool()
enum ColliderType:UInt32 {
case Savior = 1
case Chicken1 = 2
}
savior.physicsBody?.categoryBitMask = ColliderType.Savior.toRaw()
savior.physicsBody?.contactTestBitMask = ColliderType.Chicken1.toRaw()
savior.physicsBody?.collisionBitMask = ColliderType.Chicken1.toRaw()
chicken1.physicsBody?.categoryBitMask = ColliderType.Chicken1.toRaw()
chicken1.physicsBody?.contactTestBitMask = ColliderType.Savior.toRaw()
chicken1.physicsBody?.collisionBitMask = ColliderType.Savior.toRaw()
//This is outside of Gamescene class
//Collision detection
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == ColliderType.Savior.toRaw() && contact.bodyB.categoryBitMask == ColliderType.Chicken1.toRaw() ) {
chicken1.hidden = true
let chickenGrabbedLeft = SKAction.moveTo(CGPointMake(self.size.width * 0.1,self.size.height * 1.2), duration:0)
chicken1.runAction(chickenGrabbedLeft)
println("contact made")
} else if (contact.bodyA.categoryBitMask == ColliderType.Chicken1.toRaw() && contact.bodyB.categoryBitMask == ColliderType.Savior.toRaw()) {
chicken1.hidden = true
let chickenGrabbedLeft = SKAction.moveTo(CGPointMake(self.size.width * 0.1,self.size.height * 1.2), duration:0)
chicken1.runAction(chickenGrabbedLeft)
println("contact made")
}
}
When savior comes in contact with chicken1, I need it to look like chicken1 has disappeared. As it is, I have it so that chicken1 becomes hidden when it touches savior, but this isn't enough because savior still collides with it and the user can tell that the object is still there even if it isn't visible.
I don't want to delete chicken1 because I still need it to be present within the game. So I am now trying to get chicken1 to move back to its starting position (which is offscreen) when it touches savior. I did this by placing the SKAction in the above function.
It is not working. When savior touches chicken1, chicken1 still just gets hidden. It doesn't move. What should I do?
From your question, I assume that your function still runs even when chicken1 becomes hidden.
What you can do is to use a BOOL that changes to true when savior comes into contact with chicken1 and use that as a condition before running your action and change it back to false when you want it to no longer interact with those objects.