collision not detected between SKSpitekit nodes - swift

I am building a maze and I have added some SKSpritekit nodes for the walls and a dot for the player. however when the dot and the walls collide, there is no detection of collision. my code is as follows:
import UIKit
import SpriteKit
import GameplayKit
import Foundation
import GameplayKit
class level1: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
var dot = SKSpriteNode()
override func sceneDidLoad () {
buildMaze()
addDot()
func addDot() {
let startNum = x * (y - 1)
startCoord = coordArray[startNum]
dot = SKSpriteNode(imageNamed: "redDot")
dot.physicsBody?.isDynamic = true
dot.size = CGSize(width: 20, height: 20)
dot.position = startCoord
dot.physicsBody = SKPhysicsBody(circleOfRadius: 10)
dot.physicsBody?.mass = 0
dot.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(dot)
}
func buildMaze() {
let difference = coordArray[1].x - coordArray[0].x
let wallDistance = difference/2
let thickness = CGFloat(3)
let length = difference - CGFloat(thickness)/2
var count = 0
for point in coordArray {
let northWall = SKSpriteNode(color: SKColor.black, size : CGSize (width: length, height: thickness))
northWall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: length, height: thickness))
northWall.physicsBody?.mass = 200000
let southWall = SKSpriteNode(color: SKColor.black, size : CGSize (width: length, height: thickness))
southWall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: length, height: thickness))
southWall.physicsBody?.mass = 200000
let eastWall = SKSpriteNode(color: SKColor.black, size : CGSize (width: thickness, height: length ))
eastWall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: thickness, height: length))
eastWall.physicsBody?.mass = 200000
let westWall = SKSpriteNode(color: SKColor.black, size : CGSize (width: thickness, height: length ))
westWall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: thickness, height: length))
westWall.physicsBody?.mass = 200000
if !instructions[count].contains("N") {
//print("added north wall")
northWall.position = CGPoint (x: point.x , y: point.y + wallDistance)
if nodes(at: northWall.position) == [] {
addChild(northWall)}
else {print("north wall already there")}
}
if !instructions[count].contains("S") {
//print("added south wall")
southWall.position = CGPoint (x: point.x , y: point.y - wallDistance)
if nodes(at: southWall.position) == [] {
addChild(southWall)}
else {//print("southwall already there")
}
}
if !instructions[count].contains("E") {
//print("added east wall")
eastWall.position = CGPoint (x: point.x + wallDistance , y: point.y)
if nodes(at: eastWall.position) == [] {
addChild(eastWall)}
else {//print("east already there")
}
}
if !instructions[count].contains("W") {
//print("added west wall")
westWall.position = CGPoint (x: point.x - wallDistance , y: point.y)
if nodes(at: westWall.position) == [] {
addChild(westWall)}
else {//print("west wall already there")
}
}
count = count + 1
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
dot.position.x = location.x
dot.position.y = location.y
}
}
func didBegin(_ contact: SKPhysicsContact) {
print("contact!")
}
The walls appear just as I wanted them, and the dot is also in the right position.
I added a masso of 20000 for each of them so that when I move the dot, the walls stay in place. however, when I move the dot with my finger, it just goest straight through the walls of the maze instead of being stopped by them.
I added a print statement to the didBegin function to see if at least it was detecting any contact between the sprites, but it does not.
Why is this?
cheers!

First in your didMoveTo or sceneDidLoad, you need to set the physicsContactDelegate:
override func sceneDidLoad () {
physicsWorld.contactDelegate = self
buildMaze()
addDot()
}
To set the contact/collision mask, you have to do it this way, because they're based on bitwise operation:
Let's suppose you want collision between dot and walls
struct PhysicsCategory {
static let wall: UInt32 = 0x1 << 1
static let dot: UInt32 = 0x1 << 2
}
You can put the struct above you class if you want
Then, when you assign physics body, you have to set the bitmask:
For dots:
dot.physicsBody?.categoryBitMask = PhysicsCategory.dot
dot.physicsBody?.contactTestBitMask = PhysicsCategory.wall
dot.physicsBody?.collisionBitMask = PhysicsCategory.wall
//Collision is different from contact, so if you want to avoid collision
//dot.physicsBody?.collisionBitMask = PhysicsCategory.dot
Collision is different from contact, check apple documentation about it
For walls:
northWall.physicsBody?.categoryBitMask = PhysicsCategory.wall
northWall.physicsBody?.contactTestBitMask = PhysicsCategory.dot
northWall.physicsBody?.collisionBitMask = PhysicsCategory.dot
//Do the same for all walls
If you want walls to contact or collide with more than one object:
northWall.physicsBody?.contactTestBitMask = PhysicsCategory.dot | PhysicsCategory.other
northWall.physicsBody?.collisionBitMask = PhysicsCategory.dot | PhysicsCategory.other
For walls are valid all consideration as per dots

Collision and contact detection don't work very well when you set the position of your sprites directly.
In your code, the lines:
dot.position.x = location.x
dot.position.y = location.y
are directly setting the position of dot, overriding anything that the physics engine wants to do with the objects.
Also, you don't appear to have set up any of the necessary categories or collision/contactTest bit masks.
You allow manual movement of the dot but with contact detection with the walls, then you'd probably need to see if the touch onthe screen was inside a wall and then not move the dot if that was the case. (which would mean that you are not using physics at all).
Edit: my step-by-step guide for collisions and contacts:
https://stackoverflow.com/a/51041474/1430420
And a guide to collision and contactTest bit masks:
https://stackoverflow.com/a/40596890/1430420

Related

Swift / SpriteKit Collision producing varying results

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.

Tower defense: turret tracking enemy and shooting issues

Here is my code:
func bombTowerTurnShoot() {
var prevDistance:CGFloat = 1000000
var closesetZombie = zombieArray[0]
self.enumerateChildNodes(withName: "bomb tower") {
node, stop in
if self.zombieArray.count > 0 {
for zombie in self.zombieArray {
if let bombTower = node as? SKSpriteNode {
let angle = atan2(closesetZombie.position.x - bombTower.position.x , closesetZombie.position.y - bombTower.position.y)
let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
bombTower.run(actionTurn)
let turretBullet = SKSpriteNode(imageNamed: "Level 1 Turret Bullet")
turretBullet.position = bombTower.position
turretBullet.zPosition = 20
turretBullet.size = CGSize(width: 20, height: 20)
//turretBullet.setScale (frame.size.height / 5000)
turretBullet.physicsBody = SKPhysicsBody(circleOfRadius: max(turretBullet.size.width / 2, turretBullet.size.height / 2))
turretBullet.physicsBody?.affectedByGravity = false
turretBullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet //new contact
turretBullet.physicsBody!.collisionBitMask = PhysicsCategories.None
turretBullet.physicsBody!.contactTestBitMask = PhysicsCategories.Zombie
self.addChild(turretBullet)
var dx = CGFloat(closesetZombie.position.x - bombTower.position.x)
var dy = CGFloat(closesetZombie.position.y - bombTower.position.y)
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
let vector = CGVector(dx: 4.0 * dx, dy: 4.0 * dy)
func fire () {
turretBullet.physicsBody?.applyImpulse(vector)
}
func deleteBullet() {
turretBullet.removeFromParent()
}
turretBullet.run(SKAction.sequence([SKAction.wait(forDuration: 0), SKAction.run(fire), SKAction.wait(forDuration: 2.0), SKAction.run(deleteBullet) ]))
let distance = hypot(zombie.position.x - bombTower.position.x, zombie.position.y - bombTower.position.y)
if distance < prevDistance {
prevDistance = distance
closesetZombie = zombie
}
}
}
}
}
}
What this code does is turns a turret towards the closest zombie and shoot at it. As far as I can tell the turret is turn towards the closest zombie (if you can tell whether this code actually accomplishes that or not I would like to know). The bigger problem I am having is that the turrets sometimes shoot more than one bullet. I think it is because it is trying to fire at all zombies in the array not the specified one (the closest to the tower). How can I make it so that the turret only shoots the zombie that is closest?
class GameScene: SKScene, SKPhysicsContactDelegate {//new contact
var zombieArray:[SKSpriteNode] = []
...
...
}
And I append all the zombie to the array once they are added and remove them from the array once they die.
Basically, I don't know what you were doing wrong exactly. You had a ton of stuff going on, and trying to figure out the bug would probably have taken longer than rewriting it (for me at least). So that is what I did.
Here is a link to the project on github:
https://github.com/fluidityt/ShootClosestZombie/tree/master
For me, this was all about separating actions into somewhat distinct methods, and separating actions in general from logic.
You had so much going on, it was hard to test / see which parts were working correctly or not. This is where having somewhat smaller methods come in, as well as separating action from logic.. Your action may work fine, but perhaps it's not getting called due to a logic error.
So, how I implemented this was to just make your bomb turret it's own class.. that way we can have the bomb turret be in charge of most of its actions, and then let gameScene handle most of the implementation / and or logic.
The demo I've uploaded shows two turrets that auto-orient themselves to the closest zombie every frame, then shoot at them every second. Click the screen to add more zombies.
The turrets independently track the closest zombie to them so if you spawn a zombie on the left and the right, then the left turret will shoot at left zombie, and right turret will shoot at right zombie (and only once!).
class BombTower: SKSpriteNode {
static let bombName = "bomb tower"
var closestZombie: SKSpriteNode!
func updateClosestZombie() {
let gameScene = (self.scene! as! GameScene)
let zombieArray = gameScene.zombieArray
var prevDistance:CGFloat = 1000000
var closestZombie = zombieArray[0]
for zombie in zombieArray {
let distance = hypot(zombie.position.x - self.position.x, zombie.position.y - self.position.y)
if distance < prevDistance {
prevDistance = distance
closestZombie = zombie
}
}
self.closestZombie = closestZombie
}
func turnTowardsClosestZombie() {
let angle = atan2(closestZombie.position.x - self.position.x , closestZombie.position.y - self.position.y)
let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
self.run(actionTurn)
}
private func makeTurretBullet() -> SKSpriteNode {
let turretBullet = SKSpriteNode(imageNamed: "Level 1 Turret Bullet")
turretBullet.position = self.position
turretBullet.zPosition = 20
turretBullet.size = CGSize(width: 20, height: 20)
//turretBullet.setScale (frame.size.height / 5000)
turretBullet.physicsBody = SKPhysicsBody(circleOfRadius: max(turretBullet.size.width / 2, turretBullet.size.height / 2))
turretBullet.physicsBody?.affectedByGravity = false
// turretBullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet //new contact
// turretBullet.physicsBody!.collisionBitMask = PhysicsCategories.None
// turretBullet.physicsBody!.contactTestBitMask = PhysicsCategories.Zombie
return turretBullet
}
private func fire(turretBullet: SKSpriteNode) {
var dx = CGFloat(closestZombie.position.x - self.position.x)
var dy = CGFloat(closestZombie.position.y - self.position.y)
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
let vector = CGVector(dx: 4.0 * dx, dy: 4.0 * dy)
turretBullet.physicsBody?.applyImpulse(vector)
}
func addBulletThenShootAtClosestZOmbie() {
let bullet = makeTurretBullet()
scene!.addChild(bullet)
fire(turretBullet: bullet)
}
}
// TODO: delete bullets, hit detection, and add SKConstraint for tracking instead of update.
// Also, I think that we are iterating too much looking for nodes. Should be able to reduce that.
// Also also, there are sure to be bugs if zombieArray is empty.
class GameScene: SKScene {
var zombieArray: [SKSpriteNode] = []
private func makeBombArray() -> [BombTower]? {
guard self.zombieArray.count > 0 else { return nil }
var towerArray: [BombTower] = []
self.enumerateChildNodes(withName: BombTower.bombName) { node, _ in towerArray.append(node as! BombTower) }
guard towerArray.count > 0 else { return nil }
return towerArray
}
private func towersShootEverySecond(towerArray: [BombTower]) {
let action = SKAction.run {
for bombTower in towerArray {
guard bombTower.closestZombie != nil else { continue } // I haven't tested this guard statement yet.
bombTower.addBulletThenShootAtClosestZOmbie()
}
}
self.run(.repeatForever(.sequence([.wait(forDuration: 1), action])))
}
override func didMove(to view: SKView) {
// Demo setup:
removeAllChildren()
makeTestZombie: do {
spawnZombie(at: CGPoint.zero)
}
makeTower1: do {
let tower = BombTower(color: .yellow, size: CGSize(width: 55, height: 55))
let turretGun = SKSpriteNode(color: .gray, size: CGSize(width: 25, height: 15))
turretGun.position.x = tower.frame.maxX + turretGun.size.height/2
tower.name = BombTower.bombName
tower.addChild(turretGun)
addChild(tower)
}
makeTower2: do {
let tower = BombTower(color: .yellow, size: CGSize(width: 55, height: 55))
let turretGun = SKSpriteNode(color: .gray, size: CGSize(width: 25, height: 15))
turretGun.position.x = tower.frame.maxX + turretGun.size.height/2
tower.addChild(turretGun)
tower.position.x += 200
tower.name = BombTower.bombName
addChild(tower)
}
guard let towerArray = makeBombArray() else { fatalError("couldn't make array!") }
towersShootEverySecond(towerArray: towerArray)
}
private func spawnZombie(at location: CGPoint) {
let zombie = SKSpriteNode(color: .blue, size: CGSize(width: 35, height: 50))
zombieArray.append(zombie)
zombie.position = location
zombie.run(.move(by: CGVector(dx: 3000, dy: -3000), duration: 50))
addChild(zombie)
}
// Just change this to touchesBegan for it to work on iOS:
override func mouseDown(with event: NSEvent) {
let location = event.location(in: self)
spawnZombie(at: location)
}
// I think this could be a constrain or action, but I couldn't get either to work right now.
private func keepTowersTrackingNearestZombie() {
guard let towerArray = makeBombArray() else { return }
for tower in towerArray {
tower.updateClosestZombie()
tower.turnTowardsClosestZombie()
}
}
override func update(_ currentTime: TimeInterval) {
keepTowersTrackingNearestZombie()
}
}

Give an SKNode its own physics

Is there a way to give an SKNode its own physics? I have an SKShapeNode call "backGround" which I use for the parent node of most of my other nodes. I am constantly moving "background" to the left, to give the illusion that the player is moving forward. However, one of the objects that has "backGround" as a parent node is a pin with a rope hanging from it. When background accelerates to the left, is there a way to make it so the rope doesn't swing back and forth, as ropes tend to do when accelerating or decelerating?
EDIT: Here is my code:
func createRopeNode(pos: CGPoint) -> SKSpriteNode{
let ropeNode = SKSpriteNode(imageNamed: "Ball")
ropeNode.size = CGSize(width: 5, height: 5)
ropeNode.physicsBody = SKPhysicsBody(rectangleOfSize: ropeNode.size)
ropeNode.physicsBody?.affectedByGravity = true
ropeNode.physicsBody?.collisionBitMask = 0
ropeNode.alpha = 1
ropeNode.position = CGPoint(x: pos.x + 0, y: pos.y)
ropeNode.name = "RopePiece"
let text = SKSpriteNode(imageNamed: "RopeTexture")
ropeNode.zPosition = -5
text.runAction(SKAction.rotateByAngle(atan2(-dx!, dy!), duration: 0))
ropeNode.addChild(text)
return ropeNode
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
if (!playerIsConnected){
playerIsConnected = true
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
dx = pin.position.x - playerPoint!.x
dy = pin.position.y - playerPoint!.y
let length = sqrt(pow(dx!, 2) + pow(dy!, 2))
let distanceBetweenRopeNodes = 5
let numberOfPieces = Int(length)/distanceBetweenRopeNodes
var ropeNodes = [SKSpriteNode]()
//adds the pieces to the array at respective locations
for var index = 0; index < numberOfPieces; ++index{
let point = CGPoint(x: pin.position.x + CGFloat((index) * distanceBetweenRopeNodes) * sin(atan2(dy!, -dx!) + 1.5707), y: pin.position.y + CGFloat((index) * distanceBetweenRopeNodes) * cos(atan2(dy!, -dx!) + 1.5707))
let piece = createRopeNode(point)
ropeNodes.append(piece)
world.addChild(ropeNodes[index])
}
let firstJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[0].physicsBody, bodyB: pin.physicsBody, anchor:
CGPoint(x: (ropeNodes[0].position.x + pin.position.x)/2, y: (ropeNodes[0].position.y + pin.position.y)/2))
firstJoint.frictionTorque = 1
self.physicsWorld.addJoint(firstJoint)
for var i = 1; i < ropeNodes.count; ++i{
let nodeA = ropeNodes[i - 1]
let nodeB = ropeNodes[i]
let middlePoint = CGPoint(x: (nodeA.position.x + nodeB.position.x)/2, y: (nodeA.position.y + nodeB.position.y)/2)
let joint = SKPhysicsJointPin.jointWithBodyA(nodeA.physicsBody, bodyB: nodeB.physicsBody, anchor: middlePoint)
joint.frictionTorque = 0.1
self.physicsWorld.addJoint(joint)
}
finalJoint?.frictionTorque = 1
finalJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[ropeNodes.count - 1].physicsBody, bodyB: player.physicsBody, anchor:
CGPoint(x: (ropeNodes[ropeNodes.count - 1].position.x + playerPoint!.x)/2, y: (ropeNodes[ropeNodes.count - 1].position.y + playerPoint!.y)/2))
self.physicsWorld.addJoint(finalJoint!)
}
}
else{
physicsWorld.removeJoint(finalJoint!)
playerIsConnected = false
}
}
Anchor points are what you are looking for. Move the anchor point of the scene to only move the "camera" of the scene (what is displayed onscreen). This will not jostle the pin and rope. Keep in mind that the anchor point is on a slightly different scale from the scene.
Where the width of the scene could be 1024, the "width" of the of the anchor point for one scene length is 1 (basically counting as one width of the node). Same for the height, where it could be 768, the "height" would still be 1 in the anchor point coordinate space. So to move half a screen width, move the anchor point 0.5
The anchor point is a CGPoint, so you can go vertically as well. Here's a quick example:
var xValue : Float = 0.75
var yValue : Float = 0.0
self.scene?.anchorPoint = CGPointMake(xValue, yValue);
And for further reading, here's a link to the documentation on anchor points for sprites.

Ball collision and SKSpriteNode(s) collision not detected?

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.

How to make joints not rotate using sprite kit or another way of making dynamic 2d water

I want to creat a dynamic 2D water, i', following this unity tutorials
http://gamedevelopment.tutsplus.com/tutorials/creating-dynamic-2d-water-effects-in-unity--gamedev-14143
http://gamedevelopment.tutsplus.com/tutorials/make-a-splash-with-2d-water-effects--gamedev-236
And also this one
http://blog.prime31.com/water2d-part1/
Here is my code
class GameScene: SKScene, SKPhysicsContactDelegate {
var box: SKSpriteNode!
var nodes:[SKNode] = []
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.anchorPoint = CGPointMake(0.5, 0.5)
// Set physics body
let borderBody: SKPhysicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame);
borderBody.friction = 0.0;
self.physicsBody = borderBody;
// Set contact delegate
self.physicsWorld.contactDelegate = self;
box = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 20))
box.position = CGPointMake(0, 0)
box.physicsBody = SKPhysicsBody(rectangleOfSize: box.size)
box.physicsBody!.dynamic = false
self.addChild(box)
let one = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 20, height: 20))
one.position = CGPointMake(box.position.x - box.frame.size.width/2, box.position.y + box.frame.size.height * 2)
one.physicsBody = SKPhysicsBody(rectangleOfSize: one.size)
one.physicsBody!.allowsRotation = false
self.addChild(one)
nodes.append(one)
self.attachPoint(box, point2: one, box: true)
let two = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 20, height: 20))
two.position = CGPointMake(box.position.x, box.position.y + box.frame.size.height * 2)
two.physicsBody = SKPhysicsBody(rectangleOfSize: two.size)
two.physicsBody!.allowsRotation = false
self.addChild(two)
nodes.append(two)
self.attachPoint(box, point2: two, box: true)
let three = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 20, height: 20))
three.position = CGPointMake(box.position.x + box.frame.size.width/2, box.position.y + box.frame.size.height * 2)
three.physicsBody = SKPhysicsBody(rectangleOfSize: three.size)
three.physicsBody!.allowsRotation = false
self.addChild(three)
nodes.append(three)
self.attachPoint(box, point2: three, box: true)
self.attachPoint(one, point2: two, box: false)
self.attachPoint(two, point2: three, box: false)
}
func attachPoint(point1: SKSpriteNode, point2: SKSpriteNode, box: Bool){
if(box == true){
let newPoint1 = CGPointMake(self.frame.size.width/2 + point2.position.x, self.frame.size.height/2 + point1.position.y)
let newPoint2 = CGPointMake(self.frame.size.width/2 + point2.position.x, self.frame.size.height/2 + point2.position.y)
// create a joint between two bodies
let joint: SKPhysicsJointSpring = SKPhysicsJointSpring.jointWithBodyA(point1.physicsBody, bodyB: point2.physicsBody, anchorA: newPoint1, anchorB: newPoint2)
joint.damping = 2.0
joint.frequency = 9.0;
self.physicsWorld.addJoint(joint)
} else {
let newPoint1 = CGPointMake(self.frame.size.width/2 + point1.position.x, self.frame.size.height/2 + point1.position.y)
let newPoint2 = CGPointMake(self.frame.size.width/2 + point2.position.x, self.frame.size.height/2 + point2.position.y)
// create a joint between two bodies
let joint: SKPhysicsJointSpring = SKPhysicsJointSpring.jointWithBodyA(point1.physicsBody, bodyB: point2.physicsBody, anchorA: newPoint1, anchorB: newPoint2)
joint.damping = 2.0
joint.frequency = 9.0;
self.physicsWorld.addJoint(joint)
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
let node = nodes[2]
node.physicsBody?.velocity.dy = 20000
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Here is the result
This result without changes
After touch, execute code in touchesBegan method
After step 2, the nodes start to rotate and fall bellow the node
If they don't rate, following the the tutorials I will have to make SKShapeNode with the color of the node. But SKShapeNode is expensive, is there another way this effect can be accomplished?
The easiest way is to use image for the top part of the water and make it move back and forth, but it won't be dynamic..
If you have ever play Tiny Wings, the water there is implemented exactly the same way in the tutorials.
I don't know, maybe this can't be made in SpriteKit or I just don't know how