Swift for loop not working - ios

I'm baffled by this loop in my Swift code that won't work. Here's the entirety of the function--"pulseChar" is giving me the error "index out of range":
func openingSequence(){
let nodeList = [self.drum,self.piano,self.guitarBoard]
let offset = SKAction.waitForDuration(10)
let stOne = SKAction.runBlock { () -> Void in
for var i in 0...nodeList.count-1{
nodeList[i].runAction(SKAction.fadeAlphaTo(0.3, duration: 0.3))
i++
}
}
let firstLineWait = SKAction.waitForDuration(4)
let moveSprites = SKAction.runBlock { () -> Void in
moveScale(self.rheaBoard, duration: 0.5, to: CGPoint(x: 100, y: self.frame.height - 85), size: 0.4)
moveScale(self.guitarBoard, duration: 0.5, to: CGPoint(x: self.frame.midX - self.frame.midX/2, y: 65), size: 0.35)
for var i in 0...nodeList.count-1{
nodeList[i].runAction(fadeI)
i++
}
}
let fadeAudio = SKAction.runBlock { () -> Void in
fadeOtherTracksOut([9,8,4,2,1])
}
let moveFade = SKAction.group([moveSprites,fadeAudio])
let pulseChar = SKAction.runBlock { () -> Void in
for var i in 0...nodeList.count-1{
self.background.runAction(SKAction.runBlock({ () -> Void in
pulse(nodeList[i], startScale: 0.35, endScale: 0.4)
}))
i++
}
self.startInteaction = true
}
background.runAction(SKAction.sequence([offset,stOne,firstLineWait,moveFade,pulseChar]))
}
My compiler is telling me that i = 3 when it fails...but "i" should not get to three, as
nodeList.count = 3
and I use the range
for var i in 0...nodeList.count-1
Also, the identical loops before it work just fine...what's going on here? I'm new to programming so if this is a ridiculously simple fix you'll have to excuse me. But help would be greatly appreciated.
EDIT
It seems that the operator i++ needed to be inside the runBlock:
let pulseChar = SKAction.runBlock { () -> Void in
for var i in 0...nodeList.count-1{
self.background.runAction(SKAction.runBlock({ () -> Void in
pulse(nodeList[i], startScale: 0.35, endScale: 0.4)
i++
}))
}
This fixes the problem, but I would really appreciate it if someone wouldn't mind explaining why this is the case. It seems to me that if the operator i++ is within the loop it should never == 3.

Don't increment the index variable i "manually", the repeat loop does that automatically.
And you can use the half-open range operator to avoid the extra subtraction, for example
for i in 0..<nodeList.count {
nodeList[i].runAction(SKAction.fadeAlphaTo(0.3, duration: 0.3))
}
or
for node in nodeList {
node.runAction(SKAction.fadeAlphaTo(0.3, duration: 0.3))
}
if the numeric index is not needed.
Or even
nodeList.forEach{ $0.runAction(SKAction.fadeAlphaTo(0.3, duration: 0.3)) }

From swift programming guide provided by apple Check Here For More Details
Care Fully look at the following code and it's output.
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
In For in loop we do not require to increment the counter.
The increment part is handled by the loop it self
Try Following Updated code.
func openingSequence(){
let nodeList = [self.drum,self.piano,self.guitarBoard]
let offset = SKAction.waitForDuration(10)
let stOne = SKAction.runBlock { () -> Void in
for var i in 0..<nodeList.count{
nodeList[i].runAction(SKAction.fadeAlphaTo(0.3, duration: 0.3))
}
}
let firstLineWait = SKAction.waitForDuration(4)
let moveSprites = SKAction.runBlock { () -> Void in
moveScale(self.rheaBoard, duration: 0.5, to: CGPoint(x: 100, y: self.frame.height - 85), size: 0.4)
moveScale(self.guitarBoard, duration: 0.5, to: CGPoint(x: self.frame.midX - self.frame.midX/2, y: 65), size: 0.35)
for var i in 0..<nodeList.count{
nodeList[i].runAction(fadeI)
}
}
let fadeAudio = SKAction.runBlock { () -> Void in
fadeOtherTracksOut([9,8,4,2,1])
}
let moveFade = SKAction.group([moveSprites,fadeAudio])
let pulseChar = SKAction.runBlock { () -> Void in
for var i in 0..<nodeList.count{
self.background.runAction(SKAction.runBlock({ () -> Void in
pulse(nodeList[i], startScale: 0.35, endScale: 0.4)
}))
}
self.startInteaction = true
}
background.runAction(SKAction.sequence([offset,stOne,firstLineWait,moveFade,pulseChar]))
}

Related

iOS (Swift): Changing UILabel text with a for loop

I have a for loop as follows:
#objc private func resetValue() {
for i in stride(from: value, to: origValue, by: (value > origValue) ? -1 : 1) {
value = i
}
value = origValue
}
And when value is set it updates a label:
private var value = 1 {
didSet {
updateLabelText()
}
}
private func updateLabelText() {
guard let text = label.text else { return }
if let oldValue = Int(text) { // is of type int?
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
} else {
label.text = "\(value)"
}
}
I was hoping that if value=5 and origValue=2, then the label would flip through the numbers 5,4,3,2. However, this is not happening - any suggestions why, please?
I've tried using a delay function:
func delay(_ delay:Double, closure: #escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
and then placing the following within the stride code:
delay(2.0) { self.value = i }
However, this doesn't seem to work either.
Thanks for any help offered.
UIKit won't be able to update the label until your code is finished with the main thread, after the loop completes. Even if UIKit could update the label after each iteration of the loop, the loop is going to complete in a fraction of a second.
The result is that you only see the final value.
When you attempted to introduce the delay, you dispatched the update to the label asynchronously after 0.5 second; Because it is asynchronous, the loop doesn't wait for the 0.5 second before it continues with the next iteration. This means that all of the delayed updates will execute after 0.5 seconds but immediately one after the other, not 0.5 seconds apart. Again, the result is you only see the final value as the other values are set too briefly to be visible.
You can achieve what you want using a Timer:
func count(fromValue: Int, toValue: Int) {
let stride = fromValue > toValue ? -1 : 1
self.value = fromValue
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats:true) { [weak self] (timer) in
guard let strongSelf = self else {
return
}
strongSelf.value += stride
if strongSelf.value == toValue {
timer.invalidate()
}
}
}
I would also update the didSet to send the oldValue to your updateLabelText rather than having to try and parse the current text.
private var value = 1 {
didSet {
updateLabelText(oldValue: oldValue, value: value)
}
}
private func updateLabelText(oldValue: Int, value: Int) {
guard oldValue != value else {
self.label.text = "\(value)"
return
}
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
}

How do you update something in an SKAction while using repeat repeatForever?

As you'll see below, I'm trying to update the durationNumber, so that the colorSwitch object turns faster. The durationNumber does update, but colorSwitch doesn't rotate faster. How do I accomplish this?
func turnWheel() {
var durationNumber = 2.0
let rotateAction = SKAction.rotate(byAngle: .pi/2, duration: durationNumber)
let switchAction = SKAction.run {
if let newState = SwitchState(rawValue: self.switchState.rawValue + 1) {
self.switchState = newState
} else {
self.switchState = .green
}
if self.score > self.scoreCheck {
self.scoreCheck += 1
durationNumber *= 0.5
}
}
colorSwitch.run(SKAction.repeatForever(SKAction.sequence([rotateAction, switchAction])))
}
You're changing the value of the local variable, but not changing the SKAction object itself. You need to change rotateAction.duration to have any effect on the action itself.
if self.score > self.scoreCheck {
self.scoreCheck += 1
durationNumber *= 0.5
rotateAction.duration = durationNumber
}
Do not update your duration, instead update your speed
func turnWheel() {
var durationNumber = 2.0
let rotateAction = SKAction.rotate(byAngle: .pi/2, duration: durationNumber)
let switchAction = SKAction.run {
if let newState = SwitchState(rawValue: self.switchState.rawValue + 1) {
self.switchState = newState
} else {
self.switchState = .green
}
if self.score > self.scoreCheck {
self.scoreCheck += 1
colorSwitch.speed *= 2
}
}
colorSwitch.run(SKAction.repeatForever(SKAction.sequence([rotateAction, switchAction])))
}

Add a delay to a function in SpriteKit

I would like to be able to only allow the player to shoot a missile every 0.7 seconds. How do I do this? Here is my code. I have tried other methods I found on this site but they do not work.
func fireTorpedo() {
if !self.player.isPaused
{
if isSoundEffect == true {
self.run(SKAction.playSoundFileNamed("Rocket", waitForCompletion: false))
}
let torpedoNode = SKSpriteNode(imageNamed: "Rocket")
torpedoNode.position = player.position
torpedoNode.position.y += 5
torpedoNode.physicsBody = SKPhysicsBody(circleOfRadius: torpedoNode.size.width / 2)
torpedoNode.physicsBody?.isDynamic = true
torpedoNode.physicsBody?.categoryBitMask = photonTorpedoCategory
torpedoNode.physicsBody?.contactTestBitMask = alienCategory
torpedoNode.physicsBody?.collisionBitMask = 0
torpedoNode.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(torpedoNode)
let animationDuration:TimeInterval = 0.5
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: player.position.x, y: self.frame.size.height + 10), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
torpedoNode.run(SKAction.sequence(actionArray))
}
}
I like to do that like this:
class GameScene:SKScene {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if action(forKey: "shooting") == nil {
let wait = SKAction.wait(forDuration: 0.7)
run(wait, withKey: "shooting")
print("shot fired")
}
}
}
While there is a key "shooting" found on a scene, you can't shoot anymore. In real game, this would be likely the part of a node (an action should be run on a node).
First, add a flag at class-level that indicates whether the player can fire a torpedo.
var canFireTorpedo = true
Then, at the end of the fireTorpedo method, set canFireTorpedo to false, then set it to true again after 0.7 seconds:
canFireTorpedo = false
player.run(SKAction.sequence([
SKAction.wait(forDuration: 0.7),
SKAction.run { [weak self] in self?.canFireTorpedo = true }]))
After that, check canFireTorpedo at the start of the method:
func fireTorpedo() {
if canFireTorpedo {
// put everything in the method here, including the parts I added
}
}
Hopefully this code is self-explanatory.
To launch every 0.7 seconds you can do:
let actionKey = "repeatLaunchMethod"
let launchMethod = SKAction.afterDelay(0.7, runBlock: {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.fireTorpedo()
})
// do this if you want to repeat this action forever
self.player.run(SKAction.repeatForever(launchMethod), withKey: actionKey)
// do this if you want to repeat this action only n times
// self.player.run(SKAction.repeat(launchMethod, count: 5), withKey: actionKey)
To stop this action you can do:
if self.player.action(forKey: actionKey) != nil {
self.player.removeAction(forKey: actionKey)
}
Extension used to render the code more readable:
extension SKAction {
/**
* Performs an action after the specified delay.
*/
class func afterDelay(_ delay: TimeInterval, performAction action: SKAction) -> SKAction {
return SKAction.sequence([SKAction.wait(forDuration: delay), action])
}
/**
* Performs a block after the specified delay.
*/
class func afterDelay(_ delay: TimeInterval, runBlock block: #escaping () -> Void) -> SKAction {
return SKAction.afterDelay(delay, performAction: SKAction.run(block))
}
}

UberJump Tutorial SpriteKit error

I'm using Ray Wenderlich's tutorial called UberJump (https://www.raywenderlich.com/87232/make-game-like-mega-jump-sprite-kit-swift-part-2) and I've run into a logic error. The code used to remove objects is not working and I can't figure out why. I downloaded his source code and his code is not working either.
Here is what I'm having trouble with:
override func update(currentTime: NSTimeInterval) {
//new max height?
//1
if Int(player.position.y) > maxPlayerY {
//2
GameState.sharedInstance.score += Int(player.position.y) - maxPlayerY!
//3
maxPlayerY = Int(player.position.y)
//4
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
// Remove game objects that have passed by
foregroundNode.enumerateChildNodesWithName("NODE_PLATFORM", usingBlock: {
(node, stop) in
let platform = node as! PlatformNode
platform.checkNodeRemoval(self.player.position.y)
})
foregroundNode.enumerateChildNodesWithName("NODE_STAR", usingBlock: {
(node, stop) in
let star = node as! StarNode
star.checkNodeRemoval(self.player.position.y)
})
//calculate player y offset
if player.position.y > 200.0 {
backgroundNode.position = CGPoint(x: 0.0, y: -((player.position.y - 200.0)/10))
midgroundNode.position = CGPoint(x: 0.0, y: -((player.position.y - 200.0)/4))
foregroundNode.position = CGPoint(x: 0.0, y: -(player.position.y-200.0))
}
}
The "remove game objects that have passed by" code is not working. It is supposed to remove the platforms as the character jumps on them. Did I write the code out wrong? Thanks.
EDIT: Here is the checkNodeRemovalFunction as well.
class GameObjectNode: SKNode {
func collisionWithPlayer(player: SKNode) -> Bool {
// Award score
return false
}
func checkNodeRemoval(playerY: CGFloat) {
if playerY > self.position.y + 300.0 {
self.removeFromParent()
}
}
It seems like there could be an issue with your checkNodeRemoval function depending on how you're inheriting from the GameObjectNode. Something you could try is taking the checkNodeRemoval function out and checking directly in your update method.
// Remove game objects that have passed by
foregroundNode.enumerateChildNodesWithName("NODE_PLATFORM", usingBlock: {
(node, stop) in
print("found platform node")
if self.player.position.y > node.position.y + 300.0 {
print("attempt to remove platform")
platform.removeFromParent()
}
})
foregroundNode.enumerateChildNodesWithName("NODE_STAR", usingBlock: {
(node, stop) in
print("found star node")
if self.player.position.y > node.position.y + 300.0 {
print("attempt to remove star")
star.removeFromParent()
}
})

runAction SKAction.sequence not working

I can't get the runAction method to work for this code block. I'm not sure what I am doing wrong or if there is a problem outside the block, but if anyone sees anything inside that is wrong, let's start there (*Edited to include entire function). All variables work and I'm getting zero compile errors, the program just appears to skip the line currentTestIndicator.runAction(SKAction.sequence(runCheck)). All print statements work except the one inside recurse, also current++ isn't getting called. I tried running the code without recurse and it still didn't work. Ideas?
func runLevelCheck(node: SKSpriteNode, _ state: SKSpriteNode, _ bluePatterns:Array<Array<String>>, _ blueSize: CGFloat, _ blueLocations: Array<CGPoint>, _ greenPatterns:Array<Array<String>>, _ greenSize: CGFloat, _ greenLocations:Array<CGPoint>, _ redPatterns:Array<Array<String>>, _ redSize: CGFloat, _ redLocations:Array<CGPoint>, _ orangePatterns:Array<Array<String>>, _ orangeSize: CGFloat, _ orangeLocations:Array<CGPoint>)->Bool {
var passLevel = true
var current = 0
let currentTestIndicator = SKSpriteNode()
currentTestIndicator.name = "indicator"
currentTestIndicator.size = CGSizeMake(0, 128)
blueLocations[current].y)
currentTestIndicator.anchorPoint = CGPoint(x: 0.0, y: 0.5)
currentTestIndicator.zPosition = 0
node.addChild(currentTestIndicator)
let patterns = [bluePatterns, greenPatterns, redPatterns, orangePatterns]
let sizes = [blueSize, greenSize, redSize, orangeSize]
let locations = [blueLocations, greenLocations, redLocations, orangeLocations]
let colors = [color("Light Blue 01"), color("Light Green 01"), color("Light Red 01"), color("Light Orange 01")]
for sets in patterns {
for rows in sets {
if rows != [] {
print("Current set: \(current)")
print(rows)
currentTestIndicator.color = colors[current]
currentTestIndicator.position = CGPoint(x: 0.0, y: locations[current][0].y)
print(currentTestIndicator.position)
print("Sizes current = \(sizes[current])")
let growRight = SKAction.resizeToWidth(sizes[current], duration: 0.2)
let recurse = SKAction.runBlock() {
recursion(state, rows, 0, 0, &passLevel)
current++
print("Changed current value to \(current)")
}
let pause = SKAction.waitForDuration(5.0)
let shrinkLeft = SKAction.resizeToWidth(0, duration: 0.2)
let runCheck = [growRight, recurse, pause, shrinkLeft]
currentTestIndicator.runAction(SKAction.sequence(runCheck))
}
}
current = 0
}
currentTestIndicator.removeFromParent()
print("Ran level check")
switch passLevel {
case true:
print("Level passed")
case false:
print("Lavel failed")
}
return passLevel
}

Resources