I have this node that is controlled by a joystick and when I move the joystick to the left I would like to change the image of the node and the same thing when I move the joystick to the right. How would I do that? Here is the code I have:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if stickActive == true {
//EDIT Code to change texture when moving joystick to left or right.
if joyStick.position.x < base.position.x {
plane.texture = SKTexture(imageNamed: "planeleft")
} else {
plane.texture = SKTexture(imageNamed: "planeright")
}
plane.removeActionForKey("stopaction")
plane.physicsBody?.affectedByGravity = false
xJoystickDelta = location.x - base.position.x
yJoystickDelta = location.y - base.position.y
let v = CGVector(dx: location.x - base.position.x, dy: location.y - base.position.x)
let angle = atan2(v.dy, v.dx)
let length: CGFloat = base.frame.size.height / 2
let xDist: CGFloat = sin(angle - 1.57079633) * length
let yDist: CGFloat = cos(angle - 1.57079633) * length
if (CGRectContainsPoint(base.frame, location)) {
joyStick.position = location
}else {
base.alpha = 0.5 //sets the opacity to 0.5 when the joystick is touched
joyStick.position = CGPointMake(base.position.x - xDist, base.position.y + yDist)
}
}
}
}
override func update(currentTime: CFTimeInterval) {
let xScale = 0.08
let yScale = 0.08
let xAdd = self.xJoystickDelta * CGFloat(xScale)
let yAdd = self.yJoystickDelta * CGFloat(yScale)
self.plane.position.x += xAdd
self.plane.position.y += yAdd
}
I'm not familiar with this, but I know the logic you should use for this.
Complicated:
To detect if the joystick is on the left, you should use a range of the furthest left the joystick can go to the centre of the joystick when it's in the middle. The right side would be from the centre to the furthest right it can go.
Simple:
For the left, make it when the X value if the joystick is less than the default centre position. The right would be an X position greater than the middle.
These would be if statements inside the update function so it's constantly checking for this. In the statements would be something like this:
plane.texture = SKTexture(imageNamed: "TextureName")
Note: This all assumes that the joystick is in a fixed position and has a set centre
Hope this helps!
Im looking to create a swift function to return the degrees (Float) of the users touch on an image (SKSpritenode). In touchesBegan, I know how to detect the x & y positions of my image. The idea is to create a function that takes in these positions and returns the degrees.
Amended - The following code now works:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.anchorPoint = CGPointMake(0.5, 0.5)
myNode.position = CGPointMake(0, -myNode.frame.height / 2)
self.addChild(myNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
if myNode.containsPoint(location) {
print("tapped!")
let origin = myNode.position
let touch = touch.locationInNode(myNode.parent!)
let diffX = touch.x - origin.x
let diffY = touch.y - origin.y
let radians = atan2(diffY, diffX)
let degrees = radians * CGFloat(180 / M_PI)
print("degrees = \(degrees)")
}
}
}
You need to compare the user's touch position to an origin point, which might be the centre of your sprite node for example. Here's some code to get you started:
let origin = CGPoint(x: 0, y: 0)
let touch = CGPoint(x: 100, y: 100)
let diffX = touch.x - origin.x
let diffY = touch.y - origin.y
let radians = atan2(diffY, diffX)
let degrees = radians * CGFloat(180 / M_PI)
That last value – degrees – is the one you want if you want to show users information. If you want to do more calculations, you should probably stick with radians.
So my problem regards the rotation of a sprite. I want it look like it's balancing. When you tap left - the sprite moves left, when you tap right - the sprite moves right. To get a better idea, put a pencil on it's end and try balance it... Yeah... that (but only in x axis).
Currently I am rotating a sprite either clockwise or anti-clockwise dependent upon whether I tap the left or right side of the screen. This is all done using SKActions.
The problem with this is that it results in a very 'jerky' and not particularly realistic motion.
I assume that I want to use physics body and something similar to velocity but my questions are:
the use of velocity is right... isn't it?
would i better off with a volume based sprite (and get it to take into account it's volume and mass) or just a simple edge based sprite?
Thanks in advance!
-- Code -
This is how I'm currently rotating and moving my sprite:
import SpriteKit
enum rotationDirection{
case clockwise
case counterClockwise
case none
}
// Creates GameScene and initialises Sprites in Scene //
class GameScene: SKScene, SKPhysicsContactDelegate {
// Rotation direction variable for ship motion (rotation and movement) //
var currentRotationDirection = rotationDirection.none
var xVelocity: CGFloat = 0
var sprite = SKSpriteNode()
// Setup Scene here //
override func didMoveToView(view: SKView) {
// Background colour //
self.backgroundColor = SKColor.whiteColor()
// sprite Physics + add's sprite //
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
sprite.physicsBody?.affectedByGravity = true
sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addSprite()
// Initialises sprite node and it's properties //
func addSprite() {
// Sprite dimension properities //
sprite.name = "sprite"
sprite = SKSpriteNode(imageNamed: "sprite")
sprite.setScale(0.5)
sprite.position = CGPointMake(frame.midX, 220)
sprite.anchorPoint = CGPoint(x: 0.5, y: 0.25)
sprite.zPosition = 1;
// sprite Physics properties //
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
sprite.physicsBody?.categoryBitMask = UInt32(shipCategory)
sprite.physicsBody?.dynamic = true
sprite.physicsBody?.contactTestBitMask = UInt32(obstacleCategory)
sprite.physicsBody?.collisionBitMask = 0
self.addChild(sprite)
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Defines UI Touch //
let touch = touches.first as UITouch!
let touchPosition = touch.locationInNode(self)
// Sets up Inital Rotation Direction //
let newRotationDirection : rotationDirection = touchPosition.x < CGRectGetMidX(self.frame) ? .clockwise : .counterClockwise
// Left or Right movement based on touch //
if touchPosition.x < CGRectGetMidX(self.frame) {xVelocity = -75}
else {xVelocity = 75}
// Clockwise or anticlockwise rotation based on touch //
if currentRotationDirection != newRotationDirection && currentRotationDirection != .none {
reverseRotation()
currentRotationDirection = newRotationDirection
}
else if (currentRotationDirection == .none) {
setupRotationWith(direction: newRotationDirection)
currentRotationDirection = newRotationDirection
}
}
func reverseRotation() {
let oldRotateAction = sprite.actionForKey("rotate")
let newRotateAction = SKAction.reversedAction(oldRotateAction!)
sprite.runAction(newRotateAction(), withKey: "rotate")
}
func stopRotation() {
sprite.removeActionForKey("rotate")
}
func setupRotationWith(direction direction: rotationDirection){
let angle : CGFloat = (direction == .clockwise) ? CGFloat(M_PI) : -CGFloat(M_PI)
let rotate = SKAction.rotateByAngle(angle, duration: 2)
let repeatAction = SKAction.repeatActionForever(rotate)
sprite.runAction(repeatAction, withKey: "rotate")
}
override func update(currentTime: CFTimeInterval) {
let rate: CGFloat = 0.3; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
let relativeVelocity: CGVector = CGVector(dx:xVelocity-sprite.physicsBody!.velocity.dx, dy:0);
sprite.physicsBody!.velocity=CGVector(dx:sprite.physicsBody!.velocity.dx+relativeVelocity.dx*rate, dy:0);
}
I think what your are going to want to look into is acceleration. Right now you are adjusting verlocityX with a static amount using:
if touchPosition.x < CGRectGetMidX(self.frame) {
xVelocity = -75
}
else {
xVelocity = 75
}
Doing this will result in your jerky motion. Which is actually just linear. Using acceleration you can avoid this.
For this you'll need two more variables for maxVelocity and acceleration. You want a maxVelocity probably to limit the velocity. Each frame you'll need to increase the velocity with the acceleration amount.
I suggest you try something like this:
velocity = 0 // No movement
acceleration = 2 // Increase velocity with 2 each frame
maxVelocity = 75 // The maximum velocity
if touchPosition.x < CGRectGetMidX(self.frame) {
xVelocity += acceleration
if xVelocity <= -maxVelocity {
xVelocity = -maxVelocity
}
}
else {
xVelocity -= acceleration
if xVelocity >= maxSpeed {
xVelocity = maxSpeed
}
}
Increasing your velocity like this will result with a rotation speed of 2 in the first frame, than 4 in the second and 6 in the thirds etc. So it will have an increasing effect. While at the same time, when trying to reverse it you wont have a jerky effect either. Say the velocity is at 50, you first decrease it to 48, 46, 44, etc. Not until the 0 point you will actually start rotating the other way.
I am trying to make a simple bubble tapped game. I have a created a SKSpriteKid node in my code. The problem is I want to sprite to disappear when user tap on it. I can't seem to removeparent() the node. How do I tackle this?
I have created a sprite with bubble1 as the name.
func bubblePressed(bubble1: SKSpriteNode) {
print("Tapped")
bubble1.removeFromParent()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
if (touchedNode.name == "bubble1") {
touchedNode.removeFromParent()
print("hit")
}
}
}
//the node's creation.
func bubblesinView() {
//create the sprite
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
//spawn in the X axis min is size, max is the total height minus size bubble
let width_bubble_1 = random(min: bubble1.size.width/2, max: size.width - bubble1.size.height/2)
//y: size.height * X, X is 0 at bottom page to max
//spawn position, let x be random
bubble1.position = CGPoint(x: width_bubble_1, y: size.height)
// Add the monster to the scene
addChild(bubble1)
// Determine speed of the monster from start to end
let bubblespeed = random(min: CGFloat(1.1), max: CGFloat(4.0))
//this tell the bubble to move to the given position, changeble x must be a random value
let moveAction = SKAction.moveTo(CGPoint(x: width_bubble_1, y: size.height * 0.1), duration: NSTimeInterval(bubblespeed))
let actionMoveDone = SKAction.removeFromParent()
bubble1.runAction(SKAction.sequence([moveAction, actionMoveDone]))
}
I am really trying to make this work. Thanks in advance!
If you want to use the name property of the node, you should set it to something. The runtime cannot figure out the variable name as the name. So in bubblesInView function after
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
you should do
bubble1.name = "bubble1"
You just forgot to name the node :
//create the sprite
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
bubble1.name = "bubble1"
I've been wracking my brain for a couple of days trying to come up with a way to move a player from there current position to new position using Swift and SpriteKit. Sounds relatively easy.
Now, I know I can use a CGPath and a SKAction to move the player along a path, but what I need to know is how to create the path for the player to move along.
I need the player to move through a predetermined radius as it turns towards the new point first as it moves, let me demonstrate...
So, the red circle is the player and their current orientation, the large circle is the turn radius and the red crosses are possible points where the player wants to move to (obviously you'd only have one at any point in time, but the idea is demonstrate the difference in movement between one possible point and another)
Also, the player could move left or right depending in which ever path is shortest to the target point.
What I tried (sorry the list is kind of short)...
Basically, I know the current position/orientation of the player; I know the radius of the turn circle and I know the point I want to move to. I need to calculate the arc through which the player will need to initially move through to orientate themselves to the new point (tacking on a CGPathAddLineToPoint to the end of the arc should be trivial)
Other then spending copious amounts of time reading through the docs, Googling, reading blog posts and tutorials, I also tried looping through a series of angles from a start angle through a given iteration level (+/-0.5 degrees for example) and calculating the angle between the current point and next point on the circle and comparing that to the angle of the current point to the target point and basically selecting the angle with the lowest difference/delta ...
So, the two red circles represent two points on the circle, the blue line represents the angle between them, the green line represents the angle from the first point to the target point.
Let's just say, that while that might possibly work, I'm kind of horrified at the idea and hope that it might be possible to come up with a better/faster solution.
I'm not sure if something like CGPathAddArcToPoint would help, as it would create an arc from my players current position to the target point, rather then allow the player to move through a turning circle.
Once the player leaves the turning circle, I'm not particular fussed if the move in a straight line or not (ie they could curve slightly to the target point), but I'm currently focused on trying to calculate the required arc needed to get the player started.
Sorry, my maths is pretty poor, so, please, be nice
The code "currently" looks something like (a complete mess)
func pointTowards(point thePoint: CGPoint) {
// Need to calculate the direction of the turn
//let angle = atan2(thePoint.y - self.position.y, thePoint.x - self.position.x) - CGFloat(180.0.toRadians());
let angle = angleBetween(startPoint: self.position, endPoint: thePoint) - CGFloat(180.0.toRadians())
if (self.zRotation < 0) {
// self.zRotation
// self.zRotation = self.zRotation + M_PI * 2;
}
let rotateTo: SKAction = SKAction.rotateToAngle(angle, duration: 1, shortestUnitArc: true)
rotateTo.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(rotateTo)
let offset = CGPoint(x: rotorBlur.position.x, y: rotorBlur.position.y + (rotorBlur.size.width / 2))
let radius = rotorBlur.size.width / 2.0
var points: [AnglesAndPoints] = self.pointsOnCircleOf(
radius: radius,
offset: offset);
let centerPoint = CGPoint(x: offset.x + radius, y: offset.y + radius)
var minAngle = CGFloat.max
var minDelta = CGFloat.max
for var p: Int = 1; p < points.count; p++ {
let p1 = points[p - 1].point
let p2 = points[p].point
let point = angleBetween(startPoint: p1, endPoint: p2) - CGFloat(180.0.toRadians())
let target = angleBetween(startPoint: p1, endPoint: thePoint) - CGFloat(180.0.toRadians())
let delta = target - point
if delta < minDelta {
minDelta = delta
minAngle = points[p - 1].angle
}
}
println("projected: \(minAngle); delta = \(minDelta)")
if let pathNode = pathNode {
pathNode.removeFromParent()
}
//points = self.pointsOnCircleOf(
// radius: rotorBlur.size.width / 2.0,
// offset: CGPoint(x: 0, y: rotorBlur.size.width / 2));
let path = CGPathCreateMutable()
CGPathAddArc(
path,
nil,
0,
rotorBlur.size.width / 2,
rotorBlur.size.width / 2,
CGFloat(-180.0.toRadians()),
minAngle,
true)
pathNode = SKShapeNode()
pathNode?.path = path
pathNode?.lineWidth = 1.0
pathNode?.strokeColor = .lightGrayColor()
addChild(pathNode!)
}
func pointsOnCircleOf(radius r : CGFloat, offset os: CGPoint) -> [AnglesAndPoints] {
var points: [AnglesAndPoints] = []
let numPoints = 360.0 * 2.0
let delta = 360.0 / numPoints
for var degrees: Double = 0; degrees < numPoints; degrees += delta {
var point: CGPoint = pointOnCircle(angle: CGFloat(degrees.toRadians()), radius: r)
point = CGPoint(x: point.x + os.x, y: point.y + os.y)
points.append(AnglesAndPoints(angle: CGFloat(degrees.toRadians()), point: point))
}
return points
}
func pointOnCircle(angle radians:CGFloat, radius theRadius:CGFloat) -> CGPoint {
return CGPointMake((cos(radians) * theRadius),
(sin(radians) * theRadius));
}
func angleBetween(startPoint p1: CGPoint, endPoint p2: CGPoint) -> CGFloat {
return atan2(p2.y - p1.y, p2.x - p1.x) //- CGFloat(180.0.toRadians());
}
Basically, I went about pre-calculating the points on a circle of a given radius with a given offset, which is just horrible and if I had the time right now, would re-work it so that the point was dynamically created (or I could cache the values some how and simply translate them), but as I said, this was such a horrible idea I really wanted to find a different way and abandon this approach
I'm pretty sure that the current code doesn't take into the players current orientation and it should be supplying a start angle and direction (counter/clockwise) in which to iterate, but I've gotten to the point I'd like to see if their is simply a better solution then this before trying to fix any more issues with it
Funny, I actually have motion in my game almost exactly as you described except that instead of always going clock-wise when on the right side and counter-clock when on the left, it will pick the closer path.
So I grabbed some of the code and modified it sightly to fit your description. It will move left when the target point is to the left of the player, else it will move right. You can also set the speed of the node, as well as the radius and position of the "orbit."
My implementation however does not use SKActions and paths to move. Everything is done dynamically in real-time which allows for collisions with the moving objects and greater motion control. However if you absolutely need to use paths with SKActions let me know and I'll try to come up with a solution. Essentially what it comes down to is finding the arc to the tangent points (which the code already does to an extent).
The physics calculations come from my two answerers here, and here.
The way the implementation works is that it first determines the final destination point, as well as the angular distance to the best tangent point using a secondary circle to find the tangent points. Then using centripetal motion, the node moves along the path to the tangent point and then switches to linear motion to finish moving to the end destination.
Below is the code for the GameScene:
import SpriteKit
enum MotionState { case None, Linear, Centripetal }
class GameScene: SKScene {
var node: SKShapeNode!
var circle: SKShapeNode!
var angularDistance: CGFloat = 0
var maxAngularDistance: CGFloat = 0
let dt: CGFloat = 1.0/60.0 //Delta Time
var centripetalPoint = CGPoint() //Point to orbit.
let centripetalRadius: CGFloat = 60 //Radius of orbit.
var motionState: MotionState = .None
var invert: CGFloat = 1
var travelPoint: CGPoint = CGPoint() //The point to travel to.
let travelSpeed:CGFloat = 200 //The speed at which to travel.
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
circle = SKShapeNode(circleOfRadius: centripetalRadius)
circle.strokeColor = SKColor.redColor()
circle.hidden = true
self.addChild(circle)
}
func moveToPoint(point: CGPoint) {
travelPoint = point
motionState = .Centripetal
//Assume clockwise when point is to the right. Else counter-clockwise
if point.x > node.position.x {
invert = -1
//Assume orbit point is always one x radius right from node's position.
centripetalPoint = CGPoint(x: node.position.x + centripetalRadius, y: node.position.y)
angularDistance = CGFloat(M_PI)
} else {
invert = 1
//Assume orbit point is always one x radius left from node's position.
centripetalPoint = CGPoint(x: node.position.x - centripetalRadius, y: node.position.y)
angularDistance = 0
}
}
final func calculateCentripetalVelocity() {
let normal = CGVector(dx:centripetalPoint.x + CGFloat(cos(self.angularDistance))*centripetalRadius,dy:centripetalPoint.y + CGFloat(sin(self.angularDistance))*centripetalRadius);
let period = (CGFloat(M_PI)*2.0)*centripetalRadius/(travelSpeed*invert)
self.angularDistance += (CGFloat(M_PI)*2.0)/period*dt;
if (self.angularDistance>CGFloat(M_PI)*2)
{
self.angularDistance = 0
}
if (self.angularDistance < 0) {
self.angularDistance = CGFloat(M_PI)*2
}
node.physicsBody!.velocity = CGVector(dx:(normal.dx-node.position.x)/dt ,dy:(normal.dy-node.position.y)/dt)
//Here we check if we are at the tangent angle. Assume 4 degree threshold for error.
if abs(maxAngularDistance-angularDistance) < CGFloat(4*M_PI/180) {
motionState = .Linear
}
}
final func calculateLinearVelocity() {
let disp = CGVector(dx: travelPoint.x-node.position.x, dy: travelPoint.y-node.position.y)
let angle = atan2(disp.dy, disp.dx)
node.physicsBody!.velocity = CGVector(dx: cos(angle)*travelSpeed, dy: sin(angle)*travelSpeed)
//Here we check if we are at the travel point. Assume 15 point threshold for error.
if sqrt(disp.dx*disp.dx+disp.dy*disp.dy) < 15 {
//We made it to the final position! Code that happens after reaching the point should go here.
motionState = .None
println("Node finished moving to point!")
}
}
override func update(currentTime: NSTimeInterval) {
if motionState == .Centripetal {
calculateCentripetalVelocity()
} else if motionState == .Linear {
calculateLinearVelocity()
}
}
func calculateMaxAngularDistanceOfBestTangent() {
let disp = CGVector(dx: centripetalPoint.x - travelPoint.x, dy: centripetalPoint.y - travelPoint.y)
let specialCirclePos = CGPoint(x: (travelPoint.x+centripetalPoint.x)/2.0, y: (travelPoint.y+centripetalPoint.y)/2.0)
let specialCircleRadius = sqrt(disp.dx*disp.dx+disp.dy*disp.dy)/2.0
let tangentPair = getPairPointsFromCircleOnCircle(centripetalPoint, radiusA: centripetalRadius, pointB: specialCirclePos, radiusB: specialCircleRadius)
let tangentAngle1 = (atan2(tangentPair.0.y - centripetalPoint.y,tangentPair.0.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
let tangentAngle2 = (atan2(tangentPair.1.y - centripetalPoint.y,tangentPair.1.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
if invert == -1 {
maxAngularDistance = tangentAngle2
} else {
maxAngularDistance = tangentAngle1
}
}
//Not mine, modified algorithm from https://stackoverflow.com/q/3349125/2158465
func getPairPointsFromCircleOnCircle(pointA: CGPoint, radiusA: CGFloat, pointB: CGPoint, radiusB: CGFloat) -> (CGPoint,CGPoint) {
let dX = (pointA.x - pointB.x)*(pointA.x - pointB.x)
let dY = (pointA.y - pointB.y)*(pointA.y - pointB.y)
let d = sqrt(dX+dY)
let a = (radiusA*radiusA - radiusB*radiusB + d*d)/(2.0*d);
let h = sqrt(radiusA*radiusA - a*a);
let pointCSub = CGPoint(x:pointB.x-pointA.x,y:pointB.y-pointA.y)
let pointCScale = CGPoint(x: pointCSub.x*(a/d), y: pointCSub.y*(a/d))
let pointC = CGPoint(x: pointCScale.x+pointA.x, y: pointCScale.y+pointA.y)
let x3 = pointC.x + h*(pointB.y - pointA.y)/d;
let y3 = pointC.y - h*(pointB.x - pointA.x)/d;
let x4 = pointC.x - h*(pointB.y - pointA.y)/d;
let y4 = pointC.y + h*(pointB.x - pointA.x)/d;
return (CGPoint(x:x3, y:y3), CGPoint(x:x4, y:y4));
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touchPos = (touches.first! as! UITouch).locationInNode(self)
node = SKShapeNode(circleOfRadius: 10)
node.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
node.physicsBody = SKPhysicsBody(circleOfRadius: 10)
self.addChild(node)
moveToPoint(touchPos)
calculateMaxAngularDistanceOfBestTangent() //Expensive!
circle.hidden = false
circle.position = centripetalPoint
}
}
Note that the circle you see is another node I added to the scene to make the motion more visible; you can easily just remove it. When debugging you might also find it useful to add nodes at the tangent points. The tangentPair tuple inside the calculateMaxAngularDistanceOfBestTangent function contains the two tangent points.
Additionally note that finding the tangent points/angles is expensive but it only happens each time you need to move to a new point. If however you game requires constantly moving to a new point, using this algorithm repeatedly on many nodes can be costly (always profile before assuming this though). Another way to check when to move from centripetal motion to linear motion is to check if the velocity vector is approaching the end position as shown below. This is less accurate but allows you to remove the calculateMaxAngularDistanceOfBestTangent function entirely.
let velAngle = atan2(node.physicsBody!.velocity.dy,node.physicsBody!.velocity.dx)
let disp = CGVector(dx: travelPoint.x-node.position.x, dy: travelPoint.y-node.position.y)
let dispAngle = atan2(disp.dy,disp.dx)
//Here we check if we are at the tangent angle. Assume 4 degree threshold for error.
if velAngle != 0 && abs(velAngle - dispAngle) < CGFloat(4*M_PI/180) {
motionState = .Linear
}
Lastly let me know if you need to use paths with SKActions, regardless I think I will update this last part showing how this is done (unless someone beats me to it! And as I mentioned earlier the code I posted does this to an extent.) I don't have time to right now but hopefully I get a chance to soon! I hope something mentioned in this answer helps you. Good luck with your game.
Update including SKActions
The code below shows getting the same exact effect except this time using SKActions to animate a CGPath to the tangent angle then to the final destination point. It is much simpler as there is no longer a manual calculation of centripetal and linear motion, however because it is an animation you lose the dynamic real-time motion control that the solution above provides.
class GameScene: SKScene {
var centripetalPoint = CGPoint() //Point to orbit.
let centripetalRadius: CGFloat = 60 //Radius of orbit.
var travelPoint: CGPoint = CGPoint() //The point to travel to.
var travelDuration: NSTimeInterval = 1.0 //The duration of action.
var node: SKShapeNode!
var circle: SKShapeNode!
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
circle = SKShapeNode(circleOfRadius: centripetalRadius)
circle.strokeColor = SKColor.redColor()
circle.hidden = true
self.addChild(circle)
}
//Not mine, modified algorithm from https://stackoverflow.com/q/3349125/2158465
func getPairPointsFromCircleOnCircle(pointA: CGPoint, radiusA: CGFloat, pointB: CGPoint, radiusB: CGFloat) -> (CGPoint,CGPoint) {
let dX = (pointA.x - pointB.x)*(pointA.x - pointB.x)
let dY = (pointA.y - pointB.y)*(pointA.y - pointB.y)
let d = sqrt(dX+dY)
let a = (radiusA*radiusA - radiusB*radiusB + d*d)/(2.0*d);
let h = sqrt(radiusA*radiusA - a*a);
let pointCSub = CGPoint(x:pointB.x-pointA.x,y:pointB.y-pointA.y)
let pointCScale = CGPoint(x: pointCSub.x*(a/d), y: pointCSub.y*(a/d))
let pointC = CGPoint(x: pointCScale.x+pointA.x, y: pointCScale.y+pointA.y)
let x3 = pointC.x + h*(pointB.y - pointA.y)/d;
let y3 = pointC.y - h*(pointB.x - pointA.x)/d;
let x4 = pointC.x - h*(pointB.y - pointA.y)/d;
let y4 = pointC.y + h*(pointB.x - pointA.x)/d;
return (CGPoint(x:x3, y:y3), CGPoint(x:x4, y:y4));
}
func moveToPoint(point: CGPoint) {
travelPoint = point
//Assume clockwise when point is to the right. Else counter-clockwise
if point.x > node.position.x {
centripetalPoint = CGPoint(x: node.position.x + centripetalRadius, y: node.position.y)
} else {
centripetalPoint = CGPoint(x: node.position.x - centripetalRadius, y: node.position.y)
}
let disp = CGVector(dx: centripetalPoint.x - travelPoint.x, dy: centripetalPoint.y - travelPoint.y)
let specialCirclePos = CGPoint(x: (travelPoint.x+centripetalPoint.x)/2.0, y: (travelPoint.y+centripetalPoint.y)/2.0)
let specialCircleRadius = sqrt(disp.dx*disp.dx+disp.dy*disp.dy)/2.0
let tangentPair = getPairPointsFromCircleOnCircle(centripetalPoint, radiusA: centripetalRadius, pointB: specialCirclePos, radiusB: specialCircleRadius)
let tangentAngle1 = (atan2(tangentPair.0.y - centripetalPoint.y,tangentPair.0.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
let tangentAngle2 = (atan2(tangentPair.1.y - centripetalPoint.y,tangentPair.1.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, node.position.x, node.position.y)
if travelPoint.x > node.position.x {
CGPathAddArc(path, nil, node.position.x+centripetalRadius, node.position.y, centripetalRadius, CGFloat(M_PI), tangentAngle2, true)
} else {
CGPathAddArc(path, nil, node.position.x-centripetalRadius, node.position.y, centripetalRadius, 0, tangentAngle1, false)
}
CGPathAddLineToPoint(path, nil, travelPoint.x, travelPoint.y)
let action = SKAction.followPath(path, asOffset: false, orientToPath: false, duration: travelDuration)
node.runAction(action)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touchPos = (touches.first! as! UITouch).locationInNode(self)
node = SKShapeNode(circleOfRadius: 10)
node.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(node)
moveToPoint(touchPos)
circle.hidden = false
circle.position = centripetalPoint
}
}