Alex
Alex

Reputation: 33

Moving multiple sprite nodes at once on swift

Can I make an array of SK nodes of which one is selected randomly and brought from the top to bottom of the screen. For example say I have 25 or so different platforms that will be falling out of the sky on a portrait iPhone. I need it to randomly select one of the platforms from the array to start and then after a certain amount of time/ or pixel space randomly select another to continue the same action until reaching the bottom etc. Im new to swift but have a pretty decent understanding of it. I haven't been able to find out how to create an array of SKsprite nodes yet either. Could someone help with this?

So far the only way I've been able to get any sort of effect similar to what I've wanted is by placing each of the nodes off the screen and adding them to a dictionary and making them move like this

class ObstacleStatus {

    var isMoving = false
    var timeGapForNextRun = Int(0)
    var currentInterval = Int(0)
    init(isMoving: Bool, timeGapForNextRun: Int, currentInterval: Int) {
        self.isMoving = isMoving
        self.timeGapForNextRun = timeGapForNextRun
        self.currentInterval = currentInterval
}

func shouldRunBlock() -> Bool {
    return self.currentInterval >  self.timeGapForNextRun

}

and

func moveBlocks(){
    for(blocks, ObstacleStatus) in self.blockStatuses {
        var thisBlock = self.childNodeWithName(blocks)
        var thisBlock2 = self.childNodeWithName(blocks)
        if ObstacleStatus.shouldRunBlock() {
            ObstacleStatus.timeGapForNextRun = randomNum()
            ObstacleStatus.currentInterval = 0
            ObstacleStatus.isMoving = true
        }

        if ObstacleStatus.isMoving {
            if thisBlock?.position.y > blockMaxY{
                    thisBlock?.position.y -= CGFloat(self.fallSpeed)
            }else{
                thisBlock?.position.y = self.origBlockPosistionY
                ObstacleStatus.isMoving = false
            }
        }else{
                ObstacleStatus.currentInterval++
            }

    }
}

using this for the random function

func randomNum() -> Int{
 return randomInt(50, max: 300)        
}

func randomInt(min: Int, max:Int) -> Int {
    return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}

All this has been doing for me is moving the pieces down at random timed intervals often overlapping them, But increasing the min or max of the random numbers doesn't really have an affect on the actual timing of the gaps. I need to be able to specify a distance or time gap.

Upvotes: 2

Views: 1250

Answers (1)

Milos
Milos

Reputation: 2758

One of many possible solutions is to create a falling action sequence which calls itself recursively until no more platform nodes are left. You can control the mean "gap time" and the range of its random variation. Here is a working example (assuming the iOS SpriteKit game template):

import SpriteKit

extension Double {
    var cg: CGFloat { return CGFloat(self) }
}

extension Int {
    var cg: CGFloat { return CGFloat(self) }
}

func randomInt(range: Range<Int>) -> Int {
    return range.startIndex + Int(arc4random_uniform(UInt32(range.endIndex - range.startIndex)))
}

extension Array {
    func randomElement() -> Element? {
        switch self.count {
        case 0: return nil
        default: return self[randomInt(0..<self.count)]
        }
    }

    func apply<Ignore>(f: (T) -> (Ignore)) {
        for e in self { f(e) }
    }
}

class GameScene: SKScene {

    var screenWidth: CGFloat { return UIScreen.mainScreen().bounds.size.width }
    var screenHeight: CGFloat { return UIScreen.mainScreen().bounds.size.height }

    let PlatformName = "Platform"
    let FallenPlatformName = "FallenPlatform"

    func createRectangularNode(#x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) -> SKShapeNode {
        let rect = CGRect(x: x, y: y, width: width, height: height)
        let path = UIBezierPath(rect: rect)
        let node = SKShapeNode(path: path.CGPath)
        return node
    }

    func createPlatformNodes(numNodes: Int, atHeight: CGFloat) -> [SKShapeNode] {
        var padding = 20.cg
        let width = (screenWidth - padding) / numNodes.cg - padding
        padding = (screenWidth - width * numNodes.cg) / (numNodes.cg + 1)
        let height = width / 4
        var nodes = [SKShapeNode]()
        for x in stride(from: padding, to: numNodes.cg * (width + padding), by: width + padding) {
            let node = createRectangularNode(x: x, y: atHeight, width: width, height: height)
            node.fillColor = SKColor.blackColor()
            node.name = PlatformName
            nodes.append(node)
        }
        return nodes
    }

    func createFallingAction(#by: CGFloat, duration: NSTimeInterval, timeGap: NSTimeInterval, range: NSTimeInterval = 0) -> SKAction {
        let gap = SKAction.waitForDuration(timeGap, withRange: range)
//      let fall = SKAction.moveToY(toHeight, duration: duration) // moveToY appears to have a bug: behaves as moveBy
        let fall = SKAction.moveByX(0, y: -by, duration: duration)
        let next = SKAction.customActionWithDuration(0) { [unowned self]
            node, time in
            node.name = self.FallenPlatformName
            self.fallNextNode()
        }
        return SKAction.sequence([gap, fall, next])
    }

    func fallNextNode() {
        if let nextNode = self[PlatformName].randomElement() as? SKShapeNode {
            let falling = createFallingAction(by: screenHeight * 0.7, duration: 1, timeGap: 2.5, range: 2) // mean time gap and random range
            nextNode.runAction(falling)
        } else {
            self.children.apply { ($0 as? SKShapeNode)?.fillColor = SKColor.redColor() }
        }
    }

    override func didMoveToView(view: SKView) {
        self.backgroundColor = SKColor.whiteColor()
        for platform in createPlatformNodes(7, atHeight: screenHeight * 0.8) {
            self.addChild(platform)
        }
        fallNextNode()
    }
}

Upvotes: 1

Related Questions