Trying to add levels to my swift sprite kit game - swift

I'm trying to add one simple level to my flappy bird styled game. I figure a way of doing it using 2 different scenes but would prefer it to be within the same one. I simply want the walls to appear more frequently once a score of 15 has been reached! Here is my code
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false{
gameStarted = true
body.physicsBody?.affectedByGravity = true
let spawn = SKAction.run({
() in
self.createWalls()
})
let delay = SKAction.wait(forDuration: 2.0)
let spawnDelay = SKAction.sequence([delay, spawn])
let spawnDelayForever = SKAction.repeatForever(spawnDelay)
self.run(spawnDelayForever)
let distance = CGFloat(self.frame.width + wallPair.frame.width)
let movePipes = SKAction.moveBy(x: -distance - 50, y: 0, duration: TimeInterval( 0.01 * distance))
let removePipes = SKAction.removeFromParent()
moveAndRemove = SKAction.sequence([movePipes,removePipes])
body.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
body.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 68))
run(flap)
}
Ive tried adding a if statement before which was an if statement like
if score = 20{
but that just delays applying impulse till the player has a score of 20.
Is there a way I can say
if score is between 0 and 20 then run my first spawning action
then another saying if score > 20 run my second spawning action which would just have a slower spawning time?
Hope this makes sense! Thanks

You could move the spawnDelayForever logic into a function and call it first with the 2 second delay as parameter, then when the game score gets to 20, call the spawnDelayForever function again passing 5 as input parameter. You would need to add the action using a key.
self.run(spawnDelayForever(withDelay: 2.0), withKey: "spawnDelayForever")
Then remove the running action before adding again.
self.removeAction(forKey: "spawnDelayForever")
self.run(spawnDelayForever(withDelay: 5.0), withKey: "spawnDelayForever")
Your code might look something like this.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
gameStarted = true
body.physicsBody?.affectedByGravity = true
self.run(spawnDelayForever(withDelay: 2.0), withKey: "spawnDelayForever")
// other code...
} else if score > 20 && spawnDelayApplied == false {
self.removeAction(forKey: "spawnDelayForever")
self.run(spawnDelayForever(withDelay: 5.0), withKey: "spawnDelayForever")
spawnDelayApplied = true
}
}
func spawnDelayForever(withDelay sec: TimeInterval) -> SKAction {
let spawn = SKAction.run({
() in
self.createWalls()
})
let delay = SKAction.wait(forDuration: sec)
let spawnDelay = SKAction.sequence([delay, spawn])
return SKAction.repeatForever(spawnDelay)
}

Related

Swift SpriteKit: How do I change action duration after every repeat in RepeatForever of SKAction?

I am attempting to set the duration of SKAction.moveBy to reduce by 1.0 every time it repeats. For some reason when SKAction.repeatForever is ran, variables such as duration (or wallSpeedMultiplier) do not get changed on the repeat. Does anyone know a work around to this?
I tried putting it in a sequence with an action to change the duration, to move the object, then wait for the movement to be done to repeat.
var wallSpeedMultiplier = 10.0
...
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: 15.0, midPoint: road!.position)
road!.run(SKAction.resize(toWidth: 100, duration: 2.0))
let multiplyIt = SKAction.run { [self] in
wallSpeedMultiplier = wallSpeedMultiplier - 1.0
}
let moveIt = SKAction.moveBy(x: size.width, y: 0, duration: wallSpeedMultiplier)
let waitIt = SKAction.wait(forDuration: wallSpeedMultiplier)
let sequenceIt = SKAction.sequence([moveIt,waitIt, multiplyIt])
road!.run(SKAction.repeatForever(oscillate))
road!.run(SKAction.repeatForever(sequenceIt))
Tried solution from https://www.stackoverflow.com/a/34633329/3402095
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !gameStarted {
gameStarted = true
road!.run(SKAction.resize(toWidth: 100, duration: 2.0))
recursive()
}
}
func recursive() {
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: wallSpeedMultiplier, midPoint: road!.position)
let multiplyIt = SKAction.run { [self] in
wallSpeedMultiplier = wallSpeedMultiplier - 5.0
}
let recursive = SKAction.sequence([oscillate, SKAction.wait(forDuration: wallSpeedMultiplier),multiplyIt])
road!.run(recursive, withKey: "aKey")
}
FIXED:
func recursive() {
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: wallSpeedMultiplier, midPoint: road!.position)
let multiplyIt = SKAction.run { [self] in
wallSpeedMultiplier = wallSpeedMultiplier - 5.0
print(wallSpeedMultiplier)
print(oscillate)
}
let recursive = SKAction.sequence([oscillate, multiplyIt, SKAction.run({[unowned self] in NSLog("Block executed"); self.recursive()})])
road!.run(recursive, withKey: "aKey")
}

How to make SKAction Oscillation move faster and faster?

Looking to make an SKSprite (road in this case) move back and forth across the screen. I'd like for the road to move faster and faster every second. I implemented the following but it only sets the oscillation once and never gets updated. Is there any way I can make it oscillate faster and faster? Or is there a better way of doing this using something else?
var wallSpeedMultiplier = 1.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !gameStarted {
titleView.removeFromParent()
tapToStart.removeFromParent()
gameStarted = true
let wait = SKAction.wait(forDuration: 1.0)
let block = SKAction.run({
[unowned self] in
wallSpeedMultiplier = wallSpeedMultiplier - 0.01
})
let sequence = SKAction.sequence([wait, block])
run(SKAction.repeatForever(sequence), withKey: "timer")
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: (30*wallSpeedMultiplier), midPoint: road!.position)
road!.run(SKAction.repeatForever(oscillate))
road!.run(SKAction.moveBy(x: size.width, y: 0, duration: 5))
}
}
extension SKAction {
static func oscillation(amplitude a: CGFloat, timePeriod t: CGFloat, midPoint: CGPoint) -> SKAction {
let action = SKAction.customAction(withDuration: Double(t)) { node, currentTime in
let displacement = a * sin(2 * π * currentTime / t)
node.position.x = midPoint.x + displacement
}
return action
}
}

Label not showing up (Swift 4 SpriteKit)

I am making a game in Xcode with Swift 4, SpriteKit. My crash detection is working, however when I try to make text appear onto the screen when the player crashes, the text doesn't appear. My game basically has the use control a rocket ship which must dodge meteors, the player also has the ability to fire bullets. I have detected when the rocket ship hits a meteor but cannot get text to appear onto the screen. I know for a fact that the crash detection is working, because whenever I add a print statement in the didBegin function, the print statement executes. So it must be that way im displaying the label, but im not sure what I'm doing wrong with the label.
Here's my code:
import SpriteKit
import GameplayKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate{
let player = SKSpriteNode(imageNamed: "spaceship")
let stars = SKSpriteNode(imageNamed: "stars")
let meteor = SKSpriteNode(imageNamed: "meteor")
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
print(frame.size.width)
print(frame.size.height)
stars.position = CGPoint(x:0, y:0)
stars.zPosition = 1
player.position = CGPoint(x:0, y:-320)
player.zPosition = 4
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = 2
player.physicsBody?.collisionBitMask = 1
player.physicsBody?.contactTestBitMask = 1
self.addChild(player)
self.addChild(stars)
self.addMeteor()
}
func didBegin(_ contact: SKPhysicsContact) {
gameOver()
}
func addMeteor() {
meteor.physicsBody = SKPhysicsBody(circleOfRadius: meteor.size.width / 2)
meteor.physicsBody?.affectedByGravity = false
meteor.setScale(0.50)
meteor.position = CGPoint(x:Int(arc4random()%300),y:Int(arc4random()%600))
//meteor.position = CGPoint(x:0 , y:0)
meteor.zPosition = 4
meteor.physicsBody?.categoryBitMask = 1
meteor.physicsBody?.collisionBitMask = 0
meteor.physicsBody?.contactTestBitMask = 2
self.addChild(meteor)
}
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = player.position
bullet.setScale(0.5)
bullet.zPosition = 3
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
func gameOver() {
print("Game Over!")
var gameOverLabel: SKLabelNode!
gameOverLabel = SKLabelNode(fontNamed: "Chalkduster")
gameOverLabel.text = "Game Over! You lost!"
gameOverLabel.horizontalAlignmentMode = .right
gameOverLabel.position = CGPoint(x: 0, y:0)
self.addChild(gameOverLabel)
print("Label added")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
fireBullet()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let previousPointOfTouch = touch.previousLocation(in: self)
let amountDragged = pointOfTouch.x - previousPointOfTouch.x
player.position.x += amountDragged
}
}
override func update(_ currentTime: TimeInterval) {
meteor.position.y -= 6
if meteor.position.y < player.position.y - 300{
meteor.removeFromParent()
addMeteor()
}
}
}
Not sure what I'm doing wrong? I call the gameOver() function in didBegin which is supposed to be executed every time collision is detected. But nothing happens? The meteor simply goes past the rocket ship. If someone could help me that'd be awesome!
As i see you are trying to center the label on the screen, you may try to add this to your code, on gameover func:
1.Try change the position to this
gameOverLabel.position = CGPoint(x: (self.scene!.frame.width / 2) - (self.scene!.frame.width * self.scene!.anchorPoint.x) / 2, y:(self.scene!.frame.height / 2) - (self.scene!.frame.height * self.scene!.anchorPoint.y))
2.I don't know if you have something that is over the label and affecting its view, so try to:
gameOverLabel.zPosition = 10 //A number higher than any other zPosition
3.The label may be too small...try:
gameOverLabel.fontSize = 30 //Adjust it to what you want
4.Just a tip:
move the:
var gameOverLabel: SKLabelNode!
to next line after the
let meteor = SKSpriteNode(imageNamed: "meteor")
This way you will have a universal label, and do not have to regenerate the label every time.
If you do this, just a final tip...I don't know if you have a func like gameRestart() or something like this...but make sure that when you restart the game, you remove the label:
gameOverLabel.removeFromParent()
If you don't do this, when the app try to add the label again it will crash!
Hope it helps!
You are not moving any of your objects as far as your physics in concerned. You need to apply forces instead of manually moving a position. Also your player dynamic is set to false, which means that he never moves, so contacts only happen on the meteor end.

Shooting automatically in a Shooter Game (Swift 4 - SpriteKit)

I'm working on a game project with Xcode.
I've written the code that makes the ship shoot the projectiles, but I don't know what function to use to make the ship shoot automatically.
Could you help me? Thank you in advance!
Here's my code, from the GameScene :
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let projectile = SKSpriteNode(imageNamed: "projectile")
projectile.zPosition = 1
projectile.position = CGPoint(x: player.position.x, y: player.position.y)
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
projectile.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(projectile)
let action = SKAction.moveTo(x: self.frame.width + projectile.size.width, duration: 0.5)
projectile.run(action, completion: {
projectile.removeAllActions()
projectile.removeFromParent()
})
}
Based on a comment by Jake I am assuming that you want the ship to fire "automatically" not repeating while holding the finger down.
You can use the update func to control the automatic shooting. in my example the update command fires every 1 second
private var updateTime: Double = 0
override func update(_ currentTime: TimeInterval) {
if updateTime == 0 {
updateTime = currentTime
}
if currentTime - updateTime > 1 {
self.shoot()
updateTime = currentTime
}
}
func shoot() {
let projectile = SKSpriteNode(imageNamed: "projectile")
projectile.zPosition = 1
projectile.position = CGPoint(x: player.position.x, y: player.position.y)
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
projectile.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(projectile)
let action = SKAction.moveTo(x: self.frame.width + projectile.size.width, duration: 0.5)
projectile.run(action, completion: {
projectile.removeAllActions()
projectile.removeFromParent()
})
}

Swift 3 Bullet Firing Delay

In my game, you tap anywhere on the screen and a bullet goes in that direction. The only problem is that you can shoot as fast as you can tap. Is there any way to add a delay after each shot. So I would like to shoot, wait 1 second then shoot. Here is my code in touchesEnded:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
//Set up initial location of bullet and properties
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.position = player.position
bullet.setScale(0.75)
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
bullet.physicsBody?.isDynamic = true
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.usesPreciseCollisionDetection = true
//Determine offset of location to bullet
let offset = touchLocation - bullet.position
//Stops Bullet from shooting backwards
if (offset.y < 0) { return }
addChild(bullet)
//Get the direction of where to shoot
let direction = offset.normalized()
//Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
//Add the shoot amount to the current position
let realDest = shootAmount + bullet.position
//Create the actions
if currentGameState == gameState.inGame {
let actionMove = SKAction.move(to: realDest, duration: 1.0)
let actionMoveDone = SKAction.removeFromParent()
bullet.run(SKAction.sequence([actionMove, actionMoveDone]))
}
}
Thanks for any help.
This is a more simple approach, based on the use of Date:
var time = Date()
func shoot(after timeInterval: Double) {
guard Date() - timeInterval > time else {
print("WAIT")
return
}
print("SHOOT")
time = Date() // reset the timer
}
// CALL THIS INSIDE touchesEnded
shoot(after: 1)
Just modify for your needs :]
You could take a look at the Throttle implementation of RxSwift for one possible solution. Throttle is used to limit the number of events created in a defined time interval:
let timeIntervalSinceLast: RxTimeInterval
if let lastSendingTime = _lastSentTime {
timeIntervalSinceLast = now.timeIntervalSince(lastSendingTime)
}
else {
timeIntervalSinceLast = _parent._dueTime
}
let couldSendNow = timeIntervalSinceLast >= _parent._dueTime
if couldSendNow {
self.sendNow(element: element)
return
}
You can do this using action keys. An action key is a string that makes an action identifiable.
How to use it in this case?
As I said already in comments, you will fire a bullet, then run an action with a key, on a specific node, which will last one second. A presence of this key/action means that weapon is locked. So every time you try to fire a bullet, you check if this key is present on a specific node. When action finishes, the key will be automatically removed as well. Here is the code:
import SpriteKit
let kLockWeaponActionKey = "kLockWeaponActionKey"
class GameScene: SKScene {
func shoot(atPoint targetLocation:CGPoint){
// 1 check if weapon is unlocked, or return
guard self.action(forKey: kLockWeaponActionKey) == nil else {
print("Weapon locked")
return
}
let bullet = SKSpriteNode(color: .purple, size: CGSize(width: 20, height: 20))
addChild(bullet)
let shoot = SKAction.move(to: targetLocation, duration: 3)
//2 shoot
bullet.run(shoot)
//3 lock weapon
self.run(SKAction.wait(forDuration: 1), withKey: kLockWeaponActionKey)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let targetLocation = touch.location(in: self)
self.shoot(atPoint:targetLocation)
}
}
}
If you try to spam bullets fast, you will see a log in the console which says : "weapon locked".