Just getting into SpriteKit on iOS using Swift. I have a 'Breakout' game from a tutorial and I want to implement a countdown before every ball launches, by putting an SKLabel in the middle of the screen which counts down from 5 to 1 before removing itself and the game starting. Whilst the countdown is going on, you can see the full games screen with the wall, stationary ball etc.
I can't work out where in the game loop to do this. If I do the countdown in didMoveToView (where I create the wall and initialise the ball and paddle), I never see it, but I see my debugging messages in the log. I guess that didMoveToView is called before the SKScene is presented.
I tried to use a flag to call the countdown function the first time 'Update' is called, but again I saw the countdown executed before anything appeared on screen - i think 'update' is called initially before the scene is rendered.
I could implement a 'Tap screen to start' screen in a another SKScene, but I really wanted a countdown on the screen with the wall and the (stationary) ball ready to go. I could create this countdown scene using a background image of the game screen, but this seems awkward.
Any suggestions gratefully received,
Steve
Added these functions and a call to countdown(5) at the end of didMoveToView
func countdown(count: Int) {
countdownLabel.horizontalAlignmentMode = .Center
countdownLabel.verticalAlignmentMode = .Baseline
countdownLabel.position = CGPoint(x: size.width/2, y: size.height*(1/3))
countdownLabel.fontColor = SKColor.whiteColor()
countdownLabel.fontSize = size.height / 30
countdownLabel.zPosition = 100
countdownLabel.text = "Launching ball in \(count)..."
addChild(countdownLabel)
let counterDecrement = SKAction.sequence([SKAction.waitForDuration(1.0),
SKAction.runBlock(countdownAction)])
runAction(SKAction.sequence([SKAction.repeatAction(counterDecrement, count: 5),
SKAction.runBlock(endCountdown)]))
}
func countdownAction() {
count--
countdownLabel.text = "Launching ball in \(count)..."
}
func endCountdown() {
countdownLabel.removeFromParent()
ball.physicsBody!.applyImpulse(CGVectorMake(20, 20))
}
So I create and set up the text of the countdown and then create and run an SKAction that waits for 1 second before decrementing the countdown and updating the label. It repeats this 5 times and then removes the countdown label before finally giving the ball an impulse to start it moving and so the game proper can start.
Seems to work ok...
This is where NSTimers really come in handy. An NSTimer basically activates a function every so often (you specify how often).
Update: Sometimes it's better not to use an NSTimer; see here: https://stackoverflow.com/a/24950982/5700898
Here's a code sample, using NSTimer:
class ViewController: UIViewController {
var countdownTime = 5
var countdownTimer = NSTimer()
// above, setting your timer and countdown time as global variables so they can be accessed in multiple functions.
func PlayerPressesStart() {
countdownTime = 5
countdownTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "Countdown", userInfo: nil, repeats: true) // calls the function "Countdown" every second.
}
func Countdown() { // this is the function called by the timer every second, which causes your "countdownTime" to go down by 1. When it reaches 0, it starts the game.
countdownTime--
if countdownTime > 0 {
countdownTimer.invalidate()
placewhereyoudisplaycountdown.text = String(countdownTime)
}
if countdownTime == 0 {
// call the function where game action begins here, or call the function that makes the game begin here.
}
} // closing bracket for the View Controller, don't include this if you're copying and pasting to your already existing View Controller.
Related
This is my first ever post - I have searched for a long time and could not find the answer.
I am making a game with SpriteKit and want the player to be able to only launch one bomb at a time- i.e they can't fire again until the previous bomb has exploded or gone off screen. Currently when the player taps the screen, they can launch as many bombs as they want.
Any help would be greatly appreciated!
Thanks,
Iain
Steve's idea works out well and is better than mine, but here is a more novice-friendly explanation IMO... Put this in your gamescene :)
var canFireMissile = true
func fireMissile() {
guard canFireMissile else { return }
canFireMissile = false // So you can't fire anymore missiles until 0.5secs later
let wait = SKAction.wait(forDuration: 0.5) // the duration of the missile animation (example)
let reset = SKAction.run { canFireMissile = true } // lets us refire the missile
let sequence = SKAction.sequence([wait, reset])
run(sequence)
}
override func mouseDown(with event: NSEvent) { // touchesBegan on iOS
fireMissile()
}
Create a SKSpriteNode property for your misssile.
Create an SKAction for the movement of the missile and give the action a key so you can refer to it by name).
When the fire button is pressed, check to see if the named action is already running; if it is, do nothing, otherwise run the ‘fireMissile’ action.
I'm creating a SpriteKit game that updates based on the amount of time passed. The game spawns enemies using an NSTimer and its scheduledTimerWithTimeInterval method, calling the spawnEnemy function every 2.0 seconds.
When 5 seconds have passed there should be a very brief intermission, preventing new enemies from spawning in order to show a level change animation.
When the initial 5 seconds has been reached, everything works well up until the conditional where self.nextLevelDelayTicker == 100. Once this conditional is met, the "YOLO" string is only fired once in the console. However, I'm assuming multiple instances of NSTimer are being created and stored within self.timer since a massive amount of enemies are spawned after self.resumeGame() is called to create a new scheduled timer.
Any ideas on why this is happening even though I have flags set up within my conditional to only call the self.resumeGame() function once?
func resumeGame() {
// Start game timer
// Need a way to access ib action of pause button
self.timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "spawnEnemy", userInfo: nil, repeats: true)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if gameTicker.isActive == true {
gameTicker.increment()
}
// If gameTicker is equal to 5 seconds, increase enemy amount
if gameTicker.getTimePassed() % 500 == 0 {
self.enemyAmount += 1
self.timer?.invalidate()
levelCount += 1
gameTicker.isActive = false
}
// If level has been completed and last ghost has been killed, activate next level scene
if gameTicker.isActive == false && enemyArray.count == 0 {
self.nextLevelDelayTicker.increment()
if self.nextLevelDelayTicker.getTimePassed() == 100 {
print("YOLO")
self.gameTicker.isActive = true
self.nextLevelDelayTicker.reset()
self.resumeGame()
}
}
}
Trying to follow your code.. but I think your approach here isn't great for spritekit. It's probably making things way more complicated than it needs to be.
You can keep track of time using your update method directly. It would probably be worth rewriting this part of your code. Would work better within spritekit and be less prone to bugs.
All you really need is delta time.
scene properties
// time values
var delta = NSTimeInterval(0)
var last_update_time = NSTimeInterval(0)
// some time youre trying to keep track of
var timeLimit = NSTimeInterval(5)
var timeLimitMax = NSTimeInterval(5)
your scene's update method
func update(currentTime: NSTimeInterval) {
if last_update_time == 0.0 {
delta = 0
} else {
delta = currentTime - last_update_time
}
last_update_date = currentTime
// now we can keep track of time
timeLimit -= self.delta
if timeLimit <= 0 {
// do something and reset timer
timeLimit = timeLimitMax
}
}
Now if you're going to be consistently spawning something every number of seconds then we dont even need to bother with update to do this. Just put this in your viewDidLoad
Now we're running this code every two seconds forever. The best part is this will pause and resume with your game automatically. You don't have to manage SKAction too much. spritekit does it for you :)
let spawnAction = SKAction.repeatActionForever(
SKAction.sequence([
SKAction.waitForDuration(2),
SKAction.runBlock({
[unowned self] in
self.spawnEnemy()
})
])
)
runAction(spawnAction)
I'm looking for a way to exit a function without using guard. After extensive searching, I cannot find a way to exit a function and call the next at the same time when a button it pressed.
The button calls a repeat using a selector:
#IBAction func BottomLeft(sender: AnyObject) {
NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(0.01), target: self, selector: "bottomLeftMovement", userInfo: nil, repeats: true)
}
This is when the code button is pressed, and the ball will follow this movement:
func bottomLeftMovement() {
Ballx = Ballx - 0.6125
Bally = Bally + 1.2
self.Ball.center.x = Ballx
self.Ball.center.y = Bally
}
I am looking to make it so that when a different button is pressed, that this function will be exited and the corresponding one will be called.
I cannot hard program into the first function, as it is an interchangeable thing, as opposed to a function chain.
Any help would be much appreciated.
What your code is doing is creating a new timer every time you tap on the button. This is not the correct way to do this. I'd recommend you do some research/reading on game development.
But basically, if you're gonna do things this way (which again, is not a good thing (tm)). You need to keep track of your timer in a property, and invalidate it (to stop it). Read about timers too (https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/)
So, in short:
- don't do this
- if you must, track your timer in a property
- invalidate() your timer and start a new one as required
Note: Track and invalidate the timer like this:
(note I'm doing this from memory, not tested, but something along the lines of...)
class someClass {
var myTimer = NSTimer?
func myFunction() {
if let timer = myTimer {
timer.invalidate()
}
timer = NSTimer(...
I am making a game using Swift and SpriteKit. I have written something of this sort:
node.alpha = 0
sleep(1)
node.alpha = 1
where node is an SKSpriteNode. Instead of making node invisible, freezing the running of the program for one second and making node visible again, what this code does is it simply freezes the running of the program for one second. I figured that all visual changes take place periodically, maybe after each update. How can resolve this and make the node disappear for one second, having the program frozen?
Thank you!
This answer is going to assume you want to just pause the scene, and not the entire program.
What you want to use is the SKActions sequence and waitForDuration,
and the nodes pause variable.
You essentially want to move the entire scene into a separate node, and let another node control the pausing of it.
Set your scene nodes like this:
let sceneWorld = SKNode() //make this available to the entire class
let timerNode = SKNode()
...
override func didMoveToView()
{
....
scene.addNode(timerNode)
scene.addNode(sceneWorld) //sceneWorld is where you will be adding all gfx sprites now
}
func pauseWorld()
{
let wait1sec = SKAction.waitForDuration(1)
let unpause = SKAction.runBlock({sceneWorld.pause = false;showNodes(true)})
timerNode.runAction(SKAction.sequence[wait1sec,unpause])
showNodes(false)
sceneWorld.pause = true;
}
func showNodes(show : Boolean)
{
let alpha = (show) : 1.0 ? 0.0
//set all nodes that need to hide here with alpha
...
}
The solution of luk2303 has worked perfectly for me in my apps.
you can create a timer like this:
node.alpha = 0
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "makeNodeVisible", userInfo: nil, repeats: false)
func makeNodeVisible(){
node.alpha = 1
}
For some reason using the sleep function won't work in SpriteKit because it is meant for the ViewController or something like that.
I'm trying to create a replay button for my game, but whenever I go back to the game scene, It seems as if nothing ever stopped. The time is a negative number and the game will just crash. I tried...
if timeInt < 0 {
//////////////
let retryScene = RetryScene(size: self.frame.size)
self.view?.presentScene(retryScene)
self.removeAllChildren()
self.removeAllActions()
///// end game
timeInt = 45
}
I figured removing all children would work and resetting the time would work too. I used a function that updates every second to make the time work. So all functions keep going as if the scene never ended. What should I do?
All the time i whant to restart game I'am presenting Game scene. (starting game scene from begining)
It should look like this
if (node.name == "ReplayButton") {
var gameScene = GameScene(size: self.size)
var transition = SKTransition.doorsCloseHorizontalWithDuration(0.5)
gameScene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(gameScene, transition: transition)
}
I fixed it by stopping the timer that makes the update function.
if time < 0 {
timer.invalidate()
}