How to pause an SKSpriteNode, Swift - swift

I created this game using sprite kit. During the game sprite nodes are moving. When the "Game Over" Label pops up, I would like the monster sprite node to stop moving or pause, but the rest of the scene to still move on. I know where to put the code, I just don't know how to write it. This is my monster code.
func addMonster() {
// Create sprite
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 + 20)
self.addChild(monster)
}
EDIT
override func update(currentTime: CFTimeInterval) {
if isStarted == true {
if currentTime - self.lastMonsterAdded > 1 {
self.lastMonsterAdded = currentTime + 3.0
self.addMonster()
}
self.moveObstacle()
} else {
}
self.moveBackground()
if isGameOver == true {
}
}

If I understand your question correctly, you want to pause a single (or small number of) nodes. In that case...
monster.paused = true
Is probably what you want.

Related

Inconsistent contact detection in Swift 3 using SpriteKit

I'm having an issue with contact detection in Swift 3 using SpriteKit. The contact detection is working...sometimes. It seems purely random as to when it fires and when it doesn't.
I have a yellow "bullet" that moves up on the screen to hit a red sprite named targetSprite. The desired behavior is to have the bullet removed when it hits the target, but sometimes it just passes through underneath.
I've found many questions about contact detection not working at all, but I haven't found any dealing with inconsistent detection.
What can I do to fix this?
Here's the code:
import SpriteKit
import GameplayKit
enum PhysicsCategory:UInt32 {
case bullet = 1
case sprite1 = 2
case targetSprite = 4
// each new value should double the previous
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create sprites
let sprite1 = SKSpriteNode(color: SKColor.blue, size: CGSize(width:100,height:100))
let targetSprite = SKSpriteNode(color: SKColor.red, size: CGSize(width:100,height:100))
let bullet = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 20, height: 20))
// show the bullet?
var isShowingBullet = true
// Timers
//var timer:Timer? = nil
var fireBulletTimer:Timer? = nil
// set up bullet removal:
var bulletShouldBeRemoved = false
let bulletMask = PhysicsCategory.bullet.rawValue
override func didMove(to view: SKView) {
// Physics
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
targetSprite.physicsBody?.affectedByGravity = false
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.centerRect.size)
bullet.physicsBody?.affectedByGravity = false
// Contact Detection:
targetSprite.physicsBody?.categoryBitMask = PhysicsCategory.targetSprite.rawValue
targetSprite.physicsBody?.contactTestBitMask =
//PhysicsCategory.sprite1.rawValue |
PhysicsCategory.bullet.rawValue
targetSprite.physicsBody?.collisionBitMask = 0 // no collision detection
// bullet physics
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet.rawValue
bullet.physicsBody?.contactTestBitMask =
PhysicsCategory.targetSprite.rawValue
bullet.physicsBody?.collisionBitMask = 0 // no collision detection
// execute once:
fireBulletTimer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.fireBullet),
userInfo: nil,
repeats: false)
// Add sprites to the scene:
self.addChild(sprite1)
self.addChild(bullet)
self.addChild(targetSprite)
// Positioning
targetSprite.position = CGPoint(x:0, y:300)
// Note: bullet and sprite1 are at 0,0 by default
// Delegate
self.physicsWorld.contactDelegate = self
}
func didBegin(_ contact: SKPhysicsContact) {
print("didBegin(contact:))")
//let firstBody:SKPhysicsBody
// let otherBody:SKPhysicsBody
// Use 'bitwise and' to see if both bits are 1:
if contact.bodyA.categoryBitMask & bulletMask > 0 {
//firstBody = contact.bodyA
//otherBody = contact.bodyB
print("if contact.bodyA....")
bulletShouldBeRemoved = true
}
else {
//firstBody = contact.bodyB
//otherBody = contact.bodyA
print("else - if not contacted?")
}
/*
// Find the type of contact:
switch otherBody.categoryBitMask {
case PhysicsCategory.targetSprite.rawValue: print(" targetSprite hit")
case PhysicsCategory.sprite1.rawValue: print(" sprite1 hit")
case PhysicsCategory.bullet.rawValue: print(" bullet hit")
default: print(" Contact with no game logic")
}
*/
} // end didBegin()
func didEnd(_ contact: SKPhysicsContact) {
print("didEnd()")
}
func fireBullet() {
let fireBulletAction = SKAction.move(to: CGPoint(x:0,y:500), duration: 1)
bullet.run(fireBulletAction)
}
func showBullet() {
// Toggle to display or not, every 1 second:
if isShowingBullet == true {
// remove (hide) it:
bullet.removeFromParent()
// set up the toggle for the next call:
isShowingBullet = false
// debug:
print("if")
}
else {
// show it again:
self.addChild(bullet)
// set up the toggle for the next call:
isShowingBullet = true
// debug:
print("else")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bulletShouldBeRemoved {
bullet.removeFromParent()
}
}
}
Sorry for the inconsistent indentation, I can't seem to find an easy way to do this...
EDIT:
I have found that using 'frame' instead of 'centerRect' makes the collision area the size of the sprite. For example:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
should be:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.frame.size)
First advice - Do not use NSTimer (aka Timer) in SpriteKit. It is not paired with a game loop and can cause different issues in a different situations. Read more here ( answer posted by LearnCocos2D)
So, do this:
let wait = SKAction.wait(forDuration: 1)
run(wait, completion: {
[unowned self] in
self.fireBullet()
})
What I have noticed is that if I run your code in Simulator, I get the behaviour you have described. didBegin(contact:) is being fired randomly. Still, this is not happening on a device for me, and device testing is what matters.
Now, when I have removed Timer and did the same thing with SKAction(s) everything worked, means contact were detected every time.
Have you tried adding
.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true
SpriteKit physics engine will calculate collision correctly if you do following:
1) set "usesPreciseCollisionDetection" property to true for bullet's physics body. This will change collision detection algorithm for this body. You can found more information about this property here, chapter "Working with Collisions and Contacts".
2) move your bullet using applyImpulse or applyForce methods. Collision detection will not woking correctly if you move body by changing it's position manually. You can find more information here, chapter "Making Physics Bodies Move".

scoring system counting multiple times

My Goal: To achieve a scoring system that only counts when a level unit is behind the player once and therefore if the player goes back it doesn’t count the same one twice
I currently have a scoring system that adds one to the score label every time I tap my player to go forward in my game, this has its flaws as the player can go backwards and then tap to go forward which would count onto the score again if the player went backwards then forwards.
I came up with the idea to count how many platforms(LevelUnits) are behind the player so that if they go back it won’t count the same platform(LevelUnit) twice when they decide to go forward again.
This is what I came up with:
override func update(_ currentTime: TimeInterval) {
let scoreLabel = childNode(withName: "scoreLabel") as! Score // links the scorelabel with Score class
worldNode.enumerateChildNodes(withName: "levelUnit") {
node, stop in
let nodeLocation:CGPoint = self.convert(node.position, from: self.worldNode) //converts cordinates of level units with the world node.
let player1Location:CGPoint = self.convert(self.thePlayer.position, from: self.worldNode)
if (nodeLocation.y < (player1Location.y) - self.levelUnitHeight ) { // checks to see if the node is behind the player's position
self.countLevelUnits += 1
}
if (self.countLevelUnits > scoreLabel.number) {
while self.countLevelUnits > scoreLabel.number {
scoreLabel.addOneToScore()
}
} else if (self.countLevelUnits < scoreLabel.number) {
// Do Nothing
}
}
}
The only problem is that because they’re constantly behind the player the code counts it multiple times and continues to do so unless I turn off the game. Is there a way to just count the LevelUnits behind the player once for each one that goes behind the players position?
EDIT 1: LEVEL UNIT CLASS
class LevelUnit:SKNode {
//General Level Unit and Object Variables
var imageName:String = ""
var levelUnitSprite:SKSpriteNode = SKSpriteNode()
var levelUnitWidth:CGFloat = 0
var levelUnitHeight:CGFloat = 0
var theType:LevelType = LevelType.normal
let thePlayer:Player = Player(imageNamed: "Frog")
var levelUnitPicker = 0
var levelUnitsSpawned = 0
var levelUnitScored:Bool = false
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init () {
super.init()
}
func setUpLevel(){
imageName = “Wall”
let theSize:CGSize = CGSize(width: levelUnitWidth, height: levelUnitHeight) //defines the size background sprite as defined by the height and width of level unit
let tex:SKTexture = SKTexture(imageNamed: imageName) //defines the testue that the backgroundsprite will have.
levelUnitSprite = SKSpriteNode(texture: tex, color: SKColor.black, size: theSize)
scoredLevelUnit = false
self.addChild(levelUnitSprite) //adds the level unit to the scene
self.name = "levelUnit" //names the whole backgroundsprite under the name level Unit
self.position = CGPoint(x: levelUnitSprite.size.width / 2, y: 0)
//The Level Types Physics Body
if (theType == LevelType.blank) {
levelUnitSprite.physicsBody = SKPhysicsBody(rectangleOf: levelUnitSprite.size)
levelUnitSprite.physicsBody!.categoryBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.contactTestBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.isDynamic = false
} else if (theType == LevelType.normal) {
levelUnitSprite.physicsBody = SKPhysicsBody(rectangleOf: levelUnitSprite.size)
levelUnitSprite.physicsBody!.categoryBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.contactTestBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.isDynamic = false
} else if (theType == LevelType.floating) {
levelUnitSprite.physicsBody = SKPhysicsBody(rectangleOf: levelUnitSprite.size)
levelUnitSprite.physicsBody!.categoryBitMask = BodyType.floating.rawValue
levelUnitSprite.physicsBody!.contactTestBitMask = BodyType.floating.rawValue
levelUnitSprite.physicsBody!.isDynamic = false
}
}
If you use a custom subclass for your level unit, you can easily add a Bool property scored; when you "score" this object, set scored = true, then next time you can check if scored == true and not score it again
override func update(_ currentTime: TimeInterval) {
let scoreLabel = childNode(withName: "scoreLabel") as! Score // links the scorelabel with Score class
worldNode.enumerateChildNodes(withName: "levelUnit") {
node, stop in
if let levelNode = node as? LevelUnit
let nodeLocation:CGPoint = self.convert(levelNode.position, from: self.worldNode) //converts cordinates of level units with the world node.
let player1Location:CGPoint = self.convert(self.thePlayer.position, from: self.worldNode)
if (nodeLocation.y < (player1Location.y) - self.levelUnitHeight ) { // checks to see if the node is behind the player's position
if !levelNode.levelUnitScored {
scoreLabel.addOneToScore()
levelNode.levelUnitScored = true
}
}
}
}
}

Detect if a SKSpriteNode left the screen at the bottom and call a function

I have tried to find a way to detect if a SKSpriteNode left the screen (I would like to call a Game Over function).
I have declared the node within a function (It is not global if I get that right?) and made and SKAction that moves the Node out of the screen and removes it afterwards.
This is what I came up with:
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
Now what I need is to call GameOver() if the node left the screen.
I am very thankful for every answer!
In your scene you have to remember the reference for node you want to check and then in update method you just do the following:
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
Edit:
class MyScene: SKScene {
// ....
//here is the list of nodes which you want to check
var nodesToCheck = [SKSpriteNode]()
//here is your spawn function
func spawnNewNode() {
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
nodesToCheck.append(node)
}
//and here is the update method
override func update(currentTime: NSTimeInterval) {
super.update(currentTime)
// ... every other update logic
for node in nodesToCheck {
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
}
}
func gameOver() {
println("Damn!")
}
}
Dont forget to remove your node from nodeToCheck array when they are no longer scene members.

How do I add scoring to my swift spritekit game?

I have these four circles that are moving from the bottom of the screen to the top. I have another circle in the middle of the screen. The circle in the middle can move to the left and right by touching either the left or right side of the screen. I want the score to increment by one every time the circle in the middle of the screen dodges one of the other 4 circles that are coming up. How would I accomplish this? Thanks. This is the code I have that is not working for what I want it to do.
// This function is called on contact between physics objects
func didBeginContact(contact:SKPhysicsContact){
let node1:SKNode = contact.bodyB.node!
let node2:SKNode = contact.bodyB.node!
if primaryBall == contact.bodyA.node! {
primaryBall.physicsBody?.affectedByGravity = true
view?.scene?.paused = false
} else {
primaryBall.physicsBody?.affectedByGravity = false
}
if ballToDodge.size.height > -50 + primaryBall.size.height {
self.score++
highscoreLabel.text = String(score)
}
}
override func update(currentTime: CFTimeInterval) {
if ballToDodge.position.y < enemy.position.y {
self.score++
highscoreLabel.text = String(score)
}
}

Sprite Kit physicsBody.resting behavior

I am using Swift and Sprite Kit to develop a game on XCode Beta 6.
In order to detect if all nodes are sleeping, i check their physicsBody.resting property.
In update method i print out the result.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var hero:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFromRect:self.frame)
hero = SKSpriteNode(imageNamed: "Spaceship")
hero.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
hero.zPosition = 10.0
hero.physicsBody = SKPhysicsBody(circleOfRadius: hero.size.width/2)
hero.physicsBody.allowsRotation = false
hero.physicsBody.linearDamping = 0.5
self.addChild(hero)
}
override func update(currentTime: CFTimeInterval) {
if hero.physicsBody.resting {
println("resting")
} else {
println("moving")
}
}
}
To my surprise, the results are:
moving
resting
moving
(n times the same)
moving
resting
So why the hero is moving, although i didn't do anything. The node moves N times and takes a break(resting), after that goes on moving.
Can anyone explain that behaviour? Is that a bug or do i miss something? Thanks in advance.
If you examine the velocity of a physics body, you'll see that it is indeed moving but at a rate that is not perceivable. That's why the resting property is not set. A more reliable way to check if a SKPhysicsBody is at rest is to test if its linear and angular speeds are nearly zero. Here's an example of how to do that:
func speed(velocity:CGVector) -> Float {
let dx = Float(velocity.dx);
let dy = Float(velocity.dy);
return sqrtf(dx*dx+dy*dy)
}
func angularSpeed(velocity:CGFloat) -> Float {
return abs(Float(velocity))
}
// This is a more reliable test for a physicsBody at "rest"
func nearlyAtRest(node:SKNode) -> Bool {
return (self.speed(node.physicsBody.velocity)<self.verySmallValue
&& self.angularSpeed(node.physicsBody.angularVelocity) < self.verySmallValue)
}
override func update(_ currentTime: TimeInterval) {
/* Enumerate over child nodes with names starting with "circle" */
enumerateChildNodesWithName("circle*") {
node, stop in
if (node.physicsBody.resting) {
println("\(node.name) is resting")
}
if (self.nearlyAtRest(node)) {
println("\(node.name) is nearly resting")
}
}
}