So I want a game made, this was striped from the Xcode SpriteKit sampler, pretty simple. It will evolve greatly as I get this key issue out of the way. It has a player, Wall's, and a door. Nodes are assigned, player works fine. Wall's attempted for children in self, but crashes with my comments removed. I have a guess as multiple nodes of same name? But the door, when assigned node, for some reason no matter what slowly falls, with no gravity ticked and no gravity coded.
Those are lesser concerns. I come to you today to pick at why my collisions might not be activating my collision argument functions, to enter the house.
Yes I am aware it says contact mapped to the event. It suits my theory I am pretty sure.
//
// GameScene.swift
// Sandbox
//
// Created by M on 7/1/16.
// Copyright © 2016 M. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
var playerNode : SKSpriteNode?
var wallNode : SKSpriteNode?
var doorNode : SKSpriteNode?
private var spinnyNode : SKShapeNode?
var furnishing : SKSpriteNode?
var playerCategory = 0x1 << 0
var wallCategory = 0x1 << 1
var doorCategory = 0x1 << 2
var pathCategory = 0x1 << 3
func nextRoom() {
let sceneNode = SKScene(fileNamed: "MyScene")
sceneNode?.scaleMode = .aspectFill
// Present the scene
if let view = self.view {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func loadRoom() {
let furnishing = SKSpriteNode(color: #colorLiteral(red: 0.2464724183, green: 0.05352632701, blue: 0.03394328058, alpha: 1), size:CGSize(width:25, height:25))
doorNode?.addChild(furnishing)
}
func enterHouse() {
let newWindow = CGSize(width: 500, height: 500)
doorNode?.scale(to: newWindow)
loadRoom()
}
func exitHouse(){
let oldWindow = CGSize(width: 100, height: 100)
doorNode?.scale(to: oldWindow)
}
override func sceneDidLoad() {
self.lastUpdateTime = 0
physicsWorld.contactDelegate = self
// Get nodes from scene and store for use later
self.playerNode = self.childNode(withName: "//player") as? SKSpriteNode
playerNode?.physicsBody = SKPhysicsBody(rectangleOf: (playerNode?.frame.size)!)
playerNode?.physicsBody?.isDynamic = true
playerNode?.physicsBody?.affectedByGravity = false
playerNode?.physicsBody?.categoryBitMask = UInt32(playerCategory)
playerNode?.physicsBody?.collisionBitMask = UInt32(wallCategory)
playerNode?.physicsBody?.contactTestBitMask = UInt32(doorCategory)
for child in self.children {
/*if child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
wallNode?.physicsBody?.isDynamic = false
wallNode?.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode?.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(child)
}
}*/
}
self.doorNode = self.childNode(withName: "door") as? SKSpriteNode
doorNode?.physicsBody?.affectedByGravity = false
doorNode?.physicsBody?.isDynamic = false
doorNode?.physicsBody = SKPhysicsBody(rectangleOf: (doorNode?.frame.size)!)
doorNode?.physicsBody?.categoryBitMask = UInt32(doorCategory)
doorNode?.physicsBody?.contactTestBitMask = UInt32(playerCategory)
}
func touchDown(atPoint pos : CGPoint) {
let fromX = playerNode?.position.x
let fromY = playerNode?.position.y
let toX = pos.x
let toY = pos.y
let resultX = toX - (fromX)!
let resultY = toY - (fromY)!
let newX = (playerNode?.position.x)! + resultX / 10
let newY = (playerNode?.position.y)! + resultY / 10
playerNode?.position.x = newX
playerNode?.position.y = newY
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
func didBeginContact(contact: SKPhysicsContact) {
//this gets called automatically when two objects begin contact with each other
// 1. Create local variables for two physics bodies
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
// 2. Assign the two physics bodies so that the one with the lower category is always stored in firstBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if secondBody.categoryBitMask == UInt32(doorCategory){
enterHouse()
}
}
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(withDeltaTime: dt)
}
self.lastUpdateTime = currentTime
}
}
I presume this line is causing the crash
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
as you are force unwrapping (!) the wallNode size however looking at your code you never assign it to anything like so
wallNode = self.childNode(withName: "wallNode") as? SKSpriteNode
or in the for loop.
Try this code in your for in loop that should avoid crashes and assigns your wall node.
for child in self.children where child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode = child // Try this
if let wallNode = wallNode { // safely unwrap wall node to avoid crashes
wallNode.physicsBody = SKPhysicsBody(rectangleOf: (wallNode.frame.size))
wallNode.physicsBody?.isDynamic = false
wallNode.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(wallNode) // add wall node here instead if you are using your wallNode property
}
}
}
Hope this helps
Related
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.
I'm creating a game that involves a timer that spawns objects every second that fall down the screen. To gain points you must catch the objects.
I want the rate of spawning to increase once the player reaches a certain amount of points.
I have tried to do this by assigning the interval of the timer an integer (SpeedNumber) value of 1 second to start with and have created and if statement that is supposed to change that integer to 0.5 once the player reaches a certain amount of points. This makes sense to me but it is not working.
Why is this not working and what should I change?
import SpriteKit
struct physicsCatagory {
static let person : UInt32 = 0x1 << 1
static let Ice : UInt32 = 0x1 << 2
static let IceTwo : UInt32 = 0x1 << 3
static let IceThree : UInt32 = 0x1 << 4
static let Score : UInt32 = 0x1 << 5
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var scorenumber = Int()
var lifenumber = Int()
var SpeedNumber : Double = 0.5
var person = SKSpriteNode(imageNamed: "Person")
let Score = SKSpriteNode()
var ScoreLable = SKLabelNode()
override func didMoveToView(view: SKView) {
self.scene?.backgroundColor = UIColor.purpleColor()
physicsWorld.contactDelegate = self
self.scene?.size = CGSize(width: 640, height: 1136)
lifenumber = 0
SpeedNumber = 1
Score.size = CGSize(width: 648, height: 1)
Score.position = CGPoint(x: 320, y: -90)
Score.physicsBody = SKPhysicsBody(rectangleOfSize: Score.size)
Score.physicsBody?.affectedByGravity = false
Score.physicsBody?.dynamic = false
Score.physicsBody?.categoryBitMask = physicsCatagory.Score
Score.physicsBody?.collisionBitMask = 0
Score.physicsBody?.contactTestBitMask = physicsCatagory.IceThree
Score.color = SKColor.blueColor()
self.addChild(Score)
person.position = CGPointMake(self.size.width/2, self.size.height/12)
person.setScale(0.4)
person.physicsBody = SKPhysicsBody(rectangleOfSize: person.size)
person.physicsBody?.affectedByGravity = false
person.physicsBody?.categoryBitMask = physicsCatagory.person
person.physicsBody?.contactTestBitMask = physicsCatagory.Ice
person.physicsBody?.collisionBitMask = physicsCatagory.Ice
person.physicsBody?.dynamic = false
ScoreLable.position = CGPoint(x: self.frame.width / 2, y: 1000)
ScoreLable.text = "\(scorenumber)"
ScoreLable.fontColor = UIColor.yellowColor()
ScoreLable.fontSize = 100
ScoreLable.fontName = "Zapfino "
self.addChild(ScoreLable)
var IceThreeTimer = NSTimer.scheduledTimerWithTimeInterval(SpeedNumber, target: self, selector: ("spawnThirdIce"), userInfo: nil, repeats: true)
self.addChild(person)
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == physicsCatagory.person && secondBody.categoryBitMask == physicsCatagory.IceThree || firstBody.categoryBitMask == physicsCatagory.IceThree && secondBody.categoryBitMask == physicsCatagory.person{
scorenumber++
if scorenumber == 5 {
SpeedNumber = 0.5
}
ScoreLable.text = "\(scorenumber)"
CollisionWithPerson(firstBody.node as! SKSpriteNode, Person: secondBody.node as! SKSpriteNode)
}
if firstBody.categoryBitMask == physicsCatagory.Score && secondBody.categoryBitMask == physicsCatagory.IceThree ||
firstBody.categoryBitMask == physicsCatagory.IceThree && secondBody.categoryBitMask == physicsCatagory.Score{
lifenumber++
if lifenumber == 3{
self.view?.presentScene(EndScene())
}
//self.view?.presentScene(EndScene())
}
}
func CollisionWithPerson (Ice: SKSpriteNode, Person: SKSpriteNode){
Person.removeFromParent()
}
func spawnThirdIce(){
var Ice = SKSpriteNode(imageNamed: "Ice")
Ice.setScale(0.9)
Ice.physicsBody = SKPhysicsBody(rectangleOfSize: Ice.size)
Ice.physicsBody?.categoryBitMask = physicsCatagory.IceThree
Ice.physicsBody?.contactTestBitMask = physicsCatagory.person | physicsCatagory.Score
Ice.physicsBody?.affectedByGravity = false
Ice.physicsBody?.dynamic = true
let MinValue = self.size.width / 8
let MaxValue = self.size.width - 20
let SpawnPoint = UInt32(MaxValue - MinValue)
Ice.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
self.addChild(Ice)
let action = SKAction.moveToY(-85, duration: 2.5)
let actionDone = SKAction.removeFromParent()
Ice.runAction(SKAction.sequence([action,actionDone]))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You could do what you want as follows:
First declare 2 properties (in your class but outside all the function definitions)
var timeOfLastSpawn: CFTimeInterval = 0.0
var timePerSpawn: CFTimeInterval = 1.0
Then, in Update, check to see if the timePerSpawn has been exceeded. If so, call your spawn process which spawns new objects and then reset the time since the last spawn:
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if (currentTime - timeOfLastSpawn > timePerSpawn) {
spawnObject()
self.timeOfLastSpawn = currentTime
}
}
func spawnObject() {
// Your spawn code here
}
The advantage of making the spawn process a separate function is that you can call it from didMoveToView or any other place to spawn objects outside of the normal time-controlled cycle.
You can change the value of timePerSpawn as necessary to control the rate at which objects are spawned.
You could also look into creating an SKAction that runs spawnObject at specified time intervals, but I think to change the rate at which objects are spawned, you'll have to delete and re-create the SKAction, but your could do this in a setter for timePerSpawn.
You shouldn't really use NSTimer is SpriteKit, as the SpriteKit engine will be unaware of what the timer is doing and can't control it (one example is that the timer keeps running if the set the scene to paused).
You should move your declaration of var IceThreeTimer to the class level (outside of the method, right where you declare SpeedNumber. This will make sure that your handle to the pointer will be available in both the didMoveToView and didBeginContact methods.
In didMoveToView you change your declaration to:
// I removed the "var"
IceThreeTimer = NSTimer.scheduledTimerWithTimeInterval(SpeedNumber, target: self, selector: ("spawnThirdIce"), userInfo: nil, repeats: true)
Then in didBeginContact you modify:
if scorenumber == 5 {
SpeedNumber = 0.5
// Stop the already running timer
IceThreeTimer.invalidate()
// Schedule a new timer
IceThreeTimer = NSTimer.scheduledTimerWithTimeInterval(SpeedNumber, target: self, selector: ("spawnThirdIce"), userInfo: nil, repeats: true)
}
I have a collision based game with missiles and bombs. Everything works perfect right now with 1 collision and it removes the node from screen.
Although I want to be able to make bomb1 of my "bombs" harder to destroy so I would like to require it being hit twice. How can I do this?
Here are the bombs
func enemies() {
let bomb1 = SKSpriteNode(imageNamed: "Bomb1")
let bomb2 = SKSpriteNode(imageNamed: "bomb2")
let bomb3 = SKSpriteNode(imageNamed: "Bomb3")
let enemy = [bomb1, bomb2, bomb3]
// Enemy Physics
for bomb in enemy {
bomb1.size = CGSize(width: 55, height: 55)
bomb2.size = CGSize(width: 55, height: 70)
bomb3.size = CGSize(width: 30, height: 30)
bomb.physicsBody = SKPhysicsBody(circleOfRadius: bomb.size.width / 4)
bomb.physicsBody?.categoryBitMask = PhysicsCategory.enemy
bomb.physicsBody?.collisionBitMask = PhysicsCategory.missile | PhysicsCategory.airDefense
bomb.physicsBody?.contactTestBitMask = PhysicsCategory.missile | PhysicsCategory.airDefense
bomb.physicsBody?.affectedByGravity = false
bomb.physicsBody?.dynamic = true
bomb1.name = "enemy1"
bomb2.name = "enemy2"
bomb3.name = "enemy3"
}
Here is the function thats called when the bomb hits the missile.
func collisionWithMissile(enemy : SKSpriteNode, missile : SKSpriteNode) {
enemy.physicsBody?.dynamic = true
enemy.physicsBody?.affectedByGravity = true
missile.physicsBody?.affectedByGravity = true
enemy.physicsBody?.mass = 10.0
missile.physicsBody?.mass = 5.0
enemy.removeAllActions()
missile.removeAllActions()
enemy.removeFromParent()
missile.removeFromParent()
enemy.physicsBody?.contactTestBitMask = 0
enemy.physicsBody?.collisionBitMask = 0
enemy.name = nil
score++
scoreLbl.text = "\(score)"
}
This may help with the error Im getting
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.node != nil && contact.bodyB.node != nil {
let firstBody = contact.bodyA.node as! Bomb
let secondBody = contact.bodyB.node as! SKSpriteNode
// Bomb1 **********
if ((firstBody.name == "enemy1") && (secondBody.name == "missile")) {
collisionWithMissile(firstBody, missile: secondBody)
atomicExplosion(contact.bodyA.node!.position)
missileExplosion(contact.bodyB.node!.position)
}
You can subclass SKSpriteNode for your bombs and add a collision counter to this class. Inside your collisionWithMissile func you check the value of this counter and remove the bomb if a given value is reached otherwise you increase the counter.
Your subclass can look like this
class Bomb: SKSpriteNode {
var hitCount: Int
init(imageNamed: String, hitCount: Int) {
self.hitCount = hitCount
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The init method has hitCount as a parameter, so you can create easily Bombs with different strength. Your collisionWithMissile method could be like this. Just fill the if-else parts with your needed code.
func collisionWithMissile(enemy : Bomb, missile : SKSpriteNode) {
if enemy.hitCount <= 0 {
enemy.removeFromParent()
} else {
enemy.hitCount -= 1
}
}
So I created this game where you have to shoot at objects. Now, I have an imageset that replicates an object exploding. I would like to call those images to appear in a sequence so it looks like an explosion after the projectile hits the object. Obviously the images have to be called at the exact location of where the projectile hits the object. Does anyone have any idea on how to make this happen? Here is some code.
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
projectile.removeFromParent()
monster.removeFromParent()
playerScore = playerScore + 1
playerScoreUpdate()
if (playerScore > 100) {
let reveal = SKTransition.crossFadeWithDuration(0.3)
let gameOverScene = GameOverScene(size: self.size, won: true)
self.view?.presentScene(gameOverScene, transition: reveal)
}
}
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & UInt32(laserCategory)) != 0 && (secondBody.categoryBitMask & UInt32(monsterCategory)) != 0 {
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)
}
if playerScore > highScore() {
saveHighScore(playerScore)
println("New Highscore = " + highScore().description)
highScoreLabel.text = "Best score: \(highScore().description)"
} else {
println("HighScore = " + highScore().description ) // "HighScore = 100"
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "muteSound" {
if musicIsPlaying == true {
backgroundMusicPlayer.stop()
musicIsPlaying = false
} else if musicIsPlaying == false {
backgroundMusicPlayer.play()
musicIsPlaying = true
}
} else {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
let projectile = SKSpriteNode(imageNamed: "boom")
projectile.setScale(0.6)
projectile.position = player.position
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.dynamic = true
projectile.physicsBody?.categoryBitMask = UInt32(laserCategory)
projectile.physicsBody?.contactTestBitMask = UInt32(monsterCategory)
projectile.physicsBody?.collisionBitMask = 0
projectile.physicsBody?.usesPreciseCollisionDetection = true
// 3 - Determine offset of location to projectile
let offset = touchLocation - projectile.position
// 4 - Bail out if you are shooting down or backwards
if (offset.y < 0) { return }
// 5 - OK to add now - you've double checked position
addChild(projectile)
// 6 - Get the direction of where to shoot
let direction = offset.normalized()
// 7 - Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
// 8 - Add the shoot amount to the current position
let realDest = shootAmount + projectile.position
// 9 - Create the actions
let actionMove = SKAction.moveTo(realDest, duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
if !isStarted {
start()
}else{
projectile.runAction(SKAction.sequence([actionMove, actionMoveDone]))
}
}
}
}
func addMonster() {
let monster = SKSpriteNode(imageNamed: "box")
monster.setScale(0.6)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.dynamic = true
monster.physicsBody?.categoryBitMask = UInt32(monsterCategory)
monster.physicsBody?.contactTestBitMask = UInt32(laserCategory)
monster.physicsBody?.collisionBitMask = 0
monster.name = "box"
var random : CGFloat = CGFloat(arc4random_uniform(320))
monster.position = CGPointMake(random, self.frame.size.height + 10)
self.addChild(monster)
}
For you explosion you could create an SKSpriteNode that play the frames you mentioned:
1. You're going to need the images as an array of SKTextures. You said you've got you images in an image set so the easiest thing to do may be to create an array using a for loop, for example:
// I don't know how many images you've got, so I'll use 10.
var textures: [SKTexture] = []
for i in 0..<10 {
let imageName = "explosion\(i)"
textures.append(SKTexture(imageNamed: imageName))
}
Alternatively, which is what I would recommend, is to create a Texture Atlas of your images. (For more information on texture atlases see here) To create an atlas, make a folder with the extension .atlas and adding all your explosion images to it. (Then add this to your project). Here's an extension I wrote to get your sprites out of a texture atlas, ready for animation:
extension SKTextureAtlas {
func textureArray() -> [SKTexture] {
var textureNames = self.textureNames as! [String]
// They need to be sorted because there's not guarantee the
// textures will be in the correct order.
textureNames.sort { $0 < $1 }
return textureNames.map { SKTexture(imageNamed: $0) }
}
}
And here's how to use it:
let atlas = SKTextureAtlas(named: "MyAtlas")
let textures = atlas.textureArray()
2. Now you've got your textures you need to create an SKSpriteNode and animate it:
let explosion = SKSpriteNode(texture: textures[0])
let timePerFrame = // this is specific to your animation.
let animationAction = SKAction.animateWithTextures(textures, timePerFrame: timePerFrame)
explosion.runAction(animationAction)
3. Add the sprite to your scene and position it correctly. To add it in the correct place you could use the contactPoint variable on SKPhysicsContact, after checking it was the projectile hitting an object.
func didBeginContact(contact: SKPhysicsContact) {
// Other stuff...
explosion.position = contact.contactPoint
self.addChild(explosion)
}
Hope that helps!
I have been struggling for the past two days to get two SKSpriteNodes to register a collision and evoke didBegin#contact.
I've set their bit masks 'categoryBitMask', 'contactTestBitMask' and 'collisionTestBitMask' for both objects.
I've also set the 'dynamic' property for both to 'true'
initPhysics() seems to set up the physicsWorld okay.
All I'm expecting is that didBegin#Contact is called, but it is not
//Set up Physicsbody bit masks
let playerCarBitMask: UInt32 = 0x1 << 1
let slowCarBitMask: UInt32 = 0x1 << 2
//initPhysics
func initPhysics() {
println("(((((((((((((( Initiating Physicsbody ))))))))))))))")
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector.zeroVector
println("self.physicsWorld.contactDelegate = \(self.physicsWorld.contactDelegate)")
}
//setupPlayer
func setupPlayer() {
car = SKSpriteNode(imageNamed: "redCarUp")
car.setScale(2.0)
car.position = CGPoint(x: 800, y: 400)
car.zPosition = 100
car.name = "car"
gameNode.addChild(car)
let carBody = SKPhysicsBody(
rectangleOfSize: car.frame.size, center: car.position)
carBody.dynamic = true
carBody.categoryBitMask = playerCarBitMask
carBody.contactTestBitMask = slowCarBitMask
carBody.mass = 5
carBody.collisionBitMask = slowCarBitMask
car.physicsBody = carBody
println("carBody = \(carBody)")
println("carBody.dynamic = \(carBody.dynamic)")
println("carBody.mass = \(carBody.mass)")
println("carBody.categoryBitMask = \(carBody.categoryBitMask)")
println("carBody.contactTestBitMask = \(carBody.contactTestBitMask)")
println("carBody.collisionBitMask = \(carBody.contactTestBitMask)")
slowCar = SKSpriteNode(imageNamed: "blueCarUp")
slowCar.setScale(2.0)
let slowCarScenePos = CGPoint(
x: 680,
y: 2048)
slowCar.position = gameNode.convertPoint(slowCarScenePos, fromNode: self)
println("slowCar.position = \(slowCar.position) ****")
slowCar.zPosition = 80
slowCar.name = "slowCar"
let slowCarBody = SKPhysicsBody(
rectangleOfSize: slowCar.frame.size, center: slowCar.position)
println("slowCar = \(slowCar) ****")
slowCarBody.dynamic = true
slowCarBody.categoryBitMask = slowCarBitMask
slowCarBody.contactTestBitMask = playerCarBitMask
slowCarBody.mass = 5
slowCarBody.collisionBitMask = playerCarBitMask
slowCar.physicsBody = slowCarBody
gameNode.addChild(slowCar)
}
func didBeginContact(contact: SKPhysicsContact!) {
println("*******************PhysicsContact********************")
}
'didBeginContact' has been changed to 'didBegin' in swift 3
func didBegin(_ contact: SKPhysicsContact) {
//stuff
}
I had a code from swift 2 and 'didBeginContact' was sitting there but wasn't being called. After quite a white I figured out that the function was changed. So, I thought my answer could help someone.
If you want make a contact between car and slowCar you have to init the categoryBitMask of both physicsBodies (I think you did). See the code below to get contact between two physicsBodies. When there is a contact it returns your display function :
//init your categoryBitMask :
let carCategory:UInt32 = 0x1 << 0
let SlowCarCategory:UInt32 = 0x1 << 1
//init car
car.physicsBody?.categoryBitMask = carCategory
car.physicsBody?.contactTestBitMask = slowCarCategory
//init slowCar
slowCar.physicsBody?.categoryBitMask = slowCarCategory
slowCar.physicsBody?.contactTestBitMask = CarCategory
// set your contact function
func didBeginContact(contact: SKPhysicsContact!)
{
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else
{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & carCategory) != 0 && (secondBody.categoryBitMask & slowCarCategory) != 0)
{
displayfunction(firstBody.node as SKSpriteNode, car: secondBody.node as SKSpriteNode)
}
}
func displayFunction (slowCar : SKSpriteNode, car : SKSpriteNode)
It turned out to be a simple problem. In my original code I was setting parameters for the SKPhysicsBody detection frame like so:
let carBody = SKPhysicsBody(
rectangleOfSize: car.frame.size, center: car.position)
Similarly I was doing the same for the second node that I was testing physics collisions for.
Simply removing the 'centre:' parameters like so:
let carBody = SKPhysicsBody(rectangleOfSize: car.frame.size)
for the two sprite nodes solved the problem and the nodes now crash into each other and push themselves aside as expected.
Please Note that contact will not be detected between two static bodies
(node.physicsBody?.isDynamic = false)