{"id":59886,"date":"2018-11-05T08:57:00","date_gmt":"2018-11-05T08:57:00","guid":{"rendered":"http:\/\/www.gamasutra.com\/view\/news\/329944"},"modified":"2018-11-05T08:57:00","modified_gmt":"2018-11-05T08:57:00","slug":"game-programming-deep-dive-3-scenarios-for-movement-prediction","status":"publish","type":"post","link":"https:\/\/sickgaming.net\/blog\/2018\/11\/05\/game-programming-deep-dive-3-scenarios-for-movement-prediction\/","title":{"rendered":"Game Programming Deep Dive: 3 scenarios for movement prediction"},"content":{"rendered":"<p><strong><i><small> The following blog post, unless otherwise noted, was written by a member of Gamasutra\u0092s community.<br \/>The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company. <\/small><\/i><\/strong> <\/p>\n<hr \/>\n<p>We need to use movement prediction in our games more often than we might expect. We, as humans, predict the future position of objects all the time, and we act accordingly to that prediction. For example, when we want to catch a moving ball, we do not run to the place where it is currently in the air or on the ground. Instead, we naturally predict where the ball will be in a few seconds and move toward that predicted position. Based on our prediction skills we can catch the ball easily, or not so easily if our prediction was wrong.<\/p>\n<p>Here are the examples of calculating predicted movement in a game:<\/p>\n<ul>\n<li>A trajectory of a grenade, to prevent a character from running towards it.<\/li>\n<p>&#013; <\/p>\n<li>Firing a projectile in the direction of the predicted place of a moving enemy.<\/li>\n<p>&#013; <\/p>\n<li>Movement of a vehicle in the close future to allow characters to avoid it.<\/li>\n<p>&#013; <\/p>\n<li>Predicted position of a replicated object over the network to compensate for the lag.<\/li>\n<p>&#013;\n<\/ul>\n<p>In general, if we want to predict the movement of an object, we need to execute some form of physical simulation. The ideal situation would be if we could simulate the whole physical world for the needed time in the future. In most cases, because of the limited computing power, this is not (yet!) reasonable. However, depending on our needs, we can perform a simplified simulation to get the predicted state of the single object in the future.<\/p>\n<p>In this article, I would like to describe three different scenarios of how we typically use prediction in games:<\/p>\n<ol>\n<li><strong>Character<\/strong>. In this case, we are interested in a predicted position for a short amount of time in the future.<\/li>\n<p>&#013; <\/p>\n<li><strong>Projectile<\/strong>. For projectiles, we want a much longer time of predicted movement including collisions.<\/li>\n<p>&#013; <\/p>\n<li><strong>Vehicle<\/strong>. For vehicles, to properly handle the predicted movement, we also need to take into account the orientation with angular velocity.<\/li>\n<p>&#013;\n<\/ol>\n<p>Nevertheless, we still need to remember that this is a prediction. We are not running the full physical simulation. Rather, we want to compute the most probable state of the object in the future.<\/p>\n<p>To perform our predictions as a form of physical simulation, we need the basic physical quantity for the kinematic object: a velocity [1]. In general, the velocity is a rate of change of position in respect to time and is expressed using a derivative:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction.png\" \/><\/p>\n<p>However, for our prediction needs, we can use the formula for the velocity during the constant movement, without referring directly to the derivative:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-1.png\" \/><\/p>\n<p>where <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-2.png\" \/>\u00a0is the position displacement (from point <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-3.png\" \/>\u00a0to <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-4.png\" \/>) and <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-5.png\" \/>\u00a0is the elapsed time (from <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-6.png\" \/>\u00a0to <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-7.png\" \/>).<\/p>\n<p>Velocity is a vector quantity, which has a direction and a length. The length of a velocity vector is a scalar value and can be called speed.<\/p>\n<p>Our main concern for the character is the prediction for the short amount of time in the future &#8211; usually less than 1 s. This is the basic case for the prediction, where we make a few assumptions:<\/p>\n<ol>\n<li>We simulate the character\u2019s movement as a point moving along the straight line without any collisions.<\/li>\n<p>&#013; <\/p>\n<li>We know the character\u2019s current velocity.<\/li>\n<p>&#013; <\/p>\n<li>We assume that the character will continue its movement along the straight line with its current velocity.<\/li>\n<p>&#013;\n<\/ol>\n<p>We start with the formula for the constant velocity from the previous section:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-8.png\" \/><\/p>\n<p>But this time we assume that <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-9.png\" \/>, <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-3.png\" \/>, and <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-5.png\" \/>\u00a0are known variables and <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-4.png\" \/>\u00a0is unknown.<\/p>\n<p>We multiply both sides by <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-5.png\" \/>:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-10.png\" \/><\/p>\n<p>Then, we move <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-3.png\" \/>\u00a0to the left side:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-11.png\" \/><\/p>\n<p>Finally, we swap both sides of the equation:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-12.png\" \/><\/p>\n<p>and we get the final formula, where <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-3.png\" \/>\u00a0is the current position of the character, <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-9.png\" \/>\u00a0is the character\u2019s velocity, <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-5.png\" \/>\u00a0is the time of the prediction, and <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-4.png\" \/>\u00a0is our predicted position in the future.<\/p>\n<p>This formula has a natural geometrical explanation. The object moves with a constant linear movement, so we are moving the point along the velocity vector <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-9.png\" \/>\u00a0from the point <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-3.png\" \/>\u00a0to the point <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-4.png\" \/>.<\/p>\n<p>Below you can find the code in Unity Engine\u2122 with a straightforward implementation of the final formula:<\/p>\n<pre><code class=\"language-cs\">Vector3 LinearMovementPrediction(Vector3 CurrentPosition, Vector3 CurrentVelocity, float PredictionTime)&#013;\n{&#013; Vector3 PredictedPosition = CurrentPosition + CurrentVelocity * PredictionTime;&#013; return PredictedPosition;&#013;\n}<\/code><\/pre>\n<p>In navigation, the process to predict the position in the future is called \u201cdead reckoning\u201d [2] and it uses this formula as the basic computation technique.<\/p>\n<p>This is also a simple formula to predict the movement of a character in multiplayer games while waiting for a new network data to arrive [3, section \u2018Display of Targets\u2019].<\/p>\n<p>On the image below, we have an example with a geometrical description for this prediction scenario:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-13.png\" \/>\u00a0<\/p>\n<p>The blue curve marks the movement of the object (dashed blue line is the future movement). Velocity is the derivative of position function, so it lies on a tangent at the point <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-3.png\" \/>\u00a0[4, section &#8216;Kinematic quantities&#8217;]. Since we are predicting position along the velocity vector, we can observe how a longer prediction time <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-5.png\" \/>\u00a0will cause the predicted position to diverge from the real position in the future. A shorter prediction time <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-5.png\" \/>\u00a0gives a better approximation.<\/p>\n<h3><strong>Acceleration<\/strong><\/h3>\n<p>There are situations when we want to consider acceleration in our prediction. In such cases, instead of assuming the constant velocity, we will assume the constant acceleration during the prediction period:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-14.png\" \/><\/p>\n<p>where <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-15.png\" \/>\u00a0is the initial velocity, <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-16.png\" \/>\u00a0is the final velocity, and <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-5.png\" \/>\u00a0is the prediction time.<\/p>\n<p>The formula for the predicted position <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-4.png\" \/>\u00a0with the constant acceleration <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-17.png\" \/>\u00a0is given by<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-18.png\" \/><\/p>\n<p>As we can see, when there is no acceleration (<img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-19.png\" \/>), this formula still gives the same prediction result as the equation from the previous section.<\/p>\n<p>The formula can be easily explained using the graph below for one-dimensional movement:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-20.png\" \/><\/p>\n<p>The graphs show a relation between velocity and time. In other words, it shows how velocity (a blue line) changes over time. When the graph is given in such a way, the area under the velocity line (marked with a blue color) is the distance traveled by the moving object [1, section \u2018Instantaneous velocity\u2019]. We can calculate that area as a sum of two figures: a rectangle A and a right triangle B.<\/p>\n<p>Using notation for the areas with the initial distance <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-21.png\" \/>\u00a0and the final distance <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-22.png\" \/>\u00a0traveled by the moving object, we get the formula:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-23.png\" \/><\/p>\n<p>The area of the rectangle A is:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-24.png\" \/><\/p>\n<p>The area of the right triangle B can be computed as the half of the upper rectangle:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-25.png\" \/><\/p>\n<p>Because the acceleration is given by <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-26.png\" \/>\u00a0, so <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-27.png\" \/>. Using that, we can change the equation above to:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-28.png\" \/><\/p>\n<p>Finally, substituting for A and B in the first formula for <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-22.png\" \/>\u00a0we get:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-29.png\" \/><\/p>\n<p>By renaming variables <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-21.png\" \/>\u00a0to <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-3.png\" \/>\u00a0and <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-22.png\" \/>\u00a0to <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-4.png\" \/>, we obtain the formula for the predicted position <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-4.png\" \/>\u00a0given at the beginning of this section:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-18.png\" \/><\/p>\n<p>In the case of projectiles, we are interested in the much longer time of our prediction. Additionally, we want to track the projectile when it bounces off other objects.<\/p>\n<p>To perform this prediction, we need to execute a simulation loop and check for collisions in every step. We will assume simple ballistics physics for the projectile moving as a point [4, section \u2018Uniform acceleration\u2019]. Below is the code with the implementation in the Unity Engine:<\/p>\n<pre><code class=\"language-cs\">Vector3 PredictProjectileMovement(Vector3 InitialPosition, Vector3 InitialVelocity, float TimeToExplode)&#013;\n{&#013; float Restitution = 0.5f;&#013;\n\u00a0\u00a0\u00a0\u00a0Vector3 Position = InitialPosition;&#013;\n\u00a0\u00a0\u00a0\u00a0Vector3 Velocity = InitialVelocity;&#013;\n\u00a0\u00a0\u00a0 Vector3 GravitationalAcceleration = new Vector3(0.0f, -9.81f, 0.0f);&#013;\n\u00a0\u00a0\u00a0 float t = 0.0f;&#013;\n\u00a0\u00a0\u00a0 float DeltaTime = 0.02f;&#013;\n&#013;\n\u00a0\u00a0\u00a0\u00a0while (t &lt; TimeToExplode)&#013;\n\u00a0\u00a0\u00a0 {&#013;\n\u00a0\u00a0\u00a0 Vector3 PreviousPosition = Position;&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Vector3 PreviousVelocity = Velocity;&#013;\n&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Position += Velocity * DeltaTime + 0.5f * GravitationalAcceleration * DeltaTime * DeltaTime;&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Velocity += GravitationalAcceleration * DeltaTime;&#013;\n&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Collision detection.&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RaycastHit HitInfo;&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (Physics.Linecast(PreviousPosition, Position, out HitInfo))&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Recompute velocity at the collision point.&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 float FullDistance = (Position - PreviousPosition).magnitude;&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 float HitCoef = (FullDistance &gt; 0.000001f) ? (HitInfo.distance \/ FullDistance) : 0.0f;&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Velocity = PreviousVelocity + GravitationalAcceleration * DeltaTime * HitCoef;&#013;\n&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Set the hit point as the new position.&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Position = HitInfo.point;&#013;\n&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Collision response. Bounce velocity after the impact using coefficient of restitution.&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 float ProjectedVelocity = Vector3.Dot(HitInfo.normal, Velocity);&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Velocity += -(1+Restitution) * ProjectedVelocity * HitInfo.normal;&#013; }&#013;\n&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0t += DeltaTime;&#013; }&#013;\n&#013; \/\/ Return the final predicted position.&#013;\n\u00a0\u00a0\u00a0 return Position;&#013;\n}<\/code><\/pre>\n<p>We will explain the code given above in the following three subsections: Simulation loop, Collision detection, and Collision response.<\/p>\n<h3><strong>Simulation loop<\/strong><\/h3>\n<p>The choice of the time step (<em>DeltaTime<\/em>) for every iteration depends on our requirements for accuracy. Typically, this will be the same value as the time step for our physics engine (in Unity Engine the default value is 0.02 s).<\/p>\n<p>At the beginning of the loop, before evaluating our movement equations, we store position and velocity in <em>PreviousPosition<\/em> and <em>PreviousVelocity<\/em> variables. This is required to perform collision detection.<\/p>\n<p>We are considering only gravitational acceleration, which is constant during the whole movement. Because of that, we can use the formula from the previous section to compute the new position after a given time step:<\/p>\n<pre><code class=\"language-cs\">Position += Velocity * DeltaTime + 0.5f * GravitationalAcceleration * DeltaTime * DeltaTime;<\/code><\/pre>\n<p>Similarly, the new velocity is calculated using acceleration:<\/p>\n<pre><code class=\"language-cs\">Velocity += GravitationalAcceleration * DeltaTime;<\/code><\/pre>\n<h3><strong>Collision detection<\/strong><\/h3>\n<p>The essential part of the loop is the test for collisions, which is described in the image below:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image059.png\" \/><\/p>\n<p>We use position from the previous step (variable <em>PreviousPosition<\/em>) and the newly calculated position (variable <em>Position<\/em>) to test if the line segment between them hits any objects. The collision test is performed by the Unity Engine method <em>Physics<\/em><em>.LineCast<\/em>:<\/p>\n<pre><code class=\"language-cs\">Physics.Linecast(PreviousPosition, Position, out HitInfo)<\/code><\/pre>\n<p>If there is a collision, the method returns true and stores the result in <em>HitInfo<\/em>:<\/p>\n<ul>\n<li><em>HitInfo.point<\/em> &#8211; the impact point (<em>HitPoint<\/em> in the image above).<\/li>\n<p>&#013; <\/p>\n<li><em>HitInfo.normal<\/em> &#8211; the unit vector perpendicular to the surface of the collision (<em>HitNormal<\/em> in the image above).<\/li>\n<p>&#013; <\/p>\n<li><em>HitInfo.distance<\/em> &#8211; the distance from the beginning of the line segment (variable <em>PreviousPosition<\/em>) to the impact point.<\/li>\n<p>&#013;\n<\/ul>\n<p>Having that data, we can recalculate position and velocity at the point of collision.<\/p>\n<p>The velocity is recomputed first to get the precise value at the <em>HitPoint<\/em>. The variable <em>HitCoef<\/em> has a value between 0 and 1, and is the ratio of the <em>HitInfo.distance<\/em> to the total length of the line segment. We recompute the velocity using the same movement equation but scaling the last component with the <em>HitCoef<\/em>. In this way, we scale the <em>DeltaTime<\/em> to the moment of collision and obtain the value of the velocity at the collision point.<\/p>\n<p>The new position is simply the point of impact.<\/p>\n<h3><strong>Collision response<\/strong><\/h3>\n<p>Finally, we are ready to bounce the velocity according to the impacted surface. We use the <em>HitNormal<\/em> and coefficient of restitution (how strong the bounce should be) [5]. The value of <em>Restitution<\/em> should be between 0 and 1. If the <em>Restitution<\/em> variable is 1, there is a perfect bounce, and no energy is lost at the collision. If the <em>Restitution<\/em> is 0, then the whole energy is lost, and the velocity along the hit normal is canceled entirely.<\/p>\n<p>Calculation of the bounced velocity (after the impact) is described in the image below and the following text derives the final formula:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image060.png\" \/><\/p>\n<p>To compute the velocity after the impact, we need to assume that we want to apply an impulse along the vector <em>n<\/em> (<em>HitNormal<\/em>) with unknown magnitude <em>j<\/em> to change the velocity:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image062.png\" \/><\/p>\n<p>We start our computations with the main equation defining coefficient of restitution as a relation between relative velocities:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image064.png\" \/><\/p>\n<p>In general, relative velocities <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-15.png\" \/>\u00a0and <img decoding=\"async\" src=\"http:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2018\/11\/game-programming-deep-dive-3-scenarios-for-movement-prediction-16.png\" \/>\u00a0are computed as projections onto the collision normal of differences between velocities for two colliding objects (symbol <img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image066.png\" \/>\u00a0is the dot product):<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image068.png\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image070.png\" \/><\/p>\n<p>But in our case, because we collide only with a static environment (object B has always zero velocities), it can be simplified to:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image072.png\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image074.png\" \/><\/p>\n<p>Combining three equations together we obtain:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image076.png\" \/><\/p>\n<p>Now we are using the first equation and making a substitution for <em>VelocityAfterHit<\/em>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image078.png\" \/><\/p>\n<p>The left side transformation:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image080.png\" \/><\/p>\n<p>We move the first term to the right side:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image082.png\" \/><\/p>\n<p>Because the collision normal <em>n<\/em> is a unit vector, it has length 1, so <img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image084.png\" \/>\u00a0is also 1:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image086.png\" \/><\/p>\n<p>Finally, we can simplify the right side:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image088.png\" \/><\/p>\n<p>Going back and making a substitution for <em>j<\/em> in the first equation:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image090.png\" \/><\/p>\n<p>This equation is implemented in the code as the line:<\/p>\n<pre><code class=\"language-cs\">Velocity += -(1+Restitution) * ProjectedVelocity * HitInfo.normal;<\/code><\/pre>\n<p>This concludes the details of the collision response.<\/p>\n<p>Summarizing this section, we can observe that our function calculates the full predicted trajectory for the projectile. The value of variable <em>Position<\/em> can be stored at the end of every iteration as a consecutive point of the curve. We can use it to draw the expected trajectory when a player is aiming to throw a grenade. Also, we can send the final position to the AI system to mark places to avoid.<\/p>\n<p>When predicting the movement of the vehicle, we will consider only a short amount of prediction time. Unlike previous cases, we need to consider an angular velocity [6]. Vehicles are usually relatively big objects in the game and turning is an essential part of their movement. Hence, even if we consider a short time of prediction (less than 1 s), we still need to calculate the rotational component of the movement.<\/p>\n<p>Angular velocity is the rate of change of orientation in respect to time. Angular velocity is an equivalent to linear velocity but for rotational movement. Angular velocity is a vector quantity. The direction of the vector represents the axis of rotation, and the length of the vector tells us about the rate of change of orientation \u2013 in radians per seconds.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image091.png\" \/><\/p>\n<p>To calculate the full predicted state of the vehicle we need to compute both components of movement: linear and rotational. Linear component (predicted position) can be evaluated in the same way as for characters. For the rotational component, we will assume the constant value of angular velocity during the prediction. The code is presented below:<\/p>\n<pre><code class=\"language-cs\">Quaternion RotationalMovementPrediction(Quaternion CurrentOrientation, Vector3 AngularVelocity, float PredictionTime)&#013;\n{&#013; float RotationAngle = AngularVelocity.magnitude * PredictionTime;&#013; Vector3 RotationAxis = AngularVelocity.normalized;&#013;\n&#013; Quaternion RotationFromAngularVelocity = Quaternion.AngleAxis(RotationAngle * Mathf.Rad2Deg, RotationAxis);&#013;\n&#013; Quaternion PredictedOrientation = CurrentOrientation * RotationFromAngularVelocity;&#013; return PredictedOrientation;&#013;\n}<\/code><\/pre>\n<p>We build rotation quaternion using the axis and angle interface &#8211; the method <em>Quaternion.AngleAxis()<\/em>. The axis of rotation is taken directly from angular velocity as a normalized vector:<\/p>\n<pre><code class=\"language-cs\">RotationAxis = AngularVelocity.normalized;<\/code><\/pre>\n<p>The angle of rotation is computed as the multiplication of the rate of change and our prediction time:<\/p>\n<pre><code class=\"language-cs\">RotationAngle = AngularVelocity.magnitude * PredictionTime;<\/code><\/pre>\n<p>Since the length of angular velocity vector represents the rate of change of the orientation, if we multiply it by our desired time of prediction, the output is the total angle we need to rotate around the axis. For example, let us assume that our vehicle is turning with a rate 0.25 radians per second. If we multiply it by 2 s of prediction time, then we get 0.5 radians as the value of the <em>RotationAngle<\/em> variable (approximately 28.6 degrees). This is the predicted angle the vehicle will turn in 2 s.<\/p>\n<p>Having the rotation axis and the rotation angle, we can build the quaternion from angular velocity (also, using conversion from radians to degrees, because the Unity method expects the angle in degrees as an input):<\/p>\n<pre><code class=\"language-cs\">RotationFromAngularVelocity = Quaternion.AngleAxis(RotationAngle * Mathf.Rad2Deg, RotationAxis);<\/code><\/pre>\n<p>Finally, we need to compute the quaternion to represent the final predicted state. We will use multiplication, which represents a composition of two orientations. Therefore, we multiply the current orientation by the rotation quaternion, which represents the change from angular velocity:<\/p>\n<pre><code class=\"language-cs\">PredictedOrientation = CurrentOrientation * RotationFromAngularVelocity;<\/code><\/pre>\n<p>The result of this multiplication is the orientation of the predicted state of the vehicle:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image092.png\" \/><\/p>\n<h3><strong>Iterations<\/strong><\/h3>\n<p>While executing rotational prediction once will give us a good approximation, for objects like cars or tanks, we might need to execute the prediction several times with smaller time steps. Also, linear velocity in vehicles is aligned with their forward direction, and it changes with every orientation change. To achieve better results &#8211; especially for a longer prediction time &#8211; we can execute both linear and rotational prediction in a loop with just a few iterations. In every iteration, we will match the linear velocity with the new forward direction from orientation to approximate the behavior of vehicle physics. The code is:<\/p>\n<pre><code class=\"language-cs\">void VehicleMovementPrediction(Vector3 Position, Vector3 LinearVelocity, Quaternion Orientation, Vector3 AngularVelocity, float PredictionTime, int NumberOfIterations, out Vector3 outPosition, out Quaternion outOrientation)&#013;\n{&#013; float DeltaTime = PredictionTime \/ NumberOfIterations;&#013;\n\u00a0\u00a0\u00a0 for (int i=1 ; i&lt;=NumberOfIterations ; ++i)&#013;\n\u00a0\u00a0\u00a0 {&#013;\n\u00a0\u00a0\u00a0 Position = LinearMovementPrediction(Position, LinearVelocity, DeltaTime);&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Orientation = RotationalMovementPrediction(Orientation, AngularVelocity, DeltaTime);&#013;\n&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Match LinearVelocity with the new forward direction from Orientation.&#013;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 LinearVelocity = Orientation * new Vector3(0.0f, 0.0f, LinearVelocity.magnitude);&#013;\n\u00a0\u00a0\u00a0 }&#013;\n&#013; outPosition = Position;&#013; outOrientation = Orientation;&#013;\n}<\/code><\/pre>\n<p>After executing the loop, we should see prediction results similar to the image below:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gamasutra.com\/db_area\/images\/blog\/329703\/image093.png\" \/><\/p>\n<p>We have presented three different scenarios for movement prediction. What they have in common is that they all perform simplified physics simulation customized for prediction needs. However, it is important to remember that this is only a prediction and it is impossible to fully predict the future of an interactive world controlled by human players. Our gameplay logic, which relies on prediction results, needs to be prepared for a failure and be ready to deliver a fail-safe solution.<\/p>\n<p>I would like to thank Luis Bermudez, Mohammed Yassir Ouali, and Philippe Baron for their valuable feedback.<\/p>\n<p>[1] \u201cVelocity\u201d, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Velocity\">https:\/\/en.wikipedia.org\/wiki\/Velocity<\/a><\/p>\n<p>[2] \u201cDead reckoning\u201d, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Dead_reckoning\">https:\/\/en.wikipedia.org\/wiki\/Dead_reckoning<\/a><\/p>\n<p>[3] \u201cLatency Compensating Methods in Client\/Server In-game Protocol Design and Optimization\u201d, <a href=\"https:\/\/developer.valvesoftware.com\/wiki\/Latency_Compensating_Methods_in_Client\/Server_In-game_Protocol_Design_and_Optimization\">https:\/\/developer.valvesoftware.com\/wiki\/Latency_Compensating_Methods_in_Client\/Server_In-game_Protocol_Design_and_Optimization<\/a><\/p>\n<p>[4] \u201cEquations of motion\u201d, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Equations_of_motion\">https:\/\/en.wikipedia.org\/wiki\/Equations_of_motion<\/a><\/p>\n<p>[5] \u201cCoefficient of restitution\u201d, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Coefficient_of_restitution\">https:\/\/en.wikipedia.org\/wiki\/Coefficient_of_restitution<\/a><\/p>\n<p>[6] \u201cAngular velocity\u201d, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Angular_velocity\">https:\/\/en.wikipedia.org\/wiki\/Angular_velocity<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The following blog post, unless otherwise noted, was written by a member of Gamasutra\u0092s community.The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company. We need to use movement prediction in our games more often than we might expect. We, as humans, predict the future position of objects [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":59887,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20],"tags":[],"class_list":["post-59886","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news"],"_links":{"self":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/59886","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/comments?post=59886"}],"version-history":[{"count":0,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/59886\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media\/59887"}],"wp:attachment":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media?parent=59886"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/categories?post=59886"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/tags?post=59886"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}