I am trying to make a game with Unity that uses rockets that are in orbit and can transition to different planets, but I need to predict the rocket path in real-time, depending on the current velocity/direction of the rocket allot like spaceflight simulator does.
I have managed to get realistic physics and orbits when adding gravitational forces to a rigidBody:
var sateliteCords = Satelite.transform.position;
var planetCords = gameObject.transform.position;
var distance = sateliteCords - planetCords;
distanceFromSatelite = Vector3.Distance(Satelite.transform.position, gameObject.transform.position);
F = (Gravity * Mass) / Mathf.Pow(distanceFromSatelite,2);
forces = (distance / Mathf.Sqrt(Mathf.Pow(distance.x, 2)+ Mathf.Pow(distance.y, 2)+ Mathf.Pow(distance.z, 2))) * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces * Time.deltaTime, ForceMode.Force);
but I don't know how to predict future points in time to give me the orbit shape.
I have tried "speeding up the simulation" with Time.timeScale = 50; but it isn't fast enough.
I read somewhere that I need a copy of the solar system running independently form the unity updates, and calculate the rocket trajectory from there but I don't know how to approach that.
I'm a bit of a noob when it comes to physics simulations so can one of you geniuses please help?
Here's what your code is actually doing:
var sateliteCords = Satelite.transform.position;
var planetCords = gameObject.transform.position;
var distance = sateliteCords - planetCords;
distanceFromSatelite = distance.magnitude;
F = (Gravity * Mass) / Mathf.Pow(distanceFromSatelite,2);
forces = distance / distanceFromSatellite * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces * Time.deltaTime, ForceMode.Force);
You can further reduce it by doing:
var distance = sateliteCords - planetCords;
distanceSqrMag = distance.sqrMagnitude;
F = (Gravity * Mass) / distanceSqrMag;
forces = distance.normalized * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces * Time.deltaTime, ForceMode.Force);
Now that the code is much simpler, I can find a crucial mistake. ForceMode.Force already multiplies the Force by Time.deltaTime. What you want here is to switch to ForceMode.Impulse, or remove the deltaTime.
F = (Gravity * Mass) / distanceSqrMag;
forces = distance.normalized * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces, ForceMode.Force);
Or better yet, assuming Mass is the mass of the satellite, and gravity is not a general constant, but just the local gravity of your planet. ForceMode.Force divides the force by the mass of the rigidbody it is applied on.
F = Gravity / distanceSqrMag;
forces = distance.normalized * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces, ForceMode.Acceleration);
Now that's all out the way, you can do a very simple approximation by doing something like
var currentPos = _rigidBody.position;
var prevPos = currentPos;
var currentVelocity = _rigidBody.velocity;
var planetCords = gameObject.transform.position;
for (int i = 0; i < stepCount; i++)
{
var distance = planetCords - currentPos;
var forceMag = Gravity / distance.sqrMagnitude;
forces = distance.normalized * forceMag;
currentVelocity += forces * Time.fixedDeltaTime;
currentPos += currentVelocity * Time.fixedDeltaTime;
//Replace by a TrailRenderer you reset each frame
Debug.DrawLine(prevPos, currentPos, Color.Red, Time.deltaTime);
prevPos = currentPos;
}
Note that this code is untested, I probably made a mistake here or there, but that's the gist of it.
Don't worry about getting any difference in trajectory, this is exactly what unity does to predict positions. No need for fancy calculations when you got all the computing power you need.
If you want more than one-body gravity simulation, it's simple. Either apply the gravity of all planets, or simply the gravity of the closest planet taking its mass into account. I recommend the former if you have less than a dozen planets.
You don't need conics, or advanced math, or any of that. You're not kerbal space program. Sometime brute force is the best solution.
Related
I'd want to rotate the character along the y-axis (green) of another gameobject.
Here's a picture of my system.
I'd want to rotate the character in the direction indicated by the red arrow (see figure, it is not x axis).
I looked at other questions and tried a lot of code,
but I still didn't achieve the desired results.
This is my latest code that created the picture above.
The picture on the right is what I want, but the picture on the left is not.
Any advice is appreciated. Thank you!
IEnumerator RotateToDirection(){
var targetRotation = new Vector3(0, ArrowObject.transform.localEulerAngles.y, 0);
var elapsedTime = 0.0f;
var waitTime = 1f;
while (elapsedTime < waitTime)
{
elapsedTime += Time.deltaTime;
Character.transform.localEulerAngles
= Vector3.Lerp(Character.transform.localEulerAngles, targetDirection, (elapsedTime / waitTime));
yield return null;
}
}
Don't use eulerAngles
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.
From your images it sounds like you want the character's forward axis to align with another objects up axis so you would probably use e.g. Quaternion.LookRotation something like e.g.
var startRotation = Character.transform;
var targetDirection = ArrowObject.transform.up;
var targetRotation = Quaternion.LookRotation(targetDirection);
for(var timePassed = 0f; timePassed< 1f; timePassed += Time.deltaTime)
{
Character.transform.rotation = Quaternion.Lerp(startRotation, targetRotation, timePassed / 1f);
yield return null;
}
Character.transform.rotation = targetRotation;
I'm trying to make an airplane controller, I am kind of aiming for something between arcade and realistic, so I want the plane to turn with a force proportional to the roll.
I haven't coded in any adjustments and I'm still prototyping the whole thing, but I encountered a problem with getting the signed rotation angle while using quaternions, I had a look at Determining if quarternion rotation is clockwise or counter clockwise here on SO but I am having trouble generalizing the solution to the (almost) arbitrary plane the rotation can be at.
What I made by now:
private void FixedUpdate()
{
float desiredYaw = _yaw * _rotationSpeed * Time.fixedDeltaTime;
float desiredPitch = -_pitch * _rotationSpeed * Time.fixedDeltaTime;
float rotationStepSize = _throttle * Time.fixedDeltaTime;
Quaternion toRotate = Quaternion.Euler(desiredPitch, 0, desiredYaw);
Quaternion straighRotation = Quaternion.LookRotation(_transform.forward, Vector3.up );
_rotation = _transform.rotation * toRotate;
float turningForce = Quaternion.Angle( _rotation, straighRotation );
_rigidbody.MoveRotation( _rotation );
_rigidbody.AddTorque( turningForce * _rotationForce * rotationStepSize * Vector3.up );
_rigidbody.AddRelativeForce( _speed * rotationStepSize * Vector3.forward );
}
EDIT: I realized I'm calculating the turning force using the roll rather then the yaw, that was intended just wrong wording, corrected now.
Since all you need is a factor that describes how downward the plane's right is, you can just use the y component of the plane's right for that. No need to bring in quaternions or even trigonometry. Explanation in comments:
private void FixedUpdate()
{
// ...
// Calculate how downward local right is in range [-1,1]
// The more downward, the more tilted right the plane is
// positive = tilted right
// negative = tilted left
float turnFactor = -_transform.right.y;
// Could do things to modify turnFactor to affect easing here.
// For instance, if turning rate should start slower then rapidly increase:
// turnFactor = Mathf.Sign(turnFactor) * turnFactor * turnFactor;
// Use factor and _rotationForce member to calculate torque, apply along
// global up.
// We expect to call this every fixed frame so we can just use the default
// ForceMode of ForceMode.Force which multiplies fixed delta time inside.
_rigidbody.AddTorque(_rotationForce * turnFactor * Vector3.up);
// ...
}
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}");
}
}
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
}