{"id":54008,"date":"2022-03-10T06:00:24","date_gmt":"2022-03-10T11:00:24","guid":{"rendered":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=54008"},"modified":"2022-03-09T15:17:01","modified_gmt":"2022-03-09T20:17:01","slug":"lets-make-a-spaceship-part-3-back-up","status":"publish","type":"post","link":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=54008","title":{"rendered":"Let&#8217;s Make a Spaceship Part 3: Back Up"},"content":{"rendered":"<p>I sort of skipped a bunch of steps at the end of the last entry, because I was in a hurry to get to the point where we could see ships shooting at each other. But now that we&#8217;ve made it, let&#8217;s back up and talk about how we got here. Specifically, I&#8217;d like to respond to <a href=\"?p=53914#comment-1318372\">this comment<\/a>&#8230;<\/p>\n<blockquote><p>I really enjoy this kind of content \u2013 programming, explaining complex IDE type objects (I\u2019ll probably never try Unity \u2013 I\u2019m a back end natural language processing type guy \u2013 but it is fun to see someone else struggle with it). Looking at the GIF I\u2019m sad there wasn\u2019t a paragraph or two about how the lasers were added: was that hard? Does Unity have an \u201cAdd laser\u201d button? I feel like it might. I also think that each ship must track some sort of hit points value, because I saw one of them get hit (oh to have a discussion about hit boxes, and how to determine if a laser is inside one or not \u2013 I remember two decades ago trying to code up hit detection code and there are lots of optimizations in that area) and then flame out and explode. Lots of complexity hiding behind that I suspect!<\/p><\/blockquote>\n<p>Unity doesn&#8217;t have an &#8220;add laser&#8221; button, but we can re-use the trick from <a href=\"?p=53914\">last time<\/a> to save ourselves a bit of work.\u00a0<br \/>\n<!--more--><br \/>\n<div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/modship3-2.jpg' width=100% alt='Looking back, I think the lasers would look better if they weren&apos;t the same thickness as the fighter trails.' title='Looking back, I think the lasers would look better if they weren&apos;t the same thickness as the fighter trails.'\/><\/div><div class='mouseover-alt'>Looking back, I think the lasers would look better if they weren&apos;t the same thickness as the fighter trails.<\/div><\/p>\n<p>In Unity, you create game objects, place them in the scene, and move them around to make gameplay happen. Usually a game object will have some geometry attached to it. Like, I could jump over to Blender and whip up a laser bolt model to use. But instead I create a laser object with no geometry and attach a TrailRenderer to it.\u00a0<\/p>\n<p>A TrailRenderer is what we&#8217;re using to give the fighter ships glowing exhaust trails. I just need to set the length to a smaller value and it works as a Star Wars style laser bolt.<\/p>\n<h3>Hit Detection<\/h3>\n<p>I don&#8217;t actually need proper hit detection for these space battles. Since none of these things are player-controlled, I don&#8217;t need to play fair. Rather than tracking laser trajectories and testing for collisions, I just roll dice in the background.\u00a0<\/p>\n<p>When the laser is fired,\u00a0 I roll to see if it is destined to hit. If it&#8217;s a miss, then I have the laser fly past the target. If it&#8217;s a hit, then the laser travels directly at the target. Properly leading a target takes extra calculations, so I don&#8217;t bother. Instead, the laser just flies itself into the target like a homing missile. If it moved slowly, then you&#8217;d be able to see the curve. But since the lasers are moving so fast, this brazen cheating is undetectable.\u00a0<\/p>\n<p>However, I do still need collision detection!<\/p>\n<p>See, my camera system is very primitive. I click on an object to begin following it, and then I use the controls to zoom \/ orbit the object. When you click on the window, Unity casts a ray from the camera origin to the mouse location. If I want to be able to select an object, then I need to give it some sort of ability to be hit.<\/p>\n<p>In Unity, an object needs to have a collider if you want to detect interactions with other objects. Things like objects running into each other, objects running into the environment, objects being hit with rays, etc. If you don&#8217;t give an object a collider, then it will ghost through everything else in the scene and will never be available as a target for bullets, mouse-clicks, and the like.<\/p>\n<p>There are a few different types of colliders you can use. As the developer, you need to balance speed with accuracy. You can have flawless per-pixel hit detection on every strand of your protagonist&#8217;s hair if you don&#8217;t care about framerate, or you can have blazing fast and wildly inaccurate collision checks that treat your protagonist like a big cube.\u00a0<\/p>\n<p><b>Sphere<\/b>\u00a0<\/p>\n<p>This is the simplest collider of them all. You give the sphere a radius and Unity will treat the object like a ball.<\/p>\n<p><b>Cube<\/b>\u00a0<\/p>\n<p>A bit more accurate than the sphere in many cases, but very slightly more expensive.<\/p>\n<p><b>Simplified Model<\/b>\u00a0<\/p>\n<p>This is the most common technique. You have one model for the visuals, and another 3D model that&#8217;s the same thing with simpler geometry. A great example of this is in Team Fortress 2, where characters are treated as though their limbs are made of boxes for the purposes of hit detection.<\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/modship3-1.jpg' width=100% alt='' title=''\/><\/div><div class='mouseover-alt'><\/div><\/p>\n<p>This provides a pretty nice tradeoff between speed and accuracy. However, it&#8217;s not perfect. In the above image, you can see that everyone effectively has a box over their heads. If you hit anywhere on the box, then it counts as a headshot, even if the bullet would technically miss the head inside.\u00a0<\/p>\n<p>If this is still too sloppy for you, then there&#8217;s only one option left&#8230;<\/p>\n<p><b>Full Model<\/b><\/p>\n<p>If you really need absolute perfection, then just use the same geometry for the visuals and collisions. This might be acceptable if the base model is fairly simple, or if you only have a small number of these things in the scene, or if you&#8217;re not going to do a lot of collision checks.<\/p>\n<p>This will quickly turn into a disaster if your game features lots of busy models and lots of collision checks. For example, imagine a battle royale shooter between anime-style characters where the figures have lots of elaborate hair, hanging belts, and fluttering capes. Give everyone a machine gun and watch your framerate drop to single digits.<\/p>\n<p>Sure, the game will be unplayable, but it will finally put to rest arguments over hitboxes.<span class='snote' title='1'>This is a lie. People will argue about hitboxes no matter what you do.<\/span><\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/modship3-3.jpg' width=100% alt='The circle thing is the sphere collider. Clicking in the circle counts as clicking on the ship.' title='The circle thing is the sphere collider. Clicking in the circle counts as clicking on the ship.'\/><\/div><div class='mouseover-alt'>The circle thing is the sphere collider. Clicking in the circle counts as clicking on the ship.<\/div><\/p>\n<p>For the purposes of this project, I just need to put a sphere collider over the fighters so they can be clicked on. And since the ships are usually pretty tiny and clicking on fast-moving things is hard, I make the sphere about twice the size of the ship so that clicking on them is easier.<\/p>\n<h3>Hitpoints<\/h3>\n<p>Initially I started out with a simple system where shots always hit. Each fighter had 4 hitpoints, so ships exploded after 4 hits. However, this made fights incredibly mechanical. Everyone fired at the same rate, and so the resulting dogfight was very rhythmic. Four shots, then an explosion.<\/p>\n<p>pew-pew-pew-pew-BOOM!<\/p>\n<p>pew-pew-pew-pew-BOOM!<\/p>\n<p>pew-pew-pew-pew-BOOM!<\/p>\n<p>And so on. I tried adding a random to-hit chance, but the underlying rhythm of the lasers was still there. I made sure everyone&#8217;s lasers were out of phase with each other. That scattered the shots, but the ships still blew up very regularly. It just didn&#8217;t feel like the chaos of a real dogfight.<\/p>\n<p>So what I did was assign each ship a random armor class of 18 or less. Ships roll 3d6 to shoot, and they have to roll higher than the AC of the ship they&#8217;re attacking in order to hit. This finally broke up the action and made things feel organic.\u00a0<\/p>\n<p>Small mistake: The highest AC is 18, and you have to roll higher than the AC to score a hit. So once in a while I&#8217;d get a fight that had an AC 18 fighter in it, and that fighter would be an untouchable god. Very high AC values in general were bad, because it made sure the last two fighters would take forever to finish each other off. Capping AC to ~15 or so gave the fight an organic feel without turning the ending into a slog.<\/p>\n<p>There&#8217;s one final problem I want to talk about&#8230;<\/p>\n<h3>Iteration Friction<\/h3>\n<p>When you&#8217;re making some form of content, you often have to deal with iteration friction. Maybe you&#8217;re doing a mural and you have to wait for a layer of paint to dry before you can appraise the work you just did. Maybe you&#8217;re making a videogame level and you need to launch the game \/ load the level to see your changes. Maybe you&#8217;re editing a video and your editing software lags and stutters on the 4K footage, so you have to render a section of the video to see if the audio is in sync and the cuts work. Maybe you&#8217;re writing a book and you need to get feedback from your editor to know if your revisions are any good.<\/p>\n<p>Friction sucks, and it&#8217;s a major reason why giving accurate time estimates on a project is such a nightmare. Five or ten seconds might sound small, but those lost moments add up over the long haul. These intervals are particularly bad for concentration. The chunks of time are large enough to break your concentration, but they&#8217;re not big enough that you could use them to accomplish something else.\u00a0<\/p>\n<p>Moments of friction can lead to you getting restless, picking up your phone, and suddenly this 15 second wait turns into a 15 minute distraction on social media.\u00a0<\/p>\n<p>Friction tends to increase as a project matures. When you&#8217;ve just started a new level, you can rebuild the lights and shadows instantly. Once you&#8217;ve put a couple of days into it, that same task might take over a minute. The more complicated the level gets, the longer it takes to recalculate the lights and the longer it takes to load it in-game.\u00a0<\/p>\n<p>I imagine this is the reason so many games have your character crawl through a narrow gap that breaks line-of-sight.<span class='snote' title='2'><a href=\"?p=50501\">Jedi Fallen Order<\/a> is the most recent example of this that I&#8217;m familiar with.<\/span> The level can be split into two smaller halves, and then those parts can be stapled together at the end. This saves the level designers from needing to work on the entire area as a single project.<\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/modship2-animated.gif' width=100% alt='I can&apos;t capture a non-responsive window in a screenshot, so here&apos;s another look at the animated .gif from last week.' title='I can&apos;t capture a non-responsive window in a screenshot, so here&apos;s another look at the animated .gif from last week.'\/><\/div><div class='mouseover-alt'>I can&apos;t capture a non-responsive window in a screenshot, so here&apos;s another look at the animated .gif from last week.<\/div><\/p>\n<p>Iteration friction is a thing in coding, too. Your typical compiler can compile a single source file in a fraction of a second. But the project grows over time. One source file becomes a dozen, and then two dozen, and then fifty, and then a hundred. Pretty soon it takes fifteen seconds to compile small changes. If it takes you an hour to chase down a fiddly crash bug, you&#8217;ll probably spend ten minutes reading and writing code, and the rest of the hour will be lost to compile times and loading screens.<\/p>\n<p>Unity is actually pretty good about friction under normal conditions. Your project is going to be written in C#, which is a high-ish level language<span class='snote' title='3'>Reminder that high\u00a0 \/ low languages are backwards from expectations. High level languages are easy to read. Low-level languages are terse and inscrutable.<\/span> with a bunch of fancy tools wrapped around it. This sort of thing is prone to debilitating levels of friction. But I would say that Unity fares pretty well for what it offers. It&#8217;s going to be higher friction than a project written in vanilla C, but not by much.<\/p>\n<p>However, there&#8217;s something wrong with Unity&#8217;s HDRP. I don&#8217;t know why, but this project has a baseline 10 seconds of friction, right out of the gate. If I change ANYTHING &#8211; a source file, a texture map, a model &#8211; then when I jump back to Unity it will stall for ten seconds before I can do anything. That&#8217;s really sucking the joy out of something that&#8217;s supposed to be a no-stakes, low-effort project.<\/p>\n<p>On a whim, I make a duplicate project using Unity&#8217;s default render path. With the same code and the same assets, this new project has about one second of friction instead of ten.<\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/modship3-4.jpg' width=100% alt='Here is the ship as it looks with the standard render path. I miss the bloom lighting, but I don&apos;t miss the 10 seconds of interface lag every time I alt-Tab.' title='Here is the ship as it looks with the standard render path. I miss the bloom lighting, but I don&apos;t miss the 10 seconds of interface lag every time I alt-Tab.'\/><\/div><div class='mouseover-alt'>Here is the ship as it looks with the standard render path. I miss the bloom lighting, but I don&apos;t miss the 10 seconds of interface lag every time I alt-Tab.<\/div><\/p>\n<p>That doesn&#8217;t make a lot of sense. Yes, I&#8217;m sure the HDRP is more complex under the hood. But why does it take so long to integrate simple changes? Even if all I do is rename a single variable, I still have to wait ten seconds instead of one. What&#8217;s it doing in those nine extra seconds? Recompiling every shader for every possible render path for no reason? Is this a bug?<\/p>\n<p>Anyway, I think I&#8217;m done fiddling with this project for now. I have a list of additional things I want to try,<span class='snote' title='4'>I still want to take another swing at making the spaceship again.<\/span> but I&#8217;m currently posting 5 days a week and I can&#8217;t scrape together enough hours for programming with that workload. I hope to get some lead time on my other writing and then pick this up again later.\u00a0<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I sort of skipped a bunch of steps at the end of the last entry, because I was in a hurry to get to the point where we could see ships shooting at each other. But now that we&#8217;ve made it, let&#8217;s back up and talk about how we got here. Specifically, I&#8217;d like to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-54008","post","type-post","status-publish","format-standard","hentry","category-projects"],"_links":{"self":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/54008","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=54008"}],"version-history":[{"count":8,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/54008\/revisions"}],"predecessor-version":[{"id":54018,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/54008\/revisions\/54018"}],"wp:attachment":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=54008"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=54008"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=54008"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}