1

I have a problem with my enemies. I made 5 of them and each one is coming to a scene independently of the other. But thing is - they are pretty much the same except for looks. They likes to spawn in groups, one on another because they have one random earing mechanism for everyone. I tried to use SKConstraint to make a gap between them, but it's not working for me.

So I thought about different approach: I want to use something, like an empty node to represent enemy before it shows up on a scene (they are like lower pipes in flappy bird coming from the right side to the left). And before they will appear on screen I want my game to randomly choose between 5 nodes of them and perform correct animation. Because of that my group-appearing issue would be solved.

How could I do it?

For now I use their node. I'll provide code if it helps:

var robot = SKSpriteNode()
let robotAtlas = SKTextureAtlas(named: "robot")
var robotArray = [SKTexture]()

robotArray.append(robotAtlas.textureNamed("robot0"));
robotArray.append(robotAtlas.textureNamed("robot1"));

then I'm applying physicBodies to them

robot = SKSpriteNode(texture: robotArray[0]);
robot.position = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMidY(self.frame) - 138)
self.robot.name = "robot"
self.addChild(robot)

How can I do that or maybe there are other ways to do such thing?

For now, that's my scheme: This is the random function:

func random() -> UInt32 {
var range = UInt32(60)..<UInt32(200)
return range.startIndex + arc4random_uniform(range.endIndex - range.startIndex + 1)}

I have a custom class for appearance:

class EnemyAppear {
var nowAppear = false
var waitToAppear = UInt32(0)
var currentInterval = UInt32(0)
init(nowAppear:Bool, waitToAppear:UInt32, currentInterval:UInt32) {
self.nowAppear = nowAppear
self.waitToAppear = waitToAppear
self.currentInterval = currentInterval  }


func shouldRun() -> Bool {
return self.appearInterval > self.waitToAppear  }

Then I have a status to track enemies:

var enemyStatus:Dictionary<String,EnemyAppear> = [:]


enemyStatus["robot"] = EnemyAppear(nowAppear: false, waitToAppear: random(), currentInterval: UInt32(0))
enemyStatus["drone"] = EnemyAppear(nowAppear: false, waitToAppear: random(), currentInterval: UInt32(0))

And in Update function I have function that moves them:

func enemyRun() {
    for(enemy, enemyAppear) in self.enemyStatus {
    var thisPet = self.childNodeWithName(enemy)!
        if enemyAppear.shouldRun() {
            enemyAppear.waitToAppear = random()
            enemyAppear.currentInterval = 0
            enemyAppear.nowAppear = true
        }

        if enemyAppear.nowAppear {
            if thisPet.position.x > petMaxX {
                thisPet.position.x -= CGFloat(self.groundSpeed)
            }else {
                thisPet.position.x = self.originalPetPositionX
                enemyAppear.nowAppear = false
                self.score++
                self.scoreText.text = String(self.score)
            }
        }
    }

All I need is to set distance between enemies.

TimurTest
  • 45
  • 7
  • 1
    I've read twice your question and still not sure what you are trying to achieve. Are you trying to move enemies in formation, or to spawn them randomly offscreen and move them individually ? – Whirlwind Jul 10 '15 at 12:03
  • Create an empty/clear SKSpriteNode. – sangony Jul 10 '15 at 12:14
  • @Whirlwind By now my program is spawning 5 different enemies, 5 different nodes. I want it to spawn only 1 enemy, you know, like skeleton. And I want it to choose from 5 nodes, like from five skins, and put one on that skeleton. Think of a car factory for example. At first car has no color. And there are 5 different colors to paint that car. I want it to randomly choose that color and paint it. Does it make sense? – TimurTest Jul 10 '15 at 14:38
  • @TimurTest About spawning enemies, I am not sure how you spawn them currently but...Try to use SKAction sequence (which repeats forever). Inside that sequence first action would be SKAction waitForDuration which will wait for a certain period of time before next enemy is spawned, and the second action would be SKAction runBlock which will create an enemy and add it to the scene. – Whirlwind Jul 10 '15 at 17:10

1 Answers1

2

I think you are complicating things unnecessary or I am still misunderstanding you :( but here are some suggestions. If you just need to spawn random enemies, you can choose between few ways:

1. Making custom Robot class

You can make a class called Robot and choose different texture randomly . To accomplish randomizing textures (from a texture atlas or an array of textures) you can use arc4random() method.

Example of usage (pseudo code):

Robot.init(texture:someRandomTexture) //note that texture is a type of SKTexture, it's not a string

Robot class in this case would be subclass of SKSpriteNode and you can initialize it with a texture through constructor(like from example above), or you can move all that texture choosing logic inside constructor (init method). It's up to you.

Example of usage:

Robot.init()

The whole logic is called inside init method which calls SKSpriteNode's initWithTexture method

If you really need that your robot have a skeleton and a skin and you can't use single texture for each type of robot then you can make Robot class which is subclass of SKNode (empty container).

In that case Robot class should have two variables, SKSpriteNode skeleton and SKSpriteNode skin. Skin variable in this case will be chosen randomly. Skeleton and skin nodes should be added as children to self (self in this case is Robot class which is SKNode). SKNode does not have a visual representation, but it has position property, so when you move SKNode, you move its children as well.

2. Use SKSpriteNode

If you decide not to have a Robot class, and just use SKSpriteNode, the story is the same. I would go with a different texture for each type of enemy, but if that is not manageable you can use multiple SKSpriteNodes for each robot.

So, you should make method which will return an enemy, which is SKSpriteNode. Inside that method you will create SKSpriteNode with skeleton texture, and after that you will add skin as a child, setup skin's zPosition to be higher than skeleton's zPosition, create physics body and return that node.

One useful thing (if you just need to change a color of a sprite) is that you can easily colorize nodes programatically (colorize textures)

EDIT:

Simple example of using action sequence to spawn enemies after random duration of time :

import SpriteKit

class GameScene: SKScene {

    var enemies: NSMutableArray = []
    var textures = [SKTexture]()

    let debugLabel = SKLabelNode(fontNamed: "Arial-BoldMT")

    override func didMoveToView(view: SKView) {

        /* Setup your scene here */

        debugLabel.fontColor = SKColor.purpleColor()
        debugLabel.fontSize = 20.0
        debugLabel.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
        self.addChild(debugLabel)


        let atlas = SKTextureAtlas(named: "enemies")

        //Fill the array with textures to use them later
        //Not needed IMO, but suitable to show you how to pick a random element from an array
        for var i = 1; i<=atlas.textureNames.count; i++ {

            textures.append(atlas.textureNamed( "enemy\(i)"))

        }


         debugLabel.text = "Enemies count : \(enemies.count), Nodes count : \(self.children.count)"

        spawnEnemiesWithDelay(3)

    }


    //This method is just for creating a sprite
    func createRandomEnemy() ->SKSpriteNode{

        let rand = Int(arc4random_uniform(UInt32(textures.count)))

        let enemy =  SKSpriteNode(texture: textures[rand])

        //setup here enemy physics body

        return enemy

    }

    //This method spawns enemies after random period of time. You can stop this by removing an action key
    func spawnEnemiesWithDelay(delay: NSTimeInterval){



        let delay = SKAction.waitForDuration(delay, withRange:3) //The duration may vary in either direction by up to half of the value of the durationRange parameter. Which means duration can vary either plus or minus 1.5 sec



        let block = SKAction.runBlock({

            let enemy = self.createRandomEnemy()

            enemy.position = CGPoint(x: self.frame.size.width+enemy.size.width/2, y:100)

            self.addChild(enemy)

            self.enemies.addObject(enemy) // Store reference to enemy if needed

            let move = SKAction.moveTo(CGPoint(x: -enemy.size.width/2, y: 100), duration: 5)

            let moveAndRemove = SKAction.sequence([move, SKAction.runBlock({
                enemy.removeFromParent()


                self.enemies.removeObjectIdenticalTo(enemy as AnyObject)

            })]) //remove enemy when offscreen



            enemy.runAction(moveAndRemove, withKey: "moving")

        })

        let sequence = SKAction.sequence([delay,block])

        self.runAction(SKAction.repeatActionForever( sequence), withKey: "spawning")

    }

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */

       debugLabel.text = "Enemies count : \(enemies.count), Nodes count : \(self.children.count)"

    }
}

If your scene and view are initialized correctly, you can make enemies.atlas folder with few different textures and copy&paste this code to try it. Hope this helps a bit.

Community
  • 1
  • 1
Whirlwind
  • 14,286
  • 11
  • 68
  • 157
  • Man, thats so great advices! Thank you for your work, I wish I could mark this as the best answer multiple times! I think I would go with your second suggestion for now, but thanks to this answer I always have a backup plan now :) – TimurTest Jul 11 '15 at 05:57
  • I'm sorry to ask you again but maybe you know the answer. What if I would use regular SKSpriteNodes as you said? All I want is to leave a space between my enemies. But my random function is deciding whether it should run another enemy on a scene by time that has passed from launching that enemy last time. And I think would be better if it would decide it by time that has passed from launching ANY enemy. I've updated my question, maybe it could be done that way? – TimurTest Jul 11 '15 at 12:45
  • @TimurTest Have you tried to spawn enemies with action sequence like suggested (one after another)? Instead of your class you can start spawning sequence with a key, remove it when needed. I can't really help you with your swift code because I use just Obj-C currently. – Whirlwind Jul 11 '15 at 13:07
  • I haven't tried it yet. Using `waitForDuration` means that I wouldn't be able to use random? Because I need to put there specific time, game most likely would be dull. Player will know when another enemy will appear and so on. – TimurTest Jul 11 '15 at 14:25
  • You can use waitForDuration: withRange https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKAction_Ref/index.html#//apple_ref/occ/clm/SKAction/waitForDuration:withRange: – Whirlwind Jul 11 '15 at 14:34
  • I'm trying to implement that. Have I understood you right? Your suggestion is to make 5 SKActions with different intervals in `waitForDuration`, one for each enemy? Or It should be one global Action for all of the enemies? I'm trying to implement that global one and ran into a problem: I don't know how to pick up an enemy from the array of enemies. I've [tried random](http://stackoverflow.com/questions/31376464/how-to-randomly-choose-a-skspritenode) and did not succeed. – TimurTest Jul 13 '15 at 09:42
  • @TimurTest It should be one action which spawns enemies after delay. By spawning I meant create completely new enemy, not using one from the array. After you spawn an enemy, if needed you can add it to the array of enemies. By the way, how much enemies of the same type you can have on screen at the same time ? If I find some time, I will try to write you an simple example today... – Whirlwind Jul 13 '15 at 14:21
  • Well, I think 2 is more than enough. It's a vertical orientated simple game where hero should jump over enemies. Some kind of a flappy bird maybe but instead of pipes it has enemies size of a hero or a little bit bigger. And all of them are standing on the ground. If you could do that - that would be super awesome, because no matter what I'm trying now – I'm stuck with that problem :( Thank you! – TimurTest Jul 13 '15 at 14:52
  • @TimurTest Okay now we are progressing :) How do you make an impression of hero movement ? Do you plan to use parallax effect and just move hero up and down. Another question just to confirm... Enemies should be spawned off screen and then move in player's direction (in order to make impression that hero is moving forward), right ? – Whirlwind Jul 13 '15 at 15:15
  • @TimurTest Also don't you think it would be better idea to make a game in Landscape mode instead of Portrait ? You said it's a vertical oriented game (and enemies coming from right and going to the left) which I guess applies to portrait mode ? You will get more space if you use Landscape mode for this kind of setup. – Whirlwind Jul 13 '15 at 15:28
  • Yes, you are absolutely right about enemies, I have a velocityY function and gravity to imitate jumping. About landscape mode – yeah, I thought about it. But judging by myself, it's not very comfortable to hold iPhone horizontally. Especially not at home. And I think that simple games like this are often played at a bus, waiting in line and such, so I thought that portrait orientation is a slightly better choice. If you are interested - you can find [full code here](https://www.dropbox.com/sh/38ikoko8nvdbydi/AABOUy-bJ39sQfFDFQgcGYfWa?dl=0) – TimurTest Jul 13 '15 at 16:22
  • @TimurTest I see what you are trying to do, but I can't really modify your code in order to work because I don't have so much time to go carefully through it :) and find out how everything should work. (also I can't run it because of missing resources and a few errors) But, I've made simple example as promised to show you how you can spawn, move and remove enemies when offscreen. Check out my edit. – Whirlwind Jul 13 '15 at 18:04
  • I'm not trying to ask you to edit my code, really :D I just thought that it can speak for itself. Due to my poor english knowledge I'm afraid to say something wrong and you could misunderstand me. Anyway, thank you very much for that example! I don't understand it yet but I'm motivated to sort it out. Why we need `required init` line though? I don't know what is it doing. And thank you again! – TimurTest Jul 13 '15 at 19:13
  • @TimurTest Okay, not a problem, if it was runnable project without compiling errors I would probably try to modify it and help in that way :) You can delete that initWithCoder , it's not needed for this example. I forgot to delete that. If you are interested about it, you should search about NSCoding protocol because that's a whole another topic (not suitable for comments) ...There are some post explaining about when Swift forces us to implement initWithCoder. – Whirlwind Jul 13 '15 at 19:48
  • Oh I see. One thing seems illogical to me: Why I need to setup physics body of enemy in `createRandomEnemy` function? – TimurTest Jul 13 '15 at 20:47
  • Because in my example you are creating new enemy node every time, move it, and remove it when its offscreen. This way you can have multiple enemies of same type on screen. If you pre-create nodes (and hold them in array in order to access random enemy), you can't add them more than once enemy of same type on sceen (node can have only one parent), thus, you have to make copies of them. It can be done either way, but I've chosen to re-crate them. – Whirlwind Jul 13 '15 at 20:54
  • @TimurTest And actually why do you think it is ilogical to create physics body at the moment of creating an enemy sprite? Each enemy should have physics body, right ? So I am creating its physics body at the moment of sprite creation... – Whirlwind Jul 14 '15 at 05:14
  • `let atlas = SKTextureAtlas(named: "enemies") //Fill the array with textures to use them later`. What array? There is only an atlas as I see. I tried to copy paste your code to look at it, but on that line `let enemy = SKSpriteNode(texture: textures[rand])` it gives me "Bad instruction" error. I attached an atlas to a project with some textures to it and named it "enemies" – TimurTest Jul 22 '15 at 14:47
  • @TimurTest An array of SKTexture objects called "textures", defined as a property inside GameScene class. On my side, I don't get any error and everything works fine. Textures in atlas are named enemy1,enemy2, enemy3 etc. All images inside atlas have 2x suffix. Are you sure that you have named images correctly, and added atlas appropriately ? When adding an atlas you should name folder enemies.atlas . Also when adding that folder in Xcode it should appear as blue, not a yellow one. – Whirlwind Jul 22 '15 at 15:13