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

本秂侑毒 提交于 2019-12-03 12:16:48
MassivePenguin

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

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];

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

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

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

circleMask.alpha = .0;

EDIT: Seems to work for Mac OS only.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!