<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://toqoz.fyi/feed.xml" rel="self" type="application/atom+xml" /><link href="https://toqoz.fyi/" rel="alternate" type="text/html" /><updated>2026-06-19T09:20:35+00:00</updated><id>https://toqoz.fyi/feed.xml</id><title type="html">Michael Palmos</title><subtitle>Interesting things sometimes found here.</subtitle><entry><title type="html">Spring-based Animation and Quaternions</title><link href="https://toqoz.fyi/springs.html" rel="alternate" type="text/html" title="Spring-based Animation and Quaternions" /><published>2025-09-01T00:00:00+00:00</published><updated>2025-09-01T00:00:00+00:00</updated><id>https://toqoz.fyi/springs</id><content type="html" xml:base="https://toqoz.fyi/springs.html"><![CDATA[<p><label style="display:flex;align-items:center;gap:0.4rem;margin-bottom:1rem">
  <input id="autoplayToggle" type="checkbox" />
  <span>Autoplay Videos</span>
</label></p>

<script>
(function () {
  const STORAGE_KEY = 'autoplay_enabled';
  const toggle = document.getElementById('autoplayToggle');

  /** Return a fresh list each time in case new videos are added later */
  const videos = () => document.querySelectorAll('video');

  function apply(enabled) {
    videos().forEach(v => {
      /* 1️⃣ keep attribute + property in sync */
      if (enabled) {
        v.setAttribute('autoplay', '');
        v.autoplay = true;
        if (v.paused) v.play().catch(() => {});   // resume immediately
      } else {
        v.removeAttribute('autoplay');
        v.autoplay = false;

        /* 2️⃣ stop anything already rolling */
        if (!v.paused) v.pause();

        /* 3️⃣ optional: rewind so the first frame shows instead of a frozen mid-clip */
        v.currentTime = 0;
      }
    });
  }

  /* initial state */
  const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? 'false');
  toggle.checked = saved;
  apply(saved);

  /* listen for user changes */
  toggle.addEventListener('change', () => {
    const on = toggle.checked;
    localStorage.setItem(STORAGE_KEY, JSON.stringify(on));
    apply(on);
  });

  /* handle videos injected later (e.g. infinite-scroll posts) */
  new MutationObserver(() => apply(toggle.checked))
    .observe(document.body, { childList: true, subtree: true });
})();
</script>

<p>Spring animation is a <em>fairly</em> new technique for animating stuff going between two points.  It works by simulating a real physical spring, which gives you a really nice and responsive animation.</p>

<video width="1280" height="720" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_door_spam.webm" type="video/webm" />
</video>
<p>The difference maker here is that the spring is physically based, so it always feels “right” to creatures of Earth.</p>

<p>In fact, springs feel so right that they’ve seen widespread adoption in UI toolkits.  If you’ve used an Apple product it’s everywhere, Android has had the <a href="https://developer.android.com/jetpack/androidx/releases/dynamicanimation#1.0.0"><code class="language-plaintext highlighter-rouge">DynamicAnimation</code> library</a> since 2019, and <a href="https://react-spring.dev/"><code class="language-plaintext highlighter-rouge">react-spring</code></a> has been around for at least that long as well.</p>

<video width="500" height="600" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_ios_springs.webm" type="video/webm" />
</video>
<p><em>How many different springs do you spot?  It’s more than 4.</em></p>

<blockquote>
  <p>Maybe check out <a href="https://medium.com/@flyosity/your-spring-animations-are-bad-and-it-s-probably-apple-s-fault-784932e51733">this interesting article</a> for a sort-of history of spring-based animation.</p>
</blockquote>

<p>Games haven’t seen nearly as much adoption though.  It’s common to animate cameras using springs, but otherwise they still feel like a bit of a secret.  I feel like an obvious application is to apply them to game object transforms for some convenient, flexible, good looking animation, but I really couldn’t find much of anything out there.  So I made this post.</p>
<blockquote>
  <p>Specifically I’m referring to Unity-style transforms, with separate translation, rotation, and scale components.</p>
</blockquote>

<p>The main spanner-in-the-works when building a <em>Spring Transform</em> is applying springs to quaternions, but this ends up not being such a big deal in the end.</p>

<p>This post and the provided code will be Unity-like, but the concepts are easily applicable everywhere.</p>

<h2 id="the-use-case">The Use Case</h2>
<p>The use case for this is basically any transition that wants animating, but the real win is for interactive elements:</p>
<ul>
  <li>Doors/door handles/drawers.</li>
  <li>Switches/buttons/levers.</li>
  <li>Level editors (moving, rotating pieces).</li>
  <li>Procedural animation (look-at systems, gun reloads, etc).</li>
</ul>

<p>The procedural animation case is a bit open ended, but these examples all feel a lot better when they <em>respond well to interruption</em>, which is the spring’s specialty.  The other bonus is that you don’t need to track any additional state for whether the door/lever/switch is open or closed — just tell it what to be and let the spring handle the rest.</p>

<h2 id="what-about-tweening">What About Tweening?</h2>
<p>Something that <em>does</em> have widespread use in games is (inbe)tweening, otherwise known as easing.  There are <em>many</em> libraries that accomplish this for all major and minor game engines:</p>
<ul>
  <li><a href="https://github.com/KyryloKuzyk/PrimeTween#support">PrimeTween</a> (Unity)</li>
  <li><a href="https://docs.godotengine.org/en/stable/classes/class_tween.html">Tween</a> (Godot – Built-In)</li>
  <li><a href="https://github.com/jdcook/fresh_cooked_tweens">Fresh Cooked Tweens</a> (Unreal)</li>
</ul>

<p>My gripe with tweening is that it’s non-trivial to interrupt a tween and have it react in natural way.  Most applications just reset the tween to the beginning each time (yuck), or worse: force you to wait until the end of the tween to use the element again (guck).</p>

<video width="640" height="360" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_bad_tween.webm" type="video/webm" />
</video>
<p><em>Tween resetting on each click, yuck.</em></p>

<p>Sometimes, developers say “I can fix that”, and have gone to great lengths to make the tween react <em>“smoothly”</em>;</p>
<video width="700" height="308" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_smooth_tween_eh.webm" type="video/webm" />
</video>
<p><em>A demo from <a href="https://intween.wellcaffeinated.net/demos/smoothen-demo/">InTween</a> where they’ve used some <code class="language-plaintext highlighter-rouge">smoothen()</code> magic to try to handle spamming inputs.</em></p>

<p>No matter your determination, it just never quite works right.  In this one, the tail end of the animation has a noticeable curve after multiple clicks, and the speed is pretty jumpy as well.  Compare this to the spring demo below—it’s night and day:</p>

<video width="858" height="516" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_spring_chasing.webm" type="video/webm" />
</video>
<p><em>Fluid motion regardless of interruptions.</em></p>

<p>Spring-based animation is not necessarily better than tweening in all scenarios—they both have their place.  Tweening <em>can</em> be a good solution when you have a set target, a time you want to reach that target by, and there are no interruptions.  You get a lot of control over the particular animation, and you can even use a custom curve to get it looking exactly how you like.</p>
<blockquote>
  <p>Usually tweens that are fast to start and slow to finish feel the most responsive and hide their shortcomings best; e.g. <code class="language-plaintext highlighter-rouge">EaseOutCirc</code>.</p>
</blockquote>

<p>If your animation needs to respond to interruptions well (most interactive things should), then this is the problem that damped springs solve.</p>

<h2 id="whats-in-a-spring">What’s in a Spring?</h2>
<p>I’ve modified Ryan Juckett’s iconic <a href="https://www.ryanjuckett.com/damped-springs/">Damped Springs</a> code for my spring simulation.  His post is a great technical explainer, and I recommend it if you’re a geek.</p>

<p>The layman’s version is that a spring is just a function.  You pass in: <em>position</em>, <em>velocity</em>, <em>target position</em>.  You get back: <em>position</em>, <em>velocity</em>.  How the spring <em>behaves</em> (how quickly your position reaches its target, does it bounce or not, etc) depends on some constant values that are calculated when you initialise the spring.</p>

<p>Not all damped spring implementations are the same, but they usually share similar controls.  Here are the parameters that Ryan’s implementation uses to calculate those constants I mentioned:</p>
<ul>
  <li><strong>Angular Frequency</strong> — Controls how fast the spring oscillates (can loosely think of this as speed).</li>
  <li><strong>Damping Ratio</strong> — Controls how fast the motion decays.
    <ul>
      <li>Damping Ratio &lt; 1: Underdamped (some bounce)</li>
      <li>Damping Ratio == 1: Critically damped (never bounce)</li>
      <li>Damping Ratio &gt; 1: Overdamped (smooth curve toward target)</li>
    </ul>
  </li>
  <li><strong>Delta Time</strong> — Time between updates—you’re probably familiar, but more on this (much) later.
<img src="/assets/2025_spring_variables.png" alt="Spring variables." width="1920px" height="805px" />
<em>A visual stolen from Apple’s <a href="https://developer.apple.com/videos/play/wwdc2023/10158">Animate with springs</a> presentation (WWDC23), but reworded to match our definitions.</em></li>
</ul>

<p>For this implementation, those human-accessible controls are used to produce 4 coefficients;
<img src="/assets/2025_spring_params_computer.png" alt="Diagram showing human spring variables becoming coefficients." width="1920px" height="805px" /></p>

<p>Then, we simply use them:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Perform one "step" of the spring.</span>
<span class="k">private</span> <span class="k">static</span> <span class="p">(</span><span class="kt">float</span><span class="p">,</span> <span class="kt">float</span><span class="p">)</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="kt">float</span> <span class="n">current</span><span class="p">,</span> <span class="kt">float</span> <span class="n">velocity</span><span class="p">,</span> <span class="kt">float</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">float</span> <span class="n">dist</span> <span class="p">=</span> <span class="n">current</span> <span class="p">-</span> <span class="n">target</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">target</span> <span class="p">+</span> <span class="n">dist</span> <span class="p">*</span> <span class="n">POSPOS_COEF</span> <span class="p">+</span> <span class="n">velocity</span> <span class="p">*</span> <span class="n">POSVEL_COEF</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">vel</span> <span class="p">=</span> <span class="n">dist</span> <span class="p">*</span> <span class="n">VELPOS_COEF</span> <span class="p">+</span> <span class="n">velocity</span> <span class="p">*</span> <span class="n">VELVEL_COEF</span><span class="p">;</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">vel</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>All we’re doing is calculating a new position and velocity based on distance and those coefficients.  There really is no magic going on here.</p>

<h2 id="spring-transforms">Spring Transforms</h2>
<p>A <em>Spring Transform</em> is just a spring applied to all the components of a transform (position, rotation, scale).  The idea is that you have some target transform, for which you set your target position, rotation, scale, and then the actual transform just interpolates towards that using the wonders of spring technology.</p>

<h3 id="translation-and-scale-are-easy">Translation and Scale Are Easy</h3>
<p>Translation and scale require next-to no modification.  Just step the spring over all 3 dimensions:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="p">(</span><span class="n">Vector3</span><span class="p">,</span> <span class="n">Vector3</span><span class="p">)</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="n">Vector3</span> <span class="n">current</span><span class="p">,</span> <span class="n">Vector3</span> <span class="n">velocity</span><span class="p">,</span> <span class="n">Vector3</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>  
    <span class="n">Vector3</span> <span class="n">p</span><span class="p">;</span>
    <span class="n">Vector3</span> <span class="n">v</span><span class="p">;</span>
    <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">x</span><span class="p">)</span> <span class="p">=</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">target</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">oldVel</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">co</span><span class="p">);</span>
    <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">=</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">target</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">oldVel</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">co</span><span class="p">);</span>
    <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">z</span><span class="p">)</span> <span class="p">=</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="n">target</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="n">oldVel</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="n">co</span><span class="p">);</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">v</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<video width="1280" height="720" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_spring_translation_scale.webm" type="video/webm" />
</video>
<p><em>Damping Ratio: 0.6, Angular Frequency: 15.0</em></p>

<blockquote>
  <p><strong><em>How the blah does it all connect?</em></strong>
Here’s how you might use all that to affect your transform:</p>
  <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// `targetTransform` is just a struct with target values (t,r,s).</span>
<span class="n">Vector3</span> <span class="n">current</span> <span class="p">=</span> <span class="n">transform</span><span class="p">.</span><span class="n">localPosition</span><span class="p">;</span>
<span class="n">Vector3</span> <span class="n">target</span> <span class="p">=</span> <span class="n">targetTransform</span><span class="p">.</span><span class="n">localPosition</span><span class="p">;</span>    

<span class="p">(</span><span class="n">Vector3</span> <span class="n">newPos</span><span class="p">,</span> <span class="n">Vector3</span> <span class="n">newVel</span><span class="p">)</span> <span class="p">=</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">m_velocityPos</span><span class="p">,</span> <span class="n">target</span><span class="p">);</span>
<span class="n">transform</span><span class="p">.</span><span class="n">localPosition</span> <span class="p">=</span> <span class="n">newPos</span><span class="p">;</span> <span class="c1">// Assign back to our actual transform so that our object moves.</span>
<span class="n">m_velocityPos</span> <span class="p">=</span> <span class="n">newVel</span><span class="p">;</span>

<span class="c1">// Then, repeat for the scale component.</span>
<span class="c1">// ...</span>
</code></pre></div>  </div>
</blockquote>

<blockquote>
  <p>If you’re hollering at your screen about all the unnecessary copying happening in my <code class="language-plaintext highlighter-rouge">StepSpring()</code> functions, put down your lobster and relax.  This code is written with the primary goal being ease of understanding.</p>
</blockquote>

<h3 id="rotation-is-tricky">Rotation Is Tricky</h3>
<p>I can’t tell you how much failure I went through trying to apply damped springs to a rotation component.  Actually, I can and I will.</p>

<p>The solution I’ve come to is conceptually simple though—all great solutions are—so I feel confident about it.</p>

<p>If you’re here for answers, dammit, then… <a href="#just-use-quaternions">skip!</a></p>

<h3 id="the-naive-approach">The Naive Approach</h3>
<p>So, a quaternion in a computer is really just 4 floats.  What happens if we just feed those into the spring function?  Something pretty good, actually:</p>
<video width="964" height="540" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_quat_naive.webm" type="video/webm" />
</video>
<p></p>
<p>Maybe even good enough to use.  But there <em>are</em> bugs under the rug—the biggest one being that rotation doesn’t always take the shortest path, so you often get this weird flipping behaviour that looks bad.  Once I noticed this, I couldn’t look past it.</p>

<p>We’re also violating quaternions a bit here, evidenced by the fact that we have to re-normalize each step to prevent skewing.  This is far from the panacea.</p>

<h3 id="axis-angles">Axis Angles</h3>
<p>Another thing we can do is change our representation of rotation velocity to something that’s easier to use with a spring.</p>

<p>The idea is that we replace our velocity with a vector for the axis and an angle.  Each iteration, convert the current and target rotations to axis angle representations, and then use the spring on <em>that</em>.  A trick here is to encode the angle into the vector’s length, which saves you some bother dealing with positive/negative angles when the axis flips and stuff like that.</p>

<p>In the posts below we end up visualising velocity via axis angle.  Here’s how it looks:
<img src="/assets/2025_velocity_rotation_axis_visual.png" alt="Velocity rotation axis angles visual." width="781px" height="589px" />
<em>Dotted orangle line: axis.  Orange slice of pizza: angle.</em></p>

<p>Hopefully that all makes sense conceptually.  You have this rotational velocity vector which you calculate and apply each iteration, underpinned by the spring system.</p>

<p>But there’s problems here too.  When you move toward the new rotation axis, you’re effectively doing a linear interpolation (as opposed to a <a href="https://en.wikipedia.org/wiki/Slerp">spherical linear interpolation</a>), which means the movement is <em>technically</em> incorrect.</p>

<p>It’s best illustrated if you imagine the traditional situation where we want to turn some object to face a different direction.  In the following, one arrow represents the direction the object is <em>currently</em> facing, and one represents the direction we <em>want</em> to be facing (it doesn’t matter which is which).
<img src="/assets/2025_lerp_vs_slerp.png" alt="Incorrect lerping vs slerping example." width="2000px" height="1800px" />
<em>Slerp (green) vs Lerp (red)</em></p>

<p>The red path is much shorter!  And insidiously, the speed is inconsistent—we spend much more time at the edges of our path than we do in the middle;
<img src="/assets/2025_lerp_speed_distance.png" alt="Incorrect speed caused by lerp instead of slerp." width="1718px" height="1009px" />
<em>These lines intersect with the path at even intervals, I promise.</em></p>

<p>Our rotation axis is making this same error when we’re stepping our spring towards the new axis.  We’re just interpolating from one rotation axis to the next, without care for the spherical nature of it all.  But honestly, in spite of all my scaremongering, you can pretty much get away with this.  I think you’d struggle to find anyone who’d notice the difference—it’s much harder to see when we’re talking about rotation axes instead of directions, and it only really occurs when a lot of movement is already happening.  There’s something wrong with me though, so my search continued.</p>

<blockquote>
  <p>Unfortunately, I don’t have any code for this approach anymore.  <a href="https://stackoverflow.com/a/77129354">Here’s</a> a Stack Overflow answer that outlines one implementation, although it may take a bit of elbow grease to use with the spring system implemented here.</p>
</blockquote>

<h3 id="spherical-coordinates">Spherical Coordinates</h3>
<p>I also gave spherical coordinates a shot.  It sounded sane: convert rotation to a spherical coordinate (these are described by 2 angles and a distance from the origin), do all of the spring calculations in that space, and then finally convert it back and build a rotation out of it.
<img src="/assets/2025_spherical_coordinates.png" alt="Spherical coordinates example" width="639px" height="641px" />
<em>Spherical coordinates, they look like this.</em></p>

<p>But I imagine all the math guys are tossing their heads back and slapping their knees right now, because it turns out it’s actually quite difficult to get velocity—or really any kind of translation—in spherical coordinates.  θ and Φ are essentially longitude and latitude, so if you want to travel across the sphere along a particular path, you need to manage the complicated non-linear relationship between them.  If that’s not bad enough, you’ll also need to handle the angles flipping as you cross particular planes:</p>
<video width="836" height="718" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_spherical_coordinates_flipping.webm" type="video/webm" />
</video>
<p><em><a href="https://mathinsight.org/spherical_coordinates">https://mathinsight.org/spherical_coordinates</a></em></p>

<p>There are some papers describing how to work out these sorts of things in spherical coordinates, but it really just felt to me that spherical coordinates are more useful for describe a <em>coordinate</em>, rather than a space to work in and do translations and such.</p>

<p>Maybe I’m missing something with this one, I don’t know.  But there should be a simpler answer than this.</p>

<h3 id="just-use-quaternions">Just Use Quaternions</h3>
<p>Why don’t we just use quaternions?  It makes sense.  We have a quaternion for the current orientation, a quaternion for the target orientation, and a velocity quaternion.  We just need to fully understand things to get the rotations right.</p>

<p>Now, I hear you: “Quaternions can’t represent rotations larger than 180 degrees!  Your velocity quaternion is going to be ruined!”  Sure.  I saw this discussion a fair bit, and it turned me off using them for a while.  But do we really need to be concerned about rotating more than 180 degrees <em>in a single step</em>?  With the way our calculations currently work, the velocity variable gets quite large at one of the in-between stages, but we can avoid that, and I’ll show you how.</p>

<p>Here’s how a basic implementation might look for our spring rotation.  We do have to write a pretty different <code class="language-plaintext highlighter-rouge">StepSpring()</code> function here, so I’ve copied over some commented code from the base implementation to make things easier to follow:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="n">Quaternion</span> <span class="nf">Multiply</span><span class="p">(</span><span class="n">Quaternion</span> <span class="n">input</span><span class="p">,</span> <span class="kt">float</span> <span class="n">scalar</span><span class="p">)</span> <span class="p">{</span>  
    <span class="k">return</span> <span class="k">new</span> <span class="nf">Quaternion</span><span class="p">(</span><span class="n">input</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">,</span> <span class="n">input</span><span class="p">.</span><span class="n">y</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">,</span> <span class="n">input</span><span class="p">.</span><span class="n">z</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">,</span> <span class="n">input</span><span class="p">.</span><span class="n">w</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">);</span>  
<span class="p">}</span>

<span class="k">private</span> <span class="k">static</span> <span class="n">Quaternion</span> <span class="nf">ShortestRotation</span><span class="p">(</span><span class="n">Quaternion</span> <span class="n">a</span><span class="p">,</span> <span class="n">Quaternion</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>  
    <span class="k">if</span> <span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="nf">Dot</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="p">&lt;</span> <span class="m">0.0f</span><span class="p">)</span> <span class="p">{</span>  
        <span class="k">return</span> <span class="n">a</span> <span class="p">*</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">Inverse</span><span class="p">(</span><span class="nf">Multiply</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="p">-</span><span class="m">1.0f</span><span class="p">));</span>  
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>  
        <span class="k">return</span> <span class="n">a</span> <span class="p">*</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">Inverse</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>  
    <span class="p">}</span>  
<span class="p">}</span>

<span class="k">private</span> <span class="p">(</span><span class="n">Quaternion</span><span class="p">,</span> <span class="n">Quaternion</span><span class="p">)</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="n">Quaternion</span> <span class="n">current</span><span class="p">,</span> <span class="n">Quaternion</span> <span class="n">velocity</span><span class="p">,</span> <span class="n">Quaternion</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">Quaternion</span> <span class="n">p</span><span class="p">;</span>
	<span class="n">Quaternion</span> <span class="n">v</span><span class="p">;</span>

    <span class="c1">//float dist = current - target;</span>
    <span class="n">Quaternion</span> <span class="n">fromTo</span> <span class="p">=</span> <span class="nf">ShortestRotation</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">target</span><span class="p">);</span>  <span class="c1">// From target to current.</span>
    <span class="p">{</span>
        <span class="c1">// dist * POSPOS_COEF</span>
        <span class="n">Quaternion</span> <span class="n">q1</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">SlerpUnclamped</span><span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">fromTo</span><span class="p">,</span> <span class="n">POSPOS_COEF</span><span class="p">);</span>  
        <span class="c1">// velocity * POSVEL_COEF</span>
        <span class="n">Quaternion</span> <span class="n">q2</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">SlerpUnclamped</span><span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">oldVelocity</span><span class="p">,</span> <span class="n">POSVEL_COEF</span><span class="p">);</span>  
        <span class="c1">//float pos = target + dist * POSPOS_COEF + velocity * POSVEL_COEF;</span>
        <span class="n">p</span> <span class="p">=</span> <span class="n">q1</span> <span class="p">*</span> <span class="p">(</span><span class="n">q2</span> <span class="p">*</span> <span class="n">target</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="p">{</span>
        <span class="c1">// dist * VELPOS_COEF</span>
        <span class="n">Quaternion</span> <span class="n">q1</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">SlerpUnclamped</span><span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">fromTo</span><span class="p">,</span> <span class="n">VELPOS_COEF</span><span class="p">);</span>  
        <span class="c1">// velocity * VELVEL_COEF</span>
        <span class="n">Quaternion</span> <span class="n">q2</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">SlerpUnclamped</span><span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">oldVelocity</span><span class="p">,</span> <span class="n">VELVEL_COEF</span><span class="p">);</span>  
        <span class="c1">// float vel = dist * VELPOS_COEF + velocity * VELVEL_COEF;</span>
        <span class="n">v</span> <span class="p">=</span> <span class="n">q1</span> <span class="p">*</span> <span class="n">q2</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">v</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You can think of <code class="language-plaintext highlighter-rouge">SlerpUnclamped()</code> with an identity rotation as a spherical multiplication.  As such, you also don’t need to worry about normalising the result.</p>

<video width="781" height="589" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_springtransform_velocity_confused.webm" type="video/webm" />
</video>
<p><em>Purple - target direction, Green - current direction, Orange - velocity axis and angle.  Overdamped spring.</em></p>

<p>This approach works pretty well until the velocity exceeds 180 degrees, where it gets very confused.  It also works for under-damped springs, which I found tricky with an axis-angle approach:</p>
<video width="781" height="589" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_springtransform_badvelocity_underdamped.webm" type="video/webm" />
</video>

<p>Overcoming Velocity Too Big turns out to just be a matter of looking more closely at the relationship between position and velocity calculations;</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="n">dist</span> <span class="p">=</span> <span class="n">current</span> <span class="p">-</span> <span class="n">target</span><span class="p">;</span>
<span class="c1">// `velocity` here is `vel` from the previous step.</span>
<span class="kt">float</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">target</span> <span class="p">+</span> <span class="n">dist</span> <span class="p">*</span> <span class="n">POSPOS_COEF</span> <span class="p">+</span> <span class="n">velocity</span> <span class="p">*</span> <span class="n">POSVEL_COEF</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">vel</span> <span class="p">=</span> <span class="n">dist</span> <span class="p">*</span> <span class="n">VELPOS_COEF</span> <span class="p">+</span> <span class="n">velocity</span> <span class="p">*</span> <span class="n">VELVEL_COEF</span><span class="p">;</span>
</code></pre></div></div>

<p>In particular, we notice that <code class="language-plaintext highlighter-rouge">velocity</code> always gets multiplied by <code class="language-plaintext highlighter-rouge">POSVEL_COEF</code> before being applied, which gives us the actual amount of velocity added to position each step.  As you may guess, <code class="language-plaintext highlighter-rouge">POSVEL_COEF</code> is really small!  New velocity is ultimately derived from <code class="language-plaintext highlighter-rouge">dist</code>, so we can effectively pre-apply this multiply by moving it into the velocity calculation instead:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">target</span> <span class="p">+</span> <span class="n">dist</span> <span class="p">*</span> <span class="n">POSPOS_COEF</span> <span class="p">+</span> <span class="n">oldVel</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">vel</span> <span class="p">=</span> <span class="n">dist</span> <span class="p">*</span> <span class="n">VELPOS_COEF</span> <span class="p">*</span> <span class="n">POSVEL_COEF</span> <span class="p">+</span> <span class="n">oldVel</span> <span class="p">*</span> <span class="n">VELVEL_COEF</span><span class="p">;</span>
</code></pre></div></div>
<blockquote>
  <p>Here’s a Desmos graph, if you wanted to play around: <a href="https://www.desmos.com/calculator/q6fbtp4yfl">https://www.desmos.com/calculator/q6fbtp4yfl</a>.</p>
</blockquote>

<p>I always try to be certain I haven’t unintentionally broken something, and in this case it’s easy.  Just simulate both versions side by side, and make sure they match:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Old.</span>
<span class="n">Vector3</span> <span class="n">current</span> <span class="p">=</span> <span class="n">t</span><span class="p">.</span><span class="n">localPosition</span><span class="p">;</span>
<span class="n">Vector3</span> <span class="n">target</span> <span class="p">=</span> <span class="n">m_targetTransform</span><span class="p">.</span><span class="n">position</span><span class="p">;</span>

<span class="p">(</span><span class="n">Vector3</span> <span class="n">newPos0</span><span class="p">,</span> <span class="n">Vector3</span> <span class="n">newVel0</span><span class="p">)</span> <span class="p">=</span> <span class="nf">StepSpringOld</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">m_velocityPos0</span><span class="p">,</span> <span class="n">target</span><span class="p">);</span>
<span class="p">(</span><span class="n">Vector3</span> <span class="n">newPos1</span><span class="p">,</span> <span class="n">Vector3</span> <span class="n">newVel1</span><span class="p">)</span> <span class="p">=</span> <span class="nf">StepSpringNew</span><span class="p">(</span><span class="n">currentNew</span><span class="p">,</span> <span class="n">m_velocityPos1</span><span class="p">,</span> <span class="n">target</span><span class="p">);</span>
<span class="n">m_velocityPos0</span> <span class="p">=</span> <span class="n">newVel0</span><span class="p">;</span>
<span class="n">m_velocityPos1</span> <span class="p">=</span> <span class="n">newVel1</span><span class="p">;</span>

<span class="c1">// Returns true if two vectors are approximately equal (https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Vector3-operator_eq.html).</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">AssertFormat</span><span class="p">(</span><span class="n">newPos0</span> <span class="p">==</span> <span class="n">newPos1</span><span class="p">,</span> <span class="s">$"newPos0 does not match newPos1: 0: </span><span class="p">{</span><span class="n">newPos0</span><span class="p">}</span><span class="s">, 1: </span><span class="p">{</span><span class="n">newPos1</span><span class="p">}</span><span class="s">."</span><span class="p">);</span>

<span class="c1">// Doesn't really matter which we assign here -- they're the same.</span>
<span class="n">t</span><span class="p">.</span><span class="n">localPosition</span> <span class="p">=</span> <span class="n">newPos0</span><span class="p">;</span> 
</code></pre></div></div>

<p>Of course, there’s nothing to show here because… nothing happens.  The assert doesn’t fire.</p>

<p>Now just straightforwardly copy the concept over to our quaternion step:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="p">(</span><span class="n">Quaternion</span><span class="p">,</span> <span class="n">Quaternion</span><span class="p">)</span> <span class="nf">StepSpring</span><span class="p">(</span><span class="n">Quaternion</span> <span class="n">current</span><span class="p">,</span> <span class="n">Quaternion</span> <span class="n">velocity</span><span class="p">,</span> <span class="n">Quaternion</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Quaternion</span> <span class="n">p</span><span class="p">;</span>
    <span class="n">Quaternion</span> <span class="n">v</span><span class="p">;</span>

    <span class="c1">// From target to current.</span>
    <span class="n">Quaternion</span> <span class="n">fromTo</span> <span class="p">=</span> <span class="nf">ShortestRotation</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">target</span><span class="p">);</span>  

    <span class="p">{</span>
        <span class="n">Quaternion</span> <span class="n">q1</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">SlerpUnclamped</span><span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">fromTo</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">posPosCoef</span><span class="p">);</span>  
        <span class="n">p</span> <span class="p">=</span> <span class="n">q1</span> <span class="p">*</span> <span class="p">(</span><span class="n">velocity</span> <span class="p">*</span> <span class="n">target</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="p">{</span>
        <span class="n">Quaternion</span> <span class="n">q1</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">SlerpUnclamped</span><span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">fromTo</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">velPosCoef</span> <span class="p">*</span> <span class="k">this</span><span class="p">.</span><span class="n">posVelCoef</span><span class="p">);</span>  
        <span class="n">Quaternion</span> <span class="n">q2</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">SlerpUnclamped</span><span class="p">(</span><span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">,</span> <span class="n">oldVelocity</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">velVelCoef</span><span class="p">);</span>  
        <span class="n">v</span> <span class="p">=</span> <span class="n">q1</span> <span class="p">*</span> <span class="n">q2</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">v</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With velocity a fraction of what it was before, we’re nowhere close to our 180 degree limit, even during fast movement:</p>
<video width="789" height="592" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_springtransform_fixedvelocity.webm" type="video/webm" />
</video>
<p><em>Yum.</em></p>

<p>Of course, it’s not bulletproof.  If you have an exceptionally fast rotating spring (&gt; 180 degrees in a single step), you’ll still see issues.  But I just don’t think that’s very realistic.  Here’s a real fast spring with the angular frequency set to around 300(!), still not breaking that limit:</p>
<video width="789" height="592" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_springtransform_hugeangularfreq.webm" type="video/webm" />
</video>
<p><em>Fast springs are fine too.</em></p>

<h2 id="thats-all-">That’s All… ?</h2>

<p>So, now that you know how to step all the transform components, you can probably go ahead and make your own pretty good spring transform.  So far, I’ve been pretty quiet about how the scaffolding around all this might look, and that’s because ultimately I don’t feel it has much value.  The meat of this post is learning about springs, and stepping the components correctly.</p>

<p>I do, however, have some opinionated recommendations.</p>

<blockquote>
  <p>Here’s my implementation on GitHub if you’d like to take a more complete example: <a href="https://github.com/Toqozz/blog-code/blob/master/spring_transforms/Assets/SpringTransform.cs">https://github.com/Toqozz/blog-code/blob/master/spring_transforms/Assets/SpringTransform.cs</a>.</p>
</blockquote>

<hr />

<h2 id="delta-time">Delta Time</h2>
<p>Juckett has the following note in his post:</p>

<blockquote>
  <p>Because simulating damped springs requires calls to potentially expensive trigonometric and exponential functions, I’ve split the process into two steps.  The first computes a set of coefficients for the position and velocity parameters by expanding the relevant equations.  These coefficients can then be used to quickly update multiple springs using the same angular frequency, damping ratio and time step.  If your simulation updates at a locked time step, you can even cache off the coefficients once at initialization time and use them every frame!</p>
</blockquote>

<p>Which gives you a few options.</p>

<p>You can run the simulation with a variable delta time by simply re-computing the coefficients with the current delta time before updating your spring.  This is going to be slow eventually (hundreds of springs?).</p>

<p>Otherwise, you can run your spring update at a fixed timestep, using your engine’s capabilities or your own; check out the classic <a href="https://www.gafferongames.com/post/fix_your_timestep/">Gaffer On Games: Fix your Timestep!</a> post for that.</p>

<p>I’ve also seen a different <code class="language-plaintext highlighter-rouge">Update()</code> formula that skips some steps here, but I’m not really sure what the tradeoffs are, so I’m hesitant to recommend it.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">CalcDampedSimpleHarmonicMotion</span> <span class="p">(</span>
    <span class="k">ref</span> <span class="kt">float</span> <span class="k">value</span><span class="p">,</span> 
    <span class="k">ref</span> <span class="kt">float</span> <span class="n">velocity</span><span class="p">,</span> 
    <span class="kt">float</span> <span class="n">equilibriumPosition</span><span class="p">,</span> 
    <span class="kt">float</span> <span class="n">deltaTime</span><span class="p">,</span> 
    <span class="kt">float</span> <span class="n">angularFrequency</span><span class="p">,</span> 
    <span class="kt">float</span> <span class="n">dampingRatio</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">float</span> <span class="n">x</span> <span class="p">=</span> <span class="k">value</span> <span class="p">-</span> <span class="n">equilibriumPosition</span><span class="p">;</span>
    <span class="n">velocity</span> <span class="p">+=</span> <span class="p">(-</span><span class="n">dampingRatio</span> <span class="p">*</span> <span class="n">velocity</span><span class="p">)</span> <span class="p">-</span> <span class="p">(</span><span class="n">angularFrequency</span> <span class="p">*</span> <span class="n">x</span><span class="p">);</span>
    <span class="k">value</span> <span class="p">+=</span> <span class="n">velocity</span> <span class="p">*</span> <span class="n">deltaTime</span> <span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
  <p>More info: <a href="https://gist.github.com/FleshMobProductions/7b523b81d7595e685410be11b24aac3f">https://gist.github.com/FleshMobProductions/7b523b81d7595e685410be11b24aac3f</a>.</p>
</blockquote>

<p>I can see both fixed timestep and variable timestep working well here.  Fixed timestep is a bit more effort:</p>
<ul>
  <li>Make a custom timestep loop.</li>
  <li>Handle interpolation so that things look smooth.</li>
</ul>

<p>But ultimately more robust:</p>
<ul>
  <li>Much better performance.</li>
  <li>Deterministic (at least on the same system).</li>
  <li>Immune to frame hitching (without this, you might feasibly break that 180 degree limit).</li>
</ul>

<p>A variable timestep solution is fine too though.  It’s more simple, and that has value.</p>

<p>For implementing a fixed timestep correctly, I once again point you to <a href="https://www.gafferongames.com/post/fix_your_timestep/">Gaffer On Games post</a> above, which explains in more detail than I would here.  The quick version:</p>
<ul>
  <li>Accumulate <code class="language-plaintext highlighter-rouge">dt</code> every frame.</li>
  <li>Once accumulated time is greater than the fixed timestep, update your things.</li>
  <li>Every frame, run one more <em>temporary</em> step and interpolate towards it depending on how much leftover time there is on the accumulator (<code class="language-plaintext highlighter-rouge">float lerpVal = accumulated / stepTime</code>).  Next frame, throw that result away and start again from the last <em>solid</em> step.</li>
</ul>

<p>That last point is obviously the main piece of complexity.  You want a system where you can run an additional temporary step, without modifying the data in such a way that you can’t go back to it for the next solid step.</p>

<h2 id="detecting-rest-states">Detecting Rest States</h2>
<p>Wouldn’t it be useful if we had some way of telling when the spring had stopped moving?  That way, we could easily chain movements together, or execute some code when it reached the target.  This sounds trivial, but there’s a couple gotchas I thought I’d mention.</p>
<video width="878" height="904" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_spring_restandmove.webm" type="video/webm" />
</video>

<ul>
  <li>You need to consider both closeness to target <em>and</em> velocity, to handle under-damped springs.</li>
  <li>In the Quaternion case, just checking equivalency won’t be enough as the same rotation can be represented in more than one way.  You want to compare angles, or something more representative of the actual orientation instead.
    <ul>
      <li>Unity’s standard <code class="language-plaintext highlighter-rouge">Quaternion.Angle()</code> may not be accurate enough.  You’ll probably want to seek alternatives with more precision.</li>
      <li><a href="https://discussions.unity.com/t/quaternion-toangleaxis-is-unprecise/248021">https://discussions.unity.com/t/quaternion-toangleaxis-is-unprecise/248021</a>.</li>
    </ul>
  </li>
  <li>At the extremes of bouncy or slow moving springs, you may need to get a little creative.  The best option here is really to just make the calculation as accurate as possible and expose the resting thresholds to the user (with sensible defaults).
    <ul>
      <li><a href="https://github.com/pmndrs/react-spring/blob/195c479b0360bf106edf16b3c602aa3b7c02c6ad/packages/core/src/SpringValue.ts#L279">Here’s</a> what they do in <code class="language-plaintext highlighter-rouge">react-spring</code>, nothing particularly exciting though.</li>
    </ul>
  </li>
  <li>Once the spring is considered resting, it might be a good idea to assign the current position to the target position, and zero out the velocity.
    <ul>
      <li>This ensures that both the rest position is perfect, and that the spring is truly resting, regardless of complications in the last point.</li>
    </ul>
  </li>
</ul>

<video width="1280" height="720" muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2025_spring_resting.webm" type="video/webm" />
</video>

<h2 id="performance">Performance</h2>
<p>The performance of even a naive implementation is good.  If you’re caching coefficients, each spring update is only a few instructions.  As we know, updating rotation is significantly more complicated, so that’s where a lot of the time goes.</p>

<p>These results should be taken with lots of salt, as this is version is hardly optimal and also the profiler overhead is likely playing a part:
<img src="/assets/2025_spring_profiler.png" alt="A profile of my base implementation." width="1155px" height="458px" />
<em>Profile of a naive implementation.  960 springs at ~1.72ms on a Ryzen 9 5900X.</em></p>

<blockquote>
  <p>You may notice that there’s a gap—the position/rotation/scale markers don’t add up to 1.72.  This ends up being the transform assignment.</p>
</blockquote>

<p>If you want more performance, before you go trying to find some crazy optimization for the quaternion lerps, consider a data-oriented approach first.  If you’re familiar with Unity, you might be imagining the implementation of a spring transform as a component that you just drop onto a game object.  Every frame, Unity will call <code class="language-plaintext highlighter-rouge">Update()</code> on all our spring components and they’ll run all their calculations and everything will be fine.</p>

<p>While this is definitely the pattern Unity encourages, having each spring effectively be its own simulation is leaving a lot on the table.  Really what you want is some kind of spring manager which iterates over all the springs and updates them in sequence.  This way, you can store the data for all those springs in a more data efficient way and take advantage of CPU cache properties, and if you really want to, SIMD instructions.</p>

<blockquote>
  <p>You can still have a component which you drop onto a game object, but rather than managing and updating itself, have it simply register a spring with some coefficient values with the manager.</p>
</blockquote>

<p>We see a &gt;2x speedup (but keep in mind the profiler overhead from before) just by putting things in arrays.  These results can be considered accurate—there should be close to zero profiler overhead:
<img src="/assets/2025_spring_batch_profiler.png" alt="A profile of my batch implementation." width="1250px" height="459px" />
<em>Batch implementation.  960 springs at ~0.71ms.</em></p>

<p>For greater performance, the next step would be to do the work in <a href="https://docs.unity3d.com/6000.1/Documentation/Manual/job-system-jobs.html">jobs</a> on other threads, ideally using the burst compiler.  I wouldn’t be surprised if you see even a 10x speedup just from the burst compiler—it does good things.</p>

<hr />

<h2 id="notes-from-the-future">Notes From the Future</h2>
<p><em>2026-06-26</em></p>

<p>I realised recently that it’s actually quite limiting to have the spring transform as a component itself, which automatically affects the object’s transform.</p>
<ul>
  <li>It stops you from layering in effects (e.g. one spring for camera movement, another for camera shake).</li>
  <li>It adds complexity with managing world/local spaces.  If you just have a spring in an arbitrary space, then the caller decides.</li>
  <li>It makes it harder to batch them together.</li>
  <li>It stops you from simulating to arbitrary points in the future.</li>
</ul>

<p>The fix is pretty straightforward:</p>
<ul>
  <li>Stop <code class="language-plaintext highlighter-rouge">SpringTransform</code> from inheriting from <code class="language-plaintext highlighter-rouge">MonoBehaviour</code>.</li>
  <li>Remove the <code class="language-plaintext highlighter-rouge">SetTargetLocal()</code>/<code class="language-plaintext highlighter-rouge">SetTargetWorld()</code> etc distinctions.  Now it’s just <code class="language-plaintext highlighter-rouge">SetTarget()</code>.</li>
  <li>Replace <code class="language-plaintext highlighter-rouge">Update()</code> with a function <code class="language-plaintext highlighter-rouge">Advance(float dt)</code>, which simply returns a <code class="language-plaintext highlighter-rouge">Frame</code>.  The caller can assign it to their transform (or whatever else!) as necessary.</li>
</ul>

<p>I’m kicking myself for not realising this sooner!  Egg on face!  I guess that’s what dogfooding is for :)</p>

<h2 id="resources">Resources</h2>
<ul>
  <li>My implementation: <a href="https://github.com/Toqozz/blog-code/blob/master/spring_transforms/Assets/SpringTransform.cs">https://github.com/Toqozz/blog-code/blob/master/spring_transforms/Assets/SpringTransform.cs</a></li>
  <li>Juckett’s original article: <a href="https://www.ryanjuckett.com/damped-springs/">https://www.ryanjuckett.com/damped-springs/</a></li>
  <li>Another spring math explainer: <a href="https://mathproofs.blogspot.com/2013/07/critically-damped-spring-smoothing.html">https://mathproofs.blogspot.com/2013/07/critically-damped-spring-smoothing.html</a></li>
  <li>Wrap your head around what the parameters do: <a href="https://blog.maximeheckel.com/posts/the-physics-behind-spring-animations/">https://blog.maximeheckel.com/posts/the-physics-behind-spring-animations/</a></li>
  <li>Parameter explainer (again): <a href="https://www.joshwcomeau.com/animation/a-friendly-introduction-to-spring-physics/">https://www.joshwcomeau.com/animation/a-friendly-introduction-to-spring-physics/</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Spring animation is a modern technique for animating stuff going between two points. Why not apply them to game object transforms?]]></summary></entry><entry><title type="html">The (not-so) Major Confusion of Row-major and Column-major Matrices</title><link href="https://toqoz.fyi/matrix-math-confusion.html" rel="alternate" type="text/html" title="The (not-so) Major Confusion of Row-major and Column-major Matrices" /><published>2024-12-02T00:00:00+00:00</published><updated>2024-12-02T00:00:00+00:00</updated><id>https://toqoz.fyi/matrix-math-confusion</id><content type="html" xml:base="https://toqoz.fyi/matrix-math-confusion.html"><![CDATA[<p>I’ve always had trouble remembering which order I should apply matrix multiplications in code, or which side the vector should go on for a matrix transform.  Obviously the order is important; you’ll get very strange results if you get it wrong, but the devious thing about matrix math is that you’ll often also get bizarre results if your math is just wrong in some <em>other</em> way.  It definitely pays to save yourself the future headache and get this right.  It’s also not that complicated when it comes down to it.</p>

<p>“You didn’t know all this already?”  No, I didn’t know all of this already.</p>

<h2 id="math-notation-and-prepost-multiply">Math Notation and Pre/Post-Multiply</h2>

<p>There are two main conventions for matrix-vector multiply, and those are <strong>pre-multiply</strong> and <strong>post-multiply</strong>.</p>

<p>Post-multiply is the most dominant by far, especially in mathematical texts and really everything other than computer graphics.  It represents the vector as a single column matrix on the right side.  Like this:
<img src="/assets/2024_latex_column_vecs.png" alt="Matrix vs Vector, with column vectors." width="300px" /></p>

<p>There is also pre-multiply, which represents the vector as a single row matrix on the <em>left</em> side.  That looks like this:
<img src="/assets/2024_latex_row_vecs.png" alt="Matrix vs Vector, with row vectors." width="400px" /></p>

<blockquote>
  <p>You can flip between the two matrix representations by <a href="https://en.wikipedia.org/wiki/Transpose">transposing</a> the matrix (reflecting it along its diagonal).</p>
</blockquote>

<p>There is no good reasoning for these competing styles.  People are just opinionated and one <em>feels</em> better than the other to some.  The pre-multiply convention appeared in the DirectX SDK at some point, so that’s probably why it’s more common in graphics.  Not necessarily more common than post-multiply, just more common than in other fields.</p>

<p>I’m not too interested in discussing what the actual operations are that happen when you pre-multiply or post-multiply.  The core question is: how do we know which is correct in a given situation?  The answer to this depends primarily on your math library, and the convention the author preferred.  This was pretty surprising to me, but is obvious in hindsight.</p>

<p>In most game math libraries, a <code class="language-plaintext highlighter-rouge">Matrix4</code> type is stored as 4 <code class="language-plaintext highlighter-rouge">Vector4</code> types:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">Mat4</span> <span class="p">{</span>
    <span class="n">Vector4</span> <span class="n">m_elem0</span><span class="p">;</span>
    <span class="n">Vector4</span> <span class="n">m_elem1</span><span class="p">;</span>
    <span class="n">Vector4</span> <span class="n">m_elem2</span><span class="p">;</span>
    <span class="n">Vector4</span> <span class="n">m_elem3</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>Now, referring back to matrix notations above; <strong>does each vector represent a single row, or a single column of the matrix</strong>?  This is the fundamental difference between libraries.  Depending on which is used, it is <em>implied</em> that the translation components live in either the last column (if vectors represent columns) or the last row (if vectors represent rows) of the matrix above, and this is what defines whether you should pre-multiply or post-multiply.</p>

<p>The technical terminology you’ll hear is <em>row vectors</em> if your vectors are rows and <em>column vectors</em> if your vectors are columns.</p>

<blockquote>
  <p>This detail is obscured by the fact that you’ll often hear it referred to as the library using either <em>column-major</em> or <em>row-major</em> matrices, but <em>row-major</em> and <em>column-major</em> are <em>also</em> computer science terms used to define how 2D arrays are stored in memory, which might lead to you scratching your head on <a href="https://en.wikipedia.org/wiki/Row-_and_column-major_order">this wikipedia page</a>.  This is a decision made by the programming language and totally irrelevant to what we’re talking about here.  Our matrices are not even using 2D arrays!  When using either convention, the byte storage ends up being identical due to the translation components being flipped, so the storage method really doesn’t tell us anything meaningful here.</p>
</blockquote>

<blockquote>
  <p><a href="https://stackoverflow.com/a/20438735">Some people</a> might try to tell you that when you hear “row-major” or “column-major” you should think purely of storage and nothing else, but the reality is that much of the world uses this terminology to discriminate between row vectors and column vectors.</p>
</blockquote>

<h2 id="examples">Examples</h2>

<p>Let’s look at some math libraries and see if we can figure out which convention we should be using.</p>

<h3 id="vectormath"><code class="language-plaintext highlighter-rouge">VectorMath</code></h3>

<p>The aptly named <a href="https://github.com/glampert/vectormath"><code class="language-plaintext highlighter-rouge">VectorMath</code></a> is a vector/matrix math library that was open sourced by Sony around 2007.  Its matrix class is defined in the following way:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// vectormath.hpp</span>
<span class="n">VECTORMATH_ALIGNED_TYPE_PRE</span> <span class="k">class</span> <span class="nc">Matrix4</span>
<span class="p">{</span>
    <span class="n">Vector4</span> <span class="n">mCol0</span><span class="p">;</span>
    <span class="n">Vector4</span> <span class="n">mCol1</span><span class="p">;</span>
    <span class="n">Vector4</span> <span class="n">mCol2</span><span class="p">;</span>
    <span class="n">Vector4</span> <span class="n">mCol3</span><span class="p">;</span>
    <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We can easily guess that each element is intended to represent a column in a matrix.  But where are the translation components stored?</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// matrix.hpp</span>
<span class="kr">inline</span> <span class="k">const</span> <span class="n">Vector3</span> <span class="n">Matrix4</span><span class="o">::</span><span class="n">getTranslation</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">mCol3</span><span class="p">.</span><span class="n">getXYZ</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Cool, this totally aligns with our column vector math notation above.  So we’d expect to use post-multiplication here.  Let’s take a look at the operator overloads:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// matrix.hpp</span>
<span class="kr">inline</span> <span class="k">const</span> <span class="n">Vector4</span> <span class="n">Matrix4</span><span class="o">::</span><span class="k">operator</span> <span class="o">*</span> <span class="p">(</span><span class="k">const</span> <span class="n">Vector4</span> <span class="o">&amp;</span> <span class="n">vec</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">Vector4</span><span class="p">(((((</span><span class="n">mCol0</span><span class="p">.</span><span class="n">getX</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getX</span><span class="p">())</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol1</span><span class="p">.</span><span class="n">getX</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getY</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol2</span><span class="p">.</span><span class="n">getX</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getZ</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol3</span><span class="p">.</span><span class="n">getX</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getW</span><span class="p">())),</span>
                   <span class="p">((((</span><span class="n">mCol0</span><span class="p">.</span><span class="n">getY</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getX</span><span class="p">())</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol1</span><span class="p">.</span><span class="n">getY</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getY</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol2</span><span class="p">.</span><span class="n">getY</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getZ</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol3</span><span class="p">.</span><span class="n">getY</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getW</span><span class="p">())),</span>
                   <span class="p">((((</span><span class="n">mCol0</span><span class="p">.</span><span class="n">getZ</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getX</span><span class="p">())</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol1</span><span class="p">.</span><span class="n">getZ</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getY</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol2</span><span class="p">.</span><span class="n">getZ</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getZ</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol3</span><span class="p">.</span><span class="n">getZ</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getW</span><span class="p">())),</span>
                   <span class="p">((((</span><span class="n">mCol0</span><span class="p">.</span><span class="n">getW</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getX</span><span class="p">())</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol1</span><span class="p">.</span><span class="n">getW</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getY</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol2</span><span class="p">.</span><span class="n">getW</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getZ</span><span class="p">()))</span> <span class="o">+</span> <span class="p">(</span><span class="n">mCol3</span><span class="p">.</span><span class="n">getW</span><span class="p">()</span> <span class="o">*</span> <span class="n">vec</span><span class="p">.</span><span class="n">getW</span><span class="p">())));</span>
<span class="p">}</span>

<span class="c1">// vector.hpp</span>
<span class="c1">// &lt;none&gt;</span>
</code></pre></div></div>
<p>So there’s not even an operator overload for pre-multiplication.  The library is clearly telling us that it uses column vectors and post-multiplication, which matches with the standard mathematical notation.  If we wanted to be super sure we could also have a proper look at that multiplication operator and make sure that it’s doing what we expect.</p>

<p>This is an example of an opinionated library that really wants there to be a single correct representation for a transformation matrix, and that’s with the translation components in the final column.</p>

<h3 id="zmath"><code class="language-plaintext highlighter-rouge">zmath</code></h3>

<p>Now let’s take a look at <a href="https://github.com/PiergiorgioZagaria/zmath"><code class="language-plaintext highlighter-rouge">zmath</code></a>, which is also a SIMD math library for game developers, for the <a href="https://ziglang.org/">Zig</a> programming language.</p>

<p>Most people aren’t familiar with Zig, so I’ll try to do a bit more explaining here.</p>

<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// zmath.h</span>
<span class="c">// Fundamental types</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">F32x4</span> <span class="o">=</span> <span class="nb">@Vector</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="kt">f32</span><span class="p">);</span>
<span class="o">...</span>

<span class="c">// "Higher-level" aliases</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">Vec</span> <span class="o">=</span> <span class="n">F32x4</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">Mat</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="n">F32x4</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">Quat</span> <span class="o">=</span> <span class="n">F32x4</span><span class="p">;</span>
</code></pre></div></div>

<p>We can see that <code class="language-plaintext highlighter-rouge">zmath</code>s matrix type is also backed by an array of 4 <code class="language-plaintext highlighter-rouge">F32x4</code>s, which themselves are a group of 4 floats.  In Zig, types created with <code class="language-plaintext highlighter-rouge">@Vector()</code> are operated on in parallel, using SIMD instructions if possible: <a href="https://ziglang.org/documentation/master/#toc-Vectors">https://ziglang.org/documentation/master/#toc-Vectors</a>.</p>

<p>Unlike the Sony library, the fields aren’t named, so we don’t get any clues there.  They could represent either rows or columns.</p>

<p>Let’s look at how a translation matrix is created:</p>
<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="n">translation</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="kt">f32</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="kt">f32</span><span class="p">,</span> <span class="n">z</span><span class="p">:</span> <span class="kt">f32</span><span class="p">)</span> <span class="n">Mat</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">.</span><span class="p">{</span>
        <span class="n">f32x4</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
        <span class="n">f32x4</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
        <span class="n">f32x4</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
        <span class="n">f32x4</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Looks like <code class="language-plaintext highlighter-rouge">zmath</code> stores the translation in the final element, just like Sony’s library.  But should we use pre or post-multiplication?  Let’s have a look at the matrix multiplication:</p>
<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="n">mul</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">anytype</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="n">mulRetType</span><span class="p">(</span><span class="nb">@TypeOf</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="nb">@TypeOf</span><span class="p">(</span><span class="n">b</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">Ta</span> <span class="o">=</span> <span class="nb">@TypeOf</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">Tb</span> <span class="o">=</span> <span class="nb">@TypeOf</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="c">// other types stripped ...</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">Ta</span> <span class="o">==</span> <span class="n">Vec</span> <span class="k">and</span> <span class="n">Tb</span> <span class="o">==</span> <span class="n">Mat</span><span class="p">)</span> <span class="p">{</span>  <span class="c">// vector vs matrix "overload".</span>
        <span class="k">return</span> <span class="n">vecMulMat</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">Ta</span> <span class="o">==</span> <span class="n">Mat</span> <span class="k">and</span> <span class="n">Tb</span> <span class="o">==</span> <span class="n">Vec</span><span class="p">)</span> <span class="p">{</span>   <span class="c">// matrix vs vector "overload".</span>
        <span class="k">return</span> <span class="n">matMulVec</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nb">@compileError</span><span class="p">(</span><span class="s">"zmath.mul() not implemented for types: "</span> <span class="o">++</span> <span class="nb">@typeName</span><span class="p">(</span><span class="n">Ta</span><span class="p">)</span> <span class="o">++</span> <span class="s">", "</span> <span class="o">++</span> <span class="nb">@typeName</span><span class="p">(</span><span class="n">Tb</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
  <p>This might be a bit Ziggy and hard to follow.  Zig doesn’t have operator overloading, but it does have very good type information and compile time abilities.  Multiple independent versions of this function will get compiled, according to the types passed in.</p>
</blockquote>

<p>Critically, <code class="language-plaintext highlighter-rouge">zmath</code> lets us do <em>either</em> pre-multiply or post-multiply.  Unfortunately, now we need to look at which version is correct, given that our translation components are in the last element:</p>
<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="n">vecMulMat</span><span class="p">(</span><span class="n">v</span><span class="p">:</span> <span class="n">Vec</span><span class="p">,</span> <span class="n">m</span><span class="p">:</span> <span class="n">Mat</span><span class="p">)</span> <span class="n">Vec</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">vx</span> <span class="o">=</span> <span class="nb">@shuffle</span><span class="p">(</span><span class="kt">f32</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="k">undefined</span><span class="p">,</span> <span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="kt">i32</span><span class="p">{</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span> <span class="p">});</span>
    <span class="k">var</span> <span class="n">vy</span> <span class="o">=</span> <span class="nb">@shuffle</span><span class="p">(</span><span class="kt">f32</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="k">undefined</span><span class="p">,</span> <span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="kt">i32</span><span class="p">{</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span> <span class="p">});</span>
    <span class="k">var</span> <span class="n">vz</span> <span class="o">=</span> <span class="nb">@shuffle</span><span class="p">(</span><span class="kt">f32</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="k">undefined</span><span class="p">,</span> <span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="kt">i32</span><span class="p">{</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span> <span class="p">});</span>
    <span class="k">var</span> <span class="n">vw</span> <span class="o">=</span> <span class="nb">@shuffle</span><span class="p">(</span><span class="kt">f32</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="k">undefined</span><span class="p">,</span> <span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="kt">i32</span><span class="p">{</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span> <span class="p">});</span>
    <span class="k">return</span> <span class="n">vx</span> <span class="o">*</span> <span class="n">m</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="n">vy</span> <span class="o">*</span> <span class="n">m</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">vz</span> <span class="o">*</span> <span class="n">m</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="n">vw</span> <span class="o">*</span> <span class="n">m</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="n">matMulVec</span><span class="p">(</span><span class="n">m</span><span class="p">:</span> <span class="n">Mat</span><span class="p">,</span> <span class="n">v</span><span class="p">:</span> <span class="n">Vec</span><span class="p">)</span> <span class="n">Vec</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">.</span><span class="p">{</span> <span class="n">dot4</span><span class="p">(</span><span class="n">m</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">v</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span> <span class="n">dot4</span><span class="p">(</span><span class="n">m</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">v</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span> <span class="n">dot4</span><span class="p">(</span><span class="n">m</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">v</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span> <span class="n">dot4</span><span class="p">(</span><span class="n">m</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">v</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If you’re like me, you won’t intuitively know which of these is right, and you’ll just need to do it the hard way by manually verifying, or writing some test code:</p>
<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">translation</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="nf">translation</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">99.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">);</span>
<span class="k">const</span> <span class="n">point</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="nf">f32x4</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">const</span> <span class="n">post_mult</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="nf">mul</span><span class="p">(</span><span class="n">translation</span><span class="p">,</span> <span class="n">point</span><span class="p">);</span>
<span class="k">const</span> <span class="n">pre_mult</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="nf">mul</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="n">translation</span><span class="p">);</span>
<span class="n">std</span><span class="p">.</span><span class="py">debug</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"post_mult: {any}</span><span class="se">\n</span><span class="s">pre_mult: {any}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span> <span class="n">post_mult</span><span class="p">,</span> <span class="n">pre_mult</span> <span class="p">});</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./out
post_mult: { 0.0, 0.0, 0.0, 1.0 }
pre_mult: { 0.0, 99.0, 0.0, 1.0 }
</code></pre></div></div>

<p>Ok, so post-multiplication did nothing and pre-multiplication produced the correct result, so <code class="language-plaintext highlighter-rouge">zmath</code> must use row vectors.  What if we manually make a matrix and store translation at the end of each element instead?</p>

<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Translation stored on the "right" instead of the "bottom".</span>
<span class="k">const</span> <span class="n">translation</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="py">Mat</span><span class="p">{</span>
    <span class="n">zm</span><span class="p">.</span><span class="nf">f32x4</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
    <span class="n">zm</span><span class="p">.</span><span class="nf">f32x4</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">99.0</span><span class="p">),</span>
    <span class="n">zm</span><span class="p">.</span><span class="nf">f32x4</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
    <span class="n">zm</span><span class="p">.</span><span class="nf">f32x4</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span>
<span class="p">};</span>
<span class="k">const</span> <span class="n">point</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="nf">f32x4</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">const</span> <span class="n">post_mult</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="nf">mul</span><span class="p">(</span><span class="n">translation</span><span class="p">,</span> <span class="n">point</span><span class="p">);</span>
<span class="k">const</span> <span class="n">pre_mult</span> <span class="o">=</span> <span class="n">zm</span><span class="p">.</span><span class="nf">mul</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="n">translation</span><span class="p">);</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./out
post_mult: { 0.0, 99.0, 0.0, 1.0 }
pre_mult: { 0.0, 0.0, 0.0, 1.0 }
</code></pre></div></div>

<p>Now post-multiplication is correct.  So I guess this is just <code class="language-plaintext highlighter-rouge">zmath</code> supporting both row and column vectors and letting the user decide, even though post-multiplication won’t work with a matrix created via <code class="language-plaintext highlighter-rouge">zm.translation()</code>.</p>

<p>I’m not a fan of this kind of implementation, especially given that this library is advertised as a library for game developers.</p>

<p>Shader languages also commonly let you multiply things in any order, which is also a source of confusion.  If you upload your <code class="language-plaintext highlighter-rouge">zmath</code> matrices to the GPU and run your WGSL shader on them, you’ll have to flip to using post-multiplication instead, unless you transpose your matrices before upload.  This is because WGSL treats matrices as <a href="https://www.w3.org/TR/WGSL/#matrix-types">column vectors</a>:</p>
<blockquote>
  <p>A matrix is a grouped sequence of 2, 3, or 4 floating point vectors.
The key use case for a matrix is to embody a linear transformation. In this interpretation, the vectors of a matrix are treated as <em>column vectors</em>.</p>
</blockquote>

<p>The fact that you could have a shader that uses pre-multiply for some matrices and post-multiply for others is wild to me, but ultimately make sense.</p>

<p>So there you have it.  Two libraries using identical memory representations but different conventions around multiplying matrices.  All that matters is what the math library actually does.  In hindsight it’s obvious.</p>

<h2 id="what-about-matrix-multiplication-order">What about matrix multiplication order?</h2>
<p>One good thing is that once you’ve figured out pre or post-multiply, the rules are the same for matrices, unless you’re using the math library of a madman…</p>

<p>If using pre-multiplication (row vectors), then matrices should be combined left-to-right:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// A matrix that takes you from local to world space, then from world space to clip space.</span>
<span class="n">Matrix</span> <span class="n">object_to_clip</span> <span class="o">=</span> <span class="n">object_to_world</span> <span class="o">*</span> <span class="n">world_to_clip</span><span class="p">;</span>
<span class="n">Vector4</span> <span class="n">result</span> <span class="o">=</span> <span class="n">local_pos</span> <span class="o">*</span> <span class="n">object_to_clip</span><span class="p">;</span>
</code></pre></div></div>
<p>If using post-multiplication (column vectors), then matrices should be combined right-to-left:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// A matrix that takes you from local to world space, then from world space to clip space.</span>
<span class="n">Matrix</span> <span class="n">object_to_clip</span> <span class="o">=</span> <span class="n">world_to_clip</span> <span class="o">*</span> <span class="n">object_to_world</span><span class="p">;</span>
<span class="n">Vector4</span> <span class="n">result</span> <span class="o">=</span> <span class="n">object_to_clip</span> <span class="o">*</span> <span class="n">local_pos</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="one-more-example">One More Example</h2>
<p>To make things abundantly clear, here’s how a typical vertex shader in GLSL (which uses column vectors – post-multiplication) would calculate <code class="language-plaintext highlighter-rouge">gl_Position</code>, given raw matrices from <code class="language-plaintext highlighter-rouge">VectorMath</code> that have not been transposed:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">in</span> <span class="kt">vec3</span> <span class="n">inPosition</span><span class="p">;</span>
<span class="k">uniform</span> <span class="kt">mat4</span> <span class="n">objectMatrix</span><span class="p">;</span>
<span class="k">uniform</span> <span class="kt">mat4</span> <span class="n">projectionMatrix</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="nb">gl_Position</span> <span class="o">=</span> <span class="n">projectionMatrix</span> <span class="o">*</span> <span class="n">objectMatrix</span> <span class="o">*</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">inPosition</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here’s what it would look like if you uploaded the raw matrices from <code class="language-plaintext highlighter-rouge">zmath</code> without transposition:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">in</span> <span class="kt">vec3</span> <span class="n">inPosition</span><span class="p">;</span>
<span class="k">uniform</span> <span class="kt">mat4</span> <span class="n">objectMatrix</span><span class="p">;</span>
<span class="k">uniform</span> <span class="kt">mat4</span> <span class="n">projectionMatrix</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="nb">gl_Position</span> <span class="o">=</span> <span class="n">projectionMatrix</span> <span class="o">*</span> <span class="n">objectMatrix</span> <span class="o">*</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">inPosition</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>They’re the same!  Of course they are – the underlying memory representation is the same in both libraries.  The difference is that we’re switching to GLSL’s math library, which uses column vectors, and interprets our matrices as columns of vectors.</p>

<p>If this all makes sense to you then you probably understand the concept enough to never be bothered by it again.</p>

<hr />

<h2 id="useful-linksresources">Useful Links/Resources</h2>
<ul>
  <li>A catalogue of the differences between shading languages, including information about their matrix representations: <a href="https://gist.github.com/teoxoy/936891c16c2a3d1c3c5e7204ac6cd76c#21-storage-address-space">https://gist.github.com/teoxoy/936891c16c2a3d1c3c5e7204ac6cd76c#21-storage-address-space</a></li>
  <li>Another good explanation on the Khronos forums: <a href="https://community.khronos.org/t/row-major-vs-column-major-in-4-1/64122">https://community.khronos.org/t/row-major-vs-column-major-in-4-1/64122</a></li>
  <li>Matrix multiply confusion, and an OpenGL author dispelling it: <a href="https://steve.hollasch.net/cgindex/math/matrix/column-vec.html">https://steve.hollasch.net/cgindex/math/matrix/column-vec.html</a></li>
  <li>A more detailed explainer that goes into the math, as well as coordinate spaces: <a href="https://seanmiddleditch.github.io/matrices-handedness-pre-and-post-multiplication-row-vs-column-major-and-notations/">https://seanmiddleditch.github.io/matrices-handedness-pre-and-post-multiplication-row-vs-column-major-and-notations/</a></li>
  <li>Another explainer: <a href="https://austinmorlan.com/posts/opengl_matrices/">https://austinmorlan.com/posts/opengl_matrices/</a></li>
  <li>Pretty useful overal resource on OpenGL transformations: <a href="https://www.opengl.org/archives/resources/faq/technical/transformations.htm">https://www.opengl.org/archives/resources/faq/technical/transformations.htm</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[I’ve always had trouble remembering which order I should apply matrix multiplications in code, or which side the vector should go on for a matrix transform. Obviously the order is important; you’ll get very strange results if you get it wrong, but the devious thing about matrix math is that you’ll often also get bizarre results if your math is just wrong in some other way. It definitely pays to save yourself the future headache and get this right. It’s also not that complicated when it comes down to it.]]></summary></entry><entry><title type="html">Recording Gameplay: The Right Way</title><link href="https://toqoz.fyi/game-video-recording.html" rel="alternate" type="text/html" title="Recording Gameplay: The Right Way" /><published>2024-03-04T00:00:00+00:00</published><updated>2024-03-04T00:00:00+00:00</updated><id>https://toqoz.fyi/game-video-recording</id><content type="html" xml:base="https://toqoz.fyi/game-video-recording.html"><![CDATA[<video muted="true" autoplay="autoplay" loop="loop" controls="true">
    <source src="/assets/2024_recording_recording.webm" type="video/webm" />
</video>
<p><em>A recording of a recording.</em></p>

<p>Recently I was struggling to record some demo webms on macOS – basic stuff, like moving a sprite back and forth with some logic.  For some reason, I couldn’t get things to move smoothly, without stuttering, so all my recordings were messed up.  This issue itself is bizarre (this should be possible, and trust me, I tried), but that’s not the point of this post.  In reality, you’re never going to get a truly high quality recording from a screen capture anyway.  Games are constantly fighting the OS for processor time, and even well-made games have frame timing issues.  If you’re not pumping out a new frame exactly every 16.6667ms (or whatever your desired framerate is), then your recording is flawed.</p>

<p>The proper solution for recording video is to run your game with a fixed delta time (whatever you want your video framerate to be recorded at) and just spit out images from the game every frame, and then compile those into a video.  You’ll get a perfectly smooth recording of your game, regardless of framerate issues.</p>

<p>This is actually how a lot of recording works for video game frag movies.  <a href="https://lawena.github.io/">Lawena</a> is a tool for source engine games which lets you crank the graphics settings of your game and record at any framerate you want.  It’s really important when you’re compiling a video like that to have high quality source recordings, sometimes at over 240FPS (for slow-mo effects).</p>

<p>Additionally, when you record directly from the frame buffer, you have control over <em>when</em> in the frame your recording should take place.  For example, if you wanted to do some custom post processing, you could copy your frame before post processing passes are applied.</p>

<blockquote>
  <p><em>Why is my recording flawed if my framerate is slightly off?</em>
<img src="/assets/2024_recording_frame_timing.png" alt="Recording frames vs game frames." />
Here’s a comparison of game frames (white), to recording frames (red diamonds).  Assuming the recording can keep up, when you’re recording your screen, you can assume that the recording program is capturing a frame at every interval (60FPS – ~16.67ms in this case).  Remember that when the recorder captures a frame, it’s actually capturing the last displayed frame, which could have been who knows how long ago (the dotted red lines at the top signify how much time we’re missing at each recording sample).  The result of this is that if you were to step through the frames in your video file, you’d trivially notice that each frame doesn’t have the same amount of movement, despite the recording being at a constant framerate.</p>

  <p>I’ve also seen people, when trying to make recordings where the game won’t make framerate, slow the game down to half speed, do the recording, and then speed it up 2x in editing software so that things look smooth.  This is obviously flawed if you understand the above.  Please don’t do this.  If you absolutely must, then record in the highest framerate you possibly can so that the ‘lost time’ is minimized.</p>
</blockquote>

<h3 id="the-naive-solution">The Naive Solution</h3>
<p>The naive solution is obvious.  All you really need is a way to grab the output pixels from the framebuffer, and write those pixels to some kind of image format.  From there you can use other tools like <code class="language-plaintext highlighter-rouge">ffmpeg</code> to compile the frames into video.</p>

<p>Accessing the framebuffer is not always easy if a suitable abstraction is not provided, but most major engines should have <em>something</em>.  I’ve been meaning to try out <a href="https://wgpu.rs/">wgpu</a> some more, so I just built something from those primitives.  Here’s the basics of how I’m grabbing the output frame pixels and dumping them to a <code class="language-plaintext highlighter-rouge">.png</code>, for those curious:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// `end_frame` is a function on a `Recorder` struct which keeps track of recording state etc.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">end_frame</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">device</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">wgpu</span><span class="p">::</span><span class="n">Device</span><span class="p">,</span> <span class="n">encoder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">wgpu</span><span class="p">::</span><span class="n">CommandEncoder</span><span class="p">,</span> <span class="n">frame_texture</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">wgpu</span><span class="p">::</span><span class="n">Texture</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="o">!</span><span class="k">self</span><span class="py">.recording</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Copy our screen tetxure data into a buffer.</span>
    <span class="n">encoder</span><span class="nf">.copy_texture_to_buffer</span><span class="p">(</span>
        <span class="nn">wgpu</span><span class="p">::</span><span class="n">ImageCopyTexture</span> <span class="p">{</span>
            <span class="n">aspect</span><span class="p">:</span> <span class="nn">wgpu</span><span class="p">::</span><span class="nn">TextureAspect</span><span class="p">::</span><span class="n">All</span><span class="p">,</span>
            <span class="n">texture</span><span class="p">:</span> <span class="n">frame_texture</span><span class="p">,</span>
            <span class="n">mip_level</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">origin</span><span class="p">:</span> <span class="nn">wgpu</span><span class="p">::</span><span class="nn">Origin3d</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="nn">wgpu</span><span class="p">::</span><span class="n">ImageCopyBuffer</span> <span class="p">{</span>
            <span class="n">buffer</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">self</span><span class="py">.buffer</span><span class="p">,</span>
            <span class="n">layout</span><span class="p">:</span> <span class="nn">wgpu</span><span class="p">::</span><span class="n">ImageDataLayout</span> <span class="p">{</span>
                <span class="n">offset</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
                <span class="n">bytes_per_row</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="mi">4</span> <span class="o">*</span> <span class="k">self</span><span class="py">.width</span><span class="p">),</span>    <span class="c1">// 4 bytes (bgra) * width</span>
                <span class="n">rows_per_image</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="k">self</span><span class="py">.height</span><span class="p">),</span>
            <span class="p">},</span>
        <span class="p">},</span>
        <span class="n">frame_texture</span><span class="nf">.size</span><span class="p">(),</span>
    <span class="p">);</span>

    <span class="c1">// Map the buffer so that we can read it on the CPU side.</span>
    <span class="c1">// It's an async operation in wgpu, so just block for simplicity.</span>
    <span class="k">let</span> <span class="n">buffer_slice</span> <span class="o">=</span> <span class="k">self</span><span class="py">.buffer</span><span class="nf">.slice</span><span class="p">(</span><span class="o">..</span><span class="p">);</span>
    <span class="nn">pollster</span><span class="p">::</span><span class="nf">block_on</span><span class="p">(</span><span class="k">async</span> <span class="p">{</span>
        <span class="k">let</span> <span class="p">(</span><span class="n">tx</span><span class="p">,</span> <span class="n">rx</span><span class="p">)</span> <span class="o">=</span> <span class="nn">futures_intrusive</span><span class="p">::</span><span class="nn">channel</span><span class="p">::</span><span class="nn">shared</span><span class="p">::</span><span class="nf">oneshot_channel</span><span class="p">();</span>
        <span class="n">buffer_slice</span><span class="nf">.map_async</span><span class="p">(</span><span class="nn">wgpu</span><span class="p">::</span><span class="nn">MapMode</span><span class="p">::</span><span class="n">Read</span><span class="p">,</span> <span class="k">move</span> <span class="p">|</span><span class="n">result</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">tx</span><span class="nf">.send</span><span class="p">(</span><span class="n">result</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="p">});</span>

        <span class="n">device</span><span class="nf">.poll</span><span class="p">(</span><span class="nn">wgpu</span><span class="p">::</span><span class="nn">Maintain</span><span class="p">::</span><span class="n">Wait</span><span class="p">);</span>
        <span class="n">rx</span><span class="nf">.receive</span><span class="p">()</span><span class="k">.await</span><span class="nf">.unwrap</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="p">});</span>

    <span class="c1">// Finally write out our png.</span>
    <span class="p">{</span>
        <span class="k">let</span> <span class="n">filename</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"frame_{:06}.png"</span><span class="p">,</span> <span class="k">self</span><span class="py">.frame_number</span><span class="p">);</span>
        <span class="k">self</span><span class="py">.frame_number</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="c1">// `data` gets dropped at the end of this scope, so that we can unmap the buffer.</span>
        <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="n">buffer_slice</span><span class="nf">.get_mapped_range</span><span class="p">();</span>
        <span class="nn">lodepng</span><span class="p">::</span><span class="nf">encode_file</span><span class="p">(</span>
            <span class="o">&amp;</span><span class="n">filename</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">data</span><span class="p">,</span>
            <span class="k">self</span><span class="py">.width</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">,</span>
            <span class="k">self</span><span class="py">.height</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">,</span>
            <span class="nn">lodepng</span><span class="p">::</span><span class="nn">ColorType</span><span class="p">::</span><span class="n">BGRA</span><span class="p">,</span>
            <span class="mi">8</span><span class="p">,</span>
        <span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Failed to write PNG."</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">self</span><span class="py">.buffer</span><span class="nf">.unmap</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>It looks like Unity has the <a href="https://docs.unity3d.com/ScriptReference/ScreenCapture.html">ScreenCapture</a> class for getting pixels, though I suspect there’s better ways.</p>
</blockquote>

<p>You’ll also want to artificially set your game’s <code class="language-plaintext highlighter-rouge">dt</code> to whatever the recording framerate is, so that your game <em>appears</em> to be running at that framerate in the recording:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">update</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">real_dt</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">dt</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="py">.recorder</span><span class="nf">.is_recording</span><span class="p">()</span> <span class="p">{</span>
            <span class="mf">1.0</span> <span class="o">/</span> <span class="k">self</span><span class="py">.recorder</span><span class="nf">.recording_framerate</span><span class="p">()</span> <span class="k">as</span> <span class="nb">f32</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">real_dt</span>
        <span class="p">}</span>
    <span class="p">};</span>

    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You should also consider limiting the game’s framerate to match this so that it’s actually playable, but note that framerate limiting should not generally be considered a substitute for setting the <code class="language-plaintext highlighter-rouge">dt</code> if you want a perfect recording.</p>

<p>When you’re done, you can use <code class="language-plaintext highlighter-rouge">ffmpeg</code> to compile the PNGs into whatever format you want:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-framerate</span> 60 <span class="nt">-i</span> frame_%06d.png <span class="nt">-c</span>:v libx264 <span class="nt">-profile</span>:v high <span class="nt">-crf</span> 20 <span class="nt">-pix_fmt</span> yuv420p output.mp4
</code></pre></div></div>

<p>This behaves fine, but leaves a lot to be desired.  The first issue is that writing PNGs is <em>slow</em>.  It took me 24ms~ on average to write a PNG to disk in a release build, and debug builds are <em>much</em> slower.  This alone is enough to stop us from recording and playing at the same time at 60FPS (although the recording itself will still look smooth for reasons that should be clear by now).  The second issue is that this approach quickly eats drive space for longer recordings, especially with complex scenes where PNG’s compression isn’t doing as much.</p>

<p><img src="/assets/2024_png_profile.png" alt="PNG profile capture" />
<em>Profile capture taken with <a href="https://github.com/wolfpld/tracy">Tracy Profiler</a>.</em></p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">lodepng</code> is not the best we can do.  <a href="https://github.com/richgel999/fpng">There are faster PNG encoders</a>.  We could also turn down the compression for a faster write, or <a href="https://blender.stackexchange.com/questions/148231/what-image-format-encodes-the-fastest-or-at-least-faster-png-is-too-slow">use a different format altogether</a>.</p>
</blockquote>

<blockquote>
  <p>If you’re wondering how much of this time is the OS writing the file to disk: it’s about a millisecond faster on average when we encode into memory.</p>
</blockquote>

<h3 id="encoding-with-ffmpeg-directly">Encoding With <code class="language-plaintext highlighter-rouge">ffmpeg</code> Directly</h3>
<p>It turns out that <code class="language-plaintext highlighter-rouge">ffmpeg</code> actually accepts raw frames (of course).  So you can simplify this whole pipeline by just sending the raw data to <code class="language-plaintext highlighter-rouge">ffmpeg</code> directly and letting it handle it.  Here’s roughly the process I’m using:</p>
<ul>
  <li>When recording starts:
    <ul>
      <li>Launch an <code class="language-plaintext highlighter-rouge">ffmpeg</code> process in a separate thread.  Spin in that thread, writing all the received frames into the process’ <code class="language-plaintext highlighter-rouge">stdin</code>.</li>
      <li>Enable ‘recording mode’, which sets the game’s <code class="language-plaintext highlighter-rouge">dt</code> to <code class="language-plaintext highlighter-rouge">1.0/60.0</code> (my target recording framerate) and limits the game’s framerate so that things are playable.</li>
    </ul>
  </li>
  <li>At the end of every frame, grab the frame output and send the raw bytes through to the <code class="language-plaintext highlighter-rouge">ffmpeg</code> thread.  You’ll want to buffer (i.e. copy) the output here, since <code class="language-plaintext highlighter-rouge">ffmpeg</code> might take longer than a frame to encode your video frame.</li>
  <li>When recording stops:
    <ul>
      <li>Close <code class="language-plaintext highlighter-rouge">stdin</code> on the <code class="language-plaintext highlighter-rouge">ffmpeg</code> thread and <code class="language-plaintext highlighter-rouge">.join()</code> from the main thread (this is blocking until <code class="language-plaintext highlighter-rouge">ffmpeg</code> finishes writing the file).</li>
      <li>Disable recording mode.</li>
    </ul>
  </li>
</ul>

<p>The <code class="language-plaintext highlighter-rouge">ffmpeg</code> command you’ll want for raw frames is something like the following:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg
    <span class="nt">-r</span> 60               <span class="c"># your framerate</span>
    <span class="nt">-f</span> rawvideo
    <span class="nt">-vcodec</span> rawvideo
    <span class="nt">-pix_fmt</span> bgra       <span class="c"># your pixel format</span>
    <span class="nt">-s</span> 1280x720         <span class="c"># your window size</span>
    <span class="nt">-i</span> -                <span class="c"># input as stdin</span>
    <span class="nt">-c</span>:v libvpx-vp9     <span class="c"># standard webm codec</span>
    <span class="nt">-pix_fmt</span> yuv420p    <span class="c"># standard pixel format</span>
    output.webm
</code></pre></div></div>

<p>There’s really not much to it.  In fact, it almost doesn’t feel like it’s worth writing about.  But before I got this working I didn’t really have a concrete idea about how this all should work, so I figured it’s worth sharing.</p>

<blockquote>
  <p>You’ll notice that I’m sidestepping my ‘first’ issue with writing the PNGs: encoding with <code class="language-plaintext highlighter-rouge">ffmpeg</code> is also slow, and we can’t do it on the main thread.  You could also encode your PNGs in a separate thread to the same effect, or just use a format that’s faster to encode.  I mostly just wanted to complain and show off the Tracy Profiler.  I guess I’m also more OK with accepting a separate thread for handling the whole recording/encoding pipeline, rather than just for writing out a buffer of images.</p>
</blockquote>

<hr />

<p>Here’s some code for how I’m doing my recordings.  The main thing I’m yet to do is wait for the <code class="language-plaintext highlighter-rouge">ffmpeg</code> process to finish in a non-blocking way, and display the progress (displaying the frames waiting for encoding would be enough).  This code is truncated to make it easier to read for the purposes of the post; you can find the full source on <a href="https://github.com/Toqozz/blog-code/blob/master/recording/recorder.rs">Github</a>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// recorder.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">begin_record</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">self</span><span class="py">.recording</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>       

    <span class="nd">assert!</span><span class="p">(</span><span class="k">self</span><span class="py">.ffmpeg</span><span class="nf">.is_none</span><span class="p">());</span>
    <span class="k">let</span> <span class="n">ffmpeg</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="p">(</span><span class="n">send</span><span class="p">,</span> <span class="n">recv</span><span class="p">)</span> <span class="o">=</span> <span class="nn">mpsc</span><span class="p">::</span><span class="nn">channel</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;&gt;</span><span class="p">();</span>
        <span class="k">let</span> <span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="k">self</span><span class="py">.width</span><span class="p">,</span> <span class="k">self</span><span class="py">.height</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">handle</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">move</span> <span class="p">||</span> <span class="p">{</span>
            <span class="c1">// do ffmpeg stuff</span>
            <span class="k">let</span> <span class="n">size</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"{}x{}"</span><span class="p">,</span> <span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">ffmpeg_cmd</span> <span class="o">=</span> <span class="s">"ffmpeg"</span><span class="p">;</span>
            <span class="k">let</span> <span class="n">args</span> <span class="o">=</span> <span class="p">[</span>
                <span class="s">"-r"</span><span class="p">,</span> <span class="s">"60"</span><span class="p">,</span>                     <span class="c1">// Input frame rate.</span>
                <span class="s">"-f"</span><span class="p">,</span> <span class="s">"rawvideo"</span><span class="p">,</span>               <span class="c1">// Input format.</span>
                <span class="s">"-vcodec"</span><span class="p">,</span> <span class="s">"rawvideo"</span><span class="p">,</span>
                <span class="s">"-pix_fmt"</span><span class="p">,</span> <span class="s">"bgra"</span><span class="p">,</span>
                <span class="s">"-s"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">size</span><span class="p">,</span>
                <span class="s">"-i"</span><span class="p">,</span> <span class="s">"-"</span><span class="p">,</span>                      <span class="c1">// Input as stdin.</span>
                <span class="s">"-c:v"</span><span class="p">,</span> <span class="s">"libvpx-vp9"</span><span class="p">,</span>           <span class="c1">// Output codec.</span>
                <span class="s">"-pix_fmt"</span><span class="p">,</span> <span class="s">"yuv420p"</span><span class="p">,</span>
                <span class="s">"-y"</span><span class="p">,</span>                           <span class="c1">// Overwrite output file.</span>
                <span class="s">"output.webm"</span><span class="p">,</span>                  <span class="c1">// File name.</span>
            <span class="p">];</span>

            <span class="k">let</span> <span class="k">mut</span> <span class="n">child</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">process</span><span class="p">::</span><span class="nn">Command</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">ffmpeg_cmd</span><span class="p">)</span>
                <span class="nf">.args</span><span class="p">(</span><span class="o">&amp;</span><span class="n">args</span><span class="p">)</span>
                <span class="nf">.stdin</span><span class="p">(</span><span class="nn">Stdio</span><span class="p">::</span><span class="nf">piped</span><span class="p">())</span>
                <span class="nf">.spawn</span><span class="p">()</span>
                <span class="nf">.expect</span><span class="p">(</span><span class="s">"Failed to spawn FFmpeg command"</span><span class="p">);</span>
            
            <span class="k">let</span> <span class="n">stdin</span> <span class="o">=</span> <span class="n">child</span><span class="py">.stdin</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Couldn't open ffmpeg stdin."</span><span class="p">);</span>
            
            <span class="k">for</span> <span class="n">data</span> <span class="k">in</span> <span class="n">recv</span> <span class="p">{</span>
                <span class="n">stdin</span><span class="nf">.write_all</span><span class="p">(</span><span class="n">data</span><span class="nf">.as_slice</span><span class="p">())</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="p">}</span>
            
            <span class="k">let</span> <span class="n">output</span> <span class="o">=</span> <span class="n">child</span><span class="nf">.wait_with_output</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="c1">// Check the output and error messages</span>
            <span class="k">if</span> <span class="n">output</span><span class="py">.status</span><span class="nf">.success</span><span class="p">()</span> <span class="p">{</span>
                <span class="nd">println!</span><span class="p">(</span><span class="s">"FFmpeg command executed successfully."</span><span class="p">);</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="c1">// The `stderr` field of the output contains any error messages</span>
                <span class="k">let</span> <span class="n">error_message</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from_utf8_lossy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">output</span><span class="py">.stderr</span><span class="p">);</span>
                <span class="nd">println!</span><span class="p">(</span><span class="s">"FFmpeg command failed: {}"</span><span class="p">,</span> <span class="n">error_message</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">});</span>
        
        <span class="n">FfmpegProcess</span> <span class="p">{</span>
            <span class="n">send</span><span class="p">,</span>
            <span class="n">handle</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">};</span>
    
    <span class="k">self</span><span class="py">.ffmpeg</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">ffmpeg</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">end_frame</span><span class="p">(</span>
    <span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span>
    <span class="n">device</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">wgpu</span><span class="p">::</span><span class="n">Device</span><span class="p">,</span>
    <span class="n">encoder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">wgpu</span><span class="p">::</span><span class="n">CommandEncoder</span><span class="p">,</span>
    <span class="n">frame_texture</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">wgpu</span><span class="p">::</span><span class="n">Texture</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="o">!</span><span class="k">self</span><span class="py">.recording</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">encoder</span><span class="nf">.copy_texture_to_buffer</span><span class="p">(</span>
        <span class="nn">wgpu</span><span class="p">::</span><span class="n">ImageCopyTexture</span> <span class="p">{</span>
            <span class="n">aspect</span><span class="p">:</span> <span class="nn">wgpu</span><span class="p">::</span><span class="nn">TextureAspect</span><span class="p">::</span><span class="n">All</span><span class="p">,</span>
            <span class="n">texture</span><span class="p">:</span> <span class="n">frame_texture</span><span class="p">,</span>
            <span class="n">mip_level</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">origin</span><span class="p">:</span> <span class="nn">wgpu</span><span class="p">::</span><span class="nn">Origin3d</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="nn">wgpu</span><span class="p">::</span><span class="n">ImageCopyBuffer</span> <span class="p">{</span>
            <span class="n">buffer</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">self</span><span class="py">.buffer</span><span class="p">,</span>
            <span class="n">layout</span><span class="p">:</span> <span class="nn">wgpu</span><span class="p">::</span><span class="n">ImageDataLayout</span> <span class="p">{</span>
                <span class="n">offset</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
                <span class="n">bytes_per_row</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="mi">4</span> <span class="o">*</span> <span class="k">self</span><span class="py">.width</span><span class="p">),</span>    <span class="c1">// 4 bytes (bgra) * width</span>
                <span class="n">rows_per_image</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="k">self</span><span class="py">.height</span><span class="p">),</span>
            <span class="p">},</span>
        <span class="p">},</span>
        <span class="n">frame_texture</span><span class="nf">.size</span><span class="p">(),</span>
    <span class="p">);</span>

    <span class="k">let</span> <span class="n">buffer_slice</span> <span class="o">=</span> <span class="k">self</span><span class="py">.buffer</span><span class="nf">.slice</span><span class="p">(</span><span class="o">..</span><span class="p">);</span>
    <span class="nn">pollster</span><span class="p">::</span><span class="nf">block_on</span><span class="p">(</span><span class="k">async</span> <span class="p">{</span>
        <span class="k">let</span> <span class="p">(</span><span class="n">tx</span><span class="p">,</span> <span class="n">rx</span><span class="p">)</span> <span class="o">=</span> <span class="nn">futures_intrusive</span><span class="p">::</span><span class="nn">channel</span><span class="p">::</span><span class="nn">shared</span><span class="p">::</span><span class="nf">oneshot_channel</span><span class="p">();</span>
        <span class="n">buffer_slice</span><span class="nf">.map_async</span><span class="p">(</span><span class="nn">wgpu</span><span class="p">::</span><span class="nn">MapMode</span><span class="p">::</span><span class="n">Read</span><span class="p">,</span> <span class="k">move</span> <span class="p">|</span><span class="n">result</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">tx</span><span class="nf">.send</span><span class="p">(</span><span class="n">result</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="p">});</span>
        <span class="n">device</span><span class="nf">.poll</span><span class="p">(</span><span class="nn">wgpu</span><span class="p">::</span><span class="nn">Maintain</span><span class="p">::</span><span class="n">Wait</span><span class="p">);</span>
        <span class="n">rx</span><span class="nf">.receive</span><span class="p">()</span><span class="k">.await</span><span class="nf">.unwrap</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="p">});</span>
            
    <span class="p">{</span>
        <span class="c1">// We need to drop this before unmapping.</span>
        <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="n">buffer_slice</span><span class="nf">.get_mapped_range</span><span class="p">();</span>

        <span class="c1">// Send buffer to recording thread.  We need to copy the data to do this safely.</span>
        <span class="k">if</span> <span class="k">self</span><span class="py">.recording</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">ffmpeg</span> <span class="o">=</span> <span class="k">self</span><span class="py">.ffmpeg</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">vec</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">with_capacity</span><span class="p">(</span><span class="n">data</span><span class="nf">.len</span><span class="p">());</span>
            <span class="n">vec</span><span class="nf">.extend_from_slice</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
            <span class="n">ffmpeg</span><span class="py">.send</span><span class="nf">.send</span><span class="p">(</span><span class="n">v</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Failed to send to ffmpeg thread."</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">self</span><span class="py">.buffer</span><span class="nf">.unmap</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">end_record</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">self</span><span class="py">.recording</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
    
    <span class="k">let</span> <span class="n">ffmpeg</span> <span class="o">=</span> <span class="k">self</span><span class="py">.ffmpeg</span><span class="nf">.take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="nf">drop</span><span class="p">(</span><span class="n">ffmpeg</span><span class="py">.send</span><span class="p">);</span>
    <span class="n">ffmpeg</span><span class="py">.handle</span><span class="nf">.join</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="lessons-learned-from-the-future">Lessons Learned From The Future</h2>
<p><em>13/02/2025</em></p>

<p>There are a few more noteworthy things that I’ve come across using this in a non-indie environment.</p>

<h3 id="pipe-behaviour">Pipe Behaviour</h3>
<p>Pipes are inherently quite tied to the OS.  In implementing this through C with Windows APIs you might learn that there’s actually a lot of OS-specific setup and choices to make, in contrast to the Rust standard library, which is more-or-less “give me a pipe”.</p>

<blockquote>
  <p>You can of course configure the pipe however you like with Rust, these details are just abstracted away by default.</p>
</blockquote>

<blockquote>
  <p>Check out the pipe setup in <a href="https://github.com/cademtz/ffmpipe/blob/804a4feab371d0b77db7d3cf37a34c7c5354e176/src/ffmpipe.cpp#L88">this</a> project on GitHub for a sensible Windows/C++ implementation.</p>
</blockquote>

<p>The kinds of choices here are unsurprisingly about the specifics of I/O.  Typically the encoding process is slower than the recording – the game may be pumping out 60fps, while your encoder can only encode 30fps.  This time needs to be reclaimed at some point, and you’ll want to consider your tradeoffs here.  On Windows, a non-async pipe will block on write once it’s backed up enough, which is fine for recording pre-made scenes, but not so much if you’re trying to interact or play – lots of stuttering.  Of course, this stuttering won’t be present in the recording, just while playing.</p>

<p>The easiest solution is to just pick a fast encoder (such as NVENC) when recording interactive shots.</p>

<h3 id="media-players-like-yuv420">Media Players Like YUV420</h3>
<p>Although the command in my original post actually has this already, I think it’s worth drawing more attention to.  <code class="language-plaintext highlighter-rouge">-pix_fmt yuv420p</code> is important if you want your video to <em>just work</em> on the most possible video players.  It tells <code class="language-plaintext highlighter-rouge">ffmpeg</code> to encode the video in the YUV colour space, with 4:2:0 subsampling.  Without this flag, much of the time <code class="language-plaintext highlighter-rouge">ffmpeg</code> will default to <code class="language-plaintext highlighter-rouge">-pix_fmt yuv444p</code>, which is 4:4:4 subsampling.  Advanced video players such as <code class="language-plaintext highlighter-rouge">mpv</code> and <code class="language-plaintext highlighter-rouge">vlc</code> will generally play either back just fine, but more primitive ones will not (Windows Media Player, some web browsers, etc).</p>

<h3 id="ffmpeg-syntax">FFmpeg Syntax</h3>
<p>This isn’t really a problem, but I ended up moving towards the following command syntax which I like more:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-y</span> <span class="nt">-f</span> rawvideo <span class="nt">-framerate</span> 60 <span class="nt">-pix_fmt</span> rgba <span class="nt">-s</span>:v 1920x1080 <span class="nt">-i</span> pipe:0 <span class="nt">-c</span>:v libx264 <span class="nt">-pix_fmt</span> yuv420p <span class="nt">-crf</span> 26 <span class="nt">-preset</span> ultrafast output.mp4
</code></pre></div></div>
<p>Some of these actually do have consequences.  <code class="language-plaintext highlighter-rouge">-s:v</code> will change specifically the video stream’s resolution, while <code class="language-plaintext highlighter-rouge">-s</code> usually affects the video stream by default.  It’s all pretty confusing.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[A recording of a recording.]]></summary></entry><entry><title type="html">Verlet Rope in Games</title><link href="https://toqoz.fyi/game-rope.html" rel="alternate" type="text/html" title="Verlet Rope in Games" /><published>2020-08-26T00:00:00+00:00</published><updated>2020-08-26T00:00:00+00:00</updated><id>https://toqoz.fyi/game-rope</id><content type="html" xml:base="https://toqoz.fyi/game-rope.html"><![CDATA[<p><img src="/assets/2020_rope_showoff.gif" alt="Rope demo" width="1229px" height="618px" /></p>

<p>For the past month and a bit I’ve been working on a (2D) game which uses a rope as a core mechanic.  I expected this to be challenging, but was (unsurprisingly) caught by a bunch of things and learnt some valuable lessons along the way, which I’m hoping to document here.</p>

<p>Let’s first acknowledge that rope is used pretty sparingly in video games, with good reason:</p>
<ul>
  <li>It’s computationally expensive to simulate.</li>
  <li>It’s cognitively expensive to develop.</li>
  <li>Using physically-based rope for anything remotely interesting is full of edge cases and other hurdles.</li>
  <li>It can be a pain to render.</li>
</ul>

<p>It also might just be not that exciting as a game mechanic, but we can’t work that out without solving the technical problems first.</p>

<p>This isn’t to say you can’t implement a rope that looks good and works fine.  Actually, if you’re willing to make some concessions, believable rope is damn easy.  It’s really only once the rope becomes a meaningful mechanic, and utilises things like collision, that things get a bit hairy.  Hopefully I can help to solve some of these for you.</p>

<p>This post is largely about working in 2D, but most concepts should be able to be transferred to 3D as well.  Likewise, code in this post is Unity-specific, but should be able to be adjusted to fit any language or engine.</p>

<h2 id="write-your-own-physics">Write Your Own Physics</h2>
<p>Don’t make the mistake of seeing “hinge joint”, “angle joint”, or some other variation in your game/physics engine and thinking you can string a bunch of them together to make a rope.  Not only will it be buggy, but you’ll have to do a lot more work to have any control over it, and any new interaction you want to code will be anxiety in a bottle.</p>

<p><img src="/assets/2020_whip.png" alt="Wonky whip." width="480px" height="270px" /></p>

<p>Verlet integration–<strong>specifically <em>position-based</em> Verlet</strong>–is a very easy to understand method for calculating trajectories of objects, used as an alternative to something like Euler or RK4 integration.  Verlet in general is used often in molecular dynamics for its high stability, but that’s not really what we’re interested in.  Position-based Verlet is a great fit for simulating rope because it calculates the velocity of an object from its previous position:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">float</span> <span class="n">STEP_TIME</span> <span class="p">=</span> <span class="p">.</span><span class="m">01f</span><span class="p">;</span>
<span class="n">Vector2</span> <span class="n">gravity</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector2</span><span class="p">(</span><span class="m">0f</span><span class="p">,</span> <span class="p">-</span><span class="m">9.8f</span><span class="p">);</span>

<span class="c1">// Euler (semi-implicit) approach:</span>
<span class="c1">// The RK4 struct would be very similar if not identical.</span>
<span class="k">class</span> <span class="nc">EulerObject</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">position</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">velocity</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">mass</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">void</span> <span class="nf">StepEuler</span><span class="p">(</span><span class="n">EulerObject</span> <span class="n">obj</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Euler (semi-implicit) calculation:</span>
    <span class="c1">// acceleration = force / mass;</span>
    <span class="c1">// next_vel = vel + (acceleration * dt);</span>
    <span class="c1">// next_pos = cur_pos + (next_vel * dt);</span>
    <span class="n">Vector2</span> <span class="n">acceleration</span> <span class="p">=</span> <span class="n">gravity</span> <span class="p">/</span> <span class="n">obj</span><span class="p">.</span><span class="n">mass</span><span class="p">;</span>
    <span class="n">obj</span><span class="p">.</span><span class="n">velocity</span> <span class="p">+=</span> <span class="n">acceleration</span> <span class="p">*</span> <span class="n">stepTime</span><span class="p">;</span>
    <span class="n">obj</span><span class="p">.</span><span class="n">position</span> <span class="p">+=</span> <span class="n">velocity</span> <span class="p">*</span> <span class="n">stepTime</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Verlet approach:</span>
<span class="k">class</span> <span class="nc">VerletNode</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">position</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">oldPosition</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">void</span> <span class="nf">StepVerlet</span><span class="p">(</span><span class="n">VerletNode</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Verlet calculation (velocity derived from previous step):</span>
    <span class="c1">// next_pos = cur_pos + (cur_pos - old_pos) + acceleration * dt * dt;</span>
    <span class="c1">// NOTE: cur_pos - old_pos is not actual velocity.  To calculate real velocity, use (cur_pos - old_pos) / dt.</span>
    <span class="n">Vector2</span> <span class="n">temp</span> <span class="p">=</span> <span class="n">node</span><span class="p">.</span><span class="n">position</span><span class="p">;</span>
    <span class="n">node</span><span class="p">.</span><span class="n">position</span> <span class="p">+=</span> <span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">position</span> <span class="p">-</span> <span class="n">node</span><span class="p">.</span><span class="n">oldPosition</span><span class="p">)</span> <span class="p">+</span> <span class="n">gravity</span> <span class="p">*</span> <span class="n">STEP_TIME</span> <span class="p">*</span> <span class="n">STEP_TIME</span><span class="p">;</span>
    <span class="n">node</span><span class="p">.</span><span class="n">oldPosition</span> <span class="p">=</span> <span class="n">temp</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p><strong><em>Why the names <code class="language-plaintext highlighter-rouge">EulerObject</code> and <code class="language-plaintext highlighter-rouge">VerletNode</code>?</em></strong>
In your typical (Euler-based) physics engine, you expect each physical object to have some kind of <code class="language-plaintext highlighter-rouge">Rigidbody</code> concept, with a position, velocity, mass, etc, and a collider.  In a Verlet-based simulation, you’re much more likely to see the integration properties (position, velocity, etc) attached to the actual vertices of the collider, if a collider is being used at all.</p>
</blockquote>

<p>The implication this has is that we don’t need to do any velocity calculation ourselves to have a good looking simulation, we just need to update object positions.  A good example of this behavior being useful might be to imagine an object which is programmed to follow the mouse cursor.  With a position-based integrator, we can flick the mouse, release the object, and expect it to fly naturally on its own.  Normally, we’d be expected to do some release velocity calculation, and then manually add the appropriate force to the <code class="language-plaintext highlighter-rouge">Rigidbody</code>.</p>

<p><img src="/assets/2020_verlet_demo.gif" alt="Verlet demo." width="1229px" height="618px" />
<em>Verlet demo (smoothing applied).</em></p>

<p>The last piece of the puzzle here is distance constraints, which aim to keep the distance between two nodes constant <sup>obviously</sup>.  After applying velocity, we move the position of each node in the rope to a new one which obeys the distance constraint.  This is an iterative process, and it doesn’t ensure that constraints are satisfied after it’s finished; we apply the constraints many times to get reasonably close to a solve–when I mention “iterations” throughout this post, this is what I’m referring to.  Again, position-based integration makes this easier because we only need to update positions and let the simulation figure things out with the new information.</p>

<p>If you put all that together you’ll get something like the following:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">VerletNode</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">position</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">oldPosition</span><span class="p">;</span>
    
    <span class="k">public</span> <span class="nf">VerletNode</span><span class="p">(</span><span class="n">Vector2</span> <span class="n">startPos</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">startPos</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">oldPosition</span> <span class="p">=</span> <span class="n">startPos</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Rope</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">iterations</span> <span class="p">=</span> <span class="m">80</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">int</span> <span class="n">totalNodes</span> <span class="p">=</span> <span class="m">40</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">nodeDistance</span> <span class="p">=</span> <span class="p">.</span><span class="m">1f</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">gravity</span><span class="p">;</span>	<span class="c1">// (0f, -20f)</span>

    <span class="k">private</span> <span class="n">VerletNode</span><span class="p">[]</span> <span class="n">nodes</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Awake</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">nodes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">VerletNode</span><span class="p">[</span><span class="n">totalNodes</span><span class="p">];</span>

        <span class="c1">// Spawn nodes starting from the transform position and working down.</span>
        <span class="n">Vector2</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">totalNodes</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">VerletNode</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
            <span class="n">pos</span><span class="p">.</span><span class="n">y</span> <span class="p">-=</span> <span class="n">nodeDistance</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">FixedUpdate</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">Simulate</span><span class="p">();</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">iterations</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="nf">ApplyConstraints</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Simulate</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">nodes</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="n">VerletNode</span> <span class="n">node</span> <span class="p">=</span> <span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>

            <span class="n">Vector2</span> <span class="n">temp</span> <span class="p">=</span> <span class="n">node</span><span class="p">.</span><span class="n">position</span><span class="p">;</span>
            <span class="n">node</span><span class="p">.</span><span class="n">position</span> <span class="p">+=</span> <span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">position</span> <span class="p">-</span> <span class="n">node</span><span class="p">.</span><span class="n">oldPosition</span><span class="p">)</span> <span class="p">+</span> <span class="n">gravity</span> <span class="p">*</span> <span class="p">(</span><span class="n">Time</span><span class="p">.</span><span class="n">fixedDeltaTime</span> <span class="p">*</span> <span class="n">Time</span><span class="p">.</span><span class="n">fixedDeltaTime</span><span class="p">);</span>
            <span class="n">node</span><span class="p">.</span><span class="n">oldPosition</span> <span class="p">=</span> <span class="n">temp</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">ApplyConstraints</span><span class="p">()</span> <span class="p">{</span>  
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">nodes</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="n">VerletNode</span> <span class="n">node1</span> <span class="p">=</span> <span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="n">VerletNode</span> <span class="n">node2</span> <span class="p">=</span> <span class="n">nodes</span><span class="p">[</span><span class="n">i</span> <span class="p">+</span> <span class="m">1</span><span class="p">];</span>

            <span class="c1">// First node follows the mouse, for debugging.</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="p">==</span> <span class="m">0</span> <span class="p">&amp;&amp;</span> <span class="n">Input</span><span class="p">.</span><span class="nf">GetMouseButton</span><span class="p">(</span><span class="m">0</span><span class="p">))</span> <span class="p">{</span>
                <span class="c1">// Camera.main is terribly inefficient here, you should cache the camera.</span>
                <span class="n">node1</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">cam</span><span class="p">.</span><span class="nf">ScreenToWorldPoint</span><span class="p">(</span><span class="n">Input</span><span class="p">.</span><span class="n">mousePosition</span><span class="p">);</span>
            <span class="p">}</span>

            <span class="c1">// Current distance between rope nodes.</span>
            <span class="kt">float</span> <span class="n">diffX</span> <span class="p">=</span> <span class="n">node1</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">x</span> <span class="p">-</span> <span class="n">node2</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
            <span class="kt">float</span> <span class="n">diffY</span> <span class="p">=</span> <span class="n">node1</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">y</span> <span class="p">-</span> <span class="n">node2</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">y</span><span class="p">;</span>
            <span class="kt">float</span> <span class="n">dist</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="nf">Distance</span><span class="p">(</span><span class="n">node1</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">node2</span><span class="p">.</span><span class="n">position</span><span class="p">);</span>
            <span class="kt">float</span> <span class="n">difference</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
            <span class="c1">// Guard against divide by 0.</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">dist</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span> 
                <span class="n">difference</span> <span class="p">=</span> <span class="p">(</span><span class="n">nodeDistance</span> <span class="p">-</span> <span class="n">dist</span><span class="p">)</span> <span class="p">/</span> <span class="n">dist</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">Vector2</span> <span class="n">translate</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector2</span><span class="p">(</span><span class="n">diffX</span><span class="p">,</span> <span class="n">diffY</span><span class="p">)</span> <span class="p">*</span> <span class="p">(.</span><span class="m">5f</span> <span class="p">*</span> <span class="n">difference</span><span class="p">);</span>

            <span class="n">node1</span><span class="p">.</span><span class="n">position</span> <span class="p">+=</span> <span class="n">translate</span><span class="p">;</span>
            <span class="n">node2</span><span class="p">.</span><span class="n">position</span> <span class="p">-=</span> <span class="n">translate</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>View the complete script on GitHub <a href="https://github.com/Toqozz/blog-code/blob/master/rope/Assets/Rope.cs">here</a> (contains work discussed later).</p>
</blockquote>

<p>That might seem like a lot to digest, but we’ve covered most of the theory.  There are a few bonus things I would like to mention though:</p>

<h3 id="use-fixed-timestep">Use Fixed Timestep</h3>
<p>If for some bad reason you’re like me and are considering a variable delta time for your physics, the TL;DR is <strong>don’t</strong>.  There are ways to simulate Verlet integration with a variable delta time, but you’ll never get the consistency of a fixed timestep, and the lack of predictable behavior is just crippling.</p>

<p>The GitHub version uses a custom fixed timestep implementation which can offer some more flexibility here.</p>

<h3 id="number-of-iterations">Number of Iterations</h3>
<p>The more iterations you have, the stiffer the rope will be.  The more nodes you have, the more iterations you need to maintain that stiffness.  So things get pretty slow if you want a long and detailed rope.  If you’re in Unity and your rope is super long, you probably want to look at the Burst compiler, discussed later.</p>

<p>There’s also a good optimization to reduce iterations by adding an additional constraint between the first and the last node, making sure that the overall rope length stays consistent, and lessening the load for the other constraints.  There is a downside of the rope feeling a bit weird when stretched, so try both to see whether this is worthwhile for you:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Distance constraint which reduces iterations, but doesn't handle stretchyness in a natural way.</span>
<span class="n">VerletNode</span> <span class="n">first</span> <span class="p">=</span> <span class="n">nodes</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="n">VerletNode</span> <span class="n">last</span> <span class="p">=</span> <span class="n">nodes</span><span class="p">[</span><span class="n">nodes</span><span class="p">.</span><span class="n">Length</span><span class="p">-</span><span class="m">1</span><span class="p">];</span>
<span class="c1">// Same distance calculation as above, but less optimal.</span>
<span class="kt">float</span> <span class="n">distance</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="nf">Distance</span><span class="p">(</span><span class="n">first</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">last</span><span class="p">.</span><span class="n">position</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">distance</span> <span class="p">&gt;</span> <span class="m">0</span> <span class="p">&amp;&amp;</span> <span class="n">distance</span> <span class="p">&gt;</span> <span class="n">nodes</span><span class="p">.</span><span class="n">Length</span> <span class="p">*</span> <span class="n">nodeDistance</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Vector2</span> <span class="n">dir</span> <span class="p">=</span> <span class="p">(</span><span class="n">last</span><span class="p">.</span><span class="n">position</span> <span class="p">-</span> <span class="n">first</span><span class="p">.</span><span class="n">position</span><span class="p">).</span><span class="n">normalized</span><span class="p">;</span>
    <span class="n">last</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">first</span><span class="p">.</span><span class="n">position</span> <span class="p">+</span> <span class="n">nodes</span><span class="p">.</span><span class="n">Length</span> <span class="p">*</span> <span class="n">nodeDistance</span> <span class="p">*</span> <span class="n">dir</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="debug-rendering">Debug Rendering</h3>
<p>Debug rendering is as easy as drawing lines between the points.  Here’s a gizmos implementation:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">OnDrawGizmos</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(!</span><span class="n">Application</span><span class="p">.</span><span class="n">isPlaying</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">nodes</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="p">%</span> <span class="m">2</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">Gizmos</span><span class="p">.</span><span class="n">color</span> <span class="p">=</span> <span class="n">Color</span><span class="p">.</span><span class="n">green</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">Gizmos</span><span class="p">.</span><span class="n">color</span> <span class="p">=</span> <span class="n">Color</span><span class="p">.</span><span class="n">white</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">Gizmos</span><span class="p">.</span><span class="nf">DrawLine</span><span class="p">(</span><span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">position</span><span class="p">,</span> <span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">+</span><span class="m">1</span><span class="p">].</span><span class="n">position</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/2020_rope_debugrender.gif" alt="Rope debug rendering." width="992px" height="539px" /></p>

<hr />

<p>This makes for a pretty good (and fun to play with) rope simulation!  But it’s missing collision, which makes it difficult to utilize the rope mechanically.</p>

<h3 id="collision-detection-and-resolution">Collision Detection and Resolution</h3>
<p>Unfortunately, collision, and more specifically collision resolution, is really the hard part of physics simulations.  Our Verlet sim makes things a bit easier, but some of the stuff is still alien.  I’ll be walking us through collision resolution for the 2 most basic colliders, <em>Circle</em> and <em>Box</em>.</p>

<p>The basic idea is that we should check collisions each time we move a node, and if the node is colliding after the move then we should resolve the collision by pushing it out the shortest distance we can.  This includes movement via constraints, so we need to check collision of each node every iteration.</p>

<p><img src="/assets/2020_rope_collision.png" alt="Collision diagram." width="2732px" height="2048px" />
<em>Rope swings into collider, and nodes are pushed out along the closest edge.</em></p>

<p>For relatively detailed and stiff rope (40 nodes, 80 iterations), that’s potentially 3,200 collision resolves each tick, more if nodes are touching multiple colliders at once.  We’ll need something fast here.</p>

<h4 id="detection">Detection</h4>
<p>The easiest way for us to detect rope collision is to use an <a href="https://docs.unity3d.com/ScriptReference/Physics2D.OverlapCircle.html">overlap circle</a> around each node, which will return an array of colliders within the specified radius of said node.  I mentioned before that we need to detect collision every iteration.  The thing is that collision objects don’t actually move between iterations because our rope simulation all happens in a single frame, and so it doesn’t make sense to query the physics engine every iteration.  What we actually want to do is take a kind of snapshot of the colliders within a reasonable distance of the rope, and use that for our entire rope step.</p>

<blockquote>
  <p>In Unity (and in most physics engines), colliders only update each physics tick, even if their transform moved in-between.  This means we only have to update the snapshot of the <em>latest</em> physics steps.  This is important if you’re using a custom step time for your rope.</p>
</blockquote>

<p><img src="/assets/2020_collider_snapshot.png" alt="Collision snapshot." width="2732px" height="2048px" />
<em>“Snapshot” colliders within a reasonable distance of the rope.</em></p>

<p>Here’s an excerpt of our rope script with the snapshot implementation:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="n">ColliderType</span> <span class="p">{</span>
    <span class="n">Circle</span><span class="p">,</span>
    <span class="n">Box</span><span class="p">,</span>
    <span class="n">None</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">class</span> <span class="nc">CollisionInfo</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">id</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">ColliderType</span> <span class="n">colliderType</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">colliderSize</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">position</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">scale</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Matrix4x4</span> <span class="n">wtl</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Matrix4x4</span> <span class="n">ltw</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">numCollisions</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">int</span><span class="p">[]</span> <span class="n">collidingNodes</span><span class="p">;</span> <span class="c1">// You probably want to use byte[] here instead, unless you have &gt;255 nodes.</span>

    <span class="k">public</span> <span class="nf">CollisionInfo</span><span class="p">(</span><span class="kt">int</span> <span class="n">maxCollisions</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">id</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">colliderType</span> <span class="p">=</span> <span class="n">ColliderType</span><span class="p">.</span><span class="n">None</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">colliderSize</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="n">zero</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="n">zero</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">scale</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="n">zero</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">wtl</span> <span class="p">=</span> <span class="n">Matrix4x4</span><span class="p">.</span><span class="n">zero</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">ltw</span> <span class="p">=</span> <span class="n">Matrix4x4</span><span class="p">.</span><span class="n">zero</span><span class="p">;</span>

        <span class="k">this</span><span class="p">.</span><span class="n">numCollisions</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">collidingNodes</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[</span><span class="n">maxCollisions</span><span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Rope</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="c1">// Maximum total number of colliders that the rope can touch.</span>
    <span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">MAX_ROPE_COLLISIONS</span> <span class="p">=</span> <span class="m">32</span><span class="p">;</span>
    <span class="c1">// Collision radius around each node.  Set it high to avoid tunneling.</span>
    <span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">COLLISION_RADIUS</span> <span class="p">=</span> <span class="p">.</span><span class="m">5f</span><span class="p">;</span>
    <span class="c1">// Collider buffer size; the maximum number of colliders that a single node can touch at once.</span>
    <span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">COLLIDER_BUFFER_SIZE</span> <span class="p">=</span> <span class="m">8</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">int</span> <span class="n">totalNodes</span><span class="p">;</span>
    <span class="c1">// -- *snip* --</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">collisionRadius</span> <span class="p">=</span> <span class="p">.</span><span class="m">5f</span><span class="p">;</span> <span class="c1">// Snapshot radius around each node, set it high to avoid tunneling.</span>
    <span class="k">private</span> <span class="kt">int</span> <span class="n">numCollisions</span><span class="p">;</span>
    <span class="k">private</span> <span class="kt">bool</span> <span class="n">shouldSnapshotCollision</span><span class="p">;</span>
	<span class="k">private</span> <span class="n">CollisionInfo</span><span class="p">[]</span> <span class="n">collisionInfos</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Collider2D</span><span class="p">[]</span> <span class="n">colliderBuffer</span><span class="p">;</span>
    
    <span class="k">private</span> <span class="k">void</span> <span class="nf">Awake</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// -- *snip* --</span>
        <span class="c1">// Allocate collision structures.</span>
        <span class="n">collisionInfos</span> <span class="p">=</span> <span class="k">new</span> <span class="n">CollisionInfo</span><span class="p">[</span><span class="n">MAX_ROPE_COLLISIONS</span><span class="p">];</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">collisionInfos</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="c1">// Each collider can collide with as many nodes as are in the rope.</span>
            <span class="n">collisionInfos</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CollisionInfo</span><span class="p">(</span><span class="n">totalNodes</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// Buffer for `OverlapCircleNonAlloc`.</span>
        <span class="n">colliderBuffer</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Collider2D</span><span class="p">[</span><span class="n">COLLIDER_BUFFER_SIZE</span><span class="p">];</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">FixedUpdate</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">shouldSnapshotCollision</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="kt">int</span> <span class="nf">SnapshotCollisions</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">numCollisions</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
        <span class="c1">// Loop through each node and get collisions within a radius.</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">nodes</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="kt">int</span> <span class="n">collisions</span> <span class="p">=</span>
                <span class="n">Physics2D</span><span class="p">.</span><span class="nf">OverlapCircleNonAlloc</span><span class="p">(</span><span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">position</span><span class="p">,</span> <span class="n">collisionRadius</span><span class="p">,</span> <span class="n">colliderBuffer</span><span class="p">);</span>

            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">j</span> <span class="p">&lt;</span> <span class="n">collisions</span><span class="p">;</span> <span class="n">j</span><span class="p">++)</span> <span class="p">{</span>
                <span class="n">Collider2D</span> <span class="n">col</span> <span class="p">=</span> <span class="n">colliderBuffer</span><span class="p">[</span><span class="n">j</span><span class="p">];</span>
                <span class="kt">int</span> <span class="n">id</span> <span class="p">=</span> <span class="n">col</span><span class="p">.</span><span class="nf">GetInstanceID</span><span class="p">();</span>

                <span class="c1">// Check if we already have this collider in our collisionInfos.</span>
                <span class="kt">int</span> <span class="n">idx</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
                <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">k</span> <span class="p">&lt;</span> <span class="n">numCollisions</span><span class="p">;</span> <span class="n">k</span><span class="p">++)</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">collisionInfos</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">id</span> <span class="p">==</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span>
                        <span class="n">idx</span> <span class="p">=</span> <span class="n">k</span><span class="p">;</span>
                        <span class="k">break</span><span class="p">;</span>
                    <span class="p">}</span>
                <span class="p">}</span>

                <span class="c1">// If we didn't have the collider, we need to add it.</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">idx</span> <span class="p">&lt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
                    <span class="c1">// Record all the data we need to use into our class.</span>
                    <span class="n">CollisionInfo</span> <span class="n">ci</span> <span class="p">=</span> <span class="n">collisionInfos</span><span class="p">[</span><span class="n">numCollisions</span><span class="p">];</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">id</span> <span class="p">=</span> <span class="n">id</span><span class="p">;</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">wtl</span> <span class="p">=</span> <span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">worldToLocalMatrix</span><span class="p">;</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">ltw</span> <span class="p">=</span> <span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">localToWorldMatrix</span><span class="p">;</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">ltw</span><span class="p">.</span><span class="nf">GetColumn</span><span class="p">(</span><span class="m">0</span><span class="p">).</span><span class="n">magnitude</span><span class="p">;</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">ltw</span><span class="p">.</span><span class="nf">GetColumn</span><span class="p">(</span><span class="m">1</span><span class="p">).</span><span class="n">magnitude</span><span class="p">;</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">;</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">numCollisions</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>	<span class="c1">// 1 collision, this one.</span>
                    <span class="n">ci</span><span class="p">.</span><span class="n">collidingNodes</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">=</span> <span class="n">i</span><span class="p">;</span>

                    <span class="k">switch</span> <span class="p">(</span><span class="n">col</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">case</span> <span class="n">CircleCollider2D</span> <span class="n">c</span><span class="p">:</span>
                            <span class="n">ci</span><span class="p">.</span><span class="n">colliderType</span> <span class="p">=</span> <span class="n">ColliderType</span><span class="p">.</span><span class="n">Circle</span><span class="p">;</span>
                            <span class="n">ci</span><span class="p">.</span><span class="n">colliderSize</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">colliderSize</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">c</span><span class="p">.</span><span class="n">radius</span><span class="p">;</span>
                            <span class="k">break</span><span class="p">;</span>
                        <span class="k">case</span> <span class="n">BoxCollider2D</span> <span class="n">b</span><span class="p">:</span>
                            <span class="n">ci</span><span class="p">.</span><span class="n">colliderType</span> <span class="p">=</span> <span class="n">ColliderType</span><span class="p">.</span><span class="n">Box</span><span class="p">;</span>
                            <span class="n">ci</span><span class="p">.</span><span class="n">colliderSize</span> <span class="p">=</span> <span class="n">b</span><span class="p">.</span><span class="n">size</span><span class="p">;</span>
                            <span class="k">break</span><span class="p">;</span>
                        <span class="k">default</span><span class="p">:</span>
                            <span class="n">ci</span><span class="p">.</span><span class="n">colliderType</span> <span class="p">=</span> <span class="n">ColliderType</span><span class="p">.</span><span class="n">None</span><span class="p">;</span>
                            <span class="k">break</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="n">numCollisions</span><span class="p">++;</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">numCollisions</span> <span class="p">&gt;=</span> <span class="n">MAX_ROPE_COLLISIONS</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">return</span><span class="p">;</span>
                    <span class="p">}</span>
                <span class="c1">// If we found the collider, then we just have to increment collisions and add our node.</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="n">CollisionInfo</span> <span class="n">ci</span> <span class="p">=</span> <span class="n">collisionInfos</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">numCollisions</span> <span class="p">&gt;=</span> <span class="n">totalNodes</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">continue</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="n">ci</span><span class="p">.</span><span class="n">collidingNodes</span><span class="p">[</span><span class="n">ci</span><span class="p">.</span><span class="n">numCollisions</span><span class="p">++]</span> <span class="p">=</span> <span class="n">i</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="n">shouldSnapshotCollision</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I apologise for the volume of code in this post; this shit is technical!</p>

<p>The first thing to notice is that the <code class="language-plaintext highlighter-rouge">CollisionInfo</code> class, which we use to hold the data we need from <code class="language-plaintext highlighter-rouge">Collider2D</code>.  Using a reference to the <code class="language-plaintext highlighter-rouge">Collider2D</code> is <strong>much</strong> slower (~2x), probably due to its large size and scattered pointers, both of which are terrible for cache performance.</p>

<p>There’s an annoying amount of constants.  Feel free to convert the arrays to lists to avoid some of these, I just prefer having control over the allocations.</p>

<p>An unexpected complication is that we have to do a bit of work to check whether we already have each collider before adding it.  If you’re expecting many collisions (more than 30), you probably want to consider a <code class="language-plaintext highlighter-rouge">Dictionary</code> or some kind of <code class="language-plaintext highlighter-rouge">Set</code> to store the collision information.  If you’re not expecting so many, a dictionary will likely just make things slower.</p>

<h4 id="resolution">Resolution</h4>
<p>Circle collisions are easily resolved by checking the node distance against the collider radius.  Collisions with boxes that rotate, however, can be challenging.  The easiest way is to convert our collision point to the box’s local space, which removes the scale and rotation, making it axis-aligned, which is considerably easier to work with.  Once we’ve detected and resolved the collision, we can convert the no-longer-colliding point back to world space.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">AdjustCollisions</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">numCollisions</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
        <span class="n">CollisionInfo</span> <span class="n">ci</span> <span class="p">=</span> <span class="n">collisionInfos</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>

        <span class="k">switch</span> <span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">colliderType</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">case</span> <span class="n">ColliderType</span><span class="p">.</span><span class="n">Circle</span><span class="p">:</span> <span class="p">{</span>
                <span class="kt">float</span> <span class="n">radius</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">colliderSize</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Max</span><span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>

                <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">j</span> <span class="p">&lt;</span> <span class="n">ci</span><span class="p">.</span><span class="n">numCollisions</span><span class="p">;</span> <span class="n">j</span><span class="p">++)</span> <span class="p">{</span>
                    <span class="n">VerletNode</span> <span class="n">node</span> <span class="p">=</span> <span class="n">nodes</span><span class="p">[</span><span class="n">ci</span><span class="p">.</span><span class="n">collidingNodes</span><span class="p">[</span><span class="n">j</span><span class="p">]];</span>
                    <span class="kt">float</span> <span class="n">distance</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="nf">Distance</span><span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">node</span><span class="p">.</span><span class="n">position</span><span class="p">);</span>

                    <span class="c1">// Early out if we're not colliding.</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">distance</span> <span class="p">-</span> <span class="n">radius</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">continue</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="c1">// Push point outside circle.</span>
                    <span class="n">Vector2</span> <span class="n">dir</span> <span class="p">=</span> <span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">position</span> <span class="p">-</span> <span class="n">ci</span><span class="p">.</span><span class="n">position</span><span class="p">).</span><span class="n">normalized</span><span class="p">;</span>
                    <span class="n">Vector2</span> <span class="n">hitPos</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">position</span> <span class="p">+</span> <span class="n">dir</span> <span class="p">*</span> <span class="n">radius</span><span class="p">;</span>
                    <span class="n">node</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">hitPos</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span> <span class="k">break</span><span class="p">;</span>

            <span class="k">case</span> <span class="n">ColliderType</span><span class="p">.</span><span class="n">Box</span><span class="p">:</span> <span class="p">{</span>
                <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">j</span> <span class="p">&lt;</span> <span class="n">ci</span><span class="p">.</span><span class="n">numCollisions</span><span class="p">;</span> <span class="n">j</span><span class="p">++)</span> <span class="p">{</span>
                    <span class="n">VerletNode</span> <span class="n">node</span> <span class="p">=</span> <span class="n">nodes</span><span class="p">[</span><span class="n">ci</span><span class="p">.</span><span class="n">collidingNodes</span><span class="p">[</span><span class="n">j</span><span class="p">]];</span>
                    <span class="n">Vector2</span> <span class="n">localPoint</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">wtl</span><span class="p">.</span><span class="nf">MultiplyPoint</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">position</span><span class="p">);</span>

                    <span class="c1">// If distance from center is more than box "radius", then we can't be colliding.</span>
                    <span class="n">Vector2</span> <span class="n">half</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">colliderSize</span> <span class="p">*</span> <span class="p">.</span><span class="m">5f</span><span class="p">;</span>
                    <span class="n">Vector2</span> <span class="n">scalar</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">;</span>
                    <span class="kt">float</span> <span class="n">dx</span> <span class="p">=</span> <span class="n">localPoint</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
                    <span class="kt">float</span> <span class="n">px</span> <span class="p">=</span> <span class="n">half</span><span class="p">.</span><span class="n">x</span> <span class="p">-</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Abs</span><span class="p">(</span><span class="n">dx</span><span class="p">);</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">px</span> <span class="p">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">continue</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="kt">float</span> <span class="n">dy</span> <span class="p">=</span> <span class="n">localPoint</span><span class="p">.</span><span class="n">y</span><span class="p">;</span>
                    <span class="kt">float</span> <span class="n">py</span> <span class="p">=</span> <span class="n">half</span><span class="p">.</span><span class="n">y</span> <span class="p">-</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Abs</span><span class="p">(</span><span class="n">dy</span><span class="p">);</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">py</span> <span class="p">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">continue</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="c1">// Push node out along closest edge.</span>
                    <span class="c1">// Need to multiply distance by scale or we'll mess up on scaled box corners.</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">px</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">.</span><span class="n">x</span> <span class="p">&lt;</span> <span class="n">py</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span>
                        <span class="kt">float</span> <span class="n">sx</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Sign</span><span class="p">(</span><span class="n">dx</span><span class="p">);</span>
                        <span class="n">localPoint</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span>  <span class="n">half</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">sx</span><span class="p">;</span>
                    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                        <span class="kt">float</span> <span class="n">sy</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Sign</span><span class="p">(</span><span class="n">dy</span><span class="p">);</span>
                        <span class="n">localPoint</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">half</span><span class="p">.</span><span class="n">y</span> <span class="p">*</span> <span class="n">sy</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="n">Vector2</span> <span class="n">hitPos</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">ltw</span><span class="p">.</span><span class="nf">MultiplyPoint</span><span class="p">(</span><span class="n">localPoint</span><span class="p">);</span>
                    <span class="n">node</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">hitPos</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span> <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>Excellent resource on intersection tests, which explains this calculation a bit better: https://noonat.github.io/intersect/</p>
</blockquote>

<p>At this point, you might want to check out the <a href="https://github.com/Toqozz/blog-code/blob/master/rope/Assets/Rope.cs">full script</a> on GitHub to see everything with context.  Note that it’s also using custom line rendering, explained below.</p>

<p><img src="/assets/2020_rope_collision.gif" alt="Rope collision demo." width="1184px" height="572px" /></p>

<p><em>Rounding the edges on boxes with some circles can make things feel a bit better.</em></p>

<hr />

<h2 id="performance-and-the-burst-compiler">Performance, and the Burst Compiler</h2>
<p>Despite fairly aggressive optimization, this rope implementation is still slower than I’m comfortable with:</p>

<p><img src="/assets/2020_rope_profiler.png" alt="Profiler, non-Jobs." width="1102px" height="383px" /></p>

<p><em>200 nodes, 80 iterations.</em></p>

<p>While I’m sure we could squeeze out a bit more performance, the improvement is unlikely to be drastic; I’m fairly sure we’re touching the edges of Unity, C# and its C# / C++ interoperability here.</p>

<blockquote>
  <p>Got something way faster in C#?  Let me know so I can improve this post!</p>
</blockquote>

<p>A concern I have here is that the performance impact of the rope is quite “spiky” because we’re updating on a fixed timestep rather than every frame.  If we add many more scripts, and bring the baseline CPU time up 14ms or so, players are likely to feel this.  We need to either further optimize to make spikes less noticeable, or run rope simulation in a different thread.</p>

<p>My first thought to improve performance was to parallelize the algorithm, but this turns out to not really be very fruitful.  I don’t want to talk about this too much, but the critical part is that all granular steps depend on the step before them, so the algorithm would have to be dramatically changed to see any tangible benefit.  The extent of these archetypal changes made parallel avenue ultimately not worthwhile for me.  If you think it is worthwhile, <a href="https://github.com/JUSTIVE/GPU-Cloth-Simulation">this GPU cloth simulation</a> might give you some ideas.</p>

<p>Luckily for us, Unity has recently introduced something called <a href="https://docs.unity3d.com/Packages/com.unity.burst@1.2/manual/index.html">Burst</a>, which is a compiler for generating highly optimized native code.  To use it, you have to write code in a heavily limited version of C# that only supports basic primitive types (<code class="language-plaintext highlighter-rouge">int</code>, <code class="language-plaintext highlighter-rouge">float</code>, <code class="language-plaintext highlighter-rouge">struct</code>, etc), plus some math types (<code class="language-plaintext highlighter-rouge">float2, float3x3</code>, etc).  Burst is designed to work within the new <a href="https://docs.unity3d.com/Manual/JobSystem.html">C# Job System</a> thing, so you’ll probably get funneled into using that too, unless you want to make life hard, in which case it <a href="https://forum.unity.com/threads/burst-and-thread-safe-api-outside-unity-jobs-system.522737/">seems possible</a> to beat it into working outside of it.</p>

<p><img src="/assets/2020_rope_profiler_jobs.png" alt="Profiler, Jobs." width="1103px" height="426px" /></p>

<p><em>200 nodes, 80 iterations – Jobs/Burst version.</em></p>

<p>Our implementation is pretty data oriented already, so this transition isn’t as difficult as it could be, and the payoff is invaluable.  We go from ~2.6ms to ~0.5ms in the above example (&gt;5x speedup!).  This post is pretty long already, so I’m not going to walk through the refactor details here–I’ve uploaded a heavily commented jobs system version of the simulation to the GitHub repository for this post, <a href="https://github.com/Toqozz/blog-code/blob/master/rope/Assets/RopeJob.cs">here</a>.  On top of the actual cost being less perceptible, the jobs system version actually executes in a separate thread by default, meaning the performance of other scripts won’t pile on top of it and bring the baseline up, as I was concerned about before.  This does come at an ergonomic cost–multithreaded code generally does–but in this case the benefits are too important to give up.  To maximize this benefit, you’ll naturally want to set the script which executes the job first in <a href="https://docs.unity3d.com/Manual/class-MonoManager.html">Script Execution Order</a>.</p>

<p>Please note that both of these benchmarks represent a <em>good</em> case in terms of collision.  With most nodes colliding we see ~3.7ms from the regular version and ~0.8ms from the Jobs/Burst version.</p>

<p>Profiling was done on a Ryzen 1700.</p>

<h3 id="changing-rope-parameters-during-a-job">Changing Rope Parameters During a Job</h3>
<p>The ergonomic cost of running our code on a separate thread is that we can’t change rope properties at any time during runtime.  For example, we can’t change a node constraint while the job is running because the Unity Jobs system considers this type of behavior <em>unsafe</em>.  This is really problematic because if we want to use the rope for gameplay, we probably want to change constraints and other rope properties all the time.</p>

<p>There are a few potential workarounds here:</p>
<ul>
  <li>A command queue which takes all the rope change commands, and then applies them after the job has finished. – <strong>1 frame delay on input.</strong></li>
  <li>Make sure the rope script runs first in <a href="https://docs.unity3d.com/Manual/class-MonoManager.html">Script Execution Order</a> (you should be doing this already), and then only change rope properties in <code class="language-plaintext highlighter-rouge">LateUpdate()</code>. – <strong>1 frame delay on input.</strong></li>
  <li>Put scripts which change rope properties <em>even further</em> before the rope script in <a href="https://docs.unity3d.com/Manual/class-MonoManager.html">Script Execution Order</a>. – <strong>0 frame delay on input.</strong></li>
</ul>

<p>Once implemented, the first approach is the easiest to use, but it comes at the cost of potential garbage collection (if you use lambdas in your implementation) and input delay.  The second and third options have next to no implementation cost, but I generally recommend the second for stuff that isn’t mission critical because changing script execution order for everything is annoying.  This is what the script in the repo uses.</p>

<h2 id="render-your-own-line">Render Your Own Line</h2>
<p>If you’re lucky, this won’t be a problem.</p>

<p>Unity includes a built-in <code class="language-plaintext highlighter-rouge">LineRenderer</code> component which takes a list of vertices and mostly works fine.  Sometimes it doesn’t work fine though, and can make some lines pretty ugly:</p>

<p><img src="/assets/2020_linerenderer_bad.png" alt="Unity buggy line renderer." width="828px" height="468px" />
<img src="/assets/2020_linerenderer_bad2.png" alt="Unity buggy line renderer, part 2." width="884px" height="542px" />
<em>I love Unity woooo!</em></p>

<p>It turns out that good line rendering in general is a complicated problem.  If you’re interested in why, I highly recommend Matt Deslauriers’ <a href="https://mattdesl.svbtle.com/drawing-lines-is-hard">post</a> on it.  Cognizant of these complications, I advocate building something that renders our rope well, but doesn’t try to do too much else.  Rendering your own line is good if you can’t live with something a bit buggy, want more control, or just want to learn.  It’s also a bit faster than <code class="language-plaintext highlighter-rouge">LineRenderer</code>.</p>

<p>The line drawing approach is pretty simple.  We just want to draw a rectangle between each pair of nodes, and a circle at each node to join them together:</p>

<p><img src="/assets/2020_rope_rendering.png" alt="Line rendering diagram." width="1920px" height="1080px" /></p>

<p>We can render this with just a couple quads for each pair of nodes:</p>

<p><img src="/assets/2020_rope_rendering_triangles.png" alt="Line rendering diagram with tris." width="1920px" height="1080px" /></p>

<p>When it comes to actually computing this mesh, the basic solution is iterate through the line and calculate triangles, then create a mesh with the result.  This would be fine were we not updating the line each frame.  For a rope of 100 nodes (not uncommon for a high quality rope), that’s at least 800 operations (~2 quads per segment) every frame on the game’s main thread.  I haven’t tested this, and it would probably run <em>OK</em>, but if we can easily do something better, we should.</p>

<p>If your brain is heading towards “geometry shader” right now, cool!  This <em>would</em> be a great use for geometry shaders, but I’ve learned to avoid them as Metal (and so macOS) doesn’t support them, and they can also be <a href="http://www.joshbarczak.com/blog/?p=667">quite slow in general</a>.  The life of a geometry shader is really quite tragic.</p>

<p>Since our line has a fixed number of vertices, we can sort of imitate a geometry shader by creating a mesh with the right number of vertices from the beginning, and then moving those vertices to the right positions in the vertex shader.  It’s not as ergonomic as a geometry shader would be, but it’s pretty fast and works on all platforms.</p>

<p>Here’s a more concrete version of what we need to do:</p>
<ol>
  <li><strong>C# / Unity:</strong> Create a mesh with the right number of vertices (<code class="language-plaintext highlighter-rouge">totalNodes * 8</code>).</li>
  <li><strong>C# / Unity:</strong> Calculate and assign triangle indices.</li>
  <li><strong>Vertex Shader:</strong> Figure out which vertices relate to which rope segments, and move them to the right place.</li>
</ol>

<p>The first and second are both part of creating the mesh.  The main thing to remember when calculating triangle indices is that Unity uses a clockwise winding order, so we want our triangle indices to rotate clockwise to indicate they’re facing the camera and shouldn’t get culled;</p>

<blockquote>
  <p><a href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Read more about winding and face culling order here</a>.</p>
</blockquote>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Mesh</span> <span class="n">mesh</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Mesh</span><span class="p">();</span>
<span class="p">{</span>
    <span class="n">Vector3</span><span class="p">[]</span> <span class="n">vertices</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Vector3</span><span class="p">[</span><span class="n">totalNodes</span> <span class="p">*</span> <span class="n">VERTICES_PER_NODE</span><span class="p">];</span>
    <span class="kt">int</span><span class="p">[]</span> <span class="n">triangles</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[</span><span class="n">totalNodes</span> <span class="p">*</span> <span class="n">TRIANGLES_PER_NODE</span> <span class="p">*</span> <span class="m">3</span><span class="p">];</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">totalNodes</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
        <span class="c1">// 4 triangles per node, 3 indices per triangle.</span>
        <span class="kt">int</span> <span class="n">idx</span> <span class="p">=</span> <span class="n">i</span> <span class="p">*</span> <span class="n">TRIANGLES_PER_NODE</span> <span class="p">*</span> <span class="m">3</span><span class="p">;</span>
        <span class="c1">// 8 vertices per node.</span>
        <span class="kt">int</span> <span class="n">vIdx</span> <span class="p">=</span> <span class="n">i</span> <span class="p">*</span> <span class="n">VERTICES_PER_NODE</span><span class="p">;</span>

        <span class="c1">// Rect between segments.</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">0</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">;</span>        <span class="c1">// v1 top</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">1</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">1</span><span class="p">;</span>      <span class="c1">// v2 bottom</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">2</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">2</span><span class="p">;</span>      <span class="c1">// v1 bottom</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">3</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">;</span>        <span class="c1">// v1 top</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">4</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">3</span><span class="p">;</span>      <span class="c1">// v2 top</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">5</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">1</span><span class="p">;</span>      <span class="c1">// v2 bottom</span>

        <span class="c1">// End cap quad.</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">6</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">4</span><span class="p">;</span>      <span class="c1">// tl</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">7</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">7</span><span class="p">;</span>      <span class="c1">// br</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">8</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">6</span><span class="p">;</span>      <span class="c1">// bl</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">9</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">4</span><span class="p">;</span>      <span class="c1">// tl</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">10</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">5</span><span class="p">;</span>     <span class="c1">// tr</span>
        <span class="n">triangles</span><span class="p">[</span><span class="n">idx</span> <span class="p">+</span> <span class="m">11</span><span class="p">]</span> <span class="p">=</span> <span class="n">vIdx</span><span class="p">+</span><span class="m">7</span><span class="p">;</span>     <span class="c1">// br</span>
    <span class="p">}</span>

    <span class="c1">// We only really care about the number of vertices, not what they actually are -- the positions aren't used.</span>
    <span class="n">mesh</span><span class="p">.</span><span class="n">vertices</span> <span class="p">=</span> <span class="n">vertices</span><span class="p">;</span>
    <span class="n">mesh</span><span class="p">.</span><span class="n">triangles</span> <span class="p">=</span> <span class="n">triangles</span><span class="p">;</span>
    <span class="c1">// Since we pretty much want the rope to always render (it's always going to be on screen if it's active), we</span>
    <span class="c1">// just set the bounds super large to avoid recalculating the bounds when the rope changes.</span>
    <span class="n">mesh</span><span class="p">.</span><span class="n">bounds</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Bounds</span><span class="p">(</span><span class="n">Vector3</span><span class="p">.</span><span class="n">zero</span><span class="p">,</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">one</span> <span class="p">*</span> <span class="m">100f</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here’s a stripped version of the moving shader – check the full version on GitHub <a href="https://github.com/Toqozz/blog-code/blob/master/rope/Assets/RopeShader.shader">here</a>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v2f</span> <span class="nf">vert</span> <span class="p">(</span><span class="n">appdata</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">v2f</span> <span class="n">o</span><span class="p">;</span>
    <span class="n">o</span><span class="p">.</span><span class="n">uv</span> <span class="o">=</span> <span class="n">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

    <span class="c1">// The node that the current vertex is associated with.</span>
    <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">v</span><span class="p">.</span><span class="n">id</span> <span class="o">/</span> <span class="n">VERTICES_PER_NODE</span><span class="p">;</span>
    <span class="c1">// The next node, clamped to the max number of nodes.</span>
    <span class="kt">int</span> <span class="n">next_idx</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">MAX_NODE_COUNT</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">idx</span><span class="o">+</span><span class="mi">1</span><span class="p">);</span>
    <span class="n">float4</span> <span class="n">p1</span> <span class="o">=</span> <span class="n">_Points</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span>
    <span class="n">float4</span> <span class="n">p2</span> <span class="o">=</span> <span class="n">_Points</span><span class="p">[</span><span class="n">next_idx</span><span class="p">];</span>
    <span class="kt">int</span> <span class="n">id</span> <span class="o">=</span> <span class="n">v</span><span class="p">.</span><span class="n">id</span> <span class="o">%</span> <span class="n">VERTICES_PER_NODE</span><span class="p">;</span>

    <span class="n">float2</span> <span class="n">dir</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">p2</span><span class="p">.</span><span class="n">xy</span> <span class="o">-</span> <span class="n">p1</span><span class="p">.</span><span class="n">xy</span><span class="p">);</span>
    <span class="n">float2</span> <span class="n">perp</span> <span class="o">=</span> <span class="n">float2</span><span class="p">(</span><span class="o">-</span><span class="n">dir</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">dir</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>   <span class="c1">// Counter-clockwise perpendicular to dir.</span>

    <span class="c1">// Currently, each line segment has 8 vertices -- 4 for the line and 4 for the start cap.</span>
    <span class="c1">// Comments are written as though p1 -&gt; p2 is going left to right.</span>
    <span class="n">float2</span> <span class="n">pos</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>          <span class="c1">// Vertex 1, v1 top.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="n">perp</span> <span class="o">*</span> <span class="n">_Width</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// Vertex 2, v2 bottom.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p2</span><span class="p">.</span><span class="n">xy</span> <span class="o">-</span> <span class="n">perp</span> <span class="o">*</span> <span class="n">_Width</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// Vertex 3, v1 bottom.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">xy</span> <span class="o">-</span> <span class="n">perp</span> <span class="o">*</span> <span class="n">_Width</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// Vertex 4, v2 top.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p2</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="n">perp</span> <span class="o">*</span> <span class="n">_Width</span><span class="p">;</span>

    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// Vertex 5, cap tl.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="n">float2</span><span class="p">(</span><span class="o">-</span><span class="n">_Width</span><span class="p">,</span> <span class="n">_Width</span><span class="p">);</span>
        <span class="n">o</span><span class="p">.</span><span class="n">uv</span> <span class="o">=</span> <span class="n">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">5</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// Vertex 6, cap tr.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="n">float2</span><span class="p">(</span><span class="n">_Width</span><span class="p">,</span> <span class="n">_Width</span><span class="p">);</span>
        <span class="n">o</span><span class="p">.</span><span class="n">uv</span> <span class="o">=</span> <span class="n">float2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">6</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// Vertex 7, cap bl.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="n">float2</span><span class="p">(</span><span class="o">-</span><span class="n">_Width</span><span class="p">,</span> <span class="o">-</span><span class="n">_Width</span><span class="p">);</span>
        <span class="n">o</span><span class="p">.</span><span class="n">uv</span> <span class="o">=</span> <span class="n">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">7</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// Vertex 8, cap br.</span>
        <span class="n">pos</span> <span class="o">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="n">float2</span><span class="p">(</span><span class="n">_Width</span><span class="p">,</span> <span class="o">-</span><span class="n">_Width</span><span class="p">);</span>
        <span class="n">o</span><span class="p">.</span><span class="n">uv</span> <span class="o">=</span> <span class="n">float2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">o</span><span class="p">.</span><span class="n">clipPos</span> <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">UNITY_MATRIX_VP</span><span class="p">,</span> <span class="n">float4</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">));</span>

    <span class="k">return</span> <span class="n">o</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">fixed4</span> <span class="n">frag</span> <span class="p">(</span><span class="n">v2f</span> <span class="n">i</span><span class="p">)</span> <span class="o">:</span> <span class="n">SV_Target</span> <span class="p">{</span>
    <span class="n">float4</span> <span class="n">col</span><span class="p">;</span>
    <span class="c1">// Could replace this `if` easily with lerp, but this is more readable for now.</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">i</span><span class="p">.</span><span class="n">endCap</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">float</span> <span class="n">dist</span> <span class="o">=</span> <span class="n">distance</span><span class="p">(</span><span class="n">i</span><span class="p">.</span><span class="n">uv</span><span class="p">,</span> <span class="n">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">));</span>
        <span class="n">col</span> <span class="o">=</span> <span class="n">_Color</span><span class="p">;</span>
        <span class="n">col</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="n">step</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span> <span class="o">-</span> <span class="n">dist</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">col</span> <span class="o">=</span> <span class="n">_Color</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">col</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Experienced developers, don’t be too upset at me for manually getting <code class="language-plaintext highlighter-rouge">id</code> in the shader, and then using an <code class="language-plaintext highlighter-rouge">if ... else</code> chain to move vertices.  This code is plenty fast (~0.006ms in <code class="language-plaintext highlighter-rouge">RenderDoc</code>) and serves its purpose in a straightforward way.</p>

<p>For those wanting something more efficient, or wondering on best practices, the best way to do this is to assign the right information to vertices directly using <a href="https://docs.unity3d.com/ScriptReference/Mesh.SetVertexBufferParams.html"><code class="language-plaintext highlighter-rouge">SetVertexBufferParams()</code></a> to avoid the <code class="language-plaintext highlighter-rouge">if ... else</code> chain.  Non-Unity users, look for something related to <code class="language-plaintext highlighter-rouge">VertexAttributes</code> in your engine / graphics library.</p>

<h2 id="questions">Questions</h2>
<h3 id="i-downloaded-the-example-project-off-github-and-i-get-errors-about-float2-not-existing-and-stuff-like-that">I downloaded the example project off GitHub and I get errors about <code class="language-plaintext highlighter-rouge">float2</code> not existing and stuff like that.</h3>
<p>Make sure the <code class="language-plaintext highlighter-rouge">Burst</code> package is installed.  This post was written using <code class="language-plaintext highlighter-rouge">version 1.2.3</code>. (In the Unity editor: Window -&gt; Package Manager -&gt; All Packages (in the dropdown) -&gt; Search for Burst)</p>

<h3 id="in-the-non-burst-version-arent-structs-faster-than-classes">In the non-Burst version, aren’t structs faster than classes?</h3>
<p>In my testing, marginally (~5%).  This seems worth doing in production, and is even required for using the burst compiler, but adds noise to the code snippets in the post, so I decided not to include this optimization in writing.  The Burst version in the <a href="https://github.com/Toqozz/blog-code/tree/master/rope">repo</a> uses structs.</p>

<h3 id="in-snapshotcollisions-isnt-it-wasteful-to-throw-away-old-collisioninfos">In <code class="language-plaintext highlighter-rouge">SnapshotCollisions()</code>, isn’t it wasteful to throw away old <code class="language-plaintext highlighter-rouge">CollisionInfo</code>s?</h3>
<p>If your colliders are static, yes.  You could optimize this for a large number of colliders by implementing some sort of cache which holds <code class="language-plaintext highlighter-rouge">CollisionInfo</code>s for longer than a physics tick if they’re used frequently.  Even if they aren’t static, I’m sure there’s gains to be had from only updating changed data such as <code class="language-plaintext highlighter-rouge">position</code>.</p>

<p>Having said that, <code class="language-plaintext highlighter-rouge">SnapshotCollision()</code> only takes about ~0.2ms on my machine, so profile first to check if this is actually worth doing.</p>

<h3 id="the-ropecs-version-on-github-doesnt-use-fixedupdate-like-in-the-example-snippet-what-gives">The <code class="language-plaintext highlighter-rouge">Rope.cs</code> version on GitHub doesn’t use <code class="language-plaintext highlighter-rouge">FixedUpdate()</code> like in the example snippet, what gives?</h3>
<p>The GitHub version uses a custom fixed update cycle rather than the built-in <code class="language-plaintext highlighter-rouge">FixedUpdate()</code>.  The primary reason for this is control.  If rope is an important mechanic in your game, it can be useful to be able to change its tick rate independently from regular physics.  I also haven’t talked about any interpolation here, so I really wanted to provide a custom update implementation.</p>

<p>A custom update is also required for the Jobs version, so we save ourselves some work on the refactor.</p>

<h3 id="collision-doesnt-work-properly-when-sprites-are-using-spritedrawmodetiled-or-spritedrawmodesliced">Collision doesn’t work properly when sprites are using <code class="language-plaintext highlighter-rouge">SpriteDrawMode.Tiled</code> or <code class="language-plaintext highlighter-rouge">SpriteDrawMode.Sliced</code>.</h3>
<p>This is because Unity has decided to separate this property entirely from the transform, and scales sprites all on its own.  This means we can have an oddly scaled sprite even with a scale of <code class="language-plaintext highlighter-rouge">(1, 1, 1)</code>.  I’m sure there’s a good reason for this, but it’s really annoying.</p>

<p>To remedy this we can manually create the transformation matrix with the scale adjusted when we snapshot collisions:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">CollisionInfo</span> <span class="n">ci</span> <span class="p">=</span> <span class="n">collisionInfos</span><span class="p">[</span><span class="n">numCollisions</span><span class="p">];</span>
<span class="n">ci</span><span class="p">.</span><span class="n">id</span> <span class="p">=</span> <span class="n">id</span><span class="p">;</span>
<span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">math</span><span class="p">.</span><span class="nf">length</span><span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">ltw</span><span class="p">.</span><span class="n">c0</span><span class="p">.</span><span class="n">xyz</span><span class="p">);</span>
<span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">math</span><span class="p">.</span><span class="nf">length</span><span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">ltw</span><span class="p">.</span><span class="n">c1</span><span class="p">.</span><span class="n">xyz</span><span class="p">);</span>
<span class="n">ci</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="p">(</span><span class="n">Vector2</span><span class="p">)</span> <span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">;</span>
<span class="n">ci</span><span class="p">.</span><span class="n">numCollisions</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span> <span class="c1">// 1 collision, this one.</span>

<span class="kt">var</span> <span class="n">sr</span> <span class="p">=</span> <span class="n">col</span><span class="p">.</span><span class="n">GetComponent</span><span class="p">&lt;</span><span class="n">SpriteRenderer</span><span class="p">&gt;();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">sr</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">sr</span><span class="p">.</span><span class="n">drawMode</span> <span class="p">!=</span> <span class="n">SpriteDrawMode</span><span class="p">.</span><span class="n">Simple</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">x</span> <span class="p">*=</span> <span class="n">sr</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
    <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">y</span> <span class="p">*=</span> <span class="n">sr</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">y</span><span class="p">;</span>
    <span class="n">ci</span><span class="p">.</span><span class="n">ltw</span> <span class="p">=</span> <span class="n">float4x4</span><span class="p">.</span><span class="nf">TRS</span><span class="p">(</span><span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">rotation</span><span class="p">,</span> <span class="n">math</span><span class="p">.</span><span class="nf">float3</span><span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="m">1f</span><span class="p">));</span>
    <span class="n">ci</span><span class="p">.</span><span class="n">wtl</span> <span class="p">=</span> <span class="n">math</span><span class="p">.</span><span class="nf">inverse</span><span class="p">(</span><span class="n">ci</span><span class="p">.</span><span class="n">ltw</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> 
    <span class="n">ci</span><span class="p">.</span><span class="n">wtl</span> <span class="p">=</span> <span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">worldToLocalMatrix</span><span class="p">;</span>
    <span class="n">ci</span><span class="p">.</span><span class="n">ltw</span> <span class="p">=</span> <span class="n">col</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">localToWorldMatrix</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This isn’t without issue.  For some reason the “real” collider is slightly bigger than what we arrive at with a manually constructed matrix:</p>

<p><img src="/assets/2020_sliced_collider.png" alt="Sliced collider boundaries." width="671px" height="379px" /></p>

<p>My guess is that this is related to slice boundaries.</p>

<p>Another solution is to just have the collider separate from the sprite and scale it normally.</p>

<h3 id="the-collider2doffset-property-doesnt-work">The <code class="language-plaintext highlighter-rouge">Collider2D.offset</code> property doesn’t work.</h3>
<p>The <code class="language-plaintext highlighter-rouge">offset</code> property doesn’t work because the collision code hasn’t implemented it.  If you need <code class="language-plaintext highlighter-rouge">offset</code>, you can implement it quite easily for boxes by first of all adding a <code class="language-plaintext highlighter-rouge">CollisionInfo.offset</code> property, and then applying it at the appropriate point in collision:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Box collision:</span>

<span class="c1">// If distance from center is more than box "radius", then we can't be colliding.</span>
<span class="n">Vector2</span> <span class="n">half</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">colliderSize</span> <span class="p">*</span> <span class="p">.</span><span class="m">5f</span><span class="p">;</span>
<span class="n">Vector2</span> <span class="n">scalar</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">scale</span><span class="p">;</span>
<span class="c1">//float dx = localPoint.x;</span>
<span class="kt">float</span> <span class="n">dx</span> <span class="p">=</span> <span class="n">localPoint</span><span class="p">.</span><span class="n">x</span> <span class="p">-</span> <span class="n">ci</span><span class="p">.</span><span class="n">offset</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">px</span> <span class="p">=</span> <span class="n">half</span><span class="p">.</span><span class="n">x</span> <span class="p">-</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Abs</span><span class="p">(</span><span class="n">dx</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">px</span> <span class="p">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">continue</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">//float dy = localPoint.y;</span>
<span class="kt">float</span> <span class="n">dy</span> <span class="p">=</span> <span class="n">localPoint</span><span class="p">.</span><span class="n">y</span> <span class="p">-</span> <span class="n">ci</span><span class="p">.</span><span class="n">offset</span><span class="p">.</span><span class="n">y</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">py</span> <span class="p">=</span> <span class="n">half</span><span class="p">.</span><span class="n">y</span> <span class="p">-</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Abs</span><span class="p">(</span><span class="n">dy</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">py</span> <span class="p">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">continue</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Need to multiply distance by scale or we'll mess up on scaled box corners.</span>
<span class="k">if</span> <span class="p">(</span><span class="n">px</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">.</span><span class="n">x</span> <span class="p">&lt;</span> <span class="n">py</span> <span class="p">*</span> <span class="n">scalar</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">float</span> <span class="n">sx</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Sign</span><span class="p">(</span><span class="n">dx</span><span class="p">);</span>
    <span class="c1">//localPoint.x = half.x * sx;</span>
    <span class="n">localPoint</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">offset</span><span class="p">.</span><span class="n">x</span> <span class="p">+</span> <span class="n">half</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">sx</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="kt">float</span> <span class="n">sy</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Sign</span><span class="p">(</span><span class="n">dy</span><span class="p">);</span>
    <span class="c1">//localPoint.y = half.y * sy;</span>
    <span class="n">localPoint</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">ci</span><span class="p">.</span><span class="n">offset</span><span class="p">.</span><span class="n">y</span> <span class="p">+</span> <span class="n">half</span><span class="p">.</span><span class="n">y</span> <span class="p">*</span> <span class="n">sy</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Note that <code class="language-plaintext highlighter-rouge">offset</code> is in local space, so we can’t easily apply it to <code class="language-plaintext highlighter-rouge">Circle</code> colliders with the provided implementation.  You’ll need to switch to something which transforms to local space (slow), like in the box collision method.</p>

<h3 id="how-can-i-prevent-tunneling">How can I prevent tunneling?</h3>
<p>My first and easiest suggestion is to try to reduce <code class="language-plaintext highlighter-rouge">maxSimMove</code> and decrease step time.  This should reduce tunneling by stopping nodes from moving too much in a single step.  However, if your colliders are small or you need a particularly fast moving rope, then this isn’t a good solution.</p>

<p>Ideally we’d raycast between the previous and current position to detect and prevent tunneling.  This is pretty complicated, and very slow if done naively.  We’d probably have to implement some kind of raycasting ourselves!</p>

<p>The best quick and dirty solution I’ve got for you is to keep track of node positions before adjusting constraints, and then do multiple collision checks at the collision phase (<code class="language-plaintext highlighter-rouge">AdjustCollisions()</code>), incrementing from the previous positions to the new position until a collision is found (if any).  This should stop tunneling with a small enough increment.  To take this idea to the next level, you probably want to implement a <a href="https://stackoverflow.com/questions/3746274/line-intersection-with-aabb-rectangle">line intersection</a> test (i.e. raycasting).</p>

<hr />

<h2 id="learnings-from-the-future">Learnings From The Future</h2>
<p><em>2025-08-07</em></p>

<p>I got the chance to implement (more like fix) a 3D cloth sim in production using some of these learnings.  Along the way, many more learnings were had, which I’m going to share here in the hopes that they might save you some time.  I’m mostly talking about cloth here, but I think most advice will apply to rope as well.</p>

<h3 id="teleporting">Teleporting</h3>
<p>In games, we teleport players and objects around a lot; loading things in, positioning things for cutscenes, rotating to look at things, etc.  This has high potential of doing things with your rope/cloth that make it look janky and bad!</p>

<p>To stay on top of this, you want to exert as much control over your simulation as possible.  For cloth at least (which is usually attached to something) this means simulating in local space.  Then, when the player moves/rotates/jumps you manually inject that as an external force into the sim.</p>

<p>With this newfound supervision over the sim, you can even and can even make creative decisions such as how much rotation should affect things in comparison to movement, or how much of the player’s animation goes into the movement of the cloth.</p>

<p>You’ll still have issues with dramatic changes to the local space though (e.g. snapping to different animations).  For our game, this mostly happens in transitional periods, so I was able to implement a <code class="language-plaintext highlighter-rouge">Settle()</code> function which just steps the simulation a fixed number of times on the spot.  Far from perfect, but reasonable.</p>

<h3 id="performancemake-it-less-stretchy">Performance/Make It Less Stretchy</h3>
<p>As you increase the number of nodes, it becomes more and more difficult to manage the “stretchyness” of the spring without nuking performance.  An answer here is to layer in a new type of constraint—the hard constraint.  Hard constraints are meant to be solved in a specific order (e.g. top to bottom) and instead of moving both vertices an equal amount toward each other, we just move the bottom one the full distance to the top.  This way, the “length” of the rope/cloth can be solved in just 1 iteration.</p>

<p>In this system, the update loop looks something like this:</p>
<ul>
  <li>Calculate external forces.</li>
  <li>Step (some number of times).
    <ul>
      <li>Apply external forces.</li>
      <li>Update integration.</li>
      <li>Iterate (as many times as suitable).
        <ul>
          <li>Process collision.</li>
          <li>Process stretch constraints.</li>
          <li>Process hard constraints.</li>
        </ul>
      </li>
      <li>Process collision one more time.</li>
    </ul>
  </li>
  <li>Step one more time, and interpolate towards the result.</li>
</ul>

<blockquote>
  <p>Great article with some more pertinent information here: <a href="https://www.gamedeveloper.com/programming/the-secrets-of-cloth-simulation-in-i-alan-wake-i-">https://www.gamedeveloper.com/programming/the-secrets-of-cloth-simulation-in-i-alan-wake-i-</a>.</p>
</blockquote>

<p>For cloth, the order that you apply your constraints can be important even if we’re just talking about stretch constraints.  I’m not sure if there is a <em>right way</em> to do it, but I ended up just averaging the post-constraint positions to stop it mattering.</p>

<h3 id="you-really-want-to-interpolate">You Really Want To Interpolate</h3>
<p>A fixed timestep is definitely the way to go, but does come with some obstacles.  Unless you’re stepping your simulation faster than the game’s framerate, you’ll might see your rope/cloth clipping through collision objects, particularly during continuous movement (e.g. a character raising their knee while wearing a dress).  There’s also the obvious one of the simulation being jittery because it doesn’t match the game’s framerate.</p>

<p>Interpolation solves most of the headaches here.  Basically, you run 1 more tentative simulation step <em>every frame</em>, and then blend towards that result for rendering.  Next frame, you throw that tentative step away and do it again.  Not only does this effectively make the simulation perfectly smooth, but it means you get <em>some</em> response on the first frame that the action (collider moving, wind force, etc) happens.</p>

<blockquote>
  <p>Check out <a href="https://www.gafferongames.com/post/fix_your_timestep/">Fix Your Timestep!</a> for a more detailed explanation (near the bottom).</p>
</blockquote>

<p>I’ve also seen people step collision every frame regardless of interpolation, to make super sure that frame is presented with any clipping.  I don’t feel like this is a bad option if avoiding clipping is very important for you, even if it does mess with the simulation a bit.</p>

<h3 id="theres-a-hitman-paper">There’s a Hitman Paper</h3>
<p>Turns out the first Hitman game used Verlet integration for its physics.  It covers Verlet integration in much more depth and is probably one of the best resources out there that I didn’t know about at the time of writing.</p>
<ul>
  <li><a href="https://graphics.cs.cmu.edu/nsp/course/15-869/2006/papers/jakobsen.htm">https://graphics.cs.cmu.edu/nsp/course/15-869/2006/papers/jakobsen.htm</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Object Pooling in Unity</title><link href="https://toqoz.fyi/object-pooling.html" rel="alternate" type="text/html" title="Object Pooling in Unity" /><published>2019-11-02T00:00:00+00:00</published><updated>2019-11-02T00:00:00+00:00</updated><id>https://toqoz.fyi/object-pooling</id><content type="html" xml:base="https://toqoz.fyi/object-pooling.html"><![CDATA[<p><img src="/assets/2019_projectile_spam.gif" alt="Demo gif" width="918px" height="514px" />
<a href="https://docs.unity3d.com/ScriptReference/Object.Instantiate.html"><code class="language-plaintext highlighter-rouge">Instantiate()</code></a> is expensive.  If possible, you should never use it at runtime.  For one-offs this is usually done by spawning whatever objects you need in <code class="language-plaintext highlighter-rouge">Awake()</code>, and then calling <code class="language-plaintext highlighter-rouge">SetActive(true)</code> when you need them.  For more than one-offs, you probably want to use a pool.
A pool is a group of objects that you instantiate at some convenient time (probably on scene load), and then later on, when you want to spawn an object, you grab it from the pool instead of making it fresh.  This technique is used extensively throughout just about any well-programmed game.</p>

<blockquote>
  <p>A good rule is to never call <code class="language-plaintext highlighter-rouge">Instantiate()</code> when the player has control.</p>
</blockquote>

<h2 id="a-dead-simple-pool">A Dead Simple Pool</h2>
<p>Here’s a simple, performant pool you can use:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Pool</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="p">[</span><span class="n">Serializable</span><span class="p">]</span> <span class="c1">// Let this appear in the inspector.</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">ObjectPool</span> <span class="p">{</span>
        <span class="k">public</span> <span class="kt">int</span> <span class="n">amount</span><span class="p">;</span>
        <span class="k">public</span> <span class="n">PooledObject</span> <span class="n">objectToPool</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Singleton boilerplate.</span>
    <span class="k">private</span> <span class="k">static</span> <span class="n">Pool</span> <span class="n">_instance</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">Pool</span> <span class="n">Instance</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(!</span><span class="n">_instance</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">_instance</span> <span class="p">=</span> <span class="n">FindObjectOfType</span><span class="p">&lt;</span><span class="n">Pool</span><span class="p">&gt;();</span>
            <span class="p">}</span>

            <span class="k">return</span> <span class="n">_instance</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// List of objects to pool (only used for instantiation)</span>
    <span class="k">public</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">ObjectPool</span><span class="p">&gt;</span> <span class="n">objectPools</span><span class="p">;</span>

    <span class="c1">// Pool of objects, indexed by instance ID.</span>
    <span class="c1">// A queue works pretty naturally here.</span>
    <span class="k">private</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">Queue</span><span class="p">&lt;</span><span class="n">PooledObject</span><span class="p">&gt;&gt;</span> <span class="n">pool</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Awake</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Spawn all objects in provided pools.</span>
        <span class="n">pool</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">Queue</span><span class="p">&lt;</span><span class="n">PooledObject</span><span class="p">&gt;&gt;();</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="n">ObjectPool</span> <span class="n">objPool</span> <span class="k">in</span> <span class="n">objectPools</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">int</span> <span class="n">amount</span> <span class="p">=</span> <span class="n">objPool</span><span class="p">.</span><span class="n">amount</span><span class="p">;</span>
            <span class="n">PooledObject</span> <span class="n">obj</span> <span class="p">=</span> <span class="n">objPool</span><span class="p">.</span><span class="n">objectToPool</span><span class="p">;</span>

            <span class="c1">// Saved prefabs have an instance id, which we can use to talk about the same prefab from other scripts.</span>
            <span class="kt">int</span> <span class="n">id</span> <span class="p">=</span> <span class="n">obj</span><span class="p">.</span><span class="nf">GetInstanceID</span><span class="p">();</span>
            <span class="n">Queue</span><span class="p">&lt;</span><span class="n">PooledObject</span><span class="p">&gt;</span> <span class="n">queue</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Queue</span><span class="p">&lt;</span><span class="n">PooledObject</span><span class="p">&gt;(</span><span class="n">amount</span><span class="p">);</span>
            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">amount</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
                <span class="kt">var</span> <span class="n">clone</span> <span class="p">=</span> <span class="nf">Instantiate</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">transform</span><span class="p">);</span>
                <span class="n">clone</span><span class="p">.</span><span class="n">id</span> <span class="p">=</span> <span class="n">id</span><span class="p">;</span>
                <span class="n">clone</span><span class="p">.</span><span class="n">Finished</span> <span class="p">+=</span> <span class="n">ReQueue</span><span class="p">;</span>      <span class="c1">// When `Finish()` is called, we put our object back in the queue.</span>
                <span class="n">clone</span><span class="p">.</span><span class="n">gameObject</span><span class="p">.</span><span class="nf">SetActive</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
                <span class="n">queue</span><span class="p">.</span><span class="nf">Enqueue</span><span class="p">(</span><span class="n">clone</span><span class="p">);</span>
            <span class="p">}</span>

            <span class="n">pool</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">queue</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="n">PooledObject</span> <span class="nf">GetNextObject</span><span class="p">(</span><span class="n">PooledObject</span> <span class="n">obj</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// @NOTE: create a new queue if none exists, for pooling "unplanned" objects?</span>
        <span class="kt">var</span> <span class="n">queue</span> <span class="p">=</span> <span class="n">pool</span><span class="p">[</span><span class="n">obj</span><span class="p">.</span><span class="nf">GetInstanceID</span><span class="p">()];</span>
        <span class="n">PooledObject</span> <span class="n">clone</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
        <span class="c1">// If queue is empty (has been exhausted -- the pool size was too small), extend the queue by instantiating a new object,</span>
        <span class="c1">// and add it to the future queue.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">queue</span><span class="p">.</span><span class="n">Count</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">Debug</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"Object Pool queue was empty; wasn't able to get a new pooled object, so one will be instatiated."</span><span class="p">);</span>
            <span class="n">clone</span> <span class="p">=</span> <span class="nf">Instantiate</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">transform</span><span class="p">);</span>
            <span class="n">clone</span><span class="p">.</span><span class="n">id</span> <span class="p">=</span> <span class="n">obj</span><span class="p">.</span><span class="nf">GetInstanceID</span><span class="p">();</span>
            <span class="n">clone</span><span class="p">.</span><span class="n">Finished</span> <span class="p">+=</span> <span class="n">ReQueue</span><span class="p">;</span>
            <span class="n">clone</span><span class="p">.</span><span class="n">gameObject</span><span class="p">.</span><span class="nf">SetActive</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">clone</span> <span class="p">=</span> <span class="n">queue</span><span class="p">.</span><span class="nf">Dequeue</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">clone</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Gets an object from the pool and returns it after setting position, rotation, and active.</span>
    <span class="k">public</span> <span class="n">PooledObject</span> <span class="nf">Spawn</span><span class="p">(</span><span class="n">PooledObject</span> <span class="n">obj</span><span class="p">,</span> <span class="n">Vector3</span> <span class="n">position</span><span class="p">,</span> <span class="n">Quaternion</span> <span class="n">rotation</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">var</span> <span class="n">clone</span> <span class="p">=</span> <span class="nf">GetNextObject</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span>
        <span class="n">clone</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span> <span class="p">=</span> <span class="n">position</span><span class="p">;</span>
        <span class="n">clone</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">rotation</span> <span class="p">=</span> <span class="n">rotation</span><span class="p">;</span>
        <span class="n">clone</span><span class="p">.</span><span class="n">gameObject</span><span class="p">.</span><span class="nf">SetActive</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">clone</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">ReQueue</span><span class="p">(</span><span class="n">PooledObject</span> <span class="n">obj</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Hide object and insert back in queue for reuse.</span>
        <span class="n">obj</span><span class="p">.</span><span class="n">gameObject</span><span class="p">.</span><span class="nf">SetActive</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
        <span class="kt">var</span> <span class="n">queue</span> <span class="p">=</span> <span class="n">pool</span><span class="p">[</span><span class="n">obj</span><span class="p">.</span><span class="n">id</span><span class="p">];</span>
        <span class="n">queue</span><span class="p">.</span><span class="nf">Enqueue</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">PooledObject</code> is teeny tiny:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PooledObject</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="p">[</span><span class="n">HideInInspector</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">id</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">PooledObject</span><span class="p">&gt;</span> <span class="n">Finished</span><span class="p">;</span>

    <span class="c1">// A component reference for fast access -- avoids calls to GetComponent&lt;&gt;().</span>
    <span class="k">public</span> <span class="n">Component</span> <span class="n">behaviour</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">T</span> <span class="n">As</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;()</span> <span class="k">where</span> <span class="n">T</span><span class="p">:</span> <span class="n">Component</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">behaviour</span> <span class="k">as</span> <span class="n">T</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Finish</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">Finished</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">Finished</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Convenience method to call finish when particles finish.</span>
    <span class="c1">// Needs ParticleSystem stop action to be set to "Callback".</span>
    <span class="k">private</span> <span class="k">void</span> <span class="nf">OnParticleSystemStopped</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">Finish</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="usage">Usage</h3>
<p>Usage is as straightforward as possible.  Create a new Game Object in your scene and attach the <code class="language-plaintext highlighter-rouge">Pool</code> script.  Then, attach the <code class="language-plaintext highlighter-rouge">PooledObject</code> script to any prefabs that you want to be pooled, and drag them into the object pools list, with a best-guess for how many will be used concurrently:</p>

<blockquote>
  <p>If you exceed the amount in the object pool, new ones will be spawned with <code class="language-plaintext highlighter-rouge">Instantiate()</code> rather than failing.</p>
</blockquote>

<p><img src="/assets/2019_create_pool.gif" alt="Setup process demonstration" width="560px" height="594px" /></p>

<p>Then, instead of calling <code class="language-plaintext highlighter-rouge">Instatiate()</code> and <code class="language-plaintext highlighter-rouge">Destroy()</code>, we call <code class="language-plaintext highlighter-rouge">Pool.Spawn()</code> and <code class="language-plaintext highlighter-rouge">PooledObject.Finish()</code>:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProjectileSpawner</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">spawnRate</span> <span class="p">=</span> <span class="m">0.1f</span><span class="p">;</span>
<span class="p">++</span>  <span class="k">public</span> <span class="n">PooledObject</span> <span class="n">projectile</span><span class="p">;</span>
<span class="p">--</span>  <span class="k">public</span> <span class="n">Projectile</span> <span class="n">projectile</span><span class="p">;</span>

    <span class="k">private</span> <span class="kt">float</span> <span class="n">timer</span> <span class="p">=</span> <span class="m">0f</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timer</span> <span class="p">+=</span> <span class="n">Time</span><span class="p">.</span><span class="n">deltaTime</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">timer</span> <span class="p">&gt;</span> <span class="n">spawnRate</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">timer</span> <span class="p">-=</span> <span class="n">spawnRate</span><span class="p">;</span>

            <span class="c1">// Spawn object with random 2D rotation.</span>
<span class="p">++</span>          <span class="n">PooledObject</span> <span class="n">instance</span> <span class="p">=</span>
<span class="p">++</span>              <span class="n">Pool</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">Spawn</span><span class="p">(</span><span class="n">projectile</span><span class="p">,</span> <span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">Euler</span><span class="p">(</span><span class="m">0f</span><span class="p">,</span> <span class="m">0f</span><span class="p">,</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="m">0f</span><span class="p">,</span> <span class="m">360f</span><span class="p">)));</span>
            <span class="c1">// We can avoid GetComponent&lt;&gt;() for a frequently accessed component, which is nice.</span>
<span class="p">++</span>          <span class="n">instance</span><span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">Projectile</span><span class="p">&gt;().</span><span class="n">speed</span> <span class="p">=</span> <span class="n">Range</span><span class="p">.</span><span class="nf">Range</span><span class="p">(.</span><span class="m">5f</span><span class="p">,</span> <span class="m">1f</span><span class="p">);</span>

<span class="p">--</span>          <span class="n">Projectile</span> <span class="n">instance</span> <span class="p">=</span>
<span class="p">--</span>              <span class="nf">Instantiate</span><span class="p">(</span><span class="n">projectile</span><span class="p">,</span> <span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">Euler</span><span class="p">(</span><span class="m">0f</span><span class="p">,</span> <span class="m">0f</span><span class="p">,</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="m">0f</span><span class="p">,</span> <span class="m">360f</span><span class="p">)));</span>
<span class="p">--</span>          <span class="n">instance</span><span class="p">.</span><span class="n">speed</span> <span class="p">=</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(.</span><span class="m">5f</span><span class="p">,</span> <span class="m">1f</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Projectile</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="p">...</span>
    <span class="k">private</span> <span class="k">void</span> <span class="nf">OnTriggerEnter2D</span><span class="p">(</span><span class="n">Collider2D</span> <span class="n">other</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">...</span>

<span class="p">++</span>      <span class="n">GetComponent</span><span class="p">&lt;</span><span class="n">PooledObject</span><span class="p">&gt;().</span><span class="nf">Finish</span><span class="p">();</span>
<span class="p">--</span>      <span class="nf">Destroy</span><span class="p">(</span><span class="n">gameObject</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>It would be better to cache the <code class="language-plaintext highlighter-rouge">PooledObject</code> component.  <code class="language-plaintext highlighter-rouge">GetComponent&lt;&gt;()</code> has been used here for simplicity.</p>
</blockquote>

<p><img src="/assets/2019_projectile_spawner.gif" alt="Projectile spawner with pool" width="1018px" height="613px" /></p>

<h2 id="instantiate-doesnt-care-but-setactive-does"><code class="language-plaintext highlighter-rouge">Instantiate()</code> doesn’t care, but <code class="language-plaintext highlighter-rouge">SetActive()</code> does</h2>
<p>The really nice thing about using <code class="language-plaintext highlighter-rouge">Instatiate()</code> and <code class="language-plaintext highlighter-rouge">Destroy()</code> is that you don’t have to worry about any kind of previous state on the object – everything is new and fresh.  If you’re disabling and re-enabling objects (like in a pool), you <strong>do</strong> have to pay attention to object state; particle progress, animations, and any variables changed on components can all trip you up.  There are ways you could reset components to fresh (serialization), but if you want to keep your performance intact, this is just something that you’re going to have to eat, sorry.</p>

<hr />

<h2 id="the-numbers">The Numbers</h2>
<p>What good is an optimization without profiling?</p>

<p>I’m testing this with a modified version of the above <code class="language-plaintext highlighter-rouge">ProjectileSpawner</code> code.  To avoid variance, I’ve removed the randomness and some other stuff;</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">BenchmarkProjectileSpawner</span><span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">delay</span> <span class="p">=</span> <span class="m">5f</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">amount</span> <span class="p">=</span> <span class="m">5000</span><span class="p">;</span>

    <span class="c1">// Pool variant.</span>
    <span class="k">public</span> <span class="n">PooledObject</span> <span class="n">projectile</span><span class="p">;</span>
    <span class="p">-------------------------------</span>
    <span class="c1">// Instantiate variant.</span>
    <span class="k">public</span> <span class="n">GameObject</span> <span class="n">projectile</span><span class="p">;</span>

    <span class="k">private</span> <span class="kt">float</span> <span class="n">timer</span> <span class="p">=</span> <span class="m">0f</span><span class="p">;</span>

    <span class="c1">// These probably allocate, so cache them for benchmarking.</span>
    <span class="k">private</span> <span class="n">Vector3</span> <span class="n">position</span> <span class="p">=</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">zero</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Quaternion</span> <span class="n">rotation</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="n">identity</span><span class="p">;</span>

    <span class="c1">// Update is called once per frame</span>
    <span class="k">private</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timer</span> <span class="p">+=</span> <span class="n">Time</span><span class="p">.</span><span class="n">deltaTime</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">timer</span> <span class="p">&gt;</span> <span class="n">delay</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Only fire once.</span>
            <span class="n">timer</span> <span class="p">=</span> <span class="p">-</span><span class="n">Mathf</span><span class="p">.</span><span class="n">Infinity</span><span class="p">;</span>
            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">amount</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
                <span class="c1">// Pool variant.</span>
                <span class="n">Pool</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">Spawn</span><span class="p">(</span><span class="n">projectile</span><span class="p">,</span> <span class="n">position</span><span class="p">,</span> <span class="n">rotation</span><span class="p">);</span>
                <span class="p">-----------------------------------------------------------</span>
                <span class="c1">// Instantiate variant.</span>
                <span class="nf">Instantiate</span><span class="p">(</span><span class="n">projectile</span><span class="p">,</span> <span class="n">position</span><span class="p">,</span> <span class="n">rotation</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">BenchmarkProjectile</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">speed</span> <span class="p">=</span> <span class="m">1f</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">PooledObject</span> <span class="n">pooledObject</span><span class="p">;</span>

    <span class="k">private</span> <span class="kt">float</span> <span class="n">timer</span> <span class="p">=</span> <span class="m">0f</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Vector2</span> <span class="n">up</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="n">up</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Awake</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">pooledObject</span> <span class="p">=</span> <span class="n">GetComponent</span><span class="p">&lt;</span><span class="n">PooledObject</span><span class="p">&gt;();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">transform</span><span class="p">.</span><span class="nf">Translate</span><span class="p">(</span><span class="n">up</span> <span class="p">*</span> <span class="n">speed</span> <span class="p">*</span> <span class="n">Time</span><span class="p">.</span><span class="n">deltaTime</span><span class="p">);</span>

        <span class="n">timer</span> <span class="p">+=</span> <span class="n">Time</span><span class="p">.</span><span class="n">deltaTime</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">timer</span> <span class="p">&gt;</span> <span class="m">2f</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">timer</span> <span class="p">=</span> <span class="m">0f</span><span class="p">;</span>
            <span class="c1">// Pool variant.</span>
            <span class="n">pooledObject</span><span class="p">.</span><span class="nf">Finish</span><span class="p">();</span>
            <span class="p">------------------------</span>
            <span class="c1">// Instantiate variant.</span>
            <span class="nf">Destroy</span><span class="p">(</span><span class="n">gameObject</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The test is pretty basic.  We spawn 5,000 projectiles with either <code class="language-plaintext highlighter-rouge">Instantiate()</code> or <code class="language-plaintext highlighter-rouge">Pool.Spawn()</code>, and observe the results through the built-in profiler.  We’ll also have a look at differences in destruction times between the two approaches.</p>

<p>These results aren’t on-the-nose accurate – I’m running in editor, and I’m only eyeballing the variance.  They’re more than enough, however, to get the point across;</p>

<p><img src="/assets/2019_instantiate_5k_profiler.gif" alt="Profiler when running with Instantiate" width="1008px" height="672px" />
<em><code class="language-plaintext highlighter-rouge">Instatiate()</code>/<code class="language-plaintext highlighter-rouge">Destroy()</code> profiler</em></p>

<p><img src="/assets/2019_pool_5k_profiler.gif" alt="Profiler when running with object pools" width="1008px" height="672px" />
<em>Object pool profiler</em></p>

<p>The results are in!</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Instantiate</th>
      <th>Pool</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Spawn</strong></td>
      <td>~206.37ms</td>
      <td>~33.72ms</td>
    </tr>
    <tr>
      <td><strong>Despawn</strong></td>
      <td>~44.61ms</td>
      <td>~17.69ms</td>
    </tr>
  </tbody>
</table>

<p>Spawning 5,000 projectiles was enough to make us drop a couple of frames even with our object pool.  With <code class="language-plaintext highlighter-rouge">Instantiate()</code>, it’s closer to a freeze.  When spawning lots of objects in one place, you probably <em>always</em> want to split the operation over multiple frames, regardless of how optimized your architecture is.  Even a small amount of stutter in games is horrible and really goes against “game feel” – even if players can’t point out why, they’ll feel uncomfortable playing your game.</p>

<p>If you’re thinking: “Why would I ever want to spawn thousands of objects at once?  <code class="language-plaintext highlighter-rouge">Instantiate()</code> is good enough.”, here’s some things to consider:</p>
<ul>
  <li>You might not spawn that many objects from any <em>one place</em> at the same time, but as your game gets bigger you can end up spawning a significant number of objects at the same time either by coincidence, or because an in-game event triggers a bunch of things at once; e.g. AI enemies shooting at a player.</li>
  <li>The objects I’ve used in this benchmark are pretty much as simple as they come – only a few (basic) components and no children.  Add a couple <code class="language-plaintext highlighter-rouge">Rigidbody</code>s, character animations, and child objects, and the number of things you can spawn without players feeling anything drops <strong>hard</strong>.</li>
  <li>PC (where this benchmark was run) is probably the fastest platform you’ll be running your game on.  Spawning even a couple of fat objects on mobile can be enough to cause a stutter.</li>
</ul>

<p>An object pool significantly increases your headroom for spawning lots of objects at runtime, and is almost as convenient as instantiation.  If you’re calling <code class="language-plaintext highlighter-rouge">Instantiate()</code>, chances are that it can be replaced with an object pool.</p>

<blockquote>
  <p>Source code and Unity project available at <a href="https://github.com/Toqozz/blog-code/tree/master/object_pool">https://github.com/Toqozz/blog-code/tree/master/object_pool</a>.</p>
</blockquote>]]></content><author><name></name></author><summary type="html"><![CDATA[Instantiate() is expensive. If possible, you should never use it at runtime. For one-offs this is usually done by spawning whatever objects you need in Awake(), and then calling SetActive(true) when you need them. For more than one-offs, you probably want to use a pool. A pool is a group of objects that you instantiate at some convenient time (probably on scene load), and then later on, when you want to spawn an object, you grab it from the pool instead of making it fresh. This technique is used extensively throughout just about any well-programmed game.]]></summary></entry><entry><title type="html">Drawing Thousands of Meshes with DrawMeshInstanced / Indirect in Unity</title><link href="https://toqoz.fyi/thousands-of-meshes.html" rel="alternate" type="text/html" title="Drawing Thousands of Meshes with DrawMeshInstanced / Indirect in Unity" /><published>2019-11-01T00:00:00+00:00</published><updated>2019-11-01T00:00:00+00:00</updated><id>https://toqoz.fyi/thousands-of-meshes</id><content type="html" xml:base="https://toqoz.fyi/thousands-of-meshes.html"><![CDATA[<p><img src="/assets/2019_mesh_spiral.gif" alt="Demo gif" width="1029px" height="634px" />
GPU instancing is a graphics technique available in Unity to draw lots of the same mesh and material quickly.</p>

<p>In the right circumstances, GPU instancing can allow you to feasibly draw even millions of meshes.  Unity tries to make this work automatically for you if it can.  If all your meshes use the same material, ‘GPU Instancing’ is ticked, your shader supports instancing, lighting and shadows play nicely, you’re not using a skinned mesh renderer, etc, Unity will automatically batch meshes into a single draw call.</p>
<blockquote>
  <p>A low number of draw calls is usually a sign of a well-performing game.  For each new draw call, the GPU has to do a context switch, which is <em>expensive</em>.</p>
</blockquote>

<p>There’s a lot of ifs and buts here, which is a pain in the ass if you just want to write performant code, and even if you do get GPU instancing to work, the overhead of <code class="language-plaintext highlighter-rouge">GameObject</code>s and <code class="language-plaintext highlighter-rouge">Transform</code>s alone is <em>huge</em>.</p>

<hr />

<h2 id="drawmeshinstanced"><a href="https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstanced.html"><code class="language-plaintext highlighter-rouge">DrawMeshInstanced()</code></a></h2>
<p>You can use <code class="language-plaintext highlighter-rouge">Graphics.DrawMeshInstanced()</code> to get around a lot of these conditions.  This will draw a number of meshes (up to 1023 in a single batch) for a single frame.</p>

<p>This is a particularly nice solution for when you want to draw a lot of objects that don’t move very much, or only move in the shader (trees, grass).  It allows you to easily shove meshes to the GPU, customize them with <code class="language-plaintext highlighter-rouge">MaterialPropertyBlock</code>s, and avoid the fat overhead of <code class="language-plaintext highlighter-rouge">GameObject</code>s.  Additionally, Unity has to do a little less work to figure out if it can instance the objects or not, and will throw an error rather than silently nerfing performance.  The main downside here though is that moving these objects usually results in a huge <code class="language-plaintext highlighter-rouge">for</code> loop, which kills performance.</p>

<blockquote>
  <p>If you <em>must</em> move meshes while using <code class="language-plaintext highlighter-rouge">DrawMeshInstanced()</code>, consider using a sleep/wake model to reduce the size of these loops as much as possible.</p>
</blockquote>

<h3 id="example">Example</h3>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">DrawMeshInstancedDemo</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="c1">// How many meshes to draw.</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">population</span><span class="p">;</span>
    <span class="c1">// Range to draw meshes within.</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">range</span><span class="p">;</span>

    <span class="c1">// Material to use for drawing the meshes.</span>
    <span class="k">public</span> <span class="n">Material</span> <span class="n">material</span><span class="p">;</span>
  
    <span class="k">private</span> <span class="n">Matrix4x4</span><span class="p">[]</span> <span class="n">matrices</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">MaterialPropertyBlock</span> <span class="n">block</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">Mesh</span> <span class="n">mesh</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Setup</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">Mesh</span> <span class="n">mesh</span> <span class="p">=</span> <span class="nf">CreateQuad</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="n">mesh</span> <span class="p">=</span> <span class="n">mesh</span><span class="p">;</span>

        <span class="n">matrices</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Matrix4x4</span><span class="p">[</span><span class="n">population</span><span class="p">];</span>
        <span class="n">Vector4</span><span class="p">[]</span> <span class="n">colors</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Vector4</span><span class="p">[</span><span class="n">population</span><span class="p">];</span>

        <span class="n">block</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MaterialPropertyBlock</span><span class="p">();</span>

        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">population</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="c1">// Build matrix.</span>
            <span class="n">Vector3</span> <span class="n">position</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector3</span><span class="p">(</span><span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="n">range</span><span class="p">,</span> <span class="n">range</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="n">range</span><span class="p">,</span> <span class="n">range</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="n">range</span><span class="p">,</span> <span class="n">range</span><span class="p">));</span>
            <span class="n">Quaternion</span> <span class="n">rotation</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">Euler</span><span class="p">(</span><span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="m">180</span><span class="p">,</span> <span class="m">180</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="m">180</span><span class="p">,</span> <span class="m">180</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="m">180</span><span class="p">,</span> <span class="m">180</span><span class="p">));</span>
            <span class="n">Vector3</span> <span class="n">scale</span> <span class="p">=</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">one</span><span class="p">;</span>

            <span class="n">mat</span> <span class="p">=</span> <span class="n">Matrix4x4</span><span class="p">.</span><span class="nf">TRS</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="n">rotation</span><span class="p">,</span> <span class="n">scale</span><span class="p">);</span>

            <span class="n">matrices</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">mat</span><span class="p">;</span>

            <span class="n">colors</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">Color</span><span class="p">.</span><span class="nf">Lerp</span><span class="p">(</span><span class="n">Color</span><span class="p">.</span><span class="n">red</span><span class="p">,</span> <span class="n">Color</span><span class="p">.</span><span class="n">blue</span><span class="p">,</span> <span class="n">Random</span><span class="p">.</span><span class="k">value</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// Custom shader needed to read these!!</span>
        <span class="n">block</span><span class="p">.</span><span class="nf">SetVectorArray</span><span class="p">(</span><span class="s">"_Colors"</span><span class="p">,</span> <span class="n">colors</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="n">Mesh</span> <span class="nf">CreateQuad</span><span class="p">(</span><span class="kt">float</span> <span class="n">width</span> <span class="p">=</span> <span class="m">1f</span><span class="p">,</span> <span class="kt">float</span> <span class="n">height</span> <span class="p">=</span> <span class="m">1f</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Create a quad mesh.</span>
        <span class="c1">// See source for implementation.</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Start</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">Setup</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Draw a bunch of meshes each frame.</span>
        <span class="n">Graphics</span><span class="p">.</span><span class="nf">DrawMeshInstanced</span><span class="p">(</span><span class="n">mesh</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">material</span><span class="p">,</span> <span class="n">matrices</span><span class="p">,</span> <span class="n">population</span><span class="p">,</span> <span class="n">block</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Essentially, we fill a big array with matrices representing object transforms (position, rotation, scale) and then pass that array to <code class="language-plaintext highlighter-rouge">Graphics.DrawMeshInstanced()</code>, which will assign those matrices in the shader automagically (assuming the shader supports instancing).</p>

<p>You can still grab the instanceID to do per-mesh customizations via <code class="language-plaintext highlighter-rouge">MaterialPropertyBlock</code>s (see color array), but you’ll probably need to use a custom shader:</p>

<blockquote>
  <p><em>A custom shader is only required if you’re customizing per-mesh properties.</em></p>
</blockquote>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Shader</span> <span class="s">"Custom/InstancedColor"</span> <span class="p">{</span>
    <span class="n">SubShader</span> <span class="p">{</span>
        <span class="n">Tags</span> <span class="p">{</span> <span class="s">"RenderType"</span> <span class="o">=</span> <span class="s">"Opaque"</span> <span class="p">}</span>

        <span class="n">Pass</span> <span class="p">{</span>
            <span class="n">CGPROGRAM</span>
            <span class="cp">#pragma vertex vert
</span>            <span class="cp">#pragma fragment frag
</span>            <span class="cp">#pragma multi_compile_instancing
</span>
            <span class="cp">#include</span> <span class="cpf">"UnityCG.cginc"</span><span class="cp">
</span>
            <span class="k">struct</span> <span class="n">appdata_t</span> <span class="p">{</span>
                <span class="n">float4</span> <span class="n">vertex</span>   <span class="o">:</span> <span class="n">POSITION</span><span class="p">;</span>
                <span class="n">float4</span> <span class="n">color</span>    <span class="o">:</span> <span class="n">COLOR</span><span class="p">;</span>
                <span class="n">UNITY_VERTEX_INPUT_INSTANCE_ID</span>
            <span class="p">};</span>

            <span class="k">struct</span> <span class="n">v2f</span> <span class="p">{</span>
                <span class="n">float4</span> <span class="n">vertex</span>   <span class="o">:</span> <span class="n">SV_POSITION</span><span class="p">;</span>
                <span class="n">fixed4</span> <span class="n">color</span>    <span class="o">:</span> <span class="n">COLOR</span><span class="p">;</span>
            <span class="p">};</span> 

            <span class="n">float4</span> <span class="n">_Colors</span><span class="p">[</span><span class="mi">1023</span><span class="p">];</span>   <span class="c1">// Max instanced batch size.</span>

            <span class="n">v2f</span> <span class="n">vert</span><span class="p">(</span><span class="n">appdata_t</span> <span class="n">i</span><span class="p">,</span> <span class="n">uint</span> <span class="n">instanceID</span><span class="o">:</span> <span class="n">SV_InstanceID</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// Allow instancing.</span>
                <span class="n">UNITY_SETUP_INSTANCE_ID</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>

                <span class="n">v2f</span> <span class="n">o</span><span class="p">;</span>
                <span class="n">o</span><span class="p">.</span><span class="n">vertex</span> <span class="o">=</span> <span class="n">UnityObjectToClipPos</span><span class="p">(</span><span class="n">i</span><span class="p">.</span><span class="n">vertex</span><span class="p">);</span>
                <span class="n">o</span><span class="p">.</span><span class="n">color</span> <span class="o">=</span> <span class="n">float4</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>

                <span class="c1">// If instancing on (it should be) assign per-instance color.</span>
                <span class="cp">#ifdef UNITY_INSTANCING_ENABLED
</span>                    <span class="n">o</span><span class="p">.</span><span class="n">color</span> <span class="o">=</span> <span class="n">_Colors</span><span class="p">[</span><span class="n">instanceID</span><span class="p">];</span>
                <span class="cp">#endif
</span>
                <span class="k">return</span> <span class="n">o</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">fixed4</span> <span class="n">frag</span><span class="p">(</span><span class="n">v2f</span> <span class="n">i</span><span class="p">)</span> <span class="o">:</span> <span class="n">SV_Target</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">i</span><span class="p">.</span><span class="n">color</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">ENDCG</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/2019_1022_uncolored.png" alt="1022 meshes with standard shader" width="1041px" height="646px" />
<em>1022 meshes with the standard shader.</em>
<img src="/assets/2019_1022_colored.png" alt="1022 meshes with color shader" width="1044px" height="649px" />
<em>1022 meshes with a custom shader, and per-mesh colors</em></p>

<blockquote>
  <p>Note that shadows are missing on the colored version.  This is because we’re using a custom shader to apply the colors which doesn’t have a shadow pass.  Have a look at <a href="https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html">this</a> for some examples of adding shadow casting/receiving to a custom shader.</p>
</blockquote>

<blockquote>
  <p>If setting up a random array in the shader feels awkward, that’s because it is.  There doesn’t seem to be a way to get Unity to set up an array for you and index the color automatically.  If we were using individual game objects, we could do something like <a href="https://docs.unity3d.com/540/Documentation/Manual/GPUInstancing.html">this</a>.  You could probably get this to work by digging into the shader source and having a look at what names Unity uses for the arrays, but that’s pretty convoluted for no good reason.</p>
</blockquote>

<h2 id="drawmeshinstancedindirect"><a href="https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html"><code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect()</code></a></h2>
<p><code class="language-plaintext highlighter-rouge">DrawMeshInstanced()</code> turns out to be a sort of wrapper around <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect()</code>.  You can achieve everything in the latter that you can with the former (and vice versa, with complications).  <code class="language-plaintext highlighter-rouge">DrawMeshInstanced()</code> is mainly a friendly way to draw meshes without touching the GPU.</p>

<p>Naturally, some nice things get lost in the abstraction.  First of all, the <code class="language-plaintext highlighter-rouge">Indirect</code> variant allows you to bypass the 1023 mesh limit and draw as many meshes as you like in a single batch (the 1023 mesh limit seems to actually inherited from <a href="https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.CopyProbeOcclusionArrayFrom.html"><code class="language-plaintext highlighter-rouge">MaterialPropertyBlock</code></a>).  The primary benefit, however, is that you can offload the entirety of the work onto the GPU.  With <code class="language-plaintext highlighter-rouge">DrawMeshInstanced()</code>, Unity has to upload the array of mesh matrices to the GPU each frame, whereas <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect()</code> creates and stores data on the GPU indefinitely.  This also means using GPU-based structures to store data, mainly <a href="https://docs.unity3d.com/ScriptReference/ComputeBuffer.html"><code class="language-plaintext highlighter-rouge">ComputeBuffer</code>s</a>, which can be scary up front but turns out to be more convenient, and opens the door to some easy mass parallelisation via compute shaders.</p>

<h3 id="example-1">Example</h3>
<p>Here’s the same program as above but using <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect()</code> instead:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">DrawMeshInstancedIndirectDemo</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">population</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">range</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">Material</span> <span class="n">material</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">ComputeBuffer</span> <span class="n">meshPropertiesBuffer</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">ComputeBuffer</span> <span class="n">argsBuffer</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">Mesh</span> <span class="n">mesh</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Bounds</span> <span class="n">bounds</span><span class="p">;</span>

    <span class="c1">// Mesh Properties struct to be read from the GPU.</span>
    <span class="c1">// Size() is a convenience funciton which returns the stride of the struct.</span>
    <span class="k">private</span> <span class="k">struct</span> <span class="nc">MeshProperties</span> <span class="p">{</span>
        <span class="k">public</span> <span class="n">Matrix4x4</span> <span class="n">mat</span><span class="p">;</span>
        <span class="k">public</span> <span class="n">Vector4</span> <span class="n">color</span><span class="p">;</span>

        <span class="k">public</span> <span class="k">static</span> <span class="kt">int</span> <span class="nf">Size</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span>
                <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">)</span> <span class="p">*</span> <span class="m">4</span> <span class="p">*</span> <span class="m">4</span> <span class="p">+</span> <span class="c1">// matrix;</span>
                <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">)</span> <span class="p">*</span> <span class="m">4</span><span class="p">;</span>      <span class="c1">// color;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Setup</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">Mesh</span> <span class="n">mesh</span> <span class="p">=</span> <span class="nf">CreateQuad</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="n">mesh</span> <span class="p">=</span> <span class="n">mesh</span><span class="p">;</span>

        <span class="c1">// Boundary surrounding the meshes we will be drawing.  Used for occlusion.</span>
        <span class="n">bounds</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Bounds</span><span class="p">(</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">one</span> <span class="p">*</span> <span class="p">(</span><span class="n">range</span> <span class="p">+</span> <span class="m">1</span><span class="p">));</span>

        <span class="nf">InitializeBuffers</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">InitializeBuffers</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Argument buffer used by DrawMeshInstancedIndirect.</span>
        <span class="kt">uint</span><span class="p">[]</span> <span class="n">args</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">uint</span><span class="p">[</span><span class="m">5</span><span class="p">]</span> <span class="p">{</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span> <span class="p">};</span>
        <span class="c1">// Arguments for drawing mesh.</span>
        <span class="c1">// 0 == number of triangle indices, 1 == population, others are only relevant if drawing submeshes.</span>
        <span class="n">args</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">mesh</span><span class="p">.</span><span class="nf">GetIndexCount</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
        <span class="n">args</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">population</span><span class="p">;</span>
        <span class="n">args</span><span class="p">[</span><span class="m">2</span><span class="p">]</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">mesh</span><span class="p">.</span><span class="nf">GetIndexStart</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
        <span class="n">args</span><span class="p">[</span><span class="m">3</span><span class="p">]</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">mesh</span><span class="p">.</span><span class="nf">GetBaseVertex</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
        <span class="n">argsBuffer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ComputeBuffer</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span> <span class="p">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint</span><span class="p">),</span> <span class="n">ComputeBufferType</span><span class="p">.</span><span class="n">IndirectArguments</span><span class="p">);</span>
        <span class="n">argsBuffer</span><span class="p">.</span><span class="nf">SetData</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>

        <span class="c1">// Initialize buffer with the given population.</span>
        <span class="n">MeshProperties</span><span class="p">[]</span> <span class="n">properties</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MeshProperties</span><span class="p">[</span><span class="n">population</span><span class="p">];</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">population</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="n">MeshProperties</span> <span class="n">props</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MeshProperties</span><span class="p">();</span>
            <span class="n">Vector3</span> <span class="n">position</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector3</span><span class="p">(</span><span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="n">range</span><span class="p">,</span> <span class="n">range</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="n">range</span><span class="p">,</span> <span class="n">range</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="n">range</span><span class="p">,</span> <span class="n">range</span><span class="p">));</span>
            <span class="n">Quaternion</span> <span class="n">rotation</span> <span class="p">=</span> <span class="n">Quaternion</span><span class="p">.</span><span class="nf">Euler</span><span class="p">(</span><span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="m">180</span><span class="p">,</span> <span class="m">180</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="m">180</span><span class="p">,</span> <span class="m">180</span><span class="p">),</span> <span class="n">Random</span><span class="p">.</span><span class="nf">Range</span><span class="p">(-</span><span class="m">180</span><span class="p">,</span> <span class="m">180</span><span class="p">));</span>
            <span class="n">Vector3</span> <span class="n">scale</span> <span class="p">=</span> <span class="n">Vector3</span><span class="p">.</span><span class="n">one</span><span class="p">;</span>

            <span class="n">props</span><span class="p">.</span><span class="n">mat</span> <span class="p">=</span> <span class="n">Matrix4x4</span><span class="p">.</span><span class="nf">TRS</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="n">rotation</span><span class="p">,</span> <span class="n">scale</span><span class="p">);</span>
            <span class="n">props</span><span class="p">.</span><span class="n">color</span> <span class="p">=</span> <span class="n">Color</span><span class="p">.</span><span class="nf">Lerp</span><span class="p">(</span><span class="n">Color</span><span class="p">.</span><span class="n">red</span><span class="p">,</span> <span class="n">Color</span><span class="p">.</span><span class="n">blue</span><span class="p">,</span> <span class="n">Random</span><span class="p">.</span><span class="k">value</span><span class="p">);</span>

            <span class="n">properties</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">props</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">meshPropertiesBuffer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ComputeBuffer</span><span class="p">(</span><span class="n">population</span><span class="p">,</span> <span class="n">MeshProperties</span><span class="p">.</span><span class="nf">Size</span><span class="p">());</span>
        <span class="n">meshPropertiesBuffer</span><span class="p">.</span><span class="nf">SetData</span><span class="p">(</span><span class="n">properties</span><span class="p">);</span>
        <span class="n">material</span><span class="p">.</span><span class="nf">SetBuffer</span><span class="p">(</span><span class="s">"_Properties"</span><span class="p">,</span> <span class="n">meshPropertiesBuffer</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="n">Mesh</span> <span class="nf">CreateQuad</span><span class="p">(</span><span class="kt">float</span> <span class="n">width</span> <span class="p">=</span> <span class="m">1f</span><span class="p">,</span> <span class="kt">float</span> <span class="n">height</span> <span class="p">=</span> <span class="m">1f</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">...</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Start</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">Setup</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">Graphics</span><span class="p">.</span><span class="nf">DrawMeshInstancedIndirect</span><span class="p">(</span><span class="n">mesh</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">material</span><span class="p">,</span> <span class="n">bounds</span><span class="p">,</span> <span class="n">argsBuffer</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">OnDisable</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Release gracefully.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">meshPropertiesBuffer</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">meshPropertiesBuffer</span><span class="p">.</span><span class="nf">Release</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="n">meshPropertiesBuffer</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">argsBuffer</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">argsBuffer</span><span class="p">.</span><span class="nf">Release</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="n">argsBuffer</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
  <p>The <code class="language-plaintext highlighter-rouge">bounds</code> parameter of <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect()</code> is used for determining whether the mesh is in view and culling it.  The documentation states  <code class="language-plaintext highlighter-rouge">Meshes are not further culled by the view frustum or baked occluders ...</code> which gave me the impression that culling was simply disabled for all meshes drawn with DrawMeshInstanced/Indirect, but this is not so.  Unity will cull all the instanced meshes if the provided mesh bounds are not in view.  It will not, however, cull individual instanced meshes – you’ll have to calculate this yourself if you find it necessary.</p>
</blockquote>

<p>And the associated shader:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Shader</span> <span class="s">"Custom/InstancedIndirectColor"</span> <span class="p">{</span>
    <span class="n">SubShader</span> <span class="p">{</span>
        <span class="n">Tags</span> <span class="p">{</span> <span class="s">"RenderType"</span> <span class="o">=</span> <span class="s">"Opaque"</span> <span class="p">}</span>

        <span class="n">Pass</span> <span class="p">{</span>
            <span class="n">CGPROGRAM</span>
            <span class="cp">#pragma vertex vert
</span>            <span class="cp">#pragma fragment frag
</span>
            <span class="cp">#include</span> <span class="cpf">"UnityCG.cginc"</span><span class="cp">
</span>
            <span class="k">struct</span> <span class="n">appdata_t</span> <span class="p">{</span>
                <span class="n">float4</span> <span class="n">vertex</span>   <span class="o">:</span> <span class="n">POSITION</span><span class="p">;</span>
                <span class="n">float4</span> <span class="n">color</span>    <span class="o">:</span> <span class="n">COLOR</span><span class="p">;</span>
            <span class="p">};</span>

            <span class="k">struct</span> <span class="n">v2f</span> <span class="p">{</span>
                <span class="n">float4</span> <span class="n">vertex</span>   <span class="o">:</span> <span class="n">SV_POSITION</span><span class="p">;</span>
                <span class="n">fixed4</span> <span class="n">color</span>    <span class="o">:</span> <span class="n">COLOR</span><span class="p">;</span>
            <span class="p">};</span> 

            <span class="k">struct</span> <span class="n">MeshProperties</span> <span class="p">{</span>
                <span class="n">float4x4</span> <span class="n">mat</span><span class="p">;</span>
                <span class="n">float4</span> <span class="n">color</span><span class="p">;</span>
            <span class="p">};</span>

            <span class="n">StructuredBuffer</span><span class="o">&lt;</span><span class="n">MeshProperties</span><span class="o">&gt;</span> <span class="n">_Properties</span><span class="p">;</span>

            <span class="n">v2f</span> <span class="n">vert</span><span class="p">(</span><span class="n">appdata_t</span> <span class="n">i</span><span class="p">,</span> <span class="n">uint</span> <span class="n">instanceID</span><span class="o">:</span> <span class="n">SV_InstanceID</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">v2f</span> <span class="n">o</span><span class="p">;</span>

                <span class="n">float4</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">_Properties</span><span class="p">[</span><span class="n">instanceID</span><span class="p">].</span><span class="n">mat</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">vertex</span><span class="p">);</span>
                <span class="n">o</span><span class="p">.</span><span class="n">vertex</span> <span class="o">=</span> <span class="n">UnityObjectToClipPos</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
                <span class="n">o</span><span class="p">.</span><span class="n">color</span> <span class="o">=</span> <span class="n">_Properties</span><span class="p">[</span><span class="n">instanceID</span><span class="p">].</span><span class="n">color</span><span class="p">;</span>

                <span class="k">return</span> <span class="n">o</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">fixed4</span> <span class="n">frag</span><span class="p">(</span><span class="n">v2f</span> <span class="n">i</span><span class="p">)</span> <span class="o">:</span> <span class="n">SV_Target</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">i</span><span class="p">.</span><span class="n">color</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">ENDCG</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
  <p>The struct for your <code class="language-plaintext highlighter-rouge">StructuredBuffer</code> <strong>must</strong> be byte-wise identical throughout shader/compute shader/script or you’ll see bugginess.  These structures are only matched up between script and shader by reading bytes.</p>
</blockquote>

<p><img src="/assets/2019_1022_indirect_colored.png" alt="1022 meshes using DrawMeshInstancedIndirect" width="1038px" height="644px" />
<em>1022 meshes drawn with <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect</code></em></p>

<p>A key difference here is that with <code class="language-plaintext highlighter-rouge">DrawMeshInstanced()</code>, we were giving Unity an array of matrices and having it automagically figure out vertex positions before we got to the shader.  Here, we’re being much more direct in that we’re pushing the matrices to the GPU and applying the transformation ourselves.  The shader instancing code has been cut down significantly, which is nice, and we’ve gained about a millisecond in rendering.
Note, however, that this number is heavily influenced by the rest of the game.  In our basic scene with nothing happening, bandwidth and CPU time is abundant, so the benefits of <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect()</code> are likely less prevalent than in the real world.</p>

<p>The 1023 mesh limit has also disappeared.  Pushing the population up to even 100, 000 has little affect on my system (Ryzen 1700, 1080Ti):</p>

<p><img src="/assets/2019_100k_indirect_colored.png" alt="100k meshes using DrawMeshInstancedIndirect" width="1043px" height="645px" />
<em>100k meshes drawn with <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect</code></em></p>

<hr />

<h2 id="adding-movement-with-a-compute-shader">Adding Movement With a Compute Shader</h2>
<p>Now that we’re using <code class="language-plaintext highlighter-rouge">DrawMeshInstancedIndirect()</code>, we can add some mesh movement without crushing performance.</p>

<p><a href="https://docs.unity3d.com/Manual/class-ComputeShader.html">Compute Shaders</a> are special programs that run on the GPU and allow you to utilize the massive parallel power of graphics devices for non-graphics code.  I’m mostly looking to demonstrate how you can use a compute shader with the other tools shown so far, so I won’t explain compute shaders too in-depth; <a href="http://kylehalladay.com/blog/tutorial/2014/06/27/Compute-Shaders-Are-Nifty.html">here</a> is a good blog post talking about them, which covers everything better than I could.</p>

<p>Here’s a compute shader which moves our meshes based on some “pusher”:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma kernel CSMain
</span>
<span class="k">struct</span> <span class="n">MeshProperties</span> <span class="p">{</span>
    <span class="n">float4x4</span> <span class="n">mat</span><span class="p">;</span>
    <span class="n">float4</span> <span class="n">color</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">RWStructuredBuffer</span><span class="o">&lt;</span><span class="n">MeshProperties</span><span class="o">&gt;</span> <span class="n">_Properties</span><span class="p">;</span>
<span class="n">float3</span> <span class="n">_PusherPosition</span><span class="p">;</span>

<span class="c1">// We used to just be able to use (1, 1, 1) threads for whatever population (not sure the old limit), but a Unity update</span>
<span class="c1">// imposed a thread limit of 65535.  Now, to populations above that, we need to be more granular with our threads.</span>
<span class="p">[</span><span class="n">numthreads</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">)]</span>
<span class="kt">void</span> <span class="nf">CSMain</span> <span class="p">(</span><span class="n">uint3</span> <span class="n">id</span> <span class="o">:</span> <span class="n">SV_DispatchThreadID</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">float4x4</span> <span class="n">mat</span> <span class="o">=</span> <span class="n">_Properties</span><span class="p">[</span><span class="n">id</span><span class="p">.</span><span class="n">x</span><span class="p">].</span><span class="n">mat</span><span class="p">;</span>
    <span class="c1">// In a transform matrix, the position (translation) vector is the last column.</span>
    <span class="n">float3</span> <span class="n">position</span> <span class="o">=</span> <span class="n">float3</span><span class="p">(</span><span class="n">mat</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">3</span><span class="p">],</span> <span class="n">mat</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">3</span><span class="p">],</span> <span class="n">mat</span><span class="p">[</span><span class="mi">2</span><span class="p">][</span><span class="mi">3</span><span class="p">]);</span>

    <span class="kt">float</span> <span class="n">dist</span> <span class="o">=</span> <span class="n">distance</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="n">_PusherPosition</span><span class="p">);</span>
    <span class="c1">// Scale and reverse distance so that we get a value which fades as it gets further away.</span>
    <span class="c1">// Max distance is 5.0.</span>
    <span class="n">dist</span> <span class="o">=</span> <span class="mi">5</span><span class="p">.</span><span class="mi">0</span> <span class="o">-</span> <span class="n">clamp</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="n">dist</span><span class="p">);</span>

    <span class="c1">// Get the vector from the pusher to the position, and scale it.</span>
    <span class="n">float3</span> <span class="n">push</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">position</span> <span class="o">-</span> <span class="n">_PusherPosition</span><span class="p">)</span> <span class="o">*</span> <span class="n">dist</span><span class="p">;</span>
    <span class="c1">// Create a new translation matrix which represents a move in a direction.</span>
    <span class="n">float4x4</span> <span class="n">translation</span> <span class="o">=</span> <span class="n">float4x4</span><span class="p">(</span>
        <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">push</span><span class="p">.</span><span class="n">x</span><span class="p">,</span>
        <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">push</span><span class="p">.</span><span class="n">y</span><span class="p">,</span>
        <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">push</span><span class="p">.</span><span class="n">z</span><span class="p">,</span>
        <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span>
    <span class="p">);</span>

    <span class="c1">// Apply translation to existing matrix, which will be read in the shader.</span>
    <span class="n">_Properties</span><span class="p">[</span><span class="n">id</span><span class="p">.</span><span class="n">x</span><span class="p">].</span><span class="n">mat</span> <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">translation</span><span class="p">,</span> <span class="n">mat</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Take note that the <code class="language-plaintext highlighter-rouge">MeshProperties</code> struct is byte-wise identical to the structs in the other files.  I mentioned this earlier, but it’s paramount that you keep these structs in sync or your program won’t work (without error!).</p>

<p>You might also be wondering whether you could accomplish this same thing in the regular shader as well, by changing the <code class="language-plaintext highlighter-rouge">StructuredBuffer</code> to a <code class="language-plaintext highlighter-rouge">RWStructuredBuffer</code> and doing a similar computation.  The problem this introduces is that regular shaders are designed to work on a per-vertex or per-fragment basis, and so to get the same effect (moving an individual <em>mesh</em>) you’d have to either set some flag to mark the mesh as “moved” (horrible, absolutely kills parallelisation), or change the per-mesh approach to a per-vertex approach, probably with a different result.
While I’m sure it’s possible to achieve the same thing without a compute shader, my point is that compute shaders let you specify granularity on your own terms.</p>

<p>Of course, we need to make a couple additions to our script to actually run our compute shader.  Where <code class="language-plaintext highlighter-rouge">compute</code> is our compute shader and <code class="language-plaintext highlighter-rouge">pusher</code> is the object we want to use to push:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">DrawMeshInstancedIndirectDemo</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">population</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">range</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">Material</span> <span class="n">material</span><span class="p">;</span>
<span class="p">++</span>  <span class="k">public</span> <span class="n">ComputeShader</span> <span class="n">compute</span><span class="p">;</span>
<span class="p">++</span>  <span class="k">public</span> <span class="n">Transform</span> <span class="n">pusher</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">ComputeBuffer</span> <span class="n">meshPropertiesBuffer</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">ComputeBuffer</span> <span class="n">argsBuffer</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">Mesh</span> <span class="n">mesh</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Bounds</span> <span class="n">bounds</span><span class="p">;</span>

    <span class="p">...</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">InitializeBuffers</span><span class="p">()</span> <span class="p">{</span>
<span class="p">++</span>      <span class="kt">int</span> <span class="n">kernel</span> <span class="p">=</span> <span class="n">compute</span><span class="p">.</span><span class="nf">FindKernel</span><span class="p">(</span><span class="s">"CSMain"</span><span class="p">);</span>

        <span class="c1">// Argument buffer used by DrawMeshInstancedIndirect.</span>
        <span class="kt">uint</span><span class="p">[]</span> <span class="n">args</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">uint</span><span class="p">[</span><span class="m">5</span><span class="p">]</span> <span class="p">{</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span> <span class="p">};</span>
        <span class="c1">// Arguments for drawing mesh.</span>
        <span class="c1">// 0 == number of triangle indices, 1 == population, others are only relevant if drawing submeshes.</span>
        <span class="n">args</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">mesh</span><span class="p">.</span><span class="nf">GetIndexCount</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
        <span class="p">...</span>

        <span class="n">meshPropertiesBuffer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ComputeBuffer</span><span class="p">(</span><span class="n">population</span><span class="p">,</span> <span class="n">MeshProperties</span><span class="p">.</span><span class="nf">Size</span><span class="p">());</span>
        <span class="n">meshPropertiesBuffer</span><span class="p">.</span><span class="nf">SetData</span><span class="p">(</span><span class="n">properties</span><span class="p">);</span>
<span class="p">++</span>      <span class="n">compute</span><span class="p">.</span><span class="nf">SetBuffer</span><span class="p">(</span><span class="n">kernel</span><span class="p">,</span> <span class="s">"_Properties"</span><span class="p">,</span> <span class="n">meshPropertiesBuffer</span><span class="p">);</span>
        <span class="n">material</span><span class="p">.</span><span class="nf">SetBuffer</span><span class="p">(</span><span class="s">"_Properties"</span><span class="p">,</span> <span class="n">meshPropertiesBuffer</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="p">...</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
<span class="p">++</span>      <span class="kt">int</span> <span class="n">kernel</span> <span class="p">=</span> <span class="n">compute</span><span class="p">.</span><span class="nf">FindKernel</span><span class="p">(</span><span class="s">"CSMain"</span><span class="p">);</span>

<span class="p">++</span>      <span class="n">compute</span><span class="p">.</span><span class="nf">SetVector</span><span class="p">(</span><span class="s">"_PusherPosition"</span><span class="p">,</span> <span class="n">pusher</span><span class="p">.</span><span class="n">position</span><span class="p">);</span>
<span class="p">++</span>      <span class="c1">// We used to just be able to use `population` here, but it looks like a Unity update imposed a thread limit (65535) on my device.</span>
<span class="p">++</span>      <span class="c1">// This is probably for the best, but we have to do some more calculation.  Divide population by numthreads.x (declared in compute shader).</span>
<span class="p">++</span>      <span class="n">compute</span><span class="p">.</span><span class="nf">Dispatch</span><span class="p">(</span><span class="n">kernel</span><span class="p">,</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">CeilToInt</span><span class="p">(</span><span class="n">population</span> <span class="p">/</span> <span class="m">64f</span><span class="p">),</span> <span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">);</span>
        <span class="n">Graphics</span><span class="p">.</span><span class="nf">DrawMeshInstancedIndirect</span><span class="p">(</span><span class="n">mesh</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">material</span><span class="p">,</span> <span class="n">bounds</span><span class="p">,</span> <span class="n">argsBuffer</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/2019_compute_movement.gif" alt="Pushing a lot of meshes around" width="1029px" height="634px" /></p>

<p>And that’s all, really.  Now you know how to move and draw (hundreds of) thousands of meshes efficiently.</p>

<blockquote>
  <p>Source code and Unity project available at <a href="https://github.com/Toqozz/blog-code/tree/master/mesh_batching">https://github.com/Toqozz/blog-code/tree/master/mesh_batching</a>.</p>
</blockquote>]]></content><author><name></name></author><summary type="html"><![CDATA[GPU instancing is a graphics technique available in Unity to draw lots of the same mesh and material quickly. In the right circumstances, GPU instancing can allow you to feasibly draw even millions of meshes. Unity tries to make this work automatically for you if it can. If all your meshes use the same material, ‘GPU Instancing’ is ticked, your shader supports instancing, lighting and shadows play nicely, you’re not using a skinned mesh renderer, etc, Unity will automatically batch meshes into a single draw call. A low number of draw calls is usually a sign of a well-performing game. For each new draw call, the GPU has to do a context switch, which is expensive. There’s a lot of ifs and buts here, which is a pain in the ass if you just want to write performant code, and even if you do get GPU instancing to work, the overhead of GameObjects and Transforms alone is huge. DrawMeshInstanced() You can use Graphics.DrawMeshInstanced() to get around a lot of these conditions. This will draw a number of meshes (up to 1023 in a single batch) for a single frame. This is a particularly nice solution for when you want to draw a lot of objects that don’t move very much, or only move in the shader (trees, grass). It allows you to easily shove meshes to the GPU, customize them with MaterialPropertyBlocks, and avoid the fat overhead of GameObjects. Additionally, Unity has to do a little less work to figure out if it can instance the objects or not, and will throw an error rather than silently nerfing performance. The main downside here though is that moving these objects usually results in a huge for loop, which kills performance. If you must move meshes while using DrawMeshInstanced(), consider using a sleep/wake model to reduce the size of these loops as much as possible. Example public class DrawMeshInstancedDemo : MonoBehaviour { // How many meshes to draw. public int population; // Range to draw meshes within. public float range; // Material to use for drawing the meshes. public Material material; private Matrix4x4[] matrices; private MaterialPropertyBlock block; private Mesh mesh; private void Setup() { Mesh mesh = CreateQuad(); this.mesh = mesh; matrices = new Matrix4x4[population]; Vector4[] colors = new Vector4[population]; block = new MaterialPropertyBlock(); for (int i = 0; i &lt; population; i++) { // Build matrix. Vector3 position = new Vector3(Random.Range(-range, range), Random.Range(-range, range), Random.Range(-range, range)); Quaternion rotation = Quaternion.Euler(Random.Range(-180, 180), Random.Range(-180, 180), Random.Range(-180, 180)); Vector3 scale = Vector3.one; mat = Matrix4x4.TRS(position, rotation, scale); matrices[i] = mat; colors[i] = Color.Lerp(Color.red, Color.blue, Random.value); } // Custom shader needed to read these!! block.SetVectorArray("_Colors", colors); } private Mesh CreateQuad(float width = 1f, float height = 1f) { // Create a quad mesh. // See source for implementation. } private void Start() { Setup(); } private void Update() { // Draw a bunch of meshes each frame. Graphics.DrawMeshInstanced(mesh, 0, material, matrices, population, block); } } Essentially, we fill a big array with matrices representing object transforms (position, rotation, scale) and then pass that array to Graphics.DrawMeshInstanced(), which will assign those matrices in the shader automagically (assuming the shader supports instancing). You can still grab the instanceID to do per-mesh customizations via MaterialPropertyBlocks (see color array), but you’ll probably need to use a custom shader: A custom shader is only required if you’re customizing per-mesh properties. Shader "Custom/InstancedColor" { SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; }; float4 _Colors[1023]; // Max instanced batch size. v2f vert(appdata_t i, uint instanceID: SV_InstanceID) { // Allow instancing. UNITY_SETUP_INSTANCE_ID(i); v2f o; o.vertex = UnityObjectToClipPos(i.vertex); o.color = float4(1, 1, 1, 1); // If instancing on (it should be) assign per-instance color. #ifdef UNITY_INSTANCING_ENABLED o.color = _Colors[instanceID]; #endif return o; } fixed4 frag(v2f i) : SV_Target { return i.color; } ENDCG } } } 1022 meshes with the standard shader. 1022 meshes with a custom shader, and per-mesh colors Note that shadows are missing on the colored version. This is because we’re using a custom shader to apply the colors which doesn’t have a shadow pass. Have a look at this for some examples of adding shadow casting/receiving to a custom shader. If setting up a random array in the shader feels awkward, that’s because it is. There doesn’t seem to be a way to get Unity to set up an array for you and index the color automatically. If we were using individual game objects, we could do something like this. You could probably get this to work by digging into the shader source and having a look at what names Unity uses for the arrays, but that’s pretty convoluted for no good reason. DrawMeshInstancedIndirect() DrawMeshInstanced() turns out to be a sort of wrapper around DrawMeshInstancedIndirect(). You can achieve everything in the latter that you can with the former (and vice versa, with complications). DrawMeshInstanced() is mainly a friendly way to draw meshes without touching the GPU. Naturally, some nice things get lost in the abstraction. First of all, the Indirect variant allows you to bypass the 1023 mesh limit and draw as many meshes as you like in a single batch (the 1023 mesh limit seems to actually inherited from MaterialPropertyBlock). The primary benefit, however, is that you can offload the entirety of the work onto the GPU. With DrawMeshInstanced(), Unity has to upload the array of mesh matrices to the GPU each frame, whereas DrawMeshInstancedIndirect() creates and stores data on the GPU indefinitely. This also means using GPU-based structures to store data, mainly ComputeBuffers, which can be scary up front but turns out to be more convenient, and opens the door to some easy mass parallelisation via compute shaders. Example Here’s the same program as above but using DrawMeshInstancedIndirect() instead: public class DrawMeshInstancedIndirectDemo : MonoBehaviour { public int population; public float range; public Material material; private ComputeBuffer meshPropertiesBuffer; private ComputeBuffer argsBuffer; private Mesh mesh; private Bounds bounds; // Mesh Properties struct to be read from the GPU. // Size() is a convenience funciton which returns the stride of the struct. private struct MeshProperties { public Matrix4x4 mat; public Vector4 color; public static int Size() { return sizeof(float) * 4 * 4 + // matrix; sizeof(float) * 4; // color; } } private void Setup() { Mesh mesh = CreateQuad(); this.mesh = mesh; // Boundary surrounding the meshes we will be drawing. Used for occlusion. bounds = new Bounds(transform.position, Vector3.one * (range + 1)); InitializeBuffers(); } private void InitializeBuffers() { // Argument buffer used by DrawMeshInstancedIndirect. uint[] args = new uint[5] { 0, 0, 0, 0, 0 }; // Arguments for drawing mesh. // 0 == number of triangle indices, 1 == population, others are only relevant if drawing submeshes. args[0] = (uint)mesh.GetIndexCount(0); args[1] = (uint)population; args[2] = (uint)mesh.GetIndexStart(0); args[3] = (uint)mesh.GetBaseVertex(0); argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments); argsBuffer.SetData(args); // Initialize buffer with the given population. MeshProperties[] properties = new MeshProperties[population]; for (int i = 0; i &lt; population; i++) { MeshProperties props = new MeshProperties(); Vector3 position = new Vector3(Random.Range(-range, range), Random.Range(-range, range), Random.Range(-range, range)); Quaternion rotation = Quaternion.Euler(Random.Range(-180, 180), Random.Range(-180, 180), Random.Range(-180, 180)); Vector3 scale = Vector3.one; props.mat = Matrix4x4.TRS(position, rotation, scale); props.color = Color.Lerp(Color.red, Color.blue, Random.value); properties[i] = props; } meshPropertiesBuffer = new ComputeBuffer(population, MeshProperties.Size()); meshPropertiesBuffer.SetData(properties); material.SetBuffer("_Properties", meshPropertiesBuffer); } private Mesh CreateQuad(float width = 1f, float height = 1f) { ... } private void Start() { Setup(); } private void Update() { Graphics.DrawMeshInstancedIndirect(mesh, 0, material, bounds, argsBuffer); } private void OnDisable() { // Release gracefully. if (meshPropertiesBuffer != null) { meshPropertiesBuffer.Release(); } meshPropertiesBuffer = null; if (argsBuffer != null) { argsBuffer.Release(); } argsBuffer = null; } } The bounds parameter of DrawMeshInstancedIndirect() is used for determining whether the mesh is in view and culling it. The documentation states Meshes are not further culled by the view frustum or baked occluders ... which gave me the impression that culling was simply disabled for all meshes drawn with DrawMeshInstanced/Indirect, but this is not so. Unity will cull all the instanced meshes if the provided mesh bounds are not in view. It will not, however, cull individual instanced meshes – you’ll have to calculate this yourself if you find it necessary. And the associated shader: Shader "Custom/InstancedIndirectColor" { SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; }; struct MeshProperties { float4x4 mat; float4 color; }; StructuredBuffer&lt;MeshProperties&gt; _Properties; v2f vert(appdata_t i, uint instanceID: SV_InstanceID) { v2f o; float4 pos = mul(_Properties[instanceID].mat, i.vertex); o.vertex = UnityObjectToClipPos(pos); o.color = _Properties[instanceID].color; return o; } fixed4 frag(v2f i) : SV_Target { return i.color; } ENDCG } } } The struct for your StructuredBuffer must be byte-wise identical throughout shader/compute shader/script or you’ll see bugginess. These structures are only matched up between script and shader by reading bytes. 1022 meshes drawn with DrawMeshInstancedIndirect A key difference here is that with DrawMeshInstanced(), we were giving Unity an array of matrices and having it automagically figure out vertex positions before we got to the shader. Here, we’re being much more direct in that we’re pushing the matrices to the GPU and applying the transformation ourselves. The shader instancing code has been cut down significantly, which is nice, and we’ve gained about a millisecond in rendering. Note, however, that this number is heavily influenced by the rest of the game. In our basic scene with nothing happening, bandwidth and CPU time is abundant, so the benefits of DrawMeshInstancedIndirect() are likely less prevalent than in the real world. The 1023 mesh limit has also disappeared. Pushing the population up to even 100, 000 has little affect on my system (Ryzen 1700, 1080Ti): 100k meshes drawn with DrawMeshInstancedIndirect Adding Movement With a Compute Shader Now that we’re using DrawMeshInstancedIndirect(), we can add some mesh movement without crushing performance. Compute Shaders are special programs that run on the GPU and allow you to utilize the massive parallel power of graphics devices for non-graphics code. I’m mostly looking to demonstrate how you can use a compute shader with the other tools shown so far, so I won’t explain compute shaders too in-depth; here is a good blog post talking about them, which covers everything better than I could. Here’s a compute shader which moves our meshes based on some “pusher”: #pragma kernel CSMain struct MeshProperties { float4x4 mat; float4 color; }; RWStructuredBuffer&lt;MeshProperties&gt; _Properties; float3 _PusherPosition; // We used to just be able to use (1, 1, 1) threads for whatever population (not sure the old limit), but a Unity update // imposed a thread limit of 65535. Now, to populations above that, we need to be more granular with our threads. [numthreads(64,1,1)] void CSMain (uint3 id : SV_DispatchThreadID) { float4x4 mat = _Properties[id.x].mat; // In a transform matrix, the position (translation) vector is the last column. float3 position = float3(mat[0][3], mat[1][3], mat[2][3]); float dist = distance(position, _PusherPosition); // Scale and reverse distance so that we get a value which fades as it gets further away. // Max distance is 5.0. dist = 5.0 - clamp(0.0, 5.0, dist); // Get the vector from the pusher to the position, and scale it. float3 push = normalize(position - _PusherPosition) * dist; // Create a new translation matrix which represents a move in a direction. float4x4 translation = float4x4( 1, 0, 0, push.x, 0, 1, 0, push.y, 0, 0, 1, push.z, 0, 0, 0, 1 ); // Apply translation to existing matrix, which will be read in the shader. _Properties[id.x].mat = mul(translation, mat); } Take note that the MeshProperties struct is byte-wise identical to the structs in the other files. I mentioned this earlier, but it’s paramount that you keep these structs in sync or your program won’t work (without error!). You might also be wondering whether you could accomplish this same thing in the regular shader as well, by changing the StructuredBuffer to a RWStructuredBuffer and doing a similar computation. The problem this introduces is that regular shaders are designed to work on a per-vertex or per-fragment basis, and so to get the same effect (moving an individual mesh) you’d have to either set some flag to mark the mesh as “moved” (horrible, absolutely kills parallelisation), or change the per-mesh approach to a per-vertex approach, probably with a different result. While I’m sure it’s possible to achieve the same thing without a compute shader, my point is that compute shaders let you specify granularity on your own terms. Of course, we need to make a couple additions to our script to actually run our compute shader. Where compute is our compute shader and pusher is the object we want to use to push: public class DrawMeshInstancedIndirectDemo : MonoBehaviour { public int population; public float range; public Material material; ++ public ComputeShader compute; ++ public Transform pusher; private ComputeBuffer meshPropertiesBuffer; private ComputeBuffer argsBuffer; private Mesh mesh; private Bounds bounds; ... private void InitializeBuffers() { ++ int kernel = compute.FindKernel("CSMain"); // Argument buffer used by DrawMeshInstancedIndirect. uint[] args = new uint[5] { 0, 0, 0, 0, 0 }; // Arguments for drawing mesh. // 0 == number of triangle indices, 1 == population, others are only relevant if drawing submeshes. args[0] = (uint)mesh.GetIndexCount(0); ... meshPropertiesBuffer = new ComputeBuffer(population, MeshProperties.Size()); meshPropertiesBuffer.SetData(properties); ++ compute.SetBuffer(kernel, "_Properties", meshPropertiesBuffer); material.SetBuffer("_Properties", meshPropertiesBuffer); } ... private void Update() { ++ int kernel = compute.FindKernel("CSMain"); ++ compute.SetVector("_PusherPosition", pusher.position); ++ // We used to just be able to use `population` here, but it looks like a Unity update imposed a thread limit (65535) on my device. ++ // This is probably for the best, but we have to do some more calculation. Divide population by numthreads.x (declared in compute shader). ++ compute.Dispatch(kernel, Mathf.CeilToInt(population / 64f), 1, 1); Graphics.DrawMeshInstancedIndirect(mesh, 0, material, bounds, argsBuffer); } } And that’s all, really. Now you know how to move and draw (hundreds of) thousands of meshes efficiently. Source code and Unity project available at https://github.com/Toqozz/blog-code/tree/master/mesh_batching.]]></summary></entry><entry><title type="html">Finding Sprite UV/Texture Coordinates in Unity</title><link href="https://toqoz.fyi/unity-sprite-texture-coordinates.html" rel="alternate" type="text/html" title="Finding Sprite UV/Texture Coordinates in Unity" /><published>2019-07-02T00:00:00+00:00</published><updated>2019-07-02T00:00:00+00:00</updated><id>https://toqoz.fyi/unity-sprite-texture-coordinates</id><content type="html" xml:base="https://toqoz.fyi/unity-sprite-texture-coordinates.html"><![CDATA[<p><img src="/assets/2019_eyedropper.png" alt="Eyedrop demo." width="751px" height="456px" />
If you want to do any kind of texture manipulation in games, you’ll need some form of texture coordinates.  If you’re in 3D, you can obtain UV coordinates of a particular point on a mesh via <a href="https://docs.unity3d.com/ScriptReference/RaycastHit.html">raycasting</a>, but there’s no easy way to achieve the same thing for sprite renders.</p>

<p>Luckily, we can calculate these coordinates ourselves.
The approach here is not very complicated, however, there are many edge cases, and the Unity documentation doesn’t exactly make things simple to arrive at a good solution on your own.</p>

<h2 id="code">Code</h2>
<p>Attach this to each sprite:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">RequireComponent</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">SpriteRenderer</span><span class="p">))]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">CoordinateMap</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">private</span> <span class="n">Sprite</span> <span class="n">sprite</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Start</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">sprite</span> <span class="p">=</span> <span class="n">GetComponent</span><span class="p">&lt;</span><span class="n">SpriteRenderer</span><span class="p">&gt;().</span><span class="n">sprite</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">Vector2</span> <span class="nf">TextureSpaceCoord</span><span class="p">(</span><span class="n">Vector3</span> <span class="n">worldPos</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">float</span> <span class="n">ppu</span> <span class="p">=</span> <span class="n">sprite</span><span class="p">.</span><span class="n">pixelsPerUnit</span><span class="p">;</span>
        
        <span class="c1">// Local position on the sprite in pixels.</span>
        <span class="n">Vector2</span> <span class="n">localPos</span> <span class="p">=</span> <span class="n">transform</span><span class="p">.</span><span class="nf">InverseTransformPoint</span><span class="p">(</span><span class="n">worldPos</span><span class="p">)</span> <span class="p">*</span> <span class="n">ppu</span><span class="p">;</span>
        
        <span class="c1">// When the sprite is part of an atlas, the rect defines its offset on the texture.</span>
        <span class="c1">// When the sprite is not part of an atlas, the rect is the same as the texture (x = 0, y = 0, width = tex.width, ...)</span>
        <span class="kt">var</span> <span class="n">texSpacePivot</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector2</span><span class="p">(</span><span class="n">sprite</span><span class="p">.</span><span class="n">rect</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">sprite</span><span class="p">.</span><span class="n">rect</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">+</span> <span class="n">sprite</span><span class="p">.</span><span class="n">pivot</span><span class="p">;</span>
        <span class="n">Vector2</span> <span class="n">texSpaceCoord</span> <span class="p">=</span> <span class="n">texSpacePivot</span> <span class="p">+</span> <span class="n">localPos</span><span class="p">;</span>

        <span class="k">return</span> <span class="n">texSpaceCoord</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="n">Vector2</span> <span class="nf">TextureSpaceUV</span><span class="p">(</span><span class="n">Vector3</span> <span class="n">worldPos</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Texture2D</span> <span class="n">tex</span> <span class="p">=</span> <span class="n">sprite</span><span class="p">.</span><span class="n">texture</span><span class="p">;</span>
        <span class="n">Vector2</span> <span class="n">texSpaceCoord</span> <span class="p">=</span> <span class="nf">TextureSpaceCoord</span><span class="p">(</span><span class="n">worldPos</span><span class="p">);</span>
        
        <span class="c1">// Pixels to UV(0-1) conversion.</span>
        <span class="n">Vector2</span> <span class="n">uvs</span> <span class="p">=</span> <span class="n">texSpaceCoord</span><span class="p">;</span>
        <span class="n">uvs</span><span class="p">.</span><span class="n">x</span> <span class="p">/=</span> <span class="n">tex</span><span class="p">.</span><span class="n">width</span><span class="p">;</span>
        <span class="n">uvs</span><span class="p">.</span><span class="n">y</span> <span class="p">/=</span> <span class="n">tex</span><span class="p">.</span><span class="n">height</span><span class="p">;</span>


        <span class="k">return</span> <span class="n">uvs</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The magic here is mostly in <code class="language-plaintext highlighter-rouge">Vector2 localPos = transform.InverseTransformPoint(worldPos) * ppu;</code>.  Here we convert to the sprites coordinate system–which ensures that we match correct coordinates regardless of scale, rotation–and then scale to pixels;</p>

<p><img src="/assets/2019_scale_demo.png" alt="Eyedropper rotation + scale demo." width="751px" height="456px" /></p>

<p>As an example of usage, here’s a basic <code class="language-plaintext highlighter-rouge">Eyedropper</code> script:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">RequireComponent</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">SpriteRenderer</span><span class="p">))]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Eyedropper</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">CoordinateMap</span> <span class="n">mapper</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">SpriteRenderer</span> <span class="n">spriteRenderer</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Sprite</span> <span class="n">spriteToEyedrop</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Start</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">spriteRenderer</span> <span class="p">=</span> <span class="n">GetComponent</span><span class="p">&lt;</span><span class="n">SpriteRenderer</span><span class="p">&gt;();</span>
        <span class="n">spriteToEyedrop</span> <span class="p">=</span> <span class="n">mapper</span><span class="p">.</span><span class="n">GetComponent</span><span class="p">&lt;</span><span class="n">SpriteRenderer</span><span class="p">&gt;().</span><span class="n">sprite</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">Input</span><span class="p">.</span><span class="nf">GetMouseButton</span><span class="p">(</span><span class="m">0</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// NOTE: if your objects aren't at zPos = 0, you'll have to adjust for that.</span>
            <span class="n">Vector2</span> <span class="n">mouseCoord</span> <span class="p">=</span> <span class="n">Input</span><span class="p">.</span><span class="n">mousePosition</span><span class="p">;</span>
            <span class="n">Vector2</span> <span class="n">worldPos</span> <span class="p">=</span> <span class="n">Camera</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="nf">ScreenToWorldPoint</span><span class="p">(</span><span class="n">mouseCoord</span><span class="p">);</span>

            <span class="n">Vector2</span> <span class="n">coords</span> <span class="p">=</span> <span class="n">mapper</span><span class="p">.</span><span class="nf">TextureSpaceCoord</span><span class="p">(</span><span class="n">worldPos</span><span class="p">);</span>
            <span class="c1">//Vector2 coords = mapper.TextureSpaceUV(worldPos);</span>

            <span class="n">Color</span> <span class="n">pixel</span> <span class="p">=</span> <span class="n">spriteToEyedrop</span><span class="p">.</span><span class="n">texture</span><span class="p">.</span><span class="nf">GetPixel</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">coords</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">coords</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
            <span class="c1">//Color pixel = sprite.texture.GetPixelBilinear(coords.x, coords.y);</span>

            <span class="n">spriteRenderer</span><span class="p">.</span><span class="n">color</span> <span class="p">=</span> <span class="n">pixel</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="notes-and-potential-alternatives">Notes and Potential Alternatives</h2>
<p>Another solution I thought of was to use a 3D raycast from the camera to the sprite, which <em>should</em> return correct a correct UV coordinate from the sprite mesh.  This solution doesn’t even work (<code class="language-plaintext highlighter-rouge">raycastHit.textureCoord</code> always returns <code class="language-plaintext highlighter-rouge">(0.0, 0.0)</code>), and you need to attach a 3D collider to your sprite renderer, which is all kinds of wrong.  You could probably get this to work by replacing your sprite renderers with quads, but you of course lose all associated advantages.</p>

<p><a href="https://stackoverflow.com/questions/44143733/unity-correct-sprite-texture-width-height">Most</a> <a href="https://gamedev.stackexchange.com/questions/117139/how-to-get-a-pixel-color-from-a-specific-sprite-on-touch-unity">solutions</a> I <a href="https://forum.unity.com/threads/uv-texture-coordinates-bounds-using-sprite-packer.400592/">found</a> calculate UVs from sprite rect calculations.  Unless you absolutely <em>never</em> want to rotate, scale, or flip your sprites, <strong>do not do this</strong>: the calculation will bork.</p>

<blockquote>
  <p>Full code and sample scene available at: <a href="https://github.com/Toqozz/blog-code/tree/master/sprite_coordinates">https://github.com/Toqozz/blog-code/tree/master/sprite_coordinates</a></p>
</blockquote>]]></content><author><name></name></author><summary type="html"><![CDATA[If you want to do any kind of texture manipulation in games, you’ll need some form of texture coordinates. If you’re in 3D, you can obtain UV coordinates of a particular point on a mesh via raycasting, but there’s no easy way to achieve the same thing for sprite renders.]]></summary></entry><entry><title type="html">Everything You Need to Know About Modding Duck Game</title><link href="https://toqoz.fyi/duck-game-mods.html" rel="alternate" type="text/html" title="Everything You Need to Know About Modding Duck Game" /><published>2018-11-20T00:00:00+00:00</published><updated>2018-11-20T00:00:00+00:00</updated><id>https://toqoz.fyi/duck-game-mods</id><content type="html" xml:base="https://toqoz.fyi/duck-game-mods.html"><![CDATA[<p><img src="/assets/2018_duck_game_title.jpg" alt="duck-game-cover.jpg" width="1600px" height="800px" />
So you want to make some mods for Duck Game?  Look no further.  This guide should tell you almost everything you need to know about making a good mod that actually functions.  We’ll talk about getting set up, explain some of Duck Game’s core mod structure, talk about networking, and hopefully most of the other little details you might need.</p>

<h2 id="is-making-mods-difficult">Is Making Mods Difficult?</h2>
<p>Mods for Duck Game can definitely be made with only a basic understanding of programming.  Whether those mods are spaghetti or not might be a different story, but making basic mods in Duck Game is relatively easy.</p>

<p>Skills that may help significantly: knowledge of basic object oriented programming, basic C# knowledge, a bit of intuition in reading other people’s code, and most of all, <strong>time</strong>.  You might have heard elsewhere that writing mods for Duck Game is very fiddly, and that’s because it is.  A lot (most?) of your time will be spent reading through the Duck Game source code to try and figure out how the developer has achieved a similar effect to something you’re trying to make, and then deciphering <em>why</em> exactly it has been done in that way.  If you don’t have much in the way of these skills just yet, making mods can be a great learning experience.</p>

<h2 id="before-starting">Before Starting</h2>
<p>First of all, please read through <a href="https://steamcommunity.com/sharedfiles/filedetails/?id=484818341">this</a> guide if you haven’t already.  It’ll help you set up your environment for writing your first mod and explain some basic variables, although I didn’t find the latter to be of much use personally.</p>

<hr />

<h2 id="the-modding-process">The Modding Process</h2>
<p>Let’s make a simple “Hello World” mod.  I’m going to explain the entire process from head to toe, which should give you some intuition on how to progress when you want to write some mods for yourself.</p>

<p>First of all, we need a mod idea.  A simple example that comes to mind is a pistol that shoots missiles.</p>

<p>The first thing we need to do is open up the Duck Game executable in a .NET decompiler (<a href="https://github.com/icsharpcode/ILSpy#ilspy-------">ILSpy</a> or <a href="https://www.jetbrains.com/decompiler/">dotPeek</a> work… I’ll be using dotPeek).  This will let us see how the game was coded and give us some hints as to how we should code things.</p>

<p><img src="/assets/2018_dotpeek.png" alt="dotPeek" width="693px" height="570px" /></p>

<p>Since we’re making a pistol, let’s navigate to the Pistol class and have a look at what’s going on;</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pistol.cs</span>
<span class="k">namespace</span> <span class="nn">DuckGame</span>
<span class="p">{</span>
  <span class="p">[</span><span class="nf">EditorGroup</span><span class="p">(</span><span class="s">"guns"</span><span class="p">)]</span>
  <span class="p">[</span><span class="nf">BaggedProperty</span><span class="p">(</span><span class="s">"isInDemo"</span><span class="p">,</span> <span class="k">true</span><span class="p">)]</span>
  <span class="k">public</span> <span class="k">class</span> <span class="nc">Pistol</span> <span class="p">:</span> <span class="n">Gun</span>
  <span class="p">{</span>
    <span class="k">private</span> <span class="n">SpriteMap</span> <span class="n">_sprite</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">Pistol</span><span class="p">(</span><span class="kt">float</span> <span class="n">xval</span><span class="p">,</span> <span class="kt">float</span> <span class="n">yval</span><span class="p">)</span>
      <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">xval</span><span class="p">,</span> <span class="n">yval</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="n">ammo</span> <span class="p">=</span> <span class="m">9</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_ammoType</span> <span class="p">=</span> <span class="p">(</span><span class="n">AmmoType</span><span class="p">)</span> <span class="k">new</span> <span class="nf">AT9mm</span><span class="p">();</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_type</span> <span class="p">=</span> <span class="s">"gun"</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SpriteMap</span><span class="p">(</span><span class="s">"pistol"</span><span class="p">,</span> <span class="m">18</span><span class="p">,</span> <span class="m">10</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">AddAnimation</span><span class="p">(</span><span class="s">"idle"</span><span class="p">,</span> <span class="m">1f</span><span class="p">,</span> <span class="k">true</span><span class="p">,</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[</span><span class="m">1</span><span class="p">]);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">AddAnimation</span><span class="p">(</span><span class="s">"fire"</span><span class="p">,</span> <span class="m">0.8f</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">3</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">AddAnimation</span><span class="p">(</span><span class="s">"empty"</span><span class="p">,</span> <span class="m">1f</span><span class="p">,</span> <span class="k">true</span><span class="p">,</span> <span class="m">2</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">graphic</span> <span class="p">=</span> <span class="p">(</span><span class="n">Sprite</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">center</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(</span><span class="m">10f</span><span class="p">,</span> <span class="m">3f</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">collisionOffset</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(-</span><span class="m">8f</span><span class="p">,</span> <span class="p">-</span><span class="m">3f</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">collisionSize</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(</span><span class="m">16f</span><span class="p">,</span> <span class="m">9f</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_barrelOffsetTL</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(</span><span class="m">18f</span><span class="p">,</span> <span class="m">2f</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_fireSound</span> <span class="p">=</span> <span class="s">"pistolFire"</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_kickForce</span> <span class="p">=</span> <span class="m">3f</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_holdOffset</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(-</span><span class="m">1f</span><span class="p">,</span> <span class="m">0.0f</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">loseAccuracy</span> <span class="p">=</span> <span class="m">0.1f</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">maxAccuracyLost</span> <span class="p">=</span> <span class="m">0.6f</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_bio</span> <span class="p">=</span> <span class="s">"Old faithful, the 9MM pistol."</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="n">_editorName</span> <span class="p">=</span> <span class="k">nameof</span> <span class="p">(</span><span class="n">Pistol</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="n">physicsMaterial</span> <span class="p">=</span> <span class="n">PhysicsMaterial</span><span class="p">.</span><span class="n">Metal</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span>
    <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="n">currentAnimation</span> <span class="p">==</span> <span class="s">"fire"</span> <span class="p">&amp;&amp;</span> <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="n">finished</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">SetAnimation</span><span class="p">(</span><span class="s">"idle"</span><span class="p">);</span>
      <span class="k">base</span><span class="p">.</span><span class="nf">Update</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnPressAction</span><span class="p">()</span>
    <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">ammo</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span>
      <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">SetAnimation</span><span class="p">(</span><span class="s">"fire"</span><span class="p">);</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="m">3</span><span class="p">;</span> <span class="p">++</span><span class="n">index</span><span class="p">)</span>
        <span class="p">{</span>
          <span class="n">Vec2</span> <span class="n">vec2</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">Offset</span><span class="p">(</span><span class="k">new</span> <span class="nf">Vec2</span><span class="p">(-</span><span class="m">9f</span><span class="p">,</span> <span class="m">0.0f</span><span class="p">));</span>
          <span class="n">Vec2</span> <span class="n">hitAngle</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">barrelVector</span><span class="p">.</span><span class="nf">Rotate</span><span class="p">(</span><span class="n">Rando</span><span class="p">.</span><span class="nf">Float</span><span class="p">(</span><span class="m">1f</span><span class="p">),</span> <span class="n">Vec2</span><span class="p">.</span><span class="n">Zero</span><span class="p">);</span>
          <span class="n">Level</span><span class="p">.</span><span class="nf">Add</span><span class="p">((</span><span class="n">Thing</span><span class="p">)</span> <span class="n">Spark</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="n">vec2</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">vec2</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">hitAngle</span><span class="p">,</span> <span class="m">0.1f</span><span class="p">));</span>
        <span class="p">}</span>
      <span class="p">}</span>
      <span class="k">else</span>
        <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">SetAnimation</span><span class="p">(</span><span class="s">"empty"</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nf">Fire</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Not so complicated!  We just assign some properties in the constructor related to the gun’s look and feel, and then code the rest of the gun’s behaviour by hooking into some functions.</p>

<p>A big thing to recognise here is that Duck Game modding is largely made possible by method overriding.  I’m sure there’s a name for this kind of pattern, but basically to make derivatives of a particular class without duplicating a lot of code, we inherit that class, override important methods and add some code, and then call the base method at some point.  This has a kind of cascade effect; in the Pistol class’ <code class="language-plaintext highlighter-rouge">Update()</code> method, we call the Gun class’ <code class="language-plaintext highlighter-rouge">Update()</code>, which calls its own <code class="language-plaintext highlighter-rouge">base.Update()</code>, etc, etc.</p>

<p>This is especially useful in creating objects for games because we often have a lot of similar base behaviour, but want to <strong>add</strong> extra things to it.  You can see this in action through the rest of the Duck Game items: Pistol inherits Gun which inherits Holdable which inherits PhysicsObject which inherits ….</p>

<p>With that in mind, we can begin making our rocket pistol;</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// RocketPistol.cs</span>

<span class="k">namespace</span> <span class="nn">DuckGame.ExampleMod</span> <span class="p">{</span>
    <span class="c1">// Allow item to appear in the level editor.</span>
    <span class="p">[</span><span class="nf">EditorGroup</span><span class="p">(</span><span class="s">"ExampleMod|guns"</span><span class="p">)]</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">RocketPistol</span><span class="p">()</span> <span class="p">:</span> <span class="n">Pistol</span> <span class="p">{</span>
        <span class="c1">// xval and yval are passed to the base class.</span>
        <span class="k">public</span> <span class="nf">RocketPistol</span><span class="p">(</span><span class="kt">float</span> <span class="n">xval</span><span class="p">,</span> <span class="kt">float</span> <span class="n">yval</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">xval</span><span class="p">,</span> <span class="n">yval</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="n">_editorName</span> <span class="p">=</span> <span class="s">"Rocket Pistol"</span><span class="p">;</span>
            <span class="c1">// We only need to change the ammo type, everything else is inherited from the base constructor.</span>
            <span class="k">this</span><span class="p">.</span><span class="n">_ammoType</span> <span class="p">=</span> <span class="p">(</span><span class="n">AmmoType</span><span class="p">)</span> <span class="k">new</span> <span class="nf">ATMissile</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As you can see, we only really need to change the ammo type and the base constructor will handle the rest.  Figuring out how and which ammo type to switch to was relatively straightforward because the ammo types are labeled quite clearly – this isn’t always the case, and you might need to do some digging to find out what to do to get what you want working.</p>

<p>At this point I’d recommend making a custom map using the game’s level editor and placing a spawner with your item.  As per the <code class="language-plaintext highlighter-rouge">EditorGroup</code> code above, it can be found under ExampleMod -&gt; guns -&gt; Rocket Pistol.</p>

<p><img src="/assets/2018_rocket_pistol_in_menu.png" alt="Rocket pistol in level editor" width="1343px" height="471px" />
<img src="/assets/2018_rocket_pistol_in_action.gif" alt="Rocket pistol in action" width="782px" height="466px" /></p>

<h3 id="more-functionality">More Functionality</h3>
<p>Lets add something else.  What if we wanted to make it so that the weapon gets dropped every time we shoot?  Here’s where some intuition comes in, and getting to know the codebase will help.  Knocking items out of people’s hands is a fairly common action in Duck Game, so you can bet there’s a method in place already for it.  The most logical place for this method would be on the Duck object – indeed, <code class="language-plaintext highlighter-rouge">Disarm()</code> exists.  The next thing we need is a reference to the Duck object so that we can actually call this method.  A logical place for this might be on the item – indeed, <code class="language-plaintext highlighter-rouge">owner</code> exists.</p>

<p>Once we’ve figured out what method we want to use, it’s relatively easy to find out how to use it.  Right click on the method in your decompiler and click “Find Usages”.  This will show you all the usages of the method in the Duck Game source, and is your number one guide in figuring out how to use something.
<img src="/assets/2018_dotpeek_find_usages.gif" alt="dotPeek find usages" width="1131px" height="587px" /></p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// RocketPistol.cs</span>

<span class="k">namespace</span> <span class="nn">DuckGame.ExampleMod</span> <span class="p">{</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">RocketPistol</span> <span class="p">:</span> <span class="n">Pistol</span> <span class="p">{</span>
        <span class="c1">// xval and yval are passed to the base class.</span>
        <span class="k">public</span> <span class="nf">RocketPistol</span><span class="p">(</span><span class="kt">float</span> <span class="n">xval</span><span class="p">,</span> <span class="kt">float</span> <span class="n">yval</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">xval</span><span class="p">,</span> <span class="n">yval</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="n">_editorName</span> <span class="p">=</span> <span class="s">"Rocket Pistol"</span><span class="p">;</span>
            <span class="c1">// We only need to change the ammo type, everything else is inherited from the base constructor.</span>
            <span class="k">this</span><span class="p">.</span><span class="n">_ammoType</span> <span class="p">=</span> <span class="p">(</span><span class="n">AmmoType</span><span class="p">)</span> <span class="k">new</span> <span class="nf">ATMissile</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnPressAction</span><span class="p">()</span> <span class="p">{</span>
            <span class="c1">// Run the base action.</span>
            <span class="k">base</span><span class="p">.</span><span class="nf">OnPressAction</span><span class="p">();</span>
            <span class="c1">// It's best practice to double check that we have an owner before accessing it.</span>
            <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">owner</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
                <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">owner</span> <span class="k">as</span> <span class="n">Duck</span><span class="p">).</span><span class="nf">Disarm</span><span class="p">((</span><span class="n">Thing</span><span class="p">)</span> <span class="k">this</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/2018_rocket_pistol_with_disarm.gif" alt="Rocket pistol with disarm" width="782px" height="466px" /></p>

<h3 id="changing-look-and-details">Changing Look and Details</h3>

<p>Changing the look of your item can be easy or difficult depending on how much you want to change.  For starters, let’s change the weapon sprite to something more fitting.  Place the sprite you want to use in your mod’s content folder (<code class="language-plaintext highlighter-rouge">Documents/DuckGame/Mods/ExampleMod/content</code> if you’re following along), and give it a fitting name.  I’ll be using something I prepared earlier;</p>

<p><img src="/assets/2018_rocket_pistol_sprite.png" alt="Rocket pistol sprite" width="260px" height="260px" />
<em>(real sprite is much smaller (26x26))</em></p>

<blockquote>
  <p>Sprites / sounds used can be found at <a href="https://www.dropbox.com/sh/eyfkrfl7pen9g4q/AAA5nLatiaS457ny3dBKY7XZa?dl=0">this Dropbox folder.</a></p>
</blockquote>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="nn">DuckGame.DevelopmentMod</span> <span class="p">{</span>
    <span class="p">[</span><span class="nf">EditorGroup</span><span class="p">(</span><span class="s">"ExampleMod|guns"</span><span class="p">)]</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">RocketPistol</span> <span class="p">:</span> <span class="n">Pistol</span> <span class="p">{</span>
         <span class="c1">// Property to hold our sprite.</span>
        <span class="k">private</span> <span class="n">SpriteMap</span> <span class="n">_sprite</span><span class="p">;</span>

        <span class="k">public</span> <span class="nf">RocketPistol</span><span class="p">(</span><span class="kt">float</span> <span class="n">xval</span><span class="p">,</span> <span class="kt">float</span> <span class="n">yval</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">xval</span><span class="p">,</span> <span class="n">yval</span><span class="p">)</span> <span class="p">{</span>
            <span class="p">...</span>

            <span class="c1">// Get the sprite from content/rocketpistol.png (extensions not included).</span>
            <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SpriteMap</span><span class="p">(</span><span class="nf">GetPath</span><span class="p">(</span><span class="s">"rocketpistol"</span><span class="p">),</span> <span class="m">26</span><span class="p">,</span> <span class="m">26</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.</span><span class="n">graphic</span> <span class="p">=</span> <span class="p">(</span><span class="n">Sprite</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">;</span>
            <span class="k">this</span><span class="p">.</span><span class="n">center</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(</span><span class="m">11f</span><span class="p">,</span> <span class="m">16f</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.</span><span class="n">collisionOffset</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(-</span><span class="m">7f</span><span class="p">,</span> <span class="p">-</span><span class="m">4f</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.</span><span class="n">collisionSize</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(</span><span class="m">17f</span><span class="p">,</span> <span class="m">9f</span><span class="p">);</span>

            <span class="c1">// Switch the shot sound with a custom one (located at content/sounds/laser.</span>
            <span class="k">this</span><span class="p">.</span><span class="n">_fireSound</span> <span class="p">=</span> <span class="nf">GetPath</span><span class="p">(</span><span class="s">"sounds/laser"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Most of this should be relatively straightforward by now.  For reference, to figure out which variables to change I simply looked at the pistol class (listed above) and applied some common sense and trial / error.</p>

<p>There are a few points here that would benefit from an explanation however, namely the collision information.  A picture is worth a thousand words, so here’s a diagram:</p>

<p><img src="/assets/2018_dg_sprite_diagram.png" alt="Rocket pistol sprite diagram" width="520px" height="520px" />
<em><strong>red</strong> – center<br /><strong>green</strong> – collision offset<br /><strong>yellow</strong> – collision size</em></p>

<p>The first point to understand here are first of all that 0, 0 is placed at the top left of the sprite.  The second point is that the collision offset and collision size are <em>relative</em> values; collision offset is relative to the center point, and collision size is relative to the collision offset.</p>

<p>With that sorted, let’s move on to something more fun: <strong>animations</strong>.  Animations in duck game are mainly delivered in the form of sprite sheets.  You’re probably familiar with these if you’ve done any kind of game development before.  Here’s one I made for our rocket pistol:</p>

<p><img src="/assets/2018_rocket_pistol_frames.png" alt="Rocket pistol frames" width="4420px" height="260px" />
<em>(real sprite sheet has a transparent background)</em></p>

<p>The idea is simple; you make a sprite sheet similar to the above which contains all the frames of your animation, preferably in order.  You can – and should – store multiple “separate” animations within this file if you need more than one animation for the same object.  We can then create and assign animations from within the weapon’s constructor;</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">RocketPistol</span><span class="p">(</span><span class="kt">float</span> <span class="n">xval</span><span class="p">,</span> <span class="kt">float</span> <span class="n">yval</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">xval</span><span class="p">,</span> <span class="n">yval</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_editorName</span> <span class="p">=</span> <span class="s">"Rocket Pistol"</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_ammoType</span> <span class="p">=</span> <span class="p">(</span><span class="n">AmmoType</span><span class="p">)</span> <span class="k">new</span> <span class="nf">ATMissile</span><span class="p">();</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SpriteMap</span><span class="p">(</span><span class="nf">GetPath</span><span class="p">(</span><span class="s">"rocketpistol"</span><span class="p">),</span> <span class="m">26</span><span class="p">,</span> <span class="m">26</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>

    <span class="c1">// Create idle animation.</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">AddAnimation</span><span class="p">(</span><span class="s">"idle"</span><span class="p">,</span> <span class="p">.</span><span class="m">08f</span><span class="p">,</span> <span class="k">true</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="m">5</span><span class="p">);</span>
    <span class="c1">// Create fire animation (more of a reload animation here really).</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">AddAnimation</span><span class="p">(</span><span class="s">"fire"</span><span class="p">,</span> <span class="p">.</span><span class="m">15f</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="m">6</span><span class="p">,</span> <span class="m">7</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">9</span><span class="p">,</span> <span class="m">10</span><span class="p">,</span> <span class="m">11</span><span class="p">,</span> <span class="m">12</span><span class="p">,</span> <span class="m">13</span><span class="p">);</span>
    <span class="c1">// Create empty animation.</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">AddAnimation</span><span class="p">(</span><span class="s">"empty"</span><span class="p">,</span> <span class="p">.</span><span class="m">08f</span><span class="p">,</span> <span class="k">true</span><span class="p">,</span> <span class="m">14</span><span class="p">,</span> <span class="m">15</span><span class="p">,</span> <span class="m">16</span><span class="p">);</span>

    <span class="k">this</span><span class="p">.</span><span class="n">graphic</span> <span class="p">=</span> <span class="p">(</span><span class="n">Sprite</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="n">center</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(</span><span class="m">11f</span><span class="p">,</span> <span class="m">16f</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="n">collisionOffset</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(-</span><span class="m">7f</span><span class="p">,</span> <span class="p">-</span><span class="m">4f</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="n">collisionSize</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vec2</span><span class="p">(</span><span class="m">17f</span><span class="p">,</span> <span class="m">9f</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_fireSound</span> <span class="p">=</span> <span class="nf">GetPath</span><span class="p">(</span><span class="s">"sounds/laser"</span><span class="p">);</span>

    <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">SetAnimation</span><span class="p">(</span><span class="s">"idle"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">AddAnimation()</code> function takes 4 parameters: the name of the animation, the speed of each frame, whether the animation loops or not, and then the actual animation frames.  I’m not sure exactly what the speed parameter represents, but lower values will make the animation slower, and higher values faster.  You can also repeat frames if you need part of an animation to last a little longer – the default pistol does this with its “fire” animation.<br />To play an animation, we simply call <code class="language-plaintext highlighter-rouge">SetAnimation()</code> with the animation’s name.</p>

<p>To get our animations to play at the correct time, I’ve copied some of the code from the base pistol class’ <code class="language-plaintext highlighter-rouge">OnPressAction()</code> function into our own.  This lets us have a bit more control over when we can set animations and under what circumstance.  I’ve also added the smoke you see when you fire the regular rocket launcher, removed the pistol sparks, and made some of our logic make a bit more sense:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnPressAction</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">ammo</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// If gun is going to be empty, don't load a new rocket.</span>
        <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">.</span><span class="nf">SetAnimation</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">ammo</span> <span class="p">==</span> <span class="m">1</span> <span class="p">?</span> <span class="s">"empty"</span> <span class="p">:</span> <span class="s">"fire"</span><span class="p">);</span>

        <span class="c1">// Smoke, modified from Bazooka.cs</span>
        <span class="c1">// This code has probably been lost in translation when decompiling, which is why it's so cryptic.</span>
        <span class="kt">int</span> <span class="n">num</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">5</span><span class="p">;</span> <span class="p">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">MusketSmoke</span> <span class="n">musketSmoke</span> <span class="p">=</span>
                <span class="k">new</span> <span class="nf">MusketSmoke</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">x</span> <span class="p">-</span> <span class="m">16f</span> <span class="p">+</span> <span class="n">Rando</span><span class="p">.</span><span class="nf">Float</span><span class="p">(</span><span class="m">32f</span><span class="p">)</span> <span class="p">+</span> <span class="k">this</span><span class="p">.</span><span class="n">offDir</span> <span class="p">*</span> <span class="m">10f</span><span class="p">),</span> <span class="k">this</span><span class="p">.</span><span class="n">y</span> <span class="p">-</span> <span class="m">16f</span> <span class="p">+</span> <span class="n">Rando</span><span class="p">.</span><span class="nf">Float</span><span class="p">(</span><span class="m">32f</span><span class="p">));</span>
            <span class="n">musketSmoke</span><span class="p">.</span><span class="n">depth</span> <span class="p">=</span> <span class="p">(</span><span class="n">Depth</span><span class="p">)</span> <span class="p">((</span><span class="kt">float</span><span class="p">)</span> <span class="p">(.</span><span class="m">9f</span> <span class="p">+</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span> <span class="n">i</span> <span class="p">*</span> <span class="p">(</span><span class="m">1f</span> <span class="p">/</span> <span class="m">1000f</span><span class="p">)));</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">num</span> <span class="p">&lt;</span> <span class="m">3</span><span class="p">)</span>
                <span class="n">musketSmoke</span><span class="p">.</span><span class="n">move</span><span class="p">.</span><span class="n">x</span> <span class="p">-=</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">offDir</span> <span class="p">*</span> <span class="n">Rando</span><span class="p">.</span><span class="nf">Float</span><span class="p">(</span><span class="m">0.1f</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">num</span> <span class="p">&gt;</span> <span class="m">3</span> <span class="p">&amp;&amp;</span> <span class="n">num</span> <span class="p">&lt;</span> <span class="m">5</span><span class="p">)</span>
                <span class="n">musketSmoke</span><span class="p">.</span><span class="n">fly</span><span class="p">.</span><span class="n">x</span> <span class="p">+=</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">offDir</span> <span class="p">*</span> <span class="p">(</span><span class="m">2f</span> <span class="p">+</span> <span class="n">Rando</span><span class="p">.</span><span class="nf">Float</span><span class="p">(</span><span class="m">7.8f</span><span class="p">));</span>
            <span class="n">Level</span><span class="p">.</span><span class="nf">Add</span><span class="p">((</span><span class="n">Thing</span><span class="p">)</span> <span class="n">musketSmoke</span><span class="p">);</span>
            <span class="p">++</span><span class="n">num</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// It makes more sense to disarm here because it only happens when we actually shoot a rocket.</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">owner</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">owner</span> <span class="k">as</span> <span class="n">Duck</span><span class="p">).</span><span class="nf">Disarm</span><span class="p">((</span><span class="n">Thing</span><span class="p">)</span> <span class="k">this</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">this</span><span class="p">.</span><span class="nf">Fire</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">...</span>
</code></pre></div></div>

<p>Copying code from the base class into your custom one is something you’ll find yourself doing often in order to have more control over what the item does – intricate mods can quickly become very tricky because of this.</p>

<p><img src="/assets/2018_rocket_pistol_sprites_and_animations.gif" alt="Rocket Pistol with custom sprites and animations" width="602px" height="540px" /></p>

<p>I now realise that most of the reload animation gets lost in the smoke – but it’s a nice detail nonetheless.</p>

<hr />

<h2 id="publishing">Publishing</h2>

<p>To upload your mod to the steam workshop, I recommend reading the section titled “Uploading and updating” of the <a href="https://steamcommunity.com/sharedfiles/filedetails/?id=484818341">official steam guide</a>.</p>

<hr />

<h2 id="networking">Networking</h2>

<p>There’s not a lot of information out there on making your mod work online.</p>

<p>The first thing you should know is that Duck Game doesn’t use your typical client-server architecture.  There is a main host that handles level transitions and things like that, but each duck / computer effectively acts as its own server for objects that it thinks it owns.  Examples of these might be items that you’ve picked up (weapons, boxes, etc), or things you’ve interacted with (doors, mostly).  The main thing to realise here is that all clients work together to send and receive information and synchronise their version of the scene.  Each duck is responsible for synchronising the things that they own, and your mod is responsible for specifying exactly how that happens.</p>

<h3 id="state-bindings">State Bindings</h3>
<p>Variables that have state bindings attached to them will have their values sent from the object owner to the other clients.  You should use these to keep important variables synchronised across clients.</p>

<p>An example of state bindings being used in the official game is for grenades;</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Grenade</span> <span class="p">:</span> <span class="n">Gun</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">StateBinding</span> <span class="n">_timerBinding</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StateBinding</span><span class="p">(</span><span class="k">nameof</span> <span class="p">(</span><span class="n">_timer</span><span class="p">),</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
    <span class="k">public</span> <span class="n">StateBinding</span> <span class="n">_pinBinding</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StateBinding</span><span class="p">(</span><span class="k">nameof</span> <span class="p">(</span><span class="n">_pin</span><span class="p">),</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="n">_pin</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">float</span> <span class="n">_timer</span> <span class="p">=</span> <span class="m">1.2f</span><span class="p">;</span>
<span class="p">...</span>

    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
      <span class="k">base</span><span class="p">.</span><span class="nf">Update</span><span class="p">();</span>
      <span class="k">if</span> <span class="p">(!</span><span class="k">this</span><span class="p">.</span><span class="n">_pin</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="n">_timer</span> <span class="p">-=</span> <span class="m">0.01f</span><span class="p">;</span>

      <span class="k">if</span> <span class="p">(!</span><span class="k">this</span><span class="p">.</span><span class="n">_localDidExplode</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">_timer</span> <span class="p">&lt;</span> <span class="m">0.0</span> <span class="p">{</span>
          <span class="k">this</span><span class="p">.</span><span class="nf">CreateExplosion</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">position</span><span class="p">);</span>
       <span class="p">}</span>
<span class="p">...</span>

    <span class="p">}</span>
<span class="p">...</span>

<span class="p">}</span>
</code></pre></div></div>
<blockquote>
  <p>Note that this code has been simplified, the real decompiled code is much  more complex.</p>
</blockquote>

<p>There are two statebindings here, one for the pin, and one for the timer.  Synchronising the pin means that each client knows when to start their timer, and synchronising the timer means that the grenade should explode at the same time on each client, even if someone lags and gets the pin message late.  Really, the game would probably work fine most of the time with just one of these, but both are used to cover a few edge cases and make everything a bit smoother.</p>

<p>It’s not necessary to synchronise everything.  Try to reason about what your mod <em>needs</em> to run properly, and what will <em>help</em> it run better.  This is how almost all efficient networking works.  If you were making a sticky grenade, it probably doesn’t make sense to update the grenade’s position over each network frame to make it follow a player.  Instead, you could send the player it was stuck to just once, and update the position locally.</p>

<p> 
There’s a degree of authority involved with state bindings.  You can’t really change a binded variable of an object you don’t own; if you do, the owner will sooner or later tell you what the real value of that variable is and override your changes.  This doesn’t mean that you should rely on network variables; good networking runs locally and keeps itself synchronised as often as possible.</p>

<p>On the PC release, state bindings are generally synchronised as soon as possible upon being changed.</p>

<h3 id="fondle">Fondle()</h3>
<p><code class="language-plaintext highlighter-rouge">Fondle()</code> is used to take to take ownership of an object.  As we know, this mostly means handing over control of the state bindings.  When a local Duck picks up or interacts with an object, it automatically calls <code class="language-plaintext highlighter-rouge">Fondle()</code> on the object to make sure it’s in control of the object.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Door.cs</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnSoftImpact</span><span class="p">(</span><span class="n">MaterialThing</span> <span class="n">with</span><span class="p">,</span> <span class="n">ImpactedFrom</span> <span class="k">from</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">with</span><span class="p">.</span><span class="n">isServerForObject</span> <span class="p">&amp;&amp;</span> <span class="k">this</span><span class="p">.</span><span class="n">locked</span> <span class="p">&amp;&amp;</span> <span class="n">with</span> <span class="k">is</span> <span class="n">Key</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">Network</span><span class="p">.</span><span class="n">isActive</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// Take ownership of the door object.</span>
      <span class="n">with</span><span class="p">.</span><span class="nf">Fondle</span><span class="p">((</span><span class="n">Thing</span><span class="p">)</span> <span class="k">this</span><span class="p">);</span>
      <span class="n">Send</span><span class="p">.</span><span class="nf">Message</span><span class="p">((</span><span class="n">NetMessage</span><span class="p">)</span> <span class="k">new</span> <span class="nf">NMUnlockDoor</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
      <span class="k">this</span><span class="p">.</span><span class="n">networkUnlockMessage</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">this</span><span class="p">.</span><span class="nf">UnlockDoor</span><span class="p">(</span><span class="n">with</span> <span class="k">as</span> <span class="n">Key</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">base</span><span class="p">.</span><span class="nf">OnSoftImpact</span><span class="p">(</span><span class="n">with</span><span class="p">,</span> <span class="k">from</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If your mod wants to change the state of some world object (such as a level block), you should use <code class="language-plaintext highlighter-rouge">Fondle()</code> to take ownership beforehand.  This will keep all clients in sync and thus maintain consistency.</p>

<blockquote>
  <p>Aside, broken doors in Duck Game are considered <code class="language-plaintext highlighter-rouge">_fucked</code>.</p>
  <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(!</span><span class="k">this</span><span class="p">.</span><span class="n">_fucked</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">_hitPoints</span> <span class="p">&lt;</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">_maxHealth</span> <span class="p">/</span> <span class="m">2.0</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SpriteMap</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">secondaryFrame</span> <span class="p">?</span> <span class="s">"flimsyDoorDamaged"</span> <span class="p">:</span> <span class="s">"doorFucked"</span><span class="p">,</span> <span class="m">32</span><span class="p">,</span> <span class="m">32</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="n">graphic</span> <span class="p">=</span> <span class="p">(</span><span class="n">Sprite</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="n">_sprite</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="n">_fucked</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>
</blockquote>

<h3 id="leveladd-and-isserverforobject"><code class="language-plaintext highlighter-rouge">Level.Add()</code> and <code class="language-plaintext highlighter-rouge">isServerForObject</code></h3>
<p>When you need to add something to a level, be it a feather, box, bullet, or otherwise, chances are you’ll be using <code class="language-plaintext highlighter-rouge">Level.Add()</code> in some form or another.</p>

<p>When a client who is the object owner calls <code class="language-plaintext highlighter-rouge">Level.Add()</code>, it adds the object to the level and marks its entirety to be sent to each client.  When a client who isn’t the object owner calls <code class="language-plaintext highlighter-rouge">Level.Add()</code>, it only adds the object to the level.  For this reasoning, it is important that you <strong>only</strong> call <code class="language-plaintext highlighter-rouge">Level.Add()</code> for the object’s owner; if you don’t, chances are two copies will be spawned – one local (spawned by you) and one remote (spawned by the owner).</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// From a custom fire grenade mod.</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnExplode</span><span class="p">(</span><span class="n">Vec2</span> <span class="n">pos</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">isServerForObject</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">_fireExplodeAmount</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="kt">float</span> <span class="n">speedx</span> <span class="p">=</span> <span class="n">Rando</span><span class="p">.</span><span class="nf">Float</span><span class="p">(</span><span class="n">_fireExplodeSpeed</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="p">-</span><span class="n">_fireExplodeSpeed</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
            <span class="kt">float</span> <span class="n">speedy</span> <span class="p">=</span> <span class="n">Rando</span><span class="p">.</span><span class="nf">Float</span><span class="p">(-</span><span class="n">_fireExplodeSpeed</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
            <span class="n">Level</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">SmallFire</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">speedx</span><span class="p">,</span> <span class="n">speedy</span><span class="p">,</span>
                <span class="k">false</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="k">true</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="k">false</span><span class="p">));</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="n">SFX</span><span class="p">.</span><span class="nf">Play</span><span class="p">(</span><span class="nf">GetPath</span><span class="p">(</span><span class="s">"sounds"</span> <span class="p">+</span> <span class="n">Path</span><span class="p">.</span><span class="n">DirectorySeparatorChar</span> <span class="p">+</span> <span class="s">"incendiary.wav"</span><span class="p">),</span> <span class="m">1f</span><span class="p">,</span> <span class="m">0f</span><span class="p">,</span> <span class="m">0f</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The same goes for playing sounds which should be synchronized, but you’ll have to use a <code class="language-plaintext highlighter-rouge">NetSoundBinding</code>, which I won’t cover here (it’s very similar to a <code class="language-plaintext highlighter-rouge">StateBinding</code>).</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// Duck.cs</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Scream</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">Network</span><span class="p">.</span><span class="n">isActive</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">isServerForObject</span><span class="p">)</span>
            <span class="k">this</span><span class="p">.</span><span class="n">_netScream</span><span class="p">.</span><span class="nf">Play</span><span class="p">(</span><span class="m">1f</span><span class="p">,</span> <span class="m">0.0f</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="notes--extra-thought-dump-stuff">Notes / extra thought dump stuff</h2>
<p>Resources that helped me:</p>
<ul>
  <li>https://steamcommunity.com/sharedfiles/filedetails/?id=484818341</li>
  <li>https://steamcommunity.com/sharedfiles/filedetails/?id=595956552</li>
  <li>https://steamcommunity.com/app/312530/discussions/3/458604254441171969/</li>
  <li>https://steamcommunity.com/app/312530/discussions/3/541906989409705452/</li>
</ul>

<h3 id="spinning">spinning</h3>
<p>Setting <code class="language-plaintext highlighter-rouge">gravMultiplier</code> to a value of 0 or less, or <code class="language-plaintext highlighter-rouge">weight</code> to a value of 5 or more, will stop an item from spinning in the air.  Only classes inheriting from <code class="language-plaintext highlighter-rouge">Gun</code> spin.</p>

<h3 id="soft-impact-vs-solid-impact">soft impact vs solid impact</h3>
<p>Soft impacts happen when an object moves through something (throwing a grenade sideways through boxes).  Solid impacts happen when an object hits something that causes it to stop / triggers physics (throwing a grenade against a wall).</p>]]></content><author><name></name></author><summary type="html"><![CDATA[So you want to make some mods for Duck Game? Look no further. This guide should tell you almost everything you need to know about making a good mod that actually functions. We’ll talk about getting set up, explain some of Duck Game’s core mod structure, talk about networking, and hopefully most of the other little details you might need.]]></summary></entry><entry><title type="html">An Object Database Pattern for Unity3D</title><link href="https://toqoz.fyi/unity-object-database.html" rel="alternate" type="text/html" title="An Object Database Pattern for Unity3D" /><published>2018-08-30T00:00:00+00:00</published><updated>2018-08-30T00:00:00+00:00</updated><id>https://toqoz.fyi/unity-object-database</id><content type="html" xml:base="https://toqoz.fyi/unity-object-database.html"><![CDATA[<p>Assigning object references through the Unity inspector is a great tool.  Unfortunately though, it tends to really get in the way of doing code-based object instantiation; in particular, there’s no clean Unity-endorsed solution to making simple static classes which utilize game objects.</p>

<p>When I really need to solve this problem, I’ve been using a scriptable object database type solution.  I’ll tie this post into my <a href="https://toqoz.svbtle.com/a-unity-inventory-system-that-actually-works">inventory post</a>, where we needed a simple and tangle-free way to associate item names with game objects, because raw jsonified object references would change between builds.</p>

<h2 id="what-we-want">What We Want</h2>
<p>What we really want here is pretty basic.  We just want a way to do something like <code class="language-plaintext highlighter-rouge">ItemDatabase.GetActual("ruby");</code> and get back an asset – be it a prefab, scriptable object, sprite, whatever.  A dictionary of sorts, basically.  It also needs to be able to be accessed without first instantiating something in the scene – no <code class="language-plaintext highlighter-rouge">MonoBehaviour</code>.</p>

<p>The only way to achieve this is of course using <code class="language-plaintext highlighter-rouge">Resources.Load()</code> in some way.</p>

<h2 id="solution">Solution</h2>
<p>Let’s start with a really simple pseudo-dictionary implementation.  I’m just using <code class="language-plaintext highlighter-rouge">switch</code> here because it’s much easier to demonstrate the concept with.  If you’re using this kind of pattern for something serious, consider using a <a href="https://github.com/azixMcAze/Unity-SerializableDictionary"><code class="language-plaintext highlighter-rouge">SerializableDictionary</code> type</a> (just copy in the <code class="language-plaintext highlighter-rouge">Assets/SerializableDictionary</code> folder into your project), which will make insertion / deletion far easier.</p>

<blockquote>
  <p>Check out <a href="https://github.com/Toqozz/blog-code/blob/master/database/SoundDatabase.cs"><code class="language-plaintext highlighter-rouge">SoundDatabase.cs</code></a> in the <a href="https://github.com/Toqozz/blog-code/blob/master/database">github repo</a> of this post for an example of a serializable dictionary being used in a solution like this, though I don’t recommend actually using it in production.</p>
</blockquote>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ItemDatabase.cs</span>

<span class="p">[</span><span class="nf">CreateAssetMenu</span><span class="p">(</span><span class="n">menuName</span> <span class="p">=</span> <span class="s">"Items/Database"</span><span class="p">,</span> <span class="n">fileName</span> <span class="p">=</span> <span class="s">"ItemDatabase.asset"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ItemDatabase</span> <span class="p">:</span> <span class="n">ScriptableObject</span> <span class="p">{</span>
    <span class="c1">// Item objects.</span>
    <span class="k">public</span> <span class="n">Item</span> <span class="n">Ruby</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Item</span> <span class="n">Sapphire</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Item</span> <span class="n">Emerald</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Item</span> <span class="n">Amethyst</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">Item</span> <span class="nf">GetActual</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">name</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">//Debug.Log("GetActual(): name is null or empty.  You're either checking an empty slot or using this function incorrectly.");</span>
            <span class="k">return</span> <span class="k">null</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">switch</span> <span class="p">(</span><span class="n">name</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">())</span> <span class="p">{</span>
            <span class="k">case</span> <span class="s">"ruby"</span><span class="p">:</span> <span class="k">return</span> <span class="n">Ruby</span><span class="p">;</span>
            <span class="k">case</span> <span class="s">"sapphire"</span><span class="p">:</span> <span class="k">return</span> <span class="n">Sapphire</span><span class="p">;</span>
            <span class="k">case</span> <span class="s">"emerald"</span><span class="p">:</span> <span class="k">return</span> <span class="n">Emerald</span><span class="p">;</span>
            <span class="k">case</span> <span class="s">"amethyst"</span><span class="p">:</span> <span class="k">return</span> <span class="n">Amethyst</span><span class="p">;</span>

            <span class="k">default</span><span class="p">:</span>
                <span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Could not find an Item for key \""</span> <span class="p">+</span> <span class="n">name</span> <span class="p">+</span> <span class="s">"\", is it typed correctly?"</span><span class="p">);</span>
                <span class="k">return</span> <span class="k">null</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And create the corresponding scriptable object asset in your <code class="language-plaintext highlighter-rouge">Resources</code> folder:</p>

<p><img src="/assets/2018_creating_item_database.gif" alt="Creating the item database" /></p>

<p>Now we can resolve object references from a “pure code” class if we need to.  Going back to our inventory example, we can get reliable item references without needing any “living” game objects:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Item.cs</span>

<span class="p">...</span>

<span class="p">[</span><span class="n">System</span><span class="p">.</span><span class="n">Serializable</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ItemInstance</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ItemIdentifier</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Quantity</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Quality</span><span class="p">.</span><span class="n">QualityGrade</span> <span class="n">Quality</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsNew</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">Item</span> <span class="n">_item</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Item</span> <span class="n">item</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">_item</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">_item</span> <span class="p">=</span> <span class="p">((</span><span class="n">ItemDatabase</span><span class="p">)</span> <span class="n">Resources</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="s">"ItemDatabase"</span><span class="p">)).</span><span class="nf">GetActual</span><span class="p">(</span><span class="n">ItemIdentifier</span><span class="p">);</span>
            <span class="p">}</span>

            <span class="k">return</span> <span class="n">_item</span><span class="p">;</span>
       <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="nf">ItemInstance</span><span class="p">(</span><span class="kt">string</span> <span class="n">itemName</span><span class="p">,</span> <span class="kt">int</span> <span class="n">quantity</span><span class="p">,</span> <span class="n">Quality</span><span class="p">.</span><span class="n">QualityGrade</span> <span class="n">quality</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">isNew</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">ItemIdentifier</span> <span class="p">=</span> <span class="n">itemName</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">Quantity</span> <span class="p">=</span> <span class="n">quantity</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">Quality</span> <span class="p">=</span> <span class="n">quality</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">IsNew</span> <span class="p">=</span> <span class="n">isNew</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="take-note">Take Note</h2>
<p>We now have a tangle free way to retrieve game object references.  <code class="language-plaintext highlighter-rouge">Resources.Load()</code> is an excellent tool to know about, but make no mistake, assigning objects to <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> variables is definitely the preferred solution <em>most of the time</em>.  Doing so allows Unity to perform some space-wise optimizations and not include any unused assets in your builds.  When we abandon this system, Unity is forced to include all assets in the  <code class="language-plaintext highlighter-rouge">Resources/</code> folder.  <em>Note:</em> this includes objects that your database links to.</p>

<p>Some people really resent any use of the <code class="language-plaintext highlighter-rouge">Resources/</code> folder.  I feel as though this resentment is a little misguided sometimes.  To my knowledge, the worst thing about using the <code class="language-plaintext highlighter-rouge">Resources/</code> folder is that it potentially makes your build a bit bigger.  This seems like a really insignificant trade-off when compared to the benefits that using it can bring–<code class="language-plaintext highlighter-rouge">MonoBehaviour</code>-only type problem solving can seriously get in the way of elegant code-based solutions.  For these scenarios, I think it’s well worthwhile to make good use of <code class="language-plaintext highlighter-rouge">Resources.Load()</code>.</p>

<p>To avoid any confusion, when I say <code class="language-plaintext highlighter-rouge">MonoBehaviour</code>-only type problem solving, I mean avoiding all use of the <code class="language-plaintext highlighter-rouge">Resources/</code> folder and strictly assigning objects in the inspector (drag and drop).</p>

<p>Further, note that you probably shouldn’t use something like this for huge databases.  Loading all those assets into memory can take time.  A solution might be to load the database once using an Init() method when the game starts.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Assigning object references through the Unity inspector is a great tool. Unfortunately though, it tends to really get in the way of doing code-based object instantiation; in particular, there’s no clean Unity-endorsed solution to making simple static classes which utilize game objects.]]></summary></entry><entry><title type="html">Making a Painless Inventory System with Scriptable Objects in Unity</title><link href="https://toqoz.fyi/unity-painless-inventory.html" rel="alternate" type="text/html" title="Making a Painless Inventory System with Scriptable Objects in Unity" /><published>2018-06-28T00:00:00+00:00</published><updated>2018-06-28T00:00:00+00:00</updated><id>https://toqoz.fyi/unity-painless-inventory</id><content type="html" xml:base="https://toqoz.fyi/unity-painless-inventory.html"><![CDATA[<p>Something that might surprise you is that there is actually a lack of content on making easy to use inventory systems in Unity.</p>

<p>When I say easy to use, I mean:</p>
<ul>
  <li><strong>Based on Scriptable Objects.</strong></li>
  <li>Easily saved to and loaded from disk.</li>
  <li>Resource efficient.</li>
  <li>Integrates with the Unity UI.</li>
</ul>

<p>And I think that these points are quite significant, particularly the first, because it ties everything else together.  The majority of material I found online<a href="https://unity3d.com/learn/tutorials/projects/adventure-game-tutorial/inventory"><sup>1</sup></a> <a href="https://github.com/nzhul/inventory-system"><sup>2</sup></a> <a href="https://www.reddit.com/r/Unity3D/comments/6yt3e5/anybody_have_any_tips_on_a_scriptable_object/"><sup>3</sup></a> <a href="https://answers.unity.com/questions/1260736/scriptable-objects-as-inventory-items-unable-to-sa.html"><sup>4…</sup></a> really doesn’t explain much more than some tips on getting started, or misses these marks.  So my goal here is to try and collate the information and experience I gathered when writing our inventory system.</p>

<h2 id="why-scriptable-objects">Why Scriptable Objects?</h2>
<p>Don’t be alarmed if you haven’t even heard of scriptable objects in Unity until now.  These things are severely underrepresented in the Unity documentation and examples.  Consequently, they seem to have a kind of air around them that suggests they’re complicated and difficult to understand, when the opposite is true.  If you know what <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> is, I’m sure <code class="language-plaintext highlighter-rouge">ScriptableObject</code> will be no stretch.</p>

<p>Classes that inherit from <code class="language-plaintext highlighter-rouge">ScriptableObject</code> are essentially <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> classes with the exception that they don’t need to be attached to game objects.  In the right situation, this means that they can be much more versatile and efficient than their counterparts.  They also offer a powerful abstraction, which is often exactly what you want in complicated systems.</p>

<p>Consider the following class structure:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Item</th>
      <th style="text-align: left">HealthPot : Item</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>string</strong> name</td>
      <td style="text-align: left"><strong>float</strong> healthToAdd</td>
    </tr>
  </tbody>
</table>

<p>We have two basic classes, one specifies a base item and the other specifies a health pot (which inherits from <code class="language-plaintext highlighter-rouge">Item</code>).</p>

<p>In the interest of efficiency, we probably don’t want these to be <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> classes–that’d mean a new prefab for every item!  But what if we eventually wanted to attach an image, game object, or some other Unity construct to the item?  This is the use-case for scriptable objects.</p>

<p>Scriptable objects provide access to all the engine structures and behaviour while maintaining a lightweight base.  Unlike <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> classes, scriptable objects don’t need to be attached to game objects – they don’t even need to be instanced in the scene.  They’re also nicely tied into Unity; you can instance a scriptable object as an asset from an easy right click in your project directory, and that object can also be inspected and changed from the UI – they’re fully understood objects within the Unity ecosystem.  Code-based instantiation does of course still exist, but a real benefit here is being able to take hold of a real “physical” representation of your objects (designers take note!)</p>

<p><img src="/assets/2018_creating_unity_scriptableobject.gif" alt="Creating a scriptable object asset in Unity" width="614px" height="523px" /></p>

<p>I realise that I might be jumping ahead a bit here, but the main point is that scriptable objects allow us to create objects that access Unity structures, without incurring the fee of needing them to be attached to game objects.  Furthermore, they allow a great deal of abstraction, and abstraction is <strong>important</strong> in any large scale project.</p>

<blockquote>
  <p>Some more resources on scriptable objects: <a href="https://www.youtube.com/watch?v=6vmRwLYWNRo">1</a> <a href="https://www.youtube.com/watch?v=raQ3iHhE_Kk">2</a>.  These both highlight a bunch of use-cases (there are many)</p>
</blockquote>

<h2 id="backend-and-frontend">Backend and Frontend</h2>
<p>Many inventory systems tie the frontend (display of items) and backend (saving, loading, positioning) together.  I think its important to have a separation here because it heavily abstracts the behaviour of each component (and we already know abstraction is good).  We can then (more easily) have tightly-wound and optimised operations while maintaining feature rich and satisfying item behaviour.</p>

<h3 id="backend">Backend</h3>
<p>First of all, we need item classes.  This is pretty straightforward; here’s an example of the class structure we’re using in our game:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Item.cs</span>

<span class="p">[</span><span class="n">System</span><span class="p">.</span><span class="n">Serializable</span><span class="p">]</span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Item</span> <span class="p">:</span> <span class="n">ScriptableObject</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">itemName</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">GameObject</span> <span class="n">physicalRepresentation</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Gem.cs</span>

<span class="p">[</span><span class="n">System</span><span class="p">.</span><span class="n">Serializable</span><span class="p">]</span>
<span class="p">[</span><span class="nf">CreateAssetMenu</span><span class="p">(</span><span class="n">menuName</span> <span class="p">=</span> <span class="s">"Items/Gem"</span><span class="p">,</span> <span class="n">fileName</span> <span class="p">=</span> <span class="s">"GemName.asset"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Gem</span> <span class="p">:</span> <span class="n">Item</span> <span class="p">{</span>
    <span class="k">public</span> <span class="k">enum</span> <span class="n">GemType</span> <span class="p">{</span>
        <span class="n">Ruby</span><span class="p">,</span> <span class="n">Diamond</span><span class="p">,</span> <span class="n">Sapphire</span><span class="p">,</span> <span class="n">Emerald</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">GemType</span> <span class="n">gemType</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>All our items look like this.  You’ll notice a couple things related to scriptable objects here.</p>

<p><code class="language-plaintext highlighter-rouge">[System.Serializable]</code> makes our objects easily readable (as data), and is not specific to scriptable objects.  Serialization allows our objects to be viewed from the inspector, and also saved / loaded correctly.  A nice bonus of scriptable objects is that they fully support serialization, and overcome a lot of the <a href="http://theliquidfire.com/2018/04/16/scriptable-objects/">issues</a> you might come across if you’re using regular C# classes.</p>

<p>We inherit from <code class="language-plaintext highlighter-rouge">ScriptableObject</code>, which allows us to use <code class="language-plaintext highlighter-rouge">GameObject</code> in our <code class="language-plaintext highlighter-rouge">physicalRepresentation</code> field.  This field contains an item prefab (our inventory is 3D); if you were making a 2D inventory, this would probably be an <code class="language-plaintext highlighter-rouge">Image</code> or something similar.</p>

<p><img src="/assets/2018_golemancer_prototype_inventory.png" alt="Golemancer prototype inventory" width="827px" height="537px" /></p>

<p>The last oddity is the <code class="language-plaintext highlighter-rouge">[CreateAssetMenu(menuName = "Items/Gem", fileName = "GemName.asset")]</code> line.  All this does is add an entry for our object to the right click menu in our project folder.</p>

<p><img src="/assets/2018_gem_right_click.png" alt="&quot;Gem&quot; added to right click menu" width="663px" height="269px" /></p>

<blockquote>
  <p>There’s a one-to-many relationship between these scriptable objects and their assets.  Remember that they’re really just classes underneath.</p>
</blockquote>

<p>Something that scriptable objects don’t handle all too well (at least in this context) is mutability; the item classes we have here are <em>very much</em> templates.  In our game, most items have a quality associated with them, which differs between items – i.e. two of the same item can have different qualities.  If you want your scriptable objects to have a field unique to that object instance (durability, quality, etc), then you either have to create all your items through code (sacrificing many of the benefits…), or get a little creative;</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Item.cs</span>
<span class="p">...</span>
<span class="c1">// A class that holds a real instance of a ScriptableObject item.</span>
<span class="c1">// Allows us to have copies with mutable data.</span>
<span class="p">[</span><span class="n">System</span><span class="p">.</span><span class="n">Serializable</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ItemInstance</span> <span class="p">{</span>
    <span class="c1">// Reference to scriptable object "template".</span>
    <span class="k">public</span> <span class="n">Item</span> <span class="n">item</span><span class="p">;</span>
    <span class="c1">// Object-specific data.</span>
    <span class="k">public</span> <span class="n">Quality</span><span class="p">.</span><span class="n">QualityGrade</span> <span class="n">quality</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">ItemInstance</span><span class="p">(</span><span class="n">Item</span> <span class="n">item</span><span class="p">,</span> <span class="n">Quality</span><span class="p">.</span><span class="n">QualityGrade</span> <span class="n">quality</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">item</span> <span class="p">=</span> <span class="n">item</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">quality</span> <span class="p">=</span> <span class="n">quality</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We’re storing the item template (and all its specific data), along with an <em>instance-specific</em> field.  A potential issue here is when we have items that do not have a quality: how do we distinguish?  You’ll have to get a bit more creative with your <code class="language-plaintext highlighter-rouge">ItemInstance</code>’s to solve this.</p>

<p>This is the first part of making our inventory painless – designers can create new items and change them using only the UI.  We’re using code to eventually actually create the items, but with some extra scaffolding you can tie even that to the UI.</p>

<hr />

<p>The last piece of the backend is the part that holds reference to all your items.  Things get a bit more tricky here, but I’ll explain as best I can.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Inventory.cs</span>

<span class="p">[</span><span class="nf">CreateAssetMenu</span><span class="p">(</span><span class="n">menuName</span> <span class="p">=</span> <span class="s">"Items/Inventory"</span><span class="p">,</span> <span class="n">fileName</span> <span class="p">=</span> <span class="s">"Inventory.asset"</span><span class="p">)]</span>
<span class="p">[</span><span class="n">System</span><span class="p">.</span><span class="n">Serializable</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Inventory</span> <span class="p">:</span> <span class="n">ScriptableObject</span> <span class="p">{</span>
    <span class="c1">// Saving using unity dev example.</span>
    <span class="c1">// https://bitbucket.org/richardfine/scriptableobjectdemo/src/9a60686609a42fea4d00f5d20ffeb7ae9bc56eb9/Assets/ScriptableObject/GameSession/GameSettings.cs?at=default#GameSettings.cs-16,79,83,87,90</span>
    <span class="k">private</span> <span class="k">static</span> <span class="n">Inventory</span> <span class="n">_instance</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">Inventory</span> <span class="n">Instance</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(!</span><span class="n">_instance</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">Inventory</span><span class="p">[]</span> <span class="n">tmp</span> <span class="p">=</span> <span class="n">Resources</span><span class="p">.</span><span class="n">FindObjectsOfTypeAll</span><span class="p">&lt;</span><span class="n">Inventory</span><span class="p">&gt;();</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">tmp</span><span class="p">.</span><span class="n">Length</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">_instance</span> <span class="p">=</span> <span class="n">tmp</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
                    <span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Found inventory as: "</span> <span class="p">+</span> <span class="n">_instance</span><span class="p">);</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Did not find inventory, loading from file or template."</span><span class="p">);</span>
                    <span class="n">SaveManager</span><span class="p">.</span><span class="nf">LoadOrInitializeInventory</span><span class="p">();</span>
                <span class="p">}</span>
            <span class="p">}</span>

            <span class="k">return</span> <span class="n">_instance</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">InitializeFromDefault</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_instance</span><span class="p">)</span> <span class="nf">DestroyImmediate</span><span class="p">(</span><span class="n">_instance</span><span class="p">);</span>
        <span class="n">_instance</span> <span class="p">=</span> <span class="nf">Instantiate</span><span class="p">((</span><span class="n">Inventory</span><span class="p">)</span> <span class="n">Resources</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="s">"InventoryTemplate"</span><span class="p">));</span>
        <span class="n">_instance</span><span class="p">.</span><span class="n">hideFlags</span> <span class="p">=</span> <span class="n">HideFlags</span><span class="p">.</span><span class="n">HideAndDontSave</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">LoadFromJSON</span><span class="p">(</span><span class="kt">string</span> <span class="n">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_instance</span><span class="p">)</span> <span class="nf">DestroyImmediate</span><span class="p">(</span><span class="n">_instance</span><span class="p">);</span>
        <span class="n">_instance</span> <span class="p">=</span> <span class="n">ScriptableObject</span><span class="p">.</span><span class="n">CreateInstance</span><span class="p">&lt;</span><span class="n">Inventory</span><span class="p">&gt;();</span>
        <span class="n">JsonUtility</span><span class="p">.</span><span class="nf">FromJsonOverwrite</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">File</span><span class="p">.</span><span class="nf">ReadAllText</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">_instance</span><span class="p">);</span>
        <span class="n">_instance</span><span class="p">.</span><span class="n">hideFlags</span> <span class="p">=</span> <span class="n">HideFlags</span><span class="p">.</span><span class="n">HideAndDontSave</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">SaveToJSON</span><span class="p">(</span><span class="kt">string</span> <span class="n">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Debug</span><span class="p">.</span><span class="nf">LogFormat</span><span class="p">(</span><span class="s">"Saving inventory to {0}"</span><span class="p">,</span> <span class="n">path</span><span class="p">);</span>
        <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">File</span><span class="p">.</span><span class="nf">WriteAllText</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">JsonUtility</span><span class="p">.</span><span class="nf">ToJson</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">true</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="cm">/* Inventory START */</span>
    <span class="k">public</span> <span class="n">ItemInstance</span><span class="p">[]</span> <span class="n">inventory</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">SlotEmpty</span><span class="p">(</span><span class="kt">int</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">inventory</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="p">==</span> <span class="k">null</span> <span class="p">||</span> <span class="n">inventory</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">item</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
            <span class="k">return</span> <span class="k">true</span><span class="p">;</span>

        <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Get an item if it exists.</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">GetItem</span><span class="p">(</span><span class="kt">int</span> <span class="n">index</span><span class="p">,</span> <span class="k">out</span> <span class="n">ItemInstance</span> <span class="n">item</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// inventory[index] doesn't return null, so check item instead.</span>
        <span class="k">if</span> <span class="p">(</span><span class="nf">SlotEmpty</span><span class="p">(</span><span class="n">index</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">item</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
            <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">item</span> <span class="p">=</span> <span class="n">inventory</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
        <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Remove an item at an index if one exists at that index.</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">RemoveItem</span><span class="p">(</span><span class="kt">int</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nf">SlotEmpty</span><span class="p">(</span><span class="n">index</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// Nothing existed at the specified slot.</span>
            <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">inventory</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>

        <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Insert an item, return the index where it was inserted.  -1 if error.</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="nf">InsertItem</span><span class="p">(</span><span class="n">ItemInstance</span> <span class="n">item</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">inventory</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="nf">SlotEmpty</span><span class="p">(</span><span class="n">i</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">inventory</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">item</span><span class="p">;</span>
                <span class="k">return</span> <span class="n">i</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">// Couldn't find a free slot.</span>
        <span class="k">return</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Simply save.</span>
    <span class="k">private</span> <span class="k">void</span> <span class="nf">Save</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">SaveManager</span><span class="p">.</span><span class="nf">SaveInventory</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I’ve construed a lot of the save/load and instance behaviour from Richard Fine’s <a href="https://bitbucket.org/richardfine/scriptableobjectdemo/src">scriptable object example</a>.  I don’t pretend to be an expert on this code, but the methods here are quite honest.</p>

<p>First of all, we have a getter whose goal is simply to maintain reference to the active inventory.  This is achieved by searching for all loaded objects of type inventory – i.e. if there is an instance of <code class="language-plaintext highlighter-rouge">Inventory</code> available, it’ll be assigned to <code class="language-plaintext highlighter-rouge">_instance</code> and returned.  For the most part this is just a fancy singleton.  It also makes use of <code class="language-plaintext highlighter-rouge">SaveManager</code> to automatically load the inventory as necessary – more on this later.</p>

<p>Next we have <code class="language-plaintext highlighter-rouge">InitializeFromDefault</code>, which initializes the inventory based on some default version.  This will be the “template” in your project directory – <em>put it in the Resources folder</em>:</p>

<p><img src="/assets/2018_unity_creating_inventory_template.gif" alt="Creating the inventory template" width="736px" height="585px" /></p>

<p>And the methods that follow are of course for reading and writing our object data to disk.  Nothing out of the ordinary here.  If your local inventory is valuable (should not be tampered with) you probably want to incorporate some encryption at this point.<br /><code class="language-plaintext highlighter-rouge">HideAndDontSave</code> tells Unity not to show the object in the hierarchy, and not to save it to the scene.</p>

<p>What comes next shouldn’t be too foreign.  These are all standard helper methods for managing an array.  After any operation that modifies the inventory, we call <code class="language-plaintext highlighter-rouge">Save()</code>, which will be discussed shortly.  We can afford to save every update in our game because it doesn’t happen very frequently and the inventory isn’t very large; if you have a particularly large inventory or update frequently, you might want to configure something less granular.<br />  We’re also not checking for out of bounds exceptions here, which may be something useful to include.</p>

<h3 id="saving">Saving</h3>
<p>What good is an inventory that can’t be saved (and loaded)?  We’ve got some methods in place to help us, but it would be more convenient if we had something to manage the whole operation:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SaveManager.cs</span>

<span class="k">using</span> <span class="nn">System.IO</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">SaveManager</span> <span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">LoadOrInitializeInventory</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Saving and loading.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">File</span><span class="p">.</span><span class="nf">Exists</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="n">persistentDataPath</span><span class="p">,</span> <span class="s">"inventory.json"</span><span class="p">)))</span> <span class="p">{</span>
            <span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Found file inventory.json, loading inventory."</span><span class="p">);</span>
            <span class="n">Inventory</span><span class="p">.</span><span class="nf">LoadFromJSON</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span>
                <span class="n">Application</span><span class="p">.</span><span class="n">persistentDataPath</span><span class="p">,</span> <span class="s">"inventory.json"</span><span class="p">));</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Couldn't find inventory.json, loading from template."</span><span class="p">);</span>
            <span class="n">Inventory</span><span class="p">.</span><span class="nf">InitializeFromDefault</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">SaveInventory</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">Inventory</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">SaveToJSON</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span>
            <span class="n">Application</span><span class="p">.</span><span class="n">persistentDataPath</span><span class="p">,</span> <span class="s">"inventory.json"</span><span class="p">));</span>
    <span class="p">}</span>


    <span class="c1">// Load from the default, for situations where we just want to reset.</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">LoadFromTemplate</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">Inventory</span><span class="p">.</span><span class="nf">InitializeFromDefault</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">LoadOrInitializeInventory()</code> does what you might expect: loads the inventory if it exists, or creates a new one if it doesn’t.  Hopefully the rest of these methods are self explanatory.</p>

<blockquote>
  <p>The default persistent data path is <code class="language-plaintext highlighter-rouge">/Users/&lt;user&gt;/AppData/LocalLow/&lt;organization&gt;/&lt;game&gt;/</code> on Windows.  For other devices, see the <a href="https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html">documentation</a> (which isn’t very helpful).</p>
</blockquote>

<blockquote>
  <p>A sample save file might look like the following:</p>
  <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"inventory"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"item"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"instanceID"</span><span class="p">:</span><span class="w"> </span><span class="mi">5416</span><span class="w">
                </span><span class="err">//</span><span class="w"> </span><span class="err">Or</span><span class="w"> </span><span class="err">in</span><span class="w"> </span><span class="err">builds...</span><span class="w">
                </span><span class="err">//</span><span class="w"> </span><span class="nl">"m_FileID"</span><span class="p">:</span><span class="w"> </span><span class="mi">2334</span><span class="p">,</span><span class="w">
                </span><span class="err">//</span><span class="w"> </span><span class="nl">"m_PathID"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"quality"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"item"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"instanceID"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"quality"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"item"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"instanceID"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"quality"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>  </div>
  <p><code class="language-plaintext highlighter-rouge">item</code> (scriptable object reference) fields change between editor restarts and builds, so your save file won’t work between editor sessions, and wont transfer from editor to build.  Item references <em>should</em> survive between builds, provided that you don’t change or remove objects used in the save.</p>
</blockquote>

<blockquote>
  <p><strong>NB:</strong> If you want to guarantee that saves persist between builds, you can store scriptable object names (or IDs) rather than references, and then make use of <code class="language-plaintext highlighter-rouge">Resources.Load()</code> to load them.  I’ll probably cover this in a later blog post–this one is long enough as it is.
<em>Edit: this post is now available <a href="http://toqoz.fyi/unity-object-database.html">here</a>.</em></p>
</blockquote>

<p>We now have a workable inventory backend.  To recap, our backend involves:</p>
<ul>
  <li><strong>Item classes (as scriptable objects).</strong><br />
    <em>Each type of item is represented as an asset in our project.</em></li>
  <li><strong>Inventory (as scriptable object).</strong><br />
    <em>The base inventory (probably empty) is represented as an asset in our project.</em></li>
  <li><strong>Save manager.</strong><br />
    <em>Simple helper to manage saving and loading in a convenient way.</em></li>
</ul>

<h3 id="frontend">Frontend</h3>
<p>If we’ve succeeded in our goal (making the inventory painless to use), then developing the frontend should be quite simple.</p>

<p>For this example, you should know that I’ve set up a few empty game objects to act as “slots”, which have roughly the following script attached:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Slot</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">ItemInstance</span> <span class="n">itemInstance</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>    <span class="c1">// Inventory backend representation.</span>
    <span class="k">public</span> <span class="n">GameObject</span> <span class="n">prefabInstance</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>    <span class="c1">// Inventory frontend representation.</span>

    <span class="c1">// TODO: it would be better if we used SetActive() etc rather than Instantiate/Destroy.</span>
    <span class="c1">// Use this method to set a slot's item.</span>
    <span class="c1">// The slot will automatically instantiate the gameobject associated with the item.</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">SetItem</span><span class="p">(</span><span class="n">ItemInstance</span> <span class="n">instance</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">itemInstance</span> <span class="p">=</span> <span class="n">instance</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">prefabInstance</span> <span class="p">=</span> <span class="nf">Instantiate</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="n">item</span><span class="p">.</span><span class="n">physicalRepresentation</span><span class="p">,</span> <span class="n">transform</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Remove the item from the slot, and destroy the associated gameobject.</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">RemoveItem</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">itemInstance</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
        <span class="nf">Destroy</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">prefabInstance</span><span class="p">);</span>
        <span class="k">this</span><span class="p">.</span><span class="n">prefabInstance</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And these slots are parented to another game object which acts as our main frontend:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PhysicalInventory</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Slot</span><span class="p">&gt;</span> <span class="n">inventorySlots</span><span class="p">;</span>

    <span class="c1">// Use this for initialization</span>
    <span class="k">void</span> <span class="nf">Start</span> <span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Load example.</span>
        <span class="n">inventorySlots</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Slot</span><span class="p">&gt;();</span>
        <span class="n">inventorySlots</span><span class="p">.</span><span class="nf">AddRange</span><span class="p">(</span><span class="n">GameObject</span><span class="p">.</span><span class="n">FindObjectsOfType</span><span class="p">&lt;</span><span class="n">Slot</span><span class="p">&gt;());</span>

        <span class="c1">// Maintain some order (just in case it gets screwed up).</span>
        <span class="n">inventorySlots</span><span class="p">.</span><span class="nf">Sort</span><span class="p">((</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">a</span><span class="p">.</span><span class="n">index</span> <span class="p">-</span> <span class="n">b</span><span class="p">.</span><span class="n">index</span><span class="p">);</span>

        <span class="nf">PopulateInitial</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">PopulateInitial</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">inventorySlots</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="n">ItemInstance</span> <span class="n">instance</span><span class="p">;</span>
            <span class="c1">// If an object exists at the specified location.</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">Inventory</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">GetItem</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="k">out</span> <span class="n">instance</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">inventorySlots</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="nf">SetItem</span><span class="p">(</span><span class="n">instance</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Clear</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">inventorySlots</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span>
            <span class="n">inventorySlots</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="nf">RemoveItem</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Pretty straightforward!  The inventory can be accessed from <code class="language-plaintext highlighter-rouge">Inventory.Instance</code>, no matter where we are, and will handle all its initialization on its own (saving, loading, initializing).  Very convenient.</p>

<p>The real frontend has a fair few additional methods, which you can take a look at in the git repo for this post: https://github.com/Toqozz/blog-code/tree/master/inventory.</p>

<hr />

<h2 id="actually-using-it">Actually Using It</h2>
<p>The idea is that you create a new <code class="language-plaintext highlighter-rouge">ItemInstance</code> through code when inserting an item into the inventory (or maybe when you spawn the object, or whatever works best).</p>

<p>One way you could do this is by creating a new <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> script which you’d attach to the <code class="language-plaintext highlighter-rouge">GameObject</code>, which would allow you to store the <code class="language-plaintext highlighter-rouge">ScriptableObject</code> representation;</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PhysicalItem</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">Item</span> <span class="n">scriptableObjectRepresentation</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then, when your player did some action and you want to place it in the inventory, you might do something like this:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PressEToInsert</span> <span class="p">:</span> <span class="n">MonoBehaviour</span> <span class="p">{</span>
    <span class="k">public</span> <span class="n">GameObject</span> <span class="n">player</span><span class="p">;</span>

    <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// If E is down, and player is close enough to object.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">Input</span><span class="p">.</span><span class="nf">GetKey</span><span class="p">(</span><span class="n">KeyCode</span><span class="p">.</span><span class="n">E</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="n">Vector3</span><span class="p">.</span><span class="nf">Distance</span><span class="p">(</span><span class="n">player</span><span class="p">.</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">,</span> <span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">)</span> <span class="p">&lt;</span> <span class="m">2f</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">Inventory</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">InsertItem</span><span class="p">(</span><span class="k">new</span> <span class="nf">ItemInstance</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="n">GetComponent</span><span class="p">&lt;</span><span class="n">PhysicalItem</span><span class="p">&gt;().</span><span class="n">scriptableObjectRepresentation</span><span class="p">,</span>
                                                           <span class="n">quantity</span><span class="p">:</span> <span class="m">1</span><span class="p">,</span>
                                                           <span class="n">quality</span><span class="p">:</span> <span class="n">Quality</span><span class="p">.</span><span class="n">QualityGrade</span><span class="p">.</span><span class="n">Mystic</span><span class="p">,</span>
                                                           <span class="n">isNew</span><span class="p">:</span> <span class="k">true</span><span class="p">));</span>
            <span class="c1">// Remove item from the world.</span>
            <span class="nf">Destroy</span><span class="p">(</span><span class="n">gameObject</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Note that both of the above scripts are attached to the object in this example, but it might make more sense to put the “Press E” logic on the player – it’s just easier to provide a snippet like this.</p>

<p>Here’s an example (red cube – ruby item, white cube – player):
<img src="/assets/2018_inventory_pickup_demo.gif" alt="Pickup demonstration" width="1019px" height="618px" /></p>

<p>And we can verify that the item was in fact inserted into the inventory by inspecting <code class="language-plaintext highlighter-rouge">Inventory.Instance</code> in a debugger (there should be one built into your IDE, I’m using <a href="https://www.jetbrains.com/rider/">Rider</a>):</p>

<p><img src="/assets/2018_inventory_debugging.gif" alt="Verifying item is inserted via debugger" width="1040px" height="676px" /></p>

<p>Alternatively, you can do some poor mans debugging and scatter some <code class="language-plaintext highlighter-rouge">Debug.Log</code>s around to achieve roughly the same thing with less/more effort.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Something that might surprise you is that there is actually a lack of content on making easy to use inventory systems in Unity.]]></summary></entry></feed>