2015-12-26 54 views
3

我希望我的節點以正弦曲線波行進,並且我嘗試將它用於CGPath。 如何創建遵循正弦曲線的CGPath?除了手動找到曲線上的點還有其他方法,還是隻能傳遞正弦函數?如何創建波浪路徑Swift

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

難道這通過貝塞爾路徑來完成,然後轉換成CGPaths? 謝謝。

+0

應該如何波和路徑被定向?只有水平/垂直或任意兩點之間?我認爲它只是橫向的 – Qbyte

+0

從右到左最好是@Qbyte – Dieblitzen

回答

6

不,沒有內置的方法來從函數中構建路徑,但您可以輕鬆地編寫自己的一個路徑。在斯威夫特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 
    ) 
} 

所以,讓我們通過,做一個正弦曲線的功能,因爲它跨越的的rectwidth進展:

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

注意,上述假設你想遍歷從左到右,構建函數定義的路徑。你還可以做更多的參數繪製的:

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

然後,你可以修改x協調使用正弦曲線,只是增加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 
    ) } 
} 

這樣做的優點是,你現在也能定義你想要的任何路徑,例如螺旋:

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

以下是上述的夫特2個引渡:

/// 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 
     ) 
    } 
} 
+0

@Dieblitzen - 如果它看起來是「顛倒的」,這是因爲這是爲UIKit座標系設計的。如果你想換一種方式,只需調整'convertPoint:rect:',以便不使用'rect.origin.y +'的'y'值+ rect.size.height - point.y * rect.size.height ',你可以使用'rect.origin.y + point.y * rect.size.height'。 – Rob

+0

謝謝!這看起來像一個天才的工作!儘管目前它是從屏幕中心開始的。我應該怎麼做才能改變它的起始位置?我需要編輯哪些代碼才能更改曲線的幅度和頻率?再次感謝! @Rob – Dieblitzen

+0

@Dieblitzen - 就定位而言,這只是場景可見部分的CGRect的一個函數,所以,你可能會傳錯錯誤的CGRect。我使用'SKView'方法'convertPoint:toScene:'將可視'view.bounds'的座標轉換爲'SKScene'的座標。例如。 https://gist.github.com/robertmryan/680113628a06705e17dd – Rob