Unity Lerp in Seconds - List of Points - c#

I have list of translations and rotations for a gameobject with timeframe.
Example (translation):
Point 1: (X1, Y1, Z1) Current Time T1
Point 2: (X2, Y2, Z2) Current Time T2
Point 3: (X3, Y3, Z3) Current Time T3
...
In this case, I need a coroutine with Lerp from Point 1 to 2 with (T2-T1) seconds. After, from Point 2 to 3 with T3-T2 seconds... The point is, each movement should wait the completion of previous one. The delta time between points is roughly 0.1 seconds.
How should I do it? In addition, I also have the rotations (Rx1-n,Ry1-n,Rz1-n) similar to translations.

The movement can be done in a simple Coroutine like
// Moves a given object from A to B within given duration
IEnumerator MoveWithinSeconds(Transform obj, Vector3 from, Vector3 to, float duration)
{
var timePassed = 0f;
while(timePassed < duration)
{
// will always be a factor between 0 and 1
var factor = timePassed / duration;
// optional ease-in and ease-out
// factor = Mathf.SmoothStep(0,1,factor);
// linear interpolate the position
obj.position = Vector3.Lerp(from, to, factor);
// increase timePassed by time since last frame
// using Min to avoid overshooting
timePassed += Mathf.Min(Time.deltaTime, duration - timePassed);
// "pause" the routine here, render the frame
// and continue from here in the next one
yield return null;
}
// just to be sure apply the target position in the end
obj.position = target;
}
and then simply iterate your elements from another Coroutine like
// the object you want to move
public Transform obj;
// your waypoints class with position and time stamp
public List<WAYPOINT>() waypoints;
IEnumerator MovementRoutine()
{
// if 0 or only 1 waypoint then it makes no sense to go on
if(waypoints.Count <= 1) yield break;
// pick the first item
var fromPoint = waypoints[0];
// since you already have the first start the iteration with the second item
for(var i = 1; i < waypoints.Count; i++)
{
var toPoint = waypoints[i];
var fromPosition = fromPoint.position;
var toPosition = toPoint.position;
var duration = toPoint.time - fromPoint.time;
// this executes and at the same time waits(yields) until the MoveWithinSeconds routine finished
yield return MoveWithinSeconds(obj, fromPosition, toPosition, duration);
// update fromPoint for the next step
fromPoint = toPoint;
}
}
Same can be done for rotations. Note however that rotating in eulerspace by only XYZ is a bit tricky in some conditions. You should probably rather store and use Quaternion with XYZW.
Note: This will work well as long as your frame-rate is high enough / the delta time big enough. The problem is that yield return postpones the following code at least 1 frame. So in cases where the delta time becomes smaller then the frame-rate it will not work as expected.

Related

Continuously spawn 4 squares on top of each other regardless of speed

I have a function that spawns 4 objects in a row and those objects move down.I want to keep spawning 4 objects on top of previous 4 objects, but I want to avoid having empty horizontal space between those rows. Think of it like a chess board or any board with squared scrolling downwards indefinitely, and the 4 objects that I keep spawning are chess board squares.
This is my current function that spawns 4 objects in horizontal and I have a coroutine that calls this in random intervals
public void CreateLetter()
{
var wordLetters = randomChosenWord.ToCharArray();
for (int i = 0; i < letterPlacements.Length; i++)
{
var chosenLetter = wordLetters[Random.Range(0, wordLetters.Length)];
//Do not spawn too much duplicates
var numberOfSameLetters = FindObjectsOfType<LetterController>().Where(l => l.Letter == chosenLetter).ToList().Count;
var lettersAlreadyChosen = FindObjectOfType<PlayerControls>().playerChosenLetters;
//Did player choose any letter already? spawn that letter less
if (numberOfSameLetters > (lettersAlreadyChosen.IndexOf(chosenLetter) != -1 ? 1 : 2))
{
var trimmedRandomWord = randomChosenWord.Trim(chosenLetter);
chosenLetter = trimmedRandomWord[Random.Range(0, trimmedRandomWord.Length)];
}
GameObject letter = Instantiate(letterPrefab);
letter.transform.position = new Vector2(letterPlacements[i].transform.position.x, letterPlacements[i].transform.position.y + 1);
letter.GetComponent<SpriteRenderer>().sprite = letterSprites[alphabet.IndexOf(char.ToUpper(chosenLetter))];
letter.GetComponent<LetterController>().Letter = chosenLetter;
}
}
Should make a check if the new four square positions would collide with previous four? Any ideas or suggestions?
After some digging and calculating, I found out that what I need actually is called
inverse proportion.
To make the coroutine spawn 4 squares after the previous 4 have spawned (so that they are perfectly aligned), I use the movement speed of squares and then calculate the time when the coroutine should run again and spawn another 4 squares. In other words, the bigger the movement speed, the faster the coroutine function repeats:
IEnumerator GenerateLetters()
{
while(randomChosenWord.Length > 0)
{
yield return new WaitForSeconds(1 / (movementSpeed * 0.5f));
CreateLetter();
}
}
and this is the Update function in script attached to square prefab object:
// Update is called once per frame
void Update()
{
//Move letter downwards
transform.position = new Vector2(transform.position.x, transform.position.y - movementSpeed * Time.deltaTime / 2);
gameObject.GetComponentInChildren<TextMesh>().text = Letter.ToString();
}

Constant acceleration movement with minus acceleration

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}");
}
}

Source, Target, and Intermediate Way Points

Consider two points: (0,0,0) as source and (1000,0,0) as target
A cube game object wants to travel from source and target at a pre-defined/constant speed. Time taken: t1
Introduce 100 intermediate points between source and target, i.e. INTERMEDIATE_POINTS = 10
Example: (0,0,0), (10,0,0), (20,0,0), (30,0,0).... (980,0,0), (990,0,0), (1000,0,0). Same speed, time taken: t2.
Introduce 50 intermediate points, i.e. INTERMEDIATE_POINTS = 20 ; (0,0,0), (20,0,0), (40,0,0),..., (960,0,0), (980,0,0), (1000,0,0). Same speed, time taken: t3.
Result: t1 < t3 < t2, i.e. more intermediate points, more time taken to reach the target (although same path and same speed)
Question: If you compare, the game object moves in the same way (same path, same speed) in all the three cases (no intermediates, 100 intermediates, and 50 intermediates) that are mentioned above. But why is there a time difference to reach the target?
Code to test this scenario:
using UnityEngine;
using System.Collections.Generic;
public class TestSpeed : MonoBehaviour
{
private List<Vector3> listOfPoints = new List<Vector3>();
private int INTERMEDIATE_POINTS = 1;
private int counter = 1;
private float speed = 50.0f;
private float originalDistance = 0.0f;
private float distanceCovered = 0.0f;
private float overshoot = 0.0f;
private Vector3 modifiedTarget;
// for the car movement.
private Vector3 targetPosition; // after every loop, get the next position
private Vector3 currentPosition;
// Use this for initialization
void Start()
{
for (int i = 0; i <= 1000; i = i + INTERMEDIATE_POINTS)
{
listOfPoints.Add(new Vector3(i, 0, 0));
}
currentPosition = this.transform.position; // at the beginning, from (0,0,0)
targetPosition = listOfPoints[counter];
}
// Update is called once per frame
void Update()
{
originalDistance = Vector3.Distance(targetPosition, currentPosition);
distanceCovered = Vector3.Distance(transform.position, currentPosition);
if(Vector3.Distance(transform.position, new Vector3(0,0,0)) >= 995.0f)
{
System.TimeSpan t = System.TimeSpan.FromSeconds(Time.timeSinceLevelLoad);
string answer = string.Format("{0:D2}:{1:D2}:{2:D2}",
t.Hours,
t.Minutes,
t.Seconds);
}
if ((originalDistance - distanceCovered) <= 0.0f)
{
currentPosition = transform.position;
targetPosition = listOfPoints[counter];
counter++;
}
else
{
float step = speed * Time.deltaTime;
if((distanceCovered + step) >= originalDistance)
{
overshoot = distanceCovered + step - originalDistance;
counter++;
modifiedTarget = Vector3.Lerp(targetPosition, listOfPoints[counter], (overshoot / originalDistance));
}
else
{
modifiedTarget = targetPosition;
}
transform.position = Vector3.MoveTowards(transform.position, modifiedTarget, step);
}
}
}
How to use the code:
Just create a cube game object and assign the script to it. Near to string answer set a break-point to check the time duration with various number of intermediate points.
I'm pretty sure this logic here is the cause of the strange observation:
if ((originalDistance - distanceCovered) == 0.0f)
{
currentPosition = transform.position;
targetPosition = listOfPoints[counter];
counter++;
}
You check whether or not you've arrived at your destination waypoint by checking for an exact position match; however, the distance you travel per Update is anything but exact. That means that your object could very well overshoot the destination, then try to move back towards it, overshoot it again, then repeat.
I bet if you watch your cube in the scene view, you'll see it hover around a single waypoint for a bit until it manages to hit the exact distance it needed.
You're probably better off using an inequality here, for example:
if ((originalDistance - distanceCovered) <= 0.0f)
{ /* ... */ }
Your object has reached its waypoint if the distance it has traveled is greater than or equal to the distance it needed to travel. originalDistance - distanceCovered will be negative as soon as the object has reached or passed the waypoint.
EDIT:
X.....X.....X.....X.....X.....X
Here are some waypoints. Pretend we have an object traveling along the path of waypoints. It starts at the first one on the left and goes right. It'll be represented by an O.
O.....X.....X.....X.....X.....X
Now it moves along for a while. Due to the variability of Time.deltaTime, it might move one or two spots each tick. So let's say it winds up here after a few ticks:
X....OX.....X.....X.....X.....X
And during the next tick, it moves two:
X.....XO....X.....X.....X.....X
With your original check, the object will now travel backwards. It needed to travel a distance of seven spaces, but it traveled 8. So with your original check, originalDistance - distanceCovered != 0.0f. So it'll keep trying to hit that spot over and over again until it hits it on the dot.
Even if you introduce the idea of a "threshold", you're still going to have the same problem. There is no fixed distance traveled per tick, so that means that each waypoint will have some artificial "bounce" time unless that threshold is so large that the waypoints become meaningless.
If you use originalDistance - distanceCovered <= 0.0f, you will always move on to the next waypoint if it overshoots. Instead of trying to land the object in some small window, you're just making sure that the object has passed or met its waypoint.

Why doesn't this simple physics freefall code work

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.

Finding time to target with variable velocity [closed]

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
)

Categories

Resources