Hi, I'm just starting with physics, so I'm not always sure about what I'm doing. It's a 2D project but I'm using 3D physical objects like SphereCollider etc.. What I have: Objects floating in space and affecting each other through gravity: Code (csharp): protected virtual IEnumerator OnTriggerStay(Collider other) { yield return new WaitForFixedUpdate(); if(other.attachedRigidbody) { Vector3 offsetVector = this.transform.position - other.transform.position; float distance = offsetVector.magnitude; float gravityForce = (other.rigidbody.mass * mass) / Mathf.Pow(distance, 2); // Clamp gravity. if(gravityForce > 1.0F) { gravityForce = 1.0F; } other.attachedRigidbody.constantForce.force = offsetVector.normalized * gravityForce; } } There are controllable objects on which the player can click and drag a line away from the object in order to give it a force (shoot) in the opposite direction. While aiming the time is slowed down: Code (csharp): Time.timeScale = 0.01F; Time.fixedDeltaTime = 0.02F * Time.timeScale; What I want to achieve: The player should see a rough prediction of the way while aiming. That means that the way-prediction needs to take in account the current velocity, the force which would be applied when the player release the mouse button and the gravity of the surrounding objects. What I have tried so far: For testing purposes I just save the computed/predicted positions in an array and draw those positions in OnDrawGizmos(). I wrote a method which returns the gravity influence for a certain position called computeGravityForPosition(Vector3 position). And thats how I try to calculate the positions: Code (csharp): private void drawWayPrediction() { Vector3 pos = this.transform.position; // The offsetVector for the shooting action. Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition); forceVector.z = 0.0F; // The predicted momentum scaled up to increase the strength. Vector3 force = (forceVector.normalized * forceVector.magnitude) * 10; // 1. I guess that this is wrong, but don't know how to do it properly. momentum = this.rigidbody.velocity + force; for(int i = 0; i < predictionPoints.Length; i++) { float t = i * Time.fixedDeltaTime; momentum += computeGravityForPosition(pos); pos += momentum * t * t; predictionPoints[i] = pos; } } At the beginning, when the objects just slowly approaching each other it looks okay. After the first shot, the prediction is completely wrong. I guess it is because of 1. in the code. Just adding the force to the velocity is probably horrible wrong. Please don't just lead me to some wiki page or basic physics explanations. I think I need some more specific help here and I would be pleased to get some explanation added on top. Trying to teach myself what is going wrong here led to some serious wiki page hopping of speed, velocity, acceleration and even more confusion. Thank you very much for your time.
I don't have the time at the moment, but your question deserves a bump. If no one else beats me to it, I'll have a go at helping out when I can. Don't get too excited, I got a B in physics, and that was a while back. I want to see this project, assuming there's something to see. Sounds cool!
In a simpler scenario, you could just run this kinematic equation with increasing time_delta values to get your projected displacement at successive intervals: displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f); In your case though, it's no good since you don't have a constant acceleration. Instead you'll have to take multiple samples, where the results of the previous samples affect the current one. I guess you know the further out in time the projection looks the less accurate it will be, simply by virtue of having multiple bodies acting on each other. Depending on how many bodies are acting and what granularity you want, it's unlikely everyone will have the CPU horsepower to get it approximated in a timely fashion. Therefor accuracy is guaranteed to suffer with distance-projected, based on the too-expensive-to-predict nature of your bodies. If you'd be satisfied with this approach, you could do a loop like you're doing now, but carry over results from the previous iteration to plug back into the simple kinematic equation. I'm going to refer to things as what they'd represent in this recursive loop, but keep the variable names the same as in the equation above. displacement gives projected object's position delta this recursive-projection-loop step each result is added to the previous result, thus becoming the next position on your curve a visible sprite or curve-node belongs at each position velocity_initial projected object's instantaneous velocity this step based on the last step's position delta units must be m/s or in unity terms, units per second! time_delta time in seconds each recursive-projection-step looks ahead. fixed number, does not accumulate or relate to loop iterator or anything in unity's Time class acceleration computeGravityForPosition() at the object's projected position this step projected position this step is previous step's displaced position units must be m/s^2 or in unity terms, units per second squared! Hmm.. And the first step, your acceleration includes the force you're going to give the body upon exiting "aim mode." How many times you run it determines how many nodes you get, and time_delta changes the relative distance between them. Maybe my math makes sense. I'm tired, I appologize if my logic or clarifications aren't up to par. I wanna see this game of yours, I mean it!
Thank you very much AlwaysSunny, as I have to learn for the exams at the moment, I will see what I can do with your answer at the weekend. It's gonne be a small game-app in the scale of Angry Birds and co.. I'll keep you in mind as a beta tester if you like.
It almost looks good, but at this point I still have my problems. For the "aiming mode" I have this Vector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition); I can get the direction from normalizing it and get the length from the magnitude. How do I get a force from this though, without having any acceleration? And the current movement (e.g. for the second shot) has to be taken into account. For this I only have the current velocity from the rigidbody, don't I? How am I mixing up the force (of which I'm also still in need) and the current velocity? For the rest your explanation is pretty good! Thank you very much again, the solution seems to be near
I'm really happy you've found this helpful, I enjoy this kind of work despite being somewhat inept. Might be you simply chose your words incautiously, but whatever body you're doing future projections for does have an acceleration every frame - which is exactly what you should be getting from computeGravityForPosition() - since any change in velocity is called acceleration. And your rigidbody.velocity is your velocity in units per second, so that's correct too, but you won't have that for your projections beyond step one. You'll have to have a clever way to compute each step's would-be velocity based on the projected position deltas of your previous steps. I'm fuzzy here, but I'm relatively confident it'd be projectedVelocity = (previousStep'sProjectedPosition - theStepBeforeThatOne'sProjectedPosition) / time_delta The force you'll give upon release is going to be based on the mouseDragVector as you said, but the scalar you multiply by will be determined experimentally and be based on relative masses. You should intuitively choose a maxDragLength float which your mouseDragVector's magnitude cannot exceed, then compare the mouseDragVector's length to it with percent = mouseDragVector.magnitude / maxDragLength You then multiply that 0..1 result with your experimentally chosen maxForceToApply float, like so forceToApplyOnRelease = mouseDragVector.normalized * percent * maxForceToApply The only reason to do this differently would be if you wanted to know ahead of time how many newtons of force to apply - for instance, if you were only choosing the direction to apply N force in. So in the first step of the recursive-projection-loop, I think your acceleration should be computeGravityForPosition() + forceToApplyOnRelease but in subsequent steps it should only be computeGravity, since you're giving a one-shot impulse force.
Ah okay, so if i got you right, the shooting force is okay. The problem for the initial acceleration is, that the current acceleration of the object is not taken into account. computeGravityForPosition() really only gives the gravity force for a certain point. It's completely independent of the current movement of the object. So if I shoot the object, then preparing a second shot, the prediction is not correct, because it is just using the force vector from the mousepositon (aiming) and the gravity of the sorrounding objects. It is acting as if the shootable object is standing still.
computeGravityForPosition() should give the acceleration due to gravity at a supplied point, and doesn't need to consider the current movement, that all sounds fine. We're on the same page that this is and will be a one-size-fits-all solution, which doesn't care whether the object to be affected is already in motion or not. As long as the variables supplied to the kinematic equation are in the right form, it's going to predict the future displacement of the object at time time_delta, just like it suggests it will: displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f); meters = (meters/second * seconds) + (meters/second/second * (seconds^2) * 0.5);
Hmm, but with computeGravityForPosition() I have the same problem. At the moment it is returning a vector representing the gravity force for a position. For the shootable object, I can get the current velocity and the mass from the attached rigidbody. I just don't know how to introduce those parameters to the current equations. They are not taken into account. Wether it be in computeGravityForPosition() or the initial acceleration for the way prediction. I will provide an updated version of the code... Code (csharp): Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition); forceVector.z = 0.0F; // The predicted momentum. Vector3 force = forceVector.normalized * mass * forceVector.magnitude; Vector3 pos = this.transform.position; Vector3 acceleration = computeGravityForPosition(pos) + force; // displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f); float timeDelta = 0.2F; predictionPoints[0] = pos; for(int i = 1; i < predictionPoints.Length; i++) { acceleration += computeGravityForPosition(pos); pos += (((pos - predictionPoints[i - 1]) / timeDelta) * timeDelta) + (acceleration* Mathf.Pow(timeDelta, 2) * 0.5F); predictionPoints[i] = pos; } And the gravity calculation (the list holds objects in a certain range and is maintained by OnTriggerEnter and OnTriggerExit): Code (csharp): private Vector3 computeGravityForPosition(Vector3 position) { Vector3 gravityVector = Vector3.zero; foreach(KeyValuePair<string, Rigidbody> entry in gravityInfluencer) { Vector3 offsetVector = position - entry.Value.position; float distance = offsetVector.magnitude /*- (this.transform.localScale.x / 2) Verkleinerung der Distanz, so dass Oberfläche für Anziehungskraft entscheidend ist. */; float gravityForce = (entry.Value.mass * mass) / Mathf.Pow(distance, 2); if(gravityForce > 1.0F) { gravityForce = 1.0F; } gravityVector -= offsetVector.normalized * gravityForce; } return gravityVector; }
Definitely proofread my logic - this is untested notepad coding. Code (csharp): // experiment with this public float forceFactor = 1; // currently requires these be class scope private const float projectionStepTimeDelta = 0.2f; private Vector3[] predictionPoints; // start calling when you enter aimMode, where origin = your transform.position when aimMode was entered void CalculateProjection( Vector3 origin ) { // get force vector from input Vector3 forceVector = origin - Camera.main.ScreenToWorldPoint(Input.mousePosition); forceVector.z = 0.0F; // The predicted acceleration due to user input // was Vector3 force = forceVector.normalized * mass * forceVector.magnitude; but this is equivalent to... Vector3 force = forceVector * mass * forceFactor; // introduced forceFactor for extra control // it is now officially necessary to set element zero to our origin here, as you already did predictionPoints[0] = origin; // sum the force with your computed gravity as before Vector3 acceleration = computeGravityForPosition( origin ) + force; // your magic function. 1 order of magnitude more expensive with the length of predictionPoints array RecursiveProjection( 1, rigidbody.velocity, acceleration ); // now use freshly populated predictionPoints[] to draw curve or whatever you want DrawProjectedPoints(); } void RecursiveProjection( int iterator, Vector3 velocity, Vector3 acceleration ) { // displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f); Vector3 projectedDisplacement = ( velocity * projectionStepTimeDelta ) + ( acceleration * (projectionStepTimeDelta^2) * 0.5f ); // add projected displacement to the previous point to carry the projected displacement into world space // conveniently, array element zero is already in world space, so this line should suffice predictionPoints[ iterator ] = predictionPoints[iterator-1] + projectedDisplacement; // call recursively until array is populated iterator++; if (iterator <= predictionPoints.Length-1) { // our best guess of the velocity experienced during the next step - another kinematic equation Vector3 projectedVelocity = velocity + (acceleration * projectionStepTimeDelta); // next step's acceleration, based on current accel due to gravity at our projected point Vector3 projectedAcceleration = computeGravityForPosition( predictionPoints[ iterator ] ); // call method again with our best guess values RecursiveProjection( iterator, projectedVelocity, projectedAcceleration ); } } By the way, I didn't really proof your computeGravity function carefully, but I was unclear on why gravityForce is clamped to be <=1 Just a heads up that could cause trouble unless you know what you're doing. I'd love to hear how this works out, don't leave us hanging!
Still not working :/ I think I will refrain from taking the current motion in account and just shoot the object as if it is standing still. Maybe this is more fun for a player anyways. Thank you very much for your effort!
Just to clarify, whether the object is already in motion makes no difference whatsoever using my approach, since the current velocity and force to apply are both considered in step one of the loop. You'll still need to solve the projection same way, except it will no longer be nearly as accurate. What exactly isn't working? Maybe we can figure it out.
What isn't or wasn't working all the time, was that the prediction was way to heavy. You're right, I mistaken something, with your approach the current motion is taken in account. But the prediciton was still going to outer space This is my current version. It looks pretty fine and the remaining error coud come from what you already said, that the prediciton obviously gets impresice over time. Code (csharp): private void drawWayPrediction() { Vector3 pos = this.transform.position; // The force which will be applied to the planet when player shoots. Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition); forceVector.z = 0.0F; float timeDelta = 0.2F; Vector3 force = forceVector * mass * FORCE_FACTOR; Vector3 acceleration = (this.constantForce.force + (force / this.rigidbody.mass)) * timeDelta; Vector3 velocity = this.rigidbody.velocity; predictionPoints[0] = pos; for(int i = 1; i < predictionPoints.Length; i++) { pos += (velocity * timeDelta) + (acceleration * Mathf.Pow(timeDelta, 2) * 0.5F); predictionPoints[i] = pos; velocity = (pos - predictionPoints[i - 1]) / timeDelta; acceleration = computeGravityForPosition(pos) / timeDelta; } }
Only thing I see right away is that the acceleration you calculate in the loop shouldn't be divided by the timeDelta - that doesn't make any sense. Nor does multiplying by timeDelta when you first set acceleration before the loop. Getting the velocity your way ought to be okay. Great job keeping it tidy, I went overboard trying to make it obvious what I was doing. If you're happy with the results you're getting then congratulations! I love a good tricky math problem - especially when it's someone else's problem.