{"id":178,"date":"2020-11-06T13:00:00","date_gmt":"2020-11-06T18:00:00","guid":{"rendered":"http:\/\/mattrobertson.ca\/blog\/?p=178"},"modified":"2020-11-10T20:46:50","modified_gmt":"2020-11-11T01:46:50","slug":"round-a-bout-post-mortem","status":"publish","type":"post","link":"https:\/\/mattrobertson.ca\/blog\/2020\/11\/06\/round-a-bout-post-mortem\/","title":{"rendered":"Round-a-bout Post-mortem"},"content":{"rendered":"\n<p>Now that another Ludum Dare come and gone, ratings and all, this seems like as good a time as any to do a quick post-mortem of my team&#8217;s game <a href=\"https:\/\/trmrddr.itch.io\/roundabout\" data-type=\"URL\" data-id=\"https:\/\/trmrddr.itch.io\/roundabout\">Round-a-Bout<\/a>. Our team&#8217;s third Ludum Dare game, Round-a-Bout is a little game about trying to make your way home, on a roundabout that just won&#8217;t end. Refusing to ever make a game similar to a past one, this time I had to learn how to drive. Now, let&#8217;s talk talk a little bit about what we tried, what worked, what didn&#8217;t, and what got cut.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/img.itch.zone\/aW1nLzQzNTM4MTIucG5n\/original\/wFgqj1.png\" alt=\"\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">&#8220;This time it&#8217;ll be easy&#8221;<\/h2>\n\n\n\n<p>Going into this Ludum Dare, all of my usual team (Girafalope, Trmrddr, Progs, and myself) wanted to have a pretty chill jam: do things we were confident we could do, take decent breaks, and just generally have fun. To my own disadvantage, I had just acquired a new guitar that I wanted to get the chance to use over the weekend. Surprising no one, this plan didn&#8217;t last.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How the hell do you make &#8220;Stuck in a Loop&#8221; fun?<\/h2>\n\n\n\n<figure class=\"wp-block-gallery columns-2 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\"><ul class=\"blocks-gallery-grid\"><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"841\" height=\"639\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas.png\" alt=\"\" data-id=\"195\" data-full-url=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas.png\" data-link=\"https:\/\/mattrobertson.ca\/blog\/?attachment_id=195\" class=\"wp-image-195\" srcset=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas.png 841w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas-300x228.png 300w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas-768x584.png 768w\" sizes=\"auto, (max-width: 841px) 100vw, 841px\" \/><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"821\" height=\"584\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas2.png\" alt=\"\" data-id=\"196\" data-full-url=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas2.png\" data-link=\"https:\/\/mattrobertson.ca\/blog\/?attachment_id=196\" class=\"wp-image-196\" srcset=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas2.png 821w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas2-300x213.png 300w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/10\/Ideas2-768x546.png 768w\" sizes=\"auto, (max-width: 821px) 100vw, 821px\" \/><\/figure><\/li><\/ul><figcaption class=\"blocks-gallery-caption\">Believe you me, these weren&#8217;t the wildest ideas we brainstormed<\/figcaption><\/figure>\n\n\n\n<p>The theme dropped, and we all went into a bit of a spiral of total writers block. Since our time on <a href=\"https:\/\/trmrddr.itch.io\/frogbert\" data-type=\"URL\" data-id=\"https:\/\/trmrddr.itch.io\/frogbert\">Frogbert<\/a>, we&#8217;ve moved to a more structured brainstorming methodology for picking our game ideas: for ~10 minutes, we spew whatever came to mind onto post-its, then we group things up, and then we vote to see what wins and run with it. For <a href=\"https:\/\/trmrddr.itch.io\/the-long-night\" data-type=\"URL\" data-id=\"https:\/\/trmrddr.itch.io\/the-long-night\">The Long Night<\/a> and <a href=\"https:\/\/trmrddr.itch.io\/life-is-hard-buy-a-cactus\" data-type=\"URL\" data-id=\"https:\/\/trmrddr.itch.io\/life-is-hard-buy-a-cactus\">Life Is Hard<\/a>, this process of discussion and brainstorming took maybe an hour. This time around, it took us over two.<\/p>\n\n\n\n<p>I was a little worried that, like &#8220;Out of Control&#8221; in the <a href=\"https:\/\/itch.io\/jam\/gmtk-2020\" data-type=\"URL\" data-id=\"https:\/\/itch.io\/jam\/gmtk-2020\">GMTKJam <\/a>previously, the theme would be a bit of a trap. It&#8217;s super easy to make a game about being stuck in a loop, but it felt much harder to make one that doesn&#8217;t just make the player quit out of frustration at the repetitiveness. After a lot of discussion, the idea we launched off of was this:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>&#8220;You&#8217;re trying to commute home after a terrible day of work, but every time you think you&#8217;ve found your exit, you discover you&#8217;ve found the wrong one, and the interchange gets more complicated.&#8221;<\/p><\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Driving Scares Me<\/h2>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/0654143fc3e74e69188c3ab1a713eae0.gif\" alt=\"\" class=\"wp-image-201\" width=\"532\" height=\"294\"\/><\/figure><\/div>\n\n\n\n<p>Having never made a driving-based game before, the car controls were absolutely the part I was most concerned about getting to feel correct, and I easily could have spent the entire jam working only on this.<\/p>\n\n\n\n<p>I spent the first few hours of the jam desperately trying to avoid doing a complex car simulation, hoping I could get something &#8220;simple&#8221; going by just shoving a cube rigidbody around with the right forces\/parameters. I hoped I&#8217;d be able to get away with not having to model wheels, a transmission, etc&#8230; and still get &#8220;sick drifts.&#8221; I didn&#8217;t make it happen.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/b955217dbc682b009b650427fe3fdc38-1.gif\" alt=\"\" class=\"wp-image-204\" width=\"447\" height=\"344\"\/><figcaption>This is a different problem, but it&#8217;s so funny I had to include it<\/figcaption><\/figure><\/div>\n\n\n\n<p>After a solid night&#8217;s sleep, I caved, and looked into how I could do a proper car simulation. I did a small bit of research and learned how to use the Unity&#8217;s built-in WheelControllers, which ended up being way more simple and easy to use than I had expected. Immediately noticing that the car flipped extremely easily, I lowered car&#8217;s center of gravity as low as I could possibly put it, and largely fixed the problem. Then I spent another two hours playing around with thrust forces, suspension strengths, friction coefficients, and everything else until the car felt more or less alright.<\/p>\n\n\n\n<p>Originally, we had wanted to lock the car into permanent acceleration to really make things tough. As it happens, that made it far too easy to get stuck up against a wall, so we needed some way to back up. I had hoped to work in a handbrake for sick drifts, as well as a little reverse. Unfortunately, I definitely didn&#8217;t get a handbrake going, and our dinky little reverse (half the forward acceleration force) had to pull double duty as a brake. <\/p>\n\n\n\n<p>Do I think the car controls well? No, not really, but it had perfectly serviceable driving mechanics, and we were able to quickly move on to the next part of the game: generating a map.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"815\" height=\"52\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Brakes.png\" alt=\"\" class=\"wp-image-202\" srcset=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Brakes.png 815w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Brakes-300x19.png 300w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Brakes-768x49.png 768w\" sizes=\"auto, (max-width: 815px) 100vw, 815px\" \/><figcaption>Even if the brakes kinda sucked<\/figcaption><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">World Generation&#8217;s easier than I thought!<\/h2>\n\n\n\n<p>A significant part of our idea required having the map expand off of the exit you had just found, something we were all a little worried about implementing. The original plan was to have a variety of interchange types available, and tack them on randomly somewhat haphazardly, which was shaping up to be a bit of a complex mess.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/media.discordapp.net\/attachments\/761630167352999957\/761942161834901545\/BeegRound.PNG?width=881&amp;height=408\" alt=\"\"\/><figcaption>Roughly the system we ended up using<\/figcaption><\/figure><\/div>\n\n\n\n<p>To simplify for the jam, we ended up settling on only a single type of interchange: a four-exit roundabout, which we could align in a simple grid layout. This made things pretty simple for me: every piece of map took up the same space and had a potential exit on each side, making it exceedingly easy to just create a new GameObject a static distance away from the triggered exit.<\/p>\n\n\n\n<p>Handling exit generation was a little bit more tricky. I maintain a list of every available exit in the world, add a new roundabout&#8217;s exits to the list on generation, and randomly select from the list to pick the &#8220;correct&#8221; exit. The trick came in with removal. My first thought was to remove the exit you activated to expand the map, but this ran into issues when placing a roundabout that connects to multiple pre-existing ones.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"556\" height=\"217\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/TestExit.png\" alt=\"\" class=\"wp-image-207\" srcset=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/TestExit.png 556w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/TestExit-300x117.png 300w\" sizes=\"auto, (max-width: 556px) 100vw, 556px\" \/><figcaption>This says a lot about my development flow, honestly.<\/figcaption><\/figure><\/div>\n\n\n\n<p>The Solution I ended up implementing was to, after tossing the new exits onto the list, scan the list for pairs of exits which were too close together (about 7 units away, in our case) and delete them. Thanks to the size of our roundabouts, this allowed us to catch the doubled-up exits formed by linking roundabouts without accidentally removing anything else. Not the fastest method probably, but it was simple and worked.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>List&lt;ExitController> toRemove = new List&lt;ExitController>();\nfor(int i = 0; i &lt; newRA.exits.Count; i++) {\n    for(int j = 0; j &lt; potentialExits.Count; j++){\n  \tfloat dist = Vector3.Distance(\n  \t\tnewRA.exits&#91;i].gameObject.transform.position,\n  \t\tpotentialExits&#91;j].gameObject.transform.position);\n  \tif (dist &lt;= 10.0f){\n  \t\ttoRemove.Add(newRA.exits&#91;i]);\n  \t\ttoRemove.Add(potentialExits&#91;j]);\n  \t}\n    }\n }\nforeach (ExitController exit in toRemove){\n  Destroy(exit.gameObject);\n  potentialExits.Remove(exit);\n  newRA.exits.Remove(exit);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">AI&#8217;s hard too<\/h2>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/5a59312a27172e83ea0873298f14256c.gif\" alt=\"\" class=\"wp-image-199\" width=\"473\" height=\"319\"\/><figcaption>&#8220;Spawning is a work in progress&#8221;<\/figcaption><\/figure><\/div>\n\n\n\n<p>Alright, so. I walked into this gamedev cycle wanting the obstacle cars to function like actual traffic. I wanted them to actually drive, change lanes, avoid hitting each other, and even change between roundabouts. As I discovered after an hour or so of trying: that&#8217;s incredibly difficult to do, especially in two days.<\/p>\n\n\n\n<p>So I stopped trying to over-engineer, and didn&#8217;t do it. Instead, taking advantage of the fact that all the interchanges are roundabouts, I parented all the cars to the roundabout they&#8217;re in and just rotate them around at different speeds based on distance from the circle. One notable extra bit I had to add to sell the illusion was to re-orient the cars after the rotation to make sure they&#8217;re still facing forward relative to their movement.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>float commonAngleMath = Time.deltaTime;\nif (!rotateCW) commonAngleMath *= -1.0f;\nforeach( GameObject car in AICars){\n  AICarController carCont = car.GetComponent&lt;AICarController>();\n  if (!carCont.alive) continue;\n  float dist = (12.5f - Vector3.Distance(car.transform.position, transform.position))\/12.5f + 0.5f;\n  car.transform.RotateAround(transform.position, Vector3.up, commonAngleMath * dist * carSpeed);\n\n  Vector3 newForward = Vector3.Cross(\n  car.transform.position - transform.position,\n  transform.up);\n  if (rotateCW) newForward *= -1.0f;\n  car.transform.forward = newForward;\n}<\/code><\/pre>\n\n\n\n<p>Surprising only myself, no one felt the game was worse for not having actual traffic AI. Heck, for all intents and purposes, it does.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Sound? Honestly kinda fun<\/h2>\n\n\n\n<figure class=\"wp-block-audio\"><audio controls src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Ludum_47_-_1.wav\"><\/audio><figcaption>A jaunty little piece of background music<\/figcaption><\/figure>\n\n\n\n<p>Disaster struck Saturday and Progs, our audio person, had to drop out of the jam after only having time to put together a short audio loop we could use for our gameplay. It proved an opportunity to finally try my hand at making some videogame sound effects. We were going with a pretty irreverent aesthetic with our game, so I made a list of all the sounds I wanted, grabbed my acoustic, and noodled around for about two hours to try and put something together.<\/p>\n\n\n\n<p> <\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/media.discordapp.net\/attachments\/542324446900518943\/774093371949449216\/image0.jpg?width=938&amp;height=703\" alt=\"\" width=\"538\" height=\"403\"\/><figcaption>My one tool for making the sounds I needed<\/figcaption><\/figure><\/div>\n\n\n\n<p>I recorded it with an extremely closely miked AT2020, and had to learn a little bit about how to use my DAW of choice: Reaper. Thankfully, all I really needed was a handful of tracks I could use to layer some effects. I wish I could tell you anything about my process for creating the sounds, but I &#8220;composed&#8221; what I did exceptionally quickly in kind of a fugue state. I truly did just make different noises until things felt right. I made use of the percussive elements of the guitar, harmonics, pick scraping, and pretty much any technique you can think of.<\/p>\n\n\n\n<figure class=\"wp-block-audio\"><audio controls src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Roundabout_explosion_02.wav\"><\/audio><figcaption>Maybe the best sound  I came up with: a fun explosion<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-audio\"><audio controls src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Roundabout_score_01.wav\"><\/audio><figcaption>It&#8217;s hard to hear in the game, but here&#8217;s what plays when you score points.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">How we did<\/h2>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"748\" height=\"436\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Results.png\" alt=\"\" class=\"wp-image-206\" srcset=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Results.png 748w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2020\/11\/Results-300x175.png 300w\" sizes=\"auto, (max-width: 748px) 100vw, 748px\" \/><\/figure><\/div>\n\n\n\n<p>We submitted Round-a-bout pretty comfortably on Sunday evening, with maybe our smoothest release yet. It&#8217;s by far our most on-theme showing, and we all ended up pretty pleased with what we&#8217;d managed to put together. Like with every game we&#8217;ve made, it was another incredible learning opportunity for me, and I&#8217;m looking forward to doing even better with my next go at Ludum Dare.<\/p>\n\n\n\n<p>If you&#8217;re interested, you can try Round-a-bout <a href=\"https:\/\/trmrddr.itch.io\/roundabout\" data-type=\"URL\" data-id=\"https:\/\/trmrddr.itch.io\/roundabout\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Now that another Ludum Dare come and gone, ratings and all, this seems like as good a time as any to do a quick post-mortem of my team&#8217;s game Round-a-Bout. Our team&#8217;s third Ludum Dare game, Round-a-Bout is a little<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[2,5],"class_list":["post-178","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-development","tag-post-mortem"],"_links":{"self":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts\/178","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/comments?post=178"}],"version-history":[{"count":12,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts\/178\/revisions"}],"predecessor-version":[{"id":216,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts\/178\/revisions\/216"}],"wp:attachment":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/media?parent=178"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/categories?post=178"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/tags?post=178"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}