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
UPDATE
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 :
-(void)cacheSegments
{
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.