{"id":383,"date":"2024-05-30T19:29:05","date_gmt":"2024-05-30T18:29:05","guid":{"rendered":"https:\/\/blog.powered-up-games.com\/wordpress\/?p=383"},"modified":"2024-07-09T11:44:40","modified_gmt":"2024-07-09T10:44:40","slug":"lazy-problem-solving","status":"publish","type":"post","link":"https:\/\/powered-up-games.com\/blog\/wordpress\/archives\/383","title":{"rendered":"Lazy problem solving"},"content":{"rendered":"\n<p>One problem I&#8217;ve set as an exercise for students for some time is hitting a moving target. The problem is, if the projectile takes some time to reach the target, then you need to aim ahead of the target by an amount. And that amount depends on the distance to the aim ahead point, which depends on the time, and so on.<\/p>\n\n\n\n<p>The typical approach to solving this problem is to create a quadratic equation to find the time when the projectile trajectory intersects the target trajectory. Then you can work your way back by plugging the time into the target velocity and calculating the aim ahead point. Finally you calculate the angle you need to hit that point and fire at that angle.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>equate target and projectile positions in terms of time<\/li>\n\n\n\n<li>solve for time<\/li>\n\n\n\n<li>project target position at time<\/li>\n\n\n\n<li>find angle to projected position<\/li>\n\n\n\n<li>fire along that angle<\/li>\n<\/ol>\n\n\n\n<p>If you&#8217;ve ever been through this process, you&#8217;ll know the number of terms in the equation you need to solve gets quite large during the working out. And that means that the potential for making a mistake is quite high. So you have to proceed carefully to avoid factoring errors.<\/p>\n\n\n\n<p>While it <em>is<\/em> good practice, it is also tedious. Having done it a number of times before, I found that I couldn&#8217;t be bothered going through the explanation again. I wondered if there was a simpler approach. As it turned out, there was.<\/p>\n\n\n\n<p>The trick to lazy programming is not calculating things until you absolutely need them. And the way to do that is to write your program backwards. Sometimes this is called programming by wishful thinking, or programming by intent, or top down programming. But whatever you want to call it, the purpose is to stake out our final destination before writing the code.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0\">if only there were a function like this<\/span><span role=\"button\" tabindex=\"0\" data-code=\"transform.rotation = AimAheadOfMovingTarget(target);\" style=\"color:#d8dee9ff;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki nord\" style=\"background-color: #2e3440ff\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #D8DEE9\">transform<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">rotation<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #88C0D0\">AimAheadOfMovingTarget<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">target<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #81A1C1\">;<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Another problem solving trick is to solve a simpler problem first. So let&#8217;s ignore cases where the target velocity could be in any direction and focus just on the case that forms a right angled triangle.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/blog.powered-up-games.com\/wordpress\/wp-content\/uploads\/2024\/05\/Target.png\"><img loading=\"lazy\" decoding=\"async\" width=\"375\" height=\"401\" src=\"https:\/\/blog.powered-up-games.com\/wordpress\/wp-content\/uploads\/2024\/05\/Target.png\" alt=\"\" class=\"wp-image-384\" srcset=\"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-content\/uploads\/2024\/05\/Target.png 375w, https:\/\/powered-up-games.com\/blog\/wordpress\/wp-content\/uploads\/2024\/05\/Target-281x300.png 281w\" sizes=\"auto, (max-width: 375px) 100vw, 375px\" \/><\/a><figcaption class=\"wp-element-caption\">a simpler problem<\/figcaption><\/figure>\n\n\n\n<p>To find the angle here we only need lengths <em>o<\/em> and <em>h<\/em> and we can use simple trigonometry to find the angle <em>a<\/em>:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>\\sin{a} = \\frac{o}{h}<\/pre><\/div>\n\n\n\n<p>Or:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>a = \\arcsin{\\frac{o}{h}}<\/pre><\/div>\n\n\n\n<p>So all we really need, to calculate angle <em>a<\/em>, is the ratio of lengths <em>o<\/em> and <em>h<\/em>. Given:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>v = target\\ velocity\\\\\np = projectile\\ velocity\\\\\nt = time<\/pre><\/div>\n\n\n\n<p>The values for <em>o<\/em> and <em>h<\/em> are:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>o = ||v||\\ t\\\\\nh = ||p||\\ t<\/pre><\/div>\n\n\n\n<p>So plugging that back into the expression for the angle we get:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>a = \\arcsin{\\frac{||v||\\ t}{||p||\\ t}}<\/pre><\/div>\n\n\n\n<p>And as you can see, the time variables <em>t<\/em> will cancel out leaving:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>a = \\arcsin{\\frac{||v||}{||p||}}<\/pre><\/div>\n\n\n\n<p>So we don&#8217;t need to know the intercept time <em>t<\/em>. And we also don&#8217;t need to know the distance to the target. And what this means is that the angle will be the same whatever distance the target is from us.<\/p>\n\n\n\n<p>One thing we do need to know though, is the relative direction. If the target trajectory was to the left of us, instead of to the right, the values for <em>o<\/em> and <em>h<\/em> would be the same. We wouldn&#8217;t know whether to rotate to the left or the right.<\/p>\n\n\n\n<p>To make this work we really want a negative value for <em>o<\/em> if the trajectory of the target is to the left. That way, our result will be positive for a clockwise turn and negative for anti-clockwise. So what we want is a function that, given a vector and a direction, will return us a distance in that direction. Fortunately, that function already exists. It is the dot product:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>{a} \\cdot {b} = ||a||\\ ||b|| \\cos{\\theta}<\/pre><\/div>\n\n\n\n<p>Where <em>a<\/em> and <em>b<\/em> are vectors, and <em>&theta;<\/em> is the angle between them. So given a vector that points to the right of our target:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>r = unit\\ vector\\ to\\ the\\ right\\ of\\ target<\/pre><\/div>\n\n\n\n<p>We can find a directional value for the trajectory <em>v<\/em> like this:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>v \\cdot r = ||v||\\ ||r|| \\cos{\\theta}<\/pre><\/div>\n\n\n\n<p>And since we defined <em>r<\/em> to be of length 1, this becomes:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>v \\cdot r = ||v||\\cos{\\theta}<\/pre><\/div>\n\n\n\n<p>If angle <em>&theta;<\/em> is 0, the cosine will be 1, so the result will be the length of <em>v<\/em>. But if the angle <em>&theta;<\/em> is 180\u00b0, the cosine will be -1, so the result will be the negative length of <em>v<\/em>.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:4;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0\">the simple case<\/span><span role=\"button\" tabindex=\"0\" data-code=\"Quaternion AimAheadOfMovingTarget(Target target)\n{\n\tfloat v = Vector3.Dot(target.velocity, Vector3.right);\n\tfloat p = projectile.speed;\n\tfloat a = Mathf.Asin(v \/ p) * Mathf.Rad2Deg;\n\treturn Quaternion.AngleAxis(a, Vector3.up);\n}\" style=\"color:#d8dee9ff;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki nord\" style=\"background-color: #2e3440ff\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #D8DEE9FF\">Quaternion <\/span><span style=\"color: #88C0D0\">AimAheadOfMovingTarget<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9FF\">Target target<\/span><span style=\"color: #ECEFF4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ECEFF4\">{<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">float<\/span><span style=\"color: #D8DEE9FF\"> v <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Dot<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">target<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">velocity<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">right<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">float<\/span><span style=\"color: #D8DEE9FF\"> p <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">projectile<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">speed<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">float<\/span><span style=\"color: #D8DEE9FF\"> a <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Mathf<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Asin<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">v<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">\/<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">p<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">*<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Mathf<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">Rad2Deg<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">return<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Quaternion<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">AngleAxis<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">a<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">up<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ECEFF4\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>As a bonus, if the angle is anything in between, the result will be that component of the velocity that is moving orthogonally to the right of us. So wherever the target is moving, we will get the right value to plug into our triangle to get the aim ahead angle. It does seem odd here that the other component is not relevant. It&#8217;s fairly obvious in the case that the target is coming directly towards or away from us that the angle would be 0 in both cases. Although it might be that we will never hit due to the target outrunning the projectile.<\/p>\n\n\n\n<p>But if the direction was diagonally away or towards it doesn&#8217;t seem immediately intuitive that the angle needed to hit it would be the same. If we work backwards, starting with the assumption that we hit, we can trace back to where the target must have come from.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/blog.powered-up-games.com\/wordpress\/wp-content\/uploads\/2024\/05\/Backtrack.png\"><img loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"653\" src=\"https:\/\/blog.powered-up-games.com\/wordpress\/wp-content\/uploads\/2024\/05\/Backtrack.png\" alt=\"\" class=\"wp-image-390\" srcset=\"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-content\/uploads\/2024\/05\/Backtrack.png 500w, https:\/\/powered-up-games.com\/blog\/wordpress\/wp-content\/uploads\/2024\/05\/Backtrack-230x300.png 230w\" sizes=\"auto, (max-width: 500px) 100vw, 500px\" \/><\/a><figcaption class=\"wp-element-caption\">working backwards<\/figcaption><\/figure>\n\n\n\n<p>Now if we scale down the far target triangle such that its location matches the near target, we can see that these are similar triangles and the ratio between <em>o<\/em>  and <em>h<\/em>, and therefore the angle <em>a<\/em>, remain the same.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/blog.powered-up-games.com\/wordpress\/wp-content\/uploads\/2024\/05\/Scale.png\"><img loading=\"lazy\" decoding=\"async\" width=\"497\" height=\"653\" src=\"https:\/\/blog.powered-up-games.com\/wordpress\/wp-content\/uploads\/2024\/05\/Scale.png\" alt=\"\" class=\"wp-image-393\" srcset=\"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-content\/uploads\/2024\/05\/Scale.png 497w, https:\/\/powered-up-games.com\/blog\/wordpress\/wp-content\/uploads\/2024\/05\/Scale-228x300.png 228w\" sizes=\"auto, (max-width: 497px) 100vw, 497px\" \/><\/a><figcaption class=\"wp-element-caption\">any parallel path scales down<\/figcaption><\/figure>\n\n\n\n<p>So that solves pretty much every case. However, we do still need to find this right pointing vector <em>r<\/em>. In our simple case it is a global constant, but in the more general case we will need to take the vector to the target start position into account. The vector r is 90\u00b0 to the <em>up <\/em>and <em>to target<\/em> vectors. And the <em>up<\/em> is at 90\u00b0 to the <em>to target<\/em> and <em>target velocity<\/em> vectors.<\/p>\n\n\n\n<p>We can find a vector at 90<strong>\u00b0<\/strong> to two vectors using the cross product function:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>a \\times b = ||a||\\ ||b||\\sin{\\theta}\\ n<\/pre><\/div>\n\n\n\n<p>Where <em>a<\/em> and <em>b<\/em> are the two vectors and <em>n<\/em> is a vector at 90\u00b0. As you can see, the vector n is scaled by the lengths of the vectors and the sine of the angle between them, but we can ignore all of that and just use the vector.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0\">finding the vectors<\/span><span role=\"button\" tabindex=\"0\" data-code=\"Vector3 toTarget = target.position - position;\nVector3 up = Vector3.Cross(toTarget, target.velocity);\nVector3 right = Vector3.Cross(up, toTarget).normalized;\" style=\"color:#d8dee9ff;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki nord\" style=\"background-color: #2e3440ff\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #D8DEE9FF\">Vector3 toTarget <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">target<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">position<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">-<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">position<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">Vector3 up <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Cross<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">toTarget<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">target<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">velocity<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">Vector3 right <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Cross<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">up<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">toTarget<\/span><span style=\"color: #ECEFF4\">).<\/span><span style=\"color: #D8DEE9\">normalized<\/span><span style=\"color: #81A1C1\">;<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>The final step is to combine the rotation around the <em>up <\/em>axis with a rotation to point at the target. The <a href=\"https:\/\/docs.unity3d.com\/2022.3\/Documentation\/ScriptReference\/Quaternion.html\">Quaternion <\/a>class provides functions to do both of these. We just need to remember to combine them in reverse order.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:4;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0\">final code<\/span><span role=\"button\" tabindex=\"0\" data-code=\"Quaternion AimAheadOfMovingTarget(Target target)\n{\n\tVector3 toTarget = target.position - position;\n\tVector3 up = Vector3.Cross(toTarget, target.velocity);\n\tVector3 right = Vector3.Cross(up, toTarget).normalized;\n\tfloat v = Vector3.Dot(target.velocity, right);\n\tfloat p = projectile.speed;\n\tfloat a = Mathf.Asin(v \/ p) * Mathf.Rad2Deg;\n\treturn Quaternion.AngleAxis(a, up) * Quaternion.LookRotation(toTarget, up);\n}\" style=\"color:#d8dee9ff;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki nord\" style=\"background-color: #2e3440ff\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #D8DEE9FF\">Quaternion <\/span><span style=\"color: #88C0D0\">AimAheadOfMovingTarget<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9FF\">Target target<\/span><span style=\"color: #ECEFF4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ECEFF4\">{<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\tVector3 toTarget <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">target<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">position<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">-<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">position<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\tVector3 up <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Cross<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">toTarget<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">target<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">velocity<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\tVector3 right <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Cross<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">up<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">toTarget<\/span><span style=\"color: #ECEFF4\">).<\/span><span style=\"color: #D8DEE9\">normalized<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">float<\/span><span style=\"color: #D8DEE9FF\"> v <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Vector3<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Dot<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">target<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">velocity<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">right<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">float<\/span><span style=\"color: #D8DEE9FF\"> p <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">projectile<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">speed<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">float<\/span><span style=\"color: #D8DEE9FF\"> a <\/span><span style=\"color: #81A1C1\">=<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Mathf<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">Asin<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">v<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">\/<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">p<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">*<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Mathf<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #D8DEE9\">Rad2Deg<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D8DEE9FF\">\t<\/span><span style=\"color: #81A1C1\">return<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Quaternion<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">AngleAxis<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">a<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">up<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #81A1C1\">*<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">Quaternion<\/span><span style=\"color: #ECEFF4\">.<\/span><span style=\"color: #88C0D0\">LookRotation<\/span><span style=\"color: #ECEFF4\">(<\/span><span style=\"color: #D8DEE9\">toTarget<\/span><span style=\"color: #ECEFF4\">,<\/span><span style=\"color: #D8DEE9FF\"> <\/span><span style=\"color: #D8DEE9\">up<\/span><span style=\"color: #ECEFF4\">)<\/span><span style=\"color: #81A1C1\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ECEFF4\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>I have uploaded a <a href=\"https:\/\/github.com\/LSBUSGP\/AimAhead\">Unity project<\/a> to GitHub to demonstrate this approach.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sometimes, if you are lazy enough, you can get a solution without having to do all the working out<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,6,5],"tags":[24,39,32],"class_list":["post-383","post","type-post","status-publish","format-standard","hentry","category-process","category-programming","category-video-games","tag-education","tag-programming","tag-unity"],"_links":{"self":[{"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts\/383","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/comments?post=383"}],"version-history":[{"count":13,"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts\/383\/revisions"}],"predecessor-version":[{"id":402,"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts\/383\/revisions\/402"}],"wp:attachment":[{"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/media?parent=383"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/categories?post=383"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/powered-up-games.com\/blog\/wordpress\/wp-json\/wp\/v2\/tags?post=383"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}