<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Get Info: #dailydriver</title>
    <description>Posts tagged “dailydriver” — Blog of independent game and app developer Matt Sephton. Featuring vintage Macintosh, game development, digital artwork, Japanese esoterica, video game reviews, hacks and tips, and much more.</description>
    <link>https://blog.gingerbeardman.com/tag/dailydriver/</link>
    <atom:link href="https://blog.gingerbeardman.com/tag/dailydriver/index.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 15 Jul 2026 17:33:15 +0000</pubDate>
    <lastBuildDate>Wed, 15 Jul 2026 17:33:15 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>

    
      
        <item>
          <title>DREAM RIDE for Sega Dreamcast (and emulators)</title>
          <description>&lt;p&gt;Over the Christmas break I had a spare week after a trip to Ireland was cancelled. What should I do with the time? I was already aware of the upcoming &lt;a href=&quot;https://itch.io/jam/dream-disc-24&quot;&gt;DreamDisc ‘24 game jam&lt;/a&gt; and had done some reading &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1843024068930658594&quot;&gt;a couple of months back&lt;/a&gt; scoping out a Lua-based SDK called ANTIRUINS Engine that looked familiar enough to me with my experience of LÖVE (love2d) and Playdate SDK. But what should I make with it?&lt;/p&gt;

&lt;p&gt;If I could get something fun up and running as quickly as possible it would prove the point and keep me motivated. Sounds like a plan. Also, it’s easier to make something that has already been planned out, so all the effort goes on implementation rather than design. I remembered my unreleased game &lt;a href=&quot;/tag/dailydriver/&quot;&gt;Daily Driver&lt;/a&gt;…maybe I could get those little cars moving around the screen? But what would make it &lt;em&gt;more Dreamcast?&lt;/em&gt; Simultaneous 4-player action and CPU opponents! &lt;em&gt;OK, let’s do this!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It took a bit of effort to get the build process setup, but after that I hit the ground running. I got the physics and 4-player control working pretty quickly. Then the sprites went in, albeit in the wrong colours. Physics equation and sprites are the only two things shared with &lt;a href=&quot;/tag/dailydriver/&quot;&gt;Daily Driver&lt;/a&gt;. Next I added a variety of game modes, power-ups, new sounds, all within a few days. Then I picked at it over the next week to polish it as much as possible before the game jam deadline. I thrive on deadlines.&lt;/p&gt;

&lt;p&gt;Many thanks to &lt;a href=&quot;https://bertholet.itch.io&quot;&gt;Bertholet&lt;/a&gt;, author of ANTIRUINS Engine, who has done a great job and was very gracious with his time helping me through the initial hurdles.&lt;/p&gt;

&lt;p&gt;I’ll do a full post-mortem and “what’s next” after the game jam results are announced. But I’m really happy with how it turned out. The cars look great in colour, and even with 8 of them on screen things are a rock solid 60fps, I really need to see how far I can push things.&lt;/p&gt;

&lt;hr /&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/dream-ride-game.png&quot; alt=&quot;IMG&quot; title=&quot;This shows a secret mode where the cars roaming around the menu screen remain in play!&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;download&quot;&gt;Download&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://gingerbeardman.itch.io/dream-ride&quot;&gt;gingerbeardman.itch.io/dream-ride&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Play DREAM RIDE on real hardware (tested on my original imported NTSC-U Sega Dreamcast from 1999, &lt;a href=&quot;/2020/12/03/dreamcast-gdemu-installation/&quot;&gt;fitted with a GDEMU&lt;/a&gt;) or in an emulator on your favourite console or handheld (tested with Flycast on macOS, GameForce Chi, Powkiddy RGB30, &lt;a href=&quot;https://twitter.com/0_game_it/status/1875734954946285610&quot;&gt;TrimUI Brick&lt;/a&gt;, and a modded Nintendo Switch).&lt;/p&gt;

&lt;p&gt;Future versions of the game with improvements and new modes will require payment, after all I’m a full-time indie developer and my caffeine intake needs to be maintained. Thanks for your support!&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;video&quot;&gt;Video&lt;/h2&gt;

&lt;p&gt;Below is a video where I forgot to switch audio source so it recorded audio through my Mac’s mic. 😅&lt;/p&gt;

&lt;lite-youtube style=&quot;aspect-ratio: 16/9;&quot; videoid=&quot;movAjOjZLRg&quot; params=&quot;start=0&amp;amp;modestbranding=2&quot;&gt;
&lt;/lite-youtube&gt;

</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 05 Jan 2025 23:59:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/01/05/dream-ride-for-sega-dreamcast-and-emulators/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/01/05/dream-ride-for-sega-dreamcast-and-emulators/</guid>
        </item>
      
    
      
        <item>
          <title>OpenSCAD to Sprite Sheet workflow</title>
          <description>&lt;p&gt;I just released the “OpenSCAD to Spritesheet” workflow I created for &lt;a href=&quot;/tag/dailydriver/&quot;&gt;Daily Driver&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/gingerbeardman/openscad-spritesheet&quot;&gt;github.com/gingerbeardman/openscad-spritesheet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a Frankenstein mish-mash of a Makefile and several shell scripts that evolved over many months/years. Initial rendering is done using OpenSCAD, and post-processing is done using ImageMagick. Model poses and rendering variations are controlled by variables in either the shell script or passed through to the model. The whole process is optimised to do as much in parallel as I could figure. More info at the link above! 🚗💨&lt;/p&gt;

&lt;h2 id=&quot;post-processing&quot;&gt;Post Processing&lt;/h2&gt;

&lt;p&gt;After exporting all frames there is some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image magick&lt;/code&gt; work to process the files as follows:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;stitch frames together into a single sprite sheet&lt;/li&gt;
  &lt;li&gt;split sprite sheet into RGBA channels&lt;/li&gt;
  &lt;li&gt;process channels to recolour and dither as required&lt;/li&gt;
  &lt;li&gt;recombine processed channels into new sprite sheet image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can read about that in a &lt;a href=&quot;/2021/06/05/channelling-rgb-into-1bit/&quot;&gt;previous blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;benchmarks&quot;&gt;Benchmarks&lt;/h2&gt;

&lt;p&gt;A full build of 36 cars is as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;3GHz 6-core Intel Mac mini 32GB
    &lt;ul&gt;
      &lt;li&gt;100% CPU for ~26 minutes&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;M1 Pro 10-core MacBook Pro 16GB
    &lt;ul&gt;
      &lt;li&gt;70% CPU for ~9 mins&lt;/li&gt;
      &lt;li&gt;about 3x speedup&lt;/li&gt;
      &lt;li&gt;approx 16 seconds per car&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s parallel 3D rendering, PNG writing &amp;amp; compositing &amp;amp; processing, and copying of ~140K files (which takes up ~0.5GB of disk space).&lt;/p&gt;

&lt;h2 id=&quot;example-model&quot;&gt;Example Model&lt;/h2&gt;

&lt;p&gt;Not to scale! Sizes of features are exagerated to allow for them to appear correct when rendered at a very small size.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/openscad-spritesheet-model-car.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;example-output&quot;&gt;Example Output&lt;/h2&gt;

&lt;p&gt;990 frames each for car and shadow, total of 1980 frames per sprite sheet. Each sprite sheet takes up about ~400KB of RAM on Playdate, and only one is loaded at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/openscad-spritesheet-car-table-38-38.png&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/openscad-spritesheet-car-table-38-38.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 04 Oct 2023 15:19:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/10/04/openscad-to-sprite-sheet/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/10/04/openscad-to-sprite-sheet/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Club GTi</title>
          <description>&lt;p&gt;My &lt;a href=&quot;/2023/06/07/gti-cub-supermini-festa/&quot;&gt;GTi Club: Supermini Festa!&lt;/a&gt; obsession has bled into my game &lt;a href=&quot;/tag/dailydriver/&quot;&gt;Daily Driver&lt;/a&gt;… the “Club GTi” car features smaller wheels than the standard size, more roll during turning, and lots of detail and decoration to show off the rendering workflow I spent so many hours on. A good choice for the autotesting/motorkhana/gymkhana stages.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-club-gti.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s the 3D model, created in &lt;a href=&quot;https://openscad.org&quot;&gt;OpenSCAD&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-club-gti-model.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 08 Mar 2022 12:32:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2022/03/08/club-gti/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2022/03/08/club-gti/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Teaser Artwork</title>
          <description>&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-artwork.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I commissioned the talented &lt;a href=&quot;https://www.instagram.com/vxclhd/&quot;&gt;@vxclhd&lt;/a&gt; to create a clay-style diorama of Daily Driver!&lt;/p&gt;

&lt;p&gt;There are a few things in this scene that I’ve not yet shown in game footage. Imagine this is a box shot teaser in a game magazine, while you wait for the Playdate and its game to release.&lt;/p&gt;

&lt;p&gt;“Please understand”, as Satoru Iwata used to say.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;It&amp;#39;s inspired by a style of plasticine modelling I often see in old 1980s Japanese computer/games promotional art. &lt;br /&gt;&lt;br /&gt;Examples that come to mind: box art on some of the Fujitsu FM-Towns Free Software Collection and select covers of MSX•FAN, but there are too many to mention. &lt;a href=&quot;https://t.co/aAdSEVL4yA&quot;&gt;pic.twitter.com/aAdSEVL4yA&lt;/a&gt;&lt;/p&gt;&amp;mdash; Matt Sephton🎴 (@gingerbeardman) &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1428772256239476740?ref_src=twsrc%5Etfw&quot;&gt;August 20, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 23 Aug 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/08/23/daily-driver-teaser-artwork/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/08/23/daily-driver-teaser-artwork/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: How big is a sprite sheet?</title>
          <description>&lt;p&gt;Since the early days of the project I’ve been generating the car sprites by rotating the 3D model 360 degrees in a sequence of 32 steps. It works well and I hadn’t given it much thought. Until over on the &lt;a href=&quot;https://discord.gg/zFKagQ2&quot;&gt;unofficial Playdate Discord server&lt;/a&gt; user Jono showed of a hot-dog trick rendered using 64 steps which looked super smooth! This got me thinking about increasing the number of rotation steps used in my workflow for Daily Driver.&lt;/p&gt;

&lt;h2 id=&quot;code-changes&quot;&gt;Code changes&lt;/h2&gt;

&lt;p&gt;I had to do a bit of code changing as I had used the value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;32&lt;/code&gt;, or the angular equivalent of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;11.25&lt;/code&gt; (degrees), in a few places throughout my code. Once I’d changed those to refer to a single variable I was set. I can now run the game with sprites of any number of steps.&lt;/p&gt;

&lt;h2 id=&quot;rendering-changes&quot;&gt;Rendering changes&lt;/h2&gt;

&lt;p&gt;Similar changes were also needed in my rendering workflow, moving any hardcoded steps or angle values into a variable. After replacing hard-coded magic numbers with variables, I spent way too long figuring out why angles were being rounded down to the nearest integer. Eventually I realised that in shell scripts the calculation &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$( 360/64 )&lt;/code&gt; gives an integer result of 5, whereas  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$( 360.0/64 )&lt;/code&gt; gives the more accurate floating point result of 5.625.&lt;/p&gt;

&lt;p&gt;Interestingly, I also needed to increase my filename pattern from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%03d&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%04d&lt;/code&gt; as there are now thousands of images being processed.&lt;/p&gt;

&lt;h2 id=&quot;how-high&quot;&gt;How high?&lt;/h2&gt;

&lt;p&gt;After making the necessary changes to my workflow it is very easy to render a new set of sprites. But what number of steps to use? My immediate thought was to simply double it to 64, which is higher than the frame rate I’m using and the result was definitely smoother.&lt;/p&gt;

&lt;p&gt;After this &lt;a href=&quot;https://twitter.com/mrgan&quot;&gt;Neven Mrgan&lt;/a&gt; asked if I’d explored whether there was a performance limit or upper boundary? I hadn’t but it sounded like a fun thing to explore.&lt;/p&gt;

&lt;p&gt;But why go higher? Well, even if we have more frames of animation that than refresh rate, we can still gain in terms of accuracy. The more steps there are the more discrete angles we can show the car pointing. That was good enough reason for me to try it out.&lt;/p&gt;

&lt;p&gt;I spent some time exporting a sprite at 32, 64, 90, 128, 180, 256, 360-steps to compare not only the smoothness of the animation but also other related implications.&lt;/p&gt;

&lt;h2 id=&quot;selected-results&quot;&gt;Selected results&lt;/h2&gt;

&lt;p&gt;Most modern browsers are well-behaved enough to only play an Animated GIF if it is on the page. So you’ll probably need to force reload the page (hold shift and click reload) to synchronise one or more of the below animations.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rotation-steps-32.gif#playdate&quot; alt=&quot;32 rotation steps&quot; title=&quot;Above: 32 rotation steps. After I&apos;ve mentioned looking for the &amp;quot;judder&amp;quot; you won&apos;t be able to unsee it&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rotation-steps-64.gif#playdate&quot; alt=&quot;64 rotation steps&quot; title=&quot;Above: Even with twice as many rotation steps there&apos;s still a slight judder&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rotation-steps-90.gif#playdate&quot; alt=&quot;90 rotation steps&quot; title=&quot;Above: 90 rotation steps give us one sprite every 4 degrees, judder becomes less noticable&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rotation-steps-180.gif#playdate&quot; alt=&quot;180 rotation steps&quot; title=&quot;Above: 180 rotation steps give us one sprite every 2 degrees, judder is even less noticable&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rotation-steps-360.gif#playdate&quot; alt=&quot;360 rotation steps&quot; title=&quot;Above: 360 rotation steps give us one sprite for every degree, as good as it gets&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;stats&quot;&gt;Stats&lt;/h2&gt;

&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;steps #&lt;/th&gt;
        &lt;th&gt;render time Secs&lt;/th&gt;
        &lt;th&gt;frames #&lt;/th&gt;
        &lt;th&gt;width PX&lt;/th&gt;
        &lt;th&gt;pixels W×H&lt;/th&gt;
        &lt;th&gt;png KB&lt;/th&gt;
        &lt;th&gt;pdt KB&lt;/th&gt;
        &lt;th&gt;ram Bytes&lt;/th&gt;
        &lt;th&gt;ram MB&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;32&lt;/td&gt;
        &lt;td&gt;30s&lt;/td&gt;
        &lt;td&gt;704&lt;/td&gt;
        &lt;td&gt;1216&lt;/td&gt;
        &lt;td&gt;1016576&lt;/td&gt;
        &lt;td&gt;48&lt;/td&gt;
        &lt;td&gt;39&lt;/td&gt;
        &lt;td&gt;147404&lt;/td&gt;
        &lt;td&gt;0.15&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;59s&lt;/td&gt;
        &lt;td&gt;1408&lt;/td&gt;
        &lt;td&gt;2432&lt;/td&gt;
        &lt;td&gt;2033152&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;78&lt;/td&gt;
        &lt;td&gt;294807&lt;/td&gt;
        &lt;td&gt;0.29&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;90&lt;/td&gt;
        &lt;td&gt;82s&lt;/td&gt;
        &lt;td&gt;1980&lt;/td&gt;
        &lt;td&gt;3420&lt;/td&gt;
        &lt;td&gt;2859120&lt;/td&gt;
        &lt;td&gt;111&lt;/td&gt;
        &lt;td&gt;107&lt;/td&gt;
        &lt;td&gt;414572&lt;/td&gt;
        &lt;td&gt;0.41&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;114s&lt;/td&gt;
        &lt;td&gt;2816&lt;/td&gt;
        &lt;td&gt;4864&lt;/td&gt;
        &lt;td&gt;4066304&lt;/td&gt;
        &lt;td&gt;143&lt;/td&gt;
        &lt;td&gt;140&lt;/td&gt;
        &lt;td&gt;589614&lt;/td&gt;
        &lt;td&gt;0.59&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;180&lt;/td&gt;
        &lt;td&gt;159s&lt;/td&gt;
        &lt;td&gt;3960&lt;/td&gt;
        &lt;td&gt;6840&lt;/td&gt;
        &lt;td&gt;5718240&lt;/td&gt;
        &lt;td&gt;174&lt;/td&gt;
        &lt;td&gt;188&lt;/td&gt;
        &lt;td&gt;829145&lt;/td&gt;
        &lt;td&gt;0.83&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;256&lt;/td&gt;
        &lt;td&gt;192s&lt;/td&gt;
        &lt;td&gt;5632&lt;/td&gt;
        &lt;td&gt;9728&lt;/td&gt;
        &lt;td&gt;8132608&lt;/td&gt;
        &lt;td&gt;209&lt;/td&gt;
        &lt;td&gt;227&lt;/td&gt;
        &lt;td&gt;1179228&lt;/td&gt;
        &lt;td&gt;1.18&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;360&lt;/td&gt;
        &lt;td&gt;285s&lt;/td&gt;
        &lt;td&gt;7920&lt;/td&gt;
        &lt;td&gt;13680&lt;/td&gt;
        &lt;td&gt;11436480&lt;/td&gt;
        &lt;td&gt;240&lt;/td&gt;
        &lt;td&gt;264&lt;/td&gt;
        &lt;td&gt;1658290&lt;/td&gt;
        &lt;td&gt;1.66&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;h2 id=&quot;charts&quot;&gt;Charts&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rotation-steps-charts.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;implications&quot;&gt;Implications&lt;/h2&gt;

&lt;p&gt;The increase from 32-steps to 360-steps is echoed by an increase of around a factor of ten across each of rendering time, number of frames and memory usage.&lt;/p&gt;

&lt;p&gt;In the above table we can see that related stats increase pretty much linearly, with a slight downward trend for file sizes and a slight upward trend for everything else.&lt;/p&gt;

&lt;p&gt;The increased smoothness is great, and very easy to notice. The benefits of the increased smoothness becomes less noticeable with each increase in step count. However, it’s important to remember there are other implications:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Rendering time&lt;/li&gt;
  &lt;li&gt;File size&lt;/li&gt;
  &lt;li&gt;Memory usage&lt;/li&gt;
  &lt;li&gt;Performance in-game&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;rendering-time&quot;&gt;Rendering time&lt;/h3&gt;
&lt;p&gt;This increases from what I would call a quick 30 seconds per render at 32-steps, to an extremely slow 284 seconds (4m45s) at 360-steps. For a full set of renders it’s not as easy as multiplying that number, there is some overhead that goes with it. This means a real world increase from ~20 minutes to multiple hours, which could be a problem. Cars aren’t generally re-rendered very frequently but when I do render them I do it many times as I make minor tweaks. So, at this point all numbers of steps are on the table but with a preference for those with shorter rendering times.&lt;/p&gt;

&lt;h3 id=&quot;file-size&quot;&gt;File size&lt;/h3&gt;
&lt;p&gt;Playdate’s compiled image format is very similar in file size to an optimised PNG. The file size increases by slightly more than double as the number of steps is doubled. Even the largest size at 360-steps is only 264KB (0.26MB) which seems OK in the grand scheme of things. All numbers of steps are on the table.&lt;/p&gt;

&lt;h3 id=&quot;memory-usage&quot;&gt;Memory usage&lt;/h3&gt;
&lt;p&gt;As we can see by the table below, memory usage is quite optimised on Playdate. The system is clever enough to trim transparent areas from cells of sprite sheets and store the offset of the image instead of the complete raw bitmap data. For this example sprite memory usage is roughly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(W×H÷8)*1.16&lt;/code&gt; rather than the expected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(W×H÷8)*2.0&lt;/code&gt;. The car sprite is my largest occupier of memory. Only one is loaded at a time and even at 360-steps it fits comfortably in the Playdate’s 16MB RAM. All numbers of steps are on the table.&lt;/p&gt;

&lt;h3 id=&quot;performance-in-game&quot;&gt;Performance in-game&lt;/h3&gt;
&lt;p&gt;I’m targeting a refresh rate of 60fps on the device, so it’s important for me to check that any changes to rendering don’t have an adverse effect on performance. At 32-steps the car sprite was changed roughly every second update, with 64-steps and higher the car sprite will be changed every update. Looking at performance in broad terms, there does seem to be a small hit when using the large numbers of steps. It doesn’t prevent me hitting 60fps, but it does leave me less time to do other things I have planned.&lt;/p&gt;

&lt;h2 id=&quot;final-choice&quot;&gt;Final choice?&lt;/h2&gt;
&lt;p&gt;If you’ve read this far you’re probably wanting to know my choice. How many steps did I choose? Well, the jury is out. This is the sort of thing I’ll have to live with for a while before I settle on a choice.&lt;/p&gt;

&lt;p&gt;I’ll probably end up going somewhere in the middle with a choice that gives increased smoothness and accuracy, renders fairly quickly and doesn’t affect performance in-game.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 07 Aug 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/08/07/how-big-is-a-sprite-sheet/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/08/07/how-big-is-a-sprite-sheet/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: To GIF and Back</title>
          <description>&lt;p&gt;When rendering my vehicles I generate a single PNG containing all frames. Playdate SDK calls these image tables, or more specifically matrix image tables. You could also call them sprite-sheets, with the one qualification that every sprite has the same dimensions.&lt;/p&gt;

&lt;p&gt;Given that these are plain old images they can be viewed easily. Showing a grid on top of the image makes the layout obvious. You can edit the image as a whole easily enough, but what if you want to adjust the size of every cell? Then we have a problem.&lt;/p&gt;

&lt;p&gt;There are a few apps that can load image tables, asking for the cell size during loading. My favourite such app is &lt;a href=&quot;https://www.piskelapp.com&quot;&gt;Piskel&lt;/a&gt; which has good options for import, editing, and export.&lt;/p&gt;

&lt;p&gt;However there is another type of image that consists of multiple frames of the same dimensions: the Animated GIF. There are far more apps that can deal with this format, such as Acorn or Adobe Photoshop.&lt;/p&gt;

&lt;h2 id=&quot;problem&quot;&gt;Problem&lt;/h2&gt;

&lt;p&gt;How can we convert a matrix image table to Animated GIF, and back again?&lt;/p&gt;

&lt;h2 id=&quot;thinking&quot;&gt;Thinking&lt;/h2&gt;

&lt;p&gt;We could use Piskel, but its GUI isn’t the most straightforward so using it twice at separate points in the conversion process gets old fast.&lt;/p&gt;

&lt;p&gt;So I had the idea of writing a couple of small shell scripts that leverage imagemagick and can be run from my Makefile, the command-line, macOS context menu, or all of the above.&lt;/p&gt;

&lt;h2 id=&quot;solution&quot;&gt;Solution&lt;/h2&gt;

&lt;p&gt;Below are both scripts, be sure to grab the Gist versions as they have error checking and “help” usage display.&lt;/p&gt;

&lt;h3 id=&quot;convert-image-table-to-animated-gif&quot;&gt;Convert image table to Animated GIF&lt;/h3&gt;

&lt;noscript&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/gingerbeardman/b85367a8e6b3d139d9c85f49e146af38&quot;&gt;View the source code as a Gist&lt;/a&gt;&lt;/p&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/gingerbeardman/b85367a8e6b3d139d9c85f49e146af38.js&quot;&gt;&lt;/script&gt;

&lt;h3 id=&quot;convert-animated-gif-to-image-table&quot;&gt;Convert Animated GIF to image table&lt;/h3&gt;

&lt;noscript&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/gingerbeardman/15a8e1e72745848190c0e7d583ca24e1&quot;&gt;View the source code as a Gist&lt;/a&gt;&lt;/p&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/gingerbeardman/15a8e1e72745848190c0e7d583ca24e1.js&quot;&gt;&lt;/script&gt;

</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 14 Jul 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/07/14/image-table-animated-gif/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/07/14/image-table-animated-gif/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Teaser Trailer</title>
          <description>&lt;p&gt;To celebrate today’s Playdate update video here is a Daily Driver teaser trailer. Yay!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-teaser-trailer.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 08 Jun 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/06/08/teaser-trailer/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/06/08/teaser-trailer/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Channelling RGB into 1-bit</title>
          <description>&lt;p&gt;After successfully splitting out dark and light elements of the sprites and rendering them in a HDR style, I figured: why stop there? Maybe I could squeeze another colour into the render - some specific shade of grey - that I could treat in a different way to introduce dithered areas to the sprite.&lt;/p&gt;

&lt;p&gt;This came together very quickly but it resulted in my Makefile, yet again, becoming too complicated. So I decided to rethink my approach.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit.gif&quot; alt=&quot;GIF&quot; title=&quot;Dither layer: specifying areas that should have a dither pattern applied, here the rear grille&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If only there was a way to encode information about three different colours in a single bitmap image. Hang on a minute, there already is: RGB!&lt;/p&gt;

&lt;h2 id=&quot;channels&quot;&gt;Channels&lt;/h2&gt;

&lt;p&gt;So my new idea was to use red, green and blue as the colours for my render, then split the image into those colour channels and process each individually to get separate light, dark and dithered elements.&lt;/p&gt;

&lt;p&gt;Using imagemagick it is easy to separate a single channel from an image:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;magick render.png -channel G -separate green.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From this simple start things quickly become more complex. I always find building imagemagick commands a very time consuming process. Indeed, it took me a long time to arrive at a workflow that was just right. But I think of it as time well-spent as it will result in much quicker and easier iterations when designing vehicles.&lt;/p&gt;

&lt;p&gt;Overall, I did a few more tricks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;threshold convert the channels to 1-bit colour&lt;/li&gt;
  &lt;li&gt;apply a dither pattern to the green channel&lt;/li&gt;
  &lt;li&gt;composite select layers back together as the final image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the process worked a treat!&lt;/p&gt;

&lt;p&gt;The resulting workflow takes half as long to execute as my previous workflow, with all vehicles rendering in ~8 minutes compared to ~16 minutes before that.&lt;/p&gt;

&lt;p&gt;Here are some example hi-res images showing the journey from initial render to final composite:&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit-1-render.png&quot; alt=&quot;PNG&quot; title=&quot;Render, 8-bit colour&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit-2-red.png&quot; alt=&quot;PNG&quot; title=&quot;Red channel, inverted, 1-bit colour&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit-3-green.png&quot; alt=&quot;PNG&quot; title=&quot;Green channel, 1-bit colour&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit-4-blue.png&quot; alt=&quot;PNG&quot; title=&quot;Blue channel, 1-bit colour&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit-5-alpha.png&quot; alt=&quot;PNG&quot; title=&quot;Alpha channel, 1-bit colour&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit-6-dither.png&quot; alt=&quot;PNG&quot; title=&quot;Green channel with Dither pattern applied&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-rgb-1bit-7-composite.png&quot; alt=&quot;PNG&quot; title=&quot;Final composite, 1-bit colour&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;elsewhere&quot;&gt;Elsewhere&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;2021-06-06—&lt;a href=&quot;https://news.ycombinator.com/item?id=27409371&quot;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 05 Jun 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/06/05/channelling-rgb-into-1bit/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/06/05/channelling-rgb-into-1bit/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Pseudo-HDR</title>
          <description>&lt;p&gt;I love OpenSCAD “the programmer’s solid 3D CAD modeller”, despite its idiosyncrasies. One of the quirks that causes me most pain is the lack of controls for scene lighting in the Preview render. Unfortunately, I use that very Preview render as my main output!&lt;/p&gt;

&lt;p&gt;This lighting issue manifests as shadows cast on the model. As I rotate the car some white “highlight” elements become darker. At this point the model is made up of many more shades of grey than I’d like.&lt;/p&gt;

&lt;p&gt;The problem escalates when I take the Preview renders and convert them to 1-bit (black and white) ready for display on Playdate. I use a simple threshold conversion where any pixels darker than the threshold value become black, and any lighter become white. The highlight elements that have been cast in shadow are now grey and in many cases after the threshold conversion they end up as black pixels when they should be white. The net visual result is a loss of detail as well as flashing elements as they disappearing on certain frames.&lt;/p&gt;

&lt;p&gt;My first attempt at solving this problem was to set model-specific threshold levels. This worked to a degree but there were still elements that would flash on and off as the car rotated, with headlights being the most noticeable. Whilst I lived with the issue for a long time it always bothered me as it did make the sprite feel buggy. Deep down I knew that the results could be better. However, looking at the OpenSCAD development roadmap made it clear that any improvements would have to come from me.&lt;/p&gt;

&lt;p&gt;One day, out of the blue, I realised that if black pixels are more reliably rendered then why don’t I take advantage of that and render the model in opposite colours so that the highlights are now black. After rendering I could simply negate the colours in the resulting image and the highlights would become white again. I tried a quick test and the results were good!&lt;/p&gt;

&lt;p&gt;Of course this means an additional render stage is added to my workflow, but that’s of little consequence since the Makefile is doing all the hard work. I also had to make some changes to my render scripts so that the normal and negative images are composited together into the final image. The resulting sprites have no flashing elements, maintain more fine detail, and appear a lot more solid as a result. Excellent!&lt;/p&gt;

&lt;p&gt;Afterwards it struck me that this process is similar to HDR photography where multiple photographs are combined to increase the dynamic range in the final image. So, just for kicks, I’m referring to this technique as HDR 1-bit rendering.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-prerendered-sprites-old.gif&quot; alt=&quot;GIF&quot; title=&quot;Old rendering workflow: note the disappearing headlights&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-prerendered-sprites-new.gif&quot; alt=&quot;GIF&quot; title=&quot;New rendering workflow: headlights are present and correct&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Fri, 21 May 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/05/21/pseudo-hdr/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/05/21/pseudo-hdr/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Makefile</title>
          <description>&lt;p&gt;During the shadow rendering changes I was generating new sprites quite frequently, and I quickly realised that my mess of shell scripts just wasn’t a good enough system to be used in anger. I would have to call my scripts manually, with a variety of parameters, and remember a bunch of stuff unique to each script.&lt;/p&gt;

&lt;p&gt;So I spent a day converting the script system into a Makefile, with rules for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;rendering&lt;/li&gt;
  &lt;li&gt;stitching&lt;/li&gt;
  &lt;li&gt;post-processing&lt;/li&gt;
  &lt;li&gt;copying&lt;/li&gt;
  &lt;li&gt;cleaning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also improved the scripts to make them touch all their output files with the last-modified timestamp of the original 3D model, meaning the scripts could become self-aware and know which output files are up-to-date and which need re-rendering.&lt;/p&gt;

&lt;p&gt;With this system I can generate a full set of renders of all vehicles in ~minutes, or render just a single car in ~seconds. This is such a huge time saver and has enabled me to iterate on car designs more quickly and easily than ever before. I’m already seeing the gains in much better sprites.&lt;/p&gt;

&lt;p&gt;Believe it or not this was the first Makefile I’ve ever needed to write from scratch. I knew enough about them to know that they would be a good fit for this task, and it was fun to learn more about something I only knew in passing.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 19 May 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/05/19/makefile/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/05/19/makefile/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Pre-rendering Ranger</title>
          <description>&lt;p&gt;Since very early on shadows in Daily Driver have just been simple rectangles: one size fits all, rendered from a single 3D model, and post-processed to add dithering. &lt;abbr title=&quot;Minimum Viable Product&quot;&gt;MVP&lt;/abbr&gt;, you know? Over time I decided to do multiple shadows, one each for short cars and long cars.&lt;/p&gt;

&lt;p&gt;Sometime later I threw caution to the wind and decided to render per-car shadows. However, the OpenSCAD &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;projection()&lt;/code&gt; command that I was using was so slow! In fact it zapped my desire to finish implementing the feature. Instead, I let it sit for many months.&lt;/p&gt;

&lt;p&gt;Recently, I picked things up again to get the a new trailer and demo out. And then it hit me, that if I flatten a car on the z-axis—as if Looney Tunes dropped a heavy weight on it—then that flat thing will be a close enough equivalent of a shadow for my use. So I did just that and the results were great, and more importantly very quick to render!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-prerendered-shadows-anim.gif&quot; alt=&quot;GIF&quot; title=&quot;Animation showing the theory of a model being squished down into its shadow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So I went all in and decided to not only render one shadow per-car but to render shadows for each individual frame. So now the direction of the front wheels and details of the body shape are reflected in the shadow. It might sound like a small thing but it really makes a big difference. You can see old vs new shadows in the image carousel below.&lt;/p&gt;

&lt;p&gt;It’s in situations like these that I’m really proud I put a ton of early effort into the tooling and build process that generates my sprites.&lt;/p&gt;

&lt;p&gt;Result!&lt;/p&gt;

&lt;div class=&quot;carousel__holder&quot;&gt;
    &lt;div class=&quot;carousel&quot;&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;a&quot; checked=&quot;checked&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;b&quot; /&gt;
        
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
        &lt;div class=&quot;carousel__track&quot;&gt;
          &lt;ul&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-prerendered-shadows-old.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-prerendered-shadows-old.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-prerendered-shadows-new.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-prerendered-shadows-new.png&quot; /&gt;&lt;/li&gt;
            
          &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div class=&quot;carousel__indicators&quot;&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
            
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
.carousel__holder {width: 100%; position: relative; padding-bottom: 82%; margin: 1rem 0 1rem;}
.carousel {
  height: 100%;
  width: 100%;
  overflow: hidden;
  text-align: center;
  position: absolute;
  padding: 0;
}
.carousel__staticimage,
.carousel__controls,
.carousel__activator {
  display: none;
}

.carousel__activator:nth-of-type(1):checked ~ .carousel__track {
  -webkit-transform: translateX(-000%);
          transform: translateX(-000%);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__controls:nth-of-type(1) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(1) {
  opacity: 1;
}

.carousel__activator:nth-of-type(2):checked ~ .carousel__track {
  -webkit-transform: translateX(-100%);
          transform: translateX(-100%);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__slide:nth-of-type(2) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__controls:nth-of-type(2) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(2) {
  opacity: 1;
}


.carousel__control {
  height: 30px;
  width: 30px;
  margin-top: -15px;
  top: 50%;
  position: absolute;
  display: block;
  cursor: pointer;
  border-width: 5px 5px 0 0;
  border-style: solid;
  opacity: 0.35;
  opacity: 1;
  outline: 0;
  z-index: 3;
  color: #fafafa;
  mix-blend-mode: difference;
}
.carousel__control:hover {
  opacity: 1;
}
.carousel__control--backward {
  left: 20px;
  -webkit-transform: rotate(-135deg);
          transform: rotate(-135deg);
}
.carousel__control--forward {
  right: 20px;
  -webkit-transform: rotate(45deg);
          transform: rotate(45deg);
}
.carousel__indicators {
  position: absolute;
  bottom: 20px;
  width: 100%;
  text-align: center;
}
.carousel__indicator {
  height: 10px;
  width: 10px;
  border-radius: 100%;
  display: inline-block;
  z-index: 2;
  cursor: pointer;
  opacity: 0.35;
  margin: 0 2.5px 0 2.5px;
}
.carousel__indicator:hover {
  opacity: 0.75;
}
.carousel__track {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 0;
  margin: 0;
  transition: -webkit-transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s, -webkit-transform 0.5s ease 0s;
}
.carousel__track .carousel__slide {
  display: block;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
}

.carousel__track .carousel__slide:nth-of-type(1) {
  -webkit-transform: translateX(000%) translateZ(0);
          transform: translateX(000%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(2) {
  -webkit-transform: translateX(100%) translateZ(0);
          transform: translateX(100%) translateZ(0);
}


.carousel--scale .carousel__slide {
  -webkit-transform: scale(0);
          transform: scale(0);
}
.carousel__slide {
  height: 100%;
  position: absolute;
  opacity: 0;
  overflow: hidden;
}
.carousel__slide .overlay {height: 100%;}
.carousel--thumb .carousel__indicator {
  height: 30px;
  width: 30px;
}
.carousel__indicator {
  background-color: #fafafa;
}

.carousel__slide:nth-of-type(1),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(1) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(2),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(2) {
  background-size: cover;
  background-position: center;
}

&lt;/style&gt;

&lt;script&gt;
  function isVisible(el) {
        while (el) {
            if (el === document) {
                return true;
            }

            var $style = window.getComputedStyle(el, null);

            if (!el) {
                return false;
            } else if (!$style) {
                return false;
            } else if ($style.display === &apos;none&apos;) {
                return false;
            } else if ($style.visibility === &apos;hidden&apos;) {
                return false;
            } else if (+$style.opacity === 0) {
                return false;
            } else if (($style.display === &apos;block&apos; || $style.display === &apos;inline-block&apos;) &amp;&amp;
                $style.height === &apos;0px&apos; &amp;&amp; $style.overflow === &apos;hidden&apos;) {
                return false;
            } else {
                return $style.position === &apos;fixed&apos; || isVisible(el.parentNode);
            }
        }
  }
  
  setInterval(function(){
    var j=0;
    var elements = document.querySelectorAll(&apos;.carousel__control--forward&apos;);
    for(i=(elements.length - 1);i&gt;-1;i--) {
      if(isVisible(elements[i])) j=i;
    }
    elements[j].click();
  },7000);
  
&lt;/script&gt;

</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 18 May 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/05/18/prerendering-ranger/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/05/18/prerendering-ranger/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Happy New Year!</title>
          <description>&lt;p&gt;I hope you’re safe and well and wish you a great 2021. I’m back at it, and am confident this year will be the year of Daily Driver and more besides!&lt;/p&gt;

&lt;p&gt;Recent work on the game includes improvements to the physics of in-game objects and creation of yet more vehicles and levels.&lt;/p&gt;

&lt;p&gt;“The car’s the star”, as they say, so I have sunk untold hours into both the physics and graphics of many different cars to ensure that the depth of control and animation is enough to hold a players interest for the time I hope they will spend with my game. There’s no final vehicle line-up, as often some cars don’t look unique enough after the first rough model is tested in the game. But these two are keepers!&lt;/p&gt;

&lt;div class=&quot;carousel__holder&quot;&gt;
    &lt;div class=&quot;carousel&quot;&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;a&quot; checked=&quot;checked&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;b&quot; /&gt;
        
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
        &lt;div class=&quot;carousel__track&quot;&gt;
          &lt;ul&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-happy-new-year-a.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-happy-new-year-a.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-happy-new-year-b.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-happy-new-year-b.png&quot; /&gt;&lt;/li&gt;
            
          &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div class=&quot;carousel__indicators&quot;&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
            
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
.carousel__holder {width: 100%; position: relative; padding-bottom: 82%; margin: 1rem 0 1rem;}
.carousel {
  height: 100%;
  width: 100%;
  overflow: hidden;
  text-align: center;
  position: absolute;
  padding: 0;
}
.carousel__staticimage,
.carousel__controls,
.carousel__activator {
  display: none;
}

.carousel__activator:nth-of-type(1):checked ~ .carousel__track {
  -webkit-transform: translateX(-000%);
          transform: translateX(-000%);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__controls:nth-of-type(1) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(1) {
  opacity: 1;
}

.carousel__activator:nth-of-type(2):checked ~ .carousel__track {
  -webkit-transform: translateX(-100%);
          transform: translateX(-100%);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__slide:nth-of-type(2) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__controls:nth-of-type(2) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(2) {
  opacity: 1;
}


.carousel__control {
  height: 30px;
  width: 30px;
  margin-top: -15px;
  top: 50%;
  position: absolute;
  display: block;
  cursor: pointer;
  border-width: 5px 5px 0 0;
  border-style: solid;
  opacity: 0.35;
  opacity: 1;
  outline: 0;
  z-index: 3;
  color: #fafafa;
  mix-blend-mode: difference;
}
.carousel__control:hover {
  opacity: 1;
}
.carousel__control--backward {
  left: 20px;
  -webkit-transform: rotate(-135deg);
          transform: rotate(-135deg);
}
.carousel__control--forward {
  right: 20px;
  -webkit-transform: rotate(45deg);
          transform: rotate(45deg);
}
.carousel__indicators {
  position: absolute;
  bottom: 20px;
  width: 100%;
  text-align: center;
}
.carousel__indicator {
  height: 10px;
  width: 10px;
  border-radius: 100%;
  display: inline-block;
  z-index: 2;
  cursor: pointer;
  opacity: 0.35;
  margin: 0 2.5px 0 2.5px;
}
.carousel__indicator:hover {
  opacity: 0.75;
}
.carousel__track {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 0;
  margin: 0;
  transition: -webkit-transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s, -webkit-transform 0.5s ease 0s;
}
.carousel__track .carousel__slide {
  display: block;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
}

.carousel__track .carousel__slide:nth-of-type(1) {
  -webkit-transform: translateX(000%) translateZ(0);
          transform: translateX(000%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(2) {
  -webkit-transform: translateX(100%) translateZ(0);
          transform: translateX(100%) translateZ(0);
}


.carousel--scale .carousel__slide {
  -webkit-transform: scale(0);
          transform: scale(0);
}
.carousel__slide {
  height: 100%;
  position: absolute;
  opacity: 0;
  overflow: hidden;
}
.carousel__slide .overlay {height: 100%;}
.carousel--thumb .carousel__indicator {
  height: 30px;
  width: 30px;
}
.carousel__indicator {
  background-color: #fafafa;
}

.carousel__slide:nth-of-type(1),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(1) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(2),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(2) {
  background-size: cover;
  background-position: center;
}

&lt;/style&gt;

&lt;script&gt;
  function isVisible(el) {
        while (el) {
            if (el === document) {
                return true;
            }

            var $style = window.getComputedStyle(el, null);

            if (!el) {
                return false;
            } else if (!$style) {
                return false;
            } else if ($style.display === &apos;none&apos;) {
                return false;
            } else if ($style.visibility === &apos;hidden&apos;) {
                return false;
            } else if (+$style.opacity === 0) {
                return false;
            } else if (($style.display === &apos;block&apos; || $style.display === &apos;inline-block&apos;) &amp;&amp;
                $style.height === &apos;0px&apos; &amp;&amp; $style.overflow === &apos;hidden&apos;) {
                return false;
            } else {
                return $style.position === &apos;fixed&apos; || isVisible(el.parentNode);
            }
        }
  }
  
  setInterval(function(){
    var j=0;
    var elements = document.querySelectorAll(&apos;.carousel__control--forward&apos;);
    for(i=(elements.length - 1);i&gt;-1;i--) {
      if(isVisible(elements[i])) j=i;
    }
    elements[j].click();
  },7000);
  
&lt;/script&gt;

&lt;p&gt;Interestingly, both these vehicles show off one aspect of the game that I’ve not really talked about so far: reference and nods to classic games. Here we can see an RC car (whose antenna wobbles!) that is a nod to games like Re-volt, RC Revenge, Smash Cars, RC de Go!, Excite Truck, RC Pro-Am etc. And the forklift is a nod to Shenmue and the infamous New Yokosuka Harbor District.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-happy-new-year.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 09 Jan 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/01/09/happy-new-year/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/01/09/happy-new-year/</guid>
        </item>
      
    
      
        <item>
          <title>デイリードライバー</title>
          <description>&lt;p&gt;The last couple of months have been tough going for a couple of reasons.&lt;/p&gt;

&lt;p&gt;Firstly, a new version of the Playdate SDK broke my game in a couple of important ways: my method of targeting 60fps stopped working, and more seriously the controls stopped working. The workaround for both of these issues was long and drawn out, but here’s a quick summary:&lt;/p&gt;

&lt;p&gt;Until this point I was waiting for the next frame update using the SDK &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait()&lt;/code&gt; function: one line of code that waits for a required amount of time. A change in how this works meant I was stuck. My workaround was do it in a more naïve way - just constantly checking the system timestamp to see if the allowed time for the current frame has passed. Keep it simple, I guess.&lt;/p&gt;

&lt;p&gt;The overhead of idling the CPU—the time it takes for it to wind down, do the wait, and then wind back up—is actually quite substantial when it takes a few milliseconds and you only have sixteen of them for each update!&lt;/p&gt;

&lt;p&gt;The end result is that the game is now running… better than ever. Even after these issues were resolved at a higher level by the Playdate SDK team, I have kept using my workarounds as the game runs faster. So, silver linings and all that!&lt;/p&gt;

&lt;p&gt;Secondly, an important issue that remains unresolved is exactly how—and when—Daily Driver will be released. I’m hopeful that will be as part of a Playdate Season, where games are delivered to the device on a schedule and you don’t know what game you’ll be playing until you see the light flash and you pick up and wake up your device. That’s the magic I want my players to have a piece of. That might turn out to be later rather than sooner, so we’ll have to be patient.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-katakana.png#playdate&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Anyway, other news: I’ve been trying to decide how to render the name of the game in Japanese. The English title uses &lt;em&gt;&lt;strong&gt;Futura Bold Oblique&lt;/strong&gt;&lt;/em&gt; which is a style that does not translate directly into non-Roman typefaces. I had found some bold Japanese fonts, with a little bit of character, but they seemed too cute and not geometric enough. After much investigation and many mockups I opted to draw the necessary katakana characters by hand, on a grid, and it’s turned out rather well. I’d love to expand this into a full typeface, but that’s a project for another time.&lt;/p&gt;

&lt;p&gt;Doing this sort of graphics work, or car design/rendering, are my goto procrastination tasks when I don’t have enough mojo to dive into the code and tackle the remaining work. So I’m sort of treading water on the final push of work needed to get the game over the line.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 14 Dec 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/12/14/daily-driver/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/12/14/daily-driver/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Automation Improvements</title>
          <description>&lt;p&gt;The recent automation was really just help with organisation. As soon as I started looking at running OpenSCAD from the shell/command-line it became obvious that I could do the rendering and organisation in one step without having to use external apps like Hazel.&lt;/p&gt;

&lt;p&gt;So, that’s now done. I render all the frames, with more sensible filenames, to a single folder.&lt;/p&gt;

&lt;p&gt;If I run all the renders one after the other, maxing out a single CPU core (99% CPU usage), time taken:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;~17 seconds 🐢&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But, using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt; directive and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait&lt;/code&gt; command, I can run the renders in parallel (well, technically it’s one process each; and batches of 32 works best) using all 6 CPU cores (~485% CPU usage), time taken:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;~10 seconds 🐇&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;…the beauty of the command line!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note to self: don’t publish a post about an automation breakthrough until the dust has settled.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Fri, 09 Oct 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/10/09/automation-improvements/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/10/09/automation-improvements/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Automation</title>
          <description>&lt;p&gt;In preparation for regenerating my many cars with x3 the number of sprites, I thought I’d try to sort the rendered frames automatically into named folders because this is fiddly manual work I really don’t enjoy, and a bit of a bottleneck in my asset generation. For each pose I have to render the frames then group the new image files into a folder that describes that pose, as these multiple folders are later be used for batch processing.&lt;/p&gt;

&lt;p&gt;I could use macOS Folder Actions for this, but I’ve been using an app called &lt;a href=&quot;https://www.noodlesoft.com/&quot;&gt;Hazel&lt;/a&gt; for many years to do this sort of thing, so that was my first choice.&lt;/p&gt;

&lt;p&gt;The hard work is done with a shell script, as I’m quite comfortable writing those.&lt;/p&gt;

&lt;h2 id=&quot;flow&quot;&gt;Flow&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;get most recently saved .scad file&lt;/li&gt;
  &lt;li&gt;parse filename to capture car name&lt;/li&gt;
  &lt;li&gt;parse file contents for left/right/forward/backward tilt values&lt;/li&gt;
  &lt;li&gt;concatenate all this information as our new folder name&lt;/li&gt;
  &lt;li&gt;create new folder&lt;/li&gt;
  &lt;li&gt;move matching file into new folder&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means that the folder name will change based on the render settings in the file—perfect!&lt;/p&gt;

&lt;p&gt;I still have to make 9 small sets of manual text changes to render each car pose, so that’s the next thing I’ll try to automate by running OpenSCAD from the command line.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-automation.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 07 Oct 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/10/07/automation/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/10/07/automation/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Weight Transfer</title>
          <description>&lt;p&gt;I’ve been playing old Japanese PlayStation game &lt;a href=&quot;https://digitaldriftracer.wordpress.com/2019/06/09/touge-max-series-overview/&quot;&gt;Touge Max G&lt;/a&gt; and besides this having a gymkhana mode similar to aspects of Daily Driver, it also has a realistic handling model. One of the things that struck me about that was how the car rocks back and forth when accelerating and braking/reversing. One of the other games in the Touge series also has a school mode that talks you through this concept of weight transfer. It’s realistic and adds some level of dynamics to the car, making it feel less like a solid block sliding around.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-weight-transfer.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So I’ve added this effect to Daily Driver and will be refining it going forward. I find it breathes extra life into the car just as it does in other games with full 3D physics modelling. It requires 3x the number of car sprites but because I render them from 3D it’s not much extra work and I think it’s well worth the effort.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-weight-transfer.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 03 Oct 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/10/03/weight-transfer/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/10/03/weight-transfer/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Social Update</title>
          <description>&lt;p&gt;The launch of this Patreon for Daily Driver also sees the game’s &lt;a href=&quot;https://forums.tigsource.com/index.php?topic=70808.msg1428724#msg1428724&quot;&gt;topic on TIGSource Forums&lt;/a&gt; as the first Playdate game!&lt;/p&gt;

&lt;p&gt;And also &lt;a href=&quot;https://www.indiedb.com/games/daily-driver&quot;&gt;on IndieDB&lt;/a&gt; as perhaps the first Playdate game? Though I do see fellow devs and Patreon Creators &lt;a href=&quot;https://www.patreon.com/rngparty&quot;&gt;RNG Party&lt;/a&gt; are already listed but they are yet to add their current Playdate game.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 05 Sep 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/09/05/social-update/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/09/05/social-update/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Cannonball Run</title>
          <description>&lt;p&gt;Accidental new game mode discovered?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“Cannonball Run”&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Collect stuff whilst a barrage of heavy things bounce around the screen!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was a result of my continued work implementing different surface frictions, and I mistakenly set the cones to not be affected by friction and thus never slow down. Hitting them a few times was enough to get them moving at a good speed, and then I drove around the screen.&lt;/p&gt;

&lt;p&gt;What I didn’t appreciate is that my collision model uses elastic collision, which allows the car to knock stuff over in a quite pleasing way where the car takes a little bit of a knock. But if those things are moving fast enough, as the cones are here, then any collision will also affect the car to a larger degree. You can see the car being pushed around by the out of control traffic cones.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-one-cannonball-run.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 31 Aug 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/08/31/cannonball-run/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/08/31/cannonball-run/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: One Shade of Grey?</title>
          <description>&lt;p&gt;Since I am targeting 60fps one of the things I can do is flash a black sprite every other frame to get a shade of grey. Yes! Grey on a black and white screen.&lt;/p&gt;

&lt;p&gt;It only works at high frame rates (note: 50fps isn’t high enough!) because the screen is &lt;em&gt;so damned good&lt;/em&gt; that flashing at a slower rate simply looks like… an image flashing. The effects is also visible on device for the same reason.&lt;/p&gt;

&lt;p&gt;So I’m not sure I’ll use it, as all other shadows are dithered and flashing them all will hit the frame rate. But it was fun to try! One for another game, perhaps?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I believe this technique was used on other systems with limited colours, such as Game Boy and Palm (to get 4 shades), certain Commodore 64 games (eg. Creatures 2) did it to get never-before-seen colours, and of course old arcade and video games often did exactly my trick to get their semi-opaque shadows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-one-shade-grey.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 19 Aug 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/08/19/one-shade-of-grey/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/08/19/one-shade-of-grey/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Eight-Six</title>
          <description>&lt;p&gt;When you get a crazy idea that you’ll probably never be able to use, but you do it anyway, just to scratch the itch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-eight-six.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-eight-six.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 18 Aug 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/08/18/eight-six/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/08/18/eight-six/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Playable Demo</title>
          <description>&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-playable-demo.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A made a demo build of Daily Driver. It is based on code a couple weeks old (dated &lt;strong&gt;30 July 2020&lt;/strong&gt;) so whilst not representative of where I am right now with the game it does show my progress since the last build I shared at the start of June.&lt;/p&gt;

&lt;p&gt;Controls&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;any combination of d-pad, A/B, Crank&lt;/li&gt;
  &lt;li&gt;d-pad U/D, or A/B = accelerate/brake&lt;/li&gt;
  &lt;li&gt;d-pad L/R, or Crank = steer left/right&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notes&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;runs at 60fps on device (50fps in Simulator)&lt;/li&gt;
  &lt;li&gt;pressing Menu twice will reset the current stage layout&lt;/li&gt;
  &lt;li&gt;pressing Menu then Options will load the next stage&lt;/li&gt;
  &lt;li&gt;19 different stages (all cone layouts in this build)&lt;/li&gt;
  &lt;li&gt;includes level editor!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Debug Keys for game (simulator only):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;E = open level editor&lt;/li&gt;
  &lt;li&gt;F = toggle fps indicator&lt;/li&gt;
  &lt;li&gt;J = jump!&lt;/li&gt;
  &lt;li&gt;L = list all sprites&lt;/li&gt;
  &lt;li&gt;N = next stage&lt;/li&gt;
  &lt;li&gt;P = play replay data&lt;/li&gt;
  &lt;li&gt;R = print replay data to console&lt;/li&gt;
  &lt;li&gt;T = toggle telemetry&lt;/li&gt;
  &lt;li&gt;U = toggle frame limiter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Controls for editor (simulator only):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;d-pad = move cursor&lt;/li&gt;
  &lt;li&gt;A = place an object or increment object under the cursor&lt;/li&gt;
  &lt;li&gt;B = toggle precision mode
    &lt;ul&gt;
      &lt;li&gt;cursor moves in smaller increments (5px vs 20px)&lt;/li&gt;
      &lt;li&gt;drag/move item under cursor&lt;/li&gt;
      &lt;li&gt;A = delete item under cursor&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Debug Keys for editor (simulator only):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;B = new blank stage&lt;/li&gt;
  &lt;li&gt;D = dump stage JSON to console (for copy and pasting)&lt;/li&gt;
  &lt;li&gt;E = exit level editor&lt;/li&gt;
  &lt;li&gt;O = output stage as “m-edited.json” in game Sandbox directory&lt;/li&gt;
  &lt;li&gt;Z = undo (deletes last item placed)&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 15 Aug 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/08/15/playable-demo/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/08/15/playable-demo/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Post-processing workflow</title>
          <description>&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-post-processing.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My post-processing to 1-bit is fairly simple. I use a bespoke tool that allows me to have “live” (realtime) manual control over a bunch of image filters so I can see the results immediately.&lt;/p&gt;

&lt;p&gt;But essentially it’s:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;convert to greyscale, using one of many algorithms&lt;/li&gt;
  &lt;li&gt;reduce colours to 1-bit, decide on dithering or threshold level on case-by-case basis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The grey shades that I applied to my model in OpenSCAD give an element of control during this conversion process. Greys can be pushed either way, towards black or white, depending on my need with the specific model I am working on.&lt;/p&gt;

&lt;p&gt;In this instance I desaturated the greys which blows them out to nearer white. And then I chose to threshold to reduce to b/w.&lt;/p&gt;

&lt;p&gt;I also have the wheels as a separate finished image so I don’t have to worry if their detail is lost during this phase, I can just paste over the accurate/finished wheels.&lt;/p&gt;

&lt;p&gt;Final result, unedited:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-post-processing.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I would later touch up the sprite by hand to reinforce any details I think have been lost. I use Piskel for edited sprites because it has really nice sprite sheet support, drag and drop loading, and quick and versatile exporting.&lt;/p&gt;

&lt;p&gt;Aside: 32 is a number that is a leftover from a different prototype and it’s stuck. I guess it should really be 36? or 24? or 18?. But it’s too late now! Actually it would be relatively easy to change but I have bigger fish to fry.&lt;/p&gt;

&lt;p&gt;The animations alone could run at 99fps—it’s anything that causes more drawing which slow things down. Collisions, not because they are computationally heavy, but because they cause a lot of sprite updates - which means drawing moving things - to happen. I’m working hard to maintain 60fps on device (50fps in simulator for… reasons) and am excited I’ve managed to get here.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 10 Aug 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/08/10/post-processing-workflow/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/08/10/post-processing-workflow/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Retrobatch workflow</title>
          <description>
&lt;div class=&quot;carousel__holder&quot;&gt;
    &lt;div class=&quot;carousel&quot;&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;a&quot; checked=&quot;checked&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;b&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;c&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;d&quot; /&gt;
        
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;d&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;c&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;d&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;c&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
        &lt;div class=&quot;carousel__track&quot;&gt;
          &lt;ul&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-1.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-1.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-2.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-2.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-3.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-3.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-4.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-retrobatch-workflow-4.png&quot; /&gt;&lt;/li&gt;
            
          &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div class=&quot;carousel__indicators&quot;&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;c&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;d&quot;&gt;&lt;/label&gt;
            
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
.carousel__holder {width: 100%; position: relative; padding-bottom: 82%; margin: 1rem 0 1rem;}
.carousel {
  height: 100%;
  width: 100%;
  overflow: hidden;
  text-align: center;
  position: absolute;
  padding: 0;
}
.carousel__staticimage,
.carousel__controls,
.carousel__activator {
  display: none;
}

.carousel__activator:nth-of-type(1):checked ~ .carousel__track {
  -webkit-transform: translateX(-000%);
          transform: translateX(-000%);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__controls:nth-of-type(1) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(1) {
  opacity: 1;
}

.carousel__activator:nth-of-type(2):checked ~ .carousel__track {
  -webkit-transform: translateX(-100%);
          transform: translateX(-100%);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__slide:nth-of-type(2) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__controls:nth-of-type(2) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(2) {
  opacity: 1;
}

.carousel__activator:nth-of-type(3):checked ~ .carousel__track {
  -webkit-transform: translateX(-200%);
          transform: translateX(-200%);
}
.carousel__activator:nth-of-type(3):checked ~ .carousel__slide:nth-of-type(3) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(3):checked ~ .carousel__controls:nth-of-type(3) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(3):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(3) {
  opacity: 1;
}

.carousel__activator:nth-of-type(4):checked ~ .carousel__track {
  -webkit-transform: translateX(-300%);
          transform: translateX(-300%);
}
.carousel__activator:nth-of-type(4):checked ~ .carousel__slide:nth-of-type(4) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(4):checked ~ .carousel__controls:nth-of-type(4) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(4):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(4) {
  opacity: 1;
}


.carousel__control {
  height: 30px;
  width: 30px;
  margin-top: -15px;
  top: 50%;
  position: absolute;
  display: block;
  cursor: pointer;
  border-width: 5px 5px 0 0;
  border-style: solid;
  opacity: 0.35;
  opacity: 1;
  outline: 0;
  z-index: 3;
  color: #fafafa;
  mix-blend-mode: difference;
}
.carousel__control:hover {
  opacity: 1;
}
.carousel__control--backward {
  left: 20px;
  -webkit-transform: rotate(-135deg);
          transform: rotate(-135deg);
}
.carousel__control--forward {
  right: 20px;
  -webkit-transform: rotate(45deg);
          transform: rotate(45deg);
}
.carousel__indicators {
  position: absolute;
  bottom: 20px;
  width: 100%;
  text-align: center;
}
.carousel__indicator {
  height: 10px;
  width: 10px;
  border-radius: 100%;
  display: inline-block;
  z-index: 2;
  cursor: pointer;
  opacity: 0.35;
  margin: 0 2.5px 0 2.5px;
}
.carousel__indicator:hover {
  opacity: 0.75;
}
.carousel__track {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 0;
  margin: 0;
  transition: -webkit-transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s, -webkit-transform 0.5s ease 0s;
}
.carousel__track .carousel__slide {
  display: block;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
}

.carousel__track .carousel__slide:nth-of-type(1) {
  -webkit-transform: translateX(000%) translateZ(0);
          transform: translateX(000%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(2) {
  -webkit-transform: translateX(100%) translateZ(0);
          transform: translateX(100%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(3) {
  -webkit-transform: translateX(200%) translateZ(0);
          transform: translateX(200%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(4) {
  -webkit-transform: translateX(300%) translateZ(0);
          transform: translateX(300%) translateZ(0);
}


.carousel--scale .carousel__slide {
  -webkit-transform: scale(0);
          transform: scale(0);
}
.carousel__slide {
  height: 100%;
  position: absolute;
  opacity: 0;
  overflow: hidden;
}
.carousel__slide .overlay {height: 100%;}
.carousel--thumb .carousel__indicator {
  height: 30px;
  width: 30px;
}
.carousel__indicator {
  background-color: #fafafa;
}

.carousel__slide:nth-of-type(1),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(1) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(2),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(2) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(3),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(3) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(4),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(4) {
  background-size: cover;
  background-position: center;
}

&lt;/style&gt;

&lt;script&gt;
  function isVisible(el) {
        while (el) {
            if (el === document) {
                return true;
            }

            var $style = window.getComputedStyle(el, null);

            if (!el) {
                return false;
            } else if (!$style) {
                return false;
            } else if ($style.display === &apos;none&apos;) {
                return false;
            } else if ($style.visibility === &apos;hidden&apos;) {
                return false;
            } else if (+$style.opacity === 0) {
                return false;
            } else if (($style.display === &apos;block&apos; || $style.display === &apos;inline-block&apos;) &amp;&amp;
                $style.height === &apos;0px&apos; &amp;&amp; $style.overflow === &apos;hidden&apos;) {
                return false;
            } else {
                return $style.position === &apos;fixed&apos; || isVisible(el.parentNode);
            }
        }
  }
  
  setInterval(function(){
    var j=0;
    var elements = document.querySelectorAll(&apos;.carousel__control--forward&apos;);
    for(i=(elements.length - 1);i&gt;-1;i--) {
      if(isVisible(elements[i])) j=i;
    }
    elements[j].click();
  },7000);
  
&lt;/script&gt;

&lt;h2 id=&quot;retrobatch&quot;&gt;Retrobatch&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;process dumped frames x 32
    &lt;ul&gt;
      &lt;li&gt;once each for left, right and straight directions&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;stitch processed frames together as sprite-sheets x 3
    &lt;ul&gt;
      &lt;li&gt;we end up with three long images&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;stitch 3x sprite-sheets together
    &lt;ul&gt;
      &lt;li&gt;we end up with our final image ready for post-processing&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Edit, August 2020: since writing this post I’ve been able to condense this process into 1 single workflow that executes much faster. See image 4. Thanks to Gus, maker of Retrobatch!&lt;/p&gt;

&lt;p&gt;Edit, June 2021: I replaced my all uses of Retrobatch with imagemagick commands.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 09 Aug 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/08/09/retrobatch-workflow/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/08/09/retrobatch-workflow/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: OpenSCAD workflow</title>
          <description>
&lt;div class=&quot;carousel__holder&quot;&gt;
    &lt;div class=&quot;carousel&quot;&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;a&quot; checked=&quot;checked&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;b&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;c&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;d&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel&quot; id=&quot;e&quot; /&gt;
        
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;e&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;c&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;d&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;c&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;e&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;d&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
          &lt;/div&gt;
        
        &lt;div class=&quot;carousel__track&quot;&gt;
          &lt;ul&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-1.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-1.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-2.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-2.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-3.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-3.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-4.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-4.png&quot; /&gt;&lt;/li&gt;
            
            &lt;li class=&quot;carousel__slide&quot; style=&quot;background-image: url(&apos;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-5.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-openscad-workflow-5.png&quot; /&gt;&lt;/li&gt;
            
          &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div class=&quot;carousel__indicators&quot;&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;a&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;b&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;c&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;d&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;e&quot;&gt;&lt;/label&gt;
            
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
.carousel__holder {width: 100%; position: relative; padding-bottom: 82%; margin: 1rem 0 1rem;}
.carousel {
  height: 100%;
  width: 100%;
  overflow: hidden;
  text-align: center;
  position: absolute;
  padding: 0;
}
.carousel__staticimage,
.carousel__controls,
.carousel__activator {
  display: none;
}

.carousel__activator:nth-of-type(1):checked ~ .carousel__track {
  -webkit-transform: translateX(-000%);
          transform: translateX(-000%);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__controls:nth-of-type(1) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(1):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(1) {
  opacity: 1;
}

.carousel__activator:nth-of-type(2):checked ~ .carousel__track {
  -webkit-transform: translateX(-100%);
          transform: translateX(-100%);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__slide:nth-of-type(2) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__controls:nth-of-type(2) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(2):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(2) {
  opacity: 1;
}

.carousel__activator:nth-of-type(3):checked ~ .carousel__track {
  -webkit-transform: translateX(-200%);
          transform: translateX(-200%);
}
.carousel__activator:nth-of-type(3):checked ~ .carousel__slide:nth-of-type(3) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(3):checked ~ .carousel__controls:nth-of-type(3) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(3):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(3) {
  opacity: 1;
}

.carousel__activator:nth-of-type(4):checked ~ .carousel__track {
  -webkit-transform: translateX(-300%);
          transform: translateX(-300%);
}
.carousel__activator:nth-of-type(4):checked ~ .carousel__slide:nth-of-type(4) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(4):checked ~ .carousel__controls:nth-of-type(4) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(4):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(4) {
  opacity: 1;
}

.carousel__activator:nth-of-type(5):checked ~ .carousel__track {
  -webkit-transform: translateX(-400%);
          transform: translateX(-400%);
}
.carousel__activator:nth-of-type(5):checked ~ .carousel__slide:nth-of-type(5) {
  transition: opacity 0.5s, -webkit-transform 0.5s;
  transition: opacity 0.5s, transform 0.5s;
  transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
  -webkit-transform: scale(1);
          transform: scale(1);
}
.carousel__activator:nth-of-type(5):checked ~ .carousel__controls:nth-of-type(5) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(5):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(5) {
  opacity: 1;
}


.carousel__control {
  height: 30px;
  width: 30px;
  margin-top: -15px;
  top: 50%;
  position: absolute;
  display: block;
  cursor: pointer;
  border-width: 5px 5px 0 0;
  border-style: solid;
  opacity: 0.35;
  opacity: 1;
  outline: 0;
  z-index: 3;
  color: #fafafa;
  mix-blend-mode: difference;
}
.carousel__control:hover {
  opacity: 1;
}
.carousel__control--backward {
  left: 20px;
  -webkit-transform: rotate(-135deg);
          transform: rotate(-135deg);
}
.carousel__control--forward {
  right: 20px;
  -webkit-transform: rotate(45deg);
          transform: rotate(45deg);
}
.carousel__indicators {
  position: absolute;
  bottom: 20px;
  width: 100%;
  text-align: center;
}
.carousel__indicator {
  height: 10px;
  width: 10px;
  border-radius: 100%;
  display: inline-block;
  z-index: 2;
  cursor: pointer;
  opacity: 0.35;
  margin: 0 2.5px 0 2.5px;
}
.carousel__indicator:hover {
  opacity: 0.75;
}
.carousel__track {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 0;
  margin: 0;
  transition: -webkit-transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s;
  transition: transform 0.5s ease 0s, -webkit-transform 0.5s ease 0s;
}
.carousel__track .carousel__slide {
  display: block;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
}

.carousel__track .carousel__slide:nth-of-type(1) {
  -webkit-transform: translateX(000%) translateZ(0);
          transform: translateX(000%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(2) {
  -webkit-transform: translateX(100%) translateZ(0);
          transform: translateX(100%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(3) {
  -webkit-transform: translateX(200%) translateZ(0);
          transform: translateX(200%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(4) {
  -webkit-transform: translateX(300%) translateZ(0);
          transform: translateX(300%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(5) {
  -webkit-transform: translateX(400%) translateZ(0);
          transform: translateX(400%) translateZ(0);
}


.carousel--scale .carousel__slide {
  -webkit-transform: scale(0);
          transform: scale(0);
}
.carousel__slide {
  height: 100%;
  position: absolute;
  opacity: 0;
  overflow: hidden;
}
.carousel__slide .overlay {height: 100%;}
.carousel--thumb .carousel__indicator {
  height: 30px;
  width: 30px;
}
.carousel__indicator {
  background-color: #fafafa;
}

.carousel__slide:nth-of-type(1),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(1) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(2),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(2) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(3),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(3) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(4),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(4) {
  background-size: cover;
  background-position: center;
}

.carousel__slide:nth-of-type(5),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(5) {
  background-size: cover;
  background-position: center;
}

&lt;/style&gt;

&lt;script&gt;
  function isVisible(el) {
        while (el) {
            if (el === document) {
                return true;
            }

            var $style = window.getComputedStyle(el, null);

            if (!el) {
                return false;
            } else if (!$style) {
                return false;
            } else if ($style.display === &apos;none&apos;) {
                return false;
            } else if ($style.visibility === &apos;hidden&apos;) {
                return false;
            } else if (+$style.opacity === 0) {
                return false;
            } else if (($style.display === &apos;block&apos; || $style.display === &apos;inline-block&apos;) &amp;&amp;
                $style.height === &apos;0px&apos; &amp;&amp; $style.overflow === &apos;hidden&apos;) {
                return false;
            } else {
                return $style.position === &apos;fixed&apos; || isVisible(el.parentNode);
            }
        }
  }
  
  setInterval(function(){
    var j=0;
    var elements = document.querySelectorAll(&apos;.carousel__control--forward&apos;);
    for(i=(elements.length - 1);i&gt;-1;i--) {
      if(isVisible(elements[i])) j=i;
    }
    elements[j].click();
  },7000);
  
&lt;/script&gt;

&lt;p&gt;I took the plunge and upgraded to a Mac mini and 4K display so had to migrate my setup and then figure out why my workflow was broken (spoiler: retina!) compared to my old rMBP with non-retina display.&lt;/p&gt;

&lt;p&gt;So, my workflow uses the following apps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://openscad.org&quot;&gt;OpenSCAD&lt;/a&gt; “The Programmers Solid 3D CAD Modeller”&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://flyingmeat.com/retrobatch/&quot;&gt;Retrobatch&lt;/a&gt; “a unique application for automating actions on multiple images at the same time”&lt;/li&gt;
  &lt;li&gt;post-processing “greyscale and dithering tool” (I use my own realtime tool, but any image editor would do it to a degree, see this other thread)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is so I can re-run a workflow at any point (maybe in a make file) which I often do during development. These become executable assets, of sorts, in my project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenSCAD&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;define 3D models (I get a feeling like coding CSS in a strange way)&lt;/li&gt;
  &lt;li&gt;animate the model spinning through one 360-degree rotation&lt;/li&gt;
  &lt;li&gt;dump frames out as PNG files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the model definition to try out:&lt;/p&gt;

&lt;noscript&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/gingerbeardman/a0a0b967c480ab973d40aaf5e78fd47f&quot;&gt;View the source code as a Gist&lt;/a&gt;&lt;/p&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/gingerbeardman/a0a0b967c480ab973d40aaf5e78fd47f.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;I love building 3D this way, it’s kind of like LEGO. I use basic geometric building blocks (cube, sphere, cylinder, polygon, etc) and some boolean operations (difference, intersection, union). There are some other cool things (hull, minkowski). I have the commands in &lt;a href=&quot;https://dash.app/&quot;&gt;Dash.app&lt;/a&gt;alongside the Playdate SDK docs.&lt;/p&gt;

&lt;p&gt;In the image 2 you can see what the model looks like with all the blocks I have used to make it visible at once.&lt;/p&gt;

&lt;p&gt;My particular approach is &lt;em&gt;subtractive&lt;/em&gt;— kind of like sculpting—I start with large blocks and cut away at them using other shapes and the &lt;em&gt;difference&lt;/em&gt; function. When finding the exact placement for a block I use the &lt;em&gt;#&lt;/em&gt; precedent which makes the blocks show up as semi-opaque red blocks. See main image.&lt;/p&gt;

&lt;p&gt;I colour each block in a one or two shades of grey, black and white. This is to help with the conversion to 1-bit later on. It’s not so obvious here as the lighting makes the colours look many different shades - for example the wheels are black with white centre but look grey here. See image 3.&lt;/p&gt;

&lt;p&gt;Using some simple programming constructs and variables I can add booleans to trigger different states, I use this for angled front wheels and tilted car body. See image 4.&lt;/p&gt;

&lt;p&gt;And also to set the distance and rotation of the camera relative to the model when in animation mode. In this mode I enter a speed (doesn’t matter but higher the better) number of frames = 32, and the tick the box to dump the images. The tick disappears when the images are all done. See image 5.&lt;/p&gt;

&lt;p&gt;I also rendered the skid marks, car shadow, and some other elements.&lt;/p&gt;

&lt;p&gt;There is a lot that is annoying about this app&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;not retina-optimised (so I run it in Low Resolution, set using Get Info on the app)&lt;/li&gt;
  &lt;li&gt;runs maxed out on a single core&lt;/li&gt;
  &lt;li&gt;doesn’t have configurable lighting (I’d prefer uniform or no lighting)&lt;/li&gt;
  &lt;li&gt;Qt Framework app, so not really macOS-native&lt;/li&gt;
  &lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…but I still use it! I am not aware of anything else quite like it.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 08 Aug 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/08/08/openscad-workflow/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/08/08/openscad-workflow/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Shine Get!</title>
          <description>&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-shine-get.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Added steering assist, which locks your final steering direction to one of 16 directions, rather than allowing any angle. This prevents over steer when trying to turn to the specific directions required in a level. Of course this is optional; it is variable and can be switched off if the player does not want it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25 July 2020.&lt;/strong&gt; I added another type of object, one that disappears upon collision meaning that it is collectable. It’s a kind of coin with a coin-collecting sound effect.&lt;/p&gt;

&lt;p&gt;No changes to physics have been made in a long time, but I guess on this level there are no collisions so the car is mostly at full throttle and so seems a bit more responsive than previously?&lt;/p&gt;

&lt;p&gt;I’ve already done multiple car types with different handling and sprites. Still more to do though.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 21 Jul 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/07/21/shine-get/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/07/21/shine-get/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Instant Replay</title>
          <description>&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-instant-replay.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I guess this looks like a looped GIF, but it’s not!&lt;/p&gt;

&lt;p&gt;I added some quite simple code to record every button press and the tick time at which it happened. The beauty of this approach is that most frames there are no button presses so the recorded data is very small. The work I’ve previously put in structuring the game in an Object-Oriented way really paid off.&lt;/p&gt;

&lt;p&gt;The game engine is completely deterministic—it has no random elements—so playing back the data is also very simple and the result is an exact frame by frame replay.&lt;/p&gt;

&lt;p&gt;Seeing this for the first time felt like magic. This is why I love developing games!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Fri, 17 Jul 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/07/17/instant-replay/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/07/17/instant-replay/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: High Frame Rates are Best Frame Rates</title>
          <description>&lt;p&gt;Over the course of development I’d been unhappy with the game running at 30fps, as it did not feel responsive enough. I’m a big believer that if a gamer features fast action gameplay and requires quick reactions then higher frame rates and lower response time are what is needed.&lt;/p&gt;

&lt;p&gt;Over the lifetime of the project I had beeb experimenting with running the game at a higher frame rate, as the maximum frame rate supported by Playdate SDK is 50fps. When I wrote my physics I did so in a way that it was not tied to one specific frame rate. Actually, it’s more correct to say that it is tied to the highest frame rate of 50fps but is done so in a way that it can be adapted to any frame rate.&lt;/p&gt;

&lt;p&gt;Anyway, after a round of optimisations and general improvements to the way I did both the skid marks (draw direct to background image rather as sprites) and sound effects (not attaching the whole sound engine to each object!) my game was running at max frame rate.&lt;/p&gt;

&lt;p&gt;So, I decided to see how high the game would run if I removed the frame limiter (which the SDK allows) and to my surprise my hame was running between 60 and 75fps. At this point I had the crazy idea of running my game at 60fps, because… why not? I wrote my own simple frame limiter (which would be more precise if the SDK allowed finer grained time reporting) and now the game runs faster and smoother than games should on Playdate.&lt;/p&gt;

&lt;p&gt;One interesting thing I noticed was that if I used my frame limiter code to limit the game to 50fps, I actually had a bunch more time, ~3ms, to do stuff per frame update than if I used the SDK frame limiter! I can only assume that every frame update the SDK frame limiter is doing something I am not doing.&lt;/p&gt;

&lt;p&gt;So, writing my own frame limiter clawed back some time from the SDK and also allowed me to go harder, better, faster, stronger. Double win!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 14 Jul 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/07/14/high-frame-rates/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/07/14/high-frame-rates/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Showreel</title>
          <description>&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-showreel.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Trying out some different things: a grab bag of scenarios.&lt;/p&gt;

&lt;p&gt;This long GIF shows several scenarios, different physics, and more! Essential viewing.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 04 Jul 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/07/04/showreel/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/07/04/showreel/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Chequered Flag</title>
          <description>&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-chequered-flag.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Bit of fun at lunch today.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 24 Jun 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/06/24/chequered-flag/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/06/24/chequered-flag/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Influences</title>
          <description>&lt;p&gt;Some fellow Playdate developers commented that at this point the game reminded them of &lt;em&gt;Ivan “Ironman” Stewart’s Super Off Road&lt;/em&gt;, &lt;em&gt;Super Sprint&lt;/em&gt; and even &lt;em&gt;Rocket League&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A few of my influences for vibe:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Atari’s &lt;em&gt;Sprint&lt;/em&gt; series, including &lt;em&gt;Badlands&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Ivan “Ironman” Stewart’s Super Off Road&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Wild Wheels&lt;/em&gt; (a &lt;a href=&quot;https://www.mobygames.com/game-group/ball-sports-with-vehicles&quot;&gt;car soccer game&lt;/a&gt; that pre-dates &lt;em&gt;Rocket League&lt;/em&gt; by ~20 years)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And for gameplay:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;ExciteBots: Trick Racing&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Mario Kart DS/Wii&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Power Drive&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Richard Burns Rally&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Italian Job: LA Heist&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Pro Rally&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Runabout&lt;/em&gt; series&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;GTi Club&lt;/em&gt; series&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;1080°&lt;/em&gt; series&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…so many more, some of which are not even driving games!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Thu, 18 Jun 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/06/18/influences/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/06/18/influences/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Off Road</title>
          <description>&lt;p&gt;&lt;strong&gt;6 June 2020.&lt;/strong&gt; Finally found and fixed a bug in my collision code - needed to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;math.abs()&lt;/code&gt; in one place to avoid some double negatives messing up collision rebound direction. D’oh!&lt;/p&gt;

&lt;p&gt;And now it’s time to Play Ball! I added a lap timer and a cool little LCD font.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-off-road.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9 June 2020.&lt;/strong&gt; The core flow of the game has been decided: there will be daily driving challenges with online leaderboards. So the game has found a name: Daily Driver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12 June 2020.&lt;/strong&gt; Video of gameplay footage and sound effects: &lt;a href=&quot;https://www.youtube.com/watch?v=I57JxBp4kBM&quot;&gt;www.youtube.com/watch?v=I57JxBp4kBM&lt;/a&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 06 Jun 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/06/06/off-road/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/06/06/off-road/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Gameplay</title>
          <description>&lt;p&gt;I have redone the collision/rebound physics, and car visual and steering is now affected when colliding - makes collisions feel really physical. Tyres and Cone obstacles have different collision properties so they feel different when you hit them. It’s a lot of fun to simply drive around and knock into things, which makes me confident I’m onto something good here.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-gameplay.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 June 2020.&lt;/strong&gt; I’m now happy enough with the handling, time to get some gameplay in here. At this point the game is running at the Playdate SDK default of 30fps.&lt;/p&gt;

&lt;p&gt;First playable demo released to Playdate Developer Preview group!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Fastest Time Challenge”&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;hit all 8 cones as quickly as possible&lt;/li&gt;
  &lt;li&gt;obstacles getting stuck under your car will slow you down!&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 31 May 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/05/31/gameplay/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/05/31/gameplay/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Obstacles</title>
          <description>&lt;p&gt;At this point I have three rows of rotated cars in the sprite sheet: each has wheels pointing in different directions.&lt;/p&gt;

&lt;p&gt;I’m currently doing the skid marks as sprites (which is why they disappear over time) partly because it was easy to get going, and partly to try to figure out some performance limits (~350 sprites in the GIF below, 25 of which are collidable and have my custom physics applied). Will be drawing skids marks straight to screen at some point in the future.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-obstacles.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Short term goal is to have some lap races/time-trials, plus a “driving test” or “stunt driving” mode which is what you’re seeing here.&lt;/p&gt;

&lt;p&gt;Codename is currently: Crank Turismo&lt;/p&gt;

&lt;p&gt;At this point you can’t hear my great synth-powered car engine and skidding sfx!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 30 May 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/05/30/obstacles/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/05/30/obstacles/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: from 3D to 2D</title>
          <description>&lt;p&gt;At this point, the only thing that remained of the prototype was the car sprite so I wondered about creating a new one myself. It uses 32 different images of the car with different rotations, making for smooth animation and movement on screen.&lt;/p&gt;

&lt;p&gt;Whilst I could draw all those frames by hand, I decided to go down a path that could produce assets on demand. That way if I change my mind I can reprocess the assets whenever I feel like it. The initial process was easy to setup, but I’ve been taking and simplifying the automation process ever since.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-from-3d-to-2d-a.png&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’d previously used OpenSCAD to create 3D models, so it was a natural and easy choice. Also, it’s the only 3D app I’ve ever used—not even Blender! Models are created using a definition language (think of it as a bit like CSS) where you can define shapes and how they interact. I also use the animation function to set the viewpoint and rotate the car whilst automatically saving the images.&lt;/p&gt;

&lt;p&gt;In OpenSCAD I lock the view angle and zoom. I tie rotation to the animation value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$t&lt;/code&gt;. Then I run the animation and click a box to have the app spit out all the rotated images for me.&lt;/p&gt;

&lt;p&gt;The output images need a little post-processing, so I use a single Retrobatch workflow to: crop, add transparency, invert, a few other things, and finally stitch the 32 images into one long sprite sheet. (On Windows you can use &lt;a href=&quot;http://photobat.clientside.jp&quot;&gt;Photobat&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-from-3d-to-2d-b.png#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, I run the sprite sheet through a bespoke dithering tool that allows for “live” manual tweaking to convert the greyscale images to 1-bit.&lt;/p&gt;

&lt;p&gt;That’s good enough for my current requirements. Later on I would want extra detail in the renders, either through texturing or by hand.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-from-3d-to-2d-c.png&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;29 May 2020. Soon after I would start rendering the wheels turning and after that the body so it rocks from side to side.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 27 May 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/05/27/from-3d-to-2d/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/05/27/from-3d-to-2d/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Physics</title>
          <description>&lt;p&gt;Now that I was convinced that a driving game could be fun, I was unhappy with the controls and very rudimentary “physics” that the car had. It just didn’t feel very real or compelling; there wasn’t enough depth to the control scheme.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-physics.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So I used &lt;a href=&quot;https://asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html&quot;&gt;Marco Monster’s Car Physics article&lt;/a&gt; (and looked at &lt;a href=&quot;https://github.com/search?q=2d+car+physics&amp;amp;type=repositories&quot;&gt;source code&lt;/a&gt; for various implementations of his technique) to implement more realistic car physics including drifting and skid marks. This was a bit of a watershed moment: this was the game I wanted to spend all my time on.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 24 May 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/05/24/physics/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/05/24/physics/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: Racer</title>
          <description>&lt;p&gt;The Collector prototype didn’t end up being as fun as I had hoped, but I thought it was interestingly that it felt a little like driving. So I wondered whether it would change the feeling by putting some different graphics on top of it. I ripped some temporary graphics from the Atari arcade game Badlands and added one simple collision detection rule and there it was! The first recognisable driving game that would serve as the basis for Daily Driver.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-racer.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;racer&quot;&gt;Racer&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Uses off screen collision map&lt;/li&gt;
  &lt;li&gt;Crank or buttons to steer&lt;/li&gt;
  &lt;li&gt;Buttons to accelerate&lt;/li&gt;
  &lt;li&gt;Variable maximum speeds&lt;/li&gt;
  &lt;li&gt;Lap counter with checkpoints&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 20 May 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/05/20/racer/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/05/20/racer/</guid>
        </item>
      
    
      
        <item>
          <title>Daily Driver: The Beginning</title>
          <description>&lt;p&gt;After receiving access to the Playdate SDK I took a while to read the docs, and play around with small code samples, eventually starting to create my own prototypes in April and May 2020. One prototype in particular was the genesis of Daily Driver.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/daily-driver-the-beginning.gif#playdate&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;collector&quot;&gt;Collector&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Everything is drawn using graphics primitives (circle, rect, line)&lt;/li&gt;
  &lt;li&gt;Pattern fills rather than pixel dithering&lt;/li&gt;
  &lt;li&gt;Crank or buttons to aim&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Fri, 01 May 2020 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2020/05/01/the-beginning/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2020/05/01/the-beginning/</guid>
        </item>
      
    

  </channel>
</rss>
