What is the best way to decelerate at a speed of any given value (e.g. accelerationDropOff = 1.5f) before it reaches the end destination?
public bool MoveFromCurrentToPosition(float x, float y, float velocity, float acceleration, float deltaTime)
{
float startX = positionX, startY = positionY;
float endX = x, endY = y;
float deltaX = endX - startX;
float deltaY = endY - startY;
float speed = velocity;
float elapsed = 0.01f;
// On starting movement
float distance = (float)Math.Sqrt(Math.Pow(deltaX, 2) + Math.Pow(deltaY, 2));
float directionX = deltaX / distance;
float directionY = deltaY / distance;
isMoving = true;
// On update
if (isMoving == true)
{
positionX += directionX * speed * elapsed;
positionY += directionY * speed * elapsed;
if (currentAcceleration == 0)
{
currentAcceleration = acceleration;
}
else if (currentAcceleration >= maxAcceleration) // <- Don't accelerate anymore
{
speed *= currentAcceleration;
positionX += (directionX * speed) * deltaTime; positionY += (directionY * speed) * deltaTime;
bounds.X = (int)positionX; bounds.Y = (int)positionY;
}
else
{
currentAcceleration += acceleration;
speed *= currentAcceleration;
positionX += (directionX * speed) * deltaTime; positionY += (directionY * speed) * deltaTime;
bounds.X = (int)positionX; bounds.Y = (int)positionY;
}
float a = x, o = y;
double angle = Math.Atan2(o, a);
angle = angle * 180 / Math.PI;
movementDirection = (float)(180 - angle);
// Decelerate before reaching the end point
if (Math.Sqrt(Math.Pow(positionX - startX, 2) + Math.Pow(positionY - startY, 2)) >= distance)
{
positionX = endX;
positionY = endY;
isMoving = false;
return true;
}
}
return false;
}
I have been stuck on this problem for a hour or two and Math.exe is not responding. Can anyone point me in the correct direction please?
It seems as if you are mixing up speed (velocity) and acceleration. Speed is the change of position with respect to a given time frame. Acceleration is the change of speed with respect to a given time frame. For a constant acceleration, position and velocity change as follows:
v1 = v0 + a * t
x1 = x0 + v0 * t + 1/2 * a * t^2
v0, v1 and x0, x1 are the velocities and positions at the beginning and end of the time frame, respectively, a is the acceleration, t is the time frame length. This is the exact formula if you assume constant acceleration over the period of the time frame. Often, you find approximations like the following, which introduce some integration errors:
v1 = v0 + a * t
x1 = x0 + v1 * t
I would suggest to use the exact formulas.
As far as I understand your question, you want to find an acceleration, such that a body moving at initial velocity v0 stops after travelling d length units.
This gives you the following equations:
0 = v0 + a * t //target velocity of 0
d = 0 + v0 * t + 1/2 * a * t^2 //travel distance of d
The solution is:
a = -1/2 * v0^2 / d
The time needed for this motion is:
t = 2 * d / v0
So calculate the acceleration once at the beginning of the deccelerating movement and then update current position and velocity with the formulas above.
Some additional hints for your code:
If you want to square a variable x, use x * x instead of Math.pow(x, 2). It is easier to read and has a better performance.
If you already use XNA, then use its Vector2 structure. This makes a lot of things much easier. You can just add two vectors and don't need to care about each component separately. There are methods to get the length of a vector, and so on.
Related
I am moving an object in a parabolic arc this way:
public IEnumerator ParabolicMovement()
{
Vector3 startPos;
Vector3 targetPos;
float speed = 6;
float arcHeight = 3;
Vector3 nextPos = Vector3.zero;
while (transform.position != targetPos)
{
// Compute the next position, with arc added in
float x0 = startPos.x;
float x1 = targetPos.x;
float dist = x1 - x0;
float nextX = Mathf.MoveTowards(transform.position.x, x1, (speed) * Time.deltaTime);
float baseY = Mathf.Lerp(startPos.y, targetPos.y, (nextX - x0) / dist);
float arc = arcHeight * (nextX - x0) * (nextX - x1) / (-0.25f * dist * dist);
nextPos = new Vector3(nextX, baseY + arc, transform.position.z);
transform.position = nextPos;
yield return null;
}
}
This works, but I now want to take this and make it happen over a specific amount of time. I removed the loop from the original method and broke it up into two separate methods to accomplish this:
public IEnumerator BeginJumpOverTime()
{
float duration = 1f;
float startTime = Time.time;
float endTime = startTime + duration;
while (Time.time <= endTime)
{
float tNormalized = Mathf.Clamp((Time.time - startTime) / duration, 0f, 1f);
Vector2 newXAndY = CalculateXAndY(tNormalized);
transform.position = newXAndY;
yield return null;
}
}
public Vector2 CalculateXAndY(float t)
{
Vector3 startPos = GameEngine.Instance.battleManager.TurnHero.transform.position;
Vector3 targetPos = GameEngine.Instance.battleManager.TargetEnemy.transform.position;
float speed = 6;
float arcHeight = 3;
Vector3 nextPos = Vector3.zero;
// Compute the next position to make the parabola
float x0 = startPos.x;
float x1 = targetPos.x;
float dist = x1 - x0;
float nextX = Mathf.MoveTowards(transform.position.x, x1, (speed) * (Time.deltaTime) );
float baseY = Mathf.Lerp(startPos.y, targetPos.y, (nextX - x0) / dist);
float arc = arcHeight * (nextX - x0) * (nextX - x1) / (-0.25f * dist * dist);
nextPos = new Vector3(nextX, baseY + arc, transform.position.z);
return (nextPos);
}
I'm pretty certain this concept should work, I just can't seem to figure out where to factor tNormalized when it's passed into CalculateXandY(). Is anyone math savvy able to assist me with this? Thanks a ton!
Kind regards,
So, Dani in his slightly new video -> "Making a Game, But I Only Have 3 Days" (https://youtu.be/S7Dl6ATRK2M) made a enemy which has a bow and arrow (at 5:39). I tried to recreate that but had no luck... I also can't find the website that he used... Today I found this https://physics.stackexchange.com/questions/56265/how-to-get-the-angle-needed-for-a-projectile-to-pass-through-a-given-point-for-t. It worked very well but still had problems if the target was far away and also it wasn't as accurate. The code so far is
float CalculateAngle()
{
float gravity = Physics.gravity.magnitude;
float deltaX = targetPositionMod.x - currentPosition.x;
float deltaY = targetPositionMod.y - currentPosition.y;
float RHSFirstPart = (velocity * velocity) / (gravity * deltaX);
float RHSSecondPart = Mathf.Sqrt(
((velocity * velocity) * ((velocity * velocity) - (2 * gravity * deltaY))
/ (gravity * gravity * deltaX * deltaX))
- 1);
float tanθ = RHSFirstPart - RHSSecondPart;
float angle = Mathf.Atan2(tanθ, 1) * Mathf.Rad2Deg;
if (angle < 0) return angle;
return -angle;
}
The -angle is because the forward axis starts points up when the x-rotation is negative (Unity). Maybe the reason of this not working as intended is that I am not that good at this kind of Physics (Part of that is me being only 14). Maybe the problem is in the code, maybe it is the formula. Any help is appreciated.
Thanks...
Edit:
The Archer class is:
using UnityEngine;
using System;
public class Archer : MonoBehaviour
{
[SerializeField] float velocity = default;
[SerializeField] Transform target = default;
[SerializeField] GameObject arrowPrefab = default;
[SerializeField] float coolDown = default;
Vector3 targetPositionMod;
Vector3 currentPosition;
Vector3 targetPosition;
float countDown = 0f;
void Start()
{
countDown = coolDown;
UpdateVariables();
}
void Update()
{
UpdateVariables();
SetAngle();
ShootBullet();
}
void UpdateVariables()
{
currentPosition = transform.position;
targetPositionMod = Mod(target.position);
targetPosition = target.position;
targetPosition.x /= 10;
targetPosition.y /= 10;
targetPosition.z /= 10;
countDown -= Time.deltaTime;
}
void SetAngle()
{
Vector3 direction = targetPosition - currentPosition;
Quaternion lookRotation = Quaternion.LookRotation(direction);
Vector3 rotation = lookRotation.eulerAngles;
rotation.x = (float) CalculateAngle();
transform.rotation = Quaternion.Euler(rotation.x, rotation.y, 0f);
}
void ShootBullet()
{
if (!(countDown <= 0f)) return;
countDown = coolDown;
GameObject arrow = Instantiate(arrowPrefab, transform.position, transform.rotation);
Rigidbody Rigidbody = arrow.GetComponent<Rigidbody>();
Rigidbody.AddForce(transform.forward * velocity, ForceMode.Impulse);
}
double CalculateAngle()
{
double gravity = Physics.gravity.magnitude;
double deltaX = targetPositionMod.x - currentPosition.x;
double deltaY = targetPositionMod.y - currentPosition.y;
double RHSFirstPart = (velocity * velocity) / (gravity * deltaX);
double RHSSecondPart = Math.Sqrt(
(((velocity * velocity) * ((velocity * velocity) - (2 * gravity * deltaY))
/ (gravity * gravity * deltaX * deltaX))
- 1));
double tanθ = RHSFirstPart - RHSSecondPart;
double angle = Math.Atan2(tanθ, 1) * Mathf.Rad2Deg;
if (angle < 0) return angle;
return -angle;
}
Vector3 Mod(Vector3 Vec)
{
if (Vec.x < 0) Vec.x -= 2 * Vec.x;
if (Vec.y < 0) Vec.y -= 2 * Vec.y;
if (Vec.z < 0) Vec.z -= 2 * Vec.z;
Vec.x /= 10;
Vec.y /= 10;
Vec.z /= 10;
return Vec;
}
}
Ok, as I can see, your implementation of formula from StackExchange is right, but you have to remember two things:
In unity there is a 3D world, so horizontal distance is not just pos1.x - pos2.x, but
Mathf.Sqrt( deltaX * deltaX + deltaZ * deltaZ ), where deltaX = targetPositionMod.x - currentPosition.x and deltaZ = targetPositionMod.z - currentPosition.z
In computer implementation you have no 100% accuracy of math, so some problems can appear because of computational accuracy. And it can have affect on big distances. You can try to use double instead of float or find another implementation for arctangent function (I think, this can really help). But try this (second) advice only if first didn't help. It's harder to implement and it slows computations a bit.
Algorithm:
Step 1: Set up a function that calculates the appropriate solution of a quadratic equation
a*x^2 + b*x + c = 0
double quadratic_root(a,b,c){
D = b^2 - 4*a*c
return ( - b - Math.Sqrt(D) ) / (2 * a)
}
Step 2: Input
current.x
current.y
current.z
target.x
target.y
target.z
velocity
gravity
Step 3: Calculate coefficients of the quadratic polynomial:
dist = Math.Sqrt( (target.x - current.x)^2 + (target.y - current.y)^2 )
a = gravity * dist^2 / (2 * velocity^2)
b = -dist
c = target.z - current.z + a
Step 4:
theta = Math.Atan2( quadratic_root(a,b,c), 1 )
Calculation behind the algorithm. You are in three space. The current position has coordinates
x = current.x
y = current.y
z = current.z
and the target has coordinates
x = target.x
y = target.y
z = target.z
Assume the angle between the initial velocity and the horizontal plane is theta. The magnitude of the projection of the distance between the current position and the target onto the horizontal $x,y-$plane is
dist = sqrt( (target.x - current.x)^2 - (target.y - current.y)^2 )
You are given the velocity magnitude velocity. Then, the speed with which the shadow (i.e. the orthogonal projection) of the arrow moves along the horizontal line between the source and the target is the magnitude of the shadow (i.e. the orthogonal projection) of the actual velocity
velocity * cos(theta)
The vertical speed of the arrow is then
velocity * sin(theta)
So the motion along dist follows the equation
dist = time * velocity * cos(theta)
and hence
time = dist / (velocity * cos(theta))
In the vertical direction, the motions is described by the equation
z = current.z + time * velocity * sin(theta) - time^2 * gravity / 2
You are interested in the time for which the arrow hits the target, which has vertical coordinate target.z, so
target.z = current.z + time * velocity * sin(theta) - time^2 * gravity / 2
The equation can be written as:
0 = - (target.z - current.z) + time * velocity * sin(theta) - time^2 * gravity / 2
We already know that
time = dist / (velocity * cos(theta))
so
0 = - (target.z - current.z) + dist * velocity * sin(theta) / (velocity * cos(theta)) - dist^2 * gravity / ( 2 * (velocity * cos(theta))^2 )
which can be slightly simplified to
0 = - (target.z - current.z) + dist * sin(theta) / cos(theta) - gravity * dist^2 / ( 2 * (velocity * cos(theta))^2 )
Because 1/( cos(theta)^2 ) = 1 + ( tan(theta) )^2 we obtain the quadratic in tan(theta) equation
a * ( tan(theta) )^2 + b * tan(theta) + c = 0
where
a = gravity * dist^2 / (2 * velocity^2)
b = - dist
c = target.z - current.z + a
I'm working on a script which rotates the camera diagonally (3D x & 3D y axis) around an player object. Inputs are following:
mouse z-axis for the y-axis rotation around the object
mouse x-axis for the diagonal "over-the-shoulder" rotation, thus modifying both y and x of camera rotation:
demo of my current camera script
It works, however, I can't get to clamp the y-axis rotation for the camera. X-axis works as wished. Watch the video above to see the problem at the end. It overrotates on the y axis so that the camera faces the player in the complete wrong direction. Maybe someone with a functioning brain can come up with a solution? Would be awesome as hell, thanks in advance!
Here's my script:
void Update() {
mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
mouseY = -(Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime);
rotationXAxis += mouseY;
rotationXAxis = ClampAngle(rotationXAxis, -30f, 30f);
float rotationYAxis = rotationXAxis;
rotationYAxis = ClampAngle(rotationYAxis, 0f, 30f);
Quaternion fromRotation = Quaternion.Euler(transform.rotation.eulerAngles.x, transform.rotation.eulerAngles.y, 0);
Quaternion toRotation = Quaternion.Euler(rotationXAxis, transform.rotation.eulerAngles.y - rotationYAxis * Time.deltaTime, 0);
Quaternion rotation = toRotation;
Vector3 negDistance = new Vector3(xPosOffset, yPosOffset, -distance);
Vector3 position = rotation * negDistance + player.position;
transform.rotation = rotation;
transform.position = position;
player.Rotate(Vector3.up * mouseX);
mouseY = Mathf.Lerp(mouseY, 0, Time.deltaTime);
}
float ClampAngle(float angle, float min, float max) {
if (angle < -360F)
angle += 360F;
if (angle > 360F)
angle -= 360F;
return Mathf.Clamp(angle, min, max);
}
I try to explain you in pseudo.
Sinus and cosinus are basicaly same but are offset by 90degrees.
What this function are producing is -1 .. 1 number what is -100% .. 100%
basicaly what you need is
var _x,_y,_z; //original position
var deg; // 0..360
x = _x * sin(deg);
y = _y * cos(deg);
z = _z;
This was rotate around world [0,0,0]
If you want to rotate around another axis
var a_x,a_y,a_z;
x = _x + a_x * sin(deg);
y = _y + a_y * cos(deg);
z = _z + a_z;
Biggest key is to preserve default location [_x,_y,_z]
Do not do this mistake
x = x * sin(deg);
Precisely it is a matrix
_x _y _z
x [cos -sin 0]
y [sin cos 0]
z [0 0 1]
x = _x * cos(deg) + _y * -sin(deg) + _z * 0;
y = _x * sin(deg) + _y * cos(deg) + _z * 0;
z = _x * 0 + _y * 0 + _z * 1;
Look at 3d matrix for your axis.
https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions
Try some thing like this
void Update(Player player) {
_position = player.position;
mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
mouseY = -(Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime);
_delta_x = mouseX - _position.x;
_delta_y = mouseY - _position.y;
_delta_z = 0;
_rot_x = ClampAngle(mouseX, mouseY, -30f, 30f);
_rot_y = ClampAngle(mouseX, mouseY, -30f, 30f);
_rot_z = 0;
Vector3 position = new Vector3(_delta_x, _delta_y, _delta_z);
position = position.rotate (rot_x, rot_y, rot_z);
position = position.add(_position.x,_position.y,_position.z);
player.position = position;
}
I am making a game where bullets can start orbiting a player. The orbits are circular, and not elliptical. Bullets have a drag factor of 0.
At first, I simply calculated the next position in the orbit, and divided the delta position by fixedDeltaTime to obtain its new velocity.
Unfortunately, it meant that fast bullets instead follow polygonal paths, and often miss a target.
I wanted to improve the precision by giving them an inward force, and a velocity nearly tangential to the orbit, so that they follow parabola segments around the player instead of straight lines.
The parabola segments are defined by their start and end point, and their vertices, which must lay on the orbit arc, with a velocity equal to the velocity I was previous using ((endpos - startpos) / fixedDeltaTime).
To calculate the parabola, I calculate the midway points on the arc and segment, and their difference is proportional to the force applied.
so we use the following names
fdt = fixedDeltaTime
t = fdt / 2
f = the force applied during the incoming physics frame
v0 = the velocity at the start of the frame
v1 = the velocity at the middle of the frame (at the vertex of the parabola)
dp1 = the relative position at the middle of the frame (midpos - startpos) and vertex of the parabola
dp2 = the relative position at the end of the frame (endpos - startpos)
The force is defined by these two equations:
// at vertex, only the "lateral" velocity remains
v1 = dp2 / fdt
// the difference between dp2 / 2 and dp1 is what the force can apply over half a frame
dp2 / 2 - dp1 = f * 0.5tt
therefore
(dp2 / 2 - dp1) / (0.5 * t * t) = f
(dp2 / 2 - dp1) / (0.5 * fdt/2 * fdt/2) = f
(dp2 - dp1 * 2) * 4 / (fdt * fdt) = f
//v0 is then easily calculated
v0 = v1 - t * force
v0 = (dp2 / fdt) - force * (fdt / 2)
We then get this working code:
Vector3 startPos = _rigidbody.position;
if (firstFrame == false && Vector3.Distance(predictedNextFramePos, startPos) > 0.01f)
Debug.Log("WTF");
Vector3 nextPosition;
GetLocalOrbitPos(nextTime, out nextPosition);
nextPosition += _parent.GetPosition();
float fdt = Time.fixedDeltaTime;
float halfTime = (time + nextTime) / 2f;
Vector3 halfPosition;
GetLocalOrbitPos(halfTime, out halfPosition);
halfPosition += _parent.GetPosition();
Vector3 dp2 = nextPosition - startPos;
Vector3 dp1 = halfPosition - startPos;
Vector3 force = (dp2 - 2 * dp1) * 4 / (fdt * fdt);
Vector3 v0 = (dp2 / fdt) - (force * fdt / 2f);
Vector3 deltaPosPredicted = PhysicsTools.GetMovement(v0, force, fdt);
if (Vector3.Distance(deltaPosPredicted, dp2) > 0.001f)
Debug.Log("position prediction error: " + Vector3.Distance(deltaPosPredicted, dp2));
predictedNextFramePos = deltaPosPredicted + startPos;
Vector3 deltaHPosPredicted = PhysicsTools.GetMovement(v0, force, fdt / 2f);
if (Vector3.Distance(deltaHPosPredicted, dp1) > 0.001f)
Debug.Log("position prediction error: " + Vector3.Distance(deltaHPosPredicted, dp1));
//drawing the startpos, midpos, endpos triangle
Debug.DrawLine(startPos, startPos + dp2, Color.magenta, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos + dp2, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
//drawing v0 and force
Debug.DrawLine(startPos, startPos + force, Color.gray, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + v0, Color.blue, Time.fixedDeltaTime * 2);
//drawing the parabola arc
{
Vector3 pos = startPos;
Vector3 vel = v0;
for (int i = 0; i < 10; i++)
{
Vector3 offset = PhysicsTools.GetMovementUpdateVelocity(ref vel, force, Time.fixedDeltaTime / 10f);
Debug.DrawLine(pos, pos + offset, Color.green, Time.fixedDeltaTime * 2);
pos += offset;
}
}
// Old version
// Vector3 deltaPosition = nextPosition - _rigidbody.position;
// Vector3 velocity = deltaPosition / t;
// SetPhysicsState(_rigidbody.position, velocity, time);
//Applying results
SetPhysicsState(startPos, v0, time);
_rigidbody.AddForce(force / 2f, ForceMode.Acceleration);
I am using my physics helper class
public static class PhysicsTools
{
public static Vector3 GetMovement(Vector3 velocity, Vector3 force, float time)
{
return (velocity * time) + 0.5f * force * (time * time);
}
public static Vector3 GetMovementUpdateVelocity(ref Vector3 velocity, Vector3 force, float time)
{
Vector3 ret = (velocity * time) + 0.5f * force * (time * time);
velocity += force * time;
return ret;
}
}
Everything works fine, but if, and only if, I divide the force by two when applying it. My own simulation using PhysicsTools does not require such tampering.
Here's a picture of one of my tests, with the force factor applied to both the physics engine and the PhysicsTools simulation. You can see that the simulated lines go off into the distance, but not the actual projectile, which stays in its weird pentagram, as it should.
Here we can see it working as intended (still with the applied force reduced)
My question, why would I need to divide that damned force by two?
Well here folks is what happen when you make assumptions.
I assumed that ForceMode.Continuous meant that the force would be applied continuously through the frame. That is not the case.
The unity physics engine is incapable of any kind of continuous acceleration or parabola casting. Any object moves in straight lines, and AddForce simply modifies the velocity right then and there.
It turns out that simply dividing the force by two was enough to reset the starting velocity to my previous linear solution to the problem, and that the only reason that objects seemed to react outside of the polygon was that my bullet collider was much wider than I thought it was.
Please read this post for more information: https://answers.unity.com/questions/696068/difference-between-forcemodeforceaccelerationimpul.html
The only solution to the problem is to increase the physics framerate, or to use your own raycasting solution, which comes with a slew of other problems.
How do I calculate the angle of a trajectory to hit the target, without knowing the velocity. I only know the max height, offset height and the distance to the target.
This is what I got so far (I don't know how to calculate offset height ):
float GetAngle(Vector3 startLocation, Vector3 endLocation, float maxHeight)
{
float distance = Mathf.Sqrt(Mathf.Pow(startLocation.x - endLocation.x,2) + Mathf.Pow(startLocation.z - endLocation.z,2));
float offsetHeight = endLocation.y - startLocation.y;
//how do I calculate offset height in this equation ?
return -Mathf.Atan (4 * maxHeight/ distance ) + Mathf.PI;
}
I use this to calculate the velocity (works fine I only need the correct angle):
float LaunchVelocity (Vector3 startLocation, Vector3 endLocation, float angle)
{
float range = Mathf.Sqrt(Mathf.Pow(startLocation.x - endLocation.x,2) + Mathf.Pow(startLocation.z - endLocation.z,2));
float offsetHeight = endLocation.y - startLocation.y;
float gravity = Physics.gravity.y;
float velocity = range * range * gravity;
velocity /= range * Mathf.Sin(2 * angle) + 2 * offsetHeight * Mathf.Pow(Mathf.Cos(angle),2);
return Mathf.Sqrt(velocity);
}
I got the solution:
float GetAngle(float height, Vector3 startLocation, Vector3 endLocation)
{
float range = Mathf.Sqrt(Mathf.Pow(startLocation.x - endLocation.x,2) + Mathf.Pow(startLocation.z - endLocation.z,2));
float offsetHeight = endLocation.y - startLocation.y;
float g = -Physics.gravity.y;
float verticalSpeed = Mathf.Sqrt(2 * gravity * height);
float travelTime = Mathf.Sqrt(2 * (height - offsetHeight) / g) + Mathf.Sqrt(2 * height / g);
float horizontalSpeed = range / TravelTime;
float velocity = Mathf.Sqrt(Mathf.Pow(verticalSpeed,2) + Mathf.Pow(horizontalSpeed, 2));
return -Mathf.Atan2(verticalSpeed / velocity, horizontalSpeed / velocity) + Mathf.PI;
}