brianbroom.com

Custom Drawing in Sprite Kit

SpriteKit makes it easy to add and manipulate images on iOS (thus the name), but I've occasionally wanted to draw simple elements. I recently worked out a method that works.

The gist of the technique is to draw onto a Graphics Context, then turn that into an image and then into a sprite.

First, create an image context, using whatever size you ultimately need to produce.

UIGraphicsBeginImageContext(CGSizeMake(240, 240));
CGContextRef ctx = UIGraphicsGetCurrentContext();

Then draw whatever you need, remember to set fill and stroke colors.

[[SKColor whiteColor] setFill];
CGContextFillRect(ctx, CGRectMake(0, 0, 240, 240));
[[UIColor brownColor] setFill];
CGContextFillEllipseInRect(ctx, CGRectMake(v.x-2, v.y-2, 4, 4));
CGContextFillRect(ctx, CGRectMake(c.center.x, c.center.y, 30, 30));

and so on. Polygon shapes and lines are a little more complicated, as you have do make them as a path first.

A line

CGContextMoveToPoint(ctx, de.left.x, de.left.y);
CGContextAddLineToPoint(ctx, de.right.x, de.right.y);
CGContextDrawPath(ctx, kCGPathStroke);

A filled polygon, with a trick that I saw somewhere to use an if statement to move to the first vertex of a list of points, and adding the rest to make a polygon shape.

for (NSInteger i=0; i<verts.count; i++) {
  CGPoint p = [[verts objectAtIndex:i] CGPointValue];
  if (i==0) {
    CGContextMoveToPoint(ctx, p.x, p.y);
  } else {
    CGContextAddLineToPoint(ctx, p.x, p.y);
  }
}
CGContextDrawPath(ctx, kCGPathFill);

By far the easiest mistake to make here is to leave off the CGContextDrawPath call, and then wonder why nothing shows up on your image.

Finally, build the image and generate a SpriteNode

UIImage *textureImage = UIGraphicsGetImageFromCurrentImageContext();
SKTexture *texture = [SKTexture textureWithImage:textureImage];
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithTexture:texture];
[self addChild:bg];

To start out, I added all this to the initWithSize method of MyScene.m in a SpriteKit project. At some point it will make sense to pull this out and make a subclass of SKSpriteNode. I used a class method that returned the SpriteNode to the scene, but the hard part for me is usually just getting started in the first place.

The only other gotcha is that Image Context here uses typical screen coordinate origin of upper left (and +y being down the screen), where SpriteKit uses a lower left origin. I'm sure you could transform out of it if it was really in the way, but I think for most cases that is overkill, but it is something to keep in mind.

Update: Don't forget to call UIGraphicsEndImageContext when you are done.