Reputation: 3012
I'm trying to smoothly animate a square into a circle in SpriteKit
.
I'm creating the SKShape
with a UIBezierPath
using rounded corners. Then, I vary the corner radii to animate.
My problem is that I seem to have a jump in the animation, please see the gif below. Preferably using the rounded corners technique, how can I get it to be smooth?
let shape = SKShapeNode()
let l: CGFloat = 100.0
shape.path = UIBezierPath(roundedRect: CGRect(x: -l/2, y: -l/2, width: l, height: l), byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: 0, height: 0)).cgPath
shape.position = CGPoint(x: frame.midX, y: frame.midY)
shape.fillColor = .white
addChild(shape)
let action = SKAction.customAction(withDuration: 1) { (node, t) in
let shapeNode = node as! SKShapeNode
shapeNode.path = UIBezierPath(roundedRect: CGRect(x: -l/2, y: -l/2, width: l, height: l), byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: t * l / 2, height: 0)).cgPath
}
shape.run(SKAction.repeatForever(action))
To debug I have created some shapes with progressively larger corner radii as you can see below. The numbers represent the ratio of the corner radii to the length of the square. As you can see there is a jump between 0.3
and 0.35
. I can't see what I'm missing.
let cols = 10
let rows = 1
let l: Double = 30.0
let max: Double = l / 2
let delta: Double = l * 2
for i in 0..<rows * cols {
let s = SKShapeNode()
let c: Double = Double(i % cols)
let r: Double = floor(Double(i) / Double(cols))
let pct: Double = Double(i) / (Double(rows) * Double(cols))
let rad = pct * max
s.path = UIBezierPath(roundedRect: CGRect(x: -l/2, y: -l/2, width: l, height: l), byRoundingCorners: [.topRight, .bottomRight, .topLeft, .bottomLeft], cornerRadii: CGSize(width: pct * max, height: pct * max)).cgPath
s.position = CGPoint(x: c * delta - Double(cols) / 2.0 * delta, y: r * delta - Double(rows) / 2.0 * delta)
s.lineWidth = 1.5
s.strokeColor = .white
addChild(s)
let t = SKLabelNode(text: String(format:"%0.2f", rad / l))
t.verticalAlignmentMode = .center
t.horizontalAlignmentMode = .center
t.fontName = "SanFrancisco-Bold"
t.fontSize = 15
t.position = CGPoint(x: 0, y: -delta * 0.66)
s.addChild(t)
}
Upvotes: 1
Views: 594
Reputation: 1484
The documentation of UIBezierPath.init(roundedRect:cornerRadius:)
states that:
The radius of each corner oval. A value of 0 results in a rectangle without rounded corners. Values larger than half the rectangle’s width or height are clamped appropriately to half the width or height.
But I tested this and it is actually happening exactly for values larger than a third of the width/height and not at a half of it. I'm filling a bug report and will let's hope it is quickly solved.
Bug report link: https://bugs.swift.org/browse/SR-10496
Upvotes: 0
Reputation: 11531
You may not find the answer using a current api. But you can draw it by yourself.
let duration = 10.0
let action = SKAction.customAction(withDuration: duration) { (node, t) in
let shapeNode = node as! SKShapeNode
let path = CGMutablePath()
let borderRadius = l/2 * t / CGFloat(duration);
path.move(to: CGPoint.init(x: -l/2, y: -l/2 + borderRadius));
path.addLine(to: CGPoint.init(x: -l/2, y: l/2 - borderRadius));
path.addArc(tangent1End: CGPoint(x: -l/2, y: l/2), tangent2End: CGPoint(x: -l/2 + borderRadius, y: l/2), radius: borderRadius)
path.addLine(to: CGPoint.init(x: l/2 - borderRadius, y: l/2 ));
path.addArc(tangent1End: CGPoint(x: l/2, y: l/2), tangent2End: CGPoint(x: l/2, y: l/2 - borderRadius), radius: borderRadius)
path.addLine(to: CGPoint.init(x: l/2, y: -l/2 + borderRadius));
path.addArc(tangent1End: CGPoint(x: l/2, y: -l/2), tangent2End: CGPoint(x: l/2 - borderRadius, y: -l/2), radius: borderRadius)
path.addLine(to: CGPoint.init(x: -l/2 + borderRadius, y: -l/2));
path.addArc(tangent1End: CGPoint(x: -l/2, y: -l/2), tangent2End: CGPoint(x: -l/2, y: -l/2 + borderRadius), radius: borderRadius)
path.closeSubpath()
shapeNode.path = path
}
Upvotes: 3