swift sprite-kit core-animation cabasicanimation

swift - reemplazando CALayer y CABasicAnimation con SKScene y SKActions



sprite-kit core-animation (2)

Puede lograr fácilmente este tipo de animación usando moveToX y el parámetro timingMode .

Nueva traducción de Swift 3 a continuación al final de esta respuesta.

Para dar un ejemplo, uso el Xcode Sprite-Kit "Hello, World!" demo oficial del proyecto:

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]))) } }

Salida :

Actualización (Esta parte comienza a emular la bola estática y la bola dinámica moviéndose a izquierda y derecha pero sin animaciones de 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,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 } } }

Nueva actualización con animación de metaball :

Finalmente me di cuenta de este resultado, mi objetivo es hacerlo muy similar al original:

¿Es posible hacer algunas variaciones en los tiempos (por ejemplo, valores de zoomIn o zoomOut time o actionMoveLeft, valores de tiempo de ActionMoveRight), este es el código:

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), 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,dBStartX: dBStartX,dBStopX: dBStopX) } func mediaTimingFunctionEaseInEaseOutEmulate(node:SKShapeNode,dBStartX:CGFloat,dBStopX:CGFloat) { let actionMoveLeft = SKAction.moveToX(dBStartX, 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, v: CGFloat, handeLenRate: CGFloat, 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, controlPoint1: cp1a, controlPoint2: cp2a) pathJoinedCircles.addLineToPoint(p2b) pathJoinedCircles.addCurveToPoint(p1b, controlPoint1: cp2b, 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, self.dBCircle.frame) { if (ball.actionForKey("zoom") == nil) { let zoomIn = SKAction.scaleTo(1.5, 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, v: 0.6, 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:

(He hecho un pequeño cambio en maxDistance: 4 * self.radiusBall con maxDistance: 5 * self.radiusBall para ser más similar al original pero puedes cambiarlo como desees)

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,dBStartX: dBStartX,dBStopX: dBStopX) } func mediaTimingFunctionEaseInEaseOutEmulate(node:SKShapeNode,dBStartX:CGFloat,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,actionMoveRight]))) } //MARK: - _metaball original function func _metaball(circle2:SKShapeNode, circle1:SKShapeNode, v: CGFloat, handeLenRate: CGFloat, maxDistance: CGFloat,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, 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(point: 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.move(to: p1a) pathJoinedCircles.addCurve(to: p2a, controlPoint1: cp1a, controlPoint2: cp2a) pathJoinedCircles.addLine(to: p2b) pathJoinedCircles.addCurve(to: p1b, controlPoint1: cp2b, 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,zoomOut,wait]) ball.run(seq,withKey: "zoom") } } self._metaball(circle2: ball, circle1: self.dBCircle, v: 0.6, handeLenRate: 2.0, 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, 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) } }

Estoy intentando reestructurar este proyecto de Github Swift en Metaballs para que los círculos estén representados por SKShapeNodes movidos por SKActions en lugar de CABasicAnimation .

No estoy interesado en los diversos parámetros de Metaball (handleLenRate, Spacing, etc.) que aparecen en viewController. Básicamente quiero poder especificar una posición de inicio y final para la animación usando SKActions.

No estoy seguro de cómo lograr esto, especialmente cómo reemplazar la función startAnimation continuación con SKShapeNode con SKActions:

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") }

Por favor, mira lo que he podido hacer a continuación:

Clase 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 }

Clase GameScene (que representa DBMetaballLoadingLayer y 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 */ } }

No hice ningún cambio en la clase CGPointExtension .

ACTUALIZAR

Todavía estoy tratando de obtener el efecto metaball, este es mi progreso hasta ahora basado en las sugerencias de 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, v: CGFloat, 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: 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 * handleLenRate, 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() let shapeNode = SKShapeNode(path: pathJoinedCircles.CGPath) shapeNode.fillColor = UIColor.blueColor() addChild(shapeNode) } }


Asumiendo que su pregunta es cómo replicar el mismo comportamiento con nodos y SKAction, creo que esto debería hacerlo.

var shapeNode : SKNode? func startAnimation(){ if let node = self.shapeNode { //assume that we have a node initialized at some point and added to the scene at some x1,y1 coordinates /// we define parameters for the animation let positionToReach = CGPoint(x: 100, y: 100) /// some random position let currentPosition = node.position /// we need the current position to be able to reverse the "animation" let animationDuration = 2.5 //loadingAnimation!.duration = 2.5 /// we define which actions will be run for the node let actionForward = SKAction.moveTo(positionToReach, duration: animationDuration) let actionBackwards = SKAction.moveTo(currentPosition, duration: animationDuration) // we needed two actions to simulate loadingAnimation!.autoreverses = true /// we wrap the actions in a sequence of actions let actionSequence = SKAction.sequence([actionForward, actionBackwards]) /// animations to repeat /// we want to repeat the animation forever let actionToRepeat = SKAction.repeatActionForever(actionSequence) ///loadingAnimation!.repeatCount = Float.infinity /// showtime node.runAction(actionToRepeat) } }

Avíseme si necesito actualizar alguna pieza ya que no la he probado. Aún necesita usar sus valores y objetos reales.

Me he referido referido a ¿Cómo repetiría una acción para siempre en Swift? al hacer esta respuesta.