<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Little Polygon Game Dev Blog</title>
    <link>https://blog.littlepolygon.com/</link>
    <description>Little Polygon Game Dev Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 07 Apr 2025 00:00:00 +0000</lastBuildDate>
    
    <atom:link href="https://blog.littlepolygon.com/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>🏗️ Scaffold Level Editor</title>
      <link>https://blog.littlepolygon.com/posts/scaffold/</link>
      <pubDate>Mon, 07 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/scaffold/</guid>
      <description>&lt;p&gt;On the latest &lt;a href=&#34;https://store.steampowered.com/app/3038700/Nightshift_Galaxy/&#34;&gt;&lt;em&gt;Nightshift Galaxy&lt;/em&gt;&lt;/a&gt; weekly dev stream I demonstrated the specialized level editting tool I&amp;rsquo;m building inside the Unreal Level Editor that I’m calling &lt;strong&gt;Scaffold&lt;/strong&gt;.&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/RDZPNKNbJYc&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;I gave an impromptu introduction to my motivations and inspirations live, but on reflection the topic deserves a written deep-dive.&lt;/p&gt;
&lt;p&gt;In designing tools I’m guided by three high-level goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Productivity&lt;/strong&gt;. As a solo-developer, I need to automate as much of my workflow as possible, to ensure that I focus on places where my specific skill-set adds value, and avoid labor-intensive tasks that aren’t core to gameplay.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Individuality&lt;/strong&gt;. A risk of using a generic game engine, like Unreal or Unity, and common marketplace assets is producing a generic game. By developing unique systems, I can enable unique gameplay that has market differentiation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;. As an action game with a high skill-ceiling there is no “correctness vs. performance” tradeoff; 60+ FPS is a correctness requirement.  Furthermore, in my experience, to confidently reach a larger audience with midrange commodity devices, one can’t simply chip-away at perf problems in the profiler later – one must architect for it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Scaffold&lt;/strong&gt; addresses these goals by exposing (i) an interactive interior design tool that prioritizes power-user hotkeys and hackability, (ii) gameplay systems that supplement Unreal’s built-in collision and navigation with opinionated parallel systems, and (iii) data-structures (inspired by 90s game engines) that are efficient by-construction.&lt;/p&gt;
&lt;h2 id=&#34;background-convex-decomposition&#34; &gt;Background: Convex Decomposition
&lt;span&gt;
    &lt;a href=&#34;#background-convex-decomposition&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;To explain the details of the system, we need to first establish some background in &lt;strong&gt;Convex Geometry&lt;/strong&gt; that’s relevant to the design. In a nutshell, the convex hull property describes any shape where &lt;em&gt;the straight-line connecting any two points within the shape is also contained in the shape&lt;/em&gt;.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/convex.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Sample Convex Shapes in 2D&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This is in contrast to concave shapes that have notches or holes cut out of them.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/concave.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Sample Concave Shapes in 2D&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Most spatial algorithms in games, particularly raytracing and pathfinding, implicitly rely on the convex-hull property by partitioning complex scenes into a network of connected convex-volumes through a process called &lt;strong&gt;Convex Decomposition&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this data-structure, the faces of the volumes fall into two categories: &lt;em&gt;Walls&lt;/em&gt; that close the outside of the composed shape, and &lt;em&gt;Windows&lt;/em&gt; in-between shared internal edges that are logically open.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/wall_window.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Convex Decomposition&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;To trace a ray through the scene, we start by identifying which volume contains the origin of the ray (ususally from context). Because of the convex-hull property we know we can extend a straight-line all the way to a point on the edge, which we identify through ray-plane intersection. Then we check if the edge is a “wall,” in which case we’ve successful identified the hit, or a “window,” in which case we go back and repeat the process for the newly-entered volume.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/raycast.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Raycasting within a Convex Decomposition&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This algorithm is &lt;em&gt;local&lt;/em&gt; and for volumes with a fixed-capacity for the number of sides has O(1) complexity, relative to the size of the scene, as opposed to a brute-force algorithm which inspects every single wall and therefore has O(n) complexity.&lt;/p&gt;
&lt;p&gt;We extend this algorithm to loose-props and moving-actors by associating with each volume a list of primitives, what I call an “object-bucket,” which is searched for a hit before advancing to the far-edge.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/bucket.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Raycasting with Loose Object &amp;ldquo;Buckets&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;By assigning a fixed-capacity to buckets, this keeps the cost O(1). When we consider that every actor is doing their own traces to move every frame, that gives us something like O(n) total performance complexity, vs. brute force in which every actor is checking every other actor for O(n²) total performance complexity, which is unacceptable for interactive applications.&lt;/p&gt;
&lt;p&gt;If the buckets have variable-capacity, we can avoid the “everyone crowds in one volume” edge-case by recursively subdividing buckets spatially and making a tree-like structure. For a uniform-distribution of actors, this has O(log n) trace complexity and O(n × log n) total complexity, which is the typical threshold in game programming for “fixable via perf profiling.”&lt;/p&gt;
&lt;h2 id=&#34;navigation&#34; &gt;Navigation
&lt;span&gt;
    &lt;a href=&#34;#navigation&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;Another use-case for convex decomposition is &lt;em&gt;pathfinding&lt;/em&gt;. By drawing waypoints in the middle of each “window” we know we can connect them across their shared volumes with straight-lines (again, due to the convex hull property). Furthermore, any actor inside a convex-volume can make their way to any of that volume’s waypoints, and as long as a connected path exists we can draw draw a path between any two points that is free of collision.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/nav.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Pathfinding on a Convex &amp;ldquo;Navigation Mesh&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The algorithm to determine the if the path exists is beyond the scope of this writeup, but it’s called the &lt;a href=&#34;https://en.wikipedia.org/wiki/A*_search_algorithm&#34;&gt;A* Search Algorithm&lt;/a&gt; and there’s lots of easily-searchable literature about it online.&lt;/p&gt;
&lt;h2 id=&#34;collision-and-navigation-in-general-purpose-game-engines&#34; &gt;Collision and Navigation in General-Purpose Game Engines
&lt;span&gt;
    &lt;a href=&#34;#collision-and-navigation-in-general-purpose-game-engines&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;A wrinkle in understanding how these concepts apply to modern general-purpose game engines, like Unity and Unreal, arises the architecture of their level editors. Both eskew the explicit construction of a spatial decomposition, and instead expose to the designer a simple list of loose objects that are hand-places one-by-one (&amp;ldquo;Actors&amp;rdquo; in Unreal, GameObjects&amp;quot; in Unity). Therefore, to apply these algorithms, dynamic data structures are built at run-time, on the game device, based on heuristic, fine-tuned methods.&lt;/p&gt;
&lt;p&gt;For collision and raycasting, the spatial partition is built via an invisible lattice of implicitly-linked convex grid-cells (think like the lattice in Minecraft, except each cell has a object-bucket rather than a solid voxel).&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/quadtree.gif&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Here&amp;rsquo;s a 2D &amp;ldquo;Quadtree&amp;rdquo; illustrated. The convex decomposition is limited to boxes and object-buckets, but no true &amp;ldquo;walls&amp;rdquo;.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The origin of the ray is determined via quantization, and proceeds in the same way outlined above, except the grid-cells have only “window” edges, so we’re only collision-checking the buckets. The step of quickly filtering via local buckets is described in the literature as “Broad-Phase Collision”.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/octree_cast.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Tracing a ray through a broadphase spatial partition (greatly simplified).&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Because a rectangular grid cell cleanly divides into 8 sub-cells, this lattice data structure is called an “Octree.” Due to its simplicity, Octrees are the most common spatial partitions, but other interesting schemes exist, and the solution in production often has several interesting optimizations that are beyond the scope of this summary – but the principle is the same. Both Unreal and Unity used to rely on the &lt;a href=&#34;https://en.wikipedia.org/wiki/PhysX&#34;&gt;PhysX&lt;/a&gt; library for this task, though Unreal has transitioned to an in-house solution called &lt;a href=&#34;https://dev.epicgames.com/documentation/en-us/unreal-engine/physics-in-unreal-engine&#34;&gt;Chaos&lt;/a&gt;.  Another notable solutions are &lt;a href=&#34;https://github.com/jrouwe/JoltPhysics&#34;&gt;Jolt&lt;/a&gt;, which was originally developed for the game &lt;a href=&#34;https://en.wikipedia.org/wiki/Horizon_Zero_Dawn&#34;&gt;Horizon Zero Dawn&lt;/a&gt;, and &lt;a href=&#34;https://www.havok.com/havok-physics/&#34;&gt;Havok&lt;/a&gt;, which has been around a long time, most notably/recently powering Nintendo&amp;rsquo;s &lt;a href=&#34;https://en.wikipedia.org/wiki/The_Legend_of_Zelda:_Breath_of_the_Wild&#34;&gt;Breath of the Wild&lt;/a&gt;.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/zelda.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;&amp;ldquo;I&amp;rsquo;ll just develop my indie game the same way a multimillion dollar company develops theirs&amp;rdquo; is not exactly a winning strategy.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;For navigation, general-purpose engines rely on hand-placed volumes to perform many continuous raycasts on background threads at runtime to asynchronously populate connected &amp;ldquo;ground shapes&amp;rdquo; on which pathfinding can be performed. This process is more art than science, and relies on a vast number of fine-tuning parameters that demand continuous designer tweaking for good results. Unreal, in particular, relies on the &lt;a href=&#34;https://github.com/recastnavigation/recastnavigation&#34;&gt;Recast&lt;/a&gt; library for this task.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/recast.png&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Screenshot of the Recast Navigation system, from their official Github page.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The downside of these general-purpose dynamic solutions is that the CPU and RAM requirements are higher than precomputed explicit decompositions, and they’re more likely to exhibit the variable O(n × log n) complexity that demands laborious perf-tuning. Furthermore, they transfer all the work of constructing the patritions to the end-user’s gaming device, rather than allowing static scene data to be “cooked” ahead of time on developer workstations.&lt;/p&gt;
&lt;h2 id=&#34;looking-forward-by-looking-backward&#34; &gt;Looking Forward by Looking Backward
&lt;span&gt;
    &lt;a href=&#34;#looking-forward-by-looking-backward&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;They&amp;rsquo;re workable in a large-team production setting, but in my case they resisted all three of my high-level goals.  Therefore, I began searching for alternatives &amp;ndash; not to replace these features whole-cloth, but the supplement them with parallel systems for specific cases where they&amp;rsquo;re not well-suited.&lt;/p&gt;
&lt;p&gt;The fact that in principle they all share a common data structure gave me this feeling that I should be able to make a tool which does &amp;ldquo;double duty&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;In searching for inspiration I began thinking about mid-90s shooters, which accomplished a lot of what I want on &lt;em&gt;very&lt;/em&gt; modest low-power commodity PCs, in particular &lt;a href=&#34;https://en.wikipedia.org/wiki/Doom_(1993_video_game)&#34;&gt;DOOM (1993)&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Descent_(video_game)&#34;&gt;DESCENT (1995)&lt;/a&gt;. How did those games work? And could I exploit those legacy techniques to surpass the performance and productivity limitations of the general-purpose solutions?&lt;/p&gt;
&lt;h2 id=&#34;doom-binary-space-partitioning&#34; &gt;DOOM: Binary Space Partitioning
&lt;span&gt;
    &lt;a href=&#34;#doom-binary-space-partitioning&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/e5UD5QQH_as&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Playthrough of DOOM, developed by id Software in 1993&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Loose objects like demons and bullets are pretty sparse in the original DOOM, with wall-collision accounting for most of the runtime complexity. The DOOM level editor allows for mostly arbitrary placement of walls, but then runs a special decomposition algorithm to break it down into explicit convex-regions called &lt;em&gt;Binary Space Partitioning&lt;/em&gt; (BSP).&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/deu.png&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Screenshot of the origin DOOM level editor.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The idea is to pick a wall at random, and consider the subdivide the remaining walls into those that are in-front or behind that wall’s plane (subdiving walls that straddle the plane into two parts). For those two subsets you rinse-and repeat until you’ve exhausted all the walls, and what remains at the leaves of the subdivision are a convex partition of the scene, which can be computed and stored ahead-of-time with level-data.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/bsp_source.gif&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Diagram from Valve&amp;rsquo;s Source Engine documentation demonstrating convex-decomposition via Binary Space Partitioning.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The original DOOM simplified these BSPs by imposing the restriction that all walls are perfectly vertical, so the partitioning could be mostly 2D, though later versions of &lt;a href=&#34;https://en.wikipedia.org/wiki/Quake_(video_game)&#34;&gt;Quake&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Unreal_Tournament&#34;&gt;Unreal Tournament&lt;/a&gt; used the same technique with sloped geometry, too.&lt;/p&gt;
&lt;p&gt;While it&amp;rsquo;s interesting to study to understand the history of how broad-phase collision developed historically, I didn&amp;rsquo;t really feel like there was much for me to exploit&lt;/p&gt;
&lt;h2 id=&#34;descent-convex-by-construction&#34; &gt;DESCENT: Convex By-Construction
&lt;span&gt;
    &lt;a href=&#34;#descent-convex-by-construction&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;Another interesting case I looked at was Descent, which billed itself as the first “full 3D” shooter with complex volume-filling geometry, as opposed to being mostly-flattened onto the horizontal plane like DOOM.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/PwIre2rLHv0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Playthrough of Descent, developed by Interplay in 1995&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I better appreciated for how they implemented this by playing around with the mod community’s level editor &lt;a href=&#34;https://descent2.de/dle-frames.html&#34;&gt;DLE&lt;/a&gt;, where levels are built-up from convex “segments” that are each six-sided cuboid hexahedrons. At first glance this seems like a restrictive limitation which would give rise to rectangular chambers, but creative linking across edges and corners can actually give rise to pretty much any shape you want with enough creativity. Additionally, restricting faces to all be quads simplifies how textures and materials are applied, as opposed to having to account for weird triangular topology.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/scaffold/dle2.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Screenshot of the Descent Level Editor. It looks like a mesh editor at first glance, but you&amp;rsquo;re actually editting linked cuboid &amp;ldquo;segments.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;In 2018 the original original devs reconvened to develop a spiritual successor &lt;a href=&#34;https://en.wikipedia.org/wiki/Overload_(video_game)&#34;&gt;Overload&lt;/a&gt;, with a community &lt;a href=&#34;https://store.steampowered.com/app/884970/Overload_Level_Editor/&#34;&gt;level-editor&lt;/a&gt; and tutorial series which expands on the same ideas in thoughtful ways. What really caught my eye was how they extended the idea of “applying materials” to quad-face to assembling kit-pieces which have a modern, rather than low-poly, look. For example, rock faces have rough geometry that fill-in crevices to give those areas an organic look.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/SbznHch0esI&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;First Video in a Tutorial Series on the Overload Level Editor&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Playing with these tools was a big &lt;em&gt;aha&lt;/em&gt; moment for me – that by building-up from convex shapes there’s no need to do any kind of “decomposition” like BSPs because the space is partitioned &lt;strong&gt;by-construction&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I decided to use this as a starting-point and then go in my own direction.&lt;/p&gt;
&lt;h2 id=&#34;nightshift-galaxy-scaffold&#34; &gt;Nightshift Galaxy Scaffold
&lt;span&gt;
    &lt;a href=&#34;#nightshift-galaxy-scaffold&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;Instead of building a completely separate application like the Overload team did, I decided to build inside the Unreal Level Editor. This is a tradeoff – on the one hand it limits the ease of offering modding tools to the community, which is something on my mind. On the other hand, though, it greatly reduced how much I needed to code (since the application-framework/asset-handling/undo systems were already in place), and let me focus on my value-adds, as opposed to re-implementing all the other loose-object handling that Unreal already does perfectly-fine.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Gamedev Streaming resumes _tomorrow_ at noon, PST. Thinking of doing a demo of the &amp;quot;scaffold&amp;quot; tool I&amp;#39;ve been making for level design, and maybe a little lecture of scene geometry in 3D. &lt;a href=&#34;https://twitter.com/hashtag/indiegamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#indiegamedev&lt;/a&gt; &lt;a href=&#34;https://t.co/eaWQEAOSvv&#34;&gt;pic.twitter.com/eaWQEAOSvv&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1907921527267590149?ref_src=twsrc%5Etfw&#34;&gt;April 3, 2025&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Early Sneak-Peak of Scaffold running inside the Unreal Level Editor.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I won’t go into all the boring details, suffice to say for Unreal-heads out there I started with the &lt;em&gt;SplineComponentVisualizer&lt;/em&gt; as a reference and built my own tools from there. It took about a week to get a MVP version from about 2500ish lines of code. I have all the basic operations - selection modes for vertices, edges, faces, and “cubs” (my name for convex segment – short for “cubby hole”), basic extrude and bridge options, as well as “stamps” for common shapes like cubes, rings, and cylinders.&lt;/p&gt;
&lt;p&gt;For the grey “blockout” walls, I’m using the &lt;em&gt;DynamicMesh&lt;/em&gt; object from the &lt;strong&gt;GeometryScripting&lt;/strong&gt; extension (though I intend to replace them with instanced decoration kit meshes), and to create “real collision” trigger volumes for each cub I added a &lt;em&gt;CubComponent&lt;/em&gt; subtype of &lt;em&gt;PrimitiveComponent&lt;/em&gt; by referencing the implementation of &lt;em&gt;ShapeComponent&lt;/em&gt; and &lt;em&gt;BoxComponent&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I wrote a “scaffold raytracing” test which, even in it’s simple quick&amp;amp;dirty implementation, is 100x faster than doing ordinary raytraces with Unreal’s built-in collision. It’s not something I was use for player bullets or general-purpose traces, but for specialized batches, like enemy bullet-hell patterns, it opens up a lot of opportunities.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Post-stream: updated the BulletManager to not only use framerate-independent time, but do sub-frame position sampling for higher-precise patterns. &lt;a href=&#34;https://twitter.com/hashtag/ScreenshotSaturday?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#ScreenshotSaturday&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/shmup?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#shmup&lt;/a&gt; &lt;br&gt;&lt;br&gt;🎶 Venus Wars OST (Joe Hisaishi) 🎶 &lt;a href=&#34;https://t.co/otWOFaQrOE&#34;&gt;https://t.co/otWOFaQrOE&lt;/a&gt; &lt;a href=&#34;https://t.co/K1Alnu0J8v&#34;&gt;pic.twitter.com/K1Alnu0J8v&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1895887457734062506?ref_src=twsrc%5Etfw&#34;&gt;March 1, 2025&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Footage from an earlier bullet-hell experimentation stream.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;But the real &lt;em&gt;coup de grâce&lt;/em&gt; for me was that, as I expected, Scaffold data also works as a navigation system!  I generate a waypoint for each shared cub-face, and do a quick check to see if the “floor” of the cub is a wall-or-window, and quickly generate a navigation graph that works for both ground-units and aerial units with no extra labor.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Update from yesterday&amp;#39;s stream -- the Scaffold level editor is now generating nav-data for AI-pathfinding in addition to blockout wall collision. &lt;a href=&#34;https://twitter.com/hashtag/screenshotsaturday?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#screenshotsaturday&lt;/a&gt; &lt;a href=&#34;https://t.co/qaUStE3V1j&#34;&gt;pic.twitter.com/qaUStE3V1j&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1908664789943148859?ref_src=twsrc%5Etfw&#34;&gt;April 5, 2025&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;First test of Aerial Navigation working inside Scaffold.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;At this point I’m confident in the direction of the solution, so now I’m fleshing out missing features, and adding more “smart” operations so I can layout spaces faster. The big difference between Nightshift and Descent is that I want to exit Spaceships/Asteroid-Bases/Sewer-Tunnels/Caves/Industrial-Interiors and fly around the outside, too, so I’ll need to start thinking about a “exterior hull” toolset as well. But I want to make a half-dozen maps with validated gameplay, first.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Tech Breakdown: AI with Finite State Machines</title>
      <link>https://blog.littlepolygon.com/posts/fsm/</link>
      <pubDate>Mon, 18 Nov 2024 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/fsm/</guid>
      <description>&lt;p&gt;As I transition from developing systems to developing content, a larger ratio of my programming time is devoted to AI programming.  &lt;em&gt;Note: I&amp;rsquo;m discussing classical game AI &amp;ndash; how NPCs make decisions &amp;ndash; not the overhyped odious tech-fad of the same name.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most recently, I coded the logic that pilots enemy jets: how to fly, when to turn, how to avoid obstacles, when to react to the player, and how to line up strafing attacks.&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/fX2PqEYbFj0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Music by EMMIE (Chiisana Kiseki) from the Project A-KO Greyside OST&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Let&amp;rsquo;s break down the choices I made in the high-level technical design, as well as the syntax tricks I employ in low-level C++ code.&lt;/p&gt;
&lt;h2 id=&#34;algorithms-for-game-ai&#34; &gt;Algorithms for Game AI
&lt;span&gt;
    &lt;a href=&#34;#algorithms-for-game-ai&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;The most common decision-making algorithm in games is the venerable &lt;strong&gt;Finite State Machine&lt;/strong&gt;, of &amp;ldquo;FSM.&amp;rdquo; This data-structure can be easily understood by way of a &amp;ldquo;box and arrow&amp;rdquo; diagram, where each box visualizes possible &lt;em&gt;States&lt;/em&gt; that the character can be in, and the arrows represent &lt;em&gt;Transitions&lt;/em&gt; between those states. The characters begins in a single known initial-state, and &amp;ldquo;moves&amp;rdquo; from state to state as the associated transitions are triggered.  In fact, in the Unreal Engine, character animation control is coded by authoring &amp;ldquo;Animation Blueprints&amp;rdquo; &amp;ndash; state-machines with a box-and-arrow interface.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/fsm/fsm.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Sample Animation State-Machine from the Unreal Documentation&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;FSMs are intuitive, and easy to debug, but not without tradeoffs. The principle downside is complexity &amp;ndash; the number of transitions grows potentially geometrically with the number of states, and can become overwealming. In Computer Science jargon, the complexity grows &amp;ldquo;O(N²) with the number of states,&amp;rdquo; and is related to a systems principle called &lt;a href=&#34;https://en.wikipedia.org/wiki/Metcalfe&#39;s_law&#34;&gt;Metcalfe&amp;rsquo;s Law&lt;/a&gt;.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/fsm/spaghetti.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Blueprint &amp;ldquo;Spaghettification&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The conventional answer to this problem is to break the main state-machine up into a collection of &amp;ldquo;sub-state-machines&amp;rdquo; which are individually maintainable, but require extra care and QA for their concurrent interactions.&lt;/p&gt;
&lt;p&gt;The mid-aughts, however, were a bumper decade for devising alternatives to FSMs which didn&amp;rsquo;t suffer the complexity-explosion issue.  &lt;em&gt;To return to CS jaron, these generally involve creating some kind of hierarchy which limits the complexity growth to O(n × log n)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The most popular of these was the &lt;strong&gt;Behavior-Tree&lt;/strong&gt; algorithm, popularized by it&amp;rsquo;s use in &lt;a href=&#34;https://www.gamedeveloper.com/programming/gdc-2005-proceeding-handling-complexity-in-the-i-halo-2-i-ai&#34;&gt;Halo 2, from Bungie in 2005&lt;/a&gt;. In a nutshell, the AI controller is continuously &amp;ldquo;descending&amp;rdquo; a tree-diagram over-and-over from the root where each branch is a condition, and each leaf is a state. When the tree evaluates to a different state than your current state, you kickoff a transition. This maintains the intuitive appeal and easily-visualized tooling of FSMs, without the complexity risk. It&amp;rsquo;s probably the most popular solution amongst FPS developers, and naturally is also available in Unreal&amp;rsquo;s &amp;ldquo;Blackboard&amp;rdquo; system.&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/fsm/bt.png&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Sample Behavior Tree from the Unreal Documentation&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The main drawback here is that a carelessly constructed behavior-tree can tank-performance in the continuous-evaluation step, and most implementations often add several special-case optimizations for common bottlenecks.&lt;/p&gt;
&lt;p&gt;An overkill algorithm I find most personally-intriguing is the &lt;strong&gt;Goal-Oriented Action Planning&lt;/strong&gt; system developed for &lt;a href=&#34;https://www.gamedeveloper.com/design/building-the-ai-of-f-e-a-r-with-goal-oriented-action-planning&#34;&gt;F.E.A.R., by Monolith Productions&lt;/a&gt;, the same year as Halo 2. This is a sophisticated planning system which marks up all the character &amp;ldquo;actions&amp;rdquo; with precondition/postcondition metadata which, combined with an elaborately-queryable environment database, allows characters to automatically &amp;ldquo;discover&amp;rdquo; action-sequences to accomplish goals without any explicit transitions.  This enables enemies to &amp;ldquo;think on their feet&amp;rdquo; and often discover novel emergent solutions that surprise players and even designers.  It&amp;rsquo;s even-more computationally-expensive, due to the &amp;ldquo;path-finding&amp;rdquo; aspect, but still gets a lot of use in real-time-strategy games that demand the AI to complete multi-step sequences (e.g. gather-resource -&amp;gt; build-factory -&amp;gt; attack-with-unit).&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/fsm/fear.png&#34; alt=&#34;&#34;&gt;
&lt;em&gt;F.E.A.R. also had a great slo-mo mechanic.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;All that said, for &lt;em&gt;Nightshift Galaxy&lt;/em&gt; I ditched Unreal&amp;rsquo;s builtin stuff and followed the &lt;a href=&#34;https://en.wikipedia.org/wiki/KISS_principle&#34;&gt;KISS principle&lt;/a&gt; and just wrote everything in C++. It&amp;rsquo;s still a mostly vanilla FSM, but with Behavior-Tree-inspired implicit transitions.  Being more of an arcade-game, I&amp;rsquo;m not too worried about complexity-explosion, anyway.&lt;/p&gt;
&lt;h2 id=&#34;c-state-machines&#34; &gt;C++ State Machines
&lt;span&gt;
    &lt;a href=&#34;#c-state-machines&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;In general avoiding Blueprints and developing gameplay with native C++ is win-win &amp;ndash; not only is the performance (significantly) better, but I don&amp;rsquo;t have to deal with any &amp;ldquo;&lt;a href=&#34;https://www.tumblr.com/blueprintsfromhell&#34;&gt;Blueprints From Hell&lt;/a&gt;&amp;rdquo; that require hundreds of tedious mouse-clicks to write, and are impossible to read. Furthermore, I&amp;rsquo;ve been coding C++ for decades, and have a high comfort-level with its quirks and warts.  I limit Blueprints just to places where it&amp;rsquo;s required (like animation state-machines) or purely-decorative (like kicking-off VFX and SFX).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve seen dozens of ways to represent FSMs in code over the years, often associating an object-per-state with several virtual-functions, but for Nightshift  I&amp;rsquo;m using a simplified variant of an idea first-proposed by &lt;a href=&#34;https://www.mobygames.com/person/309351/charlie-tangora/&#34;&gt;Charlie Tangora&lt;/a&gt; during my time at Yacht Club Games: one big switch!  (&lt;em&gt;Update from Charlie: &lt;a href=&#34;https://www.mobygames.com/person/319032/adam-lederer/&#34;&gt;Adam Lederer&lt;/a&gt; conceived it first&lt;/em&gt;). We declare all the states with an unnamed enum, and then shift-and-mask them together with an event-id.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* list of states (declared in source, no header) */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	STATE_UNDEFINED &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	STATE_STANDING_STILL,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	STATE_MOVING,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* list of state-events */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EVENT_UNDEFINED &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EVENT_STATE_ENTER,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EVENT_STATE_UPDATE,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EVENT_STATE_EXIT,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* event structure (with per-event-type arguments) */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSMEvent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	uint32 ID;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;union&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; { uint32 PrevID; } EnterArgs;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; DeltaTime; } UpdateArgs;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; { uint32 NextID; } ExitArgs;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	uint64 &lt;span style=&#34;color:#a6e22e&#34;&gt;MakeSwitch&lt;/span&gt;( uint32 StateID ) &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; uint64(StateID) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; (uint64(ID) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* shorthand macro */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define FSM_ID( SID, EID ) ( uint64(STATE_##SID) | (uint64(EVENT_STATE_##EID) &amp;lt;&amp;lt; 32) )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uint32 SomeEnemy&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HandleEvent( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FSMEvent&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Event ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt;( Event.MakeSwitch(State) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( STANDING_STILL, ENTER )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* code for entering the standing-still state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( STANDING_STILL, UPDATE )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* code for updating the standing-still state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( STANDING_STILL, EXIT )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* code for leaving the standing-still state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( MOVING, ENTER )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* code for entering the moving state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( MOVING, UPDATE )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* code for updating the moving state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( MOVING, EXIT )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* code for leaving the moving state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HandleEvent( Event );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Dispatching through a single virtual-function, rather than several per-event, was inspired by a &lt;a href=&#34;https://github.com/unclebob/cmuratori-discussion/blob/main/cleancodeqa-2.md&#34;&gt;Casey Muratori discussion with &amp;ldquo;Uncle Bob&amp;rdquo;&lt;/a&gt; on alternatives to &amp;ldquo;&lt;a href=&#34;https://en.wikipedia.org/wiki/SOLID&#34;&gt;SOLID&lt;/a&gt;&amp;rdquo; design-patterns.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;For me, there&amp;rsquo;s a lot to recommend this technique. The code is very regular and readable, and it&amp;rsquo;s easy to add, remove, or refactor states &lt;em&gt;without touching any headers&lt;/em&gt;, which means I can still hot-recompile FSM changes &lt;em&gt;while the game is running&lt;/em&gt; (even better than blueprints!). I can also omit empty-cases, instead of having lots of empty boilerplate handler-functions.  For edge-cases that don&amp;rsquo;t naturally fit in the conventional layout, I can always add if-branches before or after the switch. A common example is using &amp;ldquo;ranges&amp;rdquo; of state IDs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	STATE_UNDEFINED &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	STATE_IDLE,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	RANGE_AGGRO_BEGIN,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		STATE_AGGRO_TELEGRAPH_ALARM,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		STATE_AGGRO_PURSUIT,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		STATE_AGGRO_LOOK_FOR_PLAYER,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	RANGE_AGGRO_END,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uint32 SomeEnemy&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HandleEvent( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FSMEvent&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Event ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;auto&lt;/span&gt; bUpdatingAggro &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Event.ID &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; EVENT_STATE_UPDATE &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		State &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; RANGE_AGGRO_BEGIN &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		State &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; RANGE_AGGRO_END ;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( bUpdatingAggro )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* common aggro code */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HandleEvent( Event );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For transitions, I return a non-zero state ID from &lt;code&gt;HandleEvent&lt;/code&gt;. This is preferable to having a &lt;code&gt;SetState()&lt;/code&gt; subroutine like you see sometimes, because it &lt;em&gt;ensures that I&amp;rsquo;m exitting the event handler function &lt;strong&gt;before&lt;/strong&gt; changing the state&lt;/em&gt;, so our function-stack doesn&amp;rsquo;t get &amp;ldquo;plumbing&amp;rdquo; and &amp;ldquo;logic&amp;rdquo; code interleaved.  Additionally, it lets me re-run the event-handler to take multiple transitions per-update, and ensure exactly one state &amp;ldquo;consumes&amp;rdquo; &lt;code&gt;DeltaTime&lt;/code&gt; on update, which is important to avoid movement stutters (what CS peeps call &amp;ldquo;linearity&amp;rdquo;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SetState_Internal&lt;/span&gt;( uint32 InState )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* exit current state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FFSMEvent EvExit;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EvExit.ID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ENEMY_EVENT_EXIT;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EvExit.ExitArgs.NextID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InState;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;auto&lt;/span&gt; ExitResult &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HandleEvent( EvExit );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* ignore results from exit-events */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ensure( ExitResult &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* update bookkeeping */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FFSMEvent EvEnter;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EvEnter.ID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ENEMY_EVENT_ENTER;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EvEnter.EnterArgs.PrevID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; State;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	State &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InState;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	StateTime.SetNow( World );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* enter new state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;auto&lt;/span&gt; EnterResult &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Enemy_HandleEvent( EvEnter ) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* allow enter-event to short-circuit to another state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		SetState_Internal( EnterResult ); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UpdateState&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; DeltaTime )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* repeatedly update until it&amp;#39;s consumed without transitioning */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FFSMEvent EvUpdate;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EvUpdate.ID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; EVENT_STATE_UPDATE;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	EvUpdate.UpdateArgs.DeltaTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Iter &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;/* failsafe to avoid infinite loops */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		uint32 NewState &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HandleEvent( EvUpdate );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		NewState &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; NewState &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; State &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; Iter &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		NewState &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HandleEvent( EvUpdate );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		SetState_Internal( NewState );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;Iter;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uint32 SomeEnemy&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HandleEvent( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FSMEvent&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Event ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt;( Event.MakeSwitch(State) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( IDLE, UPDATE )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( AggroComponent&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;HasDetectedPlayer() )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; STATE_AGGRO_TELEGRAPH_ALARM;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* ... other idle code ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( AGGRO_TELEGRAPH_ALARM, ENTER )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Anim&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;Play( AnimAlarm );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( AGGRO_TELEGRAPH_ALARM, UPDATE )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Anim&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;IsFinished( AnimAlarm ) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; STATE_AGGRO_PURSUIT;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSM_ID&lt;/span&gt;( AGGRO_TELEGRAPH_ALARM, EXIT )&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/* stop anim if we were interrupted for some reason */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Anim&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;Clear( AnimAlarm ); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* ... etc ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HandleEvent( Event );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This isn&amp;rsquo;t a one-size-fits-all solution, and is largely tailored to my preferences, but it works well in practice compared to derpier things I&amp;rsquo;ve done in the past. 😅&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s about it, lol. Thanks for reading!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Tech Breakdown: Fixing Unreal&#39;s Horizontal Field of View</title>
      <link>https://blog.littlepolygon.com/posts/ucamera/</link>
      <pubDate>Wed, 26 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/ucamera/</guid>
      <description>&lt;p&gt;Unreal Engine applies the aspect ratio of the viewport to a fixed &lt;em&gt;Horizontal&lt;/em&gt; Field of View.  Therefore, switching from standard 16:9 to ultrawide will &lt;em&gt;crop&lt;/em&gt; the top and bottom of the view and zoom, rather than revealing more to the left and right as you&amp;rsquo;d expect, and shrinking it to 4:3 creates an extreme fisheye effect.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/ucamera/fovx.gif&#34; alt=&#34;FOVX&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Unreal Default: content on the sides of the viewport is fixed, causing weird cropping and zooming.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/ucamera/fovy.gif&#34; alt=&#34;FOVY&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Our Camera: fixing content on the top and bottom of the viewport, like we expect.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The default is basically never what we want, but it&amp;rsquo;s hard-coded and there&amp;rsquo;s no way to override it with just configuration, so we have to patch it. 😖&lt;/p&gt;
&lt;p&gt;Following the implementation of &lt;code&gt;CineCamera&lt;/code&gt;, I opted to create a transparently-replacable subtype which does the adjustment just-in-time when the desired view is computed.&lt;/p&gt;
&lt;p&gt;To start, we define two new subtypes: one for the actual camera component, and another for the wrapper camera actor.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* A new camera component which overrides the default desired view */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UCLASS( meta&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;(BlueprintSpawnableComponent) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UMyCameraComponent&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; UCameraComponent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	GENERATED_BODY()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; GetCameraView(&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime, FMinimalViewInfo&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; DesiredView) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* a wrapper for the camera actor which overrides the camera component subtype */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UCLASS()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AMyCamera&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; ACameraActor
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	GENERATED_BODY()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	AMyCamera( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FObjectInitializer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Init );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UMyCameraComponent&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;GetMyCamera&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static_cast&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;UMyCameraComponent&lt;span style=&#34;color:#f92672&#34;&gt;*&amp;gt;&lt;/span&gt;(GetCameraComponent()); }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Aside&lt;/em&gt;: The component/actor distinction is kind of annoying, because &lt;code&gt;PlayerController&lt;/code&gt; and &lt;code&gt;CameraManager&lt;/code&gt; handle camera components wrapped in camera actors slightly differently than camera components embedded in other kinds of actors. For &lt;code&gt;CineCamera&lt;/code&gt;, it&amp;rsquo;s doubly-annoying because functionality is split between the two classes, so you lose features like &lt;em&gt;Look at Tracking&lt;/em&gt; when embedding the component without the wrapper.&lt;/p&gt;
&lt;p&gt;But I digress, for the implementation we do a little math in our override of &lt;code&gt;GetCameraView&lt;/code&gt; and sprinkle a magic dependency-injection incantation in the wrapper&amp;rsquo;s constructor to instantiate the correct subcomponent (incidentally, this is also how you can change the default &lt;code&gt;CharacterMovementComponent&lt;/code&gt; in subtypes of &lt;code&gt;Character&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;namespace&lt;/span&gt; CameraUtil
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FName &lt;span style=&#34;color:#a6e22e&#34;&gt;MEMBER_CameraComponent&lt;/span&gt;( &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CameraComponent&amp;#34;&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CalcAspectRatio&lt;/span&gt;( UWorld&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; W )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( ULocalPlayer&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Player &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; W&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetFirstLocalPlayerFromController() )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FVector2D Size( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Player&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ViewportClient&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetViewportSize( Size );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Size.Y &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; KINDA_SMALL_NUMBER )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Size.X &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; Size.Y;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* fallback to default when there&amp;#39;s no viewport */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;16.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;GetFOVX&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Aspect, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; FOVY ) &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; R2D( FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Atan( FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tan( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; D2R(FOVY) ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Aspect ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;GetFOVY&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Aspect, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; FOVX ) &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; R2D( FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Atan( FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tan( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; D2R(FOVX) ) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; Aspect ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; UMyCameraComponent&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;GetCameraView( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime, FMinimalViewInfo&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; DesiredView ) &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;GetCameraView( DeltaTime, DesiredView );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* early-out when we&amp;#39;re letterboxed */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( bConstrainAspectRatio )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* for some reason, DesiredView.AspectRatio is not always right, so actually query the viewport */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; ActualAspect &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CameraUtil&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;CalcAspectRatio( GetWorld() );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DesiredFOVY &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CameraUtil&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;GetFOVY( &lt;span style=&#34;color:#ae81ff&#34;&gt;16.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9.0f&lt;/span&gt;, DesiredView.FOV );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; AdjustedFOVX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CameraUtil&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;GetFOVX( ActualAspect, DesiredFOVY );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	DesiredView.FOV &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AdjustedFOVX;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AMyCamera&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;AMyCamera( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FObjectInitializer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Init )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; Super( Init.SetDefaultSubobjectClass&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;UMyCameraComponent&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;( CameraUtil&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;MEMBER_CameraComponent ) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nothing much to write home about in this implementation, but I&amp;rsquo;ll call out a few details:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/ucamera/tan.png&#34; alt=&#34;FOV Tangent Diagram&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Aspect Ratio applies to the &lt;strong&gt;Tangent of the Half-Angle of the FOV&lt;/strong&gt;, not the angle itself, so we have to do a coordinate-space sandwich when we multiply or divide it.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve wrapped &lt;code&gt;FMath::RadiansToDegrees&lt;/code&gt; and &lt;code&gt;FMath::DegreesToRadians&lt;/code&gt; in &lt;code&gt;R2D&lt;/code&gt; and &lt;code&gt;D2R&lt;/code&gt; macros because they&amp;rsquo;re annoyingly verbose for such common subroutines.&lt;/li&gt;
&lt;li&gt;I retrieve the actual-aspect-ratio from the local player, rather than the screen, to account for &lt;strong&gt;Split-Screen Multiplayer&lt;/strong&gt; (I&amp;rsquo;m assuming all split-screens have the same aspect, so we only need to look up Player 1).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;re &lt;em&gt;really&lt;/em&gt; in a pinch and can&amp;rsquo;t control the subtype of &lt;code&gt;CameraActor&lt;/code&gt;, then you can use a custom &lt;code&gt;CameraModifier&lt;/code&gt; to post-hook the change to &lt;code&gt;DesiredView&lt;/code&gt; with the same code. Workflow-wise this is just more annoying in the common-case, so I avoided it as the by-default solution.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Indie Game Pitching in 2024</title>
      <link>https://blog.littlepolygon.com/posts/announcement/</link>
      <pubDate>Wed, 12 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/announcement/</guid>
      <description>&lt;p&gt;I can’t believe how long it&amp;rsquo;s been since I wrote for the blog! My time has been fully-committed to an ambitious project to get funding to launch a new game studio.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spoiler alert&lt;/strong&gt;: 2024 was a bad year to try and start a studio. 😩&lt;/p&gt;
&lt;p&gt;What follows is a post-mortem – &lt;strong&gt;including receipts&lt;/strong&gt; – to pull back the curtain on this shadowy part of game development, as a service to enthusiasts and other indie developers.  I also recorded a short video essay version if you just want the headlines and not all the nitty-gritty details.&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/ThiA1-3EMfU&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Here I’ll discuss:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How game funding works&lt;/li&gt;
&lt;li&gt;Our experience seeking funding&lt;/li&gt;
&lt;li&gt;The state of the games industry&lt;/li&gt;
&lt;li&gt;The future for my game, &lt;em&gt;Nightshift Galaxy&lt;/em&gt; (&lt;strong&gt;TL;DR – Wishlist on &lt;a href=&#34;https://store.steampowered.com/app/3038700/Nightshift_Galaxy/&#34;&gt;Steam&lt;/a&gt;!&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;introduction&#34; &gt;Introduction
&lt;span&gt;
    &lt;a href=&#34;#introduction&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/max01.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;For new readers, hi! I’m Max! I’ve developed games professionally for twenty years. Last year I took the plunge and left my dayjob to pursue my own original indie game after some &lt;strong&gt;&lt;a href=&#34;https://x.com/xewlupus/status/1619876459790876673&#34;&gt;minor buzz on social media&lt;/a&gt;&lt;/strong&gt; garnered inquiries from interested publishers (with help from some modest savings and my wife&amp;rsquo;s health insurance).&lt;/p&gt;
&lt;p&gt;My ambition stirred last summer when I met up with my old college friend &amp;amp; roommate, Andy Jih, at a wedding. Andy started in games the same time I did, but transitioned to tech where he’s had success as an entrepreneur. I half-jokingly asked if he wanted to help me co-found a game studio. He laughed, but we started talking and a dream took shape: &lt;strong&gt;to found a co-op game studio&lt;/strong&gt; and form a 6-12 person team that could make games at the so-called “Triple-I”/&amp;ldquo;Prestige&amp;rdquo; indie tier, and create a pipeline for our collaborators to pursue their own ideas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To do this, we would need to pitch for investment&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/college.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Nerd alert! Andy and I watching anime as students, circa 2003.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;h2 id=&#34;how-do-you-pitch-a-game&#34; &gt;How Do You Pitch a Game?
&lt;span&gt;
    &lt;a href=&#34;#how-do-you-pitch-a-game&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/max02.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The traditional game-funding route is through publishers. These companies have capital to advance money for development costs, relationships with platform/console vendors, and marketing expertise to actually promote and sell your finished game (that last part is actually important, since after all, &lt;strong&gt;&lt;a href=&#34;http://www.chrishecker.com/No_One_Knows_About_Your_Game&#34;&gt;Nobody Knows About Your Game&lt;/a&gt;&lt;/strong&gt;). In exchange, they take a percentage of the revenue. Typically, this percentage is tilted in their favor until they’ve recouped their advance, at which point the percentage is reallocated in your favor.&lt;/p&gt;
&lt;p&gt;Submitting to publishers is easy – most of them have submission forms on their website, and accept entries from anyone. Publishers expect two things: a &lt;strong&gt;demo&lt;/strong&gt; and a &lt;strong&gt;deck&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The demo is a preliminary prototype that establishes both your game idea and technical expertise. Developing the demo occupies a lot of your attention, but the demo is not what actually lands the deal – &lt;strong&gt;the goal of the demo is just to get you through the initial triage-process&lt;/strong&gt;. It’s usually played by scouts who make recommendations, rather than by higher-up decision-makers themselves.&lt;/p&gt;
&lt;p&gt;The deck is a powerpoint presentation which communicates the most crucial information that publishers actually need to make a decision:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is the &lt;strong&gt;one-sentence elevator pitch&lt;/strong&gt; for your game to players?&lt;/li&gt;
&lt;li&gt;Who are you and your team, and what is your &lt;strong&gt;professional pedigree&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What are your game&amp;rsquo;s &lt;strong&gt;unique features&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What is the &lt;strong&gt;expected playtime&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What &lt;strong&gt;game engine&lt;/strong&gt; are you using?&lt;/li&gt;
&lt;li&gt;What is the expected &lt;strong&gt;size of the market&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What are &lt;strong&gt;comparable titles&lt;/strong&gt; and how well did they do?&lt;/li&gt;
&lt;li&gt;What is the target &lt;strong&gt;release date and platforms&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;How much &lt;strong&gt;money&lt;/strong&gt; are you asking for?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usually you pitch first to the scout, who will give initial feedback and ask pointed questions. If you make a good impression, they’ll hand the materials over to a market analyst to validate the budget and revenue projections. If they like the resulting report, you’ll be called-back to pitch your deck again to actual decision-makers. If all goes well, you’ll receive a &lt;strong&gt;term sheet&lt;/strong&gt;, which is their initial offer. Sometimes you discuss terms with them directly, and sometimes you hire an &lt;strong&gt;agent&lt;/strong&gt; to negotiate on your behalf, but the final terms are ironed out between lawyers to produce a final contract. Sign that, and the advance is deposited in your bank account. Then you make the game!&lt;/p&gt;
&lt;p&gt;If the publisher-route isn’t a good fit, there are other options that can replace or supplement a publishing deal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project Financing&lt;/strong&gt; just deals with budget advances and leaves it up to you to market and port the game. This makes more sense for mature studios that have in-house marketing teams and an existing pipeline of game projects rather than startups like us.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Equity Funding&lt;/strong&gt; invest in the studio itself, rather than a specific project, with the goal of either seeing a long revenue tail (e.g. live-service games) or the opportunity to sell their stake to a larger studio once your “brand” has value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Angel / Pre-Seed Funding&lt;/strong&gt; is a fancy way of saying “hitting up rich friends for several small good-faith investments.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We targeted the publisher route, but also talked to some equity and pre-seed investors. The first order of business was to prep our pitch.&lt;/p&gt;
&lt;h2 id=&#34;creating-the-demo&#34; &gt;Creating the Demo
&lt;span&gt;
    &lt;a href=&#34;#creating-the-demo&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/max03.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I transitioned from experimental prototyping to creating a self-contained demo in August and ultimately took &lt;strong&gt;three months&lt;/strong&gt; to get a build ready. This time was spent validating that the gameplay was ready for pick-up-and-play by participating in public playtesting through &lt;strong&gt;&lt;a href=&#34;https://glitch.city/&#34;&gt;Glitch City LA&lt;/a&gt;&lt;/strong&gt; and reacting to real play-sessions by strangers. Additionally, I extensively tested and fixed all the bugs in the code, and did basic optimizations to ensure it ran at a smooth 60fps on a Steam Deck. &lt;em&gt;You can&amp;rsquo;t babysit it once you submit it, so it just has to work.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Lastly, I integrated samples of polished artwork and music to better represent the look and feel of the final product. For those assets, we used a mixture of generic asset-store models and called up favors from friends in the industry who agreed to well-below their normal rates, for which I’m eternally grateful!! Shout-outs to &lt;strong&gt;&lt;a href=&#34;http://jackcovell.com/&#34;&gt;Jack Covell&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&#34;https://www.troupegammage.com/&#34;&gt;Troupe Gammage&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&#34;http://deltaexmachina.com/&#34;&gt;Cathryn Stark&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&#34;https://meredithalden.neocities.org/about&#34;&gt;Meredith Alden-Lewis&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&#34;https://x.com/digitalgain&#34;&gt;Cherie Heiberg&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&#34;https://www.izamatrix.com/&#34;&gt;Issa&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href=&#34;https://www.evanpalmercomics.com/&#34;&gt;Evan Palmer&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In terms of playtime, a casual playthrough of the demo lasts &lt;strong&gt;10-15 minutes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In total, we ended up spending about $5,000 on models, illustrations, sound, and music for the demo and deck&lt;/strong&gt; (&lt;em&gt;actually a huge discount!!&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/glitch_city.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Testing the demo at Glitch City’s “BYOG(ame)” night.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;To help build publicity for the project at this point, I also began regularly &lt;strong&gt;&lt;a href=&#34;https://youtu.be/_eQSx2js7O4?si=tEeA7yrWpkW5F32b&#34;&gt;dev-streaming&lt;/a&gt;&lt;/strong&gt; demo work on Twitch.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/twitch.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;My cat, The Boy, is a regular guest on our weekly dev stream.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Response to the demo was warm enough that we were invited to present with the &lt;strong&gt;&lt;a href=&#34;https://mediaindieexchange.com/&#34;&gt;Media Indie Exchange&lt;/a&gt;&lt;/strong&gt; at Los Angeles Comic Con. The reception was overwhelmingly positive, including a &lt;strong&gt;&lt;a href=&#34;https://x.com/indieexchange/status/1745530976539730397&#34;&gt;great playthrough by Alex Wilmer and Bannon Rudis&lt;/a&gt;&lt;/strong&gt;, and gave us a lot of confidence in moving forward.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/mix1.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Very positive reception from very diverse players.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/mix2.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Special thanks to Meredith and Cherie, my wi~ife, for helping mind our booth.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;h2 id=&#34;creating-the-deck&#34; &gt;Creating the Deck
&lt;span&gt;
    &lt;a href=&#34;#creating-the-deck&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/max04.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;For the deck, I focused on the first half, describing the game, and Andy worked on the second half, presenting the market analysis and budget. We reached out to our industry contacts for about a dozen test-pitches, refining the slides as we received feedback. We ultimately spent over &lt;strong&gt;two months&lt;/strong&gt; creating the final deck, which had about 30 total slides.&lt;/p&gt;
&lt;p&gt;The hardest part was actually coming up with the &lt;strong&gt;name of the game&lt;/strong&gt;, haha. We actually made a giant google doc of random ideas, and it was Cherie who suggested &lt;em&gt;Nightshift Galaxy&lt;/em&gt;, combining references to gig work, science fiction, and transforming robots.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/deck1.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Work-in-progress thumbnails from early-on developing the deck.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/deck2.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Final Presentation Quality, courtesy of Evan Palmer.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/deck3.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;A few selected slides.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;strong&gt;In the interest of full context and radical transparency, I’ll share all the headline details from our original pitch&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Target: PC, Steam Deck, and all consoles (Unreal Engine 4)&lt;/li&gt;
&lt;li&gt;Single Player + Online Co-op&lt;/li&gt;
&lt;li&gt;Development schedule: &lt;strong&gt;2.5 Years&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Team size: &lt;strong&gt;6 full-time employees, 8 part-time contractors&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Budget ask: &lt;strong&gt;$3 million&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m just going to take a step back and say that, for me, 3 million dollars is a &lt;strong&gt;staggering amount&lt;/strong&gt;. I come from Rust belt Ohio where that could be a comfortable middle-class person&amp;rsquo;s total lifetime income. The easiest way to wrap your head around costs like that is the following rule of thumb: estimate $10k/head/month (Note: his doesn’t mean everyone is making over 100k/year, because it’s including health insurance, personnel costs, overhead, contingency, etc). So if we count 2 part-time people as 1 “full time” person, you get:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10 people × 30 months × $10k = $3mm&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Andy prepared a detailed spreadsheet with itemized costs, and it came out to that same ballpark.  From our test pitches, we’d heard feedback that mid-size indie games range from $1 million to $10 million, so we were feeling pretty good that we were at the “bottom third” of that range, which felt right.&lt;/p&gt;
&lt;p&gt;We picked &lt;em&gt;comps&lt;/em&gt; (&amp;ldquo;comparables&amp;rdquo;) that reflected a market-appetite for mecha combat and arcade-style flight sims, but with an opportunity to come in with a colorful and lighthearted anime tone, rather than grimdark military-industrial-complex-mercenary stuff.&lt;/p&gt;
&lt;h2 id=&#34;pitching-at-conferences&#34; &gt;Pitching at Conferences
&lt;span&gt;
    &lt;a href=&#34;#pitching-at-conferences&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/max05.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Despite having early versions of the pitch ready by December, we heeded advice to wait until Q1 2024 to start pitching so we don’t get lost in the holiday season shuffle. Furthermore, there are two industry conferences, &lt;strong&gt;DICE&lt;/strong&gt; in Las Vegas in late February and &lt;strong&gt;GDC&lt;/strong&gt; in San Francisco in early March, which are natural times to schedule face-to-face meetings with publishers and investors. These can be scheduled using a (paid) service called &lt;strong&gt;&lt;a href=&#34;https://www.meettomatch.com/&#34;&gt;MeetToMatch&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In the meantime, I continued to promote the project with &lt;strong&gt;&lt;a href=&#34;https://x.com/xewlupus/status/1736866953535651954&#34;&gt;sizzle footage&lt;/a&gt;&lt;/strong&gt; on social media and Andy arranged &lt;strong&gt;finding a lawyer and incorporating a C-Corp&lt;/strong&gt;, &lt;em&gt;tinygalactic Inc&lt;/em&gt;, so we could hit the ground running once we entered into negotiations with a publisher or investor, as well as offer concrete equity terms to partners though a co-op policy Andy designed.&lt;/p&gt;
&lt;p&gt;Also, reckoning that we could get a term sheet before summer, Andy made the rounds soliciting &lt;strong&gt;$100k of small commitments&lt;/strong&gt; from a friends-and-family round of angel investment as a financing bridge to start hiring full-time partners in anticipation of a publisher advance.&lt;/p&gt;
&lt;p&gt;Lastly, feeling confident about our demo’s reception at the MIX, we applied to &lt;strong&gt;&lt;a href=&#34;https://www.dayofthedevs.com/&#34;&gt;Day of the Devs&lt;/a&gt;&lt;/strong&gt;, a prestigious indie-game show during GDC founded by Double Fine, and were greatly surprised to be accepted, despite being so early in pre-production. Consequently, we needed to hurriedly create a &lt;strong&gt;&lt;a href=&#34;https://youtu.be/wNXfldahdjM&#34;&gt;Teaser Trailer&lt;/a&gt;&lt;/strong&gt; for their stage reel. I mostly captured in-game footage myself, and then we reached out to a friend-of-a-friend to edit it together.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/dotd2.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Sleep deprivation mania.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/dotd.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;🔥 &lt;em&gt;Demoing at Day of the Devs.&lt;/em&gt; 🔥&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;All told, the cost of legal fees, software licenses, demo equipment, conference registrations, travel, and video post-production came out to about another &lt;strong&gt;$5,000&lt;/strong&gt; for a total out-of-pocket at this point of about &lt;strong&gt;$10,000&lt;/strong&gt; (&lt;em&gt;not counting the opportunity cost of, you know, not having jobs&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Between Zoom calls that we set up before the conferences, the conferences themselves, and several weeks of followups &lt;strong&gt;we gave the pitch about two dozen times&lt;/strong&gt;, we met with several other indies pitching at about the same range who were all generous enough to share their experiences.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/conf2.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Having a playable demo at our fingertips was big for guerilla encounters.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;We sent off a few early online submissions before we took off for DICE, where we started to get our first early-warning feedback: the industry was in a downturn and places that used to regularly sign $5mm deals weren’t considering anything north of $1.5 million, and many places were capping out at $500k.&lt;/p&gt;
&lt;p&gt;Andy and I had several emergency meetings and determined that by dropping online-multiplayer and halving the amount of content we could get by with 2/3rds the staff, and get the budget-ask down to just below &lt;strong&gt;$2 million&lt;/strong&gt;, so we revised our deck to reflect that before sending out our second round of submissions and taking off for GDC. We had a hard time going lower than that without just fundamentally changing the game for which we&amp;rsquo;d already built a demo.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/conf.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;The &amp;ldquo;excitement&amp;rdquo; of publisher speed dating.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The highly positive responses we received in meetings and demos encouraged us greatly, including &lt;strong&gt;&lt;a href=&#34;https://youtu.be/FKqN8aWr49A?si=VBgEi6E1riCi-oYR&#34;&gt;a wonderful interview and playthrough&lt;/a&gt;&lt;/strong&gt; with the &lt;strong&gt;Kinda Funny&lt;/strong&gt; podcast crew, so we came home very optimistic.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/kinda.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Big thanks to Andy Cortez and Blessing Adeoye for a great interview!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;h2 id=&#34;survive-to-2025&#34; &gt;&amp;ldquo;Survive to 2025&amp;rdquo;
&lt;span&gt;
    &lt;a href=&#34;#survive-to-2025&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/max06.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;All told, &lt;strong&gt;we submitted our pitch to over 50 publishers&lt;/strong&gt;. 15 of them haven’t responded, which we’ve heard is quite common. Of the 35 who have responded, there were a few who said that our game didn’t quite fit their wheelhouse (they’re focused on edgier games, for example). The majority of publishers, though, gave very positive feedback on the game itself, including excited personal notes and even descriptions of detailed playthroughs with design ideas, but uniformly took a poor financial outlook for comps. The market is too overcrowded (almost 15K games were released on Steam in 2023, alone) to justify writing checks over $500K, and could we kindly come back with a dramatically lower budget?&lt;/p&gt;
&lt;p&gt;Calling around to other devs, we’ve learned that the story is the same for everyone right now. &lt;strong&gt;Nobody is investing in mid-range indie games&lt;/strong&gt;, even people with more mature pitches than ours. Just really small and really big games. This point was articulated poignantly in &lt;strong&gt;&lt;a href=&#34;https://youtu.be/lRVsrVZrfyg?si=hnie510p3aLIhCCQ&#34;&gt;this noclip discussion&lt;/a&gt;&lt;/strong&gt; on their own struggles to find funding. Coupled with rampant downsizing in the industry and console vendors not signing any exclusives, &lt;strong&gt;things are dire for game developers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;However, with no clear term sheet on the horizon and because Andy had reached the end of his financial runway and needed to find a job, this proved fatal for the dream of &lt;em&gt;tinygalactic Inc&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As a consequence, and with a heavy heart, I no longer felt comfortable pitching a multimillion-dollar game studio on my own, so we let our prospective partners know they should accept their other job opportunities and politely let our angel investors know that we were no longer seeking funding.&lt;/p&gt;
&lt;h2 id=&#34;future-for-nightshift-galaxy&#34; &gt;Future for &lt;em&gt;Nightshift Galaxy&lt;/em&gt;
&lt;span&gt;
    &lt;a href=&#34;#future-for-nightshift-galaxy&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/max07.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;While we’re not founding a new game studio, &lt;strong&gt;I’m going to continue working on it as a solo developer&lt;/strong&gt;.  I know it&amp;rsquo;s a great game that excites me, and I feel responsible to the followers I&amp;rsquo;ve been juicing up with sizzle footage for months.&lt;/p&gt;
&lt;p&gt;To demonstrate my commitment, &lt;strong&gt;I have finally launched the &lt;a href=&#34;https://store.steampowered.com/app/3038700/Nightshift_Galaxy/&#34;&gt;Steam page&lt;/a&gt;&lt;/strong&gt; so you can add it to your wishlist. My next milestone is to develop the build enough to enter &lt;strong&gt;Early Access&lt;/strong&gt; so people can begin to download and play the demo themselves.&lt;/p&gt;
&lt;p&gt;I do need to scope the game down a lot to be doable without a whole team, however, so I’m in the process of redesigning it to focus specifically on action and gameplay.  While I&amp;rsquo;ll still post sizzle footage from time-to-time, I hope to primary &lt;strong&gt;resume regular log-form blogging here&lt;/strong&gt; as I work through it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/action.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Let&amp;rsquo;s goooo!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The possibility is still open with a few publishers where a deal can be made for a small polish-for-release budget, and funding opportunities might be better next year, but in the meantime, if you’d like to help me pay my bills, I’ve &lt;strong&gt;opened up a &lt;a href=&#34;https://ko-fi.com/littlepolygon&#34;&gt;Ko-Fi tip jar&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/announcement/boy.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;The Boy remains on the team!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;strong&gt;Thank you for reading&lt;/strong&gt;, and I look forward to having more to share here, soon.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>👷 Project Update: Tiny Starfighter</title>
      <link>https://blog.littlepolygon.com/posts/starfighter/</link>
      <pubDate>Tue, 04 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/starfighter/</guid>
      <description>&lt;p&gt;Hey everyone, sorry for the posting delay, but I&amp;rsquo;ve been focussed on my game for the last two months, and now I have a demo build, which I made for &lt;a href=&#34;https://www.linkedin.com/pulse/glitch-city-la-x-gumbo-nyc-gumbo-collective/&#34;&gt;Glitch City LA x GUMBO NYC&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/p2jM_70PDio&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Player Mechanical Design by &lt;a href=&#34;https://twitter.com/evantickles/&#34;&gt;Evan Palmer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I&amp;rsquo;m still working on Part 3 of the &lt;a href=&#34;https://blog.littlepolygon.com/posts/loco1&#34;&gt;Locomotion&lt;/a&gt; &lt;a href=&#34;https://blog.littlepolygon.com/posts/loco2&#34;&gt;Series&lt;/a&gt;, and hope to resume regular posting soon, as well as reveal other exciting project updates!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Procedural Animation: Locomotion (Part 2)</title>
      <link>https://blog.littlepolygon.com/posts/loco2/</link>
      <pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/loco2/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://blog.littlepolygon.com/posts/loco1&#34;&gt;Part 1&lt;/a&gt; we took care of the preliminaries, setting up our assets, and getting a base layer root and hip motion going on which to start grooving.  In this part we&amp;rsquo;ll take the next &lt;em&gt;step&lt;/em&gt; and start stepping.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/loco2/mb.jpg&#34; alt=&#34;Muybridge Photos&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;One of the famous &lt;a href=&#34;https://en.wikipedia.org/wiki/Eadweard_Muybridge&#34;&gt;Eadweard Muybridge&lt;/a&gt; photos.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;First things first, to simplify things, let&amp;rsquo;s refactor our code for the legs into &lt;em&gt;helper structs&lt;/em&gt; so that we don&amp;rsquo;t have to copy-paste each line for both the left and right legs.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FootController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector RestPos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IsLeft&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn ) &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;Pawn.FootL; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; RefPos( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn ) &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IsLeft&lt;/span&gt;( Pawn ) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; Pawn.FootPosL : Pawn.FootPosR; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Init&lt;/span&gt;( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Update&lt;/span&gt;( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FootController FootL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FootController FootR;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// LocoPawn.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FootController&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Init( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	RestPos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RefPos( Pawn );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FootController&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Update( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FTransform&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; ToWorld &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Pawn.GetActorTransform();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RefPos( Pawn );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.TransformPosition( RestPos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos.Z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.InverseTransformPosition( Pos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rather than trying to fully puzzle-out stepping all at once, I thought I&amp;rsquo;d start with the simplest idea: we already implemented hip-swaying last week. What if we just pick up our feet in sync with the sway?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; StepHeight &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;75.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// LocoPawn.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FootController&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Update( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FTransform&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; ToWorld &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Pawn.GetActorTransform();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RefPos( Pawn );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; RadiansOffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; IsLeft( Pawn ) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; PI;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Radians &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Fmod( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Pawn.HipPhase &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; PI &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; RadiansOffset, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; PI );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Arc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Max( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;, Pawn.HipMulti &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Sin( Radians ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.TransformPosition( RestPos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos.Z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Pawn.StepHeight &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Arc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.InverseTransformPosition( Pos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;We take the &lt;code&gt;Max&lt;/code&gt; of the of the sway oscillation and 0 to ensure that the foot is flat at Z=0 during the off-cycle.&lt;/li&gt;
&lt;li&gt;We stagger the right foot by adding &lt;code&gt;PI&lt;/code&gt; (180 degrees) to its phase.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDY4ZDUwNmJlOGViMDcxMGYwNjExOTZlNTE5MTMzY2M3YmQ4ZDg0NCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/kUoeB3dTtiCT9ZYEXk/giphy.gif&#34; alt=&#34;Slidey Step Footage&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;To be honest, my expectations were low, but this doesn&amp;rsquo;t look too bad.&lt;/em&gt; ⛸️&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;There&amp;rsquo;s an obvious flaw with this version: the feet are &lt;em&gt;sliding&lt;/em&gt; on the ground like ice-skates. But there&amp;rsquo;s also a subtle flaw: the feet are making contact directly-below the body, as opposed to out in front. So we need to make two changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Pin&lt;/em&gt; the foot to world-space when in contact with the ground.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Extrapolate&lt;/em&gt; the target step position ahead of the body.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; StepExtrap &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.15&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FootController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector PinnedPos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; bPinned;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// LocoPawn.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FootController&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Init( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	PinnedPos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.TransformPosition( RestPos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	bPinned &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FootController&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Update( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.TransformPosition( RestPos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; Pawn.StepExtrap &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Pawn.Velocity; &lt;span style=&#34;color:#75715e&#34;&gt;// offset step-target out-front
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	Pos.Z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// pin status changed?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; bWantPin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Radians &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; PI;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( bPinned &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; bWantPin )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( bWantPin )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			PinnedPos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Pos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			PinnedPos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.InverseTransformPosition( PinnedPos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		bPinned &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; bWantPin;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// apply current status
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( bPinned )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.InverseTransformPosition( PinnedPos );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; X &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Cos( Radians );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Pos.Z &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; Pawn.StepHeight &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Arc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Lerp( PinnedPos, ToWorld.InverseTransformPosition( Pos ), X );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I chose &lt;code&gt;Cos(Radians)&lt;/code&gt; for the horizontal stepping interpolation, because the height is &lt;code&gt;Sin(Radians)&lt;/code&gt;, so that would produce a circular arc, which I thought might look good?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/IZvufUHrVuplz3jErw/giphy.gif&#34; alt=&#34;Stepping Footage&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;STOMP! STOMP! STOMP!&lt;/em&gt; 🥾&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;So circular arcs produce a stomping gait, because the end of the motion is straight-down into the ground. But when we&amp;rsquo;re walking normally, we&amp;rsquo;re usually straighening out our knee on the descent, causing our feet to glide into contact with the ground, like an airplane landing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;steparc.png&#34; alt=&#34;Step Arc Illustration&#34;&gt;&lt;/p&gt;
&lt;p&gt;To translate this to code, I noodled around with the &lt;a href=&#34;https://www.desmos.com/&#34;&gt;Desmos Graphing Calculator&lt;/a&gt; until I found a &lt;code&gt;StepArc&lt;/code&gt; graph which matches our animation onion-skin, on the normalized [0-1] range.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph.png&#34; alt=&#34;Graph&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I can&amp;rsquo;t quite explain the art of designing graphs; I&amp;rsquo;ve just been praticing since I first played &lt;a href=&#34;https://youtu.be/qQkRe_SUL1M&#34;&gt;Green Globs&lt;/a&gt; in middle school. In this case I was combining an up-down arc with an ease-out curve.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;StepArc&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; X )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// simplified version of the graph we designed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; _X &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; X );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9.481481481f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; _X &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; _X &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; _X &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; X;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FootController&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Update( ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pawn, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; X &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Min( &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0f&lt;/span&gt;, Radians &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; PI ); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Pos.Z &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; Pawn.StepHeight &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Pawn.HipMulti &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; StepArc( X );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Lerp( PinnedPos, ToWorld.InverseTransformPosition( Pos ), X );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMzJlNThlNTBiY2I5YzZkOTJlOWEzMTVhYzM4YjcwMGRmNjM5NzJiZCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/3Vxmn5PqQMCz6ljHB1/giphy.gif&#34; alt=&#34;Stepping Footage&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Smooth as silk!&lt;/em&gt; 🦵&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;We could of course interpolate the two step arcs with a control parameter called &lt;em&gt;stompiness&lt;/em&gt; if our game design calls for it.&lt;/p&gt;
&lt;p&gt;This looks great, so that&amp;rsquo;s a good place to leave it for now, but one last piece of foreshadowing: to get results looking this nice, I did have to dial-down the movement speed by more than half. The root position was cruising at sprinting speeds before, and the step-frequency required to keep up resembled a Benny Hill sketch.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNGQ1OTVkNDQ4YmUxOTllMDEyMjBjNWI2NTZkZGI4OTBjYzc2OThiZCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/Rd8tjMLtbBueKD369Z/giphy.gif&#34; alt=&#34;BENNY HILL INTENSIFIES&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://youtu.be/MK6TXMsvgQg&#34;&gt;Cue the orchestra&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The reason is because our whole mental model up until this point has describe how people &lt;em&gt;walk&lt;/em&gt;, but not how they &lt;em&gt;run&lt;/em&gt;. In Part 3 we&amp;rsquo;ll revise our mental model and introduce changes at each layer to enable sprinting. Thank you for reading! 💖&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Full Sample Code for Part 2 &lt;a href=&#34;https://gist.github.com/maxattack/3d69ba29e2f4461d4c1974ea64b444b0&#34;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Procedural Animation: Locomotion (Part 1)</title>
      <link>https://blog.littlepolygon.com/posts/loco1/</link>
      <pubDate>Sat, 29 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/loco1/</guid>
      <description>&lt;p&gt;This is the first article in a series on implementing procedural locomotion. I&amp;rsquo;m going to walk through step-by-step generating walkcycles for a pair of &lt;a href=&#34;https://youtu.be/A16YuzuKN58&#34;&gt;robo-pants&lt;/a&gt;.  Part 1 will mostly be setup and preliminaries, but we&amp;rsquo;ll at least go far enough for the gestures to start exihibiting personality.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/loco1/pants.gif&#34; alt=&#34;Pants!&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://blog.littlepolygon.com/posts/loco1/pants.fbx&#34;&gt;Download the model&lt;/a&gt;, if you&amp;rsquo;d like to follow along.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Character locomotion in games is typically two-step process: first we simulate the position of the character component in the world (sometimes called the &lt;em&gt;root&lt;/em&gt; or &lt;em&gt;capsule&lt;/em&gt; position), and then we blend and advance animations to match that movement with authored-clips in the character&amp;rsquo;s local &lt;em&gt;component-space&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For polished 3D games, this is a labor-intensive process, because animators must provide complete animation sets (Idle, Walk, Jog, Run, Jump, etc), as well as various layered, additive, and interstitial animations to fill in corner-cases. Subsequently, indie devs on a budget are always seeking ways to automate these animations.&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/LNidsMesxSE&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;David Rosen&amp;rsquo;s 2014 GDC talk on animations in &lt;a href=&#34;https://overgrowth.wolfire.com/&#34;&gt;Overgrowth&lt;/a&gt; is the best presentation on this subject.  He describes a method of generating animations from a small set of key-poses.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Because I don&amp;rsquo;t have an &amp;ldquo;animation team&amp;rdquo;, and I want to support a character creator where players can kitbash together new mecha in-game, I&amp;rsquo;ve been experimenting with generating animations synthetically with just code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmYwZTc5YWNmN2M3YWYxYjU3ZTEzMTI0ZTFmOTExMjZkNDhiODFhNCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/m33g7gAcNxR6YHfmSw/giphy.gif&#34; alt=&#34;LocoTest&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Kitbashed-character animation tests.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I&amp;rsquo;m taking direction from Richard Williams&amp;rsquo; indispensible textbook, &lt;a href=&#34;https://www.amazon.com/Animators-Survival-Kit-Richard-Williams/dp/0571202284&#34;&gt;The Animator&amp;rsquo;s Survival Kit&lt;/a&gt;, which I highly recommend reading if you&amp;rsquo;re interested in animation at all.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;ask.jpg&#34; alt=&#34;Animator&amp;rsquo;s Survival Kit Cover&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Every time I re-read it I learn something new.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Let&amp;rsquo;s get started with a simple test Pawn in Unreal which just moves around on a flat plane (let&amp;rsquo;s not worry about collision or anything). Here&amp;rsquo;s our starting-code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#pragma once
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;#34;GameFramework/Pawn.h&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;#34;LocoPawn.generated.h&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UCLASS( Abstract )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ALocoPawn&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; APawn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	GENERATED_BODY()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* Components */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UPROPERTY( VisibleAnywhere, BlueprintReadOnly )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;USkeletalMeshComponent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Skel;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* Movement Configuration */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; MovementSpeed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; AccelSeconds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DecelSeconds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.25f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; RotationSeconds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; CameraSpeed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;90.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ALocoPawn();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* APawn Interface */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SetupPlayerInputComponent&lt;/span&gt;( UInputComponent&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Comp ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BeginPlay&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tick&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector Position, Velocity &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; ), Accel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; TargetYaw, Yaw, YawSpeed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// LocoPawn.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;#34;LocoPawn.h&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;#34;Components/SkeletalMeshComponent.h&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FName &lt;span style=&#34;color:#a6e22e&#34;&gt;NAME_MoveX&lt;/span&gt;( &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MoveX&amp;#34;&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FName &lt;span style=&#34;color:#a6e22e&#34;&gt;NAME_MoveY&lt;/span&gt;( &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MoveY&amp;#34;&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FName &lt;span style=&#34;color:#a6e22e&#34;&gt;NAME_CamX&lt;/span&gt;( &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CamX&amp;#34;&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DampSpring&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Value, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Speed, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Target, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Duration, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Omega &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; Duration;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Exp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Exp( &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;Omega &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Change &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Value &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Target;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Temp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( Speed &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Change &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Omega ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( Speed &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Temp &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Omega ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Exp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Target &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; ( Change &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Temp ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Exp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;ALocoPawn()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FName &lt;span style=&#34;color:#a6e22e&#34;&gt;NAME_SkelComponent&lt;/span&gt;( &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SkelComponent&amp;#34;&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Skel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CreateDefaultSubobject&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; USkeletalMeshComponent &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;( NAME_SkelComponent );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	RootComponent &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Skel;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;SetupPlayerInputComponent( UInputComponent&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Comp ) &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;SetupPlayerInputComponent( Comp );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Comp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;BindAxis( NAME_MoveX );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Comp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;BindAxis( NAME_MoveY );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Comp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;BindAxis( NAME_CamX );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;BeginPlay() &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;BeginPlay();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* Initialize Movement */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; GetActorLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Position.Z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Velocity &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Yaw &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TargetYaw &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; GetActorRotation().Yaw;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	SetActorLocationAndRotation( Position, FRotator( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, Yaw, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tick( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime ) &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tick( DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* Update Camera Rotation */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	AddControllerYawInput( DeltaTime &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; CameraSpeed &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; GetInputAxisValue( NAME_CamX ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* Update Root Movement */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FRotator &lt;span style=&#34;color:#a6e22e&#34;&gt;ControlRotation&lt;/span&gt; ( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, GetControlRotation().Yaw, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector &lt;span style=&#34;color:#a6e22e&#34;&gt;Stick&lt;/span&gt;( GetInputAxisValue( NAME_MoveY ), GetInputAxisValue( NAME_MoveX ), &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Stick &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ControlRotation.RotateVector( Stick ).GetClampedToMaxSize( &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; StickTilt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Stick.Size();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Accel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( StickTilt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.35f&lt;/span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; AccelFriction &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; AccelSeconds;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Accel &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; AccelFriction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Velocity;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Accel &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; AccelFriction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; MovementSpeed &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Stick;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		TargetYaw &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;RadiansToDegrees( FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Atan2( Stick.Y, Stick.X ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DecelFriction &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; DecelSeconds;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Accel &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; DecelFriction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Velocity;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Velocity &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; Accel &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Position &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; Velocity &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; NormalizedSpeed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Min( &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0f&lt;/span&gt;, Velocity.Size() &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; MovementSpeed );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( NormalizedSpeed &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.05f&lt;/span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaAngle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FRotator&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;NormalizeAxis( TargetYaw &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Yaw );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		DampSpring( Yaw, YawSpeed, Yaw &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; DeltaAngle, RotationSeconds, NormalizedSpeed &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	SetActorLocationAndRotation( Position, FRotator( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, Yaw, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nothing really remarkable going on here, though I&amp;rsquo;ll highlight a few points of interest:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;DampSpring&lt;/code&gt; method is the same one I described in my article on &lt;a href=&#34;https://blog.littlepolygon.com/posts/trail&#34;&gt;Trailing Joints&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;4/T&lt;/code&gt; approximation for the coefficient of friction has always been a nice rule-of-thumb.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;NormalizedSpeed&lt;/code&gt; to scale the &lt;code&gt;DeltaTime&lt;/code&gt; argument in the rotation update is pretty clever, if I do say so myself 😎&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this in hand, we can make a child Blueprint which sets the appropriate assets.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;blueprint.jpg&#34; alt=&#34;Blueprint&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I also added a camera on a spring-arm, just so we don&amp;rsquo;t have to code that up.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;We can test at this point and confirm that we have some basic movement and camera controls, but the pants are pretty stiff since there&amp;rsquo;s not animation yet. We&amp;rsquo;ll proceed by adding secondary movement, layer by layer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNzQwM2UyYzRiNDA2OWVmMzZkNjcwNzBmMWRlNGUxOGJjYWUzMTY2ZCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/R1ykCLBTBFGIRUwK3v/giphy.gif&#34; alt=&#34;Root-Motion&#34;&gt;&lt;/p&gt;
&lt;p&gt;To start designing effects, we need a mental model of how locomotion actually works. Taking a cue from Williams, I&amp;rsquo;m thinking about it like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We start walking by leaning forward, initiating a fall.&lt;/li&gt;
&lt;li&gt;We throw one of our feet ahead to catch ourselves (the &amp;ldquo;contact&amp;rdquo; keyframe)&lt;/li&gt;
&lt;li&gt;We then push ourselves back-up, while picking up our back foot (the &amp;ldquo;passing&amp;rdquo; keyframe)&lt;/li&gt;
&lt;li&gt;Rinse &amp;amp; Repeat.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let&amp;rsquo;s start by &amp;ldquo;leaning&amp;rdquo; into our acceleration.  The cross-product of up with the acceleration produces the correct rotation axis for the lean, so I started with that. I noticed it was a bit too snappy, though, so I smoothed it out with a low-pass filter (&lt;code&gt;FDampedQuat&lt;/code&gt; is just a helper struct I wrote to apply &lt;code&gt;DampSpring&lt;/code&gt; to a rotation).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* Leaning Configuration */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; LeanMulti &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.64f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; MaxLeanAngle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;45.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; LeanSmoothingSeconds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.25f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FDampedQuat SmoothLean;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// LocoPawn.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tick( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime ) &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FQuat TargetLean &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Identity;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// ^ is a shorthand for cross-product in Unreal
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector Lean &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;UpVector &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt; Accel;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; LeanAmount &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Lean.Size();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( LeanAmount &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0f&lt;/span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FVector LeanAxis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Lean &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; LeanAmount;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; LeanAngle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; LeanMulti &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ( LeanAmount &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		LeanAngle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Min( LeanAngle, MaxLeanAngle );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		TargetLean &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat( LeanAxis, FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;DegreesToRadians( LeanAngle ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	SmoothLean.Update( TargetLean, LeanSmoothingSeconds, DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// note how we multiple on the left because the lean is in world-space
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FQuat Rotation &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; SmoothLean.Value &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FRotator( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, Yaw, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ).Quaternion();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	SetActorLocationAndRotation( Position, Rotation );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Already our character is looking more physical, and even catches itself when it stops 🤩&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExOGY4ZWQ4OTFhYjhjYjdmZDRmNDRjZmYxZjAyMDI4ZjEyM2FhZjI0ZSZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/BbFwW3yKNBcOKjQXor/giphy.gif&#34; alt=&#34;Leaning Footage&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Normally I&amp;rsquo;d be more subtle, but I&amp;rsquo;ve exagerrated it for demonstration purposes.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;At this point we need to start rotating joints, so let&amp;rsquo;s set up an &lt;a href=&#34;https://docs.unrealengine.com/5.1/en-US/animation-blueprints-in-unreal-engine/&#34;&gt;Animation Blueprint&lt;/a&gt;. For this tutorial, I&amp;rsquo;m just using out-of-the-box AnimNodes that are built into Unreal. If you want to know more about Two Bone IK, you can read my &lt;a href=&#34;https://blog.littlepolygon.com/posts/twobone&#34;&gt;breakdown&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;abp.jpg&#34; alt=&#34;Animation Blueprint&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;My convention of stacking blueprint boxes isn&amp;rsquo;t idiomatic, but I prefer it because having statements top-to-bottom and expressions left-to-right matches how code looks in text.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This blueprint has four public parameters (seen on the bottom-elft in the image):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LeftFootPos&lt;/code&gt; - the position of the left foot (in component-space)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RightFootPos&lt;/code&gt; - the position of the right foot (in component-space)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HipOffset&lt;/code&gt; - an adjustment to add to the hip location&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HipRotation&lt;/code&gt; - an adjustment to add to the hip rotation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are applied in the pipeline on the right: first we start with the mesh-space ref pose. If you have &amp;ldquo;key-poses&amp;rdquo;, like in Overgrowth, you could hook those in here. I did that on Solar Ash to give &lt;a href=&#34;https://youtu.be/mkpr2DeJR3M?t=13302&#34;&gt;procedurally-animated level-scale characters&lt;/a&gt; &amp;ldquo;stances&amp;rdquo; before the IK solvers adjusted them to conform to the environment.&lt;/p&gt;
&lt;p&gt;Next we adjust the hips with a Modify-Bone node (I&amp;rsquo;ve hidden pins we don&amp;rsquo;t need). Lastly, we use the built-in &lt;a href=&#34;https://en.wikipedia.org/wiki/Inverse_kinematics&#34;&gt;IK&lt;/a&gt; nodes rotate the legs joints so the feet end up in the right place.&lt;/p&gt;
&lt;p&gt;You can test these parameters but adjusting them in the &lt;em&gt;Anim Preview Editor&lt;/em&gt; panel, and observing the character pose in the scene preview panel (wiggling &lt;code&gt;HipOffset.Z&lt;/code&gt; makes a little dance, which I can never resist trying).&lt;/p&gt;
&lt;p&gt;To drive the animation from C++, we have to add some output parameters, and then &amp;ldquo;hook them together&amp;rdquo; in the Blueprint&amp;rsquo;s Update.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* Outputs to Animation Instance */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( BlueprintReadOnly )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FVector FootPosL &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( BlueprintReadOnly )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FVector FootPosR &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( BlueprintReadOnly )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FVector HipOffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( BlueprintReadOnly )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FRotator HipRotation &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FRotator( &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;abp2.jpg&#34; alt=&#34;Screenshot of Animation Blueprint Script&#34;&gt;&lt;/p&gt;
&lt;p&gt;This &lt;em&gt;seems&lt;/em&gt; like boilerplate. Why do we have to declare everything twice?&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;technical&lt;/em&gt; reason is that animations in Unreal evaluate on &lt;em&gt;background threads&lt;/em&gt; in parallel and can&amp;rsquo;t access unsynchronized main-thread data, so we give them their own copy. It also lets multiple unrelated Actors share the same animation setup.&lt;/p&gt;
&lt;p&gt;But the real virtue of this system becomes clear when you work in a big team: the Gameplay Programmers who maintain the Pawn and the Technical Animators who maintain the Animation Blueprint can keep their work separate, but use this update function as a &amp;ldquo;handshake&amp;rdquo; where you link up what the game provides with what the animation needs.&lt;/p&gt;
&lt;p&gt;Anyway, let&amp;rsquo;s test this by adjusting the feet so they don&amp;rsquo;t leave the ground when the character leans (In a real game you&amp;rsquo;d do raycasts for this).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FVector FootRestPosL, FootRestPosR;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// LocoPawn.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;BeginPlay() &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* Initialize feet */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FootRestPosL &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FootPosL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FootRestPosR &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FootPosR;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tick( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime ) &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// reset feet to rest-position
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FootPosL &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FootRestPosL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FootPosR &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FootRestPosR;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// convert feet to world-space
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FTransform &lt;span style=&#34;color:#a6e22e&#34;&gt;ToWorld&lt;/span&gt;( Rotation, Position );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FootPosL &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.TransformPosition( FootPosL );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FootPosR &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.TransformPosition( FootPosR );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// snap feet to ground
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FootPosL.Z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FootPosR.Z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// convert feet back to component-space
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FootPosL &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.InverseTransformPosition( FootPosL );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FootPosR &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.InverseTransformPosition( FootPosR );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZGZhNzIyOWIxZWRhNjg1OTZkOTI1NGRlNmQ5NjI3YmQ4YjMyNDEyYiZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/6oEl8oANNAhLyvZICY/giphy.gif&#34; alt=&#34;Footage of Foot Snapping&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Nice little knee-bends while turning.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The last thing we&amp;rsquo;ll do in this first part is to begin giving the hips some motion.  Checking back in with Williams&amp;rsquo; textbook,  we see his basic walkcycles moving the hips like a sine-wave, so let&amp;rsquo;s try that.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;sin.jpg&#34; alt=&#34;Walkcycle Illustration&#34;&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* Hip Configuration */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipPhaseSpeed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipAmplitudeDampSeconds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipOffsetZ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;20.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipBiasZ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UPROPERTY( EditDefaultsOnly, BlueprintReadWrite )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipRoll &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipPhase &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipMulti &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; HipMultiSpeed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// LocoPawn.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ALocoPawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tick( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime ) &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	DampSpring( HipMulti, HipMultiSpeed, StickTilt, HipAmplitudeDampSeconds, DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	HipPhase &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; HipPhaseSpeed &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; NormalizedSpeed &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	HipOffset.Z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HipMulti &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ( HipBiasZ &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; HipOffsetZ &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Sin( HipPhase &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; PI ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	HipRotation.Roll &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HipMulti &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; HipRoll &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Sin( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; HipPhase &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; PI );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I modulated the hip movement based on how much the stick is tilted (passed through a low-pass filter, first, like the lean). Then advanced phase of the sin wave based on the &lt;code&gt;NormalizedSpeed&lt;/code&gt; so it&amp;rsquo;s higher-frequency when we&amp;rsquo;re fast, and lower-frequency when we&amp;rsquo;re slow. Lastly, I swayed the hips with a roll at half the frequency, so there&amp;rsquo;s a left-right-left-right cadence as it oscillates.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNzhiMzg4ZTAyZDExNDYyN2QyYzZlNTJmZGZjODQ5YTgyOWU2OTYxYSZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/16CqA1JNMKpcV6qX9V/giphy.gif&#34; alt=&#34;Footage of Hips Bobbing&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Now we&amp;rsquo;re starting to get some personality!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;That&amp;rsquo;s a good place to stop for today.  You can find a full code listing for Part 1 &lt;a href=&#34;https://gist.github.com/maxattack/8369f4ec5f29729d2974aab0afed49ec&#34;&gt;here&lt;/a&gt;. Next time we&amp;rsquo;ll talk about taking steps.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Procedural Animation: Trailing Joints</title>
      <link>https://blog.littlepolygon.com/posts/trail/</link>
      <pubDate>Thu, 16 Mar 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/trail/</guid>
      <description>&lt;p&gt;This is my second article on procedural animation.  This time I&amp;rsquo;ll break down a simple &amp;ldquo;trailing&amp;rdquo; effect, for joints which &amp;ldquo;hang loose&amp;rdquo; and trail behind the primary movement. This is a subtle effect which can create a physical feel without too much actual physics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmFkODk5NjhhYTcxNWFiMGY1MDE5ZjE1NTY1OGI0MjI2NzJhZDhiOSZjdD1n/KvggOlQrAx5bdPc0p2/giphy.gif&#34; alt=&#34;Wing Trails&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Wings trailing behind the character to create secondary motion.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I use this to produce layered secondary motion out of otherwise simple base animations.  Many of them, like the dash, are just single-frame poses!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWJiZTIzNTM5YmYyZTBiMzhhOTA1ZmRjZGE5NmQ1NjI5ODI4NjEzNSZjdD1n/Cp4Swabl11MyuLiL62/giphy.gif&#34; alt=&#34;Limb Trails&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Limbs also trail when dashing, to avoid looking stiff. All the motion here is coming from trailing and headlook!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;As with IK, Unreal has some &lt;a href=&#34;https://docs.unrealengine.com/4.27/en-US/API/Runtime/AnimGraphRuntime/BoneControllers/FAnimNode_Trail/&#34;&gt;okay builtins&lt;/a&gt; to do this stuff, but I like writing my own so I can fine-tune them. I&amp;rsquo;ll be using Unreal C++ snippets for my explanation, but I&amp;rsquo;ll include a Unity C# listing at the bottom of the article.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the basic outline of my solution: we configure a relative &amp;ldquo;rest offset&amp;rdquo; from a &amp;ldquo;joint&amp;rdquo; bone where a &amp;ldquo;particle&amp;rdquo; sits when the system is at rest. When the joint moves, an &amp;ldquo;error&amp;rdquo; offset is induced between the particle and the target offset, which the particle resolves with a simulation, creating the trailing motion.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig1.png&#34; alt=&#34;Figure 1&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;(1) The sytem at rest, (2) the joint offset, (3) the particle simulation.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Two-point systems like this only approximate rotational dynamics, but that&amp;rsquo;s fine! A &amp;ldquo;good enough&amp;rdquo; solution that&amp;rsquo;s simple and easy to configure is more useful than a &amp;ldquo;scientifically correct&amp;rdquo; simulation that&amp;rsquo;s full of weird, hard-to-pick coefficients. Games are theater!&lt;/p&gt;
&lt;p&gt;To add a joint animation effect in unreal you need to implement the &lt;code&gt;FAnimNode&lt;/code&gt; interface. I&amp;rsquo;m subtyping &lt;code&gt;FAnimNode_SkeletalControlBase&lt;/code&gt; which takes care of most of the boilerplate.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;USTRUCT( BlueprintInternalUseOnly )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FAnimNode_SimpleTrail&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; FAnimNode_SkeletalControlBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	GENERATED_BODY()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Configuration
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	UPROPERTY(EditAnywhere, Category &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FBoneReference JointBone;       &lt;span style=&#34;color:#75715e&#34;&gt;// which bone will act at the &amp;#34;joint&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	UPROPERTY(EditAnywhere, Category &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector LocalTarget;           &lt;span style=&#34;color:#75715e&#34;&gt;// the relative offset from the joint to the target-position
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	UPROPERTY(EditAnywhere, Category &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DampSeconds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.2f&lt;/span&gt;;      &lt;span style=&#34;color:#75715e&#34;&gt;// the time it takes for the particle to resolve the target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	UPROPERTY(EditAnywhere, Category &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; AngleLimit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;30.0f&lt;/span&gt;;      &lt;span style=&#34;color:#75715e&#34;&gt;// the maximum allowed angle, in degrees
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Simulation State
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector ParticlePos;     &lt;span style=&#34;color:#75715e&#34;&gt;// the &amp;#34;particle&amp;#34; position
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector ParticleVel;     &lt;span style=&#34;color:#75715e&#34;&gt;// the &amp;#34;particle&amp;#34; speed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;  &lt;span style=&#34;color:#75715e&#34;&gt;// how many seconds to advance the simulation next?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; bReset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;      &lt;span style=&#34;color:#75715e&#34;&gt;// should we &amp;#34;reset&amp;#34; the simulation?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// FAnimNode interface
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NeedsDynamicReset&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;InitializeBoneReferences&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FBoneContainer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; RequiredBones ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IsValidToEvaluate&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; USkeleton&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Skeleton, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FBoneContainer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; RequiredBones ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ResetDynamics&lt;/span&gt;( ETeleportType InTeleportType ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UpdateInternal&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FAnimationUpdateContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Context ) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EvaluateSkeletalControl_AnyThread&lt;/span&gt;( FComponentSpacePoseContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Output, TArray&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;FBoneTransform&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;amp;&lt;/span&gt;  OutBoneTransforms) &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adjusting &lt;code&gt;LocalTarget&lt;/code&gt; tunes how much rotation is induced by movement. A smaller value will have a more extreme response to small movements, and a large value will only respond to large movements.  Adjusting &lt;code&gt;DampSeconds&lt;/code&gt; tunes how quickly the rotation returns to the rest pose (less time = stiffer feel).&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I always prioritize picking configuration units people can understand, like distance and time, rather than &amp;ldquo;multipliers&amp;rdquo; or &amp;ldquo;fudge factors.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The important method is &lt;code&gt;EvaluateSkeletalControl_AnyThread&lt;/code&gt; which takes a pose as an input, and writes a list of bone transforms back to the animation system. We&amp;rsquo;ll breeze past the other methods quickly, they&amp;rsquo;re not interesting:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; FAnimNode_SimpleTrail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;NeedsDynamicReset() &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// register for a callback when we need to &amp;#34;restart&amp;#34; the simulation (e.g. due to aniatmion state changes)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; FAnimNode_SimpleTrail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;InitializeBoneReferences( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FBoneContainer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; RequiredBones )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// map the bone name to a bone-index on a particular skeleton
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	JointBone.Initialize( RequiredBones );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; FAnimNode_SimpleTrail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;IsValidToEvaluate( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; USkeleton&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Skeleton, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FBoneContainer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; RequiredBones )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// confirm our skeleton actually has the joint with the right name and that
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// our local target is nonzero (otherwise we can&amp;#39;t convert the offset to a rotation)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; JointBone.IsValidToEvaluate( RequiredBones ) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; LocalTarget.SizeSquared() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1e-4&lt;/span&gt;f;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; FAnimNode_SimpleTrail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;ResetDynamics( ETeleportType InTeleportType )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// set a flag for evaluate to use
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	bReset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; FAnimNode_SimpleTrail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;UpdateInternal( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FAnimationUpdateContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Context )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// save the frame time for the simulation to consume
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;UpdateInternal( Context );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	DeltaTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Context.GetDeltaTime();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Note that we&amp;rsquo;re often dealing with &lt;code&gt;SizeSquared()&lt;/code&gt;, rather than &lt;code&gt;Size()&lt;/code&gt; directly, because it&amp;rsquo;s cheaper to compute.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Before we write &lt;code&gt;EvaluateSkeletalControl_AnyThread&lt;/code&gt; lets make a helper function which does the simulation logic. It&amp;rsquo;s actually really useful for other things, too!  You really don&amp;rsquo;t need to understand the math to use it, though if you&amp;rsquo;re interested it implements the closed-form solution to the &lt;a href=&#34;https://en.wikipedia.org/wiki/Damping&#34;&gt;critically-damped spring equation&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DampSpring&lt;/span&gt;( FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Pos, FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Vel, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Target, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DampSeconds, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Omega &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; Duration;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Exp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Exp( &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;Omega &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector Change &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Value &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Target;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector Temp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( Vel &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Change &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Omega ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Vel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( Vel &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Temp &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Omega ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Exp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Target &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; ( Change &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Temp ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Exp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I originally encountered this function in &lt;a href=&#34;https://archive.org/details/game-programming-gems-4/mode/2up&#34;&gt;Game Programming Gems 4&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Now we&amp;rsquo;re ready to evaluate the joint swing. We start by extracting the current bone transform in component-space (&amp;ldquo;CS&amp;rdquo;), and converting it to world-space (&amp;ldquo;WS&amp;rdquo;), and using that to compute our particle target.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; FAnimNode_SimpleTrail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;EvaluateSkeletalControl_AnyThread( FComponentSpacePoseContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Output, TArray&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;FBoneTransform&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;amp;&lt;/span&gt;  OutBoneTransforms )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FBoneContainer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; RequiredBones &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Output.Pose.GetPose().GetBoneContainer();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FCompactPoseBoneIndex BoneIndex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; JointBone.GetCompactPoseIndex( RequiredBones );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FTransform InBoneCS &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Output.Pose.GetComponentSpaceTransform( BoneIndex );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FTransform ToWorld &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Output.AnimInstanceProxy&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetComponentTransform();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FTransform InBoneWS &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InBoneCS &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ToWorld; &lt;span style=&#34;color:#75715e&#34;&gt;// note that FTransform * order is transposed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector ParticleTarget &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InBoneWS.TransformPositionNoScale( LocalParticleTarget );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With the target in hand, we have everything we need to update the simulation (note that &lt;code&gt;1e-5f&lt;/code&gt; is just scientific-notion for &lt;code&gt;0.00001&lt;/code&gt; i.e. &amp;ldquo;a very small number&amp;rdquo;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( bReset ) &lt;span style=&#34;color:#75715e&#34;&gt;// a reset was requested -- just snap to the target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		ParticlePos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ParticleTarget;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		ParticleVel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;ZeroVector;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		bReset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;if&lt;/span&gt;( DeltaTime &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1e-5&lt;/span&gt;f ) &lt;span style=&#34;color:#75715e&#34;&gt;// advance the simulation by DeltaTime
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		DampSpring( ParticlePos, ParticleVel, ParticleTarget, DampSeconds, DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		DeltaTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With the particle and target positions we can compute the &amp;ldquo;swing&amp;rdquo; rotation from the target-offset to the particle-offset.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector TargetOffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ParticleTarget &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InBoneWS.GetLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector CurrentOffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ParticlePos &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InBoneWS.GetLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FQuat Swing &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FindBetween( TargetOffset, CurrentOffset );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In order to constrain the swing to &lt;code&gt;AngleLimit&lt;/code&gt; we have to decompose the quaternion into its Angle and Axis components. Unreal&amp;rsquo;s &lt;code&gt;ToAxisAndAngle&lt;/code&gt; method does this for us.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector SwingAxis;  &lt;span style=&#34;color:#75715e&#34;&gt;// the direction of rotation in 3D, like the axle of a wheel
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; SwingRadians; &lt;span style=&#34;color:#75715e&#34;&gt;// the &amp;#34;mathy&amp;#34; unit of rotation. pi radians = 180 degrees
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	Swing.ToAxisAndAngle( SwingAxis, SwingRadians );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we check the angle, and if it&amp;rsquo;s too big, we adjust the particle position, making sure to cancel any speed to opposes the fixup.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; LimitRadians &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;DegreesToRadians( AngleLimit );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( SwingRadians &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; LimitRadians )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FQuat &lt;span style=&#34;color:#a6e22e&#34;&gt;FixupRotation&lt;/span&gt;( SwingAxis, LimitRadians &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; SwingRadians );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FVector FixupPos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InBoneWS.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; FixupRotation &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; CurrentOff;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FVector FixupOffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FixupPos &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; ParticlePos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( ( FixupOffset &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; ParticleVel ) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; ) &lt;span style=&#34;color:#75715e&#34;&gt;// dot-product check
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;			ParticleVel &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; ParticleVel.ProjectOnTo( FixupOffset );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		ParticlePos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FixupPos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Swing &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat( SwingAxis, LimitRadians );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next I check the particle distance and pull it forward or push it back if it&amp;rsquo;s too-close or too far (this prevents it from getting &amp;ldquo;stuck&amp;rdquo; for extended periods of time when you move really fast).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector ParticleOffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ParticlePos &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InBoneWS.GetLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DistSq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ParticleOffset.SizeSquared();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( DistSq &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1e-5&lt;/span&gt;f ) &lt;span style=&#34;color:#75715e&#34;&gt;// check to avoid a divide-by-zero edge-case
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; MinDistSq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.666f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.666f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; LocalTarget.SizeSquared();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; MaxDistSq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.333f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.333f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; LocalTarget.SizeSquared();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( DistSq &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; MinDistSq )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			ParticlePos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InBoneWS.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; ParticleOffset &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Sqrt( MinDistSq &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; DistSq );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;if&lt;/span&gt;( DistSq &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; MaxDistSq )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			ParticlePos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InBoneWS.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; ParticleOffset &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Sqrt( MaxDistSq &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; DistSq );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I determined with testing that about 1/3 further-out than normal is a limit that looks good in general (animation is full of fun magic numbers like this).&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Finally, we compose the final result rotation, convert it from world-space back to component-space, and write it out:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FQuat ResultWS &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Swing &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; InBoneWS.GetRotation(); &lt;span style=&#34;color:#75715e&#34;&gt;// note that FQuat * order is _NOT_ transposed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FQuat ResultCS &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToWorld.GetRotation().Inverse() &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ResultWS;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	OutBoneTransforms.Emplace( BoneIndex, FTransform( ResultCS, InBoneCS.GetLocation(), InBoneCS.GetScale3D() ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As always, this is just a base sample from which more complex AnimNodes can be forked.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unity Code Listing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll implement &lt;code&gt;MonoBehavior&lt;/code&gt; for clarity, though you&amp;rsquo;d get better performance structuring this effect as an &lt;a href=&#34;https://docs.unity3d.com/ScriptReference/Animations.AnimationScriptPlayable.html&#34;&gt;AnimationScriptPlayable&lt;/a&gt; job.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TrailingBone&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Vector3 localTarget;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; dampSeconds = &lt;span style=&#34;color:#ae81ff&#34;&gt;0.25f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; angleLimit = &lt;span style=&#34;color:#ae81ff&#34;&gt;30.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Vector3 particlePos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Vector3 particleVel;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	A little hack to restore the my rest-pose every update.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	so there isn&amp;#39;t a feedback-loop.  If you&amp;#39;re downstream from an animation 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	that&amp;#39;s over-writing the transform, you can skip this part.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Quaternion jointRestPose;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Start()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		particlePos = transform.TransformPoint( localTarget );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		jointRestPose = transform.localRotation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		transform.localRotation = jointRestPose;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// simulation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; jointPos = transform.position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; particleTarget = transform.TransformPoint( localTarget );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		particlePos = Mathf.SmoothDamp( particlePos, particleTarget, &lt;span style=&#34;color:#66d9ef&#34;&gt;ref&lt;/span&gt; particleVel, dampSeconds );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// angle limit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; targetOffset = particleTarget - jointPos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; currentOffset = particlePos - jointPos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; swing = Quaternion.FromToRotation( targetOffset, currentOffset );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		swing.ToAngleAxis( &lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; swingDegrees, &lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; swingAxis );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( swingDegrees &amp;gt; angleLimit )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fixupRotation = Quaternion.AngleAxis( swingAxis, angleLimit - swingDegrees );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fixupPos = jointPos + fixupRotation * currentOffset;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fixupOffset = fixupPos - particlePos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Vector3.Dot( fixupOffset, particleVel ) &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0f&lt;/span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				particleVel -= Vector3.Project( particleVel fixupOffset.normalized );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			particlePos = fixupPos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			swing = QUaternion.AngleAxis( angleLimit, swingAxis );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// distance limit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; particleOffset = particlePos - jointPos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; distSq = particleOffset.sqrMagnitude;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( distSq &amp;gt; Mathf.Epsilon )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; minDistSq = &lt;span style=&#34;color:#ae81ff&#34;&gt;0.666f&lt;/span&gt; * &lt;span style=&#34;color:#ae81ff&#34;&gt;0.666f&lt;/span&gt; * localTarget.sqrMagnitude;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; maxDistSq = &lt;span style=&#34;color:#ae81ff&#34;&gt;1.333f&lt;/span&gt; * &lt;span style=&#34;color:#ae81ff&#34;&gt;1.333f&lt;/span&gt; * localTarget.sqrMagnitude;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( distSq &amp;lt; minDistSq )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				ParticlePos = jointPos + particleOffset * Mathf.Sqrt( minDistSq / distSq );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( distSq &amp;gt; maxDistSq )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				ParticlePos = jointPos + particleOffset * Mathf.Sqrt( maxDistSq / distSq );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		transform.rotation = swing * transform.rotation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks for reading! ❤️&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Procedural Animation: Inverse Kinematics</title>
      <link>https://blog.littlepolygon.com/posts/twobone/</link>
      <pubDate>Sun, 26 Feb 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/twobone/</guid>
      <description>&lt;p&gt;Procedural animation is a priority on Tiny Starpilot. As a designer, I like how it it improves the responsiveness and &amp;ldquo;juiceness&amp;rdquo; of interactive characters. As a programmer, it&amp;rsquo;s my area of professional expertise. Finally, as a solo dev, it lets me create more &amp;ldquo;modular&amp;rdquo; assets which can be shared between characters with different skeletons.&lt;/p&gt;
&lt;p&gt;The biggest tool in the procedural-animation toolkit is called &lt;em&gt;Inverse Kinematics&lt;/em&gt;. With these algorithms, designers can specify &amp;ldquo;targets&amp;rdquo; (often called &lt;em&gt;effectors&lt;/em&gt;) for the animation system, and joints are internally rotated to satisfy those goals.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExODk5MzhmYmY0ZGU5MTI3MjkyYzhkM2NiZmIxMTAxODdiNjkxMTk5YyZjdD1n/szFv2sxUkHM1PrO7Uo/giphy.gif&#34; alt=&#34;Footage of Modular Animations&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;An example of my modular-design: anim nodes automatically discover and coordinate with very little explicit setup.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Unreal has built-in IK, &lt;a href=&#34;https://docs.unrealengine.com/4.26/en-US/AnimatingObjects/SkeletalMeshAnimation/ControlRig/&#34;&gt;Control Rig&lt;/a&gt;, but I&amp;rsquo;ve chosen to write my own solvers both so that I can produce tighter results than their generic &lt;a href=&#34;http://andreasaristidou.com/FABRIK.html&#34;&gt;FABRIK&lt;/a&gt; solver, and to reduce the amount of boilerplate blueprint noodling.&lt;/p&gt;
&lt;p&gt;A full study of IK would fill a college degree, so just to give a taste let&amp;rsquo;s break-down the simplest but most-common type of IK: two-bone solving. I use these to pose legs when generating locomotion, and to pose arms when aiming or grasping.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig1.png&#34; alt=&#34;Figure 1&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Given a &amp;ldquo;Base Pose&amp;rdquo; for a limb, compute the smallest shoulder/elbow rotation which places the point P3 onto the Target Effector.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;What makes this case simple is that, unlike many other problems in kinematics, there is an analytical solution! It&amp;rsquo;s based on the &lt;a href=&#34;https://en.wikipedia.org/wiki/Law_of_cosines&#34;&gt;Law of Cosines&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig2.png&#34; alt=&#34;Figure 2&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Given the length of your arm bones (A &amp;amp; B), and the offset from your shoulder to your hand (C), we can compute the angle you elbow makes (θ).&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Applying this in code introduces two wrinkles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do we translate from the flat plane to 3D spatial coordinates?&lt;/li&gt;
&lt;li&gt;How do we convert the angles into 3D orientations?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s start writing code and I&amp;rsquo;ll give you those answers in context.  Let&amp;rsquo;s start by declaring a helper-struct with all our inputs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FTwoBoneSolver&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FTransform Root; &lt;span style=&#34;color:#75715e&#34;&gt;// shoulder
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FTransform Mid;  &lt;span style=&#34;color:#75715e&#34;&gt;// elbow
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FTransform End;  &lt;span style=&#34;color:#75715e&#34;&gt;// hand
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Solve&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Target ); &lt;span style=&#34;color:#75715e&#34;&gt;// updates transforms in-place
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;FTransform is Unreal&amp;rsquo;s Position/Rotation/Scale structure.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;A quick note at this point &amp;ndash; all the joint transforms are in the same-coordinate space (in Animation Blueprints I use Component Space for everything).  If you have relative transforms you&amp;rsquo;ll need to compose them, like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FTwoBoneSolver Solver;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// convert relative transforms to same coordinate space
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;Solver.Root &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; LocalShoulder;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Solver.Mid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; LocalElbow &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Solver.Root;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Solver.End &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; LocalHand &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Solver.Mid;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// restore transform to relative local space
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;ShoulderTransform &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver.Root;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ElbowTransform &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver.Mid.GetRelativeTransform( Solver.Root );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HandTransform &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver.End.GetRelativeTransform( Solver.Mid );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Note that Unreal &lt;code&gt;FTransform&lt;/code&gt;s use transposed ( Child * Parent ) multiplication order (ugh)!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Now for the actual implementation, we start by saving the intial positions of the joints, and computing the bone lengths:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; FTwoBoneSolver&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Solve( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Target )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// input pose
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector InEndLoc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; End&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector InMidLoc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Mid&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector InRootLoc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Root&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; UpperLen &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Dist( InRootLoc, InMidLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; LowerLen &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Dist( InMidLoc, InEndLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; MaxLength &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UpperLen &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; LowerLen &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Max Length is the maximum offset we can take the hand from the shoulder. This can&amp;rsquo;t be longer than the arm itself (unless you allow stretching), and I actually clamp it 1cm shorter to avoid some edge-cases which creep in when all three joints snap into a straight line, and you can&amp;rsquo;t determine their plane.&lt;/p&gt;
&lt;p&gt;The next step is to recognize that the three points lie on a flat plane, and can be decomposed into two orthogonal &amp;ldquo;basis vectors&amp;rdquo; (&lt;em&gt;ToEnd&lt;/em&gt; and the &lt;em&gt;Pole Vector&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig3.png&#34; alt=&#34;Figure 3&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;All our 2D geometry will be multiplied by vectors to become 3D coordinates.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector ToEnd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( InEndLoc &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InRootLoc ).GetSafeNormal();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector InPoleVec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;VectorPlaneProject( InMidLoc &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InRootLoc, ToEnd );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	InPoleVec.Normalize();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next we compute the &lt;em&gt;Swing&lt;/em&gt; rotation from &lt;em&gt;ToEnd&lt;/em&gt; to &lt;em&gt;ToTarget&lt;/em&gt;. This rotation is a &lt;a href=&#34;https://en.wikipedia.org/wiki/Quaternion&#34;&gt;Quaternion&lt;/a&gt;, which is just a technical way of encoding an &lt;a href=&#34;https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation&#34;&gt;Angle-Axis Rotation&lt;/a&gt;.  Internally &lt;code&gt;FindBetween()&lt;/code&gt; is using the &lt;a href=&#34;https://en.wikipedia.org/wiki/Cross_product&#34;&gt;cross-product&lt;/a&gt; to compute the axis of rotation and the &lt;a href=&#34;https://en.wikipedia.org/wiki/Dot_product&#34;&gt;dot-product&lt;/a&gt; to compute the angle.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig4.png&#34; alt=&#34;Figure 4&#34;&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector TargetOffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( Target &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InRootLoc ).GetClampedToMaxSize( MaxLength );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; TargetDist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TargetOffset.Size(); &lt;span style=&#34;color:#75715e&#34;&gt;// we&amp;#39;ll want this later
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector ToTarget &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TargetOffset &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; TargetDist;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FQuat ToTargetSwing &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FindBetweenNormals( ToEnd, ToTarget );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector OutPoleVec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ToTargetSwing &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; InPoleVec;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Note that ( Quaternion * X ) is a shorthand for Quaternion.RotateVector( X ).&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Now we have all the inputs to the Law of Cosines, as well as the vectors to convert them from 2D to 3D.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A = TargetDist&lt;/li&gt;
&lt;li&gt;B = UpperLen&lt;/li&gt;
&lt;li&gt;C = LowerLen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rearrange the terms to solve for θ, the angle the Upper Arm bone will make with the ToTarget direction:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig5.png&#34; alt=&#34;Figure 5&#34;&gt;&lt;/p&gt;
&lt;p&gt;When we plug this in we calculate the denominator first, so we can check for a divide-by-zero edge case:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Denom &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; UpperLen &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; TargetDist;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; CosAngle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Denom &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; KINDA_SMALL_NUMBER )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		CosAngle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( TargetDist&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;TargetDist &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; UpperLen&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;UpperLen &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; LowerLen&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;LowerLen ) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; Denom;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Angle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Acos( CosAngle );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From here it&amp;rsquo;s just some trigonometry to determine the offsets along the ToTargetDir and OutPoleVec basis directions (recall that cosθ and sinθ are the horizontal and vertical components of a 2D unit direction vector):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; PoleDist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UpperLen &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Sin( Angle );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; TargetDist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UpperLen &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; CosAngle;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector OutEndLoc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InRootLoc &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; TargetOff;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector OutMidLoc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InRootLoc &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; TargetDist &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ToTargetDir &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; PoleDist &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; OutPoleVec;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So now we have the new end and mid positions (the shoulder doesn&amp;rsquo;t move). To convert these to rotations we use the same &lt;code&gt;FindBetween()&lt;/code&gt; functions, comparing the old and new directions. Do note that the MidSwing is downstream from the RootSwing, and therefore needs to combine both rotations.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector ToMidIn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InMidLoc &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InRootLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector ToMidOut &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; OutMidLoc &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InRootLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FQuat RootSwing &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FindBetween( ToMidIn, ToMidOut );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector InEndLoc_WithRootSwing &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InRootLoc &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; RootSwing &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ( InEndLoc &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; InRootLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector ToEndIn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InEndLoc_WithRootSwing &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; OutMidLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FVector ToEndOut &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; OutEndLoc &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; OutMidLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FQuat MidSwing &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FindBetween( ToEndIn, ToEndOut ) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; RootSwing;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Unlike &lt;code&gt;FTransform&lt;/code&gt;s, &lt;code&gt;FQuat&lt;/code&gt;s use the textbook ( Parent * Child ) multiplication order (YOLO)&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;And the last thing we do is write the results:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Root&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SetRotation( RootSwing &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Root&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetRotation() );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Mid&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SetLocation( OutMidLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Mid&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SetRotation( MidSwing &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Mid&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetRotation() );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	End&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SetLocation( OutEndLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	End&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SetRotation( MidSwing &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; End&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetRotation() );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before we wrap, I&amp;rsquo;ll show you a snippet of this code in use to clarify what I meant up top by writing custom code to get &amp;ldquo;tighter results&amp;rdquo; with less &amp;ldquo;boilerplate.&amp;rdquo;  Here&amp;rsquo;s the actual Animation Evaluation code for the arm-aiming modifier. The solver gets the arm in &lt;em&gt;basically&lt;/em&gt; the right pose, and then we apply a small &lt;code&gt;FindBetween()&lt;/code&gt; fixup rotation to swing the whole arm so the forearm direction matches your shooting direction.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmY5OWY5MjE5YTA5MTcyYWUyNDM5YTdiMzA1MzUyMGEzYzg0ODVhNSZjdD1n/GPuIXNKysBORLkX5ty/giphy.gif&#34; alt=&#34;Footage of the Ace Fighter Strafing and Shooting&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Pew, pew, pew!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; FAnimNode_ArmAim&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;EvaluateSkeletalControl_AnyThread( FComponentSpacePoseContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Output, TArray&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;FBoneTransform&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;amp;&lt;/span&gt;  OutBoneTransforms )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// initialize with the base pose from the previous anim node
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FTwoBoneSolver Solver;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.Root &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Output.Pose.GetComponentSpaceTransform( ShoulderIdx );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.Mid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Output.Pose.GetComponentSpaceTransform( ElbowIdx );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.End &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Output.Pose.GetComponentSpaceTransform( WristIdx );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// extent arm almost-full (99%) towards the target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Length &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.99f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Dist( Solver.Root.GetLocation(), Solver.Mid.GetLocation() ) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Dist( Solver.Mid.GetLocation(), Solver.End.GetLocation() )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.Solve( Solver.Root.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; AimingDir &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Length );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// align the elbow-&amp;gt;hand direction to the aiming direction
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector ForearmDir &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Solver.End.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Solver.Mid.GetLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	FQuat Fixup &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FindBetween( ForearmDir, AimingDir );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.Mid.SetLocation( Solver.Root.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Fixup &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ( Solver.Mid.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Solver.Root.GetLocation() ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.End.SetLocation( Solver.Root.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Fixup &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ( Solver.End.GetLocation() &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Solver.Root.GetLocation() ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.Root.SetRotation( Fixup &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Solver.Root.GetRotation() );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.Mid.SetRotation( Fixup &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Solver.Mid.GetRotation() );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Solver.End.SetRotation( Fixup &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Solver.End.GetRotation() );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	OutBoneTransforms.SetNumUninitialized(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	OutBoneTransforms[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FBoneTransform( ShoulderIdx, Solver.Root );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	OutBoneTransforms[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FBoneTransform( ElbowIdx, Solver.Mid );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	OutBoneTransforms[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FBoneTransform( WristIdx, Solver.End );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Fun fact: Unreal can evaluate &lt;code&gt;FAnimNode&lt;/code&gt;s on background threads in parallel, so we can actually go a bit hog-wild with the math.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt;: Here&amp;rsquo;s a complete listing of the same code for the &lt;a href=&#34;https://unity.com/&#34;&gt;Unity Game Engine&lt;/a&gt; in C#:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; UnityEngine;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JointPose&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Vector3 position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Quaternion rotation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TwoBoneIK&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; JointPose root;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; JointPose mid;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; JointPose end;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Solve( Vector3 InTargetPos ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; InEndLoc = end.position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; InMidLoc = mid.position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; InRootLoc = root.position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; UpperLen = Vector3.Distance( InRootLoc, InMidLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; LowerLen = Vector3.Distance( InMidLoc, InEndLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; MaxLength = UpperLen + LowerLen - &lt;span style=&#34;color:#ae81ff&#34;&gt;0.01f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ToEnd = ( InEndLoc - InRootLoc ).normalized;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; InPoleVec = Vector3.ProjectOnPlane( InMidLoc - InRootLoc, ToEnd ).normalized;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ToTargetOffset = ( InTargetPos - InRootLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( ToTargetOffset.sqrMagnitude &amp;gt; MaxLength * MaxLength )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			ToTargetOffset = ToTargetOffset.normalized * MaxLength;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ToTargetDist = ToTargetOffset.magnitude;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ToTarget = ToTargetOffset / ToTargetDist;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ToTargetSwing = Quaternion.FromToRotation( ToEnd, ToTarget );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; OutPoleVec = ToTargetSwing * InPoleVec;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; Denom = &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0f&lt;/span&gt; * UpperLen * ToTargetDist;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; CosAngle = &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Denom &amp;gt; Mathf.Epsilon )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			CosAngle = ( ToTargetDist*ToTargetDist + UpperLen*UpperLen - LowerLen*LowerLen ) / Denom;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; Angle = Mathf.Acos( CosAngle );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; PoleDist = UpperLen * Mathf.Sin( Angle );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; EffDist = UpperLen * CosAngle;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; OutEndLoc = InRootLoc + ToTargetOffset;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; OutMidLoc = InRootLoc + EffDist * ToTarget + PoleDist * OutPoleVec;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; InToMid = InMidLoc - InRootLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; OutToMid = OutMidLoc - InRootLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; RootSwing = Quaternion.FromToRotation( InToMid, OutToMid );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; InEndLoc_WithRootSwing = InRootLoc + RootSwing * ( InEndLoc - InRootLoc );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ToInEnd = InEndLoc_WithRootSwing - OutMidLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ToOutEnd = OutEndLoc - OutMidLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; MidSwing = Quaternion.FromToRotation( ToInEnd, ToOutEnd ) * RootSwing;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		root.rotation = RootSwing * root.rotation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		mid.position = OutMidLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		mid.rotation = MidSwing * mid.rotation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		end.position = OutEndLoc;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		end.rotation = MidSwing * end.rotation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks for reading! ❤️&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Tech Breakdown: Collision Sliding</title>
      <link>https://blog.littlepolygon.com/posts/sliding/</link>
      <pubDate>Mon, 13 Feb 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/sliding/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/sliding/rubrub.png&#34; alt=&#34;Cute Illustration&#34;&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve begun consolidating my various demos and prototypes into a single &lt;a href=&#34;https://www.unrealengine.com&#34;&gt;Unreal Engine&lt;/a&gt; project.&lt;/p&gt;
&lt;p&gt;A big feature to resolve with this port is how collision is handled.  Let&amp;rsquo;s discuss collision handling in general, and how to start implementing it in Unreal in particular. This will be a code-heavy post (C++).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;ue.jpg&#34; alt=&#34;Unreal Engine Screencap&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;The gang is looking pretty Unreal.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;In my Unity-based Character-Control demo I handled all collision using the &lt;a href=&#34;https://docs.unity3d.com/ScriptReference/Rigidbody.html&#34;&gt;Rigidbody&lt;/a&gt; component. This has the advantage of being quick &amp;amp; easy for early prototyping, because all the collision-handling and interactions with other moving-objects mostly &amp;ldquo;just works.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYjk2NzM2YTE1ZmVmOGY2NGYzMjM1ZmEwNzk3NDA2Mjc0NGM1ZWQ4MSZjdD1n/7UVSpwbj3qaFcZi129/giphy.gif&#34; alt=&#34;Physical Character Control&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;It&amp;rsquo;s not physics if you don&amp;rsquo;t have boxes bouncing around.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The drawback is that rigidbody collision responses are generic. Unless you&amp;rsquo;re making a physics-sandbox game, it&amp;rsquo;s hard to dial-in tight controls.  So for the port I want to start writing my own collision handling.&lt;/p&gt;
&lt;p&gt;The full implementation of a colliding character controller is beyond the scope for one blog post, but we can at least start with a simple foundation: &lt;em&gt;sliding&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;slide.png&#34; alt=&#34;Slide Figure&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;We sweep forward to an impact, and decompose the remainder into the &amp;ldquo;tangent&amp;rdquo; which lies along the surface.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;To implement this in Unreal, I started by declaring a helper object. This technique keeps code modular while still portable to different parts of the game (we can embed them in Actors, Components, Anim Nodes, Blueprint Library Functions, etc), as well as being easy to copy-paste between projects without bringing a lot of other cruft with them.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FSlide&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// out start position
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// the desired offset vector
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector Remainder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// the shape to slide
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FCollisionShape Shape &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FCollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;MakeSphere( &lt;span style=&#34;color:#ae81ff&#34;&gt;20.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// the rotation of the shape
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FQuat Rotation &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Identity;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// parameters used by unreal&amp;#39;s collision-query system
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FCollisionQueryParams QueryParams &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FCollisionQueryParams&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;DefaultQueryParam;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ECollisionChannel Channel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ECC_Pawn;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Advance Position/Remainder to the next impact
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TryStep&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; UWorld&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; World, FHitResult&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Hit );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Will be relevant later ;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector PrevNormal &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before diving into the details of this function, let&amp;rsquo;s write a quick little test actor. I&amp;rsquo;ve omitted a complete code-listing for brevity, but everything happens in &lt;code&gt;Tick()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*virtual*/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ATestSlidePawn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tick( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime ) &lt;span style=&#34;color:#75715e&#34;&gt;/*override*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Super&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Tick( DeltaTime );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Only update when player has control
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;Controller )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	UWorld&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; World &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; GetWorld();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// gamepad stick inputs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector2D &lt;span style=&#34;color:#a6e22e&#34;&gt;CamInput&lt;/span&gt; ( GetInputAxisValue( NAME_CamX ), GetInputAxisValue( NAME_CamY ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector2D &lt;span style=&#34;color:#a6e22e&#34;&gt;MoveInput&lt;/span&gt; ( GetInputAxisValue( NAME_MoveX), GetInputAxisValue( NAME_MoveY ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// use the right-stick to rotate the camera
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FRotator ControlEuler &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Controller&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;GetControlRotation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ControlEuler.Yaw &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; CamInput.X &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ControlEuler.Yaw &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FRotator&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;NormalizeAxis( ControlEuler.Yaw );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ControlEuler.Pitch &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; CamInput.Y &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ControlEuler.Pitch &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Clamp( ControlEuler.Pitch, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;70.0f&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;70.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Controller&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SetControlRotation( ControlEuler );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// compute a desired velocity by mapping the left stick relative to the camera
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FQuat CamRot &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ControlEuler.Quaternion();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector Vel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CamRot &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;  FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;500.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; MoveInput.Y, &lt;span style=&#34;color:#ae81ff&#34;&gt;500.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; MoveInput.X, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// perform the slide
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FSlide Slide;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Slide.Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; GetActorLocation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Slide.Remainder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Vel &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; DeltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Slide.QueryParams &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FCollisionQueryParams( NAME_Test, false, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// cap out at three impacts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FHitResult Hit;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; It &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; It &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; Slide.TryStep( World, Hit ); &lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;It )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Hit.bStartPenetrating )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;// Stuck! Draw a red warning sphere
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;			DrawDebugSphere( World, Slide.Position, Slide.Shape.GetSphereRadius() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5.0f&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;, FColor&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Red );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;// Impact! Draw the surface normal
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;			DrawDebugDirectionalArrow( World, Hit.ImpactPoint, Hit.ImpactPoint &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;65.0f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; Hit.Normal, &lt;span style=&#34;color:#ae81ff&#34;&gt;10.0f&lt;/span&gt;, FColor&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Green, false, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// update the actor and camera positions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	SetActorLocation( Slide.Position );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	CameraComp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SetWorldLocationAndRotation( Slide.Position &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; CamRot &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;300&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ), CamRot );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s a naive first-stab at the &lt;code&gt;FSlide::TryStep()&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; FSlide&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;TryStep( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; UWorld&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; World, FHitResult&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Hit )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// early out?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Remainder.IsNearlyZero( KINDA_SMALL_NUMBER ) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// sweep forward
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; bHit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; World&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SweepSingleByChannel( Hit, Position, Position &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Remainder, Rotation, Channel, Shape, QueryParams );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;bHit )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Hit.TraceEnd;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Remainder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// stuck?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Hit.bStartPenetrating )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Remainder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// update position and remainder
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Hit.Location;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Remainder &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; ( &lt;span style=&#34;color:#ae81ff&#34;&gt;1.f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Hit.Time );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Remainder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;VectorPlaneProject( Remainder, Hit.Normal );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Seems to do everything in the picture: we use Unreals&amp;rsquo; &lt;code&gt;SweepSingleByChannel&lt;/code&gt; method to cast for an impact in the scene, and then either fully or partially advance based on whether we get a hit. Let&amp;rsquo;s take it for a spin:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmQ2MTBmZjZhNmQ0Mjk0YzFhYzFiNGQ4ZDEzZTkwYzU3YTljODI2ZiZjdD1n/3rOyBy7yK6Wrtus4Rx/giphy.gif&#34; alt=&#34;First Test&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Derp.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;As soon we we touch collision, we get stuck. Wat?&lt;/p&gt;
&lt;p&gt;The problem is &lt;em&gt;precision&lt;/em&gt;. Positions in Unreal Engine 4 are represented using single-precision floating-point numbers, which on a good-day only get you about 6-8 digits (or &amp;ldquo;significant figures&amp;rdquo;).  Because of these tiny errors, if we get too-close to a surface we actually start overlapping it a &lt;em&gt;little bit&lt;/em&gt;, which causes &lt;code&gt;SweepSingleByChannel&lt;/code&gt; to report the &lt;code&gt;bStartPenetrating&lt;/code&gt; condition.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Unreal Engine 5 eases this situation somewhat by using double-precision floats, but the issue persists.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s try &amp;ldquo;pulling back&amp;rdquo; slightly from surfaces when updating position when we advance to create a gap for the error.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FVector &lt;span style=&#34;color:#a6e22e&#34;&gt;PullBackMove&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Move )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// magic number I found elsewhere in the Unreal Engine source code
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Epsilon &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.125f&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Dist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Move.Size();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Dist &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; Epsilon &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; Move &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ( ( Dist &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Epsilon ) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; Dist ) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we change how the position is updated.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// new position update
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//Position = Hit.Location;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Hit.TraceStart &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; PullBackMove( Hit.Location &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Hit.TraceStart );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This actually does a pretty good job. We&amp;rsquo;re not getting stuck on simple surfaces, but we can still get stuck rubbing against complex mesh-geo collision.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYTk3NDAxNjA2ZGVjNGE2Zjg1MjU1ZTFmZjBiYjFlMzQ5OWY5NGJmYiZjdD1n/ReioEOAECzu0RqQYvl/giphy.gif&#34; alt=&#34;Second Test&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Better.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The issue is that the pull-back doesn&amp;rsquo;t work at glancing angles. And there&amp;rsquo;s also a broader issue: sometimes you can&amp;rsquo;t avoid little overlaps, e.g. if you have moving world geometry that encroaches on your shape independently of precision. What we really need is to detect and depenetrate from initial overlaps before sweeping.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;FHitResult&lt;/code&gt; contains minimum-separating-vector information in its &lt;code&gt;Normal&lt;/code&gt; and &lt;code&gt;PenetrationDepth&lt;/code&gt; fields when it raises the &lt;code&gt;bStartPenetrating&lt;/code&gt; condition, but it&amp;rsquo;s actually kind of useless because it only reports a single result, whereas shapes can logically have multiple initial overlaps &amp;ndash; especially in narrow corners.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;depen.png&#34; alt=&#34;Depenetration Figure&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;A pathological, but not uncommon, case &amp;ndash; there are multiple initial overlaps, and their separation-vectors all oppose each other.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Unreal provides the &lt;code&gt;SweepMulti&lt;/code&gt; variant specifically to gather infomation to solve this case.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When &lt;code&gt;bStartPenetrating&lt;/code&gt; is raised, the results-array contains &lt;em&gt;all&lt;/em&gt; the initial overlaps.&lt;/li&gt;
&lt;li&gt;When the sweep succeeds it finds exactly-zero-or-one blocking impact (at the end of the results), &lt;em&gt;but also reports all the other overlaps along the path&lt;/em&gt;, which can be used to detect trigger-volumes with the same queries used for collision-responses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To use this, first we&amp;rsquo;ll want a helper function to &amp;ldquo;inflate&amp;rdquo; a shape, because when we depenetrate we want to use a shape with a thin &amp;ldquo;skin&amp;rdquo; which will leave an airgap for the resweep.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FCollisionShape &lt;span style=&#34;color:#a6e22e&#34;&gt;InflateShape&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FCollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Shape, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Amount )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// helper function to add a &amp;#34;skin&amp;#34; to a shape
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt;( Shape.ShapeType )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ECollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Capsule: 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; FCollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;MakeCapsule( Shape.GetCapsuleRadius() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Amount, Shape.GetCapsuleHalfHeight() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Amount );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ECollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Sphere:  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; FCollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;MakeSphere( Shape.GetSphereRadius() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Amount );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ECollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Box:     
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; FCollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;MakeBox( Shape.GetBox() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Amount );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Shape;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that in hand, here&amp;rsquo;s a wrapper for &lt;code&gt;SweepMultiByChannel&lt;/code&gt; which detects initial penetration, wiggles us free, and then resweeps if needed.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DepenAndSweep&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; UWorld&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; World, FHitResult&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Hit, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Start, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Delta, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FQuat&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Rot, ECollisionChannel Channel, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FCollisionShape&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Shape, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FCollisionQueryParams&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Params )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;Scratchpad&amp;#34; for result list, to avoid reallocating results for each sweep
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// (NOTE: makes this function not threadsafe!)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; TArray&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; FHitResult &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; Hits;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Hits.Reset();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// sweep with inflated skin
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FCollisionShape Inflated &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; InflateShape( Shape, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.25f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; bSkinHit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; World&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SweepMultiByChannel( Hits, Start, Start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Delta, Rot, Channel, Inflated, Params );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// no hits, even with skin?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;bSkinHit )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Hit.Init();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Hit.TraceStart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Start;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Hit.TraceEnd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Delta;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Hit.Location &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Delta;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Hit.Time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// have initial overlaps?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; NumInitialOverlaps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FHitResult&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; It : Hits )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( It.bStartPenetrating )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;NumInitialOverlaps;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( NumInitialOverlaps &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Hit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Hits.Last();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; bSkinHit;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// iteratively resolve penetration
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	FVector Fixup( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Iter &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; Iter &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;; &lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;Iter )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; ErrorSum &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FHitResult&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; It : Hits )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( It.bStartPenetrating )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// take the dot-product of the Fixup and the Normal to determine how much
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// of the penetration has already been taken care of.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Error &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FMath&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Max( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt;, ( It.PenetrationDepth &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.125f&lt;/span&gt; ) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; ( Fixup &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; It.Normal ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				ErrorSum &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; Error;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				Fixup &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; Error &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; It.Normal;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// If we&amp;#39;ve found a solution, stop iterating.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( ErrorSum &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; KINDA_SMALL_NUMBER )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// resweep from new start
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; World&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;SweepSingleByChannel( Hit, Start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Fixup, Start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Fixup &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Delta, Rot, Channel, Shape, Params );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;We use numeric iteration to resolve the fixup because there&amp;rsquo;s no cheap analytical solution. Having nested inner-loops like this might smell like bad perf, but the calculations are so cheap that a modern CPU won&amp;rsquo;t even break a sweat.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;em&gt;UPDATE: &lt;a href=&#34;https://twitter.com/ctangora&#34;&gt;@ctangora&lt;/a&gt; assures me that this resolver is actually equivalent to a &lt;a href=&#34;https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method&#34;&gt;Gauss-Seidel Method&lt;/a&gt;, and she&amp;rsquo;s &lt;em&gt;much&lt;/em&gt; smarter than me, so I trust her.&lt;/em&gt; ❤️&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMzhkN2MwNDlmMGJiODllNzRhZDhkYzcwMzY5NWJhNTZkYWIwZWIxZSZjdD1n/bkeRRJM5fz8Xh8l1rR/giphy.gif&#34; alt=&#34;Footage with Depenetration&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Success!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;It&amp;rsquo;s &lt;em&gt;technically&lt;/em&gt; still possible to get stuck, even with this method, but it&amp;rsquo;s highly unlikely and content-dependent, so those bugs can be fixed on a case-by-case basis. On &lt;a href=&#34;https://store.steampowered.com/app/1867530/Solar_Ash/&#34;&gt;Solar Ash&lt;/a&gt;, e.g., we&amp;rsquo;d see Rei get stuck about once every two hours or so during QA, so we just added a little white-noise to stuck positions and they&amp;rsquo;d pop-out within a few frames.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/EtNB08RO1qg&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Between the articulated moving platforms, detailed mesh collision, and changing gravity you can imagine how wild the collision issues were.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;And that&amp;rsquo;s about it for sliding. Note that this code is not meant to be used verbatim, but as a starting point. As your controls are developed, you&amp;rsquo;ll add different responses to different surface-types and movement-modes. E.g. a grounded character will treat the floor, walls, and ceilings differently.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One last cool trick before I wrap this up.&lt;/em&gt; As implemented so far, you&amp;rsquo;ll see some jitter when you try and slide between surfaces that meet at an acute angle. The solution is to recognize that in this case we actually want to slide along the &lt;em&gt;seam&lt;/em&gt; in between them. Our helper stores the &lt;code&gt;PrevNormal&lt;/code&gt; each step to compute this. Here&amp;rsquo;s a full listing along with the subtituted call to &lt;code&gt;DepenAndSweep&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; FSlide&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;TryStep( &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; UWorld&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; World, FHitResult&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; Hit )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// early out?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Remainder.IsNearlyZero( KINDA_SMALL_NUMBER ) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// sweep forward
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; bHit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DepenAndSweep( World, Hit, Position, Remainder, Rotation, Channel, Shape, QueryParams );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;bHit )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Hit.TraceEnd;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Remainder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.f&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// stuck?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Hit.bStartPenetrating )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// update position
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Hit.TraceStart &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; PullBackMove( Hit.Location &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Hit.TraceStart );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// remainder is either projected onto the impact plane, or the seam between two acute impact-planes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// dot-product to check the angle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// cross product to calcuate the seam
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; FVector Seam &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( PrevNormal &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; Hit.Normal ) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; KINDA_SMALL_NUMBER &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; ( PrevNormal &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt; Hit.Normal ) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; FVector( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0f&lt;/span&gt; ) ;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Remainder &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; ( &lt;span style=&#34;color:#ae81ff&#34;&gt;1.f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; Hit.Time );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Remainder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Seam.IsNearlyZero( &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1f&lt;/span&gt; ) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		FVector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;VectorPlaneProject( Remainder, Hit.Normal ) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Remainder.ProjectOnTo( Seam ) ;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	PrevNormal &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Hit.Normal;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Until next time, happy sliding!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>🖌️ Project Update: Playable Pilot Concepts</title>
      <link>https://blog.littlepolygon.com/posts/players/</link>
      <pubDate>Fri, 10 Feb 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/players/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/players/lineup.jpg&#34; alt=&#34;Lineup&#34;&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve planned for three playable pilots since my earliest prototype.&lt;/p&gt;
&lt;p&gt;The design goal is straightforward: approaching the same mission with different pilots who handle differently increases replay variety. It also acts as a difficulty handicap for casual or hardcore players to opt-into by choosing particular easy or challenging pilots for particular maps.&lt;/p&gt;
&lt;p&gt;This week I was feeling ambitious and decided to flex my illustration muscles, and made a nice set of pilot-portrait key-art, as well as sketch concepts for their fighter craft. I really want to push the 80s mecha-anime space-opera creative direction.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/players/pilots.jpg&#34; alt=&#34;Original Lineup&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;My original fat-pixel lineup.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;These are all still early concepts, and might change a lot. For their fighter craft, I tried to limit myself to things which can be modelled in the low-poly style, so I kept them sketchier. The one thing I &lt;em&gt;didn&amp;rsquo;t&lt;/em&gt; do was choose names. It&amp;rsquo;s too hard!&lt;/p&gt;
&lt;h2 id=&#34;the-rebel-ace&#34; &gt;The Rebel Ace
&lt;span&gt;
    &lt;a href=&#34;#the-rebel-ace&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;ace.jpg&#34; alt=&#34;Rebel Ace&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Wait, that was supposed to be a &lt;em&gt;challenge&lt;/em&gt;?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The Ace is the face for the game: a cocky protagonist with a fiery sense of justice who&amp;rsquo;s driven to protect civilians. Her primary flaw is her pride. I think of her as a mix of &lt;a href=&#34;https://dirtypair.fandom.com/wiki/Kei_(anime)&#34;&gt;Kei&lt;/a&gt;, &lt;a href=&#34;https://gundam.fandom.com/wiki/Judau_Ashta&#34;&gt;Judau Ashta&lt;/a&gt;, and &lt;a href=&#34;https://gallforce.fandom.com/wiki/Lufy&#34;&gt;Lufy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/5NYtiWgRXxFNRKFIcc/giphy.gif&#34; alt=&#34;Kei&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;If I were this cute, I&amp;rsquo;d be vain too.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Her search-and-rescue fighter craft is the &lt;em&gt;balanced&lt;/em&gt; gameplay choice, and has a good mix of firepower and evasiveness. It&amp;rsquo;s special feature is the ability to transform between mecha and jet forms.&lt;/p&gt;
&lt;h2 id=&#34;the-imperial-commander&#34; &gt;The Imperial Commander
&lt;span&gt;
    &lt;a href=&#34;#the-imperial-commander&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;commander.jpg&#34; alt=&#34;Imperial Commander&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;The only real challenge to power is power itself.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The Commander is the heel: an cold-hearted but charismatic authoritarian prone to pontificating on his political ideals. He&amp;rsquo;s the ace&amp;rsquo;s natural rival. I basically started with &lt;a href=&#34;https://gundam.fandom.com/wiki/Char_Aznable&#34;&gt;Char Aznable&lt;/a&gt;, and kept pressing the big button labelled &amp;ldquo;&lt;a href=&#34;https://finalfantasy.fandom.com/wiki/Sephiroth&#34;&gt;Sephiroth&lt;/a&gt;&amp;rdquo; until his design screamed &amp;ldquo;daddy!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;zeong.jpg&#34; alt=&#34;Char&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Classic callback.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;His attack mecha is the &lt;em&gt;tank&lt;/em&gt; choice, with high armor and ammo-payload in exchange for limited maneuverability. It basically turns the game into a shooting gallery like &lt;a href=&#34;https://youtu.be/K3YFLlD-b_s&#34;&gt;Omega Boost&lt;/a&gt;. His special is an ultra-heavy charge-attack cannon.&lt;/p&gt;
&lt;h2 id=&#34;the-space-pirate&#34; &gt;The Space Pirate
&lt;span&gt;
    &lt;a href=&#34;#the-space-pirate&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;pirate.jpg&#34; alt=&#34;Space Pirate&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Who&amp;rsquo;s content to live in a fishbowl once you&amp;rsquo;ve ridden the sea of stars?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The Pirate is the wildcard: a smuggler who delights in making her own way in the world through her own skill and wits, outside the law. But behind her hedonistic facade, lies a principled sense of honor. A total throwback to &lt;a href=&#34;https://tokinowa.fandom.com/wiki/Captain_Harlock&#34;&gt;Captain Harlock&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/Pq9uz6fWm6TMSF7kv0/giphy.gif&#34; alt=&#34;Asteroidz&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;This was actually the first model I made for this game.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Her fighter is the &lt;em&gt;glass cannon&lt;/em&gt; choice, offering high-speed and dodge-maneuvers in exchange for lighter sniper-style firepower and no armor.  More risk for more reward: her special increases the amount of loot you receive.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Math Breakdown: Anime Homing Missiles</title>
      <link>https://blog.littlepolygon.com/posts/missile/</link>
      <pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/missile/</guid>
      <description>&lt;p&gt;I designed and prototyped the missile attack! The math was clever and I want to show-off!&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s talk about &lt;a href=&#34;https://en.wikipedia.org/wiki/B%C3%A9zier_curve&#34;&gt;cubic bezier curves&lt;/a&gt;, &lt;a href=&#34;https://en.wikipedia.org/wiki/Perlin_noise&#34;&gt;perlin noise&lt;/a&gt;, and &lt;a href=&#34;https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf&#34;&gt;rotation minimizing frames&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/o5hiXadU2BbJ6skNDP/giphy.gif&#34; alt=&#34;Missile Circus!&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Doing &lt;a href=&#34;https://en.wikipedia.org/wiki/Ichir%C5%8D_Itano#Itano_Circus&#34;&gt;Ichirō Itano&lt;/a&gt; proud.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I&amp;rsquo;ll keep this article a little lighter on code. I really want to focus instead on the geometry. Many people are intimidated by math, but keep in mind that you don&amp;rsquo;t need to understand everything to use it.&lt;/p&gt;
&lt;p&gt;Even if this is retreading topics you&amp;rsquo;ve already mastered, hopefully my own solution shows you some new ways you can combine these techniques creatively.&lt;/p&gt;
&lt;h1 id=&#34;iterative-vs-closed-form-motion&#34; &gt;Iterative vs. Closed-Form Motion
&lt;span&gt;
    &lt;a href=&#34;#iterative-vs-closed-form-motion&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h1&gt;&lt;p&gt;Broadly speaking, there are two flavors of movement code.  &lt;strong&gt;Iterative Code&lt;/strong&gt; updates an object position incrementally one frame at a time, in a process college-professors call &lt;em&gt;integration&lt;/em&gt;.  A common example of this &lt;a href=&#34;https://en.wikipedia.org/wiki/Euler_method&#34;&gt;Euler&amp;rsquo;s Method&lt;/a&gt; where we compute the velocity of the object and then nudge the position in that direction over a timestep:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Update( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; DeltaTime ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Vector3 Velocity = CalculateVelocity();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Vector3 Position = GetPosition();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	SetPosition( Position + DeltaTime * Velocity );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Delta is just &amp;lsquo;math speak&amp;rsquo; for &amp;ldquo;change in&amp;rdquo; as in &amp;ldquo;the change in time this Update()&amp;rdquo;.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This is the natural way to write character control, in which the player input varies every frame, or complex physics simulations where there is no known realtime analytical solution.&lt;/p&gt;
&lt;p&gt;Alternatively, if you know the whole motion ahead of time, you can use &lt;strong&gt;Closed-Form Code&lt;/strong&gt; where you plot the whole path from initial conditions (math heads call it a &lt;em&gt;parametric curve&lt;/em&gt;), and &lt;em&gt;sample&lt;/em&gt; the current time. A good example of this is the well-known &lt;strong&gt;Cubic Bezier Curve&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Vector3 CalcBezierPos( Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; t ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; t_ = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; - t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		(t_ * t_ * t_)    * P1 + 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; * t_ * t_ * t) * P2 +
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; * t_ * t * t)  * P3 + 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		(t * t * t)       * P4 ;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;bezier.png&#34; alt=&#34;Bezier Curve&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve ever used any vector graphics tool you probably recognize this. Beziers are &lt;em&gt;cubic polynomials&lt;/em&gt; which is a fancy way of saying the simplest path with four degrees of freedom: the endpoints P0 and P3 and the &amp;ldquo;control points&amp;rdquo; P1 and P2 which affect the orientation and curvature.&lt;/p&gt;
&lt;p&gt;The input &lt;em&gt;t&lt;/em&gt; is called the &lt;em&gt;input parameter&lt;/em&gt; and is a ratio on the range 0-1.  So e.g. t=0.333 is about a third of the way through.  To move a point, we simply take the elapsed time since the start of the motion, divided by the duration.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; StartTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Duration;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Update() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; CurrentTime = Time.time;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Elapsed = CurrentTime - StartTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;( Elapsed &amp;gt;= Duration )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		SetPosition( P3 ); &lt;span style=&#34;color:#75715e&#34;&gt;// we&amp;#39;re at the end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		SetPosition( CalcBezierPos( P0, P1, P2, P3, Elapsed / Duration ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In addition to the position, we can also use the bezier parameters to calculate the &lt;em&gt;derivative at t&lt;/em&gt;, which is the rate of change.  This vector is useful because we say it&amp;rsquo;s &lt;em&gt;tangent to the curve&lt;/em&gt;, i.e. it points in the direction of the motion. To convert this to speed, divide by the duration.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Vector3 CalcBezierDeriv( Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; t ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; t_ = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; - t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;  (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		( &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; * t_ * t_ ) * ( P1 - P0 ) + 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		( &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt; * t_ * t ) * ( P2 - P1 ) + 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		( &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; * t * t ) * ( P3 - P2 ) ;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Velocity = CalcBezierDeriv( P0, P1, P2, P3, Elapsed / Duration ) / Duration;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;deriv.png&#34; alt=&#34;Derivative&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Speed = Meters Per Second = ( Meters Per T ) / ( Seconds Per T ) = Deriv / Duration&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;h1 id=&#34;simulating-homing-missiles&#34; &gt;Simulating Homing Missiles
&lt;span&gt;
    &lt;a href=&#34;#simulating-homing-missiles&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h1&gt;&lt;p&gt;Because I know where the homing missile path starts (the launcher muzzle), and where it ends (the painted target), I chose to use a bezier curve as the base for the homing missile path.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;firing_solution.png&#34; alt=&#34;Firing Solution&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;P1 is placed in front of the shooter, and P2 is projected out from the target surface.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Using a closed form kept things simple, because I didn&amp;rsquo;t have to solve a complicated &amp;ldquo;simulation&amp;rdquo; that hits the right point, and I can fine-tune the exact time between when you fire and when it hits, which is more intuitive than second-order physics units.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/Oy6MvbtJHND2DUpwoo/giphy.gif&#34; alt=&#34;First Pass Effect&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;This was a &lt;a href=&#34;https://youtu.be/FcxsgZxqnEg&#34;&gt;perfectly cromulent&lt;/a&gt; effect, if a bit bland. We can do better.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;!--

# Easing the Input #

When I picture about missiles firing, I imagine them starting a slow, and then speeding-up, not just mechanically moving at constant speed. We can get this effect by *easing* the input, by first passing it through a function. I started with a basic parabola f(x)=x²

```cs
float EasingFunction( float x ) {
	return x * x;
}

SetPosition( CalcBezierPos( P0, P1, P2, P3, EasingFunction( Elapsed / Duration ) ) );
```

![Easing Figure](easing.png)

&lt;span style=&#34;text-align: center;&#34;&gt;

*The only restriction on f is that f(0)=0 and f(1)=1.*

&lt;/span&gt;


This gave me the speedup I wanted, but as a downside caused the missiles to be completely stationary when fired, whereas I wanted them to at least start with *some speed*. I recouped that by Lerp()ing the parabola with the unmodified input to get an &#34;in between&#34; curve. 

```cs
float EasingFunction( float x ) {
	return Mathf.Lerp( x, x * x, 0.5 );
}
```

![Footage with Easing](https://media.giphy.com/media/ovK9Nnd4gpWkkpGxlH/giphy.gif)

&lt;span style=&#34;text-align: center;&#34;&gt;

*0.5 was just a guess, but it looked good.*

&lt;/span&gt;


--&gt;
&lt;h1 id=&#34;adding-noise&#34; &gt;Adding Noise
&lt;span&gt;
    &lt;a href=&#34;#adding-noise&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h1&gt;&lt;p&gt;With a solid foundation, it&amp;rsquo;s time to start adding juice. Missile storm attacks in anime take erratic paths with more dynamism. We can simulate this by adding noise.&lt;/p&gt;
&lt;p&gt;A common go-to for FX artists is &lt;em&gt;Perlin Noise&lt;/em&gt;, a kind-of pseudorandom oscillation &amp;ndash; it&amp;rsquo;s erratic, but also smooth. The code&amp;rsquo;s a little too long to post here, but it&amp;rsquo;s not hard to find samples online.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;perlin.png&#34; alt=&#34;Perlin Figure&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Search for &amp;ldquo;&lt;a href=&#34;https://github.com/WardBenjamin/SimplexNoise/blob/master/SimplexNoise/Noise.cs&#34;&gt;Simplex Noise&lt;/a&gt;&amp;rdquo; (the name of a common optimized variant).&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;An obvious problem here is that I need the offset to be zero at endpoints, so the missile lines up with the muzzle and the target. I achieved this by multiplying it by an &lt;em&gt;envelope&lt;/em&gt; which is zero at the ends and one in the middle.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;envelope.png&#34; alt=&#34;Envelope Figure&#34;&gt;&lt;/p&gt;
&lt;p&gt;How do we turn a noise function into a 3D curve-deformed offset?  We compute two independent noise values and use them as the X and Y components of an offset vector transformed by a rotation-frame that&amp;rsquo;s aligned to the bezier&amp;rsquo;s derivative (what a mouthful!).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Vector3 LocalOffset;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; NoiseFreq = &lt;span style=&#34;color:#ae81ff&#34;&gt;2f&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// tuning value for wiggle frequency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; NoiseAmp = &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;;   &lt;span style=&#34;color:#75715e&#34;&gt;// tuning value for wiggle size&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; Envelope = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; - (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; - &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; * t) * (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; - &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; * t);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LocalOffset.x = NoiseAmp * Envelope * Noise( NoiseSeedX, NoiseFreq * Elapsed );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LocalOffset.y = NoiseAmp * Envelope * Noise( NoiseSeedY, NoiseFreq * Elapsed );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LocalOffset.z = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Quaternion Frame = Quaternion.LookRotation( CalcBezierDeriv( P0, P1, P2, P3, t ) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SetPosition( CalcBezierPos( P0, P1, P2, P3, t ) + Frame * LocalOffset );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;frame.png&#34; alt=&#34;Rotation Frame&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;TL;DR We&amp;rsquo;re wiggling along the red and green arrows.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This &lt;em&gt;almost&lt;/em&gt; worked, but I saw some glitches. Vertically-Locked rotation frames calculated this way are &lt;em&gt;aligned&lt;/em&gt; to the derivative, but twist a lot &amp;ndash; especially when the path is vertical. Instead, we want so-called &lt;em&gt;Rotation Minimizing Frames&lt;/em&gt; which have no instantaneous twist, just minimal swings between directions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;twist.png&#34; alt=&#34;Twist Comparison&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;(A) Vertically-Locked Frames (B) Rotation Minimizing Frames&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The method of computing minimizing frames is mathematically complex in general, but luckily a paper was published in 2006 which described a &lt;em&gt;shockingly simple&lt;/em&gt; method for &amp;ldquo;nudging&amp;rdquo; a frame forward without twisting called the &lt;a href=&#34;https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf&#34;&gt;Double Reflection Method&lt;/a&gt;. We don&amp;rsquo;t need to understand the derivation, we can just know that it&amp;rsquo;s cheap and it works.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;double_ref.png&#34; alt=&#34;Double Reflection&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Shoutout to &lt;a href=&#34;https://twitter.com/JasperRLZ&#34;&gt;Jasper St. Pierre&lt;/a&gt; for showing me this mathy-ass math.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cs&#34; data-lang=&#34;cs&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Quaternion Frame; &lt;span style=&#34;color:#75715e&#34;&gt;// Initialize to Quaternion.LookDirection( P1 - P0 );&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; UpdateFrame( &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; t ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// starting &amp;#34;normal&amp;#34; and &amp;#34;tangent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; n0 = Frame * Vector3.up;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; t0 = Frame * Vector3.forward;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// target &amp;#34;tangent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; t1 = CalcBezierDeriv( P0, P1, P2, P3, t ).normalized;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// first reflection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; v1 = CalcBezierPos( P0, P1, P2, P3, t ) - GetPosition(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; c1 = v1.sqrMagnitude;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; n0_l = n0 - (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; / c1) * Vector3.Dot(v1, n0) * v1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; t0_l = t0 - (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; / c1) * Vector3.Dot(v1, t0) * v1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// second reflection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; v2 = t1 - t0_l;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; c2 = v2.sqrMagnitude;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; n1 = n0_l - (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; / c2) * Vector3.Dot(v2, n0_l) * v2;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// build rotation with target normal for &amp;#34;up&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Frame = Quaternion.LookRotation( t1, n1 );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;final.gif&#34; alt=&#34;Final Missile Path&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Et Voilà!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Thanks for reading! I promise I&amp;rsquo;ll do an art post next to give everyone a break from code 🙏&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Tech Breakdown: Bullet Hell</title>
      <link>https://blog.littlepolygon.com/posts/bullets/</link>
      <pubDate>Tue, 17 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/bullets/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m adding an Itano-style &lt;a href=&#34;https://youtu.be/BzXfVgYCxWI&#34;&gt;Missile Circus Attack&lt;/a&gt; right now, so let&amp;rsquo;s talk about the principles of a high-performance bullet-hell system.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/vQWulEAxw4mnScCsPN/giphy.gif&#34; alt=&#34;Bullet Hell!&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Four thousand bullets ought to be enough for anyone.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Computers are &lt;em&gt;fast&lt;/em&gt;. Your Nintendo Switch has &lt;em&gt;four&lt;/em&gt; 1.02 GHz &lt;a href=&#34;https://en.wikipedia.org/wiki/ARM_Cortex-A57&#34;&gt;ARM Cortex-A57&lt;/a&gt; CPU cores, each one 500x more powerful than the computer &lt;a href=&#34;https://en.wikipedia.org/wiki/Apollo_Guidance_Computer&#34;&gt;which sent us to the moon&lt;/a&gt;. So why is it slow to spawn a few thousand Bullet Objects?&lt;/p&gt;
&lt;p&gt;It boils down to two issues: &lt;em&gt;memory&lt;/em&gt; and &lt;em&gt;memory&lt;/em&gt; (again).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;These samples are for &lt;a href=&#34;https://unity.com/&#34;&gt;Unity&lt;/a&gt;, but the principles are engine-agnostic. For instance, I also applied them with the &lt;a href=&#34;https://www.unrealengine.com/&#34;&gt;Unreal Game Engine&lt;/a&gt; in the development of &lt;a href=&#34;https://store.steampowered.com/app/1867530/Solar_Ash/&#34;&gt;Solar Ash&lt;/a&gt;. That translation is left as an exercise to the reader.&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;memory-problem-1-allocation-and-object-pooling&#34; &gt;Memory Problem 1: Allocation and Object Pooling
&lt;span&gt;
    &lt;a href=&#34;#memory-problem-1-allocation-and-object-pooling&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h1&gt;&lt;p&gt;Normally we add objects to the scene with &lt;a href=&#34;https://docs.unity3d.com/ScriptReference/Object.Instantiate.html&#34;&gt;Object.Instantiate()&lt;/a&gt;, which allocates managed memory for C# components as well as several handles to C++ native engine resources (meshes, transforms, etc). On removal, &lt;a href=&#34;https://docs.unity3d.com/ScriptReference/Object.Destroy.html&#34;&gt;Object.Destroy()&lt;/a&gt; unregisters the resources, and releases its memory reference so that the garbage collector can come around later to return the &lt;a href=&#34;https://en.wikipedia.org/wiki/Random-access_memory&#34;&gt;RAM&lt;/a&gt; back to the system.&lt;/p&gt;
&lt;p&gt;When this overhead is amortized over the lifetime of the object it doesn&amp;rsquo;t matter, but bullets are &lt;em&gt;rapidly spawned&lt;/em&gt; and &lt;em&gt;short lived&lt;/em&gt;, so we&amp;rsquo;re continuously creating them and tearing them down, which is a drag on performance.&lt;/p&gt;
&lt;p&gt;An alternative is to pre-allocate a &lt;em&gt;pool&lt;/em&gt; of hidden bullets. To spawn a bullet, we just grab an unused one, toggle its visibility, and put it in the right place. To remove, we simply hide it and put it back in the pool to be recycled.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve implemented this several ways over the years, but here&amp;rsquo;s a pithy version that pleases me:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; UnityEngine;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PooledObject&lt;/span&gt; : MonoBehaviour
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	For the prefab, next points at the head of the freelist.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	For inactive instances, next points at the next inactive instance.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	For active instances, next points back at the source prefab.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;	[System.NonSerialized]&lt;/span&gt; PooledObject next;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; T Create&amp;lt;T&amp;gt;(T prefab, Vector3 pos, Quaternion rot) &lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt; T : PooledObject 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		T result = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (prefab.next != &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			We have a free instance ready to recycle.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			result = (T) prefab.next;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			prefab.next = result.next;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			result.transform.SetPositionAndRotation(pos, rot);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			There are no free instances, lets allocate a new one.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			result = Instantiate&amp;lt;T&amp;gt;(prefab, pos, rot);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			result.gameObject.hideFlags = HideFlags.HideInHierarchy;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		result.next = prefab;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		result.gameObject.SetActive(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Release()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (next == &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			This instance wasn&amp;#39;t made with Create(), so let&amp;#39;s just destroy it.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			Destroy(gameObject);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			Retrieve the prefab we were cloned from and add ourselves to its
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			free list.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;			*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; prefab = next;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			gameObject.SetActive(&lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			next = prefab.next;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			prefab.next = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You may be asking: I said there&amp;rsquo;d be a pool. Where is it? Shouldn&amp;rsquo;t there be a &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; or something somewhere?&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s actually all there in the &lt;code&gt;next&lt;/code&gt; reference. The pool is a &lt;a href=&#34;https://en.wikipedia.org/wiki/Linked_list&#34;&gt;Linked List&lt;/a&gt; where each instance points at the next item in a list, like a chain. A picture is worth 1,000 words:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;linked_list.jpg&#34; alt=&#34;Linked-List Figure&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;The prefab points at the first free bullet, and each free bullet points to the next free bullet. Active bullets point back at the prefab so they know where to go when they&amp;rsquo;re recycled.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;All from one field.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;With this method we can create and release bullets with a just few cheap assignments. Once the pool size stabilizes we no longer have to allocate or deallocate any memory for as long as the scene continues to run.  As a nice bonus, we naturally get separate freelists for every type of prefab that&amp;rsquo;s pooled.&lt;/p&gt;
&lt;p&gt;Challenge question: can you implement a function to free-up unused resourced by &lt;code&gt;Destroy()&lt;/code&gt;ing the freelist?&lt;/p&gt;
&lt;h1 id=&#34;memory-problem-2-context-switching-and-data-oriented-design&#34; &gt;Memory Problem 2: Context-Switching and Data-Oriented Design
&lt;span&gt;
    &lt;a href=&#34;#memory-problem-2-context-switching-and-data-oriented-design&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h1&gt;&lt;p&gt;Pooling eliminates the cost of creating and destroying bullets, but our profiler still shows a pretty siginifcant cost &lt;code&gt;Update()&lt;/code&gt;ing them. In particular are two slow operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UnityEngine.Physics.Raycast&lt;/code&gt; checking for impact collisions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UnityEngine.Transform.position=&lt;/code&gt; updating the scene position&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you dig even deeper (e.g. using a hardware profiler like &lt;a href=&#34;https://en.wikipedia.org/wiki/VTune&#34;&gt;VTune&lt;/a&gt;), you&amp;rsquo;ll see that both of these methods spend most of their time &lt;em&gt;fetching data&lt;/em&gt;. This is called a &lt;em&gt;pipeline stall&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t go too deep into this topic, since whole books have been written about it, but basically while the CPU is very fast, memory is very slow. If we can&amp;rsquo;t keep the CPU fed with data, it will spend most it&amp;rsquo;s time waiting around.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What follows is a simplified explanation that glosses over a lot, but is good-enough-for-game-programmers.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Memory is divided into two parts: a large pool of &lt;em&gt;DRAM&lt;/em&gt; which is attached to the CPU over a bus, and a small cache of fast &lt;em&gt;SRAM&lt;/em&gt; which is on the CPU die. When the CPU attempts to accesses uncached memory it&amp;rsquo;s called &lt;em&gt;cache miss&lt;/em&gt;, which triggers a system interrupt to copy the data from main memory to the cache. Since the cache is limited, stale memory in the cache is retired-back to main memory to make room. Cache hits are fast, but misses are slow &amp;ndash; dozens or even hundreds of cycles.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;E.g. you could calculate several trig functions or multiply several matrices together during one single cache-miss.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;During the bullet update, four distinct chunks of memory are accessed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The executable machine code, or &lt;em&gt;instructions&lt;/em&gt;, which are placed in a dedicated &lt;a href=&#34;https://en.wikipedia.org/wiki/CPU_cache#ICACHE&#34;&gt;ICACHE&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The gameplay script data, stored in C# objects.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&#34;https://en.wikipedia.org/wiki/PhysX&#34;&gt;PhysX&lt;/a&gt; spatial collision data, stored on the C++ &lt;a href=&#34;https://en.wikipedia.org/wiki/C_dynamic_memory_allocation&#34;&gt;heap&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The transform hierarchy data, stored natively in the engine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ICACHE misses happen when code is interleaved. So for instance, code like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Foo(x);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bar(x);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Baz(x);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Foo(y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bar(y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Baz(y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Foo(z);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bar(z);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Baz(z);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; won&amp;rsquo;t be expected to perform as well (subject to complexity and compiler inlining) as sorted code like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Foo(x);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Foo(y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Foo(z);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bar(x);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bar(y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bar(z);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Baz(x);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Baz(y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Baz(z);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; because it&amp;rsquo;s busting the cache and reloading instructions on every other line, as opposed to reusing code while it&amp;rsquo;s still hot.&lt;/p&gt;
&lt;p&gt;For bullets, this suggests first changing from every bullet having it&amp;rsquo;s own &lt;code&gt;Update()&lt;/code&gt; interleaved with other scripts to a single &lt;code&gt;BulletSystem&lt;/code&gt; which updates them all at once, e.g.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BulletSystem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Bullet[] bullets;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; bulletCount;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; dt = Time.deltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; it = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; it &amp;lt; bulletCount; ++it)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; distance = bullets[it].speed * dt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (Physics.Raycast(bullets[it].pos, bullets[it].dir, &lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; hit, dist))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// hit something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				bullet.Release();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				HandleBulletImpact(hit);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// advancing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				bullets[it].pos += distance * bullets[it].dir;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				bullets[it].transform.position = bullets[it].pos;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now all the position-calculations are performed together and the instructions only need to be fetched once. Furthermore, if bullets is an array of C# structs, as opposed to classes, then the state data will inline without indirection, further minimizing the cache miss rate (since nearby data is batched in &lt;em&gt;pages&lt;/em&gt; and &lt;em&gt;lines&lt;/em&gt; that are cached in chunks).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Depending on your access pattern, you can go even further &amp;ndash; refactoring the bullets array into several parallel arrays for different fields like position and velocity. This is called the &amp;ldquo;Array of Structures to Structure of Arrays&amp;rdquo; transformation. Many programmers are so enamored of this technique that it can become something of a cargo cult.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But we still have the calls into &lt;code&gt;Physics.Raycast&lt;/code&gt; and &lt;code&gt;Transform.position=&lt;/code&gt; busting the cache accessing a bunch of data that&amp;rsquo;s not local to the &lt;code&gt;UpdateBullets()&lt;/code&gt; script.  Here&amp;rsquo;s an illustration:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig1.jpg&#34; alt=&#34;Figure 1&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Fig.1 All the data in main memory, attached to multiple CPU cores via the system bus.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;fig2.jpg&#34; alt=&#34;Figure 2&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Fig.2 BulletSystem.Update() runs and script data is cached.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;fig3.jpg&#34; alt=&#34;Figure 3&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Fig.3 Physics.Raycast busts the cache.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;img src=&#34;fig4.jpg&#34; alt=&#34;Figure 4&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Fig.4 Transform.position= busts the cache again.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Now when the for-loop iterates we have to reload the script data and do everything again, for every bullet &amp;ndash; thousands of times. You can see how cache misses pile up &amp;ndash; we keep &lt;em&gt;context switching&lt;/em&gt; between different data structures.&lt;/p&gt;
&lt;p&gt;For a long time this was an intractable problem with Unity, but in 2017 the &lt;a href=&#34;https://blog.unity.com/technology/what-is-a-job-system&#34;&gt;Job System&lt;/a&gt; was introduced to address batch-processing like this.  I&amp;rsquo;ve suggested how this works in my illustration &amp;ndash; we&amp;rsquo;re going to &amp;ldquo;schedule&amp;rdquo; work on other CPUs in &lt;code&gt;Update()&lt;/code&gt;, and then gather their results in &lt;code&gt;LateUpdate()&lt;/code&gt;. Each CPU will keep the different types of data hot in their respective caches, minimizing the cache miss rate.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fig5.jpg&#34; alt=&#34;Figure 5&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Fig.5 By dedicating different threads to different jobs, we keep more relevant data hot in the cache more often.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Unity provides &lt;a href=&#34;https://docs.unity3d.com/ScriptReference/RaycastCommand.html&#34;&gt;RaycastCommand&lt;/a&gt; for physics query jobs and &lt;a href=&#34;https://docs.unity3d.com/ScriptReference/Jobs.IJobParallelForTransform.html&#34;&gt;IJobParallelForTransform&lt;/a&gt; for scene transformations. Unfortunately, the code for this is too complex for a code snippet in a blog, but here&amp;rsquo;s a abridged version to give you a sense of the implementation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BulletTransformJob&lt;/span&gt; : IJobParallelForTransform
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;	[ReadOnly]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; NativeArray&amp;lt;Vector3&amp;gt; positions;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Execute(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; index, TransformAccess transform) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		transform.localPosition = positions[index];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BulletSystem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; CAPACITY = &lt;span style=&#34;color:#ae81ff&#34;&gt;4096&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Bullet[] bullets;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; bulletCount = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	TransformAccessArray transforms;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	NativeArray&amp;lt;Vector3&amp;gt; positionsToWrite;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	JobHandle txJob;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	NativeArray&amp;lt;RaycastCommand&amp;gt; commands;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	NativeArray&amp;lt;RaycastHit&amp;gt; results;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	JobHandle physJob;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Awake()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		Allocate all the buffer memory we&amp;#39;ll need up-front
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		inst = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Bullet[CAPACITY];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		transforms = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TransformAccessArray(CAPACITY);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		results = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NativeArray&amp;lt;RaycastHit&amp;gt;(CAPACITY, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		commands = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NativeArray&amp;lt;RaycastCommand&amp;gt;(CAPACITY, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		positionsToWrite = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NativeArray&amp;lt;Vector3&amp;gt;(CAPACITY, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(bulletCount == &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; dt = Time.deltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		Build job input buffers.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; it=&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; it&amp;lt;bulletCount; ++it)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; bullet = bullets[it];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			positionsToWrite[it] = bullet.pos + ( dt * bullet.speed ) * bullet.dir;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			commands[it] = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; RaycastCommand(bullet.pos, bullet.dir, bullet.speed * dt);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		Schedule a batch transform update.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		BulletTransformJob job;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		job.positions = positionsToWrite;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		txJob = job.Schedule(transforms);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		Schedule a batch of physics queries.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		physJob = RaycastCommand.ScheduleBatch(commands.GetSubArray(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, bulletCount), results.GetSubArray(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, bulletCount), &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; LateUpdate()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(bulletCount == &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; dt = Time.deltaTime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		Wait for both jobs to finish, if they&amp;#39;re still going.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		txJob.Complete();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		physJob.Complete();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		Handle bullet impacts, swapping with the end of the array to remove
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; it=&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; it&amp;lt;bulletCount;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; bullet = bullets[it];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; hit = results[it];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(hit.collider == &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				bullet.pos += (dt * bullet.speed) * bullet.dir;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				++it;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			bullet.Release();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			HandleHit(hit);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			--bulletCount;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			transforms.RemoveAtSwapBack(it);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(it &amp;lt; bulletCount)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				bullets[it] = bullets[bulletCount];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				results[it] = results[bulletCount];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			bullets[bulletCount] = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; OnDestroy()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		Clean up after ourselves.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;		*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		transforms.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		results.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		commands.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		positionsToWrite.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just a quick look at the profiler in my stress-testing scene, and we&amp;rsquo;re comfortably above 100FPS. Most of the script time is actually spent on a quick-and-dirty prototype script that has nothing to do with bullets. 🤡&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;profile.jpg&#34; alt=&#34;Profiler&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;This isn&amp;rsquo;t even a build - this is in the editor, which is slow anyway.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I could push this further by simplifying the bullet objects themselves, or perhaps even rendering them with &lt;a href=&#34;https://docs.unity3d.com/Manual/GPUInstancing.html&#34;&gt;GPU Instancing&lt;/a&gt; as opposed to MeshRenderers, but for this early phase of development I&amp;rsquo;m happy knowing I don&amp;rsquo;t have any unsolvable problems which can come back to bite me later.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>🖌️ Art Breakdown: Mecha Transformation! 変身!</title>
      <link>https://blog.littlepolygon.com/posts/transformation/</link>
      <pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/transformation/</guid>
      <description>&lt;p&gt;Let&amp;rsquo;s take an art-dive into the inspiration and iterations on Tiny Starpilot&amp;rsquo;s transforming player jet/robot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExM2JhYWVjOTIwOTRjNWRlNWE0ZTEwOWNlMTBiNWY3M2YwODY0NzRkYSZjdD1n/TASt5VxYm44tCgHvPt/giphy.gif&#34; alt=&#34;Slomo Transformation&#34;&gt;&lt;/p&gt;
&lt;p&gt;Like many &lt;a href=&#34;https://youtu.be/0IJMXW0_dcU&#34;&gt;venerable 3D games&lt;/a&gt; before me, my character started as a block of tofu.&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;physical-character control + map-splines integrated into a spline-flying state :) &lt;a href=&#34;https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#gamedev&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/ScreenShotSunday?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#ScreenShotSunday&lt;/a&gt; &lt;a href=&#34;https://t.co/zPfcGAqqZG&#34;&gt;pic.twitter.com/zPfcGAqqZG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1196221917708972032?ref_src=twsrc%5Etfw&#34;&gt;November 18, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I&amp;rsquo;m particularly proud of the squish on landing.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This design actually does a lot of lifting early-on.  It cuts the overhead of iterating on character &lt;em&gt;root motion&lt;/em&gt;, and clarifies little details like how when the tofu flies it can lean-forward 90° and point it&amp;rsquo;s local-up-axis forward to avoid having to swap from a tall to a long hitbox.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a vocal contingent of indies who argue that this is also &lt;em&gt;good to ship&lt;/em&gt;. After all, if the game is already fun as a &lt;em&gt;greybox&lt;/em&gt;, what function does the art serve, except to demonstrate that you&amp;rsquo;re a big company with money to spend?&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t buy this. Even on &lt;a href=&#34;https://en.wikipedia.org/wiki/Game_studies&#34;&gt;ludological&lt;/a&gt; terms, there is utility in the &lt;a href=&#34;https://en.wikipedia.org/wiki/Semiotics&#34;&gt;semiotics&lt;/a&gt; of artwork for design-intent-signalling.&lt;/p&gt;
&lt;p&gt;But indie games are more than mechanics &amp;ndash; they&amp;rsquo;re a parasocial experience amongst and between creators and players. I&amp;rsquo;m not &lt;em&gt;just&lt;/em&gt; building an experience to tickle your &lt;a href=&#34;https://en.wikipedia.org/wiki/Proprioception&#34;&gt;proprioception&lt;/a&gt;-pleasure-centers, I&amp;rsquo;m expressing solidarity by positioning the work within a shared enthusiast genre: stylish anime mecha shiiiit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/IasT8tLTEU6aKOcy0p/giphy.gif&#34; alt=&#34;Screencap of Luffy from Gall Force&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I could watch &amp;ldquo;booting up cockpit computer&amp;rdquo; sequences all day.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The more specific proximate motivation for me to investigate character art was seeing the cool process-posts and sizzle-reels &lt;a href=&#34;https://twitter.com/burgeroise&#34;&gt;Ivy May&lt;/a&gt; made for &lt;a href=&#34;https://burgeroise.itch.io/coquette-dragoon-1&#34;&gt;Coquette Dragoon&lt;/a&gt; using &lt;a href=&#34;https://www.blender.org/&#34;&gt;Blender&lt;/a&gt;.  Now I wanted to learn Blender!!&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Girls love + alternative mecha VN series Coquette Dragoon is now out in early access! ~50k words. Contains full first two chapters and preview of chapter three! Coquette is for adults only and contains dark/suggestive themes, read at your own discretion. &lt;a href=&#34;https://t.co/mh62zNIE0h&#34;&gt;https://t.co/mh62zNIE0h&lt;/a&gt; &lt;a href=&#34;https://t.co/nnBO7ZuKVu&#34;&gt;pic.twitter.com/nnBO7ZuKVu&lt;/a&gt;&lt;/p&gt;&amp;mdash; iby (@burgeroise) &lt;a href=&#34;https://twitter.com/burgeroise/status/1549488257716858880?ref_src=twsrc%5Etfw&#34;&gt;July 19, 2022&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Support independent creators!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;My first concept sketch was a vague mix between the &lt;a href=&#34;https://zoneoftheenders.fandom.com/wiki/Vic_Viper&#34;&gt;Vic Viper&lt;/a&gt; from &lt;a href=&#34;https://youtu.be/Uj7UMpC-f6c&#34;&gt;Zone of the Enders: 2nd Runner&lt;/a&gt;, and the eponymous &lt;a href=&#34;https://ikaruga.fandom.com/wiki/Ikaruga_(fighter)&#34;&gt;Ikaruga Fighter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;ver1.jpg&#34; alt=&#34;First Concept Sketch&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I vocalized a lot of transformation and impact sounds while drawing this.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;finished the arms and shield-panels. now just for the wing panels which I... forgot to draw in my reference images -__-;; &lt;a href=&#34;https://t.co/6mYJDNb81o&#34;&gt;pic.twitter.com/6mYJDNb81o&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1236895847788326912?ref_src=twsrc%5Etfw&#34;&gt;March 9, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;TL;DR Review of Blender: great modeling workflow, but character rigging was a pain.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;One afternoon of game-engine-noodling and &lt;a href=&#34;https://en.wikipedia.org/wiki/Inverse_kinematics&#34;&gt;IK&lt;/a&gt;-wrangling later, and we were in business.&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Full integration of new character + posing + shooting + jet-transformation!! &lt;a href=&#34;https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#gamedev&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/unity3d?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#unity3d&lt;/a&gt; &lt;a href=&#34;https://t.co/4IV12m4jsO&#34;&gt;pic.twitter.com/4IV12m4jsO&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1244433204464873473?ref_src=twsrc%5Etfw&#34;&gt;March 30, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://macross.fandom.com/wiki/Skull_Squadron&#34;&gt;Skull Squadron&lt;/a&gt; color scheme, Naturally.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This looked &lt;em&gt;sicc&lt;/em&gt;, and left me feeling pretty chuffed, but shortly it became clear there were some dealbreakers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The asset was too detailed, and didn&amp;rsquo;t match the environment.&lt;/li&gt;
&lt;li&gt;It was too labor-intensive for other props.&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;jet&amp;rdquo; shape lacked personality.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I went back to the drawing board and designed a simpler character that more naturally decomposed into a &amp;ldquo;boxy&amp;rdquo;/low-poly&amp;quot;/&amp;quot;&lt;a href=&#34;https://en.wikipedia.org/wiki/Fifth_generation_of_video_game_consoles&#34;&gt;fifth-generationy&lt;/a&gt;&amp;quot; model.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;ver2.jpg&#34; alt=&#34;Second Concept Sketch&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I still wanted to retain the the boyish/feminine figure.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;For the transformation, I decided not to reinvent the wheel and takes the basic mechanics of the &lt;a href=&#34;http://www.macross2.net/m3/sdfmacross/vf-1s-valkyrie.htm&#34;&gt;Strike Valkyrie&lt;/a&gt; from my one true love, &lt;a href=&#34;https://youtu.be/I4aQQBLokNo&#34;&gt;Macross&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/e4rrWkQmHR026p6HZp/giphy.gif&#34; alt=&#34;Footage of the VF-1 Transformation&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;The legs swing into trusters, and the arms fold between them, centered under the chasis.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;My personal innovation was to make the knee slide-up along a track on the back of the leg to form v-shaped thrusters, which exagerates the thrust-aperture-narrowing animation in game and makes the afterburner feel more responsive.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;thruster_slide.jpg&#34; alt=&#34;Figure of the Modifier Leg Transofrmation&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I didn&amp;rsquo;t reinvent the wheel, but I did put my fingerprint on it.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/Y6ljFaKRTrI&#34;&gt;This was a triumph, I&amp;rsquo;m making a note here: huge success. I&amp;rsquo;s hard to overstate my satisfaction&lt;/a&gt;.  It looks great next to other low-poly assets, it&amp;rsquo;s easy to make quick changes, and it has a lot of personality.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/8kszanRO1IxXG76xYw/giphy.gif&#34; alt=&#34;Dancing Mecha&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;I think I did a &lt;a href=&#34;https://youtu.be/5jhC5Z18dbg&#34;&gt;Good Job&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;Now for the &lt;em&gt;really&lt;/em&gt; hard part: picking a &lt;em&gt;name&lt;/em&gt;. 🤦&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>⚙️ Tech Breakdown: Third Person Cameras in Games</title>
      <link>https://blog.littlepolygon.com/posts/cameras/</link>
      <pubDate>Sat, 14 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/cameras/</guid>
      <description>&lt;p&gt;Experience has made me opinionated about implementing 3rd person cameras. People naturally, but naively, think about the camera as a second actor in the world, following the player around (like &lt;a href=&#34;https://super-mario-64-official.fandom.com/wiki/Lakitu&#34;&gt;Lakitu&lt;/a&gt; in Super Mario 64).&lt;/p&gt;
&lt;p&gt;Lets discuss an alternative perspective, where you consider instead the player on the 2D picture plane (with code!).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.littlepolygon.com/posts/cameras/lakitu.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://youtu.be/cRgqouS1O6E?t=102&#34;&gt;We can rebuild him&lt;/a&gt;. We have the technology.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;em&gt;A big shoutout here in the jump to the work of &lt;a href=&#34;https://youtu.be/C7307qRmlMI&#34;&gt;John Nesky&lt;/a&gt; and &lt;a href=&#34;https://www.sciencedirect.com/book/9780123116345/real-time-cameras&#34;&gt;Mark Haigh-Hutchinson&lt;/a&gt; in contributing to my understanding of &amp;ldquo;gamatography.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So first off, let&amp;rsquo;s state clearly what the &lt;em&gt;goals&lt;/em&gt; of a 3rd person camera system ought to be, as plainly as we can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We want to place the player on the screen.&lt;/li&gt;
&lt;li&gt;We want to control how much of the screen is covered by the player.&lt;/li&gt;
&lt;li&gt;We want interactive control over the view direction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s make these into parameters that our code can script.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The following code samples will use &lt;a href=&#34;https://docs.unity3d.com/ScriptReference/&#34;&gt;Unity&amp;rsquo;s C# API&lt;/a&gt; for reference, but these methods are pretty generic and portable to any 3D environment. All you need to do is make sure you can write to the camera&amp;rsquo;s position and rotation.&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CameraParams&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Vector3 trackingPosition;    &lt;span style=&#34;color:#75715e&#34;&gt;// 3D world position of the player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Vector2 framing;             &lt;span style=&#34;color:#75715e&#34;&gt;// 2D screen position of the player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; distance;              &lt;span style=&#34;color:#75715e&#34;&gt;// offset between the camera and the player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; pitch;                 &lt;span style=&#34;color:#75715e&#34;&gt;// vertical camera tilt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; yaw;                   &lt;span style=&#34;color:#75715e&#34;&gt;// horizontal camera pan&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;strong&gt;Tracking Position&lt;/strong&gt; will come from the game &amp;ndash; typically you just take the player position and possibly apply a low-pass filter or &lt;a href=&#34;https://en.wikipedia.org/wiki/Damping&#34;&gt;critically-damped spring&lt;/a&gt; to smooth out little bumps.&lt;/p&gt;
&lt;p&gt;You can also get pretty sophisticated with it. On &lt;a href=&#34;https://store.steampowered.com/app/1867530/Solar_Ash/&#34;&gt;Solar Ash&lt;/a&gt; we projected the player position onto the ground, so that the camera wouldn&amp;rsquo;t &amp;ldquo;track&amp;rdquo; jumps, and only applied vertical smoothing to clean up uneven terrain, but no horizontal smoothing which would cause the camera to lag as Rei zipped around at 45MPH.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/OKg8ZurLcpfzfJ3sC5/giphy-downsized-large.gif&#34; alt=&#34;Solar Ash Veritcal Tracking&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;The green line here shows the tracking position.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;!--
![Solar Ash Test Footage](https://media.giphy.com/media/ggbf8gQ5vusRcM68Yj/giphy.gif)

&lt;span style=&#34;text-align: center;&#34;&gt;

*Footage from a stress-test scene I created for vertical tracking.*

&lt;/span&gt;

--&gt;
&lt;p&gt;&lt;strong&gt;Framing&lt;/strong&gt; is a 2D value in &amp;ldquo;Viewport Coordinates&amp;rdquo; where (-1,-1) is the bottom left corner of the screen, (1,1) is the top-right corner of the screen, and (0,0) is the middle of the screen.  This tells us &lt;em&gt;where to place the tracking position on the picture plane&lt;/em&gt; explicitly.&lt;/p&gt;
&lt;p&gt;For a platformer, you probably want to be bottom-third when looking horizontally, center when looking-down on the player, and bottom-center when looking-up (possibly biassed a bit horizontally for an &amp;ldquo;over the shoulder&amp;rdquo; feel).&lt;/p&gt;
&lt;p&gt;In &lt;em&gt;Tiny Starpilot&lt;/em&gt; I actually counter-frame the player and the reticle. When you&amp;rsquo;re aiming to the right, the player is framed on the left and vice-versa. This creates a more dynamic feel and also gives more screen-space to see what you&amp;rsquo;re shooting.&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Really pushing how much I can get stub in with just spheres. &lt;a href=&#34;https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#gamedev&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/screenshotsaturday?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#screenshotsaturday&lt;/a&gt; &lt;a href=&#34;https://t.co/TsN2PJjsvN&#34;&gt;pic.twitter.com/TsN2PJjsvN&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1611890892293705728?ref_src=twsrc%5Etfw&#34;&gt;January 8, 2023&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;p&gt;&lt;strong&gt;Distance&lt;/strong&gt; is just the world-space distance from the player to the camera. Pretty normal platformer logic is to come in close when looking up and pull far out when looking down.  You also might pull out for action scenes but pull in for narrative scenes to draw peoples attention (you can also do that with field-of-view, but it&amp;rsquo;ll make people sick 🤮).&lt;/p&gt;
&lt;p&gt;Lastly, &lt;strong&gt;Pitch&lt;/strong&gt; and &lt;strong&gt;Yaw&lt;/strong&gt; are the &lt;a href=&#34;https://en.wikipedia.org/wiki/Spherical_coordinate_system&#34;&gt;Spherical Coordinates&lt;/a&gt; of your viewing direction. In an Orbit Camera, you can just drive these from the right-stick or the mouse-delta, making sure to clamp the pitch and wrap-around the yaw.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.giphy.com/media/GqArgkFrYNKT3VKIq0/giphy.gif&#34; alt=&#34;Sample Leash Footage&#34;&gt;&lt;/p&gt;
&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;One advanced technique is to consider the vertical component of the &lt;a href=&#34;https://en.wikipedia.org/wiki/Cross_product&#34;&gt;cross-product&lt;/a&gt; between the view-direction and player-velocity. Scaling and adding this into yaw simulates a &amp;ldquo;leash&amp;rdquo; effect.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;So how do we convert these parameters to the camera position/rotation? Math!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; SetParams(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; Camera camera, &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; CameraParams camParams) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// compute &amp;#34;local&amp;#34; offset relative to our view rotation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; tanFOVY = Mathf.Tan(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; * Mathf.Deg2Rad * camera.fieldOfView);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; tanFOVX = tanFOVY * camera.aspect;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; localOffset = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Vector3(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	camParams.distance * tanFOVX * camParams.framing.x,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	camParams.distance * tanFOVY * camParams.framing.y,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	camParams.distance
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// compute position and rotation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rotation = Quaternion.Euler(camParams.pitch, camParams.yaw, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; position = trackingPosition - rotation * localOffset;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    camera.transform.SetPositionAndRotation(position, rotation);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Implemented as an &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods&#34;&gt;Extension Method&lt;/a&gt; for UnityEngine.Camera, as a flex.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I&amp;rsquo;ll leave puzzling out the &lt;a href=&#34;https://en.wikipedia.org/wiki/Trigonometry&#34;&gt;trigonometry&lt;/a&gt; as a exercise to the reader, but notice how we consider the field-of-view and aspect-ratio of the camera to correctly convert the viewport coordinates into a world-space parallax offset.&lt;/p&gt;
&lt;p&gt;In addition to being a more natural parameter-space to script gameplay with, these inputs also form a better &lt;em&gt;blending space&lt;/em&gt; for seamlessly moving the camera from one tracking-target to another. If you just &lt;code&gt;Lerp()&lt;/code&gt; the position and rotation you have no guarentee that the tracking position will stay on screen, or that the horizon will stay flat.&lt;/p&gt;
&lt;p&gt;The key insight here is that, given a camera and a tracking-position we can inverse-compute the camera parameters, then the save the &lt;em&gt;difference&lt;/em&gt; (or &lt;em&gt;delta&lt;/em&gt;) between the two parameter spaces, which we then add to our current inputs, but blend-out over time. That way the interpolation is in our intuitive picture-plane space.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; GetParams(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; Camera camera,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; CameraParams result,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; Vector3 trackingPosition
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; position = camera.transform.position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rotation = camera.transform.rotation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// tracking position&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.trackingPosition = trackingPosition;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// pitch/yaw&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	Vector3 eulerAngles = rotation.eulerAngles;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.pitch = eulerAngles.x;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.yaw = eulerAngles.y;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// distance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; toTrackingOff = trackingPosition - position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fwd = rotation * Vector3.forward;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.distance = Vector3.Dot(toTrackingOff, fwd);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// framing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; toCameraOff = position - trackingPosition;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parallax = Quaternion.Inverse(rotation) * toCameraOff;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; tanFOVY = Mathf.Tan(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.5f&lt;/span&gt; * Mathf.Deg2Rad * camera.fieldOfView);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; tanFOVX = tanFOVY * camera.aspect;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; screenToWorld = result.distance * &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Vector2(tanFOVX, tanFOVY);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.framing.x = -parallax.x / screenToWorld.x;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.framing.y = -parallax.y / screenToWorld.y;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;A little more verbose, but not too bad.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;With this in hand, we can now write the orbit camera blendout:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CameraBlend&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Vector2 framing;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; pitch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; yaw;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; distance;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ComputeBlendout(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; Camera camera,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; CameraBlend result,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; CameraParams newParams,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	camera.GetParams(&lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; oldParams, &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; newParams.trackingPosition);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// offset from the new target to the current view&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.framing = oldParams.framing - newParams.framing;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.pitch = oldParams.pitch - newParams.pitch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.yaw = oldParams.yaw - newParams.yaw;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.distance = oldParams.distance - newParams.distance;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// yaw can wrap-around&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (result.yaw &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;180f&lt;/span&gt;) { result.yaw -= &lt;span style=&#34;color:#ae81ff&#34;&gt;360f&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (result.yaw &amp;lt; -&lt;span style=&#34;color:#ae81ff&#34;&gt;180f&lt;/span&gt;) { result.yaw += &lt;span style=&#34;color:#ae81ff&#34;&gt;360f&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; SetParamsBlended(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; Camera camera,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; CameraParams camParams,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; CameraBlend blendout,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; progress
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; multi = &lt;span style=&#34;color:#ae81ff&#34;&gt;1f&lt;/span&gt; - Mathf.Clamp01(progress);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = camParams;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.framing += multi * blendout.framing;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.pitch += multi * blendout.pitch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.yaw += multi * blendout.yaw;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.distance += multi * blendout.distance;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	camera.SetParams(&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; result);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style=&#34;text-align: center;&#34;&gt;
&lt;p&gt;&lt;em&gt;Notice how we don&amp;rsquo;t need to adjust the tracking position &amp;ndash; we&amp;rsquo;re entirely blending in the picture-plane now, so everything stays in view.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;This article has gone long, so I&amp;rsquo;ll wrap it up. But just to leave you with some last widsom: understanding these algorithms is the first-step towards extending them. Placing Solar Ash&amp;rsquo;s camera on a firm foundation eased developing more complex picture-plane oriented tasks, like making line-of-sight collisions less disorienting, changing gravity, or my personal favorite: positioning two heads balanced in a box nicely for dialog.&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;A little mathematical flexing from &lt;a href=&#34;https://twitter.com/hashtag/SolarAsh?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#SolarAsh&lt;/a&gt; preproduction that I was pretty proud of: a method for positioning a camera -- given two heads and a not-blocked-by-UI viewport subregion -- that&amp;#39;s balanced for an player-controlled view rotation. &lt;a href=&#34;https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#gamedev&lt;/a&gt; &lt;a href=&#34;https://t.co/KXlwisvu2x&#34;&gt;pic.twitter.com/KXlwisvu2x&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1473407017016459265?ref_src=twsrc%5Etfw&#34;&gt;December 21, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;</description>
    </item>
    
    <item>
      <title>👷 Project Update: Introduction to Tiny Starpilot</title>
      <link>https://blog.littlepolygon.com/posts/introduction/</link>
      <pubDate>Tue, 10 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/posts/introduction/</guid>
      <description>&lt;p&gt;I juggle a dozen little side-projects which I dust off from time to time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Tiny Starpilot&lt;/em&gt; was a minigame I conceived of years ago when I was freelancing between jobs, and considering the possibility of becoming a one-man-band mobile game developer.&lt;/p&gt;
&lt;p&gt;The design was simple: hold the phone sideways and slide your thumbs along the side to act as tank-tread controls. Your ship would autofire on a regular beat.&lt;/p&gt;
&lt;p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/taT3P6oGGWk&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pew! Pew! Pew!&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I scribbled some quick &amp;amp; dirty pixel art, downloaded some public-domain music, cooked up some bloops with &lt;a href=&#34;https://www.bfxr.net&#34;&gt;bfxr&lt;/a&gt;, and scripted it up in &lt;a href=&#34;https://unity.com/&#34;&gt;Unity&lt;/a&gt; in an afternoon.&lt;/p&gt;
&lt;p&gt;I was fortunate to participate in a San Francisco co-working space at the time &amp;ndash; camped in a generously-donated spare-room at &lt;a href=&#34;https://www.doublefine.com/&#34;&gt;Double Fine&lt;/a&gt; &amp;ndash; surrounded by other talented creators. I received feedback to use a 3D art style, since sales prospects were better that way. So I slapped some cubes together to make a more dynamic demo.&lt;/p&gt;
&lt;p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/jx5hK5lVz_Q&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I thought the green guys could be, like, &lt;a href=&#34;https://youtu.be/ZQXsrVnPwqM&#34;&gt;Space Squids&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I didn&amp;rsquo;t end-up going the solo-dev route, because I was recruited to join the amazing team at &lt;a href=&#34;https://giantsquidstudios.com/&#34;&gt;Giant Squid&lt;/a&gt; to collaborate on &lt;a href=&#34;https://store.steampowered.com/app/384190/ABZU/&#34;&gt;ABZÛ&lt;/a&gt;. It was a life-changing oppurtunity where I had a chance to  grow as a programmer and as a technical game designer.&lt;/p&gt;
&lt;p&gt;In particular, I learned a lot about 3D &lt;a href=&#34;https://giantsquidology-blog.tumblr.com/post/147456346334/camera-control-in-abz%C3%BB&#34;&gt;Camera Design&lt;/a&gt; and &lt;a href=&#34;https://giantsquidology-blog.tumblr.com/post/144615245984/fluid-motion-in-abz%C3%BB&#34;&gt;Character Control&lt;/a&gt;.  I grew ambitous about making my side-game Full 3D, and not just &amp;ldquo;2½&amp;quot;D.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;de&#34; dir=&#34;ltr&#34;&gt;Handbreak drift, scatter-spawned asteroids, abzu-stule unrolling, player craft collision &lt;a href=&#34;https://twitter.com/hashtag/screenshotsunday?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#screenshotsunday&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#gamedev&lt;/a&gt; &lt;a href=&#34;https://t.co/G2N8h45sYh&#34;&gt;pic.twitter.com/G2N8h45sYh&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/965647051525578752?ref_src=twsrc%5Etfw&#34;&gt;February 19, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Asteroids, eat your heart out.&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;The idea for &amp;ldquo;drifting&amp;rdquo; came from conversations with my colleague &lt;a href=&#34;https://twitter.com/petey123567&#34;&gt;Pete Angstadt&lt;/a&gt; as an alternative to the &amp;ldquo;reverse shooting&amp;rdquo; that wasn&amp;rsquo;t possible with the transition from tank-tread controls to a 3D flight yoke.&lt;/p&gt;
&lt;p&gt;By this time ABZÛ had wrapped, and I&amp;rsquo;d transitioned to the new project at &lt;a href=&#34;https://www.heartmachine.com/&#34;&gt;Heart Machine&lt;/a&gt; which would become &lt;a href=&#34;https://store.steampowered.com/app/1867530/Solar_Ash/&#34;&gt;Solar Ash&lt;/a&gt;.  I was doing a lot of research into procedural animation for the &lt;a href=&#34;https://youtu.be/mnYq161hj00?t=281&#34;&gt;Level-Scale &lt;em&gt;Remnants&lt;/em&gt;&lt;/a&gt;, which bled into my silly side-projects, too.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;strong &amp;quot;boston dynamics big dog&amp;quot; energy &lt;a href=&#34;https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#gamedev&lt;/a&gt; &lt;a href=&#34;https://t.co/x5mPYynaqa&#34;&gt;pic.twitter.com/x5mPYynaqa&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1199944202089484288?ref_src=twsrc%5Etfw&#34;&gt;November 28, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Tofu-Bot 4000&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;I&amp;rsquo;m a life-long 80s mecha anime obsessive, so instinctively I thought: if I merged this with Tiny Starpilot &lt;em&gt;could this be a &lt;a href=&#34;https://youtu.be/I4aQQBLokNo&#34;&gt;Macross&lt;/a&gt; fangame??&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;fr&#34; dir=&#34;ltr&#34;&gt;Transformation! &lt;a href=&#34;https://twitter.com/hashtag/screenshotsunday?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#screenshotsunday&lt;/a&gt; &lt;a href=&#34;https://t.co/9GpxvkCnfn&#34;&gt;pic.twitter.com/9GpxvkCnfn&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1203922030216675328?ref_src=twsrc%5Etfw&#34;&gt;December 9, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;span style=&#34;text-align: center;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Just call me &lt;a href=&#34;https://en.wikipedia.org/wiki/Sh%C5%8Dji_Kawamori&#34;&gt;Shoji Kawamori&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;After posting low-key incremental updates sporadically under the #screenshotsaturday tag for a few years, suddenly this blew up:&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Really pushing how much I can get stub in with just spheres. &lt;a href=&#34;https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#gamedev&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/screenshotsaturday?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#screenshotsaturday&lt;/a&gt; &lt;a href=&#34;https://t.co/TsN2PJjsvN&#34;&gt;pic.twitter.com/TsN2PJjsvN&lt;/a&gt;&lt;/p&gt;&amp;mdash; Max! (@xewlupus) &lt;a href=&#34;https://twitter.com/xewlupus/status/1611890892293705728?ref_src=twsrc%5Etfw&#34;&gt;January 8, 2023&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;p&gt;3k likes in 24 hours on my low-follow feed probably represents the majority of engagement I&amp;rsquo;ve ever received on social media cumulatively in a decade, so I was (to say the least) surprised and humbled. That&amp;rsquo;s when I decided to start this devlog.&lt;/p&gt;
&lt;p&gt;I hope that by posting more in-depth footage and generating interest, it&amp;rsquo;ll light a fire under my butt to update more regularly, and bring the dream of a public release closer to reality. 🤞&lt;/p&gt;
&lt;p&gt;I envision posting in a few different flavors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;../../tags/update&#34;&gt;#update&lt;/a&gt; to track the game itself, with footage of new features&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../tags/tech&#34;&gt;#tech&lt;/a&gt; for explainers on math, programming, and nerd shit&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../tags/art&#34;&gt;#art&lt;/a&gt; for concept sketches and 3D models I&amp;rsquo;m noodling on&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../tags/design&#34;&gt;#design&lt;/a&gt; for thoughts on action, iteration, and experimentation&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../tags/research&#34;&gt;#research&lt;/a&gt; for reviews of related games or anime that I&amp;rsquo;m studying&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you for reading my story, and following along in the future.  There&amp;rsquo;s an old bit about how gamedev is like telling a joke and waiting for years to hear if anyone laughed. Your attention and engagement in the process motivates me to do the labor of pulling this stuff out the neoplatonic playpen of my imagination and presenting it online.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>About</title>
      <link>https://blog.littlepolygon.com/about/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://blog.littlepolygon.com/about/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;theboy.jpg&#34; alt=&#34;Photo of My Cat&#34;&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a professional game programmer in Los Angeles. I&amp;rsquo;ve collaborated on &lt;a href=&#34;https://en.wikipedia.org/wiki/Pixie_Hollow_(video_game)&#34;&gt;MMOs&lt;/a&gt;, &lt;a href=&#34;https://youtu.be/BlzbiKD1FY0&#34;&gt;branded games&lt;/a&gt;, &lt;a href=&#34;https://youtu.be/jERsB0b3Dh4&#34;&gt;mobile games&lt;/a&gt;, &lt;a href=&#34;https://en.wikipedia.org/wiki/Sifteo_Cubes&#34;&gt;micro consoles&lt;/a&gt;, and some prestige indie-games: &lt;a href=&#34;https://store.steampowered.com/app/384190/ABZU/&#34;&gt;ABZÛ&lt;/a&gt;, &lt;a href=&#34;https://store.steampowered.com/app/1867530/Solar_Ash/&#34;&gt;Solar Ash&lt;/a&gt;, and &lt;a href=&#34;https://store.steampowered.com/app/1534840/Hyper_Light_Breaker/&#34;&gt;Hyper Light Breaker&lt;/a&gt;. I developed internal tech at &lt;a href=&#34;https://www.yachtclubgames.com/&#34;&gt;Yacht Club Games&lt;/a&gt;, and now work as a freelance game/animation programmer.&lt;/p&gt;
&lt;p&gt;Little Polygon is the development blog for my side-projects, and to pontificate on various gamedev-related subjects. You can learn more about me at &lt;a href=&#34;http://littlepolygon.com&#34;&gt;my homepage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like to reach out, you can find me on &lt;a href=&#34;https://twitter.com/xewlupus&#34;&gt;twitter&lt;/a&gt;, &lt;a href=&#34;https://bsky.app/profile/littlepolygon.bsky.social&#34;&gt;bluesky&lt;/a&gt;, &lt;a href=&#34;https://mastodon.gamedev.place/@littlepolygon&#34;&gt;mastodon&lt;/a&gt;, or email max at tinygalactic dot com.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
