0
votes

I am currently writing a joystick app and I want to display the contours of my lidar sensor; I am using sprite kit but it's lagging; I am assuming that it is taking up too much memory

My lidar sensor returns to me 360 data points where each degree represents the distances of where it hit an object (wall)

I am initializing all of my sprite kit nodes in didMoveToView, is there a better way to achieve this? perhaps in init?

I am creating 720 sprite kit nodes because 360 are for the lines and 360 are for the data points (distances)

It seems as if I am creating too many sprite kit nodes (720+)

Also, I am planning to provide video streaming through my app; is Sprite Kit the best option to do this?

<code>Simulator Screen Shot</code>

import Foundation
import UIKit
import SpriteKit

class GameScene: SKScene {


    var visual = SKSpriteNode()

    let button = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 50), cornerRadius: 10)

    var distances: [Int] = [2601, 2600, 33, 2608, 2601, 2594, 2625, 2633, 2637, 2651, 2656, 2666, 2683, 2690, 2705, 2712, 2712, 2739, 2752, 53, 1103, 1060, 1019, 980, 944, 911, 33, 851, 826, 801, 777, 757, 737, 718, 701, 683, 667, 654, 53, 486, 470, 457, 448, 440, 432, 424, 416, 409, 403, 396, 389, 383, 378, 372, 367, 362, 357, 353, 348, 344, 340, 336, 333, 329, 326, 323, 319, 317, 314, 311, 309, 307, 305, 303, 301, 299, 298, 297, 295, 294, 293, 292, 291, 290, 290, 290, 290, 291, 293, 295, 303, 386, 383, 53, 350, 53, 53, 53, 53, 53, 3, 364, 362, 360, 356, 358, 355, 353, 351, 350, 349, 347, 345, 53, 53, 53, 308, 3, 309, 309, 312, 313, 315, 316, 319, 321, 53, 329, 332, 335, 332, 53, 53, 53, 53, 53, 53, 53, 3, 53, 53, 53, 53, 53, 53, 670, 33, 706, 728, 750, 772, 799, 825, 856, 887, 925, 755, 749, 744, 739, 734, 730, 727, 722, 719, 716, 714, 634, 709, 706, 704, 703, 701, 699, 700, 695, 697, 697, 338, 696, 697, 696, 698, 698, 700, 702, 703, 705, 707, 710, 712, 714, 718, 53, 53, 53, 53, 53, 53, 53, 53, 3, 53, 53, 53, 53, 53, 53, 53, 53, 3, 53, 53, 53, 53, 53, 53, 53, 3412, 3397, 3384, 53, 3538, 3603, 53, 2426, 2412, 2363, 2330, 2288, 2262, 2214, 2190, 2162, 2130, 53, 53, 53, 2807, 2631, 2408, 2638, 2607, 2601, 2562, 2534, 2515, 2496, 2478, 2462, 2445, 53, 53, 53, 1751, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 3, 53, 53, 53, 53, 53, 2643, 2643, 53, 2660, 53, 2528, 2523, 2221, 1955, 1684, 1677, 1684, 1694, 1705, 1202, 1204, 1211, 1217, 1225, 1233, 1239, 1249, 1259, 1270, 1280, 1289, 1280, 1011, 991, 977, 968, 966, 964, 968, 975, 984, 1003, 2, 53, 1297, 53, 53, 33, 1321, 1299, 1274, 1254, 1233, 1232, 1264, 1293, 3802, 53, 53, 53, 53, 53, 53, 53, 3, 53, 2878, 2870, 2848, 2821, 2803, 2788, 2769, 2753, 2740, 2708, 2711, 2701, 2682, 2656, 2664, 2644, 2644, 2631, 2626, 2616, 2610, 2604, 2605, 2605, 2600, 2598, 2603]

    var scalex: [Double] = []
    var scaley: [Double] = []
    var disx: [Double] = []
    var disy: [Double] = []

    var somenodes = [SKSpriteNode()]
    var newnodes = [SKShapeNode()]

    override init(size: CGSize) {

        print("this is in init!")
        super.init(size: size)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    let rotateAnalogStick = AnalogJoystick(diameter: 100)

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */

        visual.color = UIColor.clearColor()
        visual.size = CGSize(width: 350, height: 350)
        visual.position = CGPointMake(frame.midX, frame.midY + 150)
        addChild(visual)


        for index in 0...356 {

            var radians = Double(index) * M_PI / 180.0
            var x = Double(distances[index]) * cos(Double(radians))
            var y = Double(distances[index]) * sin(Double(radians))

            disx.append(x)
            disy.append(y)
        }


        var path = CGPathCreateMutable()
        var xmin: Double = Double(disx.minElement()!)
        var xmax: Double = Double(disx.maxElement()!)
        var ymin: Double = Double(disy.minElement()!)
        var ymax: Double = Double(disy.maxElement()!)

        var scale = max(xmax - xmin, ymax - ymin)

        for index in 0...356 {

            var radians = Double(index) * M_PI / 180.0
            var x = (Double(distances[index]) * cos(Double(radians)) / scale) * 350
            var y = (Double(distances[index]) * sin(Double(radians)) / scale) * 350

            print("(\(x),\(y))")

            var mynode = SKSpriteNode()
            mynode.color = UIColor.redColor()
            mynode.size = CGSize(width: 2, height: 2)

            let circlenode = SKShapeNode()
            let circlePath = UIBezierPath(arcCenter: CGPoint(x: 0,y: 0), radius: CGFloat(5), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
            circlenode.path = circlePath.CGPath
            //change the fill color
            circlenode.fillColor = UIColor.blueColor()
            //you can change the stroke color
            circlenode.strokeColor = UIColor.redColor()
            //you can change the line width
            circlenode.lineWidth = 0.5
            circlenode.zPosition = 1
            circlenode.position = CGPointMake(CGFloat(x), CGFloat(y))
            newnodes.append(circlenode)

            CGPathMoveToPoint(path, nil, 0, 0) // from
            CGPathAddLineToPoint(path, nil, CGFloat(x), CGFloat(y)) // to

            let shape = SKShapeNode()
            shape.path = path
            shape.strokeColor = UIColor.purpleColor()
            shape.lineWidth = 1
            visual.addChild(shape)
            visual.addChild(newnodes[index])
        }

        let newscale = SKAction.scaleTo(0.1, duration: 0.5)
        let fadein = SKAction.fadeInWithDuration(0.5)
        let fadeout = SKAction.fadeOutWithDuration(0.5)
        let outsequence = SKAction.sequence([fadeout])
        let insequence = SKAction.sequence([fadein])

        for index in 0...visual.children.count {
//            visual.children[index].runAction(insequence)
//            visual.children[index].runAction(repeatout)
        }

        button.fillColor = UIColor.blueColor()
        button.position = CGPointMake(frame.midX - 50, frame.midY - 110)

        addChild(button)

        var mylabel = SKLabelNode()
        mylabel.color = UIColor.blackColor()
        mylabel.fontSize = 12
        mylabel.fontName = "AvenirNext-Bold"
        mylabel.text = "Contour Display"
        mylabel.position = CGPointMake(50, 20)
        mylabel.zPosition = 1
        button.addChild(mylabel)

        backgroundColor = UIColor.whiteColor()
        physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)

        rotateAnalogStick.position = CGPointMake(frame.midX + 5, rotateAnalogStick.radius + 60)
        rotateAnalogStick.stick.color = UIColor.cyanColor()
        rotateAnalogStick.substrate.color = UIColor.blackColor()
        addChild(rotateAnalogStick)

        rotateAnalogStick.trackingHandler = { data in

            // cartesian to polar
            var r = sqrt(pow(Double(data.velocity.x), Double(2)) + pow(Double(data.velocity.y), Double(2)))
            var power = (r - 0)/(50 - 0) * (1-0) + 0
            var theta = atan2(Double(data.velocity.y), Double(data.velocity.x)) // atan(Double(data.velocity.y/data.velocity.x)) //
            var degrees = theta * (180.0 / M_PI)

            var packet = ""
            var offset = 0.0
            var rdirection = 0.0
            var ldirection = 0.0
            var rpower = 0.0
            var lpower = 0.0

            if (degrees >= 65 && degrees <= 115) { // move forward

                rpower = power
                lpower = power

                rdirection = 1
                ldirection = 1

            } else if (degrees <= 0 && degrees >= -65) || (degrees >= 0 && degrees <= 65) { // move right

                offset = 90 - degrees
                rdirection = min(degrees, offset)
                ldirection = max(degrees, offset)

                rpower = (rdirection - 0)/(50 - 0) * (power-0) + 0
                lpower = abs(1-rpower)

                rdirection = -1
                ldirection = 1

            } else if (degrees <= -115 && degrees >= -180) || (degrees > 115 && degrees < 180 ) { // move left

                offset = 90 - degrees
                rdirection = min(degrees, offset)
                ldirection = max(degrees, offset)

                rpower = abs(1-rpower)
                lpower = (rdirection - 0)/(50 - 0) * (power-0) + 0

                rdirection = 1
                ldirection = -1

            } else if (degrees <= -65 && degrees >= -115) { // move down

                rpower = power
                lpower = power

                rdirection = -1
                ldirection = -1
            }

            packet = "\(rdirection) \(ldirection) \(rpower) \(lpower)"

        }

        view.multipleTouchEnabled = true
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        /* Called when a touch begins */

        for touch: AnyObject in touches {
            // Get the location of the touch in this scene
            let location = touch.locationInNode(self)
            // Check if the location of the touch is within the button's bounds
            if button.containsPoint(location) {
                print("button tapped!")
            }
        }

    }

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
    }
}
1
Use Instruments to find out exactly why your code is slow. I'm not sure if you're familiar with it, but it can tell you (with some fudge factor) what percent of your app's runtime is spent on which lines. One thing that's strange to me is the store to "packet" in the joystick callback—doing string interpolation seems odd on its own, also that variable is not used. - Colin Barrett
It is lagging because of SKShapeNode usage. Each SKShapeNode requires one draw call. That is inefficient. Try using SKSpriteNode for dots. To check what I am talking about, set view.showsNodesCount = true in your game view controller. This would be the first optimisation. Next would be pooling if needed (eg reusing same nodes, but that depends on what your app does). - Whirlwind
@ColinBarrett thanks for the suggestion; I will search the tools I can use that will let me know what exactly is slowing down my app - humblebeast
@Whirlwind yes, I will try this as soon as possible, I noticed there was a difference between the shape node and sprite node - humblebeast

1 Answers

1
votes

I'd like to think that SpriteKit can bang out 720 sprites no problem. As cited, your main issue is SKShapeNode usage.

What you should do is create your "dots" as textures. If you want color/outlines, put them in the texture. Additionally, create your lines out of colored rects (which can be SKSpriteNodes). If you have multiple circle colors, put them in a texture atlas.

This will give you the better chance of improved throughput with SpriteKit.

You are doing this work in didMoveToView. How often does it need to be refreshed? If the data is set and the one difference is transforming it (eg. rotate, scale, translate), another approach would be to pre-render the whole thing using Quartz/Core Graphics and then transforming the rendering.