Poor performance with SKShapeNode in Sprite Kit

Unfortunately, SKShapeNode is not that great for what you are trying to do. However, there is a way to optimize this, albeit with some caveats.

First one of the largest problems with the fps is that the draw count gets extremely high because each line segment you add is another draw. If you set showsDrawCount on your SKView instance, you will see what I mean.

In this answer Multiple skshapenode in one draw?, you can get more information about how you can use shouldRasterize property of a SKEffectNode to solve the problem if you are drawing something once. If you don’t do this, you will have processor time spent on numerous draws each frame.

So you can see that the draws is the main issue with you not getting the performance you desire. However, you seem to want to be drawing consistently over time, so what I am going to suggest might be a viable solution for you.

The logic of the solution I am suggesting is as such :

1 – Create a SKSpriteNode that we can use as a canvas.

2 – Create one SKShapeNode that will be used to draw ONLY the current line segment.

3 – Make that SKShapeNode a child of the canvas.

4 – Draw a new line segment via SKShapeNode

5 – Use the SKView method `textureFromNode to save what has currently been drawn on the canvas.

6 – set the texture of the canvas to that texture.

Loop back to #4 and make a new path for your SKShapeNode for the next line segment.

Repeat as needed.

The result should be that your draw count will never be higher than 2 draws, which would solve the problem of a high draw count.

Basically, you are preserving what has previously been drawn in a texture, therefore only ever needing one SKShapeNode draw for the latest line segment and one draw for the SKTexture.

Again, I have not tried this process yet, and if there is any lag it would be in that textureFromNode call each frame. If anything would be your bottleneck, that would be it!

I might try this theory out some time today, as I need textureFromNode for another problem I am trying to solve, and so I’ll definitely find out how fast/slow that method is! haha


This is not complete code, but is the important parts to achieve the desired drawing performance (60fps) :

The basic node elements are :

container -> SKNode that contains all elements that need to be cached

canvas -> SKSpriteNode that will display the cached version of drawn segments

pool of segments -> used to draw segments initially, and get reused as needed

First create a pool of SKShapeNodes :

pool = [[NSMutableArray alloc]init];

//populate the SKShapeNode pool
// the amount of segments in pool, dictates how many segments
// will be drawn before caching occurs.
for (int index = 0; index < 5; index++)
    SKShapeNode *segment = [[SKShapeNode alloc]init];
    segment.strokeColor = [SKColor whiteColor];
    segment.glowWidth = 1;
    [pool addObject:segment];

Next create method for getting a SKShapeNode from pool :

-(SKShapeNode *)getShapeNode
    if (pool.count == 0)
        // if pool is empty, 
        // cache the current segment draws and return segments to pool
        [self cacheSegments];

    SKShapeNode *segment = pool[0];
    [pool removeObjectAtIndex:0];

    return segment;

Next create a method for getting a segment from pool and drawing the line :

-(void)drawSegmentFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint
    SKShapeNode *curSegment = [self getShapeNode];
    CGMutablePathRef path = CGPathCreateMutable();
    curSegment.lineWidth = 3;
    curSegment.strokeColor = [SKColor whiteColor];
    curSegment.glowWidth = 1;
    curSegment.name = @"segment";

    CGPathMoveToPoint(path, NULL, fromPoint.x, fromPoint.y);
    CGPathAddLineToPoint(path, NULL, toPoint.x, toPoint.y);
    curSegment.path = path;
    lastPoint = toPoint;
    [canvas addChild:curSegment];

Next is a method for creating a texture and returning existing segments to the pool :

    SKTexture *cacheTexture =[ self.view textureFromNode:container];
    canvas.texture = cacheTexture;
    [canvas setSize:CGSizeMake(canvas.texture.size.width, canvas.texture.size.height)];
    canvas.anchorPoint = CGPointMake(0, 0);
    [canvas enumerateChildNodesWithName:@"segment" usingBlock:^(SKNode *node, BOOL *stop)
         [node removeFromParent];
         [pool addObject:node];


Lastly the touch handlers :

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    [self cacheSegments];
    for (UITouch *touch in touches)
        CGPoint location = [touch locationInNode:self];
        lastPoint = location;
        [self drawSegmentFromPoint:lastPoint toPoint:location];
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    for (UITouch *touch in touches)
        CGPoint location = [touch locationInNode:self];
        [self drawSegmentFromPoint:lastPoint toPoint:location];

As I said, this is not all inclusive code, I assume you understand enough about the concept that you can implement into your application. These are just examples of my barebones implementation.

Leave a Comment