Why is runAction() function being called multiple times within my update() method? - swift

Within my func update(currentTime: CFTimeInterval) method in Sprite Kit, I have a gameTicker that increments by one integer every time the game is updated.
When the gameTicker is divisible by 500, I pause the ticker, disable enemies from spawning by removing the original action called in didMoveToView(), and start a nextLevelDelayTicker that functions as a brief delay. Once the nextLevelDelayTicker reaches 100, I start incrementing the original gameTicker again, reset the nextLevelDelayTicker to 0, and run an action to start spawning enemies again.
Basically, once the nextLevelDelayTicker is equal to 100, I only want to run the contents of the conditional one time. I added a print("yolo") in there to see if it was only being called once when the conditional is met, and indeed it is. However, for some reason runAction(spawnAction, withKey: "spawnAction") is being called multiple times. As soon as the condition self.nextLevelDelayTicker.getTimePassed() == 100 is met, an enormous amount of enemies are being spawned within a very short period of time.
How is runAction(spawnAction, withKey: "spawnAction") being called multiple times, but print("yolo") is only being called once?
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.removeActionForKey("spawnAction")
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()
let spawnAction = SKAction.repeatActionForever(
SKAction.sequence([
SKAction.waitForDuration(2),
SKAction.runBlock({
[unowned self] in
self.spawnEnemy()
})
])
)
runAction(spawnAction, withKey: "spawnAction")
}
}
}

So it turns out I'm a dodo head. runAction(spawnAction, withKey: "spawnAction") wasn't being called more than once, it was:
if gameTicker.getTimePassed() % 500 == 0 {
self.enemyAmount += 1
self.removeActionForKey("spawnAction")
gameTicker.isActive = false
}
being called multiple times, adding an enormous amount of enemies to the self.enemyAmount global variable that was being used to determine how many enemies spawn when spawnAction was run.
I added a flag to the conditional and it is only being called once every level cycle compared to what was ~300 times per game cycle:
if gameTicker.getTimePassed() % 500 == 0 && gameTicker.isActive == true{
self.enemyAmount += 1
self.removeActionForKey("spawnAction")
gameTicker.isActive = false
print("STOP SPAWNING")
}

Related

Step Counter Sprite Kit

I am trying to implement a step counter in my sprite Kit game.
And it should work like this:
The counter adds 1 to a value each second.
Every fifth second the duration (in this case 1) gets divided by 1.1
But if I create a func that returns the new duration, the repeat forever SKAction only uses this value for one time and then the duration never changes again.
you should make an action that calls itself, rather than using SKAction.repeatForever(...). you can recalculate values that way. not sure i entirely understand your use case, but here is an example that fires an event after a duration, and modifies that duration every fifth cycle.
var isLoopEnabled:Bool = true
var counter:Int = 0
var duration:TimeInterval = 1.0
func updateDuration() {
duration /= 1.1
}
/*
creates an event loop. the action waits, fires, then calls itself again (before exiting)
turn the loop off using the isLoopEnabled flag
*/
func loop() {
let wait = SKAction.wait(forDuration: duration)
let run = SKAction.run {
self.counter += 1 //increment counter
//update duration every fifth count
if self.counter % 5 == 0 {
self.updateDuration()
}
}
let end = SKAction.run{
print("\(self.counter) -- duration: \(self.duration)")
guard self.isLoopEnabled else { return } //flag allows you to exit loop
self.loop() //repeats by calling itself
}
let sequence = SKAction.sequence([ wait, run, end ])
self.run(sequence, withKey:"loop action")
}
override func didMove(to view: SKView) {
loop()
}

Having trouble with a while loop in Swift 5

Essentially, I'm making an app about a virtual dog to help people take care of their dogs. One of the screens gives you fifteen seconds to pet the dog five times. Whenever I try to load the screen, the app freezes. The code is inside of viewDidLoad()
while timesPetted < 5 {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1), execute: {
timer += 1
if timer == 15 {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
})
}
When I delete the while loop, the screen loads normally and runs perfectly, but (obviously) there isn't a time limit. If I move the while loop out of viewDidLoad(), I get an error saying that Xcode "Expected declaration".
Either use a timer that is set to expire in 15 seconds
let timer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { timer in
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
Or if you want to use DispatchQueue then use it only once
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
it appears the while loop is running infinitely due to the value of timesPetted. As the value of timesPetted is not changing at all once it enters the while loop.
To solve your issue you can make changes to your code as below :-
You must be updating your timesPetted value some where in the code.
lets say timesPetted is changed in function called "Petting", so when this function is called have check, which limits the user to pet till 5 times only and another check for 15 seconds. As shown below.
func petting() {
if reset.isHidden && timesPetted <= 5{ // here the reset.isHidden will become false in DispatchQueue (present in viewDidLoad) once the 15 seconds have been passed.
timesPetted += 1
}
}
And also make sure to add this line of code in your viewDidLoad.
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}

one score at a time, update method

I'm creating a simple game with SpriteKit (mostly for learning), and I got a question about score adding. some background: I'm checking if a sprite (SKShapeNode) contains another one, if true, I'm checking their color, if it is the same color, the player should get 1 score. I wrote this function:
func onMatch(){
for ring in mColorRings {
if(mPlayer.contains(ring.position)){
if mPlayer.fillColor.isEqual(ring.fillColor) {
score += 1
mScoreLbl.text = "\(score)"
}
}
}
}
which works, the problem is, I'm calling this function inside the update method. as the update method runs a lot, it calls my function a lot of time and as long as mPlayer contains ring it is adding 1 score to the player. How can I avoid that ?
This depends on your game mechanics. If the ring is supposed to give you a score one time then disappear you can safely remove it within that if test. If you want the ring to stay put and maybe be reused later you can add a boolean to the ring class called something like "scoreGiven" and redo your if test to something like this:
func onMatch(){
for ring in mColorRings {
if !ring.scoreGiven{
if(mPlayer.contains(ring.position)){
if mPlayer.fillColor.isEqual(ring.fillColor) {
score += 1
mScoreLbl.text = "\(score)"
ring.scoreGiven = true
}
}
}else if(!mPlayer.contains(ring.position)){
ring.scoreGiven = false
}
}
This is just an example, but note the "not"s in the updated if statements
Ok, so this is what i suggest:
var playerPassed = false
func onMatch(){
for ring in mColorRings {
if(mPlayer.contains(ring.position)) {
if ((mPlayer.fillColor.isEqual(ring.fillColor)) && playerPassed == false) {
score += 1
mScoreLbl.text = "\(score)"
playerPassed = true
}
}
}
}
You're creating a bool and you check if that is false(default) and if so, the block executes and when it does, the bool is set to true and the condition will be false and no longer return true.

swift spritekit increase frequency of node creation as time goes on

I have figured out how to continuously spawn a node every x seconds. However, I would like to decrease the time that I wait to create a node as the game goes on, to increase the difficulty. For example, I call this function in didMoveToView:
func createSequentialEnemies(){
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(createEnemy),
SKAction.waitForDuration(2.0)
])
))
}
This creates an enemy every 2 seconds, but I want to decrease this duration arbitrarily. For example, say that after 30 seconds of gameplay I want to now spawn enemies every 1.5 seconds. How would I change the duration dynamically?
Create a spawnDuration property and key reference in your scene class.
class SomeClass: SKScene {
private var spawnDuration: NSTimeInterval = 2.0
private let spawnKey = "SpawnKey"
}
Than adjust your spawn code to use this spawn property and key. I slightly changed the syntax as well to make it more legible in my opinion.
func createSequentialEnemies(){
removeActionForKey(spawnKey) // remove previous action if running. This way you can adjust the spawn duration property and call this method again and it will cancel previous action.
let spawnAction = SKAction.runBlock(createEnemy)
let spawnDelay = SKAction.waitForDuration(spawnDuration)
let spawnSequence = SKAction.sequence([spawnAction, spawnDelay])
runAction(SKAction.repeatActionForever(spawnSequence), withKey: spawnKey) // run action with key so you can cancel it later
}
Than you have to add some logic of when you want to change the spawn duration property you created.
Time based could be a func like this you also call once in DidMoveToView
func startDifficultyTimer() {
let difficultyTimerKey = "DifficultyTimerKey"
let action1 = SKAction.waitForDuration(30)
let action2 = SKAction.runBlock { [unowned self] in
guard self.spawnDuration > 0.2 else { // set a min limit
removeActionForKey(difficultyTimerKey) // if min duration has been reached than you might as well stop running this timer.
return
}
self.spawnDuration -= 0.5 // reduce by half a second
self.createSequentialEnemies() // spawn enemies again
}
let sequence = SKAction.sequence([action1, action2])
runAction(SKAction.repeatActionForever(sequence), withKey: difficultyTimerKey)
}
Hope this helps

How to gradually speed up falling nodes over time?

I have two dots that fall parallel to each other from the top of my screen that are to be matched with two circles on the bottom that can be rotated with touch. I have them generated like this:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(1.0)])))
}
func generateDots() {
let count = 2
let index=Int(arc4random_uniform(UInt32(count)))
let dots = SKSpriteNode(imageNamed: "Color\(index+1)")
dots.physicsBody = SKPhysicsBody(circleOfRadius: 10)
dots.physicsBody?.dynamic = true
dots.physicsBody?.affectedByGravity = false
for i in 0..<2 {
dots.physicsBody?.categoryBitMask = UInt32(0x1 << index)
dots.physicsBody?.contactTestBitMask = UInt32(0x1 << index)
}
addChild(dots)
dots.size = CGSizeMake(45, 45)
dots.position = CGPointMake(150, 400)
dots.runAction(
SKAction.moveByX(0, y: -900,
duration: NSTimeInterval(11.5)))
}
}
Is there any way to gradually speed up either how fast they're falling or gradually change the waitForDuration so that over time it will produce a node every 3 sec, then 2 sec, then 1 sec and so forth?
This is completely doable! You just need to add some variables.
If you want to change how fast they fall then you need to make a variable like
Var droptime:NSTimeInterval = 11.5
Then in your "dropdot()" method you need to do two things.
At the beginning subtract or devide your droptime variable like...
Droptime -= 1
Then at the end when you generate the falling action make it
Duration: droptime
Instead of what it was before.
If you want to make the generation time be shorter then you need to make a function that you can trigger each time you want to make your action that the scene runs (like you did in the viewdidload) and edit it so that it has variable wait and triggers itself. Also you will need to self trigger it once in your didMoveToView method.
func controlMethod() {
waitdur -= 1
runAction(SKAction.repeatActionForever( SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(waitdur),
SKAction.runBlock(controlMethod)
])))
}
Good luck!
So sorry for the formatting! I'm on mobile... Hopefully someone can fix it.