Search code examples
objective-ciphoneios7sprite-kitskcropnode

Is it possible to use a circle (SKShapeNode) as a mask in Sprite Kit?


I'm trying to create a circular mask in a Sprite Kit project. I create the circle like this (positioning it at the centre of the screen):

SKCropNode *cropNode = [[SKCropNode alloc] init];

SKShapeNode *circleMask = [[SKShapeNode alloc ]init];
CGMutablePathRef circle = CGPathCreateMutable();
CGPathAddArc(circle, NULL, CGRectGetMidX(self.frame), CGRectGetMidY(self.frame), 50, 0, M_PI*2, YES);
circleMask.path = circle;
circleMask.lineWidth = 0;
circleMask.fillColor = [SKColor blueColor];
circleMask.name=@"circleMask";

and further down the code, I set it as the mask for the cropNode:

[cropNode setMaskNode:circleMask];

... but instead of the content showing inside a circle, the mask appears as a square.

Is it possible to use a SKShapeNode as a mask, or do I need to use an image?


Solution

  • After much swearing, scouring the web, and experimentation in Xcode, I have a really hacky fix.

    Keep in mind that this is a really nasty hack - but you can blame that on Sprite Kit's implementation of SKShapeNode. Adding a fill to a path causes the following:

    • adds an extra node to your scene
    • the path becomes unmaskable - it appears 'over' the mask
    • makes any non-SKSpriteNode sibling nodes unmaskable (e.g. SKLabelNodes)

    Not an ideal state of affairs.

    Inspired by Tony Chamblee's progress timer, the 'fix' is to dispense with the fill altogether, and just use the stroke of the path:

    SKCropNode *cropNode = [[SKCropNode alloc] init];
    
    SKShapeNode *circleMask = [[SKShapeNode alloc ]init];
    CGMutablePathRef circle = CGPathCreateMutable();
    CGPathAddArc(circle, NULL, CGRectGetMidX(self.frame), CGRectGetMidY(self.frame), 50, 0, M_PI*2, YES); // replace 50 with HALF the desired radius of the circle
    circleMask.path = circle;
    circleMask.lineWidth = 100; // replace 100 with DOUBLE the desired radius of the circle
    circleMask.strokeColor = [SKColor whiteColor];
    circleMask.name=@"circleMask";
    
    [cropNode setMaskNode:circleMask];
    

    As commented, you need to set the radius to half of what you'd normally have, and the line width to double the radius.

    Hopefully Apple will look at this in future; for now, this hack is the best solution I've found (other than using an image, which doesn't really work if your mask needs to be dynamic).