How to Create Travelling Wave in Spritekit

How to create a wave path Swift

No, there isn't a built in method to build a path from a function, but you can easily write one of your own. In 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
)
}

So, let's pass a function that does one sine curve as it progresses across the width of the of the 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 }
}

Note, the above assumed that you wanted traverse from left to right, building the path defined by the function. You could also do more of a parametric rendition:

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

Then you can modify the x coordinate using sine curve, and just increment 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
) }
}

The virtue of this is that you could also now define any sort of path you want, e.g. a spiral:

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

Here are the Swift 2 renditions of the above:

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

Adding a small UIView to an SKScene

LearnCocos2D definitely deserves credit for this, so if he ever posts an answer I will accept it. Essentially, I was failing to set the frame for the navigation controller's view. Here is my method for adding the smaller view to the scene:

override func didMoveToView(view: SKView) {

anchorPoint = CGPoint(x: 0.5, y: 0.5)

self.name = "Bugs Scene"
backgroundColor = bugsColor

let smallerRect = CGRectMake(400, 24, 600, 720)
let navRect = CGRectMake(0, 0, 600, 720)
let bugsTableView: BugsTVC = BugsTVC()
nav = UINavigationController(rootViewController: bugsTableView)
nav.view.frame = navRect
smallerView = UIView(frame: smallerRect)
smallerView.backgroundColor = UIColor.clearColor()

bugsTableView.view.frame = smallerRect
smallerView.frame = smallerRect
smallerView.addSubview(nav.view)
self.view.addSubview(smallerView)

}

It could probably be refined some but this is working for me. Now if I can only figure out how to remove the view when I transition to another scene!

Detecting collision on only one side of a rectangular physics body - Swift3

You could achieve that by detecting collisions with your rectangle and then deciding whether the collision was with the side of your interest or not. Here is a discussion about how to do that. Good luck!

iOS: How to animate an image along a sine curve?

To animate along a path, you first need to define that path. If you really need a true sine curve, we can show you how to do that, but it's probably easiest to define something that approximates a sine curve using two quadratic bezier curves:

CGFloat width = ...
CGFloat height = ...
CGPoint startPoint = ...

CGPoint point = startPoint;
CGPoint controlPoint = CGPointMake(point.x + width / 4.0, point.y - height / 4.0);
CGPoint nextPoint = CGPointMake(point.x + width / 2.0, point.y);

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point];
[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];

point = nextPoint;
controlPoint = CGPointMake(point.x + width / 4.0, point.y + height / 4.0);
nextPoint = CGPointMake(point.x + width / 2.0, point.y);

[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];

That renders path like so:

Sample Image

Obviously, change startPoint, width, and height to be whatever you want. Or repeat that process of adding more bezier paths if you need more iterations.

Anyway, having defined a path, rather than rendering the path itself, you can create a CAKeyframeAnimation that animates the position of the UIView along that path:

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.duration = 1.0;
animation.path = path.CGPath;
[view.layer addAnimation:animation forKey:@"myPathAnimation"];


Related Topics



Leave a reply



Submit