我试图在Metaballs上重组这个
Github Swift project,这样就可以通过SKActions而不是CABasicAnimation来移动SKShapeNodes来表示这些圈子。
我对viewController中出现的各种Metaball参数(handleLenRate,Spacing等)不感兴趣。我基本上希望能够使用SKActions来指定动画的开始和结束位置。
我不确定如何实现这一点,特别是如何用SKShapeNode与SKActions替换下面的startAnimation函数:
func startAnimation() { let loadingLayer = self.layer as! DBMetaballLoadingLayer loadingAnimation = CABasicAnimation(keyPath: "movingBallCenterX") loadingAnimation!.duration = 2.5 loadingAnimation!.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaSEOut) loadingAnimation!.fromValue = NSValue(CGPoint:fromPoint) loadingAnimation!.toValue = NSValue(CGPoint: toPoint) loadingAnimation!.repeatCount = Float.infinity loadingAnimation!.autoreverses = true loadingLayer.addAnimation(loadingAnimation!,forKey: "loading") }
请看下面我已经做了些什么:
MBCircle类:
struct MBCircle { var center: CGPoint = CGPointZero var radius: CGFloat = 0.0 var frame: CGRect { get { return CGRect(x: center.x - radius,y: center.y - radius,width: 2 * radius,height: 2 * radius) } } } struct DefaultConfig { static let radius: CGFloat = 15.0 static let mv: CGFloat = 0.6 static let maxDistance: CGFloat = 10 * DefaultConfig.radius static let handleLenRate: CGFloat = 2.0 static let spacing: CGFloat = 160.0 }
GameScene类(表示DBMetaballLoadingLayer和DBMetaballLoadingView):
class GameScene: SKScene { private let MOVE_BALL_SCALE_RATE: CGFloat = 0.75 private let ITEM_COUNT = 2 private let SCALE_RATE: CGFloat = 1.0//0.3 private var circlePaths = [MBCircle]() var radius: CGFloat = DefaultConfig.radius var maxLength: CGFloat { get { return (radius * 4 + spacing) * CGFloat(ITEM_COUNT) } } var maxDistance: CGFloat = DefaultConfig.maxDistance var mv: CGFloat = DefaultConfig.mv var spacing: CGFloat = DefaultConfig.spacing { didSet { _adjustSpacing(spacing) } } var handleLenRate: CGFloat = DefaultConfig.handleLenRate var movingBallCenterX : CGFloat = 0.0 { didSet { if (circlePaths.count > 0) { circlePaths[0].center = CGPoint(x: movingBallCenterX,y: circlePaths[0].center.y) } } } func _generalInit() { circlePaths = Array(0..<ITEM_COUNT).map { i in var circlePath = MBCircle() circlePath.center = CGPoint(x: (radius * 10 + spacing) * CGFloat(i),y: radius * (1.0 + SCALE_RATE)) circlePath.radius = i == 0 ? radius * MOVE_BALL_SCALE_RATE : radius circlePath.sprite = SKShapeNode(circleOfRadius: circlePath.radius) circlePath.sprite?.position = circlePath.center circlePath.sprite?.fillColor = UIColor.blueColor() addChild(circlePath.sprite!) return circlePath } } func _adjustSpacing(spacing: CGFloat) { if (ITEM_COUNT > 1 && circlePaths.count > 1) { for i in 1..<ITEM_COUNT { var circlePath = circlePaths[i] circlePath.center = CGPoint(x: (radius*2 + spacing) * CGFloat(i),y: radius * (1.0 + SCALE_RATE)) } } } func _renderPath(path: UIBezierPath) { var shapeNode = SKShapeNode() shapeNode.path = path.CGPath shapeNode.fillColor = UIColor.blueColor() addChild(shapeNode) } func _Metaball(j: Int,i: Int,v: CGFloat,handeLenRate: CGFloat,maxDistance: CGFloat) { let circle1 = circlePaths[i] let circle2 = circlePaths[j] let center1 = circle1.center let center2 = circle2.center let d = center1.distance(center2) var radius1 = circle1.radius var radius2 = circle2.radius if (d > maxDistance) { _renderPath(UIBezierPath(ovalInRect: circle2.frame)) } else { let scale2 = 1 + SCALE_RATE * (1 - d / maxDistance) radius2 *= scale2 _renderPath(UIBezierPath(ovalInRect: CGRect(x: circle2.center.x - radius2,y: circle2.center.y - radius2,width: 2 * radius2,height: 2 * radius2))) } if (radius1 == 0 || radius2 == 0) { return } var u1: CGFloat = 0.0 var u2: CGFloat = 0.0 if (d > maxDistance || d <= abs(radius1 - radius2)) { return } else if (d < radius1 + radius2) { u1 = acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)) u2 = acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)) } else { u1 = 0.0 u2 = 0.0 } let angle1 = center1.angleBetween(center2) let angle2 = acos((radius1 - radius2) / d) let angle1a = angle1 + u1 + (angle2 - u1) * v let angle1b = angle1 - u1 - (angle2 - u1) * v let angle2a = angle1 + CGFloat(M_PI) - u2 - (CGFloat(M_PI) - u2 - angle2) * v let angle2b = angle1 - CGFloat(M_PI) + u2 + (CGFloat(M_PI) - u2 - angle2) * v let p1a = center1.point(radians: angle1a,withLength: radius1) let p1b = center1.point(radians: angle1b,withLength: radius1) let p2a = center2.point(radians: angle2a,withLength: radius2) let p2b = center2.point(radians: angle2b,withLength: radius2) let totalRadius = radius1 + radius2 var d2 = min(v * handeLenRate,p1a.minus(p2a).length() / totalRadius) d2 *= min(1,d * 2 / totalRadius) radius1 *= d2 radius2 *= d2 let cp1a = p1a.point(radians: angle1a - CGFloat(M_PI_2),withLength: radius1) let cp2a = p2a.point(radians: angle2a + CGFloat(M_PI_2),withLength: radius2) let cp2b = p2b.point(radians: angle2b - CGFloat(M_PI_2),withLength: radius2) let cp1b = p1b.point(radians: angle1b + CGFloat(M_PI_2),withLength: radius1) let pathJoinedCircles = UIBezierPath() pathJoinedCircles.moveToPoint(p1a) pathJoinedCircles.addCurveToPoint(p2a,controlPoint1: cp1a,controlPoint2: cp2a) pathJoinedCircles.addLineToPoint(p2b) pathJoinedCircles.addCurveToPoint(p1b,controlPoint1: cp2b,controlPoint2: cp1b) pathJoinedCircles.addLineToPoint(p1a) pathJoinedCircles.closePath() _renderPath(pathJoinedCircles) } func startAnimation() { } override func didMoveToView(view: SKView) { _generalInit() } override func update(currentTime: CFTimeInterval) { /* Called before each frame is rendered */ } }
我没有对CGPointExtension类进行任何更改。
UPDATE
我仍然试图获得Metaball效果,这是我迄今为止取得的进展,基于Alessandro Ornano的建议:
import SpriteKit extension CGPoint { func distance(point: CGPoint) -> CGFloat { let dx = point.x - self.x let dy = point.y - self.y return sqrt(dx * dx + dy * dy) } func angleBetween(point: CGPoint) -> CGFloat { return atan2(point.y - self.y,point.x - self.x) } func point(radians radians: CGFloat,withLength length: CGFloat) -> CGPoint { return CGPoint(x: self.x + length * cos(radians),y: self.y + length * sin(radians)) } func minus(point: CGPoint) -> CGPoint { return CGPoint(x: self.x - point.x,y: self.y - point.y) } func length() -> CGFloat { return sqrt(self.x * self.x + self.y + self.y) } } class GameScene: SKScene { var dBCircle : SKShapeNode! let radiusDBCircle: CGFloat = 10 let radiusBall: CGFloat = 15 var balls = [SKShapeNode]() var distanceBtwBalls : CGFloat = 15 private let SCALE_RATE: CGFloat = 0.3 override func didMoveToView(view: SKView) { // Some parameters let strokeColor = SKColor.orangeColor() let dBHeight = CGRectGetMaxY(self.frame)-84 // 64 navigationController height + 20 reasonable distance let dBStartX = CGRectGetMidX(self.frame)-160 // extreme left let dBStopX = CGRectGetMidX(self.frame)+160 // extreme right let dBWidth = dBStopX - dBStartX let totalBalls = 7 // first and last will be hidden let ballArea = dBWidth / CGFloat(totalBalls-1) distanceBtwBalls = ((ballArea-(radiusBall*2))+radiusBall*2) // Create dbCircle dBCircle = SKShapeNode.init(circleOfRadius: radiusDBCircle) dBCircle.position = CGPointMake(CGRectGetMidX(self.frame),dBHeight) dBCircle.strokeColor = strokeColor dBCircle.name = "dBCircle" dBCircle.fillColor = UIColor.clearColor() addChild(dBCircle) // Make static balls for i in 0..<totalBalls { let ball = SKShapeNode.init(circleOfRadius: radiusBall) ball.position = CGPointMake(dBStartX+(distanceBtwBalls*CGFloat(i)),dBHeight) ball.strokeColor = strokeColor ball.name = "ball" ball.fillColor = UIColor.clearColor() balls.append(ball) if i == 0 || i == totalBalls-1 { ball.hidden = true } addChild(ball) } mediaTimingFunctionEaseInEaSEOutEmulate(dBCircle,dBStartX: dBStartX,dBStopX: dBStopX) } func mediaTimingFunctionEaseInEaSEOutEmulate(node:SKShapeNode,dBStartX:CGFloat,dBStopX:CGFloat) { let actionMoveLeft = SKAction.moveToX(dBStartX,duration:1.7) actionMoveLeft.timingMode = SKActionTimingMode.EaseInEaSEOut let actionMoveRight = SKAction.moveToX(dBStopX,duration:1.7) actionMoveRight.timingMode = SKActionTimingMode.EaseInEaSEOut node.runAction(SKAction.repeatActionForever(SKAction.sequence([actionMoveLeft,actionMoveRight]))) } override func update(currentTime: NSTimeInterval) { var i = 0 self.enumerateChildNodesWithName("ball") { node,stop in let ball = node as! SKShapeNode if CGRectContainsRect(ball.frame,self.dBCircle.frame) { if (ball.actionForKey("zoom") == nil) { let zoomIn = SKAction.scaleTo(1.5,duration: 0.25) let zoomOut = SKAction.scaleTo(1.0,duration: 0.25) let seq = SKAction.sequence([zoomIn,zoomOut]) ball.runAction(seq,withKey: "zoom") } } i += 1 } movingBeziers() } func _renderPath(path: UIBezierPath) { let shapeNode = SKShapeNode(path: path.CGPath) shapeNode.fillColor = UIColor.blueColor() addChild(shapeNode) } func movingBeziers() { _renderPath(UIBezierPath(ovalInRect: dBCircle.frame)) for j in 1..<balls.count { self.latestTestMetaball(j,circleShape: dBCircle,v: 0.6,handleLenRate: 2.0,maxDistance: self.distanceBtwBalls) } } func latestTestMetaball (j: Int,circleShape: SKShapeNode,handleLenRate: CGFloat,maxDistance: CGFloat) { let circle1 = circleShape let circle2 = balls[j] let center1 = circle1.position let center2 = circle2.position let d = center1.distance(center2) var radius1 = circle1.frame.width var radius2 = circle2.frame.width var u1: CGFloat = 0.0 var u2: CGFloat = 0.0 if (d > maxDistance || d <= abs(radius1 - radius2)) { return } else if (d < radius1 + radius2) { u1 = acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)) u2 = acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)) } else { u1 = 0.0 u2 = 0.0 } let angle1 = center1.angleBetween(center2) let angle2 = acos((radius1 - radius2) / d) let angle1a = angle1 + u1 + (angle2 - u1) * v let angle1b = angle1 - u1 - (angle2 - u1) * v let angle2a = angle1 + CGFloat(M_PI) - u2 - (CGFloat(M_PI) - u2 - angle2) * v let angle2b = angle1 - CGFloat(M_PI) + u2 + (CGFloat(M_PI) - u2 - angle2) * v let p1a = center1.point(radians: angle1a,withLength: radius2) let totalRadius = radius1 + radius2 var d2 = min(v * handleLenRate,controlPoint2: cp1b) pathJoinedCircles.addLineToPoint(p1a) pathJoinedCircles.closePath() let shapeNode = SKShapeNode(path: pathJoinedCircles.CGPath) shapeNode.fillColor = UIColor.blueColor() addChild(shapeNode) } }
您可以使用moveToX和timingMode
parameter轻松实现这种动画。
原文链接:https://www.f2er.com/swift/320381.html在这个答案结尾的下面的新的Swift 3翻译。
举个例子,我使用Xcode Sprite-Kit“Hello,World!”官方项目演示:
class GameScene: SKScene { override func didMoveToView(view: SKView) { /* Setup your scene here */ let myLabel = SKLabelNode(fontNamed:"Chalkduster") myLabel.text = "Hello,World!" myLabel.fontSize = 15 myLabel.position = CGPoint(x:CGRectGetMidX(self.frame),y:CGRectGetMidY(self.frame)) self.addChild(myLabel) mediaTimingFunctionEaseInEaSEOutEmulate(myLabel) } func mediaTimingFunctionEaseInEaSEOutEmulate(node:SKLabelNode) { let actionMoveLeft = SKAction.moveToX(CGRectGetMidX(self.frame)-100,duration:1.5) actionMoveLeft.timingMode = SKActionTimingMode.EaseInEaSEOut let actionMoveRight = SKAction.moveToX(CGRectGetMidX(self.frame)+100,duration:1.5) actionMoveRight.timingMode = SKActionTimingMode.EaseInEaSEOut node.runAction(SKAction.repeatActionForever(SKAction.sequence([actionMoveLeft,actionMoveRight]))) } }
输出:
更新(这部分开始模拟静态球和动态球左右移动,但没有Metaball动画)
class GameScene: SKScene { var dBCircle : SKShapeNode! let radiusDBCircle: CGFloat = 10 let radiusBall: CGFloat = 15 private let SCALE_RATE: CGFloat = 0.3 override func didMoveToView(view: SKView) { // Some parameters let strokeColor = SKColor.orangeColor() let dBHeight = CGRectGetMaxY(self.frame)-84 // 64 navigationController height + 20 reasonable distance let dBStartX = CGRectGetMidX(self.frame)-160 // extreme left let dBStopX = CGRectGetMidX(self.frame)+160 // extreme right let dBWidth = dBStopX - dBStartX let totalBalls = 7 // first and last will be hidden let ballArea = dBWidth / CGFloat(totalBalls-1) let distanceBtwBalls = ((ballArea-(radiusBall*2))+radiusBall*2) // Create dbCircle dBCircle = SKShapeNode.init(circleOfRadius: radiusDBCircle) dBCircle.position = CGPointMake(CGRectGetMidX(self.frame),dBHeight) dBCircle.strokeColor = strokeColor dBCircle.name = "dBCircle" dBCircle.fillColor = UIColor.clearColor() addChild(dBCircle) // Make static balls for i in 0..<totalBalls { let ball = SKShapeNode.init(circleOfRadius: radiusBall) ball.position = CGPointMake(dBStartX+(distanceBtwBalls*CGFloat(i)),dBHeight) ball.strokeColor = strokeColor ball.name = "ball" ball.fillColor = UIColor.clearColor() if i == 0 || i == totalBalls-1 { ball.hidden = true } addChild(ball) } mediaTimingFunctionEaseInEaSEOutEmulate(dBCircle,dBStopX: dBStopX) } func mediaTimingFunctionEaseInEaSEOutEmulate(node:SKShapeNode,dBStopX:CGFloat) { let actionMoveLeft = SKAction.moveToX(dBStartX,duration:1.7) actionMoveLeft.timingMode = SKActionTimingMode.EaseInEaSEOut let actionMoveRight = SKAction.moveToX(dBStopX,duration:1.7) actionMoveRight.timingMode = SKActionTimingMode.EaseInEaSEOut node.runAction(SKAction.repeatActionForever(SKAction.sequence([actionMoveLeft,actionMoveRight]))) } override func update(currentTime: NSTimeInterval) { var i = 0 self.enumerateChildNodesWithName("ball") { node,stop in let ball = node as! SKShapeNode if CGRectContainsRect(ball.frame,self.dBCircle.frame) { if (ball.actionForKey("zoom") == nil) { let zoomIn = SKAction.scaleTo(1.5,duration: 0.25) let zoomOut = SKAction.scaleTo(1.0,duration: 0.25) let seq = SKAction.sequence([zoomIn,zoomOut]) ball.runAction(seq,withKey: "zoom") } } i += 1 } } }
使用Metaball动画的新更新:
最后我意识到这个结果,我的目标是使它非常类似于原来的:
可以对时间进行一些变化(例如zoomIn或zoomOut时间值或actionMoveLeft,actionMoveRight时间值),这是代码:
import SpriteKit class GameScene: SKScene { var dBCircle : SKShapeNode! let radiusDBCircle: CGFloat = 10 let radiusBall: CGFloat = 15 private let SCALE_RATE: CGFloat = 0.3 override func didMoveToView(view: SKView) { // Some parameters let strokeColor = SKColor.orangeColor() let dBHeight = CGRectGetMaxY(self.frame)-84 // 64 navigationController height + 20 reasonable distance let dBStartX = CGRectGetMidX(self.frame)-160 // extreme left let dBStopX = CGRectGetMidX(self.frame)+160 // extreme right let dBWidth = dBStopX - dBStartX let totalBalls = 7 // first and last will be hidden let ballArea = dBWidth / CGFloat(totalBalls-1) let distanceBtwBalls = ((ballArea-(radiusBall*2))+radiusBall*2) // Create dbCircle dBCircle = SKShapeNode.init(circleOfRadius: radiusDBCircle) dBCircle.position = CGPointMake(CGRectGetMidX(self.frame),duration:2.5) actionMoveLeft.timingMode = SKActionTimingMode.EaseInEaSEOut let actionMoveRight = SKAction.moveToX(dBStopX,duration:2.5) actionMoveRight.timingMode = SKActionTimingMode.EaseInEaSEOut node.runAction(SKAction.repeatActionForever(SKAction.sequence([actionMoveLeft,actionMoveRight]))) } //MARK: - _Metaball original function func _Metaball(circle2:SKShapeNode,circle1:SKShapeNode,maxDistance: CGFloat,vanishingTime : NSTimeInterval = 0.015) { let center1 = circle1.position let center2 = circle2.position let d = center1.distance(center2) var radius1 = radiusDBCircle var radius2 = radiusBall if (radius1 == 0 || radius2 == 0) { return } var u1: CGFloat = 0.0 var u2: CGFloat = 0.0 if (d > maxDistance || d <= abs(radius1 - radius2)) { return } else if (d < radius1 + radius2) { u1 = acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)) u2 = acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)) } else { u1 = 0.0 u2 = 0.0 } let angle1 = center1.angleBetween(center2) let angle2 = acos((radius1 - radius2) / d) let angle1a = angle1 + u1 + (angle2 - u1) * v let angle1b = angle1 - u1 - (angle2 - u1) * v let angle2a = angle1 + CGFloat(M_PI) - u2 - (CGFloat(M_PI) - u2 - angle2) * v let angle2b = angle1 - CGFloat(M_PI) + u2 + (CGFloat(M_PI) - u2 - angle2) * v let p1a = center1.point(radians: angle1a,withLength: radius1) let p1b = center1.point(radians: angle1b,withLength: radius1) let p2a = center2.point(radians: angle2a,withLength: radius2) let p2b = center2.point(radians: angle2b,withLength: radius2) let totalRadius = radius1 + radius2 var d2 = min(v * handeLenRate,p1a.minus(p2a).length() / totalRadius) d2 *= min(1,d * 2 / totalRadius) radius1 *= d2 radius2 *= d2 let cp1a = p1a.point(radians: angle1a - CGFloat(M_PI_2),withLength: radius1) let cp2a = p2a.point(radians: angle2a + CGFloat(M_PI_2),withLength: radius2) let cp2b = p2b.point(radians: angle2b - CGFloat(M_PI_2),withLength: radius2) let cp1b = p1b.point(radians: angle1b + CGFloat(M_PI_2),withLength: radius1) let pathJoinedCircles = UIBezierPath() pathJoinedCircles.moveToPoint(p1a) pathJoinedCircles.addCurveToPoint(p2a,controlPoint2: cp2a) pathJoinedCircles.addLineToPoint(p2b) pathJoinedCircles.addCurveToPoint(p1b,controlPoint2: cp1b) pathJoinedCircles.addLineToPoint(p1a) pathJoinedCircles.closePath() let shapeNode = SKShapeNode(path: pathJoinedCircles.CGPath) shapeNode.strokeColor = SKColor.orangeColor() shapeNode.fillColor = UIColor.clearColor() addChild(shapeNode) let wait = SKAction.waitForDuration(vanishingTime) self.runAction(wait,completion: { shapeNode.removeFromParent() }) } override func update(currentTime: NSTimeInterval) { var i = 0 self.enumerateChildNodesWithName("ball") { node,stop in let ball = node as! SKShapeNode let enlargeFrame = CGRectMake(ball.frame.origin.x-self.radiusBall*3,ball.frame.origin.y,ball.frame.width+(self.radiusBall*6),ball.frame.height) if CGRectContainsRect(enlargeFrame,duration: 0.25) zoomIn.timingMode = SKActionTimingMode.EaseInEaSEOut let zoomOut = SKAction.scaleTo(1.0,duration: 0.25) let wait = SKAction.waitForDuration(0.8) let seq = SKAction.sequence([zoomIn,zoomOut,wait]) ball.runAction(seq,withKey: "zoom") } } self._Metaball(ball,circle1: self.dBCircle,handeLenRate: 2.0,maxDistance: 4 * self.radiusBall) i += 1 } } } //MARK: - Extensions extension CGPoint { func distance(point: CGPoint) -> CGFloat { let dx = point.x - self.x let dy = point.y - self.y return sqrt(dx * dx + dy * dy) } func angleBetween(point: CGPoint) -> CGFloat { return atan2(point.y - self.y,point.x - self.x) } func point(radians radians: CGFloat,withLength length: CGFloat) -> CGPoint { return CGPoint(x: self.x + length * cos(radians),y: self.y + length * sin(radians)) } func minus(point: CGPoint) -> CGPoint { return CGPoint(x: self.x - point.x,y: self.y - point.y) } func length() -> CGFloat { return sqrt(self.x * self.x + self.y + self.y) } }
Swift 3:
(我对maxDistance做了一些改动:4 * self.radiusBall with maxDistance:5 * self.radiusBall变得更加类似于原来的,但你可以根据需要改变它)
import SpriteKit class GameScene: SKScene { var dBCircle : SKShapeNode! let radiusDBCircle: CGFloat = 10 let radiusBall: CGFloat = 15 private let SCALE_RATE: CGFloat = 0.3 override func didMove(to view: SKView) { let label = self.childNode(withName: "//helloLabel") as? SKLabelNode label?.removeFromParent() self.anchorPoint = CGPoint.zero // Some parameters let strokeColor = SKColor.orange let dBHeight = self.frame.midY let dBStartX = self.frame.midX-260 // extreme left let dBStopX = self.frame.midX+260 // extreme right let dBWidth = dBStopX - dBStartX let totalBalls = 7 // first and last will be hidden let ballArea = dBWidth / CGFloat(totalBalls-1) let distanceBtwBalls = ((ballArea-(radiusBall*2))+radiusBall*2) // Create dbCircle dBCircle = SKShapeNode.init(circleOfRadius: radiusDBCircle) dBCircle.position = CGPoint(x:self.frame.midX,y:dBHeight) dBCircle.strokeColor = strokeColor dBCircle.name = "dBCircle" dBCircle.fillColor = UIColor.clear addChild(dBCircle) // Make static balls for i in 0..<totalBalls { let ball = SKShapeNode.init(circleOfRadius: radiusBall) ball.position = CGPoint(x:dBStartX+(distanceBtwBalls*CGFloat(i)),y:dBHeight) ball.strokeColor = strokeColor ball.name = "ball" ball.fillColor = UIColor.clear if i == 0 || i == totalBalls-1 { ball.isHidden = true } addChild(ball) } mediaTimingFunctionEaseInEaSEOutEmulate(node: dBCircle,dBStopX:CGFloat) { let actionMoveLeft = SKAction.moveTo(x: dBStartX,duration:2.5) actionMoveLeft.timingMode = SKActionTimingMode.easeInEaSEOut let actionMoveRight = SKAction.moveTo(x: dBStopX,duration:2.5) actionMoveRight.timingMode = SKActionTimingMode.easeInEaSEOut node.run(SKAction.repeatForever(SKAction.sequence([actionMoveLeft,vanishingTime : TimeInterval = 0.015) { let center1 = circle1.position let center2 = circle2.position let d = center1.distance(point: center2) var radius1 = radiusDBCircle var radius2 = radiusBall if (radius1 == 0 || radius2 == 0) { return } var u1: CGFloat = 0.0 var u2: CGFloat = 0.0 if (d > maxDistance || d <= abs(radius1 - radius2)) { return } else if (d < radius1 + radius2) { u1 = acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)) u2 = acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)) } else { u1 = 0.0 u2 = 0.0 } let angle1 = center1.angleBetween(point: center2) let angle2 = acos((radius1 - radius2) / d) let angle1a = angle1 + u1 + (angle2 - u1) * v let angle1b = angle1 - u1 - (angle2 - u1) * v let angle2a = angle1 + CGFloat(M_PI) - u2 - (CGFloat(M_PI) - u2 - angle2) * v let angle2b = angle1 - CGFloat(M_PI) + u2 + (CGFloat(M_PI) - u2 - angle2) * v let p1a = center1.point(radians: angle1a,p1a.minus(point: p2a).length() / totalRadius) d2 *= min(1,withLength: radius1) let pathJoinedCircles = UIBezierPath() pathJoinedCircles.move(to: p1a) pathJoinedCircles.addCurve(to: p2a,controlPoint2: cp2a) pathJoinedCircles.addLine(to: p2b) pathJoinedCircles.addCurve(to: p1b,controlPoint2: cp1b) pathJoinedCircles.addLine(to: p1a) pathJoinedCircles.close() let shapeNode = SKShapeNode(path: pathJoinedCircles.cgPath) shapeNode.strokeColor = SKColor.orange shapeNode.fillColor = UIColor.clear addChild(shapeNode) let wait = SKAction.wait(forDuration: vanishingTime) self.run(wait,completion: { shapeNode.removeFromParent() }) } override func update(_ currentTime: TimeInterval) { var i = 0 self.enumerateChildNodes(withName: "ball") { node,stop in let ball = node as! SKShapeNode let enlargeFrame = CGRect(x:ball.frame.origin.x-self.radiusBall*3,y:ball.frame.origin.y,width:ball.frame.width+(self.radiusBall*6),height:ball.frame.height) if enlargeFrame.contains(self.dBCircle.frame) { if (ball.action(forKey: "zoom") == nil) { let zoomIn = SKAction.scale(to: 1.5,duration: 0.25) zoomIn.timingMode = SKActionTimingMode.easeInEaSEOut let zoomOut = SKAction.scale(to: 1.0,duration: 0.25) let wait = SKAction.wait(forDuration: 0.7) let seq = SKAction.sequence([zoomIn,wait]) ball.run(seq,withKey: "zoom") } } self._Metaball(circle2: ball,maxDistance: 5 * self.radiusBall) i += 1 } } } //MARK: - Extensions extension CGPoint { func distance(point: CGPoint) -> CGFloat { let dx = point.x - self.x let dy = point.y - self.y return sqrt(dx * dx + dy * dy) } func angleBetween(point: CGPoint) -> CGFloat { return atan2(point.y - self.y,point.x - self.x) } func point(radians: CGFloat,y: self.y - point.y) } func length() -> CGFloat { return sqrt(self.x * self.x + self.y + self.y) } }