swift call function multiple times inside update - swift

I create a game via SpriteKit. in the game every few second a ball spawn to the screen (via function) and the player has to blow them up. now, I want to check the player level via his score so if the score is bigger than 10 the spawnBall function will be executed twice (so 2 ball will spawn on the screen) an so on. I tried to to it via the update fun (that will "read" the player score and depends on the score will call the spawnBall function). Unfortunately when I do it the screen is spawn with million (or so) balls in few seconds (as I said I want it to call the function every few seconds and increase the call while the score is X). I really don't have any idea how to do it.
here is my code:
override func update(_ currentTime: CFTimeInterval) {
if (self.score <= 10){
spawnBalls()
}
if (self.score > 10 && self.score <= 20){
spawnBalls()
spawnBalls()
}
if (self.score > 20){
spawnBalls()
spawnBalls()
spawnBalls()
}
if (self.subscore == 3) {
_ = randomBallColorToBlow()
self.subscore = 0
}
}
func spawnBalls() {
let wait = SKAction.wait(forDuration: 1)
let action = SKAction.run {
self.createBall()
}
run(SKAction.repeatForever((SKAction.sequence([wait, action]))))
}
how can I do it without using the update function??

you are calling spawn balls 60 times a second by calling it in your update func.
try just checking if a certain requirement is met to upgrade to a higher spawn rate in your update but keep the calls out of the update func.
private var upgradedToLevel2 = false
private var upgradedToLevel3 = false
//called somewhere probably in a start game func
spawnBalls(duration: 1.0)
override func update(_ currentTime: CFTimeInterval) {
if (self.score > 10 && self.score <= 20) && !upgradedToLevel2 {
//this prevents the if loop from running more than once
upgradedToLevel2 = true
self.removeAction(forKey: "spawn")
spawnBalls(duration: 0.5)
}
if (self.score > 20) && !upgradedToLevel3 {
//this prevents the if loop from running more than once
upgradedToLevel3 = true
spawnBalls(duration: 0.33)
}
}
func spawnBalls(duration: Double) {
let wait = SKAction.wait(forDuration: duration)
let action = SKAction.run { self.createBall() }
let repeater = SKAction.repeatForever(SKAction.sequence([wait, action]))
run(repeater, withKey: "spawn")
}

As stated, you are spawning your balls multiple times and need to break it up. I would recommend keeping track of level using an Int instead of a bool to be able to handle an "infinite" amount of level ups without making an "infinite" amount of boolean variables
private var nextLevel = 0 //Level 0 allows us to spawn a ball on startup, so no need to call spawnBalls anywhere else
override func update(_ currentTime: CFTimeInterval) {
if (self.score > 10 * nextLevel){
self.removeAction(forKey: "spawn") //this prevents the if loop from running more than once
nextLevel += 1
spawnBalls(count:nextLevel,forDuration:1) //You can change the 1 here if you want to spawn balls at a faster speed, I would recommend a variable that uses nextLevel in a forumula
}
}
func spawnBalls(count:Int, forDuration duration:TimeInterval) {
let range = 0..<count
let wait = SKAction.wait(forDuration: duration)
let action = SKAction.run {range.forEach{self.createBall()}}
let repeater = SKAction.repeatForever(SKAction.sequence([wait, action]))
removeAction(forKey:"spawn")
run(repeater, withKey: "spawn")
}

Related

How to detect a Score Node that is moving Fast

Updated my Code below.
I am trying to detect an SKNode that is used to track the score. It is the child of the obstacle that moves right to left on the screen so it moves with the obstacle Node.
The problem is that I can't detect the collision with the score node after a certain speed. In my game the speed increases every time the score node is contacted and it works up to a certain speed and then the collision with the score node is not detected.
I have tried using the usesPreciseCollisionDetection = true but get the same problem.
I need to increase the speed of the obstacles and reduce the wait time between spawning the next obstacle.
what ends up happing with some of my previous attempts, where I increase the speed every time the score node is contacted, is the speed increases but the spawning time doesn't decrease so it creates a gap because the obstacle moves fast enough and goes off screen and the next one isn't spawned yet.
this is the code for the increase speed when contact score node
var speedOfObstacle = 3.0
var speedOfSpawn = 1.0
override func didMove(to view: SKView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(self.addObstacle),
SKAction.wait(forDuration: self.speedOfSpawn)
])
))
}
}
//** The above SKAction.wait(forDuration: self.speedOfSpawn) determines the spawn time or delay from repeating this sequence. I need to decrease this speedofSpawn But it doesn't change from initial amount of 1.0
func addObstacle() {
//Obstacle Code. Implement the SpeedofObstale.
}
func didBegin(_ contact: SKPhysicsContact) {
var dogBody: SKPhysicsBody
var otherBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
dogBody = contact.bodyA
otherBody = contact.bodyB
} else {
dogBody = contact.bodyB
otherBody = contact.bodyA
}
if dogBody.categoryBitMask == dogSpriteCategory && otherBody.categoryBitMask == obstacleCategory {
// print("dog hit obstacle")
} else if dogBody.categoryBitMask == dogSpriteCategory && otherBody.categoryBitMask == scoreCategory {
print("dog hit score")
score += 1
scoreLabelNode.text = String(score)
if speedOfObstacle >= 0.7 {
speedOfObstacle -= 0.03
print("speed of obstacle is \(speedOfObstacle)")
speedOfSpawn -= 0.01
print("speed of spawn \(speedOfSpawn)")
}
}
}
with the contact score node code the speedOfObstacle decreases from the 3.0 but the wait time for speedOfSpawn does not change from the initial amount of 1.0 .
The speedOfObstacle is used in the addObstacle func, didBegin(_contact), and the viewDidLoad. . The speedOfSpawn is used in the didBegin(_contact) and the viewDidLoad.
Any help on how I can either detect the score node collision at high speed or any way to increase the speed of the obstacle and the frequency of spawning the obstacle would be appreciated.
Thank you,
Updated Code. As answered by bg2b.
I have changed my code and below is what I changed.
enter code here
//replaced the repeat for ever code in view did load with
override func didMove(to view: SKView) {
let initialDelay = 1.0
run(SKAction.sequence([.wait(forDuration: initialDelay), .run { self.addObstacle() }]))
}
// then added this in the addObstacle func
//obstacle physics
//obstacle movement
addChild(obstacle)
let nextDelay = speedOfSpawn
run(SKAction.sequence([.wait(forDuration: nextDelay), .run { self.addObstacle() }]))
and put this in the did begin contact. // same as above just two if statements instead of one.
the speedOfSpawn and speedOfObstacle are declared at the top.
score += 1
scoreLabelNode.text = String(score)
scoreLabelNode.physicsBody?.usesPreciseCollisionDetection = true
if speedOfObstacle >= 1.0 {
speedOfObstacle -= 0.09
print("speed of obstacle is \(speedOfObstacle)")
}
if speedOfSpawn >= 0.40 {
speedOfSpawn -= 0.03
print("speed of spawn \(speedOfSpawn)")
}
I'm not sure about the collision issues, but for the spawning, you're making an action in didMove and then not altering that action. That is, when you write
SKAction.wait(forDuration: self.speedOfSpawn)
you're creating an SKAction that waits for a number of seconds given by the expression self.speedOfSpawn. But if you then change speedOfSpawn, the constructed action isn't made again. If you want to do something with a varying delay, one way is to not do a repeatForever but to put the scheduling of each repetition within the action that is repeating. E.g.,
func spawn() {
// spawn an enemy
...
// schedule next spawn
let nextDelay = ... // some computation of when to spawn next
run(SKAction.sequence([.wait(forDuration: nextDelay), .run { self.spawn() }]))
}
override func didMove(to view: SKView) {
let initialDelay = 1.0
run(SKAction.sequence([.wait(forDuration: initialDelay), .run { self.spawn() }]))
}
Here is another way you can control your spawning. It eliminates the need for a "speedOfSpawn" variable, and instead use the internal timing mechanics. You basically are making the spawn action go faster and faster.
override func didMove(to view: SKView) {
self.run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(self.addObstacle),
SKAction.wait(forDuration: 1.0)
])
,forKey:"Spawning")
}
func didBegin(_ contact: SKPhysicsContact) {
var dogBody: SKPhysicsBody
var otherBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
dogBody = contact.bodyA
otherBody = contact.bodyB
} else {
dogBody = contact.bodyB
otherBody = contact.bodyA
}
if dogBody.categoryBitMask == dogSpriteCategory && otherBody.categoryBitMask == obstacleCategory {
// print("dog hit obstacle")
} else if dogBody.categoryBitMask == dogSpriteCategory && otherBody.categoryBitMask == scoreCategory {
print("dog hit score")
score += 1
scoreLabelNode.text = String(score)
if speedOfObstacle >= 0.7 {
speedOfObstacle -= 0.03
print("speed of obstacle is \(speedOfObstacle)")
self.action(forKey:"Spawning")?.speed += 0.01
}
}
}
The problem with collision is you are essentially playing God when you use SKAction's.
I would recommend using the physics velocity to move your object, not SKActions.
You are also using precision on your scoreLabelNode ..... I believe you want to use the parent's node, since that should have the physics body on it.

How to add and remove a SKErmitterNode within a sequence?

I'm adding a SKErmitterNode from the update() function. I want to remove the SKErmitterNode after 2 seconds, therefore I made a sequence but in the sequence, I can´t add Nodes. And if I add the Node outside of the sequence it gets added over and over again(because I´m doing all of this in the update function) Does someone know a better way to do this?
Here is my Code from the update function:
override func update(_ currentTime: CFTimeInterval) {
if player.position.y <= player.size.height / 2{
self.player.removeFromParent()
if let particles = SKEmitterNode(fileNamed: "MyParticle.sks") {
particles.position = player.position
let addParticle = addChild(particles)
let wait = SKAction.wait(forDuration: 2.0)
let removeParticle = SKAction.removeFromParent()
let particleSequence = SKAction.sequence([addParticle, wait, removeParticle]) //Error ->Cannot convert value of type 'Void' to expected element type 'SKAction'
self.run(SKAction.run(particleSequence))
}
}
So what I recommend for you to do is to create a function like the following
func myExplosion (explosionPosition: CGPoint){
let explosion = SKEmitterNode(fileNamed: "MyParticle")// borrowed this from you
explosion?.position = explosionPosition
explosion?.zPosition = 3
self.addChild(explosion!)
self.run(SKAction.wait(forDuration: 2)){//you can always change the duration to whatever you want
explosion?.removeFromParent()
}
}
then when it is time to use this function, use it like so
myExplosion(explosionPosition: player.position)
Hope this can help you out.

Get node last position

It's the third time I'm looking for help in this question.
I have a class name BallNode of kind SKShapeNode. Inside my code I also have a function the spawn ball every 3 second from the top side of the screen. Now, I want to set a function that locate the ball position every 1 second, and so, if the ball.position.y > 200 to print a message to the console.
The purpose of this is that if any ball will be at this position (not while it's falling down) the I will call another function. I tried to do it via SKAction, update(_ currentTime: CFTimeInterval), Timer but I didn't succeed and I really have no idea what to do...
update - my current code:
var timeType1: CFTimeInterval = 0.0
var timeType2: CFTimeInterval = 2.0
override func update(_ currentTime: CFTimeInterval) {
if (currentTime - timeType1 > timeType2){
print("time")
self.checkBallsPosition()
self.timeType1 = currentTime
}
self.enumerateChildNodes(withName: "color.BallNode") { node, _ in
self.checkBallsPosition()
}
}
func checkBallsPosition() {
self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in
let x = self.createTopBorder()
x.isHidden = true
let wait2 = SKAction.wait(forDuration: 1)
let action2 = SKAction.run {
let position = Double(node.position.y)
if position < 200 {
}
else if position > 200 {
print("bolbo")
node.removeFromParent()
}
}
self.run(SKAction.sequence([wait2,action2]))
}
}
thats what I try do to so far, as I said the problem is that I want to get the ball last position. because the ball fall down the screen the last position should be when it touch the bottom border of the screen or when it touches another ball. if I set it at update I get the ball position every frame or (as I did) every second - but not the last. another problem is that the ball position can always change depends on another balls (when collision occurs).
update #2 - another functions:
func spawnBalls() {
let wait = SKAction.wait(forDuration: 3)
let action = SKAction.run {
self.createBall()
}
run(SKAction.repeatForever((SKAction.sequence([wait, action]))))
}
func createBall(){
let ball = BallNode(radius: 65)
print(ball.Name)
print(ball._subName!)
ball.position.y = ((frame.size.height) - 200)
let ballXPosition = arc4random_uniform(UInt32(frame.size.width))
ball.position.x = CGFloat(ballXPosition)
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
ball.physicsBody?.collisionBitMask = PhysicsCategory.ball
ball.physicsBody?.contactTestBitMask = PhysicsCategory.topBorder
ball.delegate = self
addChild(ball)
}
You did not post the entire code you are using. Not knowing how you are spawning your balls, how is the hierarchy and how you are moving them, I can't reproduce it here.
I am not sure if I understand it right, but I believe it might be simpler than what you are doing. See if the code below helps you, I am making the balls fall with SKAction (you can do it with physics also), and checking when they go below zero, when I remove them.
private let ballSpeed : CGFloat = 400
private let ballRadius : CGFloat = 10
private func spawnBall(atPoint pos : CGPoint){
let b = SKShapeNode(circleOfRadius: ballRadius)
b.name = "ball"
b.position = pos
addChild(b)
b.run(SKAction.moveTo(y: -size.height, duration: TimeInterval((pos.y+size.height)/400)))
}
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
enumerateChildNodes(withName: "ball") { (node, _) in
if node.position.y < 0 {
node.removeFromParent()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let pos = touch.location(in: self)
spawnBall(atPoint: pos)
}
}

Smooth animation with timer and loop in iOS app

I have ViewController with stars rating that looks like this (except that there are 10 stars)
When user opens ViewController for some object that have no rating I want to point user's attention to this stars with very simple way: animate stars highlighting (you could see such behaviour on some ads in real world when each letter is highlighted one after another).
One star highlighted
Two stars highlighted
Three stars highlighted
......
Turn off all of them
So this is the way how I am doing it
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
func ratingStarsAnimation() {
for i in 1...11 {
var timer : Double = 0.6 + Double(i)*0.12
delayWithSeconds(timer) {
ratingStars.rating = (i < 10) ? Double(i) : 0
}
}
}
What is going on here? I have function called delayWithSeconds that delays action and I use this function to delay each star highlighting. And 0.6 is initial delay before animation begins. After all stars are highlighted - last step is to turn off highlighting of all stars.
This code works but I can't say that it is smooth.
My questions are:
How can I change 0.6 + Double(i)*0.12 to get smooth animation feel?
I think that my solution with delays is not good - how can I solve smooth stars highlighting task better?
Have a look at the CADisplaylink class. Its a specialized timer that is linked to the refresh rate of the screen, on iOS this is 60fps.
It's the backbone of many 3rd party animation libraries.
Usage example:
var displayLink: CADisplayLink?
let start: Double = 0
let end: Double = 10
let duration: CFTimeInterval = 5 // seconds
var startTime: CFTimeInterval = 0
let ratingStars = RatingView()
func create() {
displayLink = CADisplayLink(target: self, selector: #selector(tick))
displayLink?.add(to: .main, forMode: .defaultRunLoopMode)
}
func tick() {
guard let link = displayLink else {
cleanup()
return
}
if startTime == 0 { // first tick
startTime = link.timestamp
return
}
let maxTime = startTime + duration
let currentTime = link.timestamp
guard currentTime < maxTime else {
finish()
return
}
// Add math here to ease the animation
let progress = (currentTime - startTime) / duration
let progressInterval = (end - start) * Double(progress)
// get value =~ 0...10
let normalizedProgress = start + progressInterval
ratingStars.rating = normalizedProgress
}
func finish() {
ratingStars.rating = 0
cleanup()
}
func cleanup() {
displayLink?.remove(from: .main, forMode: .defaultRunLoopMode)
displayLink = nil
startTime = 0
}
As a start this will allow your animation to be smoother. You will still need to add some trigonometry if you want to add easing but that shouldn't be too difficult.
CADisplaylink:
https://developer.apple.com/reference/quartzcore/cadisplaylink
Easing curves: http://gizma.com/easing/

How to properly calculate 1 second with deltaTime in Swift

I'm trying to calculate an elapsed second in deltaTime but I'm not sure how to do it, because my deltaTime constantly prints 0.0166 or 0.0167.
Here is my code:
override func update(_ currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
deltaTime = currentTime - lastTime
lastTime = currentTime
How do I make it so I can squeeze some logic in here to run every second?
EDIT: I was able to come up with the following, but is there a better way?
deltaTimeTemp += deltaTime
if (deltaTimeTemp >= 1.0) {
print(deltaTimeTemp)
deltaTimeTemp = 0
}
I always use SKActions for this type of thing: (written in swift 3)
let wait = SKAction.wait(forDuration: 1.0)
let spawnSomething = SKAction.run {
//code to spawn whatever you need
}
let repeatSpawnAction = SKAction.repeatForever(SKAction.sequence([wait, spawnSomething]))
self.run(repeatSpawnAction)
If you really only care about a 1 second interval then you should not be storing the delta for every frame. Just store the start time and calculate the elapsed time each frame. When the elapsed time exceeds your 1 second interval then you reset the start time to now.
override func update(_ currentTime: CFTimeInterval) {
let elpasedTimeSinceLastupdate = currentTime - startTime
//Check if enough time has elapsed otherwise there is nothing to do
guard elpasedTimeSinceLastupdate > requiredTimeIntervale else {
return
}
startTime = currentTime
// Do stuff here
}
I ideally you want more than 1 timer, so then you would need to maintain an array of timers and a table of intervals and blocks to call. This starts to get very complicated and really you should probably just use the built in block Timer in iOS 10, which is much more straight forward:
_ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
//Do stuff every second here
}
currentTime: number = 0;
update(delta) {
this.time += delta / 1000;
GameCurrentData.time = Math.floor(this.time);
if(this.currentTime !== GameCurrentData.time){
Globals.emitter?.Call?.("perSecond");
this.currentTime = GameCurrentData.time;
}
}