How to make SKSpriteNode fall at a downward curve - swift

What I have Now: The node currently only travels in a straight line on the x-axis.
let actionBomb = SKAction.repeatForever(SKAction.moveTo(x: 495, duration: 3.0))
What I’m looking to achieve: The goal is to have the node travel at a downward curve until hitting the ground. I’m not sure how to go about achieving this.
Here is my current code:
func bombAction(){
if(self.action(forKey: "spawnBomb") != nil){return}
let bombTimer = SKAction.wait(forDuration: 5, withRange: 8)
let spawnbombA = SKAction.run {
self.bomb = SKSpriteNode(imageNamed: "bomb1")
self.bomb.zPosition = 1
self.bomb.physicsBody = SKPhysicsBody(circleOfRadius: self.bomb.size.height / 10.0)
self.bomb.physicsBody?.isDynamic = true
self.bomb.physicsBody?.allowsRotation = false
self.bomb.physicsBody?.affectedByGravity = true
var bombPosition = Array<CGPoint>()
bombPosition.append((CGPoint(x:400, y:475)))
bombPosition.append((CGPoint(x:500, y:475)))
bombPosition.append((CGPoint(x:750, y:475)))
let shuffledLocations = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: bombPosition) as! [CGPoint]
self.bomb.position = shuffledLocations[0]
let spawnLocationBomb = bombPosition[Int(arc4random_uniform(UInt32(bombPosition.count)))]
let actionBomb = SKAction.repeatForever(SKAction.moveTo(x: 495, duration: 3.0))
self.bomb.run(actionBomb)
self.bomb.position = spawnLocationBomb
self.addChild(self.bomb)
print(spawnLocationBomb)
}
let sequenceBomb = SKAction.sequence([bombTimer, spawnbombA])
self.run(SKAction.repeatForever(sequenceBomb), withKey: "spawnBomb")
}

Don't forget to set gravity for your scene's physicsWorld
class GameScene: SKScene {
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: -1)
}
}

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.

How to detect a contact in SpriteKit

I'm trying to have a contact to be detected by SpriteKit in Swift 4. I set a flipper to a certain category and a ball to another one. Then I set their collision and contact mask to each other, I add a contactDelegate in didMove to, and print "contact" to inform that there is a contact in a didEnterFonction. However I cannot detect any contact at all. Even after following all the tutorials, and scouring through similar questions. I do not understand what I am doing wrong. My end goal is to have the ball stop from falling when it hits the flipper, but I am trying to detect any contact at all in the first place which do not work.
import SpriteKit
import GameplayKit
struct PhysicsCategory {
static let none : UInt32 = 0
static let all : UInt32 = UInt32.max
static let flip : UInt32 = 0b1
static let ball: UInt32 = 0b10 // 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
private var flip1: SKSpriteNode?
private var flip2: SKSpriteNode?
private var spinnyNode : SKShapeNode?
private var balle:SKSpriteNode?
override func sceneDidLoad() {
self.lastUpdateTime = 0
self.flip1 = self.childNode(withName:"//flip1") as? SKSpriteNode
self.flip2 = self.childNode(withName: "//flip2") as? SKSpriteNode
self.balle = self.childNode(withName: "//ball") as?SKSpriteNode
self.flip1?.physicsBody = SKPhysicsBody()
self.flip1?.physicsBody?.affectedByGravity = false
self.flip1?.physicsBody?.allowsRotation = true
self.balle?.physicsBody = SKPhysicsBody();
//self.balle?.physicsBody?.affectedByGravity = true
self.flip2?.physicsBody = SKPhysicsBody()
self.flip2?.physicsBody?.affectedByGravity = false
self.flip2?.physicsBody?.allowsRotation = true
self.balle?.physicsBody?.allContactedBodies()
self.flip1?.physicsBody?.usesPreciseCollisionDetection
= true
self.flip1?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip1?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip1?.physicsBody?.collisionBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip2?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.collisionBitMask = PhysicsCategory.ball
// Get label node from scene and store it for use later
self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
if let label = self.label {
label.alpha = 0.0
label.run(SKAction.fadeIn(withDuration: 2.0))
}
// Create shape node to use during mouse interaction
let w = (self.size.width + self.size.height) * 0.05
self.spinnyNode = SKShapeNode.init(rectOf: CGSize.init(width: w, height: w), cornerRadius: w * 0.3)
if let spinnyNode = self.spinnyNode {
spinnyNode.lineWidth = 2.5
spinnyNode.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
spinnyNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5),
SKAction.fadeOut(withDuration: 0.5),
SKAction.removeFromParent()]))
}
}
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case 0x31:
if let label = self.label {
label.run(SKAction.init(named: "Pulse")!, withKey: "fadeInOut")
}
case 123:
print("left")
self.flip1?.run(SKAction.rotate(byAngle: -1.5, duration: 0.2))
// self.flip1?.physicsBody?.applyAngularImpulse(600)
self.flip1?.run(SKAction.rotate(byAngle: 1.5, duration: 0.1))
case 124:
self.flip2?.run(SKAction.rotate(byAngle: 1.5, duration: 0.2))
self.flip2?.run(SKAction.rotate(byAngle: -1.5, duration: 0.1))
case 15:
self.balle?.run(SKAction.move(to: CGPoint(x: 50,y: 50), duration: 1))
default:
print("keyDown: \(event.characters!) keyCode: \(event.keyCode)")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(deltaTime: dt)
}
self.lastUpdateTime = currentTime
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
func didEnter(_ contact: SKPhysicsContact) {
print("contact")
}
}
You haven't given the physic bodies a shape or size. Take a look here and read the 'Creating a Body from a ___' sections.

Swift Issue with SCNSceneRenderer ---Trouble getting protocol to work

I'm working to implement a Zig Zag game and following a set of instructions from an on-line course. I have a swift UIViewController, and I am trying now to use also SCNSceneRenderer (to do the game logic of keeping the game character on the zig-zag path). The code looks like this:
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController, SCNSceneRenderer{
let scene = SCNScene()
let cameraNode = SCNNode()
var person = SCNNode()
let firstBox = SCNNode()
var goingLeft = Bool()
var tempBox = SCNNode()
var boxNumber = Int()
var prevBoxNumber = Int()
override func viewDidLoad() {
print("Yes -- view did load")
self.createScene()
}
func renderer(render: SCNSceneRenderer, updateAtTime time: TimeInterval){
let deleteBox = self.scene.rootNode.childNode(withName: "\(prevBoxNumber)", recursively: true )
if (deleteBox?.position.x)! > (person.position.x + 1) || (deleteBox?.position.z)! > (person.position.z + 1) {
prevBoxNumber += 1
deleteBox?.removeFromParentNode()
createBox()
}
}
func createBox(){
tempBox = SCNNode(geometry: firstBox.geometry)
let prevBox = scene.rootNode.childNode(withName: "\(boxNumber)", recursively: true )
boxNumber += 1
tempBox.name = "\(boxNumber)"
let randomNumber = arc4random() % 2
switch randomNumber {
case 0:
tempBox.position = SCNVector3Make((prevBox?.position.x)! - firstBox.scale.x, (prevBox?.position.y)!, (prevBox?.position.z)!)
break
case 1:
tempBox.position = SCNVector3Make((prevBox?.position.x)! , (prevBox?.position.y)!, (prevBox?.position.z)! - firstBox.scale.z)
break
default:
break
}
self.scene.rootNode.addChildNode(tempBox)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if goingLeft == false {
person.removeAllActions()
person.runAction(SCNAction.repeatForever(SCNAction.move(by: SCNVector3Make(-100, 0, 0), duration: 20)))
goingLeft = true
} else {
person.removeAllActions()
person.runAction(SCNAction.repeatForever(SCNAction.move(by: SCNVector3Make(0, 0, -100), duration: 20)))
goingLeft = false
}
print("boxNumber is \(boxNumber)")
}
func createScene(){
boxNumber = 0
prevBoxNumber = 0
self.view.backgroundColor = UIColor.orange
let sceneView = self.view as! SCNView
sceneView.delegate = self
sceneView.scene = scene
//Create Person
let personGeo = SCNSphere(radius: 0.2)
person = SCNNode (geometry : personGeo)
let personMat = SCNMaterial()
personMat.diffuse.contents = UIColor.red
personGeo.materials = [personMat]
person.position = SCNVector3Make(0, 1.1, 0)
scene.rootNode.addChildNode(person)
//Create Camera
cameraNode.camera = SCNCamera()
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.camera?.orthographicScale = 3
cameraNode.position = SCNVector3Make(20,20,20)
cameraNode.eulerAngles = SCNVector3Make(-45,45,0)
//let constraint = SCNLookAtConstraint(target: firstBox)
let constraint = SCNLookAtConstraint(target: person)
constraint.isGimbalLockEnabled = true
self.cameraNode.constraints = [constraint]
scene.rootNode.addChildNode(cameraNode)
person.addChildNode(cameraNode)
//Create Box
let firstBoxGeo = SCNBox(width: 1.0, height: 1.5, length: 1.0, chamferRadius: 0)
firstBox.geometry = firstBoxGeo
let boxMaterial = SCNMaterial()
boxMaterial.diffuse.contents = UIColor(red: 0.2, green: 0.8, blue: 0.9, alpha: 1.0)
firstBoxGeo.materials = [boxMaterial]
firstBox.position = SCNVector3Make(0,0,0)
scene.rootNode.addChildNode(firstBox)
firstBox.name = "\(boxNumber)"
for _ in 0...6{
createBox()
}
//Create light
let light = SCNNode()
light.light = SCNLight()
light.light?.type = SCNLight.LightType.directional
light.eulerAngles = SCNVector3Make(-45, 45, 0)
scene.rootNode.addChildNode(light)
//Create light2
let light2 = SCNNode()
light2.light = SCNLight()
light2.light?.type = SCNLight.LightType.directional
light2.eulerAngles = SCNVector3Make(45, 45, 0)
scene.rootNode.addChildNode(light2)
}
}
When I added SCNSceneRenderer I get the following error:
"Type 'GameViewController' cannot conform to protocol 'SCNSceneRenderer' because it has requirements that cannot be satisfied"
Since my GameViewController isn't recognized as a SCNSceneRenderer, I also get an error at this line:
sceneView.delegate = self
the error is "Cannot assign value of type 'GameViewController' to type 'SCNSceneRendererDelegate'
I'm new to swift programming, but this seems like I am trying to implement an interface like in Java, I've been looking at the Swift documentation but don't see what I need to do to make my class be functional as an SCNSceneRenderer. I would appreciate help to solve this problem. Thanks!

How can I add a physics body to an SKAtlasTexture or create an animation through Images.xcassets?

I wanted to create a small animation of a car driving down the road, so I made an atlas of 9 different pictures. The car simply looks like its wheels are rotating and the car is bouncing a bit as it drives along. I already made an SKSpriteNode with an image and added a physics body on it so that it can jump and be affected by gravity.
So I was wondering how to add either a physics body to an SKAtlasTexture or create an animation through my image.xcassets folder. I tried to just change the SKSpriteNode to SKAtlasTexture, but that obviously didn't work as there are no physics bodies in SKAtlasTexture. So that's where I'm at. Any suggestions or solutions would be greatly appreciated.
Here some parts of my code:
class PlayScene: SKScene, SKPhysicsContactDelegate {
let road = SKSpriteNode(imageNamed: "road")
var origRoadPositionX = CGFloat(0)
var maxRoad = CGFloat(0)
var groundSpeed = 3
var carBaseLine = CGFloat(0)
let car = SKSpriteNode(imageNamed: "car")
enum ColliderType:UInt32{
case car = 1
case tower = 2
}
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor(hex: 0x80E8FF)
self.physicsWorld.contactDelegate = self
//Car
self.car.position = CGPointMake(CGRectGetMinX(self.frame)-20 + self.car.size.width, self.carBaseLine)
self.car.physicsBody = SKPhysicsBody (rectangleOfSize: self.car.size)
self.car.physicsBody?.allowsRotation = false
self.car.physicsBody?.affectedByGravity = false
self.car.physicsBody?.categoryBitMask = ColliderType.car.rawValue
self.car.physicsBody?.contactTestBitMask = ColliderType.tower.rawValue
self.car.physicsBody?.collisionBitMask = ColliderType.tower.rawValue
self.addChild(car)
If more code is needed in order to find a solution, let me know and i can supply more of it.
You can use atlas folder for performing animation with images.
Consider below example:
import SpriteKit
class GameScene: SKScene {
var bombFrames : [SKTexture]!
var bomb : SKSpriteNode!
let NoneCategory : UInt32 = 0x1 << 0
let ProjectileCategory : UInt32 = 0x1 << 2
let bombCategory : UInt32 = 0x1 << 7
override func didMoveToView(view: SKView) {
/* Setup your scene here */
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addBomb), SKAction.waitForDuration(5.0)])))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func addBomb() {
let Name = "Bomb"
let AnimatedAtlas = SKTextureAtlas(named: Name)
var Framese = [SKTexture]()
let numImages = AnimatedAtlas.textureNames.count
for var i=1; i<=numImages; i++ {
let TextureName = "\(i)"
Framese.append(AnimatedAtlas.textureNamed(TextureName))
}
bombFrames = Framese
let firstFrame = bombFrames[0]
bomb = SKSpriteNode(texture: firstFrame)
let actualY = random(min: bomb.size.height/2, max: size.height - bomb.size.height/2)
bomb.position = CGPoint(x: size.width + bomb.size.width/2, y: actualY)
bomb.physicsBody = SKPhysicsBody(texture: bomb.texture, size: bomb.texture!.size())
bomb.physicsBody?.dynamic = true
bomb.physicsBody?.categoryBitMask = bombCategory
bomb.physicsBody?.contactTestBitMask = ProjectileCategory
bomb.physicsBody?.collisionBitMask = NoneCategory
bomb.physicsBody?.usesPreciseCollisionDetection = true
addChild(bomb)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actionMove = SKAction.moveTo(CGPoint(x: -bomb.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
bomb.runAction(SKAction.sequence([actionMove, actionMoveDone]))
playBombAnimation()
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func playBombAnimation() {
bomb.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(bombFrames, timePerFrame: 0.1, resize: false, restore: true)), withKey:"bombAnimation")
}
}
And don't forget to add atlas folder into your project navigator like this:
As you can see in code you can add physics body to your sprite. and if you want you can try this way.
Hope this will help.

Collision Detection In Sprite Kit using Swift

The code below is from a project I am currently working on, I have been trying to teach myself Swift language and Sprite Kit for the past few days and this is my first attempt at a game, it is a Flappy Bird type game. I ran into a problem today when I was trying to write the code for the collision detection. When the bird touches one of the pipes the game is supposed to pause. However when I run the code and the bird touches the pipe, nothing happens, the bird just bounces off of it. I have read many tutorials and watched many videos on this subject to try and resolve my problem and haven't had any luck. I have written all of the collision detection code that I learned off of the last video I watched in the code below. Could anyone please tell me what I am doing wrong. Any advice would be greatly appreciated, thank you.
//
// GameScene.swift
// Bird Flappy Game
//
// Created by Brandon Ballard on 1/4/15.
// Copyright (c) 2015 Brandon Ballard. All rights reserved.
//
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var bird = SKSpriteNode()
var pipeUpTexture = SKTexture()
var pipeDownTexture = SKTexture()
var pipesMoveAndRemove = SKAction()
let pipeGap = 150.0
enum ColliderType:UInt32 {
case BIRD = 1
case PIPE = 2
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backgroundColor = SKColor.cyanColor()
//physics
self.physicsWorld.gravity = CGVectorMake(0.0, -15.0);
self.physicsWorld.contactDelegate = self
func didBeginContact(contact: SKPhysicsContactDelegate) {
scene?.view?.paused = true
bird.setScale(12.0)
}
//Bird
var birdTexture = SKTexture(imageNamed:"Bird")
birdTexture.filteringMode = SKTextureFilteringMode.Nearest
bird = SKSpriteNode(texture: birdTexture)
bird.setScale(0.6)
bird.position = CGPoint(x: 375, y: self.frame.size.height * 0.6)
bird.physicsBody = SKPhysicsBody(circleOfRadius: bird.size.height / 2.0)
bird.physicsBody?.dynamic = true
bird.physicsBody?.allowsRotation = true
bird.physicsBody?.affectedByGravity = true
bird.physicsBody!.categoryBitMask = ColliderType.BIRD.rawValue
bird.physicsBody!.contactTestBitMask = ColliderType.PIPE.rawValue
bird.physicsBody!.collisionBitMask = ColliderType.PIPE.rawValue
self.addChild(bird)
//Ground
var groundTexture = SKTexture(imageNamed: "Ground")
var sprite = SKSpriteNode(texture: groundTexture)
sprite.setScale(2.0)
sprite.position = CGPointMake(self.size.width / 2, sprite.size.height / 2.0)
self.addChild(sprite)
var ground = SKNode()
ground.position = CGPointMake(0, groundTexture.size().height + 0)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, groundTexture.size().height * 2.0))
ground.physicsBody?.dynamic = false
self.addChild(ground)
//Pipes
//Create the Pipes
pipeUpTexture = SKTexture(imageNamed: "PipeUp")
pipeDownTexture = SKTexture(imageNamed: "PipeDown")
//Movement of Pipes
let distanceToMove = CGFloat(self.frame.size.width + 2.0 * pipeUpTexture.size().width)
let movePipes = SKAction.moveByX(-distanceToMove, y: 0.0, duration: NSTimeInterval(0.01 * distanceToMove))
let removePipes = SKAction.removeFromParent()
pipesMoveAndRemove = SKAction.sequence([movePipes,removePipes])
//Spawn Pipes
let spawn = SKAction.runBlock({() in self.spawnPipes()})
let delay = SKAction.waitForDuration(NSTimeInterval(2.0))
let spawnThenDelay = SKAction.sequence([spawn,delay])
let spawnThenDelayForever = SKAction.repeatActionForever(spawnThenDelay)
self.runAction(spawnThenDelayForever)
}
func spawnPipes() {
let pipePair = SKNode()
pipePair.position = CGPointMake(self.frame.size.width + pipeUpTexture.size().width * 2, 0)
pipePair.zPosition = -10
let height = UInt32(self.frame.size.height / 4)
let y = arc4random() % height + height
var pipeDown = SKSpriteNode(texture: pipeDownTexture)
pipeDown.setScale(2.0)////////
pipeDown.position = CGPointMake(3.0, CGFloat(y) + pipeDown.size.height + CGFloat(pipeGap) )
pipeDown.physicsBody = SKPhysicsBody(rectangleOfSize: pipeDown.size)
pipeDown.physicsBody?.dynamic = false
pipeDown.physicsBody!.affectedByGravity = false
pipeDown.physicsBody!.categoryBitMask = ColliderType.PIPE.rawValue
pipeDown.physicsBody!.contactTestBitMask = ColliderType.BIRD.rawValue
pipeDown.physicsBody!.collisionBitMask = ColliderType.BIRD.rawValue
pipePair.addChild(pipeDown)
var pipeUp = SKSpriteNode(texture: pipeUpTexture)
pipeUp.setScale(2.0)
pipeUp.position = CGPointMake(0.0, CGFloat(y))
pipeUp.physicsBody = SKPhysicsBody(rectangleOfSize: pipeUp.size )
pipeUp.physicsBody?.dynamic = false
pipeUp.physicsBody!.affectedByGravity = false
pipeUp.physicsBody!.categoryBitMask = ColliderType.PIPE.rawValue
pipeUp.physicsBody!.contactTestBitMask = ColliderType.BIRD.rawValue
pipeUp.physicsBody!.collisionBitMask = ColliderType.BIRD.rawValue
pipePair.addChild(pipeUp)
pipePair.runAction(pipesMoveAndRemove)
self.addChild(pipePair)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
bird.physicsBody?.velocity = CGVectorMake( 0, 0 )
bird.physicsBody?.applyImpulse(CGVectorMake(0,25))
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
From what I can see, you only need to DETECT collisions, not actually simulate them. For this, you need to set only the contactTestBitMask of the physicsBodies. You can set the collisionBitMask as 0.
bird.physicsBody!.collisionBitMask = 0
pipe.physicsBody!.collisionBitMask = 0
Also, as hamobi has already said, the didBeginContact method needs to be outside the didMoveToView method with the override keyword. (This question has the exact same problem as yours)
class GameScene: SKScene, SKPhysicsContactDelegate {
// ...
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
// set as delegate:
self.physicsWorld.contactDelegate = self
// ..
}
// should be called now
func didBeginContact(contact: SKPhysicsContact){
scene?.view?.paused = true
bird.setScale(12.0)
}
}
You put your didBeginContact INSIDE of didMoveToView. It's not callable from there. Put it in the body of your class