38

Is there a way to make a SKSpriteNode round cornered? I am trying to create a Tile likesqaure blocks with color filled SKSpriteNode:

SKSpriteNode *tile = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:0.0/255.0
                                                                           green:128.0/255.0
                                                                            blue:255.0/255.0
                                                                           alpha:1.0] size:CGSizeMake(30, 30)];

How can I make it round cornered?

Thanks!

Haris Hussain
  • 2,531
  • 3
  • 25
  • 38
  • 1
    **Note** you may be better off, just making the image have transparent corners - https://stackoverflow.com/a/39983382/294884 – Fattie Oct 23 '17 at 16:22

8 Answers8

53

To get a rounded corner node you can use 2 approaches, each of them requires use of SKShapeNode.

First way is to use SKShapeNode and set its path to be a rounded rectangle like this:

SKShapeNode* tile = [SKShapeNode node];
[tile setPath:CGPathCreateWithRoundedRect(CGRectMake(-15, -15, 30, 30), 4, 4, nil)];
tile.strokeColor = tile.fillColor = [UIColor colorWithRed:0.0/255.0
                                                    green:128.0/255.0
                                                     blue:255.0/255.0
                                                    alpha:1.0];

The other one uses sprite node,crop node and SKShapeNode with rounded rectangle as crop nodes mask:

SKSpriteNode *tile = [SKSpriteNode spriteNodeWithColor:[UIColor   colorWithRed:0.0/255.0
                                                                           green:128.0/255.0
                                                                            blue:255.0/255.0
                                                                           alpha:1.0] size:CGSizeMake(30, 30)];
SKCropNode* cropNode = [SKCropNode node];
SKShapeNode* mask = [SKShapeNode node];
[mask setPath:CGPathCreateWithRoundedRect(CGRectMake(-15, -15, 30, 30), 4, 4, nil)];
[mask setFillColor:[SKColor whiteColor]];
[cropNode setMaskNode:mask];
[cropNode addChild:tile];

If your tiles are one solid colour, i suggest you go with the first approach.

maksimov
  • 5,792
  • 1
  • 30
  • 38
Dobroćudni Tapir
  • 3,082
  • 20
  • 37
  • Even with SKShapeNode's current inconsistencies, the first method seems reliable and is pretty straight forward to implement. – Toby Sep 06 '14 at 04:01
  • 10
    SKShapeNode now has a 'rectOfSize:, cornerRadius:' initializer – Charlie Martin Feb 13 '15 at 04:25
  • If your tiles use textures, you can still go with the first approach, by setting the fillTexture of the SKShapeNode. – Craig Grummitt Mar 24 '15 at 22:38
  • If you'd like to make the rounded rect one solid color, call `setSetStrokeColor:` – rizzes Apr 06 '15 at 17:35
  • Note that the new cornerRadius initializer will crash devices running anything less than iOS 8. Shouldn't Xcode warn you of these issues? – Crashalot Jun 02 '15 at 22:45
  • I have to say, this is **just wrong**. In any game engine, any system, if you want a round sprite, you just make it round, a line of code. SKShapeNode is really unrelated here, it's for a totally unrelated problem. – Fattie Oct 23 '17 at 16:58
21

In Swift 3 you can create with:

let tile = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 30, height: 30), cornerRadius: 10)
Zoltan Vinkler
  • 1,207
  • 1
  • 15
  • 20
8

Hope this helps:

SKSpriteNode *make_rounded_rectangle(UIColor *color, CGSize size, float radius)
{
    UIGraphicsBeginImageContext(size);
    [color setFill];
    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
    [path fill];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    SKTexture *texture = [SKTexture textureWithImage:image];
    return [SKSpriteNode spriteNodeWithTexture:texture];
}
Krzysztof Gabis
  • 101
  • 1
  • 3
5

This is heavily inspired by the accepted answer, yet it uses a more readable way to create the SKShapeNode and also fixes the annoying pixel line around the crop. Seems like a minor detail, but it may cost someone a few minutes.

CGFloat cornerRadius = 15;
SKCropNode *cropNode = [SKCropNode node];
SKShapeNode *maskNode = [SKShapeNode shapeNodeWithRectOfSize:scoreNode.size cornerRadius:cornerRadius];
[maskNode setLineWidth:0.0];
[maskNode setFillColor:[UIColor whiteColor]];
[cropNode setMaskNode:maskNode];
[cropNode addChild:scoreNode];
Git.Coach
  • 3,032
  • 2
  • 37
  • 54
  • Sorry to repeat myself, but honestly, for the sake of anyone googling here, you just don't do this! You simply crop the PNG, it's that easy – Fattie Oct 23 '17 at 17:03
  • @Fattie My answer is general and crops any node. I wasn't using PNGs and if I did, I surely wouldn't want to touch every single one of them. So it still has its value. – Git.Coach Oct 23 '17 at 17:12
5

It's really this simple.......

class YourSprite: SKSpriteNode {

    func yourSetupFunction() {

          texture = SKTexture( image: UIImage(named: "cat")!.circleMasked! )

There's nothing more to it.

You really can not use SKShapeNode.

It's just that simple. It's an insane performance hit using SKShapeNode, it's not the right solution, it's pointlessly difficult, and the purpose of SKShapeNode just has no relation to this problem.

enter image description here

Look at all them kitties!

The code for circleMasked is this simple:

(All projects that deal with images will need this anyway.)

extension UIImage {
    
    var isPortrait:  Bool    { return size.height > size.width }
    var isLandscape: Bool    { return size.width > size.height }
    var breadth:     CGFloat { return min(size.width, size.height) }
    var breadthSize: CGSize  { return CGSize(width: breadth, height: breadth) }
    var breadthRect: CGRect  { return CGRect(origin: .zero, size: breadthSize) }
    
    var circleMasked: UIImage? {
        
        UIGraphicsBeginImageContextWithOptions(breadthSize, false, scale)
        defer { UIGraphicsEndImageContext() }
        
        guard let cgImage = cgImage?.cropping(to: CGRect(origin:
            CGPoint(
                x: isLandscape ? floor((size.width - size.height) / 2) : 0,
                y: isPortrait  ? floor((size.height - size.width) / 2) : 0),
            size: breadthSize))
        else { return nil }
        
        UIBezierPath(ovalIn: breadthRect).addClip()
        UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation)
            .draw(in: breadthRect)
        return UIGraphicsGetImageFromCurrentImageContext()
    }

// classic 'circleMasked' stackoverflow fragment
// courtesy Leo Dabius /a/29047372/294884
}

That's all there is to it.

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • Whilst this does circles well, and seems like you've walked through a series of glass walls to find this solution, how does this help make rounded rectangles as masks? – Confused Nov 13 '17 at 14:37
  • hi @Confused. I have no idea, at all, what you mean? Can you explain what you're asking - I will try to help. – Fattie Nov 13 '17 at 22:45
  • You've used a circle, which I often jokingly refer to as a fully rounded square. The OP is asking about rounded squares/rectangles. How would you do your magic upon a rounded rectangle/square? As the glass walls, I'm talking about how you're thought outside the framework to solve the problem. – Confused Nov 14 '17 at 15:15
  • hi @Confused - got you now. fortunately it's easy to modify `UIBezierPath` to give rounded corners rather than "round". i will try to put it in when I get a chance, but it is very easy: here's a great example by some excellent writer: https://stackoverflow.com/a/41962342/294884 – Fattie Nov 14 '17 at 18:24
4

Swift 4:

A good solution could be created before you should add your sprite to his parent, I've maded a little extension that could be corrected as you wish:

extension SKSpriteNode {
    func addTo(parent:SKNode?, withRadius:CGFloat) {
        guard parent != nil else { return }
        guard  withRadius>0.0 else {
            parent!.addChild(self)
            return
        }
        let radiusShape = SKShapeNode.init(rect: CGRect.init(origin: CGPoint.zero, size: size), cornerRadius: withRadius)
        radiusShape.position = CGPoint.zero
        radiusShape.lineWidth = 2.0
        radiusShape.fillColor = UIColor.red
        radiusShape.strokeColor = UIColor.red
        radiusShape.zPosition = 2
        radiusShape.position = CGPoint.zero
        let cropNode = SKCropNode()
        cropNode.position = self.position
        cropNode.zPosition = 3
        cropNode.addChild(self)
        cropNode.maskNode = radiusShape
        parent!.addChild(cropNode)
    }
}

Usage:

let rootNode = SKSpriteNode(color: .white, size: self.size)
rootNode.alpha = 1.0
rootNode.anchorPoint = CGPoint.zero
rootNode.position = CGPoint.zero
rootNode.zPosition = 1
rootNode.name = "rootNode"
rootNode.addTo(parent: self, withRadius: 40.0)
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
2

from the class reference:

"An SKSpriteNode is a node that draws a textured image, a colored square, or a textured image blended with a color."

It seems the easiest way is to draw a block with rounded corners and then use one of these class methods:

  • spriteNodeWithImageNamed:
  • spriteNodeWithTexture:
  • spriteNodeWithTexture:size:
Thorsten
  • 3,102
  • 14
  • 14
  • 5
    +1: Sprite Kit is not a drawing API. It's an engine for managing the movement of predefined blocks of pixels (aka sprites). – rickster Feb 11 '14 at 07:33
1

Here's a Swift 3 snippet based on the second solution of the accepted answer.

func createPlayerRoundedNode(){
    let tile = SKSpriteNode(color: .white, size: CGSize(width: 30, height: 30))
    tile.zPosition = 3
    tile.name = "tile node"
    let cropNode = SKCropNode()
    cropNode.zPosition = 2
    cropNode.name = "crop node"
    let mask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 30, height: 30), cornerRadius: 10)
    mask.fillColor = SKColor.darkGray
    mask.zPosition = 2
    mask.name = "mask node"
    cropNode.maskNode = mask
    self.addChild(cropNode)
    cropNode.addChild(tile)
}
JP Aquino
  • 3,946
  • 1
  • 23
  • 25