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?
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.
path
element pathIndex - 2
should be changed after bodies contact. I put a println
to check pathIndex
just after and the values weren't consistent. Any advice on this? –
Coulombe path[pathIndex-2] = CGPoint(x: 140, y: 90)
. This gives me the result I want sometimes. Other times when running the same code without any changes path[pathIndex-2]
is a different point. The log shows pathIndex
changing between 3 and 4. That's my current issue. I don't when whether the way it's updated is giving the inconsistent values –
Coulombe ballPath
when its in contact/collision with another body. The idea here is to caluculate/use the bodies contact point as some temporary point to get it to next point in ballPath
. I'm not too sure how to go about this. –
Percutaneous ballPath[x]
I would like to move the body to ballPath[x+1]
without it ever reaching ballPath[x]
. A bit like the contact point of the circle body with it's colliding body taking the place of ballPath[x]
temporarily. –
Percutaneous didBeginContact
when contact was established. The code is too a bit too long for this comment. –
Bedard let img = SKTexture(imageNamed: "rectangular"); (firstBody.node! as? SKSpriteNode)?.size = img.size(); firstBody.node!.physicsBody = SKPhysicsBody(texture: img, size: img.size()); firstBody.node!.physicsBody?.allowsRotation = false; changeCircleAction = SKAction.setTexture(img); firstBody.node!.runAction(changeCircleAction);
–
Bedard pointRadius
to be the issue. Initially the pointRadius
was the circle width. But for a rectangle, I tried changing the pointRadius
to the width or height but the result didn't change. The rectangle is larger than the circle' –
Bedard didBeginContact
) between other bodies and the rectangle are not satisfied. Yes, I am using didReachPoint
as you used it in the answer –
Bedard didReachPoint
, but I keep getting the same problem - after circle changes to rectangle all contact conditions between other bodies and the rectangle are not recognized. –
Bedard radiusPoint
. I have a few bodies located at some of the ballPath positions. I realized that at times the ball reaches these points but didBeginContact
does not recognize any contact between the ball and the body at the ballPath position. I believe this is the bigger picture of my problem. Most of my conditions are set on contact, but contact doesn't occur though it looks like it has. Is there a way this can be solved? –
Bedard 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:
© 2022 - 2024 — McMap. All rights reserved.