Spawning a Spritekit Node at a Random Time

Spawn SKSpriteNode objects at random times

Do not use DispatchQueue Set up a repeating sequential action using wait(forDuration:withRange:) like you previously had.

https://developer.apple.com/documentation/spritekit/skaction/1417760-wait

First, create a generic node used to spawn obstacles, then attach this generic node to the scene.

Finally assign the repeating sequential action to this node.
Boom, you are done.

The reason why you want to assign it to a random node is because you want to be able to give your game the opportunity to stop generating obstacles, plus you alter the speed property to make the node generate nodes faster or slower.

You also want to detach the spawning/waiting from the moving/destroying, because as of right now, your code is confused. You are saying move the scene left for 2 seconds, then wait a random amount of time to spawn the next enemy, but I think you are trying to just spawn enemies on a time interval and move the enemy to the left.

Your scene code should look something like this

class GameScene : SKScene{
let obstacleGenerator = SKNode()
func didMove(to: view){


let wait = SKAction.wait(forDuration: 1, withRange: 0.4)
let spawn = SKAction.run({createObstacles()})

let sequence = SKAction.sequence([wait, spawn])
obstacleGenerator.run(SKAction.repeatForever(sequence))
addChild(obstacleGenerator)

}

func createObstacles() {
let obstaclesArray = ["obstacle_1", "obstacle_2", "obstacle_3", "obstacle_4", "obstacle_5"]
let randomObs = obstaclesArray.randomElement()
let selectedTexture = SKTexture(imageNamed: randomObs!)
obstacle = SKSpriteNode(imageNamed: randomObs!)

obstacle.position = CGPoint(x: scene!.size.width/2 + selectedTexture.size().width, y: -120)
obstacle.zPosition = -15
let body = SKPhysicsBody(texture: selectedTexture, size: selectedTexture.size())
body.affectedByGravity = true
body.allowsRotation = false
body.isDynamic = true
body.restitution = 0
body.categoryBitMask = obstacleCategory
body.collisionBitMask = floorCategory
body.contactTestBitMask = heroCategory
obstacle.physicsBody = body
addChild(obstacle!)
let moveLeft = SKAction.moveBy(x: -scene!.size.width - obstacle.size.width - 10, y: 0, duration: 2)
let seq = SKAction.sequence([moveLeft,SKAction.removeFromParent()])
obstacle.run(seq)
}
}

Now as for your spawning increasing with each reset, you never post how you are resetting. I am going to assume you never remove the previous action upon reset, and this is why your rate increases. It is always better to just create a new GameScene instance when doing a reset.

How to get different random delays in a SpriteKit sequence?


let wait = SKAction.wait(forDuration: getRandomDelay())
let sequence = SKAction.sequence([runObstSwitch, wait])

creates the wait action once, which is then used in the sequence,
so the same amount of idle time is spent between the runObstSwitch
actions.

If you want the idle time to be variable, use
wait(forDuration:withRange:) instead. For example with

let wait = SKAction.wait(forDuration: 1.5, withRange: 2.0)
let sequence = SKAction.sequence([runObstSwitch, wait])

the delay will be a random number between 1.5-2.0/2 = 0.5 and 1.5+2.0/2 = 2.5 seconds, varying for each execution.

Sprite Kit efficient random node spawn

The reason you're only seeing one position is that you're only creating one "enemy" and moving it around with SpawnEnemy. Since your sprites are all added to the same "enemy", they will all show up wherever your enemy was moved to last.

Let's chop down what your code is to the relevant bits:

var Enemy: SKNode!

override func didMoveToView(view: SKView) {

self.Enemy1 = SKNode()
addChild(Enemy1)
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform1.position.y + 15))
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform2.position.y + 15))

}

func SpawnEnemy(position: CGPoint!){

switch (arc4random_uniform(3)){

case 0:

Enemy.position = position
Enemy.addChild(Sprite1)

case 1:

Enemy.position = position
Enemy.addChild(Sprite2)

case 2:

Enemy.position = position
Enemy.addChild(Sprite3)

default:
return
}

}

So, in didMoveToView(), you create one Enemy node. Then, as you call SpawnEnemy, you do a bunch of stuff to add other sprites, but as far as Enemy is concerned, you're just changing its position. Since you're adding the sprites to the same node, when you move that Enemy node, the child sprites move with it, too.

You have two options to fix this. Either add your sprites to some higher-level node (like the scene), or create new "enemy" nodes for each of the sprites you intend to add. Either should work, but the best choice will depend on what you're trying to accomplish with them - the scope of that discussion is beyond what you're talking about here.

Spawn nodes at random times combing waitForDuration:withRange and runBlock: in an SKAction sequence

If I remember well, the waitForDuration:withRange: method works like so :
if you put a duration of 3 (seconds) and a range of 1 seconds the random value that you get will be between 2 and 4 seconds. That said, you should use those value for what you described : let wait = SKAction.waitForDuration(1.55, withRange: 1.45)

For the freeze problem, if you pasted your code correctly here the problem come with this line self.runAction(SKAction.repeatActionForever(spawn)) where you should instead be calling your sequence like this : self.runAction(SKAction.repeatActionForever(sequence)).

PS : At a certain point you still might want to control the amount of tears on screen at the same time.

Let me know if it helped.

Periodically add nodes to the scene with a random time interval in between

In SpriteKit you usually do that using SKAction and its waitForDuration(withRange:) method. Important part would be withRange parameter (quote from docs):

Each time the action is executed, the action computes a new random
value for the duration. The duration may vary in either direction by
up to half of the value of the durationRange parameter.

For example, if you have a wait duration of 3 seconds, and range parameter set to 2, you will get delays between 2 and 4 seconds.

So here is how you could do this:

class GameScene: SKScene, SKPhysicsContactDelegate {

var lastSpawnTime:Date?

override func didMove(to view: SKView) {

let wait = SKAction.wait(forDuration: 3, withRange: 4)

let block = SKAction.run {[unowned self] in
//Debug
let now = Date()

if let lastSpawnTime = self.lastSpawnTime {

let elapsed = now.timeIntervalSince(lastSpawnTime)

print("Sprite spawned after : \(elapsed)")
}
self.lastSpawnTime = now
//End Debug

let sprite = SKSpriteNode(color: .purple, size: CGSize(width: 50, height: 50))
self.addChild(sprite)
}

let sequence = SKAction.sequence([block, wait])
let loop = SKAction.repeat(sequence, count: 10)

run(loop, withKey: "aKey")
}
}

And you will see in the console something like:

Spawning after : 1.0426310300827
Spawning after : 1.51278495788574
Spawning after : 3.98082602024078
Spawning after : 2.83276098966599
Spawning after : 3.16581499576569
Spawning after : 1.84182900190353
Spawning after : 1.21904700994492
Spawning after : 3.69742399454117
Spawning after : 3.72463399171829

Best practice for spawning many time the same node in SpriteKit (swift)

Here are some basics that you should know:

What you can do (especially if you are experiencing performance problems) is to:

  • use texture atlases. This way you are reducing number of cpu draw calls required to render a sprites.

  • cache different resources like emitters (or sounds). This way you are keep them in memory and you don't need to read them from disk.
    Search Apple's Adventure game to see how you can cache the resources. Basically, you preload everything before gameplay using the completion handler.

One way you could go is to pre-create a bunch of nodes and use them until game is finished. This way you won't re-create the SKSpriteNodes many times (which sometimes can cause noticeable lag). Here you can read about that.

Important:

  • Don't take simulator results into account, because those are not realistic. If you are interested in real information about performance you should test on device.

Hope this helps a bit.

Swift: spawn enemies at random times within a range?

Make use of arc4random which returns a random value between 0 to your number

spawndelay = arc4random_uniform(UInt32(yournumber))

For Double from here

func random(#lower: Double, upper: Double) -> Double {
let r = Double(arc4random(UInt64)) / Double(UInt64.max)
return (r * (upper - lower)) + lower
}

Spawn a node at random coordinates in SpriteKit


Sometimes the node appears on the screen, sometimes it doesn't. I guess it's going out of bounds. How can I fix that? The orientation of the phone is landscape, so do the coordinates change?

It's because the height and width used are wrong.

In your code, [MyScene sceneWithSize:skView.bounds.size] will always return size in portrait orientation, even though it's landscape on the device / emulator, it's happen because you put it on viewDidLoad.

If you want to get the size of current orientation (landscape), place the code in viewDidLayoutSubviews, not in viewDidLoad.

Go to "ViewController.m", replace the viewDidLoad to viewDidLayoutSubviews.

- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];

SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;

SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;

[skView presentScene:scene];
}

Documentation link: https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instm/UIViewController/viewDidLayoutSubviews



Related Topics



Leave a reply



Submit