问题
The movement of a physics body circle is too erratic for what I want to achieve. I would like to restrict it so it follows a certain path touching specific points (or a range of points) as shown in the image below. How can I set the physics properties to traverse a similar path?
回答1:
how to set physics properties for a circle so it follows given path
So essentially you are looking to move a node to a particular point using real-time motion. I have an answer here showing how to do this, however given the number of up votes this question has received, I will provide a more detailed answer.
What the answer I linked to doesn't provide is traversing a path of points. So below I have provided a solution showing how this can be done below. It simply just moves to each point in the path, and each time the node reaches a point, we increment the index to move to the next point. I also added a few variables for travel speed, rate (to make the motion more smooth or static) and whether or not the node should repeat the path. You could further expand upon this solution to better meet the needs of your game. I would definitely consider subclassing a node and building this behavior into it so you can re-use this motion for multiple nodes.
One final note, you may notice the calculation for the impulse varies between my solution below and the answer I linked to above. This is because I am avoiding using angle calculation because they are very expensive. Instead I am calculating a normal so that the calculation is more computationally efficient.
One final note, my answer here explains the use of the rate factor to smooth the motion and leave room for motion distortions.
import SpriteKit
class GameScene: SKScene {
var node: SKShapeNode! //The node.
let path: [CGPoint] = [CGPoint(x: 100, y: 100),CGPoint(x: 100, y: 300),CGPoint(x: 300, y: 300),CGPoint(x: 300, y: 100)] //The path of points to travel.
let repeats: Bool = true //Whether to repeat the path.
var pathIndex = 0 //The index of the current point to travel.
let pointRadius: CGFloat = 10 //How close the node must be to reach the destination point.
let travelSpeed: CGFloat = 200 //Speed the node will travel at.
let rate: CGFloat = 0.5 //Motion smoothing.
override func didMoveToView(view: SKView) {
node = SKShapeNode(circleOfRadius: 10)
node.physicsBody = SKPhysicsBody(circleOfRadius: 10)
node.physicsBody!.affectedByGravity = false
self.addChild(node)
}
final func didReachPoint() {
//We reached a point!
pathIndex++
if pathIndex >= path.count && repeats {
pathIndex = 0
}
}
override func update(currentTime: NSTimeInterval) {
if pathIndex >= 0 && pathIndex < path.count {
let destination = path[pathIndex]
let displacement = CGVector(dx: destination.x-node.position.x, dy: destination.y-node.position.y)
let radius = sqrt(displacement.dx*displacement.dx+displacement.dy*displacement.dy)
let normal = CGVector(dx: displacement.dx/radius, dy: displacement.dy/radius)
let impulse = CGVector(dx: normal.dx*travelSpeed, dy: normal.dy*travelSpeed)
let relativeVelocity = CGVector(dx:impulse.dx-node.physicsBody!.velocity.dx, dy:impulse.dy-node.physicsBody!.velocity.dy);
node.physicsBody!.velocity=CGVectorMake(node.physicsBody!.velocity.dx+relativeVelocity.dx*rate, node.physicsBody!.velocity.dy+relativeVelocity.dy*rate);
if radius < pointRadius {
didReachPoint()
}
}
}
}
I did this pretty quickly so I apologize if there is a mistake. I don't have time now but I will add a gif showing the solution later.
A note about collisions
To fix the erratic movement during a collision, after the 2 bodies collide set the "rate" property to 0 or preferably a very low number to reduce the travel velocity impulse which will give you more room for motion distortion. Then at some point in the future (maybe some time after the collision occurs or preferably when the body is moving slow again) set the rate back to its initial value. If you really want a nice effect, you can actually ramp up the rate value over time from 0 to the initial value to give yourself a smooth and gradual acceleration.
回答2:
Quick implementation using followPath:duration:
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor.blackColor()
let xWidth: CGFloat = CGRectGetMaxX(self.frame)
let yHeight: CGFloat = CGRectGetMaxY(self.frame)
let ball = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(50.0, 50.0))
let offset : CGFloat = ball.size.width / 2
// Moving path
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, offset, yHeight / 2)
CGPathAddLineToPoint(path, nil, xWidth * 2 / 3, yHeight - offset)
CGPathAddLineToPoint(path, nil, xWidth - offset, yHeight * 2 / 3)
CGPathAddLineToPoint(path, nil, xWidth / 2, offset)
CGPathAddLineToPoint(path, nil, offset, yHeight / 2)
// Movement
let moveByPath = SKAction.followPath(path, asOffset: false, orientToPath: false, duration: 4.0)
let moveForever = SKAction.repeatActionForever(moveByPath)
ball.runAction(moveForever)
self.addChild(ball)
}
In GameViewController.swift, I changed the default GameScene.unarchiveFromFile
method to let scene = GameScene(size: view.bounds.size)
for creating the scene's size.
Preview:
来源:https://stackoverflow.com/questions/32352886/how-to-set-physics-properties-for-a-circle-so-it-follows-given-path