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

5

11

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?

Schlueter answered 26/11, 2013 at 22:5 Comment(0)
S
13

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).

Schlueter answered 5/12, 2013 at 8:28 Comment(0)
N
6

Since I cannot use a SKShapeNode as mask I decided to convert it to a SKSpriteNode.

Here it is my Swift code:

let shape : SKShapeNode = ... // create your SKShapeNode
var view : SKView = ... // you can get this from GameViewController

let texture = view.textureFromNode(shape)
let sprite = SKSpriteNode(texture: texture)
sprite.position = ...

cropNode.mask = sprite

And it does work :)

Nichani answered 27/12, 2014 at 13:49 Comment(3)
It looks like you will need to add an unused shape node to the scene. I suggest you look into creating a UIImage from a CGPath or a UIBezierPath. You can then generate a texture from the image.Torietorii
Hi 0x141E, actually I am not adding shape (the SKShapeNode) to the scene. The only node that will be saved is sprite.Nichani
This is a much better solution than the accepted oneTorietorii
P
5

Yes, it is impossible to use fill-colored shapenode in current Sprite-Kit realization. It's a bug, I think.

But!

You always can render the shape to texture and use it as mask!

For ex, let edge is SKShapeNode created before.

First, render it to texture before adding to view (in this case it will clean from another nodes)

SKTexture *Mask = [self.view textureFromNode:edge];
[self addChild:edge]; //if you need physics borders

Second,

SKCropNode *cropNode = [[SKCropNode alloc] init];
[cropNode setMaskNode:[SKSpriteNode spriteNodeWithTexture:Mask]];
[cropNode addChild: [SKSpriteNode spriteNodeWithTexture:rocksTiles size:CGSizeMake(w,h)]];
cropNode.position = CGPointMake(Mask.size.width/2,Mask.size.height/2);
//note, anchorpoint of shape in 0,0, but rendered texture is in 0.5,0.5, so we need to dispose it
[self addChild:cropNode];
Palladin answered 11/2, 2014 at 16:58 Comment(1)
And now some things are possible. (iOS 8.1): int deviceOSVersion= [[[UIDevice currentDevice] systemVersion] floatValue]; if (deviceOSVersion>=8.0) treeS.fillTexture=[SKTexture textureWithImage:[UIImage imageNamed:@"treeS"]];Palladin
X
1

There is a slightly awkward solution to this issue that I believe is slightly less nasty than any of the mooted hacks around this issue. Simply wrap your SKShapeNode in an SKNode. I haven't tested what kind of performance hit this might cause, but given that SKNode is non-drawing, I'm hoping it won't be too significant.

i.e.

let background = SKSpriteNode(imageNamed: "Background")
let maskNode = SKShapeNode(path: MyPath)
let wrapperNode = SKNode()
let cropNode = SKCropNode()

wrapperNode.addChild(maskNode)
cropNode.maskNode = wrapperNode
cropNode.addChild(background)
Xanthine answered 5/6, 2017 at 10:7 Comment(0)
C
0

Setting alpha of mask node to .0 does the trick:

circleMask.alpha = .0;

EDIT: Seems to work for Mac OS only.

Crossrefer answered 18/4, 2014 at 20:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.