sprite-kit swift2 cgpath sin

sprite kit - Cómo crear una ruta de onda Swift



sprite-kit swift2 (1)

Quiero que mi nodo viaje en una onda sinusoidal, e intenté usarlo para CGPath. ¿Cómo creo un CGPath que sigue una curva sinusoidal? ¿Hay alguna otra forma que no sea encontrar manualmente los puntos en la curva, o podría simplemente pasar en una función sinusoidal?

let action = SKAction.followPath(<the sine path>, asOffset: true, orientToPath: true, duration: 5)

¿Podría hacerse esto a través de Bezier Paths y luego convertirse en CGPaths? Gracias.


No, no existe un método integrado para construir una ruta a partir de una función, pero puede escribir fácilmente una propia. En Swift 3:

/// Build path within rectangle /// /// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`. /// /// - parameter rect: The `CGRect` of points on the screen. /// /// - parameter count: How many points should be rendered. Defaults to `rect.size.width`. /// /// - parameter function: A closure that will be passed an floating point number between zero and one and should return a return value between zero and one as well. private func path(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGFloat)) -> UIBezierPath { let numberOfPoints = count ?? Int(rect.size.width) let path = UIBezierPath() path.move(to: convert(point: CGPoint(x: 0, y: function(0)), in: rect)) for i in 1 ..< numberOfPoints { let x = CGFloat(i) / CGFloat(numberOfPoints - 1) path.addLine(to: convert(point: CGPoint(x: x, y: function(x)), in: rect)) } return path } /// Convert point with x and y values between 0 and 1 within the `CGRect`. /// /// - parameter point: A `CGPoint` value with x and y values between 0 and 1. /// - parameter rect: The `CGRect` within which that point should be converted. private func convert(point: CGPoint, in rect: CGRect) -> CGPoint { return CGPoint( x: rect.origin.x + point.x * rect.size.width, y: rect.origin.y + rect.size.height - point.y * rect.size.height ) }

Entonces, pasemos una función que hace una curva sinusoidal a medida que progresa a lo largo del width de la rect :

func sinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath { // note, since sine returns values between -1 and 1, let''s add 1 and divide by two to get it between 0 and 1 return path(in: rect, count: count) { (sin($0 * .pi * 2.0) + 1.0) / 2.0 } }

Tenga en cuenta que lo anterior supuso que quería recorrer de izquierda a derecha, construyendo la ruta definida por la función. También podría hacer más de una versión paramétrica:

/// Build path within rectangle /// /// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`. /// /// - parameter rect: The `CGRect` of points on the screen. /// /// - parameter count: How many points should be rendered. Defaults to `rect.size.width` or `rect.size.width`, whichever is larger. /// /// - parameter function: A closure that will be passed an floating point number between zero and one and should return a `CGPoint` with `x` and `y` values between 0 and 1. private func parametricPath(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGPoint)) -> UIBezierPath { let numberOfPoints = count ?? max(Int(rect.size.width), Int(rect.size.height)) let path = UIBezierPath() let result = function(0) path.move(to: convert(point: CGPoint(x: result.x, y: result.y), in: rect)) for i in 1 ..< numberOfPoints { let t = CGFloat(i) / CGFloat(numberOfPoints - 1) let result = function(t) path.addLine(to: convert(point: CGPoint(x: result.x, y: result.y), in: rect)) } return path }

Luego puedes modificar la coordenada x usando la curva sinusoidal, y simplemente incrementar y :

func verticalSinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath { // note, since sine returns values between -1 and 1, let''s add 1 and divide by two to get it between 0 and 1 return parametricPath(in: rect, count: count) { CGPoint( x: (sin($0 * .pi * 2.0) + 1.0) / 2.0, y: $0 ) } }

La virtud de esto es que ahora también podría definir cualquier tipo de ruta que desee, por ejemplo, una espiral:

func spiralPath(in rect: CGRect, count: Int? = nil) -> UIBezierPath { return parametricPath(in: rect, count: count) { t in let r = 1.0 - sin(t * .pi / 2.0) return CGPoint( x: (r * sin(t * 10.0 * .pi * 2.0) + 1.0) / 2.0, y: (r * cos(t * 10.0 * .pi * 2.0) + 1.0) / 2.0 ) } }

Aquí están las representaciones de Swift 2 de lo anterior:

/// Build path within rectangle /// /// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`. /// /// - parameter rect: The `CGRect` of points on the screen. /// /// - parameter count: How many points should be rendered. Defaults to `rect.size.width`. /// /// - parameter function: A closure that will be passed an floating point number between zero and one and should return a return value between zero and one as well. private func path(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGFloat)) -> UIBezierPath { let numberOfPoints = count ?? Int(rect.size.width) let path = UIBezierPath() path.moveToPoint(convert(point: CGPoint(x: 0, y: function(0)), rect: rect)) for i in 1 ..< numberOfPoints { let x = CGFloat(i) / CGFloat(numberOfPoints - 1) path.addLineToPoint(convert(point: CGPoint(x: x, y: function(x)), rect: rect)) } return path } /// Convert point with x and y values between 0 and 1 within the `CGRect`. /// /// - parameter point: A `CGPoint` value with x and y values between 0 and 1. /// - parameter rect: The `CGRect` within which that point should be converted. private func convert(point point: CGPoint, rect: CGRect) -> CGPoint { return CGPoint( x: rect.origin.x + point.x * rect.size.width, y: rect.origin.y + rect.size.height - point.y * rect.size.height ) } func sinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath { // note, since sine returns values between -1 and 1, let''s add 1 and divide by two to get it between 0 and 1 return path(in: rect, count: count) { (sin($0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0 } } /// Build path within rectangle /// /// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`. /// /// - parameter rect: The `CGRect` of points on the screen. /// /// - parameter count: How many points should be rendered. Defaults to `rect.size.width`. /// /// - parameter function: A closure that will be passed an floating point number between zero and one and should return a `CGPoint` with `x` and `y` values between 0 and 1. private func parametricPath(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGPoint)) -> UIBezierPath { let numberOfPoints = count ?? max(Int(rect.size.width), Int(rect.size.height)) let path = UIBezierPath() let result = function(0) path.moveToPoint(convert(point: CGPoint(x: result.x, y: result.y), rect: rect)) for i in 1 ..< numberOfPoints { let t = CGFloat(i) / CGFloat(numberOfPoints - 1) let result = function(t) path.addLineToPoint(convert(point: CGPoint(x: result.x, y: result.y), rect: rect)) } return path } func verticalSinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath { // note, since sine returns values between -1 and 1, let''s add 1 and divide by two to get it between 0 and 1 return parametricPath(in: rect, count: count) { CGPoint( x: (sin($0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0, y: $0 ) } } func spiralPath(in rect: CGRect, count: Int? = nil) -> UIBezierPath { return parametricPath(in: rect, count: count) { t in let r = 1.0 - sin(t * CGFloat(M_PI_2)) return CGPoint( x: (r * sin(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0, y: (r * cos(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0 ) } }