Spritekit - Increasing speed and keeping the same distance between sprite nodes? - swift

I am having an issue where my platforms distance becomes messed up after a certain score has been reached. I want the game to start just like the spawnDelayForever part in the touchesBegan until the score reaches 5. After the score reaches 5…10…15 and so on, I want the platforms to be able to speed up with the same amount of space in between each spawn no matter what. The issue is when the score reaches 5, the platforms speed but the distance/space in between each spawn is not the same. They are further apart. Can anybody help me?
Hopefully this didn’t confuse anyone but if it did let me clarify it. I just want the platforms to speed up after a certain score has been reached with the same amount of space between each spawn. Meaning the waitForDuration should speed up (less than 2.0) with each speed increase.
Part of my code:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if gameStarted == false {
gameStarted = true
ball.physicsBody?.affectedByGravity = true
title.removeFromParent()
let spawn = SKAction.runBlock({
()in
self.creatingPlatforms()
})
let delay = SKAction.waitForDuration(2.0)
let spawnDelay = SKAction.sequence([spawn, delay])
let spawnDelayForever = SKAction.repeatActionForever(spawnDelay)
self.runAction(spawnDelayForever)
let distance = CGFloat(self.frame.width + 170 + officialPlatform.frame.width)
let movePlatforms = SKAction.moveByX(-distance, y: 0, duration: NSTimeInterval(0.01 * distance))
let removePlatforms = SKAction.removeFromParent()
moveAndRemove = SKAction.sequence([movePlatforms, removePlatforms])
ball.physicsBody?.velocity = CGVectorMake(0, 0)
ball.physicsBody?.applyImpulse(CGVectorMake(0, 0))
scoreLbl.fontSize = 35
scoreLbl.position = CGPoint(x: self.frame.width / 2, y: scene!.frame.height / 2 + 300)
scoreLbl.fontColor = UIColor.blackColor()
scoreLbl.text = "\(score)"
score = 0
self.addChild(scoreLbl)
}
else {
ball.physicsBody?.velocity = CGVectorMake(0, 0)
ball.physicsBody?.applyImpulse(CGVectorMake(0, 45))
ball.physicsBody?.restitution = 0
scoreNode.physicsBody?.restitution = 0
}
}
func creatingPlatforms() {
scoreNode = SKSpriteNode()
scoreNode.size = CGSize(width: 120, height: 25)
scoreNode.position = CGPoint(x: self.frame.width + 95, y: self.frame.height / 2)
scoreNode.physicsBody = SKPhysicsBody(rectangleOfSize: scoreNode.size)
scoreNode.physicsBody?.affectedByGravity = false
scoreNode.physicsBody?.dynamic = false
scoreNode.physicsBody?.categoryBitMask = physicsCategory.score
scoreNode.physicsBody?.collisionBitMask = 0
scoreNode.physicsBody?.contactTestBitMask = physicsCategory.ball
scoreNode.name = "scoreNode"
officialPlatform = SKNode()
let platform = SKSpriteNode(imageNamed: "platform")
platform.size = CGSize(width: 140, height: 25)
platform.position = CGPoint(x: self.frame.width + 95, y: self.frame.height / 2)
platform.physicsBody = SKPhysicsBody(rectangleOfSize: platform.size)
platform.physicsBody?.categoryBitMask = physicsCategory.platform
platform.physicsBody?.collisionBitMask = physicsCategory.ball
platform.physicsBody?.contactTestBitMask = physicsCategory.ball
platform.physicsBody?.affectedByGravity = false
platform.physicsBody?.dynamic = false
officialPlatform.name = "official platform"
officialPlatform.runAction(moveAndRemove)
officialPlatform.addChild(platform)
officialPlatform.addChild(scoreNode)
self.addChild(officialPlatform)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if score >= 5 && score <= 9 {
moveAndRemove.speed = 0.9
}
}
}

I found my answer in another question on here.
swift - Increase speed of objects over time
I changed my self.runAction(spawnDelayForever) in my touchesBegan to self.runAction(spawnDelayForever, withKey: "spawnDelayForever"). Then in the update function, I changed it to:
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if score > 0 {
self.actionForKey("spawnDelayForever")?.speed += 0.001
moveAndRemove.speed += 0.001
}
}

Related

Node spacing issues on a vertical scrolling game with variable speeds in Spritekit

So I'm creating a game in Swift with Spritekit and have ran into an issue with getting my game to work. I'm still a beginner with programming so I've likely missed out on a solution to this myself.
Anyway, so the game concept is a simple arcade vertical scroller that involves a player trying to dodge platforms as it descends downward. The mechanics (so far) are a stationary player on the y axis that can move left and right along the x axis while the platforms scroll upward along with the background moving with the platforms to give a visual effect of descent. I've gotten a build working to be fully playable, but there's an issue with spawning the platforms perfectly spaced out. Here's a sketch:
Concept Image
The picture on the left what I'm trying to achieve, while the one on the right is my current and flawed method. The main issue with the one on the right, is that it uses a collision to trigger spawning which means the spawn trigger node (red line) has to be 1 pixel tall to allow for perfect spacing. If the spawn trigger node is more than 1 pixel tall, then the collision may not trigger on that the first pixel of contact and trigger the node a few pixels deep which throws off the entire spacing. Also if the spawn trigger is only 1 pixel tall, it often won't trigger unless the everything is scrolling at slow speeds.
I've tried to think of other methods to approach this but I'm at a loss. I cannot use a simple timer to spawn nodes at intervals because the speed at which the game scrolls is variable and is constantly changing by player controls. The only two other options I can think of (which I don't know how to do either) is either spawn node sets at fixed y-positions and set that on a loop, or change it so the player is actually descending downward while everything is generating around it (seems tougher and maybe unnecessary). I'm considering just rewriting my createPlatforms() method if I need to, but here's the code for that and the background anyway:
var platformGroup = Set<SKSpriteNode>()
var platformSpeed: CGFloat = 0.6 { didSet { for platforms in platformGroup { platforms.speed = platformSpeed } } }
var platformTexture: SKTexture!
var platformPhysics: SKPhysicsBody!
var platformCount = 0
var backgroundPieces: [SKSpriteNode] = [SKSpriteNode(), SKSpriteNode()]
var backgroundSpeed: CGFloat = 1.0 { didSet { for background in backgroundPieces { background.speed = backgroundSpeed } } }
var backgroundTexture: SKTexture! { didSet { for background in backgroundPieces { background.texture = backgroundTexture } } }
func createPlatforms() {
let min = CGFloat(frame.width / 12)
let max = CGFloat(frame.width / 3)
var xPosition = CGFloat.random(in: -min ... max)
if platformCount >= 20 && platformCount < 30 {
stage = 0
setTextures()
xPosition = frame.size.width * 0.125
} else if platformCount == 30 {
stage = 2
setTextures()
} else if platformCount >= 50 && platformCount < 60 {
stage = 0
setTextures()
xPosition = 184
} else if platformCount == 60 {
stage = 3
setTextures()
}
platformPhysics = SKPhysicsBody(rectangleOf: CGSize(width: platformTexture.size().width, height: platformTexture.size().height))
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.physicsBody?.affectedByGravity = false
platformLeft.physicsBody?.collisionBitMask = 0
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 3, height: platformLeft.size.height * 3))
platformLeft.zPosition = 20
platformLeft.name = "platform"
platformLeft.speed = platformSpeed
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = true
platformRight.physicsBody?.collisionBitMask = 0
platformRight.scale(to: CGSize(width: platformRight.size.width * 3, height: platformRight.size.height * 3))
platformRight.zPosition = 20
platformRight.name = "platform"
platformRight.speed = platformSpeed
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.zPosition = 100
scoreNode.name = "scoreDetect"
scoreNode.speed = platformSpeed
let platformTrigger = SKSpriteNode(color: UIColor.orange, size: CGSize(width: frame.width, height: 4))
platformTrigger.physicsBody = SKPhysicsBody(rectangleOf: platformTrigger.size)
platformTrigger.physicsBody?.isDynamic = true
platformTrigger.physicsBody?.affectedByGravity = false
platformTrigger.physicsBody?.categoryBitMask = Collisions.detect
platformTrigger.physicsBody?.contactTestBitMask = Collisions.spawn
platformTrigger.physicsBody?.collisionBitMask = 0
platformTrigger.physicsBody?.usesPreciseCollisionDetection = true
platformTrigger.zPosition = 100
platformTrigger.name = "platformTrigger"
platformTrigger.speed = platformSpeed
let newNodes: Set<SKSpriteNode> = [platformLeft, platformRight, scoreNode, platformTrigger]
for node in newNodes {
platformGroup.insert(node)
}
let yPosition = spawnNode.position.y - transitionPlatform.size().height
let gapSize: CGFloat = -frame.size.width / 6
print(gapSize)
platformLeft.position = CGPoint(x: xPosition + platformLeft.size.width - gapSize, y: -yPosition)
platformRight.position = CGPoint(x: xPosition + gapSize, y: -yPosition)
scoreNode.position = CGPoint(x: frame.midX, y: platformLeft.position.y - platformLeft.size.height / 2)
platformTrigger.position = CGPoint(x: frame.midX, y: platformLeft.position.y)
print(platformLeft.position.y)
print(platformLeft.frame.midY)
let endPosition = frame.maxY + frame.midY
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
for node in newNodes {
let moveSequence = SKAction.sequence([
moveAction,
SKAction.removeFromParent(),
SKAction.run {
self.platformGroup.remove(node)
}
])
addChild(node)
nodeArray.append(node)
node.run(moveSequence)
}
platformCount += 1
}
func startPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
}
run(create)
}
func createBackground() {
for i in 0 ... 1 {
let background = backgroundPieces[i]
background.texture = backgroundTexture
background.anchorPoint = CGPoint(x: 0, y: 0)
background.zPosition = -5
background.size = CGSize(width: frame.size.width, height: frame.size.width * 2.5)
background.position = CGPoint(x: 0, y: background.size.height + (-background.size.height) + (-background.size.height * CGFloat(i)))
self.addChild(background)
nodeArray.append(background)
let scrollUp = SKAction.moveBy(x: 0, y: background.size.height, duration: 5)
let scrollReset = SKAction.moveBy(x: 0, y: -background.size.height, duration: 0)
let scrollLoop = SKAction.sequence([scrollUp, scrollReset])
let scrollForever = SKAction.repeatForever(scrollLoop)
background.run(scrollForever)
}
}
Does anybody have any suggestions on how I approach this or change it so it would work perfectly everytime?

SpriteKit - didBegin contact is called 30 times instead of 1 time

I am making a little FlappyBird clone and have got everything working as it should until I changed the physics body of the bird to be exact to the texture. Now what it does is when it flies through the gap in the pipes it counts 30 points instead of just 1 point.
This only happens when I use the texture exact physics body which I need because the bird isn't round nor rectangular.
How would I go about making the collision so it only collides once with each gap node. I have tried setting the categoryBitBask to 0 after the contact but then all the gaps after don't add to the point count anymore at all.
Here is the full game code:
var score = 0
class GameScene: SKScene, SKPhysicsContactDelegate {
var bird = SKSpriteNode()
var bg = SKSpriteNode()
var ground = SKSpriteNode()
var scoreLabel = SKLabelNode(fontNamed: "Candice")
var gameOverLabel = SKLabelNode(fontNamed: "Candice")
var countbg = SKSpriteNode()
var timer = Timer()
enum ColliderType: UInt32 {
case Bird = 1
case Object = 2
case Gap = 4
}
var gameOver = false
let swooshSound = SKAction.playSoundFileNamed("sfx_swooshing.wav", waitForCompletion: false)
let pointSound = SKAction.playSoundFileNamed("sfx_point.wav", waitForCompletion: false)
let hitSound = SKAction.playSoundFileNamed("sfx_hit.wav", waitForCompletion: false)
#objc func makePipes() {
let movePipes = SKAction.move(by: CGVector(dx: -2 * self.frame.width, dy: 0), duration: TimeInterval(self.frame.width / 150))
let removePipes = SKAction.removeFromParent()
let moveAndRemovePipes = SKAction.sequence([movePipes, removePipes])
let gapHeight = bird.size.height * 2.8
let movementAmount = arc4random() % UInt32(self.frame.height) / 2
let pipeOffset = CGFloat(movementAmount) - self.frame.height / 4
let pipeTexture = SKTexture(imageNamed: "pipe1.png")
let pipe1 = SKSpriteNode(texture: pipeTexture)
pipe1.position = CGPoint(x: self.frame.midX + self.frame.width, y: self.frame.midY + pipeTexture.size().height / 2 + gapHeight / 2 + pipeOffset)
pipe1.zPosition = 2
pipe1.run(moveAndRemovePipes)
pipe1.physicsBody = SKPhysicsBody(rectangleOf: pipeTexture.size())
pipe1.physicsBody!.isDynamic = false
pipe1.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
pipe1.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
pipe1.physicsBody!.collisionBitMask = ColliderType.Object.rawValue
self.addChild(pipe1)
let pipe2Texture = SKTexture(imageNamed: "pipe2.png")
let pipe2 = SKSpriteNode(texture: pipe2Texture)
pipe2.position = CGPoint(x: self.frame.midX + self.frame.width, y: self.frame.midY - pipeTexture.size().height / 2 - gapHeight / 2 + pipeOffset)
pipe2.zPosition = 2
pipe2.run(moveAndRemovePipes)
pipe2.physicsBody = SKPhysicsBody(rectangleOf: pipe2Texture.size())
pipe2.physicsBody!.isDynamic = false
pipe2.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
pipe2.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
pipe2.physicsBody!.collisionBitMask = ColliderType.Object.rawValue
self.addChild(pipe2)
let gap = SKNode()
gap.position = CGPoint(x: self.frame.midX + self.frame.width, y: self.frame.midY + pipeOffset)
gap.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 1, height: gapHeight))
gap.physicsBody!.isDynamic = false
gap.run(moveAndRemovePipes)
gap.physicsBody!.contactTestBitMask = ColliderType.Bird.rawValue
gap.physicsBody!.categoryBitMask = ColliderType.Gap.rawValue
gap.physicsBody!.collisionBitMask = ColliderType.Gap.rawValue
self.addChild(gap)
}
func didBegin(_ contact: SKPhysicsContact) {
if gameOver == false {
if contact.bodyA.categoryBitMask == ColliderType.Gap.rawValue || contact.bodyB.categoryBitMask == ColliderType.Gap.rawValue {
score += 1
scoreLabel.text = String(format: "%05d", score)
run(pointSound)
} else {
self.speed = 0
run(hitSound)
gameOver = true
timer.invalidate()
bird.removeFromParent()
let changeSceneAction = SKAction.run(changeScene)
self.run(changeSceneAction)
}
}
}
//MARK: Change to Game Over Scene
func changeScene(){
let sceneToMoveTo = GameOverScene(size: self.size)
sceneToMoveTo.scaleMode = self.scaleMode
let myTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo, transition: myTransition)
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
setupGame()
}
func setupGame() {
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.makePipes), userInfo: nil, repeats: true)
let groundTexture = SKTexture(imageNamed: "ground.png")
let moveGroundAnimation = SKAction.move(by: CGVector(dx: -groundTexture.size().width, dy: 0), duration: 7)
let shiftGroundAnimation = SKAction.move(by: CGVector(dx: groundTexture.size().width, dy: 0), duration: 0)
let moveGroundForever = SKAction.repeatForever(SKAction.sequence([moveGroundAnimation, shiftGroundAnimation]))
var i: CGFloat = 0
while i < 3 {
ground = SKSpriteNode(texture: groundTexture)
ground.position = CGPoint(x: self.size.width * i, y: self.size.height / 7.65)
ground.zPosition = 3
ground.run(moveGroundForever)
self.addChild(ground)
i += 1
}
let bottom = SKNode()
bottom.position = CGPoint(x: self.frame.midX, y: self.size.height / 7)
bottom.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.frame.width, height: 1))
bottom.physicsBody!.isDynamic = false
bottom.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
bottom.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
bottom.physicsBody!.collisionBitMask = ColliderType.Object.rawValue
self.addChild(bottom)
let bgTexture = SKTexture(imageNamed: "bg.png")
bg = SKSpriteNode(texture: bgTexture)
bg.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
bg.size = self.frame.size
bg.zPosition = 1
self.addChild(bg)
let birdTexture = SKTexture(imageNamed: "flappy1.png")
let bird2Texture = SKTexture(imageNamed: "flappy2.png")
let bird3Texture = SKTexture(imageNamed: "flappy3.png")
let bird4Texture = SKTexture(imageNamed: "flappy4.png")
let bird5Texture = SKTexture(imageNamed: "flappy5.png")
let bird6Texture = SKTexture(imageNamed: "flappy6.png")
let animation = SKAction.animate(with: [birdTexture, bird2Texture, bird3Texture, bird4Texture, bird5Texture, bird6Texture], timePerFrame: 0.1)
let makeBirdFlap = SKAction.repeatForever(animation)
bird = SKSpriteNode(texture: birdTexture)
bird.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
bird.setScale(1)
bird.zPosition = 6
bird.run(makeBirdFlap)
self.addChild(bird)
bird.physicsBody = SKPhysicsBody.init(circleOfRadius: birdTexture.size().height / 2)
//bird.physicsBody = SKPhysicsBody(texture: birdTexture, size: birdTexture.size())
bird.physicsBody!.isDynamic = false
bird.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
bird.physicsBody!.categoryBitMask = ColliderType.Bird.rawValue
bird.physicsBody!.collisionBitMask = ColliderType.Bird.rawValue
let countbg = SKSpriteNode(imageNamed: "count_bg.png")
countbg.position = CGPoint(x: self.size.width / 4.8, y: self.size.height * 0.94)
countbg.setScale(0.8)
countbg.zPosition = 4
addChild(countbg)
scoreLabel.fontSize = 80
scoreLabel.text = String(format: "%05d", score)
scoreLabel.fontColor = SKColor(red: 218/255, green: 115/255, blue: 76/255, alpha: 1)
scoreLabel.position = CGPoint(x: self.size.width / 4, y: self.size.height * 0.94)
scoreLabel.zPosition = 5
addChild(scoreLabel)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameOver == false {
bird.physicsBody!.isDynamic = true
bird.physicsBody!.velocity = CGVector(dx: 0, dy: 0)
bird.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 280))
//run(swooshSound)
} else {
gameOver = false
score = 0
self.speed = 1
self.removeAllChildren()
setupGame()
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
If you would be using RxSwift, you would be able to easily get rid of those extra events easily by using debounce() or throttle() or distinctUntilChanged(). If you are willing to try this approach, give RxSpriteKit framework a go. Otherwise, store a timestamp of the last contact and ignore the following contacts until some time period elapses.

Spritekit - Shifting between two functions with a timer

I'm making a game where I have two different functions that holds different types of enemies. I want to make a code that shifts between the two every 10 score. So from 0-10 "function 1" is active, and from 10-20 "function 2" is active, and then it changes back again and then back again and so on.
This is my two functions containing enemies:
var score = 0
func createPipes() {
PipesHolder = SKNode()
PipesHolder.name = "Pipe"
let pipeLeft = SKSpriteNode(imageNamed: "PipeRight")
pipeLeft.name = "Pipe"
pipeLeft.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pipeLeft.position = CGPoint(x: 300, y: 0)
pipeLeft.physicsBody = SKPhysicsBody(rectangleOf: pipeLeft.size)
pipeLeft.physicsBody?.categoryBitMask = ColliderType.Pipe
pipeLeft.physicsBody?.affectedByGravity = false
let pipeRight = SKSpriteNode(imageNamed: "PipeLeft")
pipeRight.name = "Pipe"
pipeRight.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pipeRight.position = CGPoint(x: -300, y: 0)
pipeRight.physicsBody = SKPhysicsBody(rectangleOf: pipeRight.size)
pipeRight.physicsBody?.categoryBitMask = ColliderType.Pipe
pipeRight.physicsBody?.affectedByGravity = false
PipesHolder.zPosition = 2
PipesHolder.xScale = 1.5
PipesHolder.yScale = 0.8
PipesHolder.position.x = CGFloat.randomBetweenNumbers(firstNum:
-220, secondNum: 220)
PipesHolder.position.y = self.frame.height + 100
PipesHolder.addChild(pipeLeft)
PipesHolder.addChild(pipeRight)
self.addChild(PipesHolder)
let destination = self.frame.height * 2
let move = SKAction.moveTo(y: -destination, duration: 10)
let remove = SKAction.removeFromParent()
let moveRight = SKAction.moveBy(x: 200, y: 0, duration: 1)
let moveLeft = SKAction.moveBy(x: -200, y: 0, duration: 1)
let moveBackAndForth =
SKAction.repeatForever(SKAction.sequence([moveRight, moveLeft]))
PipesHolder.run(moveBackAndForth)
PipesHolder.run(SKAction.sequence([move, remove]), withKey:
"MovePipes")
}
func spawnPipes() {
let spawn = SKAction.run({ () -> Void in
self.createPipes()
})
let delay = SKAction.wait(forDuration: 1)
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "SpawnPipes")
}
func createRedEnemies() {
let enemyHolder = SKNode()
enemyHolder.name = "Holder"
let enemyLeft = SKSpriteNode(imageNamed: "Enemy")
let enemyMiddle = SKSpriteNode(imageNamed: "Enemy")
let enemyRight = SKSpriteNode(imageNamed: "Enemy")
enemyLeft.name = "Enemy"
enemyLeft.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemyLeft.position = CGPoint(x: 200, y: 0)
enemyLeft.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:
enemyLeft.size.width - 5, height: enemyLeft.size.height - 5))
enemyLeft.physicsBody?.categoryBitMask = ColliderType.Enemy
enemyLeft.physicsBody?.collisionBitMask = 0
enemyLeft.physicsBody?.affectedByGravity = false
enemyMiddle.name = "Enemy"
enemyMiddle.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemyMiddle.position = CGPoint(x: 0, y: 0)
enemyMiddle.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:
enemyMiddle.size.width - 5, height: enemyMiddle.size.height - 5))
enemyMiddle.physicsBody?.categoryBitMask = ColliderType.Enemy
enemyLeft.physicsBody?.collisionBitMask = 0
enemyMiddle.physicsBody?.affectedByGravity = false
enemyRight.name = "Enemy"
enemyRight.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemyRight.position = CGPoint(x: -200, y: 0)
enemyRight.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:
enemyRight.size.width - 5, height: enemyRight.size.height - 5))
enemyRight.physicsBody?.categoryBitMask = ColliderType.Enemy
enemyLeft.physicsBody?.collisionBitMask = 0
enemyRight.physicsBody?.affectedByGravity = false
enemyHolder.zPosition = 2
enemyHolder.position.y = self.frame.height + 100
enemyHolder.position.x = CGFloat.randomBetweenNumbers(firstNum:
-100, secondNum: 100)
enemyHolder.addChild(enemyLeft)
enemyHolder.addChild(enemyMiddle)
enemyHolder.addChild(enemyRight)
self.addChild(enemyHolder)
let destination = self.frame.height * 4
let move = SKAction.moveTo(y: -destination, duration: 9)
let remove = SKAction.removeFromParent()
enemyHolder.run(SKAction.sequence([move, remove]), withKey:
"MoveEnemies")
}
func spawnEnemies() {
let spawn = SKAction.run({ () -> Void in
self.createRedEnemies()
})
let delay = SKAction.wait(forDuration: 0.4)
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "SpawnEnemies")
}
Here is the code I have for the shifted functions that I add in the "didMove":
func shiftEnemies() {
if score >= 0 && score <= 10 {
spawnEnemies()
} else if score >= 11 && score <= 20 {
spawnPipes()
} else if score >= 21 && score <= 30 {
spawnEnemies()
} else if score >= 31 && score <= 40 {
spawnPipes()
}
}
Two problems with the "shiftedEnemies()". The first one is obvious, I cant write a code for every 10 score. The second problem is that this code doesn't even work. "SpawnEnemies()" is the only function that is shown. "spawnPipes()" doesn't show, ever. Maybe problem number two will be solved when I fix problem number 1.
Thx guys!
the reason your SpawnEnemies function is the only function that is being called is because you put the function shiftEnemies in the didMove(toView:) method and didMove(toView:) only gets called one time when you present your scene
what i recommend is try calling the function shiftEnemies() in the part of code where the score is being added (most likely in your didBeginContact method)
So you want to start by calling spawnenemies and when the score goes past a multiple of 10, switch to calling spawnPipes and then back to spawnEnemies at the next multiple of 10 etc?
Simply have a bool called shouldSpawnEnemies:
var shouldSpawnEnemies = true // Start by spawning enemies
(if you want to start by spawning pipes, initialise this to false).
Initialise the score at which functions should switch:
var switchFunctionScore = 10
Put a property watcher on your score. When the score passes the 'switch' score, set the bool indicating which function to use to false. Then set the next score at which functions should be switched.
var score : int = 0 {
didSet {
if (score >= switchFunctionScore) && oldValue < switchFunctionScore) {
shouldSpawnEnemies = !shouldSpawnEnemies
switchFunctionScore += 10
}
}
Then, whenever you need to call one of these functions; just check the value of shouldSpawnEnemies:
if shouldSpawnenemies {
spawnEnemies
} else {
spawnPipes
}
I would avoid using Timer, Timer works outside of the SpriteKit time system, so for me to cheat in your game, I could constantly exit and return the app, and since Timer is based on real time and not game time, the time that is spent outside of the game will still be accounted for.
What you want to do is use SKAction wait(duration:), 'sequence, 'run(_ block:) repeatForever and repeat(_:count)
To do this, you need to break it down into steps:
1st, we want to wait 1 second and fire function 1:
let wait1Sec = SKAction.wait(duration:1)
let function1 = SKAction.run({[weak self] in self?.function1()})
let seq1 = SKAction.sequence([wait1Sec,function1])
2nd, we want to create an action that repeats it 10 times:
let repeat1 = SKAction.repeat(seq1,count:10)
3rd, we want to do this again for function 2:
let function2 = SKAction.run({[weak self] in self?.function2()})
let seq2 = SKAction.sequence([wait1Sec,function2])
let repeat2 = SKAction.repeat(seq2,count:10)
Finally, we want to combine the 2 and run it indefinetely
let seqForever = SKAction.sequence([repeat1,repeat2])
let repeatForever = SKAction.repeatForever(seqForever)
Now that we have the action, we can attach it to the scene once
scene.run(repeatForever,withKey:"forever")
You now have a solution that will constantly fire a method 10 times in 10 seconds, then switch to the other function for 10 more times in 10 seconds, repeating forever.
override func didMove(to view:SKView)
{
let wait1Sec = SKAction.wait(duration:1)
let function1 = SKAction.run({[weak self] in self?.function1()})
let seq1 = SKAction.sequence([wait1Sec,function1])
let repeat1 = SKAction.repeat(seq1,count:10)
let function2 = SKAction.run({[weak self] in self?.function2()})
let seq2 = SKAction.sequence([wait1Sec,function2])
let repeat2 = SKAction.repeat(seq2,count:10)
let seqForever = SKAction.sequence([repeat1,repeat2])
let repeatForever = SKAction.repeatForever(seqForever)
scene.run(repeatForever,withKey:"forever")
}

SpriteKit game lag when the function touchesBegan is called

I'm creating my first game, similar to flappy bird. I want it to start when the screen is touched like the real game. But it lags for about a second and an half, the result is that you die without even playing. Here is my code:
override func didMoveToView(view: SKView) {
/* Setup your scene here */
//Here i init some stuff
distanceToMove = CGFloat(self.frame.size.width + 140)
movePipes = SKAction.repeatActionForever(SKAction.moveByX(-distanceToMove, y: 0, duration: NSTimeInterval(1.2)))
removePipes = SKAction.removeFromParent()
moveAndRemove = SKAction.sequence([movePipes,removePipes])
let delay = SKAction.waitForDuration(NSTimeInterval(0.6))
let spawn = SKAction.runBlock({() in self.initPipes()})
let spawnAndDelay = SKAction.sequence([spawn,delay])
spawnAndDelayForever = SKAction.repeatActionForever(spawnAndDelay)
}
TouchesBegan:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let touch = touches.first! as UITouch!
let touchLocation = touch.locationInNode(self)
if state == GameState.Starting {
state = GameState.Playing
instNode.hidden = true
if state == GameState.Playing {
runAction(spawnAndDelayForever)
addChild(pipes)
initScore()
}
}
Pipes init:
func initPipes() {
let upper = UInt32(self.size.height - 250)
let pY = arc4random_uniform(upper) + 200
let pipePair = SKNode()
pipePair.position = CGPoint(x: self.frame.size.width + 70, y: 0)
//PIPE 1
let pipe1 = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(70, 700))
pipe1.anchorPoint = CGPointMake(0, 0)
pipe1.position = CGPoint(x: 0, y: Int(pY))
pipe1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(70, 700), center: CGPointMake(70/2, 700/2))
pipe1.physicsBody?.dynamic = false
pipe1.physicsBody?.affectedByGravity = false
pipe1.physicsBody?.categoryBitMask = PipeCategory
pipe1.physicsBody?.contactTestBitMask = PlayerCategory
pipe1.physicsBody?.collisionBitMask = PlayerCategory
pipePair.addChild(pipe1)
//PIPE 2
let pipe2 = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(70, 700))
pipe2.anchorPoint = CGPointMake(0,1)
pipe2.position = CGPoint(x: 0, y: pipe1.position.y - 150)
pipe2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(70, 700), center: CGPointMake(35, -700/2))
pipe2.physicsBody?.dynamic = false
pipe2.physicsBody?.affectedByGravity = false
pipe2.physicsBody?.categoryBitMask = PipeCategory
pipe2.physicsBody?.contactTestBitMask = PlayerCategory
pipe2.physicsBody?.collisionBitMask = PlayerCategory
pipePair.addChild(pipe2)
//SCORE
let scoreSprite = SKSpriteNode(color: SKColor.clearColor(), size: CGSize(width: 50, height: 150))
scoreSprite.anchorPoint = CGPointMake(0, 1)
scoreSprite.position = CGPointMake(pipe1.position.x + 10, pipe1.position.y)
scoreSprite.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 50, height: 150), center: CGPointMake(25, -75))
scoreSprite.physicsBody?.categoryBitMask = GapCategory
scoreSprite.physicsBody?.contactTestBitMask = PlayerCategory
scoreSprite.physicsBody?.collisionBitMask = 0
scoreSprite.physicsBody?.dynamic = false
pipePair.addChild(scoreSprite)
pipePair.runAction(moveAndRemove)
pipes.addChild(pipePair)
}
It's pretty simple: in initPipes() i create the pipes and i run the action of moving and removing. In touchesBegan i call the action of spawning them.. But it's laggy when i touch the screen.
Run your app through the Time Profiler Instrument to find out where the lag is coming from. It'll give you detailed results (down to individual lines of code) that'll let you know where your issues are.
That'll be much more accurate than people on here guessing.

Stopping the countUp speed rate

I just started to learn Swift and Sprite Kit. I need assistance with the following code.
I have a countUp that when the ball is touched the countUp starts but my problem is that every time I tap the ball the rate of speed in the countUp increases. I want the CountUp to be stable. Any help will be appreciated.
class Game: SKScene {
var Ball = SKSpriteNode(imageNamed: "Red.png")
var QuitOption = SKLabelNode()
var ScoreLabel = SKLabelNode()
var timescore = Int()
var timesecond = Int()
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor() // background for the display
self.physicsWorld.gravity = CGVectorMake(0, -9.8)
let SceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
SceneBody.friction = 0
self.physicsBody = SceneBody
Ball.size = CGSize(width: 120, height: 120)
Ball.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height*0.7)
Ball.physicsBody = SKPhysicsBody(circleOfRadius: 60)
Ball.physicsBody?.affectedByGravity = true
Ball.physicsBody?.restitution = 0.5
Ball.physicsBody?.linearDamping = 0
Ball.name = "Ball"
self.addChild(Ball)
QuitOption.text = "Quit"
QuitOption.fontName = "Noteworthy-Light"
QuitOption.fontColor = SKColor.greenColor()
QuitOption.fontSize = 35
QuitOption.position = CGPoint(x: self.frame.size.width/2 - 160, y: self.frame.size.height*1 - 110)
QuitOption.name = "Quit"
addChild(QuitOption)
ScoreLabel = SKLabelNode(fontNamed: "Noteworthy-Light")
ScoreLabel.fontSize = 50 // The + will move it to the right side and - to the left side for more accuracy.
ScoreLabel.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/4 + 400) // position of ScoreLabelNode
ScoreLabel.name = "Score+"
ScoreLabel.hidden = false
self.addChild(ScoreLabel)
}
// Making the ball jump after user touches ball
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var touch = touches.first as! UITouch
var location = touch.locationInNode(self)
var node = self.nodeAtPoint(location)
if (node.name == "Quit"){
let myScene = GameScene(size: self.size)
myScene.scaleMode = scaleMode
let reveal = SKTransition.fadeWithDuration(1)
self.view?.presentScene(myScene, transition: reveal)
}
if (node.name == "Ball"){
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
Ball.physicsBody?.allowsRotation = true
Ball.physicsBody?.velocity = CGVectorMake(0, 0)
Ball.physicsBody?.applyImpulse(CGVectorMake(0, 100))
}
}
var actionwait = SKAction.waitForDuration(0.5)
var actionrun = SKAction.runBlock({
self.timescore++
self.timesecond++
if self.timesecond == 60 {self.timesecond = 0}
self.ScoreLabel.text = " \(self.timescore/60):\(self.timesecond)"})
ScoreLabel.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
}
}
I guess that's happening because you are running same block over and over again after every touch. To ensure you run block only once, you can use some Bool indicator and after the first run, set its value appropriately, like this:
if(!self.locked){
self.locked = true // at start, this value should be false
var actionrun = SKAction.runBlock({
self.timescore++
self.timesecond++
if self.timesecond == 60 {self.timesecond = 0}
self.ScoreLabel.text = " \(self.timescore/60):\(self.timesecond)"})
ScoreLabel.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
}
}
Something as an addition to this topic and for future readers could be found here.