I'm currently developing a game with a cannon that shoots a projectile, I'm having trouble figuring out how to get the projectile to fire at the angle based on the touch location and how far away it is from the cannon. this is the code i'm using below.
let dx = cannon.position.x - (touchLocation.x)
let dy = cannon.position.y - (touchLocation.y)
let angle = atan2(dy, dx)
bullet.zRotation = angle
bulletspeed = Double.random(in: 1...6)
//let angle1 = Double.random(in: 0.2...5); let angle2 = Double.random(in: 1...4)
// dx must be somewhere between 0.2 to 5
bullet.physicsBody?.applyImpulse(CGVector(dx: -angle , dy: -angle))
this doesn't seem to work and i've resorted to using the angle as my x and y values which works but not well.
I'm trying to get the cannon to fire based on the angle of the touch location and to change the speed/power of the projectile by how far way the touch location is from the cannon. How can I do this?
The vector you need to use takes in dx and dy as parameters. You already have these but you say the speed is too fast. That’s because the length of the vector is the speed.
So in your example the speed can be calculated like...
sqrt(dx*dx+dy*dy)
What you need to do is calculate a ‘unit vector’ that is, a vector with length equal to one.
You can do this by dividing dx and dy by the length of the vector.
So...
touchDX = //your calculation
touchDY = //your calculation
touchLength = sqrt(touchDX*touchDX+touchDY*touchDY)
unitVectorDX = touchDX / touchLength
unitVectorDY = touchDY / touchLength
// now put the speed you want in...
speed = 10
vector = CGVector(dx: unitVectorDX * speed, dy: unitVectorDY * speed)
Now, if you use the vector in your impulse it will have the correct direction and speed.
Quick side note, I’m typing on my iPad so don’t have access to code completion etc... you may be able to do this using APIs on CGVector. I think I remember a ‘unitVector’ property that returns a unit vector. But I may be mistaken.
Related
I have a spinning wheel rotating at an angular speed ω, no acceleration involved, implemented with SpriteKit.
When the user push a button I need to slowly decelerate the wheel from the current angle ∂0 and end-up in a specified angle (lets call it ∂f).
I created associated to it a mass of 2.
I already tried the angularDamping and the SKAction.rotate(toAngle: duration:) but they do not fit my needs because:
With the angularDamping I cannot specify easy the angle ∂f where I want to end up.
With the SKAction.rotate(toAngle: duration:) I cannot start slowing down from the current rotation speed and it doesn't behave natural.
The only remaining approach I tried is by using the SKAction.applyTorque(duration:).
This sounds interesting but I have problems calculating the formula to obtain the correct torque to apply and especially for the inertia and radius of the wheel.
Here is my approach:
I'm taking the starting angular velocity ω as:
wheelNode.physicsBody?.angularVelocity.
I'm taking the mass from wheelNode.physicsBody?.mass
The time t is a constant of 10 (this means that in 10 seconds I want the wheel decelerating to the final angle ∂f).
The deceleration that I calculated as:
let a = -1 * ω / t
The inertia should be: let I = 1/2 * mass * pow(r, 2)*. (see notes regarding the radius please)
Then, finally, I calculated the final torque to apply as: let t = I * a (taking care that is opposite of the current angular speed of the wheel).
NOTE:
Since I don't have clear how to have the radius of the wheel I tried to grab it both from:
the wheelNode.physicsBody?.area as let r = sqrt(wheelNode.physicsBody?.area ?? 0 / .pi)
by converting from pixel to meters as the area documentation says. Then I have let r = self.wheelNode.radius / 150.
Funny: I obtain 2 different values :(
UNFORTUNATLY something in this approach is not working because so far I have no idea how to end up in the specified angle and the wheel doesn't stop anyway as it should (or the torque is too much and spins in the other direction, or is not enough). So, also the torque applied seems to be wrong.
Do you know a better way to achieve the result I need? Is that the correct approach? If yes, what's wrong with my calculations?
Kinematics makes my head hurt, but here you go. I made it to where you can input the amount of rotations and the wheel will rotate that many times as its slowing down to the angle you specify. The other function and extension are there to keep the code relatively clean/readable. So if you just want one giant mess function go ahead and modify it.
• Make sure the node's angularDampening = 0.0
• Make sure the node has a circular physicsbody
// Stops a spinning SpriteNode at a specified angle within a certain amount of rotations
//NOTE: Node must have a circular physicsbody
// Damping should be from 0.0 to 1.0
func decelerate(node: SKSpriteNode, toAngle: CGFloat, rotations: Int) {
if node.physicsBody == nil { print("Node doesn't have a physicsbody"); return } //Avoid crash incase node's physicsbody is nil
var cw:CGFloat { if node.physicsBody!.angularVelocity < CGFloat(0.0) { return -1.0} else { return 1.0} } //Clockwise - using int to reduce if statments with booleans
let m = node.physicsBody!.mass // Mass
let r = CGFloat.squareRoot(node.physicsBody!.area / CGFloat.pi)() // Radius
let i = 0.5 * m * r.squared // Intertia
let wi = node.physicsBody!.angularVelocity // Initial Angular Velocity
let wf:CGFloat = 0 // Final Angular Velocity
let ti = CGFloat.unitCircle(node.zRotation) // Initial Theta
var tf = CGFloat.unitCircle(toAngle) // Final Theta
//Correction constant based on rate of rotation since there seems to be a delay between when the action is calcuated and when it is run
//Without the correction the node stops a little off from its desired stop angle
tf -= 0.00773889 * wi //Might need to change constn
let dt = deltaTheta(ti, tf, Int(cw), rotations)
let a = -cw * 0.5 * wi.squared / abs(dt) // Angular Acceleration - cw used to determine direction
print("A:\(a)")
let time:Double = Double(abs((wf-wi) / a)) // Time needed to stop
let torque:CGFloat = i * a // Torque needed to stop
node.run(SKAction.applyTorque(torque, duration: time))
}
func deltaTheta(_ ti:CGFloat, _ tf:CGFloat, _ clockwise: Int, _ rotations: Int) -> CGFloat {
let extra = CGFloat(rotations)*2*CGFloat.pi
if clockwise == -1 {
if tf>ti { return tf-ti-2*CGFloat.pi-extra }else{ return tf-ti-extra }
}else{
if tf>ti { return tf-ti+extra }else{ return tf+2*CGFloat.pi+extra-ti }
}
}
}
extension CGFloat {
public var squared:CGFloat { return self * self }
public static func unitCircle(_ value: CGFloat) -> CGFloat {
if value < 0 { return 2 * CGFloat.pi + value }
else{ return value }
}
}
For volume controls in most cases it would be better if knob values would change exponential or logarithmical instead of linear.
Where would be the best place within the Knob.swift of the AudioKit AnalogSynthX-Example class to scale the value to any kind of curve?
I think of
func setPercentagesWithTouchPoint(_ touchPoint: CGPoint) {
// Knobs assume up or right is increasing, and down or left is decreasing
let horizontalChange = Double(touchPoint.x - lastX) * knobSensitivity
value += horizontalChange * (maximum - minimum)
let verticalChange = Double(touchPoint.y - lastY) * knobSensitivity
value -= verticalChange * (maximum - minimum)
lastX = touchPoint.x
lastY = touchPoint.y
// TODO: map to exponential/log/any curve if -> knobType is .exp
// ...
delegate?.updateKnobValue(value, tag: self.tag)
}
but maybe someone did invent this wheel already? Thnx!
Thanks for asking. The cutoff knob in the Analog Synth X repo scales logarithmically. You can look at that for a simple example.
Plus, there are new knobs in the AudioKit ROM Player repo. These improved knob controls have adjustable taper curve scaling and range settings:
https://github.com/AudioKit/ROMPlayer
I essentially want the "sprites" to collide when they stick together. However, I don't want the "joint" to be rigid; I essentially want the sprites to be able to move around as long as they are in contact with each other. Imagine two circles connected, and you can move one circle around the other, as long as it remains in contact.
I found this question: How to make one body stick to another moving object in SpriteKit and a lot of other resources that explain how to make sprites stick upon collision, but they all use SKJoints, which are rigid are not really flexible.
I guess another way to phrase it would be to say that I want the sprites to stick, but I want them to be able to "slide" on each other.
Well, I can think of one workaround, but this wouldn't work with non-normal polygons.
Sticking (pun unintended) with your circles example, what if you lock the position of the circle?
let circle1 = center circle
let circle2 = movable circle
Knowing the width of both circles, you can place in the update function that the position should be exactly the distance of:
((circle1.frame.width / 2) + (circle2.frame.width / 2))
If you're up to it, here's some code to help you on your way.
override func update(currentTime: CFTimeInterval) {
{
let distance = hypotf(Float(circle1.position.x - circle2.position.x), Float(circle1.position.y - circle2.position.y))
//calculate circle distances from each other
let radius = ((circle1.frame.width / 2) + (circle2.frame.width / 2))
//distance of circle positions
if distance != radius
{
//if distance is less or more than radius
let pointA = circle1.position
let pointB = circle2.position
let pointC = CGPointMake(pointB.x + 2, pointB.y)
let angle_ab = atan2(pointA.y - pointB.y, pointA.x - pointB.x)
let angle_cb = atan2(pointC.y - pointB.y, pointC.x - pointB.x)
let angle_abc = angle_ab - angle_cb
//get angle of circles from each other using atan2
let vectorx = cos(angle_abc)
let vectory = sin(angle_abc)
//convert angle into vectors
let x = circle1.position.x + radius * vectorx
let y = circle1.position.y + radius * vectory
//get new coordinates from vector, radius and center circle position
circle2.position = CGPointMake(x, y)
//set new position
}
}
Well you need to write code to make sure the movable circle, is well movable.
But, this should work.
I haven't tested this yet though, and I haven't even learned geometry let alone trig in school yet.
If I'm reading your question as you intended it, you can still use joints- just create actions with Inverse Kinematic constraints that allow rotation and translation around the contacting circles' joint.
https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKAction_Ref/index.html#//apple_ref/doc/uid/TP40013017-CH1-SW72
i have a question about moving a Box2D body to a specific position without using this for example.
body->SetTransform(targetVector,body->GetAngle())
I have some code which works for applyForce (here):
const float destinationControl = 0.3f;
b2Vec2 missilePosition = _physicalBody->GetPosition();
b2Vec2 diff = targetPosition - missilePosition;
float dist = diff.Length();
if (dist > 0)
{
// compute the aiming direction
b2Vec2 direction = b2Vec2(diff.x / dist, diff.y / dist);
// get the current missile velocity because we will apply a force to compensate this.
b2Vec2 currentVelocity = _physicalBody->GetLinearVelocity();
// the missile ideal velocity is the direction to the target multiplied by the max speed
b2Vec2 desireVelocity = b2Vec2(direction.x * maxSpeed, direction.y * maxSpeed);
// compensate the current missile velocity by the desired velocity, based on the control factor
b2Vec2 finalVelocity = control * (desireVelocity - currentVelocity);
// transform our velocity into an impulse (get rid of the time and mass factor)
float temp = (_physicalBody->GetMass() / normalDelta);
b2Vec2 finalForce = b2Vec2(finalVelocity.x * temp, finalVelocity.y * temp);
_physicalBody->ApplyForce(finalForce, _physicalBody->GetWorldCenter());
}
But the when the maxSpeed is to high the body move over the point to fast.
So does anyone know how to calculate a force (ApplyForce) or an impluse (ApplyLinearImpulse) to move the body to a target position (very exactly) in a specific time.
Or a solution with the code above. I mean calculate the maxSpeed to move the body in a specific time to the target position.
In my google search i found the interesting article from iforce about projected trajectory
(here). Maybe this could be help too?
Thank you in advance
I think you have it mostly correct, but you are not checking to see if the body will overshoot the target in the next time step. Here is what works for me:
b2Vec2 targetPosition = ...;
float targetSpeed = ...;
b2Vec2 direction = targetPosition - body->GetPosition();
float distanceToTravel = direction.Normalize();
// For most of the movement, the target speed is ok
float speedToUse = targetSpeed;
// Check if this speed will cause overshoot in the next time step.
// If so, we need to scale the speed down to just enough to reach
// the target point. (Assuming here a step length based on 60 fps)
float distancePerTimestep = speedToUse / 60.0f;
if ( distancePerTimestep > distanceToTravel )
speedToUse *= ( distanceToTravel / distancePerTimestep );
// The rest is pretty much what you had already:
b2Vec2 desiredVelocity = speedToUse * direction;
b2Vec2 changeInVelocity = desiredVelocity - body->GetLinearVelocity();
b2Vec2 force = body->GetMass() * 60.0f * changeInVelocity;
body->ApplyForce( force, body->GetWorldCenter(), true );
There is a way for single-time applied force to move by given distance (previous answer suggests that you can correct error in calculation by additional force in future frames):
public static void applyForceToMoveBy(float byX, float byY, Body body) {
force.set(byX, byY);
force.sub(body.getLinearVelocity());
float mass = body.getMass();
force.scl(mass * 30.45f);
body.applyForceToCenter(force, true);
}
Known limitations:
1) LinearVelocity effect was not tested;
2) calculation was tested with body linear damping = 0.5f. Appreciate, if somebody know how to add it into formula;
3) magic number 30.45f - maybe this could be fixed by point 2 or by world frame delta.
Playing around with SKSprites in the IOS SpriteKit, and I basically want to have a sprite randomly move a certain direction for a certain distance, then pick a new random direction and distance.. Simple enough, create a method that generates the random numbers then creates the animation and in the completion block of the animation have it callback the same routine.
THis does work, but it also keeps the animation from moving at the same velocity since the animations are all based on duration.. If the object has to move 100 it moves at 1/2 the speed it moves if the next random tells it to move 200... So how the heck do I have it move with a consistent speed?
#Noah Witherspoon is correct above - use Pythagorus to get a smooth speed:
//the node you want to move
SKNode *node = [self childNodeWithName:#"spaceShipNode"];
//get the distance between the destination position and the node's position
double distance = sqrt(pow((destination.x - node.position.x), 2.0) + pow((destination.y - node.position.y), 2.0));
//calculate your new duration based on the distance
float moveDuration = 0.001*distance;
//move the node
SKAction *move = [SKAction moveTo:CGPointMake(destination.x,destination.y) duration: moveDuration];
[node runAction: move];
Using Swift 2.x I've solved with this little method:
func getDuration(pointA:CGPoint,pointB:CGPoint,speed:CGFloat)->NSTimeInterval {
let xDist = (pointB.x - pointA.x)
let yDist = (pointB.y - pointA.y)
let distance = sqrt((xDist * xDist) + (yDist * yDist));
let duration : NSTimeInterval = NSTimeInterval(distance/speed)
return duration
}
With this method i can use an ivar myShipSpeed and directly call my actions for example :
let move = SKAction.moveTo(dest, duration: getDuration(self.myShip.position,pointB: dest,speed: myShipSpeed))
self.myShip.runAction(move,completion: {
// move action is ended
})
As an extension to #AndyOS's answer, if you're going to move only along one axis (X for example) you can simplify the math by doing this instead:
CGFloat distance = fabs(destination.x - node.position.x);