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, usewait(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
How to Open File Dialog with Swiftui on Platform "Uikit for MAC"
Swift Standard Documentation Comment
How to Convert a Swift String to Cfstring
Evaluate Bool Property of Optional Object in If Statement
How to Change Colour of the Certain Words in Label - Swift 3
How to Restrict the Type That a Function Throws in Swift
Macos/Swift Capture Audio with Avcapturesession
Drawing a 3D Arc and Helix in Scenekit
Error: Unable to Spawn Process (Argument List Too Long) in Xcode Build
How to Utilize Nslock to Prevent a Function from Firing Twice
Swift Error: Guard Body Must Not Fall Through
How to Get Date and Time to Show a Clock in Uilabel
How to Create String Split Extension with Regex in Swift
Swift - 'Bool' Is Not a Subtype of 'Void'
Hstack with Sf Symbols Image Not Aligned Centered
Attaching Audiounit Effects to Scnaudiosource Nodes in Scenekit