Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
so i have torpedos in my game and they start out at 0 meters per second and accelerate realistically. after so many seconds they stop accelerating and travel at a constant rate forward.
I have a distance to the target and I basically am trying to calculate lead time for autoaiming.
So given
Distance to target;
Acceleration (per second);
burn time (number of seconds before acceleration stops);
I need to basically determine I believe the average meters per second the projectile is travelling.
The only way I can see to do it is something like this.
curdistance; //stores distance traveled per second
currentspeed; //stores speed at a given second
acceleration;
for(int timer = 1; curdistance < distanceToTarget;timer++)
{
currentspeed = currentspeed + acceleration;
curdistance = curdistance + ( currentspeed);
if(timer >= burnTime)
{
acceleration = 0;
}
}
Now this works but it has 2 problems.
The burn time has to be an int or else the smaller the fraction the greater the number of runs to keep accuracy.
If i want a 4.2 burn time for example in order to keep the accuracy i have to run it 42 times and calculate for every 10th of a second.
Also the average could be off by quite a bit depending on how much it overshoots a target depending again on how precise the timer is.
if my projectile is going at 30 meters per second and it needs to go 121 meters it'll add another full second of travel before it goes ok you've gone to/past the target which will mean it will actualy be aiming as it were at a point 29 meters further than it really should.
The only way to combat this with this algorithm is to check more often every 10th or 100th of a second.
I feel like though there might be a math equation I don't know that lets me solve this precisely.
Any Help?
During accelerated motion you can use d = a*t^2/2, or equivalently t = sqrt(2*d/a), at which time velocity v = a*t.
Then you can extrapolate to the target using that v.
As you describe it your movement happens in 2 parts. The first part is accelerated movement (with constant acceleration) and the second part is movement under constant velocity.
You can calculate the traveling distance (or time) for each one individually and then combine them for the desired result.
Keep in mind that you need to check for special cases where the target is closer than the burn distance. The code below does that with the check if (distanceToTarget < burnDistance)
// these will be the results
float timeToTarget;
float averageSpeed;
// assign values to these
float distanceToTarget;
float acceleration;
float burnTime;
float burnDistance = acceleration * burnTime * burnTime * 0.5;
if (distanceToTarget < burnDistance)
{
timeToTarget = Math.Sqrt(2 * distanceToTarget / acceleration);
}
else
{
float velocity = acceleration * burnTime;
timeToTarget = burnTime + (distanceToTarget - burnDistance) / velocity;
}
averageSpeed = distanceToTarget / timeToTarget;
If
d = initial distance to the target
b = burn time
a = acceleration
When the projectile stops accelerating, it will have
speed = a*b
distance (traveled) = dt = a*b^2/2
From that moment, it will need
time for impact = ti = (d-dt)/(a*b)
The total time will be
total time for impact = ti + b
This is one way:
Function VelocityGivenTime(burnTime, givenTime)
(
T = givenTime
If T > burnTime Then T = burnTime
return acceleration * T
)
Function DistanceGivenTime(burnTime, givenTime)
(
If burntime >= givenTime Then
T = givenTime
return 0.5 * acceleration * T^2
Else
T = burnTime
D = 0.5 * acceleration * T^2
D = D + VelocityGivenTime(T) * (givenTime - burnTime)
return D
End IF
)
However, if what you really wanted was the time to a target give its distance, you could do it like this:
Function TimeGivenDistance(burnTime, distance)
(
burnDistance = DistanceGivenTime(burnTime)
If distance > burnDistance Then
return burnTime + (distance - burnDistance) / VelocityGivenTime(burnTime)
Else
return SQRT(2 * distance / acceleration)
End If
)
Related
I making movement process on Unity.
I would like to make a process that moves the object to a specified position, but as the title suggests, I want the object not only to move, but also to reach a predetermined distance with its velocity decaying.
If the acceleration is negative, I can not to process it well.
Specifically, I want to reach a position without turning back when the initial velocity is 10 as shown in the gif.
I used "s=v0t+1/2at^2" from the constant acceleration movement formula to find the acceleration "a", but that does not seem to be enough.
I would appreciate it if you could help me.
public class Test : MonoBehaviour
{
public float t;
public float initSpd;
public Transform t1, t2;
IEnumerator Start()
{
var p = t1.position;
while (true) {
t1.position = p;
var v0t = initSpd * t;
var distance = Vector2.Distance(t1.position, t2.position);
var direction = (t2.position - t1.position).normalized;
var a = (2 * (distance - v0t)) / (t * t);
var v = initSpd;
// update
yield return Utils.Coroutine.WhileForSeconds(t, () =>
{
t1.Translate(direction * v * Time.deltaTime);
v += a * Time.deltaTime;
});
}
}
}
span=3, initial velocity=0
span=3, initial velocity=3
span=3, initial velocity=10
Your math to compute a is correct, but you have posed the problem in a way that cannot be solved to your satisfaction.
As you said, with constant acceleration the position is a quadratic function of time, so it is described by
s = b t^2 + c t + d
for some constants b, c, d. The value of d is already fixed by the initial position. The value of c is already fixed by the initial velocity. There is only one free parameter left, and when you solve for s(finalTime) = goalPosition, you can't control whether or not the resulting parabola overshoots the goal or not.
You can increase the polynomial degree, but there will always be some initial velocity that is too large and causes overshoot.
Essentially, you have an optimal control / trajectory optimization problem, something like
minimize: integral(acceleration(t)^2) from t = 0 to T
subject to: x(0) given
v(0) given
x(T) given
x(t) <= x(T) for all t in [0, T] (assuming x(0) < x(T))
As you stated the problem, there was no optimization objective, but you need either a cost or a constraint on the acceleration, otherwise you could get solutions with infinite acceleration (e.g. accelerate really hard in the first time step, then coast to the goal at constant velocity).
The inequality makes it complicated. If you want a continuous-time analytic solution, then Pontryagin's principle would be a hammer to solve it, but there might be easier tricks. If you discretize time and let the acceleration be piecewise-constant, then it's an easy convex optimization problem.
If you are willing to relax the "never overshoot" and "get there at exactly this time" constraints, then very simple solution, that will scale better to more complex scenarios like external forces, is to use a feedback control law like a PD controller:
a = kp * vectorToGoal - kd * velocityVector,
where kp and kd are hand-tuned positive constants, and this expression is re-evaluated in every frame. You can tune kp and kd to minimize overshoot in your typical scenarios.
Or, you could do what most games do - allow velocity to change instantaneously :)
I found the ideal behavior achieved by changing the speed in two steps.
IEnumerator Start()
{
var p = t1.position;
while (true)
{
t1.position = p;
var direction = (t2.position - t1.position).normalized;
var distance = Vector2.Distance(t1.position, t2.position);
var v0 = initSpd;
var M = distance;
var T = duration;
var tm = M / v0;
var vm = v0 / T * tm;
var accel1 = (vm - v0) / (tm - 0);
var accel2 = (0 - vm) / (T - tm);
Debug.Log($"vo={v0}, M={M}, T={T}, tm={tm}, vm={vm}, accel1={accel1}, accel2={accel2}");
var v = initSpd;
var stime = Time.time;
var hist = 0f;
// update
yield return Utils.Coroutine.WhileForSeconds(T, () =>
{
t1.Translate(direction * v * Time.deltaTime);
hist += v * Time.deltaTime;
if (Time.time - stime <= tm)
v += accel1 * Time.deltaTime;
else
v += accel2 * Time.deltaTime;
});
Debug.Log($"elapsed={Time.time - stime}, moved distance={hist}, v={v}");
}
}
float wave = 0.5f * sin( _Time.w * Frequency + 12.0f) + 1.0f;
Even if i change "Frequency" in super small steps (0.001f) the animation tends to jump.
Thanks.
You need to keep track of the phase, because when you change frequency and then multiply by the current time, you have a sudden jump in phase.
Based on the way you currently calculate it:
in your class definition:
float mCurrentPhase = 12.0f; // start phase at 12 for some reason
in your function
mCurrentPhase += (Time.deltaTime * Frequency);
float wave = 0.5f * sin( mCurrentPhase ) + 1.0f;
What is the +12.0f for? Did you want to start the waveform at a certain point? Because +12.0f is actually going to just start you off in the next period of the sine wave - same as the last one.
Also, sine functions get flakey with really big numbers, and since sine is periodic, you can also do this:
mCurrentPhase += (Time.deltaTime * Frequency);
// Keep phase between 0 .. 2*PI ... Note not tested in Unity
if (mCurrentPhase >= 2*PI) mCurrentPhase -= 2*PI;
float wave = 0.5f * sin( mCurrentPhase ) + 1.0f;
I have some code I created to simulate a freefall of an object in a vacuum. When timestep is set to 1000ms it works perfectly with no problems. when I set timestep lower than 1000ms it deviates wildly from actual values falling far faster than normal. It takes around 14 seconds to fall 1000 meters at 1000ms which is the correct value. At 100ms timestep it only takes 5 seconds. at 10ms it only takes 2.2 seconds.
Can someone please tell me what I am doing wrong? I thought I set the timestep calculation to be able to handle smaller steps.
Thank you
Body testbody = new Body();
testbody.pos = new Vector(0, 1000);
testbody.velocity = new Vector(0, 0);
Bodytrack(testbody);
static void Bodytrack(Body body)
{
watch.Start();
int timestep = 1000;
while (body.pos.Y > 0)
{
body.pos = body.pos + (body.velocity * (timestep / 1000.0));
if (body.pos.Y <= 0) { break; }
Thread.Sleep(timestep);
CalculateAcceleration(body);
Console.Clear();
Console.WriteLine(body.velocity.Y);
Console.WriteLine(body.pos.Y);
Console.WriteLine(watch.Elapsed.TotalSeconds);
}
watch.Stop();
}
public static void CalculateAcceleration(Body body)
{
body.acceleration = new Vector(0, -9.80665);
body.velocity = Vector.Add(body.acceleration, body.velocity);
}
This line is your problem:
body.acceleration = new Vector(0, -9.80665);
Every time through the loop, you set the acceleration to -9.8 (which is the acceleration for 1000ms) instead of the acceleration for the amount of time that has passed.
You need to take into account the amount of time that as passed. If 500ms has passed, then acceleration should only be -9.80665/2.
You need to change the method to this:
public static void CalculateAcceleration(Body body, int timestep)
{
body.acceleration = new Vector(0, -9.80665 * (timestep/1000));
body.velocity = Vector.Add(body.acceleration, body.velocity);
}
If I remember my math correctly (and manage to write it down using markdown)...
The idea is that the horizontal position is not affected by anything, gravity only affects the vertical position. So we can easily calculate where the object is horizontally (assuming no collisions).
X(t) = Vx * t.
This means that when you throw something horizontally at 10 metres per second, it will have traveled 100 metres after 10 seconds.
The formula för the horizontal positioning is a bit more advanced. It contains two parts, the first part is the movement without gravity, and the second part acts as a countering force by the gravity.
Y(t) = Vy * t - (g * t^2)/2 where g is gravity (usually 9.82).
Vy * t is the constant motion upwards if gravity wouldn't have pulled it down.
(g * t^2)/2 is the increasing pull of gravity.
A object that's dropped will have no initial force in any direction, thus { Vx = 0, Vy = 0 } and you can easily get the current position of it, at any time, using pos(t) = -(g * t^2)/2. (We also know that it will fall straight down so you do not have to calculate any horizontal positioning.) You already know the speed of it by speed(t) = g * t.
You can not trust time-slices as your code does. For one, Thread.Sleep isn't exact. It's better if you calculate time elapsed since last update, and use that in your calculations.
I am testing the accelerometer code in Unity3D 4.3. What I want to do is simple change the object angle while tilting the ipad, to fake view angle like real live. Everything works fine except for the fact that the accelerometer is a bit too sensitive and I can see the GameObject is like of flickering even I put it on table. How can I make it less sensitive so that even when you hold with your hand the angle will change according to the tilt and the object remain steady?
Here are my code:
void Update () {
Vector3 dir = Vector3.zero;
dir.x = Mathf.Round(Input.acceleration.x * 1000.0f) / 1000.0f;
dir.y = Mathf.Round(Input.acceleration.y * 1000.0f) / 1000.0f;
dir.z = Mathf.Round(Input.acceleration.z * 1000.0f) / 1000.0f;
// clamp acceleration vector to the unit sphere
if (dir.sqrMagnitude > 1)
dir.Normalize();
// Make it move 10 meters per second instead of 10 meters per frame...
dir *= Time.deltaTime;
dir *= speed;
acx = dir.x;
acy = dir.y;
acz = dir.z;
transform.rotation = Quaternion.Euler(dir.y-20, -dir.x, 0);
}
You may need to use a low pass filter (s. Exponential Moving Average for a better description regarding software) before using the signal output. I always use native code to get accelerometer and gyroscope values on iPhone so I am not 100% sure how Unity handles this. But from what you are describing the values appear unfiltered.
A low pass filter calculate a weighted average from all your previous values. Having for example a filter factor on 0.1 your weighted average is:
Vector3 aNew = Input.acceleration;
float a = 0.1f * aNew + 0.9f * a;
This way your values are smoothed at the expense of a small delay. Running the accelerometer with 50 Hz you won't notice it.
I couldn't make Kay's example work as it was not multiplying the last part, so here's my small correction:
Vector3 aNew = Input.acceleration;
a = (0.1 * aNew) + (0.9 * a);
I'm making a galaxian-like shooter, and my enemy objects have a destination Vector which they travel towards, using this bit of code:
position.X -= (Motion.X / Magnitude) * Speed;
position.Y -= (Motion.Y / Magnitude) * Speed;
Motion is worked out by:
this.Motion = InitialPosition - Destination;
This makes them travel in a straight line towards the destination.
However, I want to make them a bit more interesting, and travel on a sin or cos wave, a bit like Galaxian did.
How can I do this?
You might be better off defining a bezier curve for the movement function than simple functions like a sine wave. Galaxian certainly had more complex movements than that.
Here is a link to a primer on the maths of Bezier curves. It's quite a long document, but does a good job of covering the maths involved, with plenty of examples.
Hope that helps inspire you.
One way to do this would be to create an acceleration factor for the horizontal motion and add that factor to the horizontal speed every tick. So if your horizontal speed for a given enemy was 2 to begin, and your acceleration was -.01, then after 200 ticks the enemy would be going straight down, and after another 200 ticks it would be moving at a horizontal speed of -2. This will give a nice curve.
By determining the speed and acceleration randomly for each enemy (within certain limits determined by experimentation) you can create a nice looking variety of attack profiles without too much effort. This would give a very Galaxian-like motion.
You can do the same thing with the vertical as well, though, of course, the acceleration limits would be very different...for the horizontal acceleration you would probably want to determine a range that was equal in magnitude on either side of 0 (say -.02 to +.02), while for the vertical acceleration, you probably always want the ship to end up going down off the bottom of the screen, so you probably want that acceleration to always end up positive (or negative depending on how you're doing screen coordinates.)
You would do this by utilizing waypoint navigation, in line with your current motion code. You would calculate the waypoints by graphing the sine wave. You would do this by using something to the effect of Destination.Y = Math.Sin(Destination.X) - it's a little difficult to say for sure without seeing your code at large.
Creating an oscillator and moving the enemy (even without momentum) perpendicularly to its direction by an offset equals to the sine or cosine of the oscillator would be enough.
The following example, while working, is clearly just a guideline. I hope it can help you.
var dest = new PointF(200, 100);
var pos = new PointF(30, 140);
var oscAngle = 0d;
var dirAngle = Math.Atan2(dest.Y - pos.Y, dest.X - pos.X);
//Constants for your simulation
const int movSpeed = 2;
const int amp = 2;
const double frequency = Math.PI / 5;
//Inappropriate loop condition, change it to proper
while (true)
{
oscAngle += frequency;
//Scalar offset, you can use Cos as well
var oscDelta = Math.Sin(oscAngle);
//Linear movement
var stepVector = new SizeF((float)(Math.Cos(dirAngle) * movSpeed), (float)(Math.Sin(dirAngle) * movSpeed));
//Oscillating movement, making it transversal by adding 90° to the direction angle
var oscNormalAngle = dirAngle + Math.PI / 2;
//Vector for the oscillation
var oscVector = new SizeF((float)(Math.Cos(oscNormalAngle) * oscDelta) * amp, (float)(Math.Sin(oscNormalAngle) * oscDelta) * amp);
pos += stepVector + oscVector;
//Operate below
}