Varying value inside runAction() not changing - swift

I have a run action loop that spawns an enemy then waits and spawns another. The intention is that the higher the score the quicker the enemies spawn.
But the current code has the enemies spawn at the same rate no matter what. I update the rate in the override func update(currentTime: NSTimeInterval) method all the time, so I don't know what's wrong.
override func update(currentTime: NSTimeInterval){
spawnRate = 2.0-(0.1*(Double)(((Double)(Score))/(10.0)))
if(spawnRate < 0.5){
spawnRate = 0.5
}
}
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addEnemy),
SKAction.waitForDuration(spawnRate)
])
))
}

What is happening currently is that you've:
stored duration paramter inside an action
reused that action within sequence over and over again
So nothing actually changes. To solve this, one way would be to use recursive call and change duration parameter each time:
import SpriteKit
class GameScene: SKScene {
let shape = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 20, height: 20))
var delay:NSTimeInterval = 1.0
override func didMoveToView(view: SKView) {
recursiveMethod()
}
func recursiveMethod(){
let recursive = SKAction.sequence([
SKAction.waitForDuration(delay),
SKAction.runBlock({self.recursiveMethod();println("delay \(self.delay)")})
])
runAction(recursive)
}
override func update(currentTime: NSTimeInterval) {
if(delay > 0.2){delay = delay - 0.001}
}
}

The problem is that This:
SKAction.sequence([
SKAction.runBlock(addEnemy),
SKAction.waitForDuration(spawnRate)
])
is made one's and then just repeated forever.
try to put the code in a
runAction(SKAction.repeatActionForever(runblock(spawnAndWait()))
and then make a seperate func.
spawnAndWait() { // put here the code for spawning the enemy and waiting spawnrate duration.
b.t.w
Put the code that updates the spawnRate where the score is increased. Because that is more efficient. then in the update method because that is constantly called.

Related

How can I increase and display the score every second?

I am making a SpriteKit game in Swift. While gameState = inGame, I want the score to increase every second. How and where would I calculate and display something like this?
The other answers I have found are outdated and not very helpful. There might be one I am not aware of that already exists, so I would be greatly appreciative if you could point me in that direction. Thanks for the help.
Here is a very simple way of incrementing and displaying a score every second, as you have described it.
The "timer" here will be tied to your game's framerate because the counter is checked in the update method, which can vary based on your framerate. If you need a more accurate timer, consider the Timer class and search Stack Overflow or Google to see how to use it, as it can be more complicated than the simple one here.
To test this, create a new Game template project in Xcode and replace the contents of your GameScene.swift file with the following code.
You don't actually need the parts that use gameStateIsInGame. I just put that in there as a demonstration because of your remark about checking some gameState property in order for the timer to fire. In your own code you would integrate your own gameState property however you are handling it.
import SpriteKit
class GameScene: SKScene {
var scoreLabel: SKLabelNode!
var counter = 0
var gameStateIsInGame = true
var score = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
override func didMove(to view: SKView) {
scoreLabel = SKLabelNode(fontNamed: "Chalkduster")
scoreLabel.text = "Score: 0"
scoreLabel.position = CGPoint(x: 100, y: 100)
addChild(scoreLabel)
}
override func update(_ currentTime: TimeInterval) {
if gameStateIsInGame {
if counter >= 60 {
score += 1
counter = 0
} else {
counter += 1
}
}
}
}

Swift 3 (SpriteKit): Stopping a forever looping SKAction has a delay

I have been trying to make a forever running SKAction that I can stop whenever I want to. I have done it like this:
override func didMove(to view: SKView) {
run(SKAction.repeatForever (
SKAction.sequence ([
SKAction.run(drawFrame),
SKAction.wait(forDuration: 0.01),
])), withKey: "frameDrawing"
)
}
Then in the drawFrame function I stop the SKAction like this:
func drawFrame() {
//(code)
if stop {
removeAction(forKey: "frameDrawing")
}
}
For some reason the SKAction only stops when it has run 3 or 4 more times after stop became true. I want it to stop instantly when stop is set to true, not after 3 or 4 more repeats.
If anyone knows how to fix this, please tell me because I've tried many things and they never fix the issue. Thanks!
Edit: Here is my code:
var drawingFrame = SKAction()
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
drawingFrame = SKAction.repeatForever (
SKAction.sequence ([
SKAction.run(drawFrame),
SKAction.wait(forDuration: 0.03),
]))
run(drawingFrame)
}
func drawFrame() {
//(code)
if stop {
drawingFrame.speed = 0.0
}
}
If you're wondering why I have set the SKAction drawingFrame to an empty SKAction at the start, it is because I needed to define the SKAction before both functions. Otherwise, it would be not defined for both functions.
EDIT FOR ANYONE WITH THE SAME PROBLEM: I have fixed the problem using my own thinking and #appzYourLife's solution. The most efficient way which works every time is to only run the code if stop equals false. But, make sure that the if statement that stops the program is outside of that bracket so the SKAction will eventually stop. This is the working code:
var drawingFrame = SKAction()
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
drawingFrame = SKAction.repeatForever (
SKAction.sequence ([
SKAction.run(drawFrame),
SKAction.wait(forDuration: 0.03),
]))
run(drawingFrame)
}
func drawFrame() {
if stop = false {
//(code)
}
if stop {
removeAllActions()
}
}
You may use an if else statement for the stop = false statement if you prefer that.
I don't have an exact explanation why drawFrame() is called multiple times at the moment (but I will try to discover that :D)... Anyways, try this code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var stop = false
override func didMove(to view: SKView) {
run(SKAction.repeatForever (
SKAction.sequence ([
SKAction.run({[unowned self] in
if self.stop {
self.action(forKey: "frameDrawing")?.speed = 0.0
}else{
self.drawFrame()
}
}),
SKAction.wait(forDuration:0.03),
])), withKey: "frameDrawing"
)
}
func drawFrame() {
//(code)
print("drawFrame")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
stop = !stop
print(stop ? "stopped": "running")
if !stop {
self.action(forKey: "frameDrawing")?.speed = 1.0
}
}
}
Use touchesBegan to toggle paused mode.
You can remove all the actions running on the current node. This will remove immediately not only the sequence action but also the embedded actions.
func drawFrame() {
if stop {
removeAllActions()
}
}
Try creating a reference to the SKAction, and calling a change of speed (to 0.0) on that, when you want it to stop. Both of these will be quicker than the need to look up the name of the action and then remove it. But at 0.01 you're already repeating it faster than the frame rate (0.01666), so you're always going to get at least one extra iteration of the action, no matter how well you stop it.
something like:
let myAction = SKAction.repeatForever (
SKAction.sequence ([
SKAction.run(drawFrame),
SKAction.wait(forDuration: 0.01),
]))
//when you want to run it
run(myAction)
// when you want to stop it:
myAction.speed = 0.0

Add SpriteNodes and Remove Based on Time or click using SpriteKit

I am new to swift and looking to build my first basic game. The game I have in mind involves sprites generating at random and then disappearing based on time or a click if the click is within the time allocated. So far I have created the basic framework and am still messing around with design. My problem comes in where I can't seem to remove the sprite based on time (its generating fine). Any help is appreciated and thanks in advance😊
Below is the framework I've built up so far.
import SpriteKit
var one = SKSpriteNode()
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myFunction = SKAction.runBlock({()in self.addOne()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({() in self.removeOne()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func addOne() {
let oneTexture = SKTexture(imageNamed: "blue button 10.png")
let one = SKSpriteNode(texture: oneTexture)
one.position = CGPoint(x: CGRectGetMidX(self.frame) - 100, y: CGRectGetMidY(self.frame) + 250)
one.zPosition = 1
self.addChild(one)
}
func removeOne() {
one.removeFromParent()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
It doesn't disappear because your create a new SpiteNode, but try to remove the old one, do it like this:
var one : SKSpriteNode! //instead of creating it without data, just define the type(not necessary, but I would do it)
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myFunction = SKAction.runBlock({()in self.addOne()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({() in self.removeOne()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func addOne() {
let oneTexture = SKTexture(imageNamed: "blue button 10.png")
one = SKSpriteNode(texture: oneTexture) //removed the let, so you dont create a new "one"
one.position = CGPoint(x: CGRectGetMidX(self.frame) - 100, y: CGRectGetMidY(self.frame) + 250)
one.zPosition = 1
self.addChild(one)
}
func removeOne() {
one.removeFromParent()
}
}

Increasing/Decreasing waitForDuration( )?

I am wondering if its possible to increase withForDuration( ) with a variable. Heres how I am trying to do it in short. This is also SpriteKit, and this is my first time with it so I am still a little unsure. While this snippet of code works to change the float it doesn't actually change the waitForDuration(difficulty)
var timer: NSTimer!
var difficulty = 1.0
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
player.position = CGPoint(x: size.width * 0.5, y: size.height * 0.25)
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.dynamic = true
addChild(player)
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "increaseDifficulty", userInfo: nil, repeats: true)
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addEnemy),
SKAction.waitForDuration(difficulty)
])
))
}
func increaseDifficulty() {
difficulty -= 0.1
}
One way to do this is to use the completionHandler of the runAction function. For example.
func addEnemyWithChangingDifficulty() {
let waitAction = SKAction.waitForDuration(difficulty)
let addEnemyAction = SKAction.runBlock { () -> Void in
self.addEnemy()
}
runAction(SKAction.sequence([addEnemyAction,waitAction]), completion: { () -> Void in
self.addEnemyWithChangingDifficulty()
})
}
Another way would be to use the update function to track the waitDuration.
var lastAddedTime : CFTimeInterval = 0
override func update(currentTime: CFTimeInterval) {
if currentTime - lastAddedTime > CFTimeInterval(difficulty) {
addEnemy()
lastAddedTime = currentTime
}
}
You're not getting your intended effect because the value of 'difficulty' is captured in the first declaration of your SKAction and it never refers to the outside declaration again.
Two ways to solve this:
Instead of doing SKAction.repeatActionForever(), dump the SKAction.sequence() in increaseDifficulty. You'll have to tweak the numbers a bit to make it work, but NSTimer is already running on repeat, you can use that instead.
The second way (not 100% sure about this) is to put the '&' symbol in front of difficulty. This passes difficulty by reference rather by value, which should give you the intended effect. I'm just not sure if Swift allows this, but in C++ that's what we can do.

Sprite Kit physicsBody.resting behavior

I am using Swift and Sprite Kit to develop a game on XCode Beta 6.
In order to detect if all nodes are sleeping, i check their physicsBody.resting property.
In update method i print out the result.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var hero:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFromRect:self.frame)
hero = SKSpriteNode(imageNamed: "Spaceship")
hero.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
hero.zPosition = 10.0
hero.physicsBody = SKPhysicsBody(circleOfRadius: hero.size.width/2)
hero.physicsBody.allowsRotation = false
hero.physicsBody.linearDamping = 0.5
self.addChild(hero)
}
override func update(currentTime: CFTimeInterval) {
if hero.physicsBody.resting {
println("resting")
} else {
println("moving")
}
}
}
To my surprise, the results are:
moving
resting
moving
(n times the same)
moving
resting
So why the hero is moving, although i didn't do anything. The node moves N times and takes a break(resting), after that goes on moving.
Can anyone explain that behaviour? Is that a bug or do i miss something? Thanks in advance.
If you examine the velocity of a physics body, you'll see that it is indeed moving but at a rate that is not perceivable. That's why the resting property is not set. A more reliable way to check if a SKPhysicsBody is at rest is to test if its linear and angular speeds are nearly zero. Here's an example of how to do that:
func speed(velocity:CGVector) -> Float {
let dx = Float(velocity.dx);
let dy = Float(velocity.dy);
return sqrtf(dx*dx+dy*dy)
}
func angularSpeed(velocity:CGFloat) -> Float {
return abs(Float(velocity))
}
// This is a more reliable test for a physicsBody at "rest"
func nearlyAtRest(node:SKNode) -> Bool {
return (self.speed(node.physicsBody.velocity)<self.verySmallValue
&& self.angularSpeed(node.physicsBody.angularVelocity) < self.verySmallValue)
}
override func update(_ currentTime: TimeInterval) {
/* Enumerate over child nodes with names starting with "circle" */
enumerateChildNodesWithName("circle*") {
node, stop in
if (node.physicsBody.resting) {
println("\(node.name) is resting")
}
if (self.nearlyAtRest(node)) {
println("\(node.name) is nearly resting")
}
}
}