Skspritenode - Create a Round Corner Node

SKSpriteNode - create a round corner node?

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.

How to create SKTexture with rounded corners without using a mask

Try this:

// your profile picture
UIImage *fbProfilePicture = [UIImage imageNamed:@"fbProfilePicture"];

// create the image with rounded corners
UIGraphicsBeginImageContextWithOptions(fbProfilePicture.size, NO, 0);
CGRect rect = CGRectMake(0, 0, fbProfilePicture.size.width, fbProfilePicture.size.height);
[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:20.0] addClip];
[fbProfilePicture drawInRect:rect];
UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

// use the roundedImage as texture for your sprite
SKTexture *texture = [SKTexture textureWithImage:roundedImage];
SKSpriteNode *profilePic = [SKSpriteNode spriteNodeWithTexture:texture size:CGSizeMake(fbProfilePicture.size.width, fbProfilePicture.size.height)];

[self addChild:profilePic];

The rounded corner part comes from this answer.

SpriteKit: suggestions for rounding corners of unconventional grid?

What we did was lay out the tiles then call this function to round the nodes of exposed tiles.

// Rounds corners of exposed tiles. UIKit inverts coordinates so top is bottom and vice-versa.
fileprivate func roundTileCorners() {
// Get all tiles
var tiles = [TileClass]()
tileLayer.enumerateChildNodes(withName: ".//*") { node, stop in
if node is TileClass {
tiles.append(node as! TileClass)
}
}

// Round corners for each exposed tile
for t in tiles {
// Convert tile's position to root coordinates
let convertedPos = convert(t.position, from: t.parent!)

// Set neighbor positions
var leftNeighborPos = convertedPos
leftNeighborPos.x -= tileWidth
var rightNeighborPos = convertedPos
rightNeighborPos.x += tileWidth
var topNeighborPos = convertedPos
topNeighborPos.y += tileHeight
var bottomNeighborPos = convertedPos
bottomNeighborPos.y -= tileHeight

// Set default value for rounding
var cornersToRound : UIRectCorner?

// No neighbor below & to left? Round bottom left.
if !isTileAtPoint(point: bottomNeighborPos) && !isTileAtPoint(point: leftNeighborPos) {
cornersToRound = cornersToRound?.union(.topLeft) ?? .topLeft
}

// No neighbor below & to right? Round bottom right.
if !isTileAtPoint(point: bottomNeighborPos) && !isTileAtPoint(point: rightNeighborPos) {
cornersToRound = cornersToRound?.union(.topRight) ?? .topRight
}

// No neightbor above & to left? Round top left.
if !isTileAtPoint(point: topNeighborPos) && !isTileAtPoint(point: leftNeighborPos) {
cornersToRound = cornersToRound?.union(.bottomLeft) ?? .bottomLeft
}

// No neighbor above & to right? Round top right.
if !isTileAtPoint(point: topNeighborPos) && !isTileAtPoint(point: rightNeighborPos) {
cornersToRound = cornersToRound?.union(.bottomRight) ?? .bottomRight
}

// Any corners to round?
if cornersToRound != nil {
t.roundCorners(cornersToRound: cornersToRound!)
}
}
}

// Returns true if a tile exists at <point>. Assumes <point> is in root node's coordinates.
fileprivate func isTileAtPoint(point: CGPoint) -> Bool {
return nodes(at: point).contains(where: {$0 is BoardTileNode })
}

Gradient progress bar with rounded corners SpriteKit Swift

It's about SKCropNode + its maskNode property. From the docs:

SKCropNode is a container node that you use to crop other nodes in the
scene. You add other nodes to a crop node and set the crop node's
maskNode property. For example, here are some ways you might specify a
mask:

An untextured sprite that limits content to a rectangular portion of
the scene.

A textured sprite that works as a precise per-pixel mask.

A collection of child nodes that form a unique shape.

You can animate the shape or contents of the mask to implement
interesting effects such as hiding or revealing.

So, a simple example would be like this:

    class GameScene: SKScene {

override func sceneDidLoad() {

super.sceneDidLoad()
createProgressBar()
}

private func createProgressBar(){

let barFrame = CGRect(x: 0, y: 0, width: 300, height: 15)

if let cgImage = createImage(frame: barFrame) {

let texture = SKTexture(cgImage: cgImage)

let sprite = SKSpriteNode(texture: texture)
let cropNode = SKCropNode()
let mask = SKSpriteNode(color: .gray, size: barFrame.size)

cropNode.addChild(sprite)
cropNode.maskNode = mask

sprite.anchorPoint = CGPoint(x: 0.0, y: 0.5)
mask.anchorPoint = CGPoint(x: 0.0, y: 0.5)

var counter:Double = 0
let action = SKAction.run {[weak self, sprite] in
guard let `self` = self, counter < 100 else {
sprite?.removeAction(forKey: "loop")
return

}

counter += 1
let newWidth = self.getWidth(percents: counter, spriteWidth: barFrame.width)
print("Bar width \(newWidth), percentage \(counter)")

mask.size = CGSize(width: newWidth, height: barFrame.height)
}
let wait = SKAction.wait(forDuration: 0.05)
let sequence = SKAction.sequence([wait, action])

let loop = SKAction.repeatForever(sequence)



addChild(cropNode)
cropNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)

sprite.run(loop, withKey: "loop")


}
}

private func getWidth(percents:Double, spriteWidth:Double)->Double{

let onePercent = spriteWidth / 100.0

return onePercent * percents
}


private func createImage(frame barFrame:CGRect) -> CGImage?{

if let ciFilter = CIFilter(name: "CILinearGradient"){

let ciContext = CIContext()

ciFilter.setDefaults()

let startColor = CIColor(red: 0.75, green: 0.35, blue: 0.45, alpha: 1)
let endColor = CIColor(red: 0.45, green: 0.35, blue: 0.75, alpha: 1)

let startVector = CIVector(x: 0, y: 0)
let endVector = CIVector(x: barFrame.width, y: 0)

ciFilter.setValue(startColor, forKey: "inputColor0")
ciFilter.setValue(endColor, forKey: "inputColor1")
ciFilter.setValue(startVector, forKey: "inputPoint0")
ciFilter.setValue(endVector, forKey: "inputPoint1")

if let outputImage = ciFilter.outputImage {
let cgImage = ciContext.createCGImage(outputImage, from: CGRect(x: 0, y: 0, width: barFrame.width, height: barFrame.height))
return cgImage

}
}

return nil


}
}

Now cause this is just an example I won't go all the way to implement this right, but You can maybe make a class of it with designable and inspectable properties, optimize code, make it reusable etc. But the general idea is shown here.

You use SKCropNode to add progress bar in it, and use maskNode property to reveal progress bar as percentage increases. Also I gave a method to create texture programatically, but You can use just a .png file instead.

Crop node is here used only cause of a gradient (cause we don't wan't to scale image, but rather to show it part by part). Obviously, crop node is not needed if a progress bar had only one color.

Here is final result:

progress-bar

Top Rounded corners on node

User this UIBezierPath initializer:

public convenience init(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize)

and specify [.TopLeft, .TopRight] for the byRoundingCorners argument.

shape.path = UIBezierPath(
roundedRect: CGRect(x: -128, y: -128, width: 256, height: 256),
byRoundingCorners: [.TopLeft, .TopRight]
cornerRadii: CGSize(width: 64, height: 64)).CGPath

Crop/Mask circular image node in sprite kit gives jagged edges

UPDATE:

Ok, so here's one solution I came up. Not super ideal but it works perfectly. Essentially, I have a to size/scale, and cut the image exactly the way it would go on the SKSpriteNode so I would not have to use SKCropNode or some variation of SKShapeNode.

  1. I used these UIImage extensions by Leo Dabus to resize/shape the image exactly as needed. Cut a UIImage into a circle Swift(iOS)

    var circle: UIImage? {
    let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
    let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
    imageView.contentMode = .ScaleAspectFill
    imageView.image = self
    imageView.layer.cornerRadius = square.width/2
    imageView.layer.masksToBounds = true
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
    guard let context = UIGraphicsGetCurrentContext() else { return nil }
    imageView.layer.renderInContext(context)
    let result = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return result
    }

    func resizedImageWithinRect(rectSize: CGSize) -> UIImage {
    let widthFactor = size.width / rectSize.width
    let heightFactor = size.height / rectSize.height

    var resizeFactor = widthFactor
    if size.height > size.width {
    resizeFactor = heightFactor
    }

    let newSize = CGSizeMake(size.width/resizeFactor, size.height/resizeFactor)
    let resized = resizedImage(newSize)
    return resized
    }
  2. The final codes look like this:

    //create/shape image
    let image = UIImage(named: "TestImage")
    let scaledImage = image?.resizedImageWithinRect(CGSize(width: 100, height: 100))
    let circleImage = scaledImage?.circle

    //create sprite
    let sprite = SKSpriteNode(texture: SKTexture(image: circleImage!))
    sprite.position = CGPoint(x: view.frame.width/2, y: view.frame.height/2)

    //set texture/image
    sprite.texture = SKTexture(image: circleImage!)
    sprite.physicsBody = SKPhysicsBody(texture: SKTexture(image: circleImage!), size: CGSizeMake(100, 100))
    if let physics = sprite.physicsBody {
    //add the physic properties
    }

    //scale node
    sprite.setScale(1.0)
    addChild(sprite)

So if you have a perfectly scaled asset/image, then you probably dont need to do all this work, but I'm getting images from the backend that could come in any sizes.



Related Topics



Leave a reply



Submit