3

I'm building my first game in Swift and I wanted to know how to go about handling multiple on screen sprites at once. My game pushes sprites on to screen with addChild continuously, so there are many active at once. I realized that I didn't have a proper way of simultaneously affecting all of them- like if I wanted to affect a physics property of all enemy sprites at once. So far I created an empty array var enemySprites = [enemyType1]() at the begining of GameScene and have been appending the sprite instances to it instead of using addChild to draw them directly to the scene. However, I'm not able to simply loop through and draw them to screen with:

    for enemy in enemySprites{
        addChild(enemy)
    }

this bit of code is in the override func update(currentTime: CFTimeInterval) function, so maybe I'm just misplacing it? Any help on how to go about this would be great!

muZero
  • 948
  • 9
  • 22
  • It is simple. Give them a name and enumerate them by that name. Or keep them in array and loop through that array. Looping through array in order to add sprites to the scene should work, but you probably don't want to do that in update: method (which is executed 60 times per sec). Also you may check if sprite has a parent before you add them. – Whirlwind Aug 02 '16 at 19:42
  • @whirlwind Where should I do that then? The array is continually being updated as new enemies should be pushed and old(killed) ones should be removed, so I'm not sure when or when I should be looping through the array to either `addChild(newEnemy)` to the scene or to also effect all of them at once (like a physics property). – muZero Aug 02 '16 at 20:04
  • 1
    It depends of what you are trying to achieve. I don't really get what you want, but it seems that @AlessandroOrnano gave you the right directions. For example, if you want to affect all nodes when a certain contact occur, then you should enumerate nodes in didBeginContact or similar methods. Or if you want to do the same thing, but when user touches the screen, you will do that in touchesBegan. Or if you want to do this after certain animation is done, then do it in completion handler of that animation. It is really up to you. – Whirlwind Aug 02 '16 at 20:22
  • You certainly don't want to do this in update(), as this function is called automatically 60 times per second (if possible), so you'd try to add sprites you've already added, Plus you don't need to keep your sprites in an array as they are already in an array within the scene's node tree. Just give each type of sprite a name (via its .name property) and enumerate over all off them with enumerateChildNodeWithName in update() to move them. – Steve Ives Aug 02 '16 at 23:10
  • @steveIves I'm not sure how I would implement what I want using this. If my character runs out of lives, then all onscreen & future enemies with name "type1" should have their physics bodies affected in some way- like velocity=(0,0). I'm not sure what the block parameter is to achieve this? could you elaborate? I looked on the docs and am still confused. – muZero Aug 03 '16 at 17:38
  • It is likely that you are over thinking this, just put refs to the different textures into an array and update the array when you add or delete one (not in the update method). You can then easily for loop over all the N textures in a specific array. – MoDJ Aug 09 '16 at 20:34

2 Answers2

1

Instead of use update method, you could use a timer. From sources:

public class func scheduledTimerWithTimeInterval(ti: NSTimeInterval, target aTarget: AnyObject, selector aSelector: Selector, userInfo: AnyObject?, repeats yesOrNo: Bool) -> NSTimer

So if you follow Apple guide, it will be for example:

NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("spawnAlien:"), userInfo: myParameter, repeats: true)

func spawnAlien(timer : NSTimer) {
      if let myUserInfo = timer.userInfo {
         print(myUserInfo) // a parameters passed to help you to the alien creation
      }
      timer.invalidate()
}

BUT according to Whirlwind I agree with him and with LearnCocos2d work, sprite-kit don't work well with timers (as explained in the link by LearnCocos2d) and the better way, especially as you say you develop your first game, it's to use SKAction, a combination of actions to achieve the similar behavior obtained by NSTimer.

I've think about a function or an extension, let me know if it's work as expected:

extension SKAction {
   class func scheduledTimerWithTimeInterval(time:NSTimeInterval, selector: Selector, repeats:Bool)->SKAction {
      let call = SKAction.customActionWithDuration(0.0) { node, _ in
          node.performSelector(selector)
      }
      let wait = SKAction.waitForDuration(time)
      let seq = SKAction.sequence([wait,call])
      let callSelector = repeats ? SKAction.repeatActionForever(seq) : seq
      return callSelector
   } 
}

Usage:

let spawn = SKAction.scheduledTimerWithTimeInterval(time, selector: #selector(GenericArea.spawnAlien), repeats: true)
self.runAction(spawn,withKey: "spawnAlien")
Community
  • 1
  • 1
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • Does this help with modifying all enemys at once? – muZero Aug 02 '16 at 20:00
  • What do you mean for modifying all enemies at once? What's your purpose? – Alessandro Ornano Aug 02 '16 at 20:05
  • For example, I may have ten enemies on screen but then if I bump into one I lose the game. I want them to all still float around but they shouldn't be able to collide with me anymore and should move at a slower rate. To achieve this, any new enemy instances that I generate will be modified with an `if statement`. However, all enemies that were already on screen wont be effected. Had they always been in an array or something I should be able to effect all sprites. (This is just an example of what I mean). – muZero Aug 02 '16 at 20:09
  • Every time you create an alien, create also his physicsbody and his bitmasks, then handle collisions with didBeginContact – Alessandro Ornano Aug 02 '16 at 20:11
  • @AlessandroOrnano Is there any reason why you proposed NSTimer here, instead of SKActions / update: method ? Because NSTimer is not automatically paused when view, scene or a node is paused. So custom pause feature is required in many cases... – Whirlwind Aug 02 '16 at 20:24
  • @Whirlwind you can invalidate and reproposal a timer if you want to make a pause, and of course you must do a custom method to pause ( common thing). I think using update is dangerous to continuously spawn nodes, there are other ways, I've think to timer – Alessandro Ornano Aug 02 '16 at 20:31
  • Well, okay, it is true. But it is easier to use SKAction sequence to implement spawning, and do nothing when app goes to background or user receives phone call, or something like that, instead of doing what you said. Also take a look at this : http://stackoverflow.com/a/23978854/3402095 The part "Moreover you do not know at which point in the game loop they are executed..." is interesting. Haven't tested that closely, but personally, I think SKActions and update: method are safer. You can implement ticker using update method safely as well. It is just ugly way IMO :), but certainly safe. – Whirlwind Aug 02 '16 at 20:39
  • With SKAction you must pass to runBlock, I dont like the behaviour of runblock with other skactions, but it could be another way to do – Alessandro Ornano Aug 02 '16 at 20:52
  • @Whirlwind At the end I've decide to follow LearnCocos2D and your advice, maybe can make an easiest life to who develop on sprite-kit from recently. Thank you for your intervention. – Alessandro Ornano Aug 02 '16 at 22:28
1

Sam,

Here's some sample code to update enemies when your lives reach 0:

First, we set a property observer on the lives property so we can call a function when you lose all lives:

var lives = 3 {
    didSet {
        if lives == 0 {
        updateEnemies()
    }
}

And then a function to enumerate over all the enemies and change each one's velocity to (0, 0):

func update enemies() {
        enumerateChildNodesWithName("type1") {
            node, stop in
            let enemy = node as! SKSpriteNode
            enemy.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
        }
}
muZero
  • 948
  • 9
  • 22
Steve Ives
  • 7,894
  • 3
  • 24
  • 55
  • Thanks a lot, I actually stumbled upon something very similar to this. Is there anyway that you could explain the `!` at the end of `as!`? I understand that you're essentially type casting the variable enemy of type `node` into a type of `SKSpriteNode`, but I don't get why you needed the optional part `!`. Also, if there are any resources that you'd recommend for optionals because I've done research and just can't seem to fully get it. (Also did you mean `lives` instead of `ships`?) Anyway, thanks again! – muZero Aug 23 '16 at 15:13
  • @Sam Sorry it's lives, not ships (took it from my code). Let me look into the other stuff, but there lots of explanation about optionals on SO. – Steve Ives Aug 23 '16 at 15:21