如何为每个象限创建一个末端为圆角的圆
how to create a circle with rounded ends for each quadrant
我创建了一个分成象限的圆。我试图使每个象限的末端在每个象限之间具有给定的间隙,但我得到了一些奇怪的行为。我想我错过了什么。下面是我得到的图像和相关代码。我希望两端与 Apple Watch activity 戒指相似。
class GameScene: SKScene {
let radius = CGFloat(100)
var topRightPathNode : SKShapeNode!
var bottomRightPathNode : SKShapeNode!
var bottomLeftPathNode : SKShapeNode!
var topLeftPathNode : SKShapeNode!
override func didMove(to view: SKView) {
}
override init(size: CGSize) {
super.init(size: size)
let topRightPath = arcSegment(center: CGPoint.zero, radius: radius, strokeWidth: 18, gapWidth: 6)
// TOP RIGHT
topRightPathNode = SKShapeNode(path: topRightPath)
topRightPathNode.fillColor = SKColor.white
topRightPathNode.lineWidth = 0
topRightPathNode.position = CGPoint(x: 320, y: 240)
addChild(topRightPathNode)
// BOTTOM RIGHT
var reflectOnY = CGAffineTransform(scaleX: 1.0, y: -1.0)
let bottomRightPath = topRightPath.copy(using: &reflectOnY)!
bottomRightPathNode = SKShapeNode(path: bottomRightPath)
bottomRightPathNode.fillColor = SKColor.red
bottomRightPathNode.lineWidth = 0
bottomRightPathNode.position = CGPoint(x: 320, y: 240)
addChild(bottomRightPathNode)
// BOTTOM LEFT
//var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
//let bottomLeftPath = bottomRightPath.copy(using: &reflectOnX)!
var reflectOnXAndY = CGAffineTransform(scaleX: -1.0, y: -1.0)
let bottomLeftPath = topRightPath.copy(using: &reflectOnXAndY)!
bottomLeftPathNode = SKShapeNode(path: bottomLeftPath)
bottomLeftPathNode.fillColor = SKColor.purple
bottomLeftPathNode.lineWidth = 0
bottomLeftPathNode.position = CGPoint(x: 320, y: 240)
addChild(bottomLeftPathNode)
// TOP LEFT
var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
let topLeftPath = topRightPath.copy(using: &reflectOnX)!
topLeftPathNode = SKShapeNode(path: topLeftPath)
topLeftPathNode.fillColor = SKColor.cyan
topLeftPathNode.lineWidth = 0
topLeftPathNode.position = CGPoint(x: 320, y:240)
addChild(topLeftPathNode)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func arcSegment(center : CGPoint,
radius: CGFloat,
strokeWidth: CGFloat,
gapWidth: CGFloat) -> CGPath
{
let halfStrokeWidth = strokeWidth / 2.0
let outerRadius = radius + halfStrokeWidth
let innerRadius = radius - halfStrokeWidth
let halfGap = gapWidth / 2.0
let outerStartAngle = CGFloat(atan2(sqrt(outerRadius * outerRadius - halfGap * halfGap), halfGap))
let outerEndAngle = CGFloat(atan2(halfGap, sqrt(outerRadius * outerRadius - halfGap * halfGap)))
let innerStartAngle = CGFloat(atan2(halfGap, sqrt(innerRadius * innerRadius - halfGap * halfGap)))
let innerEndAngle = CGFloat(atan2(sqrt(innerRadius * innerRadius - halfGap * halfGap), halfGap))
let path = CGMutablePath()
path.addArc(center: center, radius: outerRadius, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: true)
// Quartz 2D will assume a "moveTo" here
path.addArc(center: CGPoint(x: center.x + radius, y: center.y), radius: halfStrokeWidth, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: false)
path.addArc(center: center, radius: innerRadius, startAngle: innerStartAngle, endAngle: innerEndAngle, clockwise: false)
path.closeSubpath()
return path
}
我在回答您的第一个问题时所用的所有三角函数都是必要的,以便在您想要的曲线上获得平端,并与轴对齐。现在您已经更改并希望圆弧上的端盖圆润,您可以返回到最初的尝试并仅依靠 Quartz 2D 来使用圆润的端盖笔触。代码又简单多了,可以是:
import UIKit
import SpriteKit
import PlaygroundSupport
let radius = CGFloat(100)
let sceneSize = CGSize(width: 640, height: 480)
let sceneView = SKView(frame: CGRect(origin: CGPoint.zero, size: sceneSize))
let scene = SKScene(size: sceneSize)
scene.backgroundColor = UIColor.black
let topRightPath = arcSegment(radius: radius, gapWidth: 25)
let topRightPathNode = SKShapeNode(path: topRightPath)
topRightPathNode.strokeColor = SKColor.white
topRightPathNode.lineWidth = 18
topRightPathNode.lineCap = .round
topRightPathNode.position = CGPoint(x: 320, y: 240)
scene.addChild(topRightPathNode)
var reflectOnY = CGAffineTransform(scaleX: 1.0, y: -1.0)
let bottomRightPath = topRightPath.copy(using: &reflectOnY)!
let bottomRightPathNode = SKShapeNode(path: bottomRightPath)
bottomRightPathNode.strokeColor = SKColor.orange
bottomRightPathNode.lineWidth = 18
bottomRightPathNode.lineCap = .round
bottomRightPathNode.position = CGPoint(x: 320, y: 240)
scene.addChild(bottomRightPathNode)
var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
let bottomLeftPath = bottomRightPath.copy(using: &reflectOnX)!
let bottomLeftPathNode = SKShapeNode(path: bottomLeftPath)
bottomLeftPathNode.strokeColor = SKColor.green
bottomLeftPathNode.lineWidth = 18
bottomLeftPathNode.lineCap = .round
bottomLeftPathNode.position = CGPoint(x: 320, y: 240)
scene.addChild(bottomLeftPathNode)
let topLeftPath = bottomLeftPath.copy(using: &reflectOnY)!
let topLeftPathNode = SKShapeNode(path: topLeftPath)
topLeftPathNode.strokeColor = SKColor.blue
topLeftPathNode.lineWidth = 18
topLeftPathNode.lineCap = .round
topLeftPathNode.position = CGPoint(x: 320, y:240)
scene.addChild(topLeftPathNode)
sceneView.presentScene(scene)
PlaygroundPage.current.liveView = sceneView
PlaygroundPage.current.needsIndefiniteExecution = true
func arcSegment( radius: CGFloat,
gapWidth: CGFloat) -> CGPath
{
let halfGap = gapWidth / 2.0
let path = CGMutablePath()
let startAngle = CGFloat(atan2(sqrt(radius * radius - halfGap * halfGap), halfGap))
let endAngle = CGFloat(atan2(halfGap, sqrt(radius * radius - halfGap * halfGap)))
path.addArc( center: CGPoint.zero,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
return path
}
这是另一组代码,其中生成的路径具有圆角末端,但每个圆弧段都是填充路径而不是单笔划段。这应该允许路径在没有端盖重叠的情况下发生碰撞。
import UIKit
import SpriteKit
import PlaygroundSupport
let radius = CGFloat(100)
let sceneSize = CGSize(width: 640, height: 480)
let sceneView = SKView(frame: CGRect(origin: CGPoint.zero, size: sceneSize))
let scene = SKScene(size: sceneSize)
scene.backgroundColor = UIColor.black
let topRightPath = arcSegment(radius: radius, strokeWidth: 18, gapWidth: 25)
let topRightPathNode = SKShapeNode(path: topRightPath)
topRightPathNode.fillColor = SKColor.white
topRightPathNode.position = CGPoint(x: 320, y: 240)
topRightPathNode.lineWidth = 0
scene.addChild(topRightPathNode)
var reflectOnY = CGAffineTransform(scaleX: 1.0, y: -1.0)
let bottomRightPath = topRightPath.copy(using: &reflectOnY)!
let bottomRightPathNode = SKShapeNode(path: bottomRightPath)
bottomRightPathNode.fillColor = SKColor.orange
bottomRightPathNode.position = CGPoint(x: 320, y: 240)
bottomRightPathNode.lineWidth = 0
scene.addChild(bottomRightPathNode)
var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
let bottomLeftPath = bottomRightPath.copy(using: &reflectOnX)!
let bottomLeftPathNode = SKShapeNode(path: bottomLeftPath)
bottomLeftPathNode.fillColor = SKColor.green
bottomLeftPathNode.position = CGPoint(x: 320, y: 240)
bottomLeftPathNode.lineWidth = 0
scene.addChild(bottomLeftPathNode)
let topLeftPath = bottomLeftPath.copy(using: &reflectOnY)!
let topLeftPathNode = SKShapeNode(path: topLeftPath)
topLeftPathNode.fillColor = SKColor.blue
topLeftPathNode.position = CGPoint(x: 320, y:240)
topLeftPathNode.lineWidth = 0
scene.addChild(topLeftPathNode)
sceneView.presentScene(scene)
PlaygroundPage.current.liveView = sceneView
PlaygroundPage.current.needsIndefiniteExecution = true
func arcSegment( radius: CGFloat,
strokeWidth: CGFloat,
gapWidth: CGFloat) -> CGPath
{
let halfStrokeWidth = strokeWidth / 2.0
let outerRadius = radius + halfStrokeWidth
let innerRadius = radius - halfStrokeWidth
let halfGap = gapWidth / 2.0
let outerStartAngle = CGFloat(atan2(sqrt(outerRadius * outerRadius - halfGap * halfGap), halfGap))
let outerEndAngle = CGFloat(atan2(halfGap, sqrt(outerRadius * outerRadius - halfGap * halfGap)))
let innerStartAngle = CGFloat(atan2(halfGap, sqrt(innerRadius * innerRadius - halfGap * halfGap)))
let innerEndAngle = CGFloat(atan2(sqrt(innerRadius * innerRadius - halfGap * halfGap), halfGap))
let leftEndAngle = CGFloat(atan2(sqrt(radius * radius - halfGap * halfGap), halfGap))
let leftEndCapPoint = CGPoint(x: radius * cos(leftEndAngle),
y: radius * sin(leftEndAngle))
let rightEndCapPoint = CGPoint(x: leftEndCapPoint.y,
y: leftEndCapPoint.x)
let path = CGMutablePath()
path.addArc(center: CGPoint.zero,
radius: outerRadius,
startAngle: outerStartAngle,
endAngle: outerEndAngle,
clockwise: true)
path.addArc(center: rightEndCapPoint,
radius: halfStrokeWidth,
startAngle : 0,
endAngle : CGFloat.pi,
clockwise: true)
path.addArc(center: CGPoint.zero, radius: innerRadius, startAngle: innerStartAngle, endAngle: innerEndAngle, clockwise: false)
path.addArc(center: leftEndCapPoint,
radius: halfStrokeWidth,
startAngle : 3.0 * CGFloat.pi / 2.0,
endAngle : CGFloat.pi / 2.0,
clockwise: true)
path.closeSubpath()
return path
}
我创建了一个分成象限的圆。我试图使每个象限的末端在每个象限之间具有给定的间隙,但我得到了一些奇怪的行为。我想我错过了什么。下面是我得到的图像和相关代码。我希望两端与 Apple Watch activity 戒指相似。
class GameScene: SKScene {
let radius = CGFloat(100)
var topRightPathNode : SKShapeNode!
var bottomRightPathNode : SKShapeNode!
var bottomLeftPathNode : SKShapeNode!
var topLeftPathNode : SKShapeNode!
override func didMove(to view: SKView) {
}
override init(size: CGSize) {
super.init(size: size)
let topRightPath = arcSegment(center: CGPoint.zero, radius: radius, strokeWidth: 18, gapWidth: 6)
// TOP RIGHT
topRightPathNode = SKShapeNode(path: topRightPath)
topRightPathNode.fillColor = SKColor.white
topRightPathNode.lineWidth = 0
topRightPathNode.position = CGPoint(x: 320, y: 240)
addChild(topRightPathNode)
// BOTTOM RIGHT
var reflectOnY = CGAffineTransform(scaleX: 1.0, y: -1.0)
let bottomRightPath = topRightPath.copy(using: &reflectOnY)!
bottomRightPathNode = SKShapeNode(path: bottomRightPath)
bottomRightPathNode.fillColor = SKColor.red
bottomRightPathNode.lineWidth = 0
bottomRightPathNode.position = CGPoint(x: 320, y: 240)
addChild(bottomRightPathNode)
// BOTTOM LEFT
//var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
//let bottomLeftPath = bottomRightPath.copy(using: &reflectOnX)!
var reflectOnXAndY = CGAffineTransform(scaleX: -1.0, y: -1.0)
let bottomLeftPath = topRightPath.copy(using: &reflectOnXAndY)!
bottomLeftPathNode = SKShapeNode(path: bottomLeftPath)
bottomLeftPathNode.fillColor = SKColor.purple
bottomLeftPathNode.lineWidth = 0
bottomLeftPathNode.position = CGPoint(x: 320, y: 240)
addChild(bottomLeftPathNode)
// TOP LEFT
var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
let topLeftPath = topRightPath.copy(using: &reflectOnX)!
topLeftPathNode = SKShapeNode(path: topLeftPath)
topLeftPathNode.fillColor = SKColor.cyan
topLeftPathNode.lineWidth = 0
topLeftPathNode.position = CGPoint(x: 320, y:240)
addChild(topLeftPathNode)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func arcSegment(center : CGPoint,
radius: CGFloat,
strokeWidth: CGFloat,
gapWidth: CGFloat) -> CGPath
{
let halfStrokeWidth = strokeWidth / 2.0
let outerRadius = radius + halfStrokeWidth
let innerRadius = radius - halfStrokeWidth
let halfGap = gapWidth / 2.0
let outerStartAngle = CGFloat(atan2(sqrt(outerRadius * outerRadius - halfGap * halfGap), halfGap))
let outerEndAngle = CGFloat(atan2(halfGap, sqrt(outerRadius * outerRadius - halfGap * halfGap)))
let innerStartAngle = CGFloat(atan2(halfGap, sqrt(innerRadius * innerRadius - halfGap * halfGap)))
let innerEndAngle = CGFloat(atan2(sqrt(innerRadius * innerRadius - halfGap * halfGap), halfGap))
let path = CGMutablePath()
path.addArc(center: center, radius: outerRadius, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: true)
// Quartz 2D will assume a "moveTo" here
path.addArc(center: CGPoint(x: center.x + radius, y: center.y), radius: halfStrokeWidth, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: false)
path.addArc(center: center, radius: innerRadius, startAngle: innerStartAngle, endAngle: innerEndAngle, clockwise: false)
path.closeSubpath()
return path
}
我在回答您的第一个问题时所用的所有三角函数都是必要的,以便在您想要的曲线上获得平端,并与轴对齐。现在您已经更改并希望圆弧上的端盖圆润,您可以返回到最初的尝试并仅依靠 Quartz 2D 来使用圆润的端盖笔触。代码又简单多了,可以是:
import UIKit
import SpriteKit
import PlaygroundSupport
let radius = CGFloat(100)
let sceneSize = CGSize(width: 640, height: 480)
let sceneView = SKView(frame: CGRect(origin: CGPoint.zero, size: sceneSize))
let scene = SKScene(size: sceneSize)
scene.backgroundColor = UIColor.black
let topRightPath = arcSegment(radius: radius, gapWidth: 25)
let topRightPathNode = SKShapeNode(path: topRightPath)
topRightPathNode.strokeColor = SKColor.white
topRightPathNode.lineWidth = 18
topRightPathNode.lineCap = .round
topRightPathNode.position = CGPoint(x: 320, y: 240)
scene.addChild(topRightPathNode)
var reflectOnY = CGAffineTransform(scaleX: 1.0, y: -1.0)
let bottomRightPath = topRightPath.copy(using: &reflectOnY)!
let bottomRightPathNode = SKShapeNode(path: bottomRightPath)
bottomRightPathNode.strokeColor = SKColor.orange
bottomRightPathNode.lineWidth = 18
bottomRightPathNode.lineCap = .round
bottomRightPathNode.position = CGPoint(x: 320, y: 240)
scene.addChild(bottomRightPathNode)
var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
let bottomLeftPath = bottomRightPath.copy(using: &reflectOnX)!
let bottomLeftPathNode = SKShapeNode(path: bottomLeftPath)
bottomLeftPathNode.strokeColor = SKColor.green
bottomLeftPathNode.lineWidth = 18
bottomLeftPathNode.lineCap = .round
bottomLeftPathNode.position = CGPoint(x: 320, y: 240)
scene.addChild(bottomLeftPathNode)
let topLeftPath = bottomLeftPath.copy(using: &reflectOnY)!
let topLeftPathNode = SKShapeNode(path: topLeftPath)
topLeftPathNode.strokeColor = SKColor.blue
topLeftPathNode.lineWidth = 18
topLeftPathNode.lineCap = .round
topLeftPathNode.position = CGPoint(x: 320, y:240)
scene.addChild(topLeftPathNode)
sceneView.presentScene(scene)
PlaygroundPage.current.liveView = sceneView
PlaygroundPage.current.needsIndefiniteExecution = true
func arcSegment( radius: CGFloat,
gapWidth: CGFloat) -> CGPath
{
let halfGap = gapWidth / 2.0
let path = CGMutablePath()
let startAngle = CGFloat(atan2(sqrt(radius * radius - halfGap * halfGap), halfGap))
let endAngle = CGFloat(atan2(halfGap, sqrt(radius * radius - halfGap * halfGap)))
path.addArc( center: CGPoint.zero,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
return path
}
这是另一组代码,其中生成的路径具有圆角末端,但每个圆弧段都是填充路径而不是单笔划段。这应该允许路径在没有端盖重叠的情况下发生碰撞。
import UIKit
import SpriteKit
import PlaygroundSupport
let radius = CGFloat(100)
let sceneSize = CGSize(width: 640, height: 480)
let sceneView = SKView(frame: CGRect(origin: CGPoint.zero, size: sceneSize))
let scene = SKScene(size: sceneSize)
scene.backgroundColor = UIColor.black
let topRightPath = arcSegment(radius: radius, strokeWidth: 18, gapWidth: 25)
let topRightPathNode = SKShapeNode(path: topRightPath)
topRightPathNode.fillColor = SKColor.white
topRightPathNode.position = CGPoint(x: 320, y: 240)
topRightPathNode.lineWidth = 0
scene.addChild(topRightPathNode)
var reflectOnY = CGAffineTransform(scaleX: 1.0, y: -1.0)
let bottomRightPath = topRightPath.copy(using: &reflectOnY)!
let bottomRightPathNode = SKShapeNode(path: bottomRightPath)
bottomRightPathNode.fillColor = SKColor.orange
bottomRightPathNode.position = CGPoint(x: 320, y: 240)
bottomRightPathNode.lineWidth = 0
scene.addChild(bottomRightPathNode)
var reflectOnX = CGAffineTransform(scaleX: -1.0, y: 1.0)
let bottomLeftPath = bottomRightPath.copy(using: &reflectOnX)!
let bottomLeftPathNode = SKShapeNode(path: bottomLeftPath)
bottomLeftPathNode.fillColor = SKColor.green
bottomLeftPathNode.position = CGPoint(x: 320, y: 240)
bottomLeftPathNode.lineWidth = 0
scene.addChild(bottomLeftPathNode)
let topLeftPath = bottomLeftPath.copy(using: &reflectOnY)!
let topLeftPathNode = SKShapeNode(path: topLeftPath)
topLeftPathNode.fillColor = SKColor.blue
topLeftPathNode.position = CGPoint(x: 320, y:240)
topLeftPathNode.lineWidth = 0
scene.addChild(topLeftPathNode)
sceneView.presentScene(scene)
PlaygroundPage.current.liveView = sceneView
PlaygroundPage.current.needsIndefiniteExecution = true
func arcSegment( radius: CGFloat,
strokeWidth: CGFloat,
gapWidth: CGFloat) -> CGPath
{
let halfStrokeWidth = strokeWidth / 2.0
let outerRadius = radius + halfStrokeWidth
let innerRadius = radius - halfStrokeWidth
let halfGap = gapWidth / 2.0
let outerStartAngle = CGFloat(atan2(sqrt(outerRadius * outerRadius - halfGap * halfGap), halfGap))
let outerEndAngle = CGFloat(atan2(halfGap, sqrt(outerRadius * outerRadius - halfGap * halfGap)))
let innerStartAngle = CGFloat(atan2(halfGap, sqrt(innerRadius * innerRadius - halfGap * halfGap)))
let innerEndAngle = CGFloat(atan2(sqrt(innerRadius * innerRadius - halfGap * halfGap), halfGap))
let leftEndAngle = CGFloat(atan2(sqrt(radius * radius - halfGap * halfGap), halfGap))
let leftEndCapPoint = CGPoint(x: radius * cos(leftEndAngle),
y: radius * sin(leftEndAngle))
let rightEndCapPoint = CGPoint(x: leftEndCapPoint.y,
y: leftEndCapPoint.x)
let path = CGMutablePath()
path.addArc(center: CGPoint.zero,
radius: outerRadius,
startAngle: outerStartAngle,
endAngle: outerEndAngle,
clockwise: true)
path.addArc(center: rightEndCapPoint,
radius: halfStrokeWidth,
startAngle : 0,
endAngle : CGFloat.pi,
clockwise: true)
path.addArc(center: CGPoint.zero, radius: innerRadius, startAngle: innerStartAngle, endAngle: innerEndAngle, clockwise: false)
path.addArc(center: leftEndCapPoint,
radius: halfStrokeWidth,
startAngle : 3.0 * CGFloat.pi / 2.0,
endAngle : CGFloat.pi / 2.0,
clockwise: true)
path.closeSubpath()
return path
}