我试图在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")
}@H_502_6@
请看下面我已经做了些什么:
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
}@H_502_6@
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 */
}
}@H_502_6@
我没有对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)
}
}@H_502_6@
您可以使用moveToX和timingMode
parameter轻松实现这种动画。
在这个答案结尾的下面的新的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])))
}
}@H_502_6@
输出:
更新(这部分开始模拟静态球和动态球左右移动,但没有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
}
}
}@H_502_6@
使用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)
}
}@H_502_6@
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)
}
}@H_502_6@

