8

I need to create a cgpath continuously. At the moment I do it like that:

 func createLine(){
        var rand = randomBetweenNumbers(1, 2)
        currentY--
        if rand < 1.5{
            currentX--
            CGPathAddLineToPoint(leftPath, nil, currentX, currentY)
        }else{
            currentX++
            CGPathAddLineToPoint(leftPath, nil, currentX, currentY)
        }
        CGPathAddLineToPoint(rightPath, nil, currentX+tileSize, currentY)
        lineNode.path = leftPath
        rightNode.path = rightPath

}

And call it like that:

NSTimer.scheduledTimerWithTimeInterval(0.05, target: self, selector: Selector("startTile"), userInfo: nil, repeats: true)

But the problem is, that the frames drop lower and lower over time. Is there something I have to change so that the framerate no longer drops?

My goal is to create a random endless path.

Christian
  • 22,585
  • 9
  • 80
  • 106
  • I would guess that you need to dump the old parts of your path. Otherwise you end up having an ever increasing number of path segments. Alternately, why not create a new path every time the end of the current path is reached? – sangony May 28 '15 at 18:48
  • I can't really create a new path, because I need it to be endless or at least that a new path isn't visible for the player. – Christian May 28 '15 at 18:50
  • 2
    This should help [line caching](http://stackoverflow.com/questions/24553245/poor-performance-with-skshapenode-in-sprite-kit) – 0x141E May 28 '15 at 19:42
  • Could you put your path's points in an array and just draw what should be actually visible on screen? By the way you might prefer using `CADisplayLink` in spite of an `NSTimer`, since it fires in sync with the display refresh. – Matteo Piombo Jun 03 '15 at 04:10

1 Answers1

4

The key to maintaining a high FPS count while drawing a progressively increasing number of lines is to quickly reach a state where adding more lines to the scene has little or no effect on frame rate. There are at least two ways to accomplish this.

The most straightforward of the two is to periodically convert the previously drawn lines to an SKTexture and display the results as texture of an SKSpriteNode. Here are the steps:

  1. Create an SKNode to be used as a line container
  2. Create an SKSpriteNode that will be used as a line canvas
  3. Create an SKShapeNode to be used to draw new lines
  4. Add the container to the scene and the canvas and shape node to the container
  5. Draw a set of connected line segments using the path property of the shape node
  6. When the line count reaches a predetermined value, convert the contents of the container to an 'SKTexture'
  7. Set the texture property of the canvas to the SKTexture. Note that since the canvas is also a child of the container, its contents will also be added to the texture
  8. Lather, rinse, repeat steps 5 - 7

Here's an example implementation in Swift that draws an endless set of lines at 60 FPS on an iPhone 6 device (you should test performance on a device not with the simulator):

class GameScene: SKScene {
    // 1. Create container to hold new and old lines
    var lineContainer = SKNode()
    // 2. Create canvas
    var lineCanvas:SKSpriteNode?
    // 3. Create shape to draw new lines
    var lineNode = SKShapeNode()

    var lastDrawTime:Int64 = 0
    var lineCount = 0
    var timeScan:Int64 = 0
    var path = CGPathCreateMutable()

    var lastPoint = CGPointZero

    override func didMoveToView(view:SKView) {
        scaleMode = .ResizeFill

        // 4. Add the container to the scene and the canvas to the container 
        addChild(lineContainer)
        lineCanvas = SKSpriteNode(color:SKColor.clearColor(),size:view.frame.size)
        lineCanvas!.anchorPoint = CGPointZero
        lineCanvas!.position = CGPointZero
        lineContainer.addChild(lineCanvas!)
        lastPoint = CGPointMake(view.frame.size.width/2.0, view.frame.size.height/2.0)
    }

    // Returns a random value in the specified range
    func randomInRange(minValue:CGFloat, maxValue:CGFloat) -> CGFloat {
        let r = CGFloat(Double(arc4random_uniform(UInt32.max))/Double(UInt32.max))
        return (maxValue-minValue) * r + minValue
    }

    func drawLine() {
        if (CGPathIsEmpty(path)) {
            // Create a new line that starts where the previous line ended
            CGPathMoveToPoint(path, nil, lastPoint.x, lastPoint.y)
            lineNode.path = nil
            lineNode.lineWidth = 1.0
            lineNode.strokeColor = SKColor.blueColor()
            lineNode.zPosition = 100
            lineContainer.addChild(lineNode)
        }
        // Add a random line segment
        let x = randomInRange(size.width*0.1, maxValue: size.width*0.9)
        let y = randomInRange(size.height*0.1, maxValue: size.height*0.9)
        CGPathAddLineToPoint(path, nil, x, y)
        lineNode.path = path
        // Save the current point so we can connect the next line to the end of the last line
        lastPoint = CGPointMake(x, y)
    }

    override func update(currentTime: CFTimeInterval) {
        let lineDrawTime = timeScan / 10
        // 5. Draw a new line every 10 updates. Increment line count
        if (lineDrawTime != lastDrawTime) {
            drawLine()
            ++lineCount
        }
        // 6. and 7. Add all newly and previously drawn lines to the canvas
        if (lineCount == 8) {
            addLinesToTexture()
            lineCount = 0
        }
        lastDrawTime = lineDrawTime
        ++timeScan
    }

    func addLinesToTexture () {
        // Convert the contents of the line container to an SKTexture
        let texture = self.view!.textureFromNode(lineContainer)
        // Display the texture
        lineCanvas!.texture = texture
        // Start a new line
        lineNode.removeFromParent()
        path = CGPathCreateMutable()
    }
}
0x141E
  • 12,613
  • 2
  • 41
  • 54
  • What if you want the line to "fade out," such that each partial segment has a little more alpha than the one preceding it? Is this answer still applicable in that case? What about the elusive alternative technique you hinted at? – Andrew Dec 26 '15 at 06:13
  • @Andrew this approach is for games that draws a large number lines that persist. I suggest you create a mutable array of `SKShapeNode` lines that fade (by changing the `strokeColor` property) over time. – 0x141E Dec 26 '15 at 07:18