<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Get Info: #playdate</title>
    <description>Posts tagged “playdate” — 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/playdate/</link>
    <atom:link href="https://blog.gingerbeardman.com/tag/playdate/index.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 01 Jul 2026 16:09:47 +0000</pubDate>
    <lastBuildDate>Wed, 01 Jul 2026 16:09:47 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>

    
      
        <item>
          <title>Post-Playdate: One Year Later</title>
          <description>&lt;p&gt;It’s been a year since October 2024, when I released &lt;a href=&quot;/2024/10/08/bender-2-bend-harder-for-playdate/&quot;&gt;my last game on the Playdate Catalog&lt;/a&gt;. Over the course of that month, I became increasingly concerned about an undercurrent of threatening and antisocial behaviour from one particular developer in the community—my frequent reports of which went unanswered. Eventually—exactly a year ago today—that tension surfaced as a campaign of targeted harassment. I was assured something would be done about it, but sadly, nothing ever was.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2025/04/15/when-playdate-stopped-being-fun/&quot;&gt;So I left the Playdate community&lt;/a&gt;. Over the following months, I faced various forms of harassment: doxxing, unsavoury DMs, spam campaigns targeting my email, cell phone, and social media accounts, unfounded accusations—the list goes on. I can tell you, &lt;em&gt;this is not my idea of a fun time.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You might think that after a year this would be old news. Sadly, it’s not. I’ve long since moved on with my life and kept creating—for platforms as exotic as the &lt;a href=&quot;/2025/01/05/dream-ride-for-sega-dreamcast-and-emulators/&quot;&gt;Sega Dreamcast&lt;/a&gt; and as familiar as &lt;a href=&quot;/2025/08/21/wormhole-for-perplexity-comet/&quot;&gt;the web&lt;/a&gt;. I’m proud of what I’ve achieved, and of the choices that allow me to keep doing what I do best, regardless of the people who try to put barriers in my way.&lt;/p&gt;

&lt;p&gt;While I continued creating &lt;a href=&quot;/2025/05/09/atari-jeff-minter-game-results-jam-llamasoft-and-st-format/&quot;&gt;award-winning games&lt;/a&gt;, the chance to build the game of my dreams came along—made easier by being an employee at a company that would handle funding, marketing, and the practicalities, freeing me to focus on doing my best work. It was a decision I didn’t take lightly, both because of potential ethical concerns and the fear of further harassment. I sought advice from close friends and had thoughtful discussions with several of them. One friend in particular told me they hadn’t heard of the company, so we didn’t discuss it further. That was back in May.&lt;/p&gt;

&lt;p&gt;I announced my first work with the company in July—again, this friend said nothing. Then, in mid-October, &lt;a href=&quot;https://www.linkedin.com/posts/mattsephton_super-happy-to-share-that-ive-joined-perplexity-activity-7384626318224617472-ZQJ-&quot;&gt;I announced my new permanent position&lt;/a&gt;, and that same friend suddenly went off the deep end. I asked what had changed since May, when they hadn’t heard of the company, and why nothing was said in July, when I first went public with my new work—yet now, in October, they were suddenly so angry. Their reply:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“One of my friends saw your post and sent it to me saying ‘oh no,’ which is how I found out. And then another friend (who you also know) also said ‘oh nooooooo.’”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, essentially, this friend had been incited into anger by others—the same social dynamic that fuelled the harassment a year ago. And the worst part? Those two mutual friends are key figures in the Playdate scene—the very people who once promised to address the harassment are now perpetuating it, a year on. I need to find a stronger word than &lt;em&gt;disappointing&lt;/em&gt;, but that will have to do for now.&lt;/p&gt;

&lt;p&gt;At this point, I’m letting things run their course and will continue to do what I do best: make games. I’m looking forward to you being able to play the next one.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Plus ça change—the more things change, the more they stay the same.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Have fun,&lt;br /&gt;
matt&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 29 Oct 2025 12:12:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/10/29/post-playdate-one-year-later/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/10/29/post-playdate-one-year-later/</guid>
        </item>
      
    
      
        <item>
          <title>Donating my Playdate earnings to The Cybersmile Foundation</title>
          <description>&lt;p&gt;I’ve decided to donate all of my &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; earnings (my personal take-home, after &lt;a href=&quot;https://panic.com&quot;&gt;Panic&lt;/a&gt; and &lt;a href=&quot;https://stripe.com&quot;&gt;payment processor&lt;/a&gt; fees) to &lt;a href=&quot;https://www.cybersmile.org&quot;&gt;The Cybersmile Foundation&lt;/a&gt;, starting July 2025.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Cybersmile is a multi-award-winning nonprofit dedicated to digital wellbeing and combating all forms of cyberbullying and online abuse. It’s registered as a charity in the UK and as a 501(c)3 nonprofit organization in the US.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having experienced online abuse myself—&lt;a href=&quot;/2025/04/15/when-playdate-stopped-being-fun/&quot;&gt;and received so little support that it ultimately drove me away from Playdate as a platform&lt;/a&gt;—this feels like the most constructive and meaningful way to turn that experience into something positive. I’d love to donate the full amount these games generate, but I can only give what actually reaches me.&lt;/p&gt;

&lt;p&gt;Be excellent to each other.&lt;/p&gt;

&lt;p&gt;Peace and love,&lt;br /&gt;
matt&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/cybersmile.png&quot; alt=&quot;IMG&quot; title=&quot;Receipt for donation to The Cybersmile Foundation&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 02 Aug 2025 11:18:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/08/02/donating-my-playdate-earnings-to-the-cybersmile-foundation/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/08/02/donating-my-playdate-earnings-to-the-cybersmile-foundation/</guid>
        </item>
      
    
      
        <item>
          <title>Abandoned project: Band-kun/Beatnik for Playdate</title>
          <description>&lt;p&gt;Back in November 2020 I was exploring the &lt;a href=&quot;https://en.wikipedia.org/wiki/PC-98&quot;&gt;PC-98&lt;/a&gt; back catalog, and stumbled across a strange game called &lt;a href=&quot;https://www.mobygames.com/game/155798/band-kun/&quot;&gt;Band-kun&lt;/a&gt;. Strange in that it’s a 1-bit black and white game on a platform that was capable of colour, and also because it’s a hybrid adventure/management/music/rhythm game. &lt;a href=&quot;/2020/12/16/band-kun-musician-simulator-1990-koei/&quot;&gt;I was smitten&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;carousel__holder&quot;&gt;
    &lt;div id=&quot;carousel0&quot; class=&quot;carousel&quot;&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel0&quot; id=&quot;0a&quot; checked=&quot;checked&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel0&quot; id=&quot;0b&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel0&quot; id=&quot;0c&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel0&quot; id=&quot;0d&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel0&quot; id=&quot;0e&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel0&quot; id=&quot;0f&quot; /&gt;
        
          &lt;input class=&quot;carousel__activator&quot; type=&quot;radio&quot; name=&quot;carousel0&quot; id=&quot;0g&quot; /&gt;
        
        
          
          
          
          
          &lt;div class=&quot;carousel__controls&quot;&gt;
              &lt;label class=&quot;carousel__control carousel__control--backward&quot; for=&quot;0g&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;0b&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;0a&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;0c&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;0b&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;0d&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;0c&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;0e&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;0d&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;0f&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;0e&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;0g&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;0f&quot;&gt;&lt;/label&gt;
              &lt;label class=&quot;carousel__control carousel__control--forward&quot; for=&quot;0a&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/band-kun-01.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-01.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/band-kun-02.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-02.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/band-kun-03.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-03.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/band-kun-04.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-04.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/band-kun-05.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-05.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/band-kun-06.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-06.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/band-kun-07.png&apos;);&quot;&gt;&lt;img class=&quot;carousel__staticimage&quot; src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-07.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;0a&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;0b&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;0c&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;0d&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;0e&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;0f&quot;&gt;&lt;/label&gt;
            
              &lt;label class=&quot;carousel__indicator&quot; for=&quot;0g&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: 62.5%; margin: 1rem 0 1rem;}
.carousel {
  height: 100%;
  width: 100%;
  overflow: hidden;
  text-align: center;
  position: absolute;
  padding: 0;
}
.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__activator:nth-of-type(6):checked ~ .carousel__track {
  -webkit-transform: translateX(-500%);
          transform: translateX(-500%);
}
.carousel__activator:nth-of-type(6):checked ~ .carousel__slide:nth-of-type(6) {
  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(6):checked ~ .carousel__controls:nth-of-type(6) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(6):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(6) {
  opacity: 1;
}

.carousel__activator:nth-of-type(7):checked ~ .carousel__track {
  -webkit-transform: translateX(-600%);
          transform: translateX(-600%);
}
.carousel__activator:nth-of-type(7):checked ~ .carousel__slide:nth-of-type(7) {
  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(7):checked ~ .carousel__controls:nth-of-type(7) {
  display: block;
  opacity: 1;
}
.carousel__activator:nth-of-type(7):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(7) {
  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__track .carousel__slide:nth-of-type(6) {
  -webkit-transform: translateX(500%) translateZ(0);
          transform: translateX(500%) translateZ(0);
}

.carousel__track .carousel__slide:nth-of-type(7) {
  -webkit-transform: translateX(600%) translateZ(0);
          transform: translateX(600%) 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;
}

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

.carousel__slide:nth-of-type(7),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type(7 {
  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;#carousel0 .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;hr /&gt;

&lt;h2 id=&quot;the-idea&quot;&gt;The Idea&lt;/h2&gt;

&lt;p&gt;I was already developing for the &lt;a href=&quot;https://play.date/&quot;&gt;Playdate&lt;/a&gt;, a modern 1-bit platform, and it struck me as the perfect opportunity to create a remaster of the game. So, I set my sights on securing the licence. The original title was developed by Koei—Japan’s equivalent to Electronic Arts—a giant now known as Koei Tecmo, whose owners are worth hundreds of billions. The game itself is credited to one of those owners: Yoichi Erikawa, who used the pen name Ko Shibusawa. I had a feeling this wasn’t going to be easy.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/band-kun-playdate.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I published a thread about the game and my prototype &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1339232766861451278&quot;&gt;on Twitter&lt;/a&gt;, introducing the game to the West, which remains my most popular post and thread of all-time (~20 years, for me) at over 450K impressions. I also mirrored the thread as a &lt;a href=&quot;/2020/12/16/band-kun-musician-simulator-1990-koei/&quot;&gt;blog post&lt;/a&gt; which is no doubt an easier read.&lt;/p&gt;

&lt;p&gt;The idea for the remaster was to not remake the original game at all, but rather to take the vibe and bring it into the modern era. So there were no plans to use the original music, or perhaps even the &lt;a href=&quot;/2021/01/03/extracting-images-from-band-kun/&quot;&gt;original graphics&lt;/a&gt;. I would leverage modern device capabilities and input methods to create a totally new experience a million miles away from the limited original game from 1990.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;challenge-accepted&quot;&gt;Challenge Accepted&lt;/h2&gt;

&lt;p&gt;A couple of days later, 18th December 2020, &lt;a href=&quot;https://cabel.com&quot;&gt;Cabel Sasser&lt;/a&gt; of &lt;a href=&quot;https://panic.com&quot;&gt;Panic&lt;/a&gt; reached out:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Hey Matt, I love a good challenge—do you want me to actually try to make contact with someone at Koei? Regarding Band-Kun? (Philosophically, the idea of reviving a long-forgotten PC Koei title is just enormously interesting to me ahhahah)”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By 25th December 2020—thanks Santa!—Panic’s guy in Japan, Noby, had a breakthrough finding contact details and sent an email to Koei. Their reply came quickly on 27th December 2020 when a lovely person in Koei’s IP management department replied:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;they knew of the Playdate and were waiting patiently to pre-order&lt;/li&gt;
  &lt;li&gt;they had the licence for the Band-kun game (music to my ears!)&lt;/li&gt;
  &lt;li&gt;there was an outstanding question about the music rights&lt;/li&gt;
  &lt;li&gt;and some remaining business questions for Panic to answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project then became in need of a budget to be set: for both the licence and for me to build it. Even with this discussion happening in late December 2020, it was suggested that the game could feature in Season 2. And that, unfortunately, was the last I heard of it. Sad, and surprising, but true.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Not one to give up so easily I submitted the game twice, kind of, in pitches to Panic.&lt;/p&gt;

&lt;h2 id=&quot;open-pitch-band-kun&quot;&gt;Open Pitch: Band-kun&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;June 2022&lt;/li&gt;
  &lt;li&gt;to reignite the licence discussion&lt;/li&gt;
  &lt;li&gt;a reimagining of the original game for a modern device&lt;/li&gt;
  &lt;li&gt;as a team effort with musician and tools programmers; to hit a deadline&lt;/li&gt;
  &lt;li&gt;suggested budget was included&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cdn.gingerbeardman.com/files/playdate-pitch-2022-band-kun.pdf&quot;&gt;pitch pdf&lt;/a&gt; (191 KB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://cdn.gingerbeardman.com/files/playdate-pitch-2022-band-kun.pdf&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/abandoned-playdate-band-kun.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;On 1st May 2024 I sent a message to Panic stating that I was still interested in making the game and to check the status of the project, but never received a reply.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;season-2-pitch-beatnik&quot;&gt;Season 2 Pitch: Beatnik&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;July 2024&lt;/li&gt;
  &lt;li&gt;an all-new game&lt;/li&gt;
  &lt;li&gt;let’s forget about the IP&lt;/li&gt;
  &lt;li&gt;completely revised and redesigned&lt;/li&gt;
  &lt;li&gt;as a solo endeavour; I was confident I could complete the new scope myself&lt;/li&gt;
  &lt;li&gt;no budget was required&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cdn.gingerbeardman.com/files/playdate-pitch-2024-beatnik.pdf&quot;&gt;pitch pdf&lt;/a&gt; (185 KB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://cdn.gingerbeardman.com/files/playdate-pitch-2024-beatnik.pdf&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/abandoned-playdate-beatnik.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;gono-go&quot;&gt;Go/no-go&lt;/h2&gt;

&lt;p&gt;Neither of the pitches were successful. There was no further discussion of the licence from 2020.&lt;/p&gt;

&lt;p&gt;Regardless, I decided that I would continue working on the game and release it myself. This was the big project I was working on pretty much exclusively from May 2024 to &lt;a href=&quot;/2025/04/15/when-playdate-stopped-being-fun/&quot;&gt;October 2024&lt;/a&gt;. You can see elements of it in my &lt;a href=&quot;/2025/03/11/old-codes-new-releases-for-playdate/&quot;&gt;New World&lt;/a&gt; and &lt;a href=&quot;https://gingerbeardman.itch.io/new-world&quot;&gt;Band-o-matic&lt;/a&gt; tech demos.&lt;/p&gt;

&lt;p&gt;In August 2024, I took a two-week break from working on Beatnik to develop &lt;a href=&quot;/2024/10/08/bender-2-bend-harder-for-playdate/&quot;&gt;Bender 2: Bend Harder&lt;/a&gt;, an expanded version of a game I originally made in 2012. As part of that project, I integrated some of the tooling and library code I had built for Beatnik to test and validate a few of the technologies and concepts. &lt;a href=&quot;/2025/04/15/when-playdate-stopped-being-fun/&quot;&gt;And that was the end of that&lt;/a&gt;.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 11 May 2025 16:58:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/05/11/abandoned-project-bandkun-beatnik-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/05/11/abandoned-project-bandkun-beatnik-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>Abandoned project: Soccer for Playdate</title>
          <description>&lt;p&gt;On September 30th, 2023, I was sitting in the cinema &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1708437276756214147&quot;&gt;waiting for an anniversary screening&lt;/a&gt; of Talking Heads &lt;em&gt;“&lt;a href=&quot;https://en.wikipedia.org/wiki/Stop_Making_Sense&quot;&gt;Stop Making Sense&lt;/a&gt;”&lt;/em&gt; to begin when I got a message from &lt;a href=&quot;https://panic.com&quot;&gt;Panic&lt;/a&gt;. They asked if I was familiar with Sensible Soccer. Naturally, I was—I’ve played it since its release in 1992, as man and boy. Back in 2000, I’d even made my own arcade-style take on it called Simple Soccer, and in 2002 I designed an &lt;a href=&quot;/2002/05/23/sensible-soccer-t-shirt/&quot;&gt;official Sensible Soccer T-shirt&lt;/a&gt; that was sold on the high street. Panic had no idea about any of this, but luck works in funny ways sometimes.&lt;/p&gt;

&lt;p&gt;Panic’s idea was to make a soccer game for &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt;, tied in as a promotional companion to the soccer-infused first-person slice-of-life game &lt;a href=&quot;https://en.wikipedia.org/wiki/Despelote&quot;&gt;Despelote&lt;/a&gt; (out today—1st May, 2025). Very cool.&lt;/p&gt;

&lt;p&gt;I was super keen, as you’d imagine, and quickly got to work on a &lt;a href=&quot;https://gingerbeardman.itch.io/prototypes-for-playdate&quot;&gt;prototype&lt;/a&gt; (download at the link). It let you move around the pitch, zoom in and out, and switch between different pitch surfaces. I sent it off and asked for a budget and scope. With the expectation set that the project was going forwards and while waiting for a reply, I kept going: I got the ball moving (using the same sort of ball movement trick I first solved 25 years ago and more recently reused for &lt;a href=&quot;/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/&quot;&gt;Fore! Track&lt;/a&gt;), and had two AI-controlled teams running around the pitch, tracking the ball using the classic Sensible Soccer &lt;a href=&quot;https://readonlymemory.com/the-making-of-sensible-soccer/&quot;&gt;grid system&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But I never heard back about the budget. So I stopped—just before adding goal-scoring. It would’ve been so cool.&lt;/p&gt;

&lt;p&gt;Lesson learned. I now always ask for a contract upfront and of course don’t do spec work. If a client wants me to build something, they need to commit—and at the very least, meet me on the halfway line.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/soccer-playdate.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Thu, 01 May 2025 14:34:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/05/01/abandoned-project-soccer-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/05/01/abandoned-project-soccer-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>When Playdate Stopped Being Fun</title>
          <description>&lt;p&gt;For almost six years, &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; (a little handheld gaming device with a crank input and 1-bit screen) was a space where I did some of my most creative and personal work. I embraced its quirks, shipped over twenty releases, and explored what a tiny, constrained device could do. It brought me joy, challenge, and a sense of purpose.&lt;/p&gt;

&lt;p&gt;But over time, that feeling changed. Following a difficult incident in October 2024 involving targeted harassment—admitted to by &lt;!-- Scizzorz --&gt;the developer responsible, but met with no meaningful follow-up from the community moderators and no reply at the time of writing from &lt;a href=&quot;https://www.panic.com&quot;&gt;Panic&lt;/a&gt;—I began to feel increasingly disconnected from the scene. I gave it time, hoped things might shift, but ultimately the silence made it hard to keep building with the same heart.&lt;/p&gt;

&lt;p&gt;So, I’m stepping back. Not dramatically—just with clarity. Right now, this isn’t the environment I can thrive in. If that changes, I’d be happy to return. For now, I want to leave a record of what I made. I’m proud of all of it. I’ll continue as a full-time indie developer, but in another castle.&lt;/p&gt;

&lt;p&gt;Be excellent to each other. And have fun.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;released-projects&quot;&gt;Released Projects&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2022-03&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/today&quot;&gt;Today&lt;/a&gt; (the first Playdate thing sold on itch.io)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2022-04&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/horse-race&quot;&gt;Horse Race&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2022-04&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/audition&quot;&gt;Audition&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2022-04&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/prototypes-for-playdate&quot;&gt;Prototypes&lt;/a&gt; multi-pack&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2022-04&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/daily-driver&quot;&gt;Daily Driver&lt;/a&gt; (tech demo)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2022-05&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/circular&quot;&gt;Circular&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2022-05&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/st-din-playdate-font&quot;&gt;ST-DIN&lt;/a&gt; and &lt;a href=&quot;https://gingerbeardman.itch.io/supermini-playdate-font&quot;&gt;Supermini&lt;/a&gt; fonts&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-04&lt;/code&gt; &lt;a href=&quot;https://play.date/games/sparrow-solitaire/&quot;&gt;Sparrow Solitaire&lt;/a&gt; (with &lt;a href=&quot;https://vogelscript.itch.io&quot;&gt;Mac Vogelsang&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-05&lt;/code&gt; &lt;a href=&quot;/2023/05/10/piskel-for-playdate/&quot;&gt;Piskel for Playdate&lt;/a&gt; (desktop pixel art app)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-07&lt;/code&gt; &lt;a href=&quot;https://github.com/gingerbeardman/mandala&quot;&gt;Mandala&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-08&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/strategies&quot;&gt;Strategies&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-08&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/datediff&quot;&gt;DateDiff&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-08&lt;/code&gt; &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;YOYOZO&lt;/a&gt; (“Best Games of 2023”, &lt;a href=&quot;https://arstechnica.com/gaming/2023/12/ars-technicas-best-video-games-of-2023/7&quot;&gt;Ars Technica&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-10&lt;/code&gt; &lt;a href=&quot;https://mouflon-cloud.itch.io/kye&quot;&gt;Kye&lt;/a&gt; (with &lt;a href=&quot;https://mouflon-cloud.itch.io&quot;&gt;Jan Martinek&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-10&lt;/code&gt; &lt;a href=&quot;https://play.date/games/icarus/&quot;&gt;Super ICARUS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-12&lt;/code&gt; &lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;Fore! Track&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-12&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/see-the-sky&quot;&gt;See the Sky&lt;/a&gt; (with &lt;a href=&quot;https://twitter.com/thoruman&quot;&gt;Thoru Yamamoto&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2024-05&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/rorschach&quot;&gt;Rorschach&lt;/a&gt; (featuring mouse control)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2024-10&lt;/code&gt; &lt;a href=&quot;https://play.date/games/bender-2-bend-harder/&quot;&gt;Bender 2: Bend Harder&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2025-03&lt;/code&gt; &lt;a href=&quot;https://gingerbeardman.itch.io/shark-turtle&quot;&gt;Shark Turtle&lt;/a&gt;, originally created &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2023-06&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-teaser.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;cancelled-projects&quot;&gt;Cancelled Projects&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Aero Club&lt;/strong&gt;—Flight school simulation inspired by Mode 7 and Pilotwings&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Beatnik&lt;/strong&gt;—Rhythm game with interactive music and beat-matched dynamic visuals&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cosmic Trash&lt;/strong&gt;—Arcade action puzzle with realtime ray-traced graphics&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Daily Driver&lt;/strong&gt;—60fps arcade racing with pre-rendered graphics (reborn as &lt;a href=&quot;/2025/01/05/dream-ride-for-sega-dreamcast-and-emulators/&quot;&gt;Dream Ride&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Koatari&lt;/strong&gt;—Pachinko-style arcade game with pinball-inspired visuals, running up to 120fps&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Mole Hole&lt;/strong&gt;—A modern remake of Thoru Yamamoto’s escape-maze concept&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/molehole.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Daily Driver&lt;/strong&gt; won “Outstanding Technical Achievement (SDK)” at the&lt;a href=&quot;https://playdate-wiki.com/wiki/The_2022_Playdate_Community_Awards&quot;&gt; 2022 Community Awards&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sparrow Solitaire&lt;/strong&gt; won “Best Puzzle Game” at the &lt;a href=&quot;https://playdate-wiki.com/wiki/The_2023_Playdate_Community_Awards&quot;&gt;2023 Community Awards&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;YOYOZO&lt;/strong&gt; won “Best Arcade Game” at the &lt;a href=&quot;https://playdate-wiki.com/wiki/The_2023_Playdate_Community_Awards&quot;&gt;2023 Community Awards&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;YOYOZO&lt;/strong&gt; was named one of the “Best Games of 2023” by &lt;a href=&quot;https://arstechnica.com/gaming/2023/12/ars-technicas-best-video-games-of-2023/7&quot;&gt;Ars Technica&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Bender 2: Bend Harder&lt;/strong&gt; was nominated for “Best Arcade Game” at the &lt;a href=&quot;https://playdate-wiki.com/wiki/The_2024_Playdate_Community_Awards&quot;&gt;2024 Community Awards&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;
&lt;em&gt;Thanks to JM, JS, MR, PP, SC for feedback on versions of this post.&lt;/em&gt;
&lt;br /&gt;
&lt;em&gt;Panic were sent a draft of this post ahead of publication.&lt;/em&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 15 Apr 2025 21:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/04/15/when-playdate-stopped-being-fun/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/04/15/when-playdate-stopped-being-fun/</guid>
        </item>
      
    
      
        <item>
          <title>Old Codes, New Releases for Playdate</title>
          <description>&lt;p&gt;As I sadly move away from game dev on Playdate, I’ve released a couple of interesting old things…&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;shark-turtle&quot;&gt;SHARK TURTLE&lt;/h2&gt;

&lt;p&gt;This is a version of &lt;a href=&quot;https://en.wikipedia.org/wiki/SameGame&quot;&gt;SameGame&lt;/a&gt; (さめがめ) originally released under the name &lt;a href=&quot;https://blog.gingerbeardman.com/2023/05/24/ordering-photocopies-from-japans-national-library/&quot;&gt;CHAIN SHOT&lt;/a&gt; in 1985 by Kuniaki “Morisuke” Moribe for Fujitsu FM-8 home computer. The concept is as old as Tetris, perhaps even older, and at one time SameGame in its many guises was more popular than Tetris in Japan. One version had an active &lt;a href=&quot;/2023/05/04/macigame-user-created-graphics/&quot;&gt;modding scene&lt;/a&gt; and some had &lt;a href=&quot;/2023/08/19/fake-steve-jobs-and-letters-from-bill-g/#samegame&quot;&gt;strategy guide books&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It was submitted and rejected in the first wave of submissions to Playdate Catalog. At one point the plan was to wrap this in a theme/concept and give it the same sort of love that resulted in the great experiences of my other games like YOYOZO, Fore! Track, Bender 2: Bend Harder, but for one reason or another it never happened.&lt;/p&gt;

&lt;p&gt;What is here is a fully-playable, feature-rich, albeit “no frills” version of SameGame with fast updates, solid controls, and great music. It’s a lot of fun and great to play little-by-little when you have a spare moment, as you dictate the pace and progress of the game turn-by-turn.&lt;/p&gt;

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

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/old-playdate-shark-turtle.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;new-world&quot;&gt;NEW WORLD&lt;/h2&gt;

&lt;p&gt;This is an interactive music track where you’re the DJ. You control various instruments and vocals, doing live mixing of the track. It’s really fun to play with!&lt;/p&gt;

&lt;p&gt;It is a technology demo that was put together as part of a pitch for a game. Whilst the game never came to be, an improved version of this technology went on to be used in Bender 2: Bend Harder for the dynamic background patterns that animate to the beat of the music.&lt;/p&gt;

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

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/old-playdate-new-world.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;more-games&quot;&gt;More games!&lt;/h2&gt;

&lt;p&gt;All my Playdate games:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;YOYOZO&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;read about it being a &lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;GOTY 2023&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/sparrow-solitaire/&quot;&gt;Sparrow Solitaire&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;read the &lt;a href=&quot;/2023/04/13/sparrow-solitaire-for-playdate/&quot;&gt;making of&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/bender-2-bend-harder/&quot;&gt;Bender 2: Bend Harder&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;read the &lt;a href=&quot;/2024/10/08/bender-2-bend-harder-for-playdate/&quot;&gt;making of&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;Fore! Track&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;read the &lt;a href=&quot;/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/&quot;&gt;making of&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/icarus/&quot;&gt;Super ICARUS&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;read about its &lt;a href=&quot;/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/&quot;&gt;dynamic music&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 11 Mar 2025 16:24:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/03/11/old-codes-new-releases-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/03/11/old-codes-new-releases-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>Bender 2: Bend Harder for Playdate</title>
          <description>&lt;p&gt;Back in 2012 I wanted to make a quick game for the &lt;a href=&quot;https://twitter.com/agbicjam&quot;&gt;AGBIC&lt;/a&gt; game jam (A Game By Its Cover; make a game inspired by the imaginary cover art from the &lt;a href=&quot;http://famicase.com&quot;&gt;Famicase&lt;/a&gt; exhibition). There’s only one rule for this game jam, which is taken very seriously: &lt;em&gt;respect the wishes of the original artists, and don’t appropriate their designs without consent&lt;/em&gt;. Otherwise you’re free to do what you want. And you can choose a cover as inspiration for your game from the entire archive of Famicase entries, so every year there are more and more great covers to be inspired by.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2012&quot;&gt;2012&lt;/h2&gt;

&lt;p&gt;I had a browse of the Famicase entries and picked artwork titled &lt;a href=&quot;https://famicase.com/11/softs/12.html&quot;&gt;Bender&lt;/a&gt;, by 清水昭利 Akitoshi Shimizu, from the 2011 submissions, and set to work… thinking. One morning I had the crazy idea to &lt;a href=&quot;/2024/09/28/a-haze-of-inspiration/&quot;&gt;impose severe constraints to help my ideation&lt;/a&gt;: I should make the game using only CSS transitions (and some simple JavaScript logic to control the game state and flow). I have no idea why this seemed like a good idea, but it worked!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/bender-2-famicase-2011.jpg&quot; alt=&quot;IMG&quot; title=&quot;Bender Famicase by Akitoshi Shimizu, 2011&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The result of &lt;a href=&quot;https://twitter.com/gingerbeardman/status/227040932028108800&quot;&gt;two days work&lt;/a&gt; was a web game called Bender released 12th July 2012. It was fun to make and devilishly addictive. The &lt;a href=&quot;https://www.gingerbeardman.com/bender/&quot;&gt;web game&lt;/a&gt; still works today, on desktop and mobile, though the controls are prone to getting stuck from time to time for reasons I’ve not looked into.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.gingerbeardman.com/bender/&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/bender-2-web-2012.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2022&quot;&gt;2022&lt;/h2&gt;

&lt;p&gt;Fast forward 10 years and I decided to do a pretty straight port of the web game to the &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; handheld, that was 20th May 2022. I coded it from memory/feel, just for kicks, not that I had any choice as the available technologies are so different. It was a very bare bones game, with no music and messy code, but it was a test to see if I could release a game in a day. I did.&lt;/p&gt;

&lt;p&gt;Seeing as original Bender web game used CSS for all graphics and animation, and a little JavaScript to control the game flow, I had wondered how I would build Bender with the Playdate SDK? I settled on using only primitives and dither patterns to draw all the elements, an approach that I have continued to refine in subsequent games such as my &lt;a href=&quot;https://arstechnica.com/gaming/2023/12/ars-technicas-best-video-games-of-2023/7&quot;&gt;GOTY 2023 accolade&lt;/a&gt; and &lt;a href=&quot;https://play.date/games/community-awards-2023-arcade/&quot;&gt;award winner&lt;/a&gt; &lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;YOYOZO&lt;/a&gt;, the detailed rendered look of &lt;a href=&quot;/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/&quot;&gt;Fore! Track&lt;/a&gt;, or the stark minimalism of &lt;a href=&quot;https://play.date/games/icarus/&quot;&gt;Super ICARUS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each side of the bar is a thick line, with “butt” cap-style. To give the illusion of the bar bending I draw a circle at the origin or common point of both lines. A little bit of trigonometry was all that was needed to get the lines bending correctly. The benefits to this approach are many, such as: no images, no sprites, always smooth edges, better performance. Animation also does not use the Playdate SDK, but rather I track the positions of screen elements and move them based on some rules and algorithms.&lt;/p&gt;

&lt;p&gt;I use dithering to provide visual effects: animation of snapping and motion blur of the moving line. There’s also a zen mode that disables visual effects and the toasting words (which are randomised from large lists).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/bender-2-playdate-2022.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2023&quot;&gt;2023&lt;/h2&gt;

&lt;p&gt;I submitted Bender for consideration for &lt;a href=&quot;https://play.date/games/catalog/&quot;&gt;Playdate Catalog&lt;/a&gt; when the service launched, but it was rejected for being “too simple”. After some lengthy contemplation my takeaway from the rejection was that it meant there was not enough meat on the bones of the game, rather than anything specific against the central gameplay mechanic which is beautifully simple.&lt;/p&gt;

&lt;p&gt;Fair point. Though I did say I would expand the game, add music and so on, I learned that it’s difficult for people to imagine how you’re going to expand on a simple concept. You need to show them. It took many months—about a year and a half—for me to find the time and energy to revisit Bender.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2024&quot;&gt;2024&lt;/h2&gt;

&lt;p&gt;The end result is &lt;em&gt;Bender 2: Bend Harder&lt;/em&gt;. The new game has been remade, from scratch, right down to the fonts. And it’s packed with new things: friendlier gameplay, more obvious goals, multiple game modes, a deeper scoring system, awards/achievements, multiple secrets/easter eggs, online score boards, improved graphics, dynamic music and sound including elements that react to the beat of the music, an imaginary scenario to provide a bit of world-building, and much more besides! At ~2000 lines the source code is over 4 times the length of the first version’s ~500 lines, just to give you an idea of how much more logic there is in the new game. Not that lines of code is a good metric, but you get my point. There is much new!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There are elements to the game that I would usually disclose the technical details of—my process, tooling—but I’m currently doing my utmost to protect my “vibe” so that won’t happen today. This is why we can’t have nice things. Maybe at some point in the future, but no promises.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anyway, I’m really happy how this game has turned out. Can’t wait to see some activity on the leaderboards! Get it at &lt;a href=&quot;https://play.date/games/bender-2-bend-harder/&quot;&gt;play.date/games/bender-2-bend-harder/&lt;/a&gt; and I’ll see you in the high scores!&lt;/p&gt;

&lt;p&gt;As usual, promo artwork is by &lt;a href=&quot;https://www.instagram.com/vxclhd/&quot;&gt;vxcl&lt;/a&gt; with 1-bit equivalents drawn by me.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“If at first you don’t succeed, try, try again.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/bender-2-bend-harder.gif#playdate&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;my-other-games-for-playdate&quot;&gt;My other games for Playdate&lt;/h2&gt;

&lt;p&gt;This brings my total of games on Playdate Catalog to 5, the others being:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;YOYOZO&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;Best Games of 2023 (Ars Technica)&lt;/li&gt;
      &lt;li&gt;Best Arcade Game (Playdate Community Awards 2023)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/04/13/sparrow-solitaire-for-playdate/&quot;&gt;Sparrow Solitaire&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;Best Puzzle Game (Playdate Community Awards 2023)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/&quot;&gt;Fore! Track&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;the first &lt;a href=&quot;/2023/07/09/the-first-colour-playdate-game/&quot;&gt;colour&lt;/a&gt; Playdate game?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/icarus/&quot;&gt;Super ICARUS&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;an innovative hybrid survival-racing game&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also have a bunch of other games at &lt;a href=&quot;https://gingerbeardman.itch.io&quot;&gt;gingerbeardman.itch.io&lt;/a&gt; for both Playdate and Mac/Windows.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 08 Oct 2024 17:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2024/10/08/bender-2-bend-harder-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2024/10/08/bender-2-bend-harder-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>Mouse Support for Playdate</title>
          <description>&lt;p&gt;Since some of my first prototypes with Playdate I’ve wanted there to be a way to interact with the device using a mouse. Well, today is that day!&lt;/p&gt;

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

&lt;p&gt;No doubt you have some questions? How are you doing this? What’s the weather like? etc.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;p&gt;A custom &lt;a href=&quot;https://www.hammerspoon.org&quot;&gt;Hammerspoon&lt;/a&gt; script monitors mouse coordinates and sends them to the Playdate over serial connection. The game receives those messages through the recently added API &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;playdate.serialMessageReceived(message)&lt;/code&gt;, parses them, and acts accordingly.&lt;/p&gt;

&lt;p&gt;Currently the script sends messages such as:&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;msg mouse on
msg 0,0
msg -19,84
msg mouse off
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I use those to trigger on screen notifications, reset the game state, and move the cursor.&lt;/p&gt;

&lt;p&gt;Note: use of serial means you can’t have the Playdate Simulator or Mirror open when trying this. If you invoke the script and it can’t open the serial connection for any reason it will sound an error beep.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why&quot;&gt;Why?&lt;/h2&gt;

&lt;p&gt;Originally, back in 2020, I wanted to be able to position items in the in-game level editor of &lt;a href=&quot;/tag/dailydriver/&quot;&gt;my game Daily Driver&lt;/a&gt; more directly, using a mouse rather than typing co-ordinates into a file. Then I forgot about it for a while. Then in &lt;a href=&quot;https://sdk.play.date/changelog/#_2_4_0&quot;&gt;&lt;em&gt;Playdate SDK 2.4.0&lt;/em&gt;&lt;/a&gt; (March 1, 2024) a new API was added that made me wonder if it was now possible.&lt;/p&gt;

&lt;p&gt;I knew the &lt;a href=&quot;https://itch.io/jam/uncrankdjam&quot;&gt;Uncrank’d Game Jam&lt;/a&gt; was in progress and was thinking about what to do, and then the mouse control idea came back to me. I first put together the bare bones of an app that drew a dot to the screen, mirrored both horizontally and vertically. Then I set about proving I could forward mouse events, which resulted in a &lt;a href=&quot;https://github.com/gingerbeardman/playdate-mouse-support/blob/main/mouse/mouse_to_playdate.lua&quot;&gt;small Hammerspoon script&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After that, I built out the drawing app and that’s how &lt;a href=&quot;https://gingerbeardman.itch.io/rorschach&quot;&gt;Rorschach&lt;/a&gt; came to be. It was originally built to showcase the mouse control, but has since evolved into a lovely little toy of its own. I thought a drawing app would be an easy-to-understand visual showcase for this innovation. But, really, the possibilities for its use are endless.&lt;/p&gt;

&lt;p&gt;Developers can use it to speed up and simplify development, add finer control to their in-game level editors, and generally make their lives easier. There’s nothing stopping you giving users access to it in a game.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Make sure you’re running &lt;a href=&quot;https://gingerbeardman.itch.io/rorschach&quot;&gt;Rorschach&lt;/a&gt; or &lt;a href=&quot;https://github.com/gingerbeardman/playdate-mouse-support/releases/tag/240511&quot;&gt;Mouse Demo&lt;/a&gt; on your device&lt;/li&gt;
  &lt;li&gt;Attach your Playdate over USB and unlock the device&lt;/li&gt;
  &lt;li&gt;Install &lt;a href=&quot;https://www.hammerspoon.org/&quot;&gt;Hammerspoon&lt;/a&gt; automation tool for macOS&lt;/li&gt;
  &lt;li&gt;Install &lt;a href=&quot;https://github.com/gingerbeardman/playdate-mouse-support/blob/main/mouse/mouse_to_playdate.lua&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mouse_to_playdate.lua&lt;/code&gt;&lt;/a&gt; (instructions in the file)&lt;/li&gt;
  &lt;li&gt;Press the hotkey (defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F13&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;The guide window (virtual trackpad?) appears so you can gauge Playdate screen size&lt;/li&gt;
  &lt;li&gt;Move your mouse pointer and watch the drawing appear!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ve tested this on a MBP running Sonoma with a mouse and a trackpad. There’s very little lag, it’s really quite usable. The video has substantial lag as I was capturing through QuickTime and my phone camera. Anyway, it’s super cool!&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;demos&quot;&gt;Demos&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Rorschach drawing app is at: &lt;a href=&quot;https://gingerbeardman.itch.io/rorschach&quot;&gt;gingerbeardman.itch.io/rorschach&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;An example project with source code is at: &lt;a href=&quot;https://github.com/gingerbeardman/playdate-mouse-support&quot;&gt;github.com/gingerbeardman/playdate-mouse-support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;This is a technical proof of concept, so it can be improved&lt;/li&gt;
  &lt;li&gt;It was created to show that this can be done. It is not a final version or the most robust way of doing this. And, yes, sorry, it’s only available for macOS&lt;/li&gt;
  &lt;li&gt;My dream scenario would be for Playdate Simulator and Mirror to have this feature built-in, so you can enable the feature and then interact with the virtual screen to forward mouse events to Playdate&lt;/li&gt;
  &lt;li&gt;If that can’t happen, then maybe a more native mouse forwarding client can be created for each of macOS, Windows, and Linux&lt;/li&gt;
  &lt;li&gt;On macOS it would be cool if it supported Apple Pencil via an iPad/Sidecar&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 11 May 2024 10:52:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2024/05/11/mouse-support-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2024/05/11/mouse-support-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>A year in the life of a Playdate game developer</title>
          <description>&lt;p&gt;This week sees the &lt;a href=&quot;https://play.date/games/tags/on-sale&quot;&gt;first Anniversary Sale of Catalog, the official store for Playdate games&lt;/a&gt;. This means it’s almost the anniversary of &lt;a href=&quot;/2023/04/13/sparrow-solitaire-for-playdate/&quot;&gt;the release Sparrow Solitaire&lt;/a&gt;, which was the first Playdate work I did in 2023 after taking an enforced break.&lt;/p&gt;

&lt;p&gt;So I thought it would be interesting to look back and take stock of the last year of me making stuff for Playdate. What did I finish? What did I abandon? What is still to come?&lt;/p&gt;

&lt;p&gt;After Sparrow Solitaire (a huge game that took around a year of development time split between myself and Mac Vogelsang), it took me a few months to figure out the right sort of size and scope that would allow me to make a game every month or two, whilst minimising the risk of not selling enough. By September I was well and truly &lt;em&gt;in the zone&lt;/em&gt; and &lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;created YOYOZO&lt;/a&gt; which went on to receive a commendation from Ars Technica as one of the &lt;a href=&quot;https://arstechnica.com/gaming/2023/12/ars-technicas-best-video-games-of-2023/7&quot;&gt;“Best Games of 2023”&lt;/a&gt;. And at the &lt;a href=&quot;https://play.date/games/community-awards-2023/&quot;&gt;2023 Playdate Community Awards&lt;/a&gt;, presented on 8th March 2024, Sparrow Solitaire won &lt;em&gt;Best Puzzle Game&lt;/em&gt; and YOYOZO won &lt;em&gt;Best Arcade Game&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Somebody asked me if this is my full-time job? My answer is: yes, this is my only job and I do it as often as I’m able. It’s certainly more complicated, due to various life stuff, but that’s the gist of things. Over the last twelve months, I would say I’ve been productive for around half my time.&lt;/p&gt;

&lt;p&gt;Recently I saw somebody say &lt;a href=&quot;https://twitter.com/indieretropod/status/1764443576597946593&quot;&gt;“Be careful with scope and look what you can do in a year!”&lt;/a&gt;, about the wonderful &lt;a href=&quot;https://twitter.com/johanpeitz&quot;&gt;Johan Peitz&lt;/a&gt;, and I think that addage can also be applied to my output this year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; 22 projects, 10 of which were released, and 5 are still to come. I’m pretty happy with that!&lt;/p&gt;

&lt;p&gt;I also managed to squeeze in a couple of non-Playdate projects: my first real Love2D game &lt;a href=&quot;https://gingerbeardman.itch.io/portrait-curling&quot;&gt;Portrait Curling&lt;/a&gt;, modifications to vintage pixel art app &lt;a href=&quot;/2024/01/22/jinzo-paint-vintage-mobile-drawing-app/&quot;&gt;JINZO Paint&lt;/a&gt;, and I’m always &lt;a href=&quot;/&quot;&gt;blogging&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Don’t forget to &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;check out my Playdate games in the Catalog Sale!&lt;/a&gt; And my &lt;a href=&quot;/tag/playdate/&quot;&gt;other Playdate blog posts&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Month&lt;/th&gt;
        &lt;th&gt;Project&lt;/th&gt;
        &lt;th&gt;Genre&lt;/th&gt;
        &lt;th&gt;Status&lt;/th&gt;
        &lt;th&gt;Store&lt;/th&gt;
        &lt;th&gt;Blog&lt;/th&gt;
        &lt;th&gt;Made With&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-03&lt;/td&gt;
        &lt;td&gt;Sparrow Solitaire&lt;/td&gt;
        &lt;td&gt;Puzzle&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/sparrow-solitaire/&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/sparrow-solitaire/&quot;&gt;Catalog&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;/2023/04/13/sparrow-solitaire-for-playdate/&quot;&gt;Read&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://twitter.com/Vogelscript&quot;&gt;vogelscript&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-04&lt;/td&gt;
        &lt;td&gt;Shark Turtle&lt;/td&gt;
        &lt;td&gt;Puzzle&lt;/td&gt;
        &lt;td&gt;Unreleased&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-05&lt;/td&gt;
        &lt;td&gt;Radical&lt;/td&gt;
        &lt;td&gt;Adventure&lt;/td&gt;
        &lt;td&gt;Abandoned&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-05&lt;/td&gt;
        &lt;td&gt;Carton&lt;/td&gt;
        &lt;td&gt;Puzzle&lt;/td&gt;
        &lt;td&gt;On hold&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-06&lt;/td&gt;
        &lt;td&gt;Heno Heno&lt;/td&gt;
        &lt;td&gt;Puzzle&lt;/td&gt;
        &lt;td&gt;On hold&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-06&lt;/td&gt;
        &lt;td&gt;Fore! Track&lt;/td&gt;
        &lt;td&gt;Sports&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;Catalog&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/&quot;&gt;Read&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-07&lt;/td&gt;
        &lt;td&gt;Mandala&lt;/td&gt;
        &lt;td&gt;Toy&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://github.com/gingerbeardman/mandala&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;GitHub&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-08&lt;/td&gt;
        &lt;td&gt;Strategies&lt;/td&gt;
        &lt;td&gt;App&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://gingerbeardman.itch.io/strategies&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Itch&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-08&lt;/td&gt;
        &lt;td&gt;DateDiff&lt;/td&gt;
        &lt;td&gt;App&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://gingerbeardman.itch.io/datediff&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Itch&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-08&lt;/td&gt;
        &lt;td&gt;Fortress&lt;/td&gt;
        &lt;td&gt;Puzzle&lt;/td&gt;
        &lt;td&gt;Abandoned&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-08&lt;/td&gt;
        &lt;td&gt;Mole Hole&lt;/td&gt;
        &lt;td&gt;Adventure&lt;/td&gt;
        &lt;td&gt;Unreleased&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://twitter.com/thoruman&quot;&gt;thoruman&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-09&lt;/td&gt;
        &lt;td&gt;Rink&lt;/td&gt;
        &lt;td&gt;Sports&lt;/td&gt;
        &lt;td&gt;Tutorial&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-09&lt;/td&gt;
        &lt;td&gt;Kye&lt;/td&gt;
        &lt;td&gt;Puzzle&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://mouflon-cloud.itch.io/kye&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Itch&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://twitter.com/MouflonCloud&quot;&gt;mouflon cloud&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-09&lt;/td&gt;
        &lt;td&gt;YOYOZO&lt;/td&gt;
        &lt;td&gt;Action&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;Catalog&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;Read&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-10&lt;/td&gt;
        &lt;td&gt;Pararena&lt;/td&gt;
        &lt;td&gt;Sports&lt;/td&gt;
        &lt;td&gt;Abandoned&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-10&lt;/td&gt;
        &lt;td&gt;Soccer&lt;/td&gt;
        &lt;td&gt;Sports&lt;/td&gt;
        &lt;td&gt;Prototype&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-10&lt;/td&gt;
        &lt;td&gt;Super ICARUS&lt;/td&gt;
        &lt;td&gt;Action&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/icarus/&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://play.date/games/icarus/&quot;&gt;Catalog&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-11&lt;/td&gt;
        &lt;td&gt;Boing&lt;/td&gt;
        &lt;td&gt;Action&lt;/td&gt;
        &lt;td&gt;Prototype&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-11&lt;/td&gt;
        &lt;td&gt;Cranxious&lt;/td&gt;
        &lt;td&gt;Action&lt;/td&gt;
        &lt;td&gt;Prototype&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2023-12&lt;/td&gt;
        &lt;td&gt;See The Sky&lt;/td&gt;
        &lt;td&gt;Storybook&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://gingerbeardman.itch.io/see-the-sky&quot;&gt;Released&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Itch&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;/2023/12/16/see-the-sky-thoru-yamamoto-christmas-story-for-playdate/&quot;&gt;Read&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;&lt;a href=&quot;https://twitter.com/thoruman&quot;&gt;thoruman&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2024-01&lt;/td&gt;
        &lt;td&gt;Rocket Man&lt;/td&gt;
        &lt;td&gt;Simulation&lt;/td&gt;
        &lt;td&gt;On hold&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2024-02&lt;/td&gt;
        &lt;td&gt;Eggdog&lt;/td&gt;
        &lt;td&gt;Action&lt;/td&gt;
        &lt;td&gt;Prototype&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Thu, 07 Mar 2024 19:44:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2024/03/07/a-year-in-the-life-of-a-playdate-game-developer/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2024/03/07/a-year-in-the-life-of-a-playdate-game-developer/</guid>
        </item>
      
    
      
        <item>
          <title>See the sky: Thoru Yamamoto’s Christmas story, for Playdate</title>
          <description>&lt;p&gt;&lt;em&gt;Thoru Yamamoto&lt;/em&gt; (Japanese: 山本徹 or とーるやまもと), born 1955, is a Japanese multimedia artist. Over the years he has released work in many formats including, but not limited to: magazine illustrations, HyperCard decks, interactive CD-ROMs, printed books, websites, digital stickers, and videos. He is perhaps best known for his story books distributed as HyperCard stacks and his unique 1-bit art taking advantage of the limitation imposed by early Apple Macintosh computers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See the sky&lt;/em&gt; is one such story book, a Christmas present released in October 1992 as a series of HyperCard stacks. In 1996 it was re-released in a remastered form: as an interactive CD-ROM, produced using Macromedia Director, which added ambient music and navigation to the original images. Also released in 1996 was &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1582466068517310465&quot;&gt;a printed book of the story, presented as 3 images per page&lt;/a&gt;. Finally, in 1999 a CD-R of the combined HyperCard stacks was released.&lt;/p&gt;

&lt;h2 id=&quot;a-new-edition-for-2023&quot;&gt;A new edition for 2023&lt;/h2&gt;

&lt;p&gt;I’ve created a new version of &lt;em&gt;See the sky&lt;/em&gt; for &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt;. The remastering/remaking/porting process was quite involved, so I’d like to detail it in this blog post.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/thoru-yamamoto-see-the-sky-playdate.gif#playdate&quot; alt=&quot;See the sky&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;extracting-the-original-images&quot;&gt;Extracting the original images&lt;/h2&gt;

&lt;p&gt;I used &lt;a href=&quot;https://github.com/PierreLorenzi/HyperCardPreview&quot;&gt;HyperCardPreview&lt;/a&gt; to export the assets from the original &lt;a href=&quot;https://en.wikipedia.org/wiki/HyperCard&quot;&gt;HyperCard&lt;/a&gt; stack. I did this back in October 2022, and had to use an old MacBook Pro running Mojave to do it.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Just today, whilst writing this blog post, I figured out that HyperCardPreview will refuse to open stacks if the app Stacksmith is also present on your Mac!? As soon as I deleted Stacksmith, HyperCardPreview opened the files just fine. It seems to be some sort of issue with them competing for control of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;com.apple.hypercard.stack&lt;/code&gt; uti. Weird.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anyway, &lt;em&gt;See the sky&lt;/em&gt; consists of just over 500 full screen card images. For other stacks, you might get a combination of background images and card images that would need re-compositing - it really depends on the stack.&lt;/p&gt;

&lt;p&gt;Thoru told me that when he first started out with HyperCard he wasn’t aware of the benefits of using a common background card and different foreground cards, which is why &lt;em&gt;See the sky&lt;/em&gt; consists of only foreground cards. For this reason it was very difficult for him to upload the files, which were rather large for the time. Later productions would make more effective use of background cards to keep the file size as small as possible.&lt;/p&gt;

&lt;h2 id=&quot;implementing-a-viewer&quot;&gt;Implementing a viewer&lt;/h2&gt;

&lt;p&gt;My way of developing is to think of the goal as clearly as possible, then get something up and working as quickly as possible, and after that iterate and refine until I reach the goal. So it was easy to create a sort of slideshow viewer for the images, keeping track of the current image and allowing navigation forwards and backwards with A and B buttons, loading the next/previous image as required. Pretty quickly it became apparent that I could not simply display each screen centred as important content would frequently be out of view.&lt;/p&gt;

&lt;h2 id=&quot;adjusting-each-screen&quot;&gt;Adjusting each screen&lt;/h2&gt;

&lt;p&gt;So I added a table to store offsets for each screen. But defining these manually would be very time consuming so I set about creating an interactive way to do this. I added a debug mode in which I map the d-pad (cursor keys) to move the current image up/down/left/right, along with the ability to save the offsets table to a file. This was a huge productivity boost! But I was still finding it quite a repetitive task, so I added further key mappings so I could use the numbers on the numeric pad of my keyboard to quickly set the offsets for a screen to each of the 8 compass directions, or centred. This increased productivity even more. At this point I found myself setting the same offsets for a screen as the previous screen, so I added to more keys to copy the offset from the previous/following screen. All in all this made setting offsets for 500 screens quick and easy. When I was happy I copied the offset table into the code and that became the default values.&lt;/p&gt;

&lt;p&gt;This is all to say that whilst the original Macintosh resolution of 512×342 does not match the Playdate’s resolution of 400×240, &lt;em&gt;See the sky&lt;/em&gt; still works on Playdate because every screen has been manually repositioned to keep the important elements in view, a process similar to the “pan and scan” adaptation of movies for 4:3 TVs.&lt;/p&gt;

&lt;h2 id=&quot;hypercard-look-and-feel&quot;&gt;HyperCard look and feel&lt;/h2&gt;

&lt;p&gt;The most recognisable feature of many HyperCard stacks are the crossfades between screens. This was essential to the experience. Thoru even took advantage of the crossfade in certain scenes by changing only small elements from screen to screen so that the crossfade turned into a sort of stop-motion animation effect.&lt;/p&gt;

&lt;p&gt;To achieve the crossfade on Playdate I do the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Before the transition I capture the current screen using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;playdate.graphics.getWorkingImage()&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Next I make this image into an overlay, in my case I’m assigning it to a “fade” sprite&lt;/li&gt;
  &lt;li&gt;Behind the fade image/sprite I load in the requested next/previous image&lt;/li&gt;
  &lt;li&gt;Finally I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;playdate.graphics.image:fadedImage(alpha, Bayer8x8)&lt;/code&gt; to fade out the overlay, which happens over several frames and results in the image becoming more and more transparent, eventually disappearing completely&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the crossfade is simply one image fading away and the image behind it slowly being revealed. Magic.&lt;/p&gt;

&lt;p&gt;I also added the ability to have slower or faster crossfades depending on your preference. I do this by adjusting the refresh/frame rate of the game itself, rather than skipping any of the fade.&lt;/p&gt;

&lt;p&gt;The only other feature I needed to add was a flashing capability. This happens at a handful of places in the story to various degrees, mostly there are a few flashes in places but in one places there are 10 flashes. Given that people with photosensitive epilepsy won’t want to see the screen flash I honour the Playdate “reduce flashing” system setting. If the user has that toggled on the story will only flash once at each instance. The speed of the flashing is quite slow, and not tied to the speed of the crossfade.&lt;/p&gt;

&lt;p&gt;Page turn sounds are recordings of the sounds defined in the original HyperCard stack, another essential part of the experience. Thoru describes these sounds as “pipo papo” and that’s how I label it in the settings. I also provide the option to use a more realistic page turn sound, or switch off page turn sounds entirely.&lt;/p&gt;

&lt;h2 id=&quot;chapters&quot;&gt;Chapters&lt;/h2&gt;

&lt;p&gt;I added chapter navigation which unlocks as you go through the story, in a similar way to the 1996 CD-ROM release of &lt;em&gt;See the sky&lt;/em&gt;. This means you can revisit any previous chapter, and once you’ve reached the end of the story you’ll have access to all chapters. If you want to reset that progress, for example to let somebody else enjoy the story from the beginning and remove any temptation for them to skip ahead, you can delete the “game data” through Playdate Settings. Chapter title cards are only shown when using the chapter navigation, and not whilst going through the story, I do this by injecting temporary images into the crossfade system.&lt;/p&gt;

&lt;h2 id=&quot;progress&quot;&gt;Progress&lt;/h2&gt;

&lt;p&gt;I also added a progress bar, shown when you bring up the menu. It’s a vertical bar drawn in the centre of the screen between the menu and faded game image. The bar itself is dithered at 50%, and onto it I draw white markers for each chapter position. The current position is shown by drawing a solid white bar from the bottom of the screen to the current position. This means that the bar “fills” with white from the bottom as you progress through the story.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/thoru-yamamoto-see-the-sky-playdate.png#playdate&quot; alt=&quot;Progress bar&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;experiments-with-sound&quot;&gt;Experiments with sound&lt;/h2&gt;

&lt;p&gt;The 1996 CD-ROM release of &lt;em&gt;See the sky&lt;/em&gt; has ambient music, but Thoru and myself couldn’t figure out the rights to this so it could not be used. I had done the work, just in case, so I’ll mention it here.&lt;/p&gt;

&lt;p&gt;I &lt;a href=&quot;/2023/08/12/extracting-sounds-from-macromedia-director-files/&quot;&gt;extracted the audio from the Director files using a technique I’ve previously blogged about&lt;/a&gt;. I then converted the audio files to 44.1KHz 16-bit WAV files, and used &lt;a href=&quot;https://www.ocenaudio.com/en/startpage&quot;&gt;Ocen Audio&lt;/a&gt; to remove the base noise from those files. Basically, you select a part of the audio that contains only noise and it removes this from the entire audio file. Doing this without first upsampling the audio meant it did not work as well or at all. After the de-noising, I converted to ADPCM using adpcm-xq which gave files half the original size at higher quality and with lower noise levels.&lt;/p&gt;

&lt;h2 id=&quot;experiments-with-music&quot;&gt;Experiments with music&lt;/h2&gt;

&lt;p&gt;After we realised we would not be able to use the ambient music, I looked at using completely different music, turning to the catalogue of watson @ MusMus. I found a piece of music for each chapter of the story and extracted loops using PyMusicLooper. I then tweaked the loops by adjusting their start point by moving a section of audio from one end to the other so the loop began with the part of the audio I thought sounded best. I use &lt;a href=&quot;https://twistedwave.com&quot;&gt;TwistedWave&lt;/a&gt; for most sound editing like this. Thoru really liked the music itself but was of the opinion that no music would be more honest to the original release. I can’t argue with that reasoning. Feel free to play your own choice of music whilst experiencing the story.&lt;/p&gt;

&lt;h2 id=&quot;optimising-for-size&quot;&gt;Optimising for size&lt;/h2&gt;

&lt;p&gt;The final app binary is tiny but the images are adding up to about 3MB. That’s fine, but I wondered how I might be able to optimise them. I used imagemagick’s montage command to stitch the images together into a grid. In Playdate land we call this an image table, but you might refer to it as a sprite sheet. This reduced the size of 500 separate card images from 3MB to a single image of just 2MB. A nice saving!&lt;/p&gt;

&lt;p&gt;The 500 screen image table occupies 10.5MB RAM, so about two thirds of the available RAM on Playdate. Side-effects of bundling 500 screens as single file are a short delay on loading for users, a short delay on building for me as a developer, and the need to regenerate the single image if any of the images it contains are changed.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;At this point, I could have stopped. I should have stopped. But, of course, I didn’t.&lt;/p&gt;

  &lt;p&gt;I’ve often wondered how the dimensions of a sprite sheet grid affect the file size when saved as a compressed format such as PNG. The extreme examples in this case would be having a tall grid of 1×504 or a wide grid of 504×1, and in between we have some exact multiples. I generated one sprite sheet for each grid size and here found that for this set of images a grid of 42×12 resulted in the smallest image file size a saving of a further 4% or so. The savings are similar when converted to the Playdate pdt image format. Note that this sort of optimisation only works for solid images, not for images with any alpha/transparency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that’s it. The final size of this new version is smaller than the original, mostly due to the image optimisation. I could make it even smaller by exporting all the final crops of the screen images, create a new image table of those, displaying them with zero offset. But I decided to not go there!&lt;/p&gt;

&lt;h2 id=&quot;the-result&quot;&gt;The result&lt;/h2&gt;

&lt;p&gt;You can download &lt;em&gt;See the sky&lt;/em&gt; for Playdate from my page on itch.io, and it is a free download. A present from Thoru and myself. With best wishes for a Merry Christmas.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://gingerbeardman.itch.io/see-the-sky&quot;&gt;gingerbeardman.itch.io/see-the-sky&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;500 screens&lt;/li&gt;
  &lt;li&gt;10 chapters&lt;/li&gt;
  &lt;li&gt;~ 1 hour experience&lt;/li&gt;
  &lt;li&gt;Quick navigation menu unlocked as you go&lt;/li&gt;
  &lt;li&gt;Position is remembered between launches&lt;/li&gt;
  &lt;li&gt;Settings for sound and crossfade speed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;credits&quot;&gt;Credits&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Created by Thoru Yamamoto&lt;/li&gt;
  &lt;li&gt;Edited by Matt Sephton&lt;/li&gt;
  &lt;li&gt;© 1992 Thoru Yamamoto&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://lostmediawiki.com/Thoru_Yamamoto_works_(partially_found_interactive_media;_1990s)&quot;&gt;Thoru Yamamoto @ Lost Media Wiki&lt;/a&gt; read more about his work&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://archive.org/details/thoru-yamamoto-hypercard-stacks&quot;&gt;Thoru Yamamoto HyperCard Stacks Collection @ archive.org&lt;/a&gt; view the original &lt;em&gt;See the sky&lt;/em&gt; &amp;amp; more&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;birthday-blog-post&quot;&gt;Birthday blog post?&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Check out my other &lt;a href=&quot;/tag/birthday/&quot;&gt;#birthday&lt;/a&gt; blog posts.&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 16 Dec 2023 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/12/16/see-the-sky-thoru-yamamoto-christmas-story-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/12/16/see-the-sky-thoru-yamamoto-christmas-story-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>Dynamic music and sound techniques for video games</title>
          <description>&lt;p&gt;The only aspect of game development I’ve not attempted myself is the music. I mostly use royalty free music of Japanese origin (just because I dig their vibe, man) as in the case of &lt;a href=&quot;https://soundcloud.com/mac-vogelsang/sets/sparrow-solitaire&quot;&gt;&lt;em&gt;Sparrow Solitaire&lt;/em&gt;&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1732555533863751691&quot;&gt;&lt;em&gt;Fore! Track&lt;/em&gt;&lt;/a&gt; or in rare cases I pay friends (like the amazing Jamie Hamshere) to write music specifically for a game as in the case of &lt;a href=&quot;https://soundcloud.com/gingerbeardman/sets/yoyozo-soundtrack&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt;. Maybe one day that will change, but until then I’m enjoying gaining more understanding and control of the music in my games. Whilst I develop games for &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; these techniques are general enough to apply anywhere.&lt;/p&gt;

&lt;p&gt;The main way I make the music into more than a static track is to apply a dynamic, reactive, or adaptive effect in one way or another. In this blog post I’ll go into how I’ve achieved this. Please note this is by no means an exhaustive list, rather it’s just the ones I have personally used.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;dynamic-bpm&quot;&gt;Dynamic BPM&lt;/h2&gt;

&lt;p&gt;I use this method in &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt; because it uses “chip tune” music data representing songs composed by my friend Jamie Hamshere using &lt;a href=&quot;https://play.date/pulp/&quot;&gt;&lt;em&gt;Playdate Pulp&lt;/em&gt;&lt;/a&gt;. A playback engine for this data, written by Pulp creator Shaun Inman, works beautifully when integrated into games written using Lua and the &lt;a href=&quot;https://play.date/dev/&quot;&gt;&lt;em&gt;Playdate SDK&lt;/em&gt;&lt;/a&gt;. I added hook to allow me to set the BPM at any point to any value. The end result is that the BPM of the music scales from 130 to 135 as your score increases. As you improve at the game you’ll notice the music speed up ever so slightly along with an increase in tension and anxiety.&lt;/p&gt;

&lt;iframe width=&quot;100%&quot; height=&quot;140&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; allow=&quot;autoplay&quot; src=&quot;https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1685873466&amp;amp;color=%23ff5500&amp;amp;auto_play=false&amp;amp;hide_related=false&amp;amp;show_comments=true&amp;amp;show_user=true&amp;amp;show_reposts=false&amp;amp;show_teaser=true&amp;amp;visual=true&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Of course, it’s possible to do this in a pre-recorded song stored as a digital music file, but it’s much more difficult for that to respond to the what the player does in the game. An example that takes an interesting approach to this is the track &lt;a href=&quot;https://www.youtube.com/watch?v=1_iZh_2li4M&quot;&gt;“Sunny Day” from the game Vib Ribbon&lt;/a&gt;, and indeed the rest of its soundtrack, where tempo changes over the duration of each song.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;infinite-variations&quot;&gt;Infinite Variations&lt;/h2&gt;

&lt;p&gt;Another technique I use in &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt;, again made possible because I can modify the music data and playback parameters in real time. With this one I cycle the values for the instrument voices pseudo-randomly so that the track plays once as it was programmed and then morphs slightly for each subsequent playback. The track is quite minimal and repetitive in &lt;a href=&quot;https://sites.barbican.org.uk/reichglassadams/&quot;&gt;Steve Reich, Philip Glass or John Adams&lt;/a&gt; sort of way, so there are automated variations wandering around the original arrangement work really well. Perhaps the &lt;a href=&quot;https://www.youtube.com/watch?v=NkBXgcN3fXo&quot;&gt;ultimate implementation of this approach is &lt;em&gt;Wii Play’s&lt;/em&gt; Tanks game&lt;/a&gt;.&lt;/p&gt;

&lt;iframe width=&quot;100%&quot; height=&quot;140&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; allow=&quot;autoplay&quot; src=&quot;https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1685873439&amp;amp;color=%23ff5500&amp;amp;auto_play=false&amp;amp;hide_related=false&amp;amp;show_comments=true&amp;amp;show_user=true&amp;amp;show_reposts=false&amp;amp;show_teaser=true&amp;amp;visual=true&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;For sound effects, I vary the playback sample rate to change the pitch of sound effects. This prevents the same sound effect becoming monotonous. Two examples might be &lt;em&gt;Lara Croft&lt;/em&gt; in the first &lt;em&gt;Tomb Raider&lt;/em&gt; game, &lt;a href=&quot;https://youtu.be/Roi2UelYGsU?si=_17TmHon5JenRxCM&amp;amp;t=1079&quot;&gt;groaning the same way every time she climbs up a platform&lt;/a&gt; compared with &lt;a href=&quot;https://www.youtube.com/watch?v=JGQeQmUuMas&quot;&gt;the rich variety of sounds when &lt;em&gt;Mario&lt;/em&gt; walks on different surfaces&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;blendingfadingbalance&quot;&gt;Blending/Fading/Balance&lt;/h2&gt;

&lt;p&gt;Another idea I had was to fade or blend two tracks as the player makes progress in the game. But how to find two tracks that can be cross-faded in a way that always makes sense? Of course you can have them composed, but what about in music that already exists? If only there was an easy way to find such tracks!&lt;/p&gt;

&lt;p&gt;There is: stereo pairs! You’d be surprised at how different the left and right channels can sound whilst obviously being the same tune. Of course this means that are output audio will be mono but for me on Playdate that’s just fine. I use this method in &lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;&lt;em&gt;Fore! Track&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The idea is to adjust the balance of the two parts of the audio, at once point you’re playing just the left audio across both outputs, then you adjust the balance to play a mix of both, at the other end of the scale you’d be playing just the right audio. Unfortunately the Playdate SDK currently has no API to easily adjust balance, so I had to program a method myself. First, I convert to the destination format which is for me ADPCM using &lt;a href=&quot;https://github.com/dbry/adpcm-xq&quot;&gt;adpcm-xq&lt;/a&gt;. Once the files are in this format I split the stereo pair into two files, one for the left channel and one for the right channel. Converting to the destination format before splitting ensures that the two are exactly the same length in terms of samples/bytes.&lt;/p&gt;

&lt;p&gt;In the game I fade between the two as the score/chain increases, which has the effect of subtly changing the instrumentation of the tune. It’s one of those things that most people wouldn’t notice, but that once you know about it you can’t miss it. Sadly, I don’t have an easy way to demo this in a video or sound file.&lt;/p&gt;

&lt;p&gt;For sound effects, you might consider panning to increase immersion and guide the players visual focus through use of audio. In &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt; I pan certain sounds relative to the location of the ball, certain other sounds relative to the location of the player, and there are global sound effects that are not panned at all.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-teaser.gif#playdate&quot; alt=&quot;YOYOZO&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;progressive-loops&quot;&gt;Progressive Loops&lt;/h2&gt;

&lt;p&gt;Digital audio is a different beast. I always try to find a time that fits the game, going so far as to audition many hundreds of tracks and creating playlist of songs far ahead of ever making a game or even having and idea for a game. I try to find tracks that will loop well and not get annoying, which is easier said than done. If I can’t find a track that loops well, there’s another way.&lt;/p&gt;

&lt;p&gt;You can use &lt;a href=&quot;https://github.com/arkrow/PyMusicLooper&quot;&gt;PyMusicLooper&lt;/a&gt; to analyse a digital audio track and spit out information about ranges that loop nicely, along with a percentage indicating how good it considers the loop. In other words you can identify and extract a loop from digital audio files that sound like they could loop. Of course, can’t identify loops in tracks that aren’t repetitive or consistent in their structure.  You might get PyMusicLooper to split the file into into three sections (intro, loop, outro) or just export the loop information as a text file to use in your game. Which I choose depends on how much of the file I want to use.&lt;/p&gt;

&lt;p&gt;For an example, in my game &lt;a href=&quot;https://play.date/games/icarus&quot;&gt;Super ICARUS&lt;/a&gt; I’m using a file that gives the vibe I wanted in the game and sounded like it contained some loops even though it was not provided as a looping song. PyMusicLooper reported that it contains tens of possible loops of varying quality.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;loop&lt;/th&gt;
      &lt;th&gt;start&lt;/th&gt;
      &lt;th&gt;end&lt;/th&gt;
      &lt;th&gt;duration&lt;/th&gt;
      &lt;th&gt;match&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;3.437&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;30.755&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;27.318&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;94.87%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;0.023&lt;/td&gt;
      &lt;td&gt;27.341&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;94.79%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;7.279&lt;/td&gt;
      &lt;td&gt;34.598&lt;/td&gt;
      &lt;td&gt;27.319&lt;/td&gt;
      &lt;td&gt;94.63%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;6.850&lt;/td&gt;
      &lt;td&gt;34.168&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;93.95%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;8.127&lt;/td&gt;
      &lt;td&gt;35.445&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;93.92%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;22.221&lt;/td&gt;
      &lt;td&gt;52.953&lt;/td&gt;
      &lt;td&gt;30.732&lt;/td&gt;
      &lt;td&gt;93.80%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;25.635&lt;/td&gt;
      &lt;td&gt;56.366&lt;/td&gt;
      &lt;td&gt;30.731&lt;/td&gt;
      &lt;td&gt;93.23%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;11.970&lt;/td&gt;
      &lt;td&gt;39.288&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;93.17%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;6.850&lt;/td&gt;
      &lt;td&gt;35.875&lt;/td&gt;
      &lt;td&gt;29.025&lt;/td&gt;
      &lt;td&gt;92.13%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;6.850&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;54.660&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;47.810&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;91.98%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;20.515&lt;/td&gt;
      &lt;td&gt;52.953&lt;/td&gt;
      &lt;td&gt;32.438&lt;/td&gt;
      &lt;td&gt;91.54%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt;10.263&lt;/td&gt;
      &lt;td&gt;37.581&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;91.26%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt;22.221&lt;/td&gt;
      &lt;td&gt;54.660&lt;/td&gt;
      &lt;td&gt;32.439&lt;/td&gt;
      &lt;td&gt;91.11%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt;23.928&lt;/td&gt;
      &lt;td&gt;68.325&lt;/td&gt;
      &lt;td&gt;44.397&lt;/td&gt;
      &lt;td&gt;90.92%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;14&lt;/td&gt;
      &lt;td&gt;39.300&lt;/td&gt;
      &lt;td&gt;70.031&lt;/td&gt;
      &lt;td&gt;30.731&lt;/td&gt;
      &lt;td&gt;90.82%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;12.399&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;70.449&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;58.050&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;90.78%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;My goal was to find three loops of increasing length and with a high percentage loop quality. After some experimentation and listening, I decided on loops 0, 9, and 15 (table only shows the top 15 loops from this track, even though their percentage loop match are not 100% they still sound like good loops, so selecting these loops was a case of finding three of suitable length and content. PyMusicLooper will let you audition the loops directly, so there’s no need to use an audio editor.&lt;/p&gt;

&lt;p&gt;Using the Playdate SDK I can do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setRange()&lt;/code&gt; on the audio track to change the playback range and the music will loop between those new points when the playhead reaches the end of the range. For this reason, this method does not provide immediate results so is better used to signify a large change in progress as the delay until the change is noticed will be an unknown amount of time. But when the change does kick in it’s a really nice surprise!&lt;/p&gt;

&lt;p&gt;The final result sees the game start by playing loop 1 (synth and drums) and then as the player gets makes some good progress I switch to loop 2 (synth, drums, guitar licks), and finally as they pass a certain threshold I switch to loop 3 (synth, drums, guitar licks into guitar solo). This provides music that sounds very dynamic with little effort. You could even drop back to the shorter loops if the player lost a life, missed a target, and so on. Again, there’s no real way of me demoing this as it’s something that will become apparent through play, and the final result is just one long dynamic song!&lt;/p&gt;

&lt;p&gt;For sound effects I use the same approach as above. As an example, in &lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;&lt;em&gt;Fore! Track&lt;/em&gt;&lt;/a&gt; there is a clapping sound effect after the player gets the ball in a hole. This is a long sound effect but I play three increasingly long sections of it as the player’s chain increases (number of successive holes-in-one). It starts off as a short clap, increases to a longer more enthusiastic clap, and finally it begins with a whoop and continues to enthusiastic clap. I have a separate sound effect for the end game cheer that plays over the top of the full clap, resulting in a raucous end of game celebration.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;quantised-sounds&quot;&gt;Quantised Sounds&lt;/h2&gt;

&lt;p&gt;In &lt;a href=&quot;https://play.date/games/icarus&quot;&gt;Super ICARUS&lt;/a&gt; created certain game event sounds from small sections of the music track. I then adjust playback rate/speed/pitch. The result is that the sounds appear to be quantised or matched to the music. I’m not sure how to describe this phenomenon accurately in musical/technical terms.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I’d love to hear about other methods of achieving dynamic music and sound in video games. Feel free to reach out to me on social media!&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt; (or, how I made a Playdate game in 39KiB)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/11/26/easter-egg-emoji-converting-pixels-into-particles/&quot;&gt;Easter egg emoji: converting pixels into particles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/&quot;&gt;Dynamic music and sound techniques for video games&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
  &lt;li&gt;2023-11-22—&lt;a href=&quot;https://news.ycombinator.com/item?id=38584336&quot;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-11-22—&lt;a href=&quot;https://tildes.net/~games/1crg/dynamic_music_and_sound_techniques_for_video_games&quot;&gt;Tildes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 09 Dec 2023 01:22:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/</guid>
        </item>
      
    
      
        <item>
          <title>Easter egg emoji: converting pixels into particles</title>
          <description>&lt;p&gt;I’m &lt;a href=&quot;/2019/08/14/moai-games/&quot;&gt;fascinated with Moai&lt;/a&gt; so I always try to squeeze an appearance into my games. Moai in video games is a meme, or &lt;a href=&quot;https://en.wikipedia.org/wiki/Easter_egg_(media)&quot;&gt;easter egg&lt;/a&gt;, going all the way back to 1983. But my game YOYOZO (&lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;out now for the Playdate handheld&lt;/a&gt;) is about capturing stars in space using a yoyo, so how could I get a Moai in it?&lt;/p&gt;

&lt;p&gt;The source of my inspiration was a trip to Japan back in 2004 (my only one, so far). Visiting in August meant that one of the things we did was go to an &lt;a href=&quot;https://www.japan-guide.com/e/e2267.html&quot;&gt;annual hanabi fireworks festival&lt;/a&gt;, where I saw &lt;a href=&quot;https://blog.gaijinpot.com/four-types-of-japanese-fireworks/&quot;&gt;katamono&lt;/a&gt; for the first time. These are fireworks that explode in the shapes of drawings, like a smiley face or a magic 8-ball. I was amazed and the experience has stuck with me for over 20 years. Maybe I could add Moai into YOYOZO by making the explosions appear like the katamono?&lt;/p&gt;

&lt;p&gt;I started off by coding the patterns by hand, as a test, but my calculations weren’t precise enough and the whole endeavour quickly grew too complicated to manage by hand. I needed a better, more automated way. The method that I arrived at is what I’ll document in this post.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;pixels-as-code&quot;&gt;Pixels as code&lt;/h2&gt;

&lt;p&gt;Instead of plotting values by hand in code I figured that it would make more sense if I could draw the patterns and then somehow convert them into coordinates. I use &lt;a href=&quot;/2023/05/10/piskel-for-playdate/&quot;&gt;Piskel&lt;/a&gt; as my Playdate-centric graphics editor. It’s a really useful tool. So I drew a few emoji-like patterns, keeping in mind that they would be converted into a cluster of points and exploded from an origin. This took a bit of experimentation but I ended up with a sort of already exploded look.&lt;/p&gt;

&lt;p class=&quot;screen&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-emoji.png#pixel&quot; alt=&quot;EMOJI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To get the pixel data out of Piskel in text form I make use of its “export as a C file” feature. This results in code definitions similar to the below. (I use a script that does some simple regex search/replace to reformat these definitions to be a little more succinct and readable in my Lua code.)&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define EMOJI-TABLE-11-11_FRAME_COUNT 3
#define EMOJI-TABLE-11-11_FRAME_WIDTH 11
#define EMOJI-TABLE-11-11_FRAME_HEIGHT 11
&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* Piskel data for &quot;emoji-table-11-11&quot; */&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;emoji&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;121&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;pixels-as-points&quot;&gt;Pixels as points&lt;/h2&gt;

&lt;p&gt;With this data in hand, my plan was to convert them into points expressed as an angle and distance from an origin. This way of expressing points is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Polar_coordinate_system&quot;&gt;polar coordinate system&lt;/a&gt; so there was no need to invent anything, I just needed to code a function that would take a grid of pixels expressed as &lt;em&gt;(x, y)&lt;/em&gt; and convert them to a series of distances and angles expressed as &lt;em&gt;(r, θ)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The way I do this is to read the point data in from a grid of pixels, offsetting that data by half the width and height of the &lt;em&gt;odd-sized&lt;/em&gt; grid so that the centre of the grid &lt;em&gt;(0, 0)&lt;/em&gt; is the middle of the centre pixel. And finally I convert those adjusted &lt;em&gt;(x, y)&lt;/em&gt; values to polar &lt;em&gt;(r, θ)&lt;/em&gt; coordinates. This worked really well!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Below is a work-in-progress GIF captured on 14 September 2023, shortly after getting the feature working. This animation also shows an early version of the HUD and debug values for ball size and the length of the beam.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-emoji.gif#playdate&quot; alt=&quot;EMOJI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The finishing touches (not shown in the above animation) were to add some small amount of &lt;em&gt;pseudo-randomisation&lt;/em&gt; to the initial rotation of the emoji, the initial “colour” of each particle, and changing the coordinates of each point slightly so they appear more organic and move at slightly different speeds. I eventually settled on over a dozen such pixel patterns in the game (how many have you spotted?). Once your score is higher than 50M points every explosion is an emoji! &lt;a href=&quot;https://play.date/games/yoyozo/#gameListingMoreInfo&quot;&gt;Check out the game manual&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;pseudo-random&quot;&gt;Pseudo-random?&lt;/h2&gt;

&lt;p&gt;I try to limit my use math.random values if I can help it, or at least use it in controlled way. I already use that for the positions of the stars, so if I also used it for explosions that would mean it would become far less controlled. Controlling the use of random is important in making a game system deterministic, if you want it to react the same way every time.&lt;/p&gt;

&lt;p&gt;If you’re wondering how you can get pseudo-random values, the main method I use is a trick I learned from the old arcade game &lt;a href=&quot;/2011/10/26/flicky-1984/&quot;&gt;Flicky (1984, SEGA)&lt;/a&gt; which is a game &lt;a href=&quot;https://www.flicky1984.com/post/709058873877790720/just-a-quick-reminder-that-you-can-play-my-flicky&quot;&gt;I own as a physical cabinet&lt;/a&gt;. Anyway, in Flicky there is a diamond that appears under seemingly random conditions.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One of the MAME programmers was kind enough &lt;a href=&quot;https://www.flicky1984.com/post/54534135892/the-best-jewel-thief-in-the-world&quot;&gt;reverse engineer Flicky on my behalf and figured out what makes the diamond appear&lt;/a&gt;. It turns out the diamond will appear only if you knock out an enemy cat and it disappears outside of the centre third of the play area &lt;em&gt;and&lt;/em&gt; if the x coordinate at which the cat finally comes to rest is even. So, about 50% chance &lt;em&gt;but&lt;/em&gt; only if you position the screen correctly during play.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, taking a cue from that wonderful Flicky logic, I use modulus—which returns the remainder of a division—as my main mechanism of generating pseudo-random values. It ensures a value in the range &lt;em&gt;[0,n-1]&lt;/em&gt;. They key thing to note is that if you base it on values in your game system that are constantly changing you can get seemingly random values that have the benefit of being deterministic if the player is skilled enough at repeating their inputs. The deterministic thing is how pretty much all the old school arcade games operated from Pac-Man to Flicky and more.&lt;/p&gt;

&lt;p&gt;Common game variables I use are: game tick (my alternative to timer), x-coordinate, y-coordinate, speed, angle, or combinations of more than one of these. I also used this approach in YOYOZO for the starfield particles, and in &lt;a href=&quot;/2023/04/13/sparrow-solitaire-for-playdate/&quot;&gt;Sparrow Solitaire&lt;/a&gt; for the falling particles that make up the &lt;a href=&quot;https://www.reddit.com/r/PlaydateConsole/comments/12vcrm6/dynamic_weather_effects_and_more_in_the_sparrow/&quot;&gt;weather effects&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;playdate-particles&quot;&gt;Playdate particles&lt;/h2&gt;

&lt;p&gt;There are &lt;a href=&quot;https://github.com/PossiblyAxolotl/pdParticles&quot;&gt;one or more libraries&lt;/a&gt; available that can be used to manage particles in a performant way on Playdate. Though I tend to code my own system that is bespoke to the game I’m working on at the time (I start each game from a blank file and use minimal libraries, force of habit). But the important thing for such limited platforms, especially when using Lua, is to use a pool of particles so that you’re not constantly creating and destroying particles which would wreak havoc on performance through overuse of Lua’a garbage collector.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt; (or, how I made a Playdate game in 39KiB)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/&quot;&gt;Dynamic music and sound techniques for video games&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/yoyozo/#gameListingMoreInfo&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt; manual/player’s guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 26 Nov 2023 20:41:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/11/26/easter-egg-emoji-converting-pixels-into-particles/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/11/26/easter-egg-emoji-converting-pixels-into-particles/</guid>
        </item>
      
    
      
        <item>
          <title>YOYOZO (or, how I made a Playdate game in 39KB)</title>
          <description>&lt;blockquote&gt;
  &lt;p&gt;2023-12-27—&lt;a href=&quot;https://arstechnica.com/gaming/2023/12/ars-technicas-best-video-games-of-2023/7&quot;&gt;Ars Technica: YOYOZO wins GOTY accolade!&lt;/a&gt; almost unbelievable to be listed alongside such games as: Chants of Sennaar, Cocoon, Dave the Diver, Humanity, The Legend of Zelda: Tears of the Kingdom, Pikmin 4, Puzzmo, Super Mario Bros. Wonder, Venba and Viewfinder.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A game I made for the Playdate handheld was released today! &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;Go buy it&lt;/a&gt; and then come back to read this blog post.&lt;/p&gt;

&lt;p&gt;It’s called YOYOZO and in it you control a space yo-yo and have to collect stars in a sort of cosmic ballet. Well, at first it might feel a little like being on a fairground ride, but eventually you’ll become good enough for it to feel like ballet. The concept is based on my memory of a game called &lt;a href=&quot;https://archive.org/details/Pendulumania-v1.3&quot;&gt;Pendulumania&lt;/a&gt; that I played 20 years ago.&lt;/p&gt;

&lt;p&gt;The most amazing thing about this game, for me, is that launch version weighs in at a file size of &lt;em&gt;only 39KiB&lt;/em&gt;. I’m using the KiB unit of measurement which equates to 1024 bytes. I still find it hard to believe as the game contains so much! In this blog post I’ll go into some of the nerdy details.&lt;/p&gt;

&lt;h2 id=&quot;playdate&quot;&gt;Playdate?&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; is a handheld gaming system with a unique crank input method. I don’t use the crank in this game, but I have done in the past and will do again in the future.&lt;/p&gt;

&lt;p&gt;If you own a Playdate you can buy the game now at &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;play.date/games/yoyozo/&lt;/a&gt;. If you don’t own a Playdate, well, &lt;a href=&quot;https://play.date&quot;&gt;what are you waiting for&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-teaser.gif#playdate&quot; alt=&quot;YOYOZO&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;butwhy&quot;&gt;But…&lt;em&gt;why?&lt;/em&gt;&lt;/h2&gt;

&lt;p&gt;The drive to produce a small game started after I sent the first playable version to testers. Steve at &lt;a href=&quot;http://scenicroutesoftware.com&quot;&gt;&lt;em&gt;Scenic Route Software&lt;/em&gt;&lt;/a&gt;, purveyor of quality video games, commented how tiny the game was. At that point it was 18KiB, but had no music or sound effects or polish. There was a long way to go.&lt;/p&gt;

&lt;p&gt;Even so, I wondered how doable it would be to build the game out with an eye on keeping file size “low”. I thought back to the days of my youth where whole games would fit on a single floppy disk, with room to spare. If they could do it, shouldn’t I give it a try?&lt;/p&gt;

&lt;p&gt;It’s worth noting that even with this mindset, I didn’t make a huge sustained effort to meet the goal. On the contrary, it was just something I simply kept in mind as development proceeded. For that reason, I’m sure there are more ways the game could be made even smaller than it is, with the exact same code and content. For example, I never tried finding the most optimal format for things like music and particle data which are the two largest sets of embedded data.&lt;/p&gt;

&lt;p&gt;Finally, this is not a challenge, or me throwing down the gauntlet in any way. It’s easy enough to make a smaller game, be it similar or entirely different, you’d just have to make different choices along the way. This was just me doing something nerdy as an additional constraint on top of the already enjoyable constraints of developing for Playdate.&lt;/p&gt;

&lt;p&gt;That said, I think every game developer should regularly make a point of writing code for an underpowered device as part of their own personal development—there are so many lessons to learn.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;reasons&quot;&gt;Reasons&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The main reason for the small file size is the fact that &lt;em&gt;the game does not use any digital sound files, and very few bitmap images&lt;/em&gt; (the launch card and animation have to be bitmaps, and in-game only the logo and fonts are bitmaps). Game graphics are all drawn using only shapes (lines, rects, circles) and fills (black, white, and dither patterns).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A second reason is that whilst I use the base Playdate Lua SDK, &lt;em&gt;I don’t use any of the additional “CoreLibs”&lt;/em&gt;. The only extra graphics functions I needed were for drawing outlined or filled circles, so I use two of my own wrapper functions that are similar to those from CoreLibs/graphics but mine are smaller and more specific. For timers, I use a simple frame/tick system, an approach which has pros and cons, but it’s good enough for me.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A third reason is that I made the tough decision to reduce system assets, which means &lt;em&gt;there is no animated launcher card&lt;/em&gt;. This was a tough one, but it added so much to the file size I decided against it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Finally, I noticed that &lt;em&gt;including data inside your game code&lt;/em&gt; often trumps how well you can compress it and store it externally. For example I tried compressing the music data and storing it in an external file, but the game final file size was larger than if I embedded the data in my Lua code. Plus, it’s faster as it doesn’t need to load an additional external file.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;breakdown&quot;&gt;Breakdown&lt;/h2&gt;

&lt;p&gt;I thought it would be cool to outline the main features and how each contributes to the total file size. Note that the sizes are expressed as quantities of the compiled binary, rather than uncompiled source code. It’s also worth noting that a blank project with an empty update function results in a compiled binary of only 147 bytes. Playdate compiles to Lua bytecode.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-chart.png&quot; alt=&quot;CHART&quot; /&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Content&lt;/th&gt;
      &lt;th&gt;Kilobytes&lt;/th&gt;
      &lt;th&gt;%&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Main code&lt;/td&gt;
      &lt;td&gt;19&lt;/td&gt;
      &lt;td&gt;49&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Two music tracks&lt;/td&gt;
      &lt;td&gt;5.5&lt;/td&gt;
      &lt;td&gt;14&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Three bitmap fonts&lt;/td&gt;
      &lt;td&gt;2.5&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Synthesized sound effects&lt;/td&gt;
      &lt;td&gt;2.5&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Custom particle system&lt;/td&gt;
      &lt;td&gt;2.0&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Animated system icon&lt;/td&gt;
      &lt;td&gt;2.0&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Animated system card&lt;/td&gt;
      &lt;td&gt;2.0&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pulp music engine (modified)&lt;/td&gt;
      &lt;td&gt;1.5&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Online scoring system&lt;/td&gt;
      &lt;td&gt;0.5&lt;/td&gt;
      &lt;td&gt;1.25&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;How to play instructions&lt;/td&gt;
      &lt;td&gt;0.4&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Custom soundtrack capability&lt;/td&gt;
      &lt;td&gt;0.1&lt;/td&gt;
      &lt;td&gt;0.25&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;“Main code” contains: physics simulation, game structure and state management, multi-layered scoring and bonus system, score/stat tracking, loading and saving stats and settings, path recording and playback, animated introduction, plus the following &lt;em&gt;dynamic&lt;/em&gt; systems: scrolling starfield, screen shake, music system, sound effects system. All running at 40fps.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;abandoned-and-removed-features&quot;&gt;Abandoned and removed features&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;I tried a bunch of stuff during development. Such as asteroid fields or meteor showers that introduced obstacles that needed to be avoided, and black holes that would magnetically attract the ball. But I felt they detracted from the pureness of the concept, so I didn’t go any further with them.&lt;/li&gt;
  &lt;li&gt;The positions of stars are randomly generated, but I have implemented a fixed “daily” layout in the game, which is really fun. It’s a different experience to be able to play the same layout over and over, improving your execution of the same moves and eking out higher and higher scores. I did plan to reintroduce that option when Playdate Catalog got score boards that reset daily, but by the time that happened &lt;a href=&quot;/2025/04/15/when-playdate-stopped-being-fun/&quot;&gt;I was no longer developing Playdate&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;manualguide&quot;&gt;Manual/Guide&lt;/h2&gt;

&lt;p&gt;I really loved the manuals that came with games in the 8-bit and 16-bit era. So I thought it would be fun to write a manual/player’s guide in the old-school style. I love reading those sorts of manuals, where the developer gives you a little glimpse behind the curtain so you get an understanding of how the game works, with some small hints and tips littered throughout—for the most inquisitive players! If that sounds like your thing, &lt;a href=&quot;https://play.date/games/yoyozo/#gameListingMoreInfo&quot;&gt;download the manual from the game page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.date/games/yoyozo/#gameListingMoreInfo&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-manual.png&quot; alt=&quot;YOYOZO Manual&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;

&lt;p&gt;I worked on YOYOZO from September 5th to 27th, submitting it to Catalog on 21st and polishing it for the final week after that. After the game was approved I added online score boards one evening just prior to launch. It was in review and waiting for release longer than it was in development!&lt;/p&gt;

&lt;p&gt;The purpose of this section is not to say that developing a game quickly is better than developing one slowly, or vice versa, but rather to show the importance of scoping a game well and then sticking to the plan.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2023-09-05—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1699108587732119834&quot;&gt;initial prototype&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-09-07—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1699784106505290093&quot;&gt;playable prototype&lt;/a&gt; (3 days)&lt;/li&gt;
  &lt;li&gt;2023-09-07—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1699890693366517890&quot;&gt;quick progress&lt;/a&gt; (3 days)&lt;/li&gt;
  &lt;li&gt;2023-09-09—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1700612152707461396&quot;&gt;came up with the name&lt;/a&gt; (5 days)&lt;/li&gt;
  &lt;li&gt;2023-09-14—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1702103698749505670&quot;&gt;polishing and balancing&lt;/a&gt; (10 days)&lt;/li&gt;
  &lt;li&gt;2023-09-20—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1704608465522487681&quot;&gt;revelatory physics tweak&lt;/a&gt; (16 days)&lt;/li&gt;
  &lt;li&gt;2023-09-21—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1704991183573831711&quot;&gt;addicted to my own game&lt;/a&gt; (17 days)&lt;/li&gt;
  &lt;li&gt;2023-09-21—submitted to Catalog (17 days)&lt;/li&gt;
  &lt;li&gt;2023-09-23—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1705676134245875750&quot;&gt;game over replay&lt;/a&gt; (19 days)&lt;/li&gt;
  &lt;li&gt;2023-09-26—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1706765228879253972&quot;&gt;layout design using spreadsheet&lt;/a&gt; (22 days)&lt;/li&gt;
  &lt;li&gt;2023-09-26—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1706772586510643560&quot;&gt;game over stats screen&lt;/a&gt; (22 days)&lt;/li&gt;
  &lt;li&gt;2023-09-27—final version (23 days)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and then some waiting until:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2023-10-10—approved for Catalog (36 days)&lt;/li&gt;
  &lt;li&gt;2023-11-19—added online scoreboards (76 days)&lt;/li&gt;
  &lt;li&gt;2023-11-21—&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1727030817116053611&quot;&gt;released on Catalog&lt;/a&gt; (78 days)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…so that is 78 days (11 weeks) from initial prototype to being live on the Catalog store!&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;credits&quot;&gt;Credits&lt;/h2&gt;

&lt;p&gt;YOYOZO is a game by Matt Sephton, with music by Jamie Hamshere.&lt;/p&gt;

&lt;p&gt;Thanks to CANO-Lab and Testers.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/11/26/easter-egg-emoji-converting-pixels-into-particles/&quot;&gt;Easter egg emoji: converting pixels into particles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/&quot;&gt;Dynamic music and sound techniques for video games&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://play.date/games/yoyozo/#gameListingMoreInfo&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt; manual/player’s guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
  &lt;li&gt;2024-10-16—&lt;a href=&quot;https://gamerepublic.net/news/best-indie-game-made-in-the-north-of-england-2024-award/&quot;&gt;Game Republic 2024 Awards: Best Indie Game Made in the North of England&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2024-03-08—&lt;a href=&quot;https://play.date/games/community-awards-2023/&quot;&gt;Playdate Community Awards 2023: Best Arcade Game&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-12-27—&lt;a href=&quot;https://arstechnica.com/gaming/2023/12/ars-technicas-best-video-games-of-2023/7&quot;&gt;Ars Technica: YOYOZO wins GOTY accolade!&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-11-30—&lt;a href=&quot;https://arstechnica.com/gaming/2023/11/my-long-quest-to-revive-a-90s-windows-gaming-cult-classic/&quot;&gt;Ars Technica&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-11-24—&lt;a href=&quot;http://eepurl.com/iEHB8M&quot;&gt;Hacker Newsletter&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-11-23—&lt;a href=&quot;https://www.timeextension.com/news/2023/11/yoyozo-is-a-new-playdate-game-inspired-by-the-japanese-cult-classic-pendulumania&quot;&gt;Time Extension&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-11-22—&lt;a href=&quot;https://news.ycombinator.com/item?id=38372936&quot;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-11-22—&lt;a href=&quot;https://tildes.net/~games/1cbz/yoyozo_or_how_i_made_a_playdate_game_in_39kb&quot;&gt;Tildes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 21 Nov 2023 23:59:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/</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>The first colour Playdate game?</title>
          <description>&lt;blockquote&gt;
  &lt;p&gt;This blog post assumes some familiarity with &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; (a handheld game console with a cool crank control scheme), &lt;a href=&quot;https://play.date/dev/&quot;&gt;Playdate SDK&lt;/a&gt; and the &lt;a href=&quot;https://www.lua.org/manual/5.4/&quot;&gt;Lua programming language&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;During the development of my forthcoming Playdate game &lt;em&gt;&lt;a href=&quot;/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/&quot;&gt;Ball und Panzer Golf&lt;/a&gt;&lt;/em&gt; (tentative title), I wanted to be able to draw to the debug layer from anywhere in my code. The SDK allows you to draw to the debug layer only from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drawDebug&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;After filing a &lt;a href=&quot;https://devforum.play.date/t/additional-way-to-do-debug-draw-from-anywhere-in-code/11735&quot;&gt;feature request&lt;/a&gt; I thought about it some more and &lt;a href=&quot;&quot;&gt;came up with a workaround&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. (on initialisation) create a full screen opaque image&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;playdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graphics&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;disp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;playdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;display&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;overlay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;disp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;disp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getHeight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kColorBlack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. (anywhere in your code) draw into that using pushContext (or lockFocus)&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pushContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;overlay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kColorWhite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setLineWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drawCircleAtPoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- draw some debug stuff into our overlay image&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;popContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. (in debugDraw function) draw the single image of our collected debug drawing&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;playdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;debugDraw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;overlay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- draw our overlay image containing all debug draws&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;overlay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gfx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kColorBlack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- blank overlay ready for the next update&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This means I can draw debug info about a thing from the same code and logic responsible for that thing. For me, with this game, that makes a lot of sense.&lt;/p&gt;

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

&lt;h2 id=&quot;abusing-the-system&quot;&gt;Abusing the system&lt;/h2&gt;

&lt;p&gt;After debugging the positions of everything during my collision logic, it occurred to me that I could abuse this system to give the game a colour overlay.&lt;/p&gt;

&lt;p&gt;I do this by using the debug draw in the opposite way to how it’s supposed to be used. Instead of drawing just the debug information, I set the hole screen to draw in colour and then—in the same way as above—at various points in my code &lt;em&gt;I punch out areas of the screen I do not want drawn in colour&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I’m not quite drawing everything twice, as the areas that are punched out are simple shapes approximating the elements in my game. Plus, this is made easier for me because everything in the game is already being drawn only with filled shapes.&lt;/p&gt;

&lt;lite-youtube style=&quot;aspect-ratio: 5/3;&quot; videoid=&quot;fqv1kwfW5r8&quot; params=&quot;start=0&amp;amp;modestbranding=2&quot;&gt;
&lt;/lite-youtube&gt;

&lt;p&gt;One interesting thing about this technique is that changing the contrast (dither pattern opacity) of the golf greens only when running in “colour mode” on the Simulator made things look better. That’s to say that adding colour also adds an extra complexity with regards to contrast. I think that’s the only change to the graphics I’ve done so far but there is opportunity for more.&lt;/p&gt;

&lt;p&gt;When I sent a build out to testers I put a cryptic note in the changelog “added: chartreuse tinted glasses mode” but only one tester figured out what it was referring to. I had asked ChatGPT to suggest a colour that sounds like a shade of red, but is actually a shade of green: it suggested chartreuse (and so I use the “proper” chartreuse hex colour for the base colour of my green layer).&lt;/p&gt;

&lt;h2 id=&quot;multi-colour&quot;&gt;Multi-colour?&lt;/h2&gt;

&lt;p&gt;This approach could be taken further if the Playdate SDK allowed multiple colours to be used for debug drawing, &lt;a href=&quot;https://devforum.play.date/t/support-mutiple-colors-in-debugdraw/5848&quot;&gt;keep an eye on this feature request&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;physical-overlays&quot;&gt;Physical overlays&lt;/h2&gt;

&lt;p&gt;Back in October 2021 I bought transparency film in a range of colours to make a physical screen overlay, inspired by early arcade games like Space Invaders and the Vectrex gaming system, which worked but is obviously more hassle as the transparency picks up lint and gets dirty really quickly. This split of blue/green was for sky/grass in a 3D golf game.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/playdate-physical-overlays.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/&quot;&gt;Ball und Panzer Golf: making a Playdate game in a week&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 09 Jul 2023 12:59:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/07/09/the-first-colour-playdate-game/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/07/09/the-first-colour-playdate-game/</guid>
        </item>
      
    
      
        <item>
          <title>Ball und Panzer Golf: making a Playdate game in a week</title>
          <description>&lt;p&gt;I’ve been following the X68000 Z mini computer since it’s announcement in the hope that it will bring new activity to the X68000 scene and it seems to be having that effect. In one video from the recent 「68の日」(“68 Day”, named after the date written in Japanese order, 6-8, that’s 8th June the most special day of the year for X68000 fans) &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1669909753592512512?s=61&amp;amp;t=vJGphXuN310nHUu1fN6c7Q&quot;&gt;I spotted&lt;/a&gt; a interesting looking single screen golf game:&lt;/p&gt;

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

&lt;h2 id=&quot;hello-kata68k&quot;&gt;Hello kata68k&lt;/h2&gt;

&lt;p&gt;It was Ball und Panzer Golf an indie/doujin game by &lt;a href=&quot;https://twitter.com/kata68k&quot;&gt;@kata68k&lt;/a&gt; for the Sharp X68000 series of Japanese personal computers and the recent &lt;a href=&quot;https://www.zuiki.co.jp/products/x68000z/&quot;&gt;&lt;em&gt;Zuiki X68000 Z&lt;/em&gt;&lt;/a&gt; mini system, a new emulator based reimagining of the original classic computer. The name is a pun on the anime series &lt;a href=&quot;https://en.wikipedia.org/wiki/Girls_und_Panzer&quot;&gt;&lt;em&gt;Girls und Panzer&lt;/em&gt;&lt;/a&gt; and its shortened version is &lt;em&gt;BuPG&lt;/em&gt; which I assume is a pun on &lt;em&gt;PUBG&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;kata68k’s game, as the name implies, is golf but with a tank. It was &lt;a href=&quot;https://twitter.com/kata68k/status/1634209609069076480&quot;&gt;created in May 2023 and fine tuned for a few weeks&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/kata68k/status/1666099628947968006&quot;&gt;released in time for “68 Day”&lt;/a&gt;. It’s a single player game where you use the tank to shoot holes-in-one on a golf course and try to destroy all flags. All this to say it plays like golf, kinda, but with a focus on high scores.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Aside: my favourite source of X68000 content is the &lt;a href=&quot;https://www.youtube.com/user/pipipicpsf&quot;&gt;&lt;em&gt;PipitanTV&lt;/em&gt; YouTube channel&lt;/a&gt;, run by &lt;a href=&quot;https://twitter.com/pipixvi&quot;&gt;@pipixvi&lt;/a&gt;, which has a &lt;a href=&quot;https://www.youtube.com/watch?v=FMrsHfuuTR8&amp;amp;t=22s&quot;&gt;longer, screen capture video&lt;/a&gt; of Ball und Panzer Golf.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I played Ball und Panzer Golf v0.94 using an X68000 emulator and was instantly hooked, by the strange mashup concept and also by the scoring mechanisms that were involved. Kata’s game design values and methods align with my own to a surprising degree. kata68k grew up with the X68000 much like I grew up with another Motorola 68000-based computer: the Atari ST. We’re roughly the same age and have similar interests in many ways.&lt;/p&gt;

&lt;p&gt;Anyway, I had great fun figuring out the different types of shots and opportunities to increase my scoring ability during my few plays of the game. Once I’d figured them out, it became a task of improving my skill controlling the tank and the shot power. The shot mechanism itself is worthy of a mention: there is no real charging of the shot and the ball starts to move immediately. What you do control is the moment the ball starts to come back down to Earth. It’s a very strange way of controlling the ball, but oddly satisfying. It’s reminiscent of the two tap system that sets power in most golf games, but at the same time feels completely alien.&lt;/p&gt;

&lt;h2 id=&quot;enter-playdate&quot;&gt;Enter Playdate&lt;/h2&gt;

&lt;p&gt;I currently spend my time creating games for &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt;, a handheld gaming system with a black and white screen and a unique crank control that can be used as a method of input. Whilst on a walk in the park I couldn’t stop thinking of kata68k’s game and how it might work on Playdate. When I got back to my computer I wrote a little bit of Lua code to draw an elliptical golf green on screen, then added a hole, a flag pole, and a flag with a number on it. Then I added a loop to generate random positions and drew 18 holes, adjusting the size so they all fitted a bit better. It might just work.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/ball-und-panzer-golf-01.png#playdate&quot; alt=&quot;PNG&quot; title=&quot;The first 18 holes, eat your heart out &amp;lt;em&amp;gt;Pebble Beach no Hatou&amp;lt;/em&amp;gt;!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After a quick dinner, it was time to draw the tank. I really didn’t want to slow down my pace of progress so I decided to keep drawing the graphical elements in code using filled shapes rather then have to draw a tank in pixels. Another option would have been to use one of the cars from my game &lt;a href=&quot;/2021/08/23/daily-driver-teaser-artwork/&quot;&gt;Daily Driver&lt;/a&gt;, but the path of least resistance was to draw a filled ellipse along with a line to show the turret position.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/ball-und-panzer-golf-02.png#playdate&quot; alt=&quot;PNG&quot; title=&quot;Rudimentary tank and turret drawn using an filled ellipse and a thick line&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;day-1&quot;&gt;Day 1&lt;/h2&gt;

&lt;p&gt;That first day everything fell into place perfectly, with little friction and no refactoring. I started from a blank file and wrote just under 400 lines of code. The two most complicated elements were the randomised background and limiting the tank to its circular area, but I’d done similar things before so there was no problem solving involved, just pure implementation. In fact, a lot of this quick prototype made use of tricks and techniques I’d figured out over the past few years of Playdate development, in particular during the development of my game &lt;a href=&quot;/tag/dailydriver/&quot;&gt;Daily Driver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the end of the first day you could drive the tank around the screen, fire the ball at an angle selected using the crank or d-pad, and when you run out of balls it would trigger game over. What it didn’t have is any logic that would collide the ball with the holes, point scoring, or any sort of win state. For some reason at this point I thought it would be cool to have a black tank with three wheels.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/ball-und-panzer-golf-03.gif#playdate&quot; alt=&quot;GIF&quot; title=&quot;The state of the game the end of the first day&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;day-1-timeline&quot;&gt;Day 1: Timeline&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Time&lt;/th&gt;
      &lt;th&gt;Event&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;15:40&lt;/td&gt;
      &lt;td&gt;draw a single “hole”: flag, hole, green&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;16:00&lt;/td&gt;
      &lt;td&gt;loop to draw 18 holes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:00&lt;/td&gt;
      &lt;td&gt;(dinner)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:00&lt;/td&gt;
      &lt;td&gt;draw tank&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:20&lt;/td&gt;
      &lt;td&gt;(ask permission from kata68k)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;19:00&lt;/td&gt;
      &lt;td&gt;background (interesting use of Perlin and random)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;20:00&lt;/td&gt;
      &lt;td&gt;add controls to tank&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;20:10&lt;/td&gt;
      &lt;td&gt;(send update to kata68k)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;21:00&lt;/td&gt;
      &lt;td&gt;limit tank to circular area (tricky but fun)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22:00&lt;/td&gt;
      &lt;td&gt;ball moving&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22:10&lt;/td&gt;
      &lt;td&gt;(kata68k confirms he’s OK with my version)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22:30&lt;/td&gt;
      &lt;td&gt;add game states&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;23:00&lt;/td&gt;
      &lt;td&gt;add ball height capability&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;23:45&lt;/td&gt;
      &lt;td&gt;(send update to kata68k)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;00:26&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://twitter.com/gingerbeardman/status/1670573820581650434&quot;&gt;tweet about it&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;week-1&quot;&gt;Week 1&lt;/h2&gt;

&lt;p&gt;The following day I added a first draft of collisions and scoring. There was slower progress, but it was still very steady with no problems encountered. I was keenly aware that the collision and scoring were very naïve and that I would have to refine and improve them going forward. But, regardless, at the end of the second day it was possible to play a complete round of tank golf!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/ball-und-panzer-golf-04.gif#playdate&quot; alt=&quot;GIF&quot; title=&quot;How the game was shaping up at the end of the second day&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The next two days consisted of fine tuning, polishing, play testing, bug fixing, adding sound effects, and so on. I even managed to record a GIF where I got all 18 flags …my first speed run!? I also received some great early encouragement and feedback from Playdate Squad community members: Donald &lt;a href=&quot;https://twitter.com/Guv_Bubbs&quot;&gt;@Guv_Bubbs&lt;/a&gt;, Steve &lt;a href=&quot;https://twitter.com/ScenicSoftware&quot;&gt;@ScenicSoftware&lt;/a&gt;, Atsu &lt;a href=&quot;https://twitter.com/SquidGodDev&quot;&gt;@SquidGodDev&lt;/a&gt;, Neven &lt;a href=&quot;https://twitter.com/mrgan&quot;&gt;@neven&lt;/a&gt; and of course &lt;a href=&quot;https://twitter.com/kata68k&quot;&gt;@kata68k&lt;/a&gt; himself! The game was shaping up nicely and coming together very quickly.&lt;/p&gt;

&lt;p&gt;Opportune timing meant that I could get the game in the hands of some testers on a live stream, over at IGDA Twin Cities (MN, USA) as part of their monthly Playtest. Thanks to &lt;a href=&quot;https://twitter.com/Mark_LaCroix&quot;&gt;Mark LaCroix&lt;/a&gt; for sorting that out! It was great to watch them play the game for the first time as they discovered the details of gameplay, mechanics, controls, and scoring! I even spotted a bug.&lt;/p&gt;

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

&lt;p&gt;For the rest of the week I polished and played, addressing areas that I thought could be improved. I added accessibility improvements, more opportunities to score, better hole randomisation, even wind effects, and so much more besides.&lt;/p&gt;

&lt;p&gt;For the last couple of days of the week I kept release notes, as I felt the pace of progress slowing down towards the end of my self-imposed deadline of one week. I would categorise most changes in those final days as either quality of life improvements or minor bug fixes.&lt;/p&gt;

&lt;p&gt;I had not optimised any of the code and was targeting the default 30 frames per second. On the final evening I optimised all text drawing in my main update path, as I could see that was responsible for a huge portion of where my game was spending its time, with the CPU pegged at 100% and the frame rate not able to hit the target 30fps. So the flags with numbers on were pre-rendered as images during initialisation, and the HUD is drawn only when its contents change, and the image is cached for use at other times. These two small and quick optimisations reduced the CPU usage by 20% and put me at a solid 30fps.&lt;/p&gt;

&lt;p&gt;There are many more optimisations to be made but this is not the time to do them. There’s too much still to do and I don’t want to lose focus. But for example: I’m not currently using the Playdate SDK Sprite system and am instead doing things the traditional way of drawing everything every update, so I’m positive I can get some good gains by drawing to a few different sprite layers. There are also some functions I call regularly, such as one that counts remaining holes, that can be optimised or avoided by improving my game logic. I’m confident I can get the CPU time down a good bit more. There’s no real need to do so, but I figure anything that will help reduce power usage or is good for the player is a honourable responsibility for a developer with a conscience to take on. If that goes well I may even increase the frame rate for smoother ball movement.&lt;/p&gt;

&lt;h2 id=&quot;shaping-up&quot;&gt;Shaping up&lt;/h2&gt;

&lt;p&gt;All the graphics are still composed with filled ellipses, rectangles and lines, varying their dithering patterns to provide different textures. I really leaned into this and over the course of the week fine tuned the tank from a three-wheeled blob to a high contrast, detailed, carefully animated sprite that you might think has been rendered externally. Maybe in a forthcoming post I can put together an exploded diagram of how it’s drawn?&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/ball-und-panzer-golf-05.gif#playdate&quot; alt=&quot;GIF&quot; title=&quot;Ball und Panzer Golf for Playdate, at the end of the first week&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;

&lt;p&gt;The only things I didn’t manage to fit in in my week sprint were a couple of animations I think will help make the game feel even higher quality and that will increase players ability to read/understand what is happening on screen. I need to make some more changes to the wind feature and the way the flag flaps in the wind. Currently the flag direction is correct but I’d like the length and frequency to be relative to the strength of the wind.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pretty much all of the maths in this game is high school algebra and trigonometry, wrapped up in some smoke and mirrors to make it feel magical.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also on the task list are a couple more sound effects, with the goal being one sound effect for each important event or action in the game. I feel that sounds cues are as important as visual cues and players could pick up on one or the other so there should ideally be parity.&lt;/p&gt;

&lt;p&gt;I can’t keep up the insanely fast pace of development from this first week, not only because of the physical and mental toll it would take but also because the quick wins of the prototype phase are now gone. To add the animations I previously mentioned, the first serious refactoring of part of the code will be needed. So there is no choice but for progress to be slower from this point at least for a while.&lt;/p&gt;

&lt;p&gt;The big remaining task that will take a lot longer than a week is game structure and progression. I have ideas of how I can package the structure of the game now into a short experience with increasing difficulty level and unlimited replay-ability.&lt;/p&gt;

&lt;p&gt;But the bigger question is do I want it to be bigger than that? There are no shortage of ideas how how to mashup to concept of tanks and golf, only a shortage of time and budget to make it happen. I could even add a mini-golf type of experience where you’re playing a round of single screen holes with additional hazards pulled from both the golf and tank world.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;launched&quot;&gt;Launched!&lt;/h2&gt;

&lt;p&gt;My version of Ball und Panzer Golf was renamed Fore! Track and is available for Playdate via Catalog &lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;play.date/games/fore-track/&lt;/a&gt; or on &lt;a href=&quot;https://gingerbeardman.itch.io/fore-track&quot;&gt;itch where there’s a bargain offline version&lt;/a&gt;.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/ball-und-panzer-golf-fore-track.gif#playdate&quot; alt=&quot;GIF&quot; title=&quot;Demonstration of the launch version of &amp;lt;em&amp;gt;Fore! Track&amp;lt;/em&amp;gt;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/07/09/the-first-colour-playdate-game/&quot;&gt;The first colour Playdate game?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 26 Jun 2023 22:11:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/06/26/ball-und-panzer-golf-making-a-playdate-game-in-a-week/</guid>
        </item>
      
    
      
        <item>
          <title>Piskel for Playdate</title>
          <description>&lt;p&gt;I just pushed some changes to my Playdate-centric fork of Piskel:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/gingerbeardman/piskel-playdate/tree/dev-1047&quot;&gt;github.com/gingerbeardman/piskel-playdate/tree/dev-1047&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This fork:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;can be used to build desktop apps on latest operating systems
    &lt;ul&gt;
      &lt;li&gt;updated to future-proof dependencies and build process&lt;/li&gt;
      &lt;li&gt;builds for Windows, Linux, macOS&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;has Playdate-specific features
    &lt;ul&gt;
      &lt;li&gt;get frame size from imagetable filename&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;has quality-of-life improvements
    &lt;ul&gt;
      &lt;li&gt;save keyboard shortcut will export PNG&lt;/li&gt;
      &lt;li&gt;ignore warnings preference&lt;/li&gt;
      &lt;li&gt;turns off animated preview by default&lt;/li&gt;
      &lt;li&gt;different window size and positioning&lt;/li&gt;
      &lt;li&gt;stops nagging if run in WebKit&lt;/li&gt;
      &lt;li&gt;modern macOS icon&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;adds useful community improvements
    &lt;ul&gt;
      &lt;li&gt;Outliner tool&lt;/li&gt;
      &lt;li&gt;Dither modifier keys&lt;/li&gt;
      &lt;li&gt;Keyboard cursor&lt;/li&gt;
      &lt;li&gt;Shift Palette Color Index Brush&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;adds new default Pencil tool
    &lt;ul&gt;
      &lt;li&gt;draws in the opposite color to that of the start pixel&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;apple-silicon-support&quot;&gt;Apple silicon support&lt;/h3&gt;

&lt;p&gt;Check out the &lt;a href=&quot;https://github.com/gingerbeardman/piskel-playdate/blob/dev-1047/README.md&quot;&gt;readme&lt;/a&gt; for details on how to quickly generate a new build without having to build nw.js from scratch. It’s very easy! Thanks to &lt;a href=&quot;https://www.ayushmanchhabra.com&quot;&gt;Ayushman Chhabra&lt;/a&gt; for help and hints.&lt;/p&gt;

&lt;h3 id=&quot;image-table-support&quot;&gt;Image Table support&lt;/h3&gt;

&lt;p&gt;Most of the time I load Piskel and drop an image table (a sort of sprite sheet) on it. Having to manually enter frame/cell size dimensions got old really fast, so this was my main motivation for doing a custom build. It’s a simple hack that checks the file name and parses out the cell dimensions. Slightly more tricky was trigger changes to the import panel so that everything looked and worked as it should.&lt;/p&gt;

&lt;h3 id=&quot;ignore-warnings&quot;&gt;Ignore Warnings&lt;/h3&gt;
&lt;p&gt;After editing an image in Piskel the majority of the time I export it and then quit the app. The app always nags twice: firstly to make sure you wanted to “leave the site?” - a leftover from the fact this is a web tool at heart - and a secondly to make sure you want to “abandon unsaved changes?”. An option to ignore these warnings is such a time saver.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I also took the opportunity to add some useful features developed by the community.&lt;/p&gt;

&lt;h3 id=&quot;outliner-tool&quot;&gt;Outliner tool&lt;/h3&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://github.com/ElectricToy/piskel/pulls?q=is%3Apr+is%3Aclosed&quot;&gt;ElectricToy&lt;/a&gt; for this patch, it works like flood fill but only fills the outline of any pixels it hits. You can hold Cmd to do a slightly thicker outline including corners.&lt;/p&gt;

&lt;h3 id=&quot;dither-modifier-keys&quot;&gt;Dither modifier keys&lt;/h3&gt;

&lt;p&gt;Another one from &lt;a href=&quot;https://github.com/ElectricToy/piskel/pulls?q=is%3Apr+is%3Aclosed&quot;&gt;ElectricToy&lt;/a&gt;, this gives you 25% ad 75% dither patterns by holding modifier keys, in addition to the standard 50% checkerboard dither pattern.&lt;/p&gt;

&lt;h3 id=&quot;keyboard-cursor&quot;&gt;Keyboard cursor&lt;/h3&gt;

&lt;p&gt;I’m not sure how useful this really is, but I added it anyway. Thanks to &lt;a href=&quot;https://github.com/piskelapp/piskel/tree/keyboard-cursor&quot;&gt;juliandescottes&lt;/a&gt; for the patch. You can control the pixel cursor location using Alt+cursor, and space will activate the current tool at that location. I haven’t tried it but you could set up a game controller to use these keys and &lt;a href=&quot;https://readonlymemory.vg/the-making-of-speedball-2/&quot;&gt;draw like Dan Malone did&lt;/a&gt;!&lt;/p&gt;

&lt;h3 id=&quot;shift-palette-color-index-brush&quot;&gt;Shift Palette Color Index Brush&lt;/h3&gt;

&lt;p&gt;This tool allows you to do shading using neighbouring colours more easily. Thanks to &lt;a href=&quot;https://github.com/piskelapp/piskel/pull/887&quot;&gt;blurymind&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;And I even added a new tool myself!&lt;/p&gt;

&lt;h3 id=&quot;pencil-tool-new-default&quot;&gt;Pencil tool (new default)&lt;/h3&gt;

&lt;p&gt;Classic Macintosh style Pencil. Draws in the opposite color than that of the pixel the stroke begins on. If the stroke begins on transparent, or the secondary color, it draws in the primary color. If the stroke begins on the primary color, it draws in the secondary color. This minimizes the need to switch between selected colors. To draw in a single color you won’t need to change colors or tools at all.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 10 May 2023 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/05/10/piskel-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/05/10/piskel-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>Sparrow Solitaire Tile Workshop</title>
          <description>&lt;p&gt;A little known feature of &lt;a href=&quot;https://sparrowsolitaire.com&quot;&gt;Sparrow Solitaire&lt;/a&gt; is its ability to load user-generated content from files copied onto the &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt;. One day I thought it would be cool to combine two tile sets, so I built a web app to do it!&lt;/p&gt;

&lt;p&gt;A wild tile set builder appears! &lt;a href=&quot;https://sparrowsolitaire.com/workshop/&quot;&gt;sparrowsolitaire.com/workshop/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;3-in-1&quot;&gt;3-in-1&lt;/h2&gt;

&lt;p&gt;As well as allowing you to cherry pick tiles from existing sets (and sharing that selection data with people), you can also load your own image data into the “user” row. This is done using the Pasteboard text area, which accepts a few different types of data.&lt;/p&gt;

&lt;p&gt;First it accepts a PNG URL which will be loaded into the user row and must be the correct dimensions.&lt;/p&gt;

&lt;p&gt;Secondly it accepts data:image (recommended &amp;amp; most versatile) which can be of any dimension. You can easily copy data:image straight out of the &lt;a href=&quot;https://www.piskelapp.com&quot;&gt;Piskel&lt;/a&gt; pixel art web app. You can also generate it on the command line, using the small script below, or using &lt;a href=&quot;https://www.alfredforum.com/topic/20306-clipboard-image-to-data-uri/&quot;&gt;an Alfred workflow&lt;/a&gt; or similar automation.&lt;/p&gt;

&lt;noscript&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/gingerbeardman/c19ac6d2b8565fea9e3e45909ddddc9b&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/c19ac6d2b8565fea9e3e45909ddddc9b.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Thirdly, and finally, if you’d rather create a full tile set of your own, you can do it all in &lt;a href=&quot;https://www.piskelapp.com&quot;&gt;Piskel&lt;/a&gt; and save it online as a GIF. Paste this URL into Tile Workshop and you can save it straight to an image format suitable for use on your Playdate.&lt;/p&gt;

&lt;p&gt;Please do get in touch if you create any tile sets of your own!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/sparrow-solitaire-tile-workshop.png&quot; alt=&quot;PNG&quot; title=&quot;Sparrow Solitaire Tile Workshop&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 01 May 2023 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/05/01/sparrow-solitaire-tile-workshop/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/05/01/sparrow-solitaire-tile-workshop/</guid>
        </item>
      
    
      
        <item>
          <title>Sparrow Solitaire for Playdate</title>
          <description>&lt;p&gt;I’ve &lt;a href=&quot;/2022/07/13/sparrow-solitaire-for-playdate/&quot;&gt;previously&lt;/a&gt; written about the Playdate game Sparrow Solitaire, when we released the Early Access version of the game. That was almost 9 months ago and a lot has changed!&lt;/p&gt;

&lt;p&gt;This week the &lt;a href=&quot;https://vogelscript.itch.io/sparrow-solitaire/devlog/515286/sparrow-solitaire-v10&quot;&gt;hugely expanded full version&lt;/a&gt; of the game &lt;a href=&quot;https://vogelscript.itch.io/sparrow-solitaire&quot;&gt;released on itch.io&lt;/a&gt;, where it’s been for sale for a while, &lt;a href=&quot;https://play.date/games/sparrow-solitaire/&quot;&gt;and on Playdate Catalog&lt;/a&gt; the new on-device store. It even got its &lt;a href=&quot;https://sparrowsolitaire.com&quot;&gt;own website&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This post is a continuation of the history of the development of the game, going into the conceptual decisions and visual inspiration.&lt;/p&gt;

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

&lt;hr /&gt;

&lt;h2 id=&quot;mahjong-or-not-mahjong-that-is-the-question&quot;&gt;&lt;em&gt;Mahjong&lt;/em&gt; or &lt;em&gt;not Mahjong&lt;/em&gt;, that is the question?&lt;/h2&gt;

&lt;p&gt;This type of game is commonly called Mahjong Solitaire, because it uses mahjong tiles to form the layouts and it’s usually a single-player game. But the traditional tiles are somewhat inscrutable to newcomers. They are from a totally different game that goes by the name of &lt;em&gt;Mahjong&lt;/em&gt;, which further confuses the situation. For this reason most Mahjong Solitaire games use alternative, more recognisable and approachable tile patterns.&lt;/p&gt;

&lt;p&gt;Whilst reading about the history of mahjong, I discovered that in Chinese the game was originally called 麻雀 (pinyin: máquè)—meaning &lt;em&gt;sparrow&lt;/em&gt;. This seemed like a nice alternative name for the game, allowing me to use alliteration, and avoiding the complexity that comes with the traditional mahjong tiles, and of course having cultural sensitivity top of mind.&lt;/p&gt;

&lt;h2 id=&quot;a-brief-history-of-mahjong-solitaire&quot;&gt;A brief history of Mahjong Solitaire&lt;/h2&gt;

&lt;p&gt;Mahjong Solitaire is widely considered a Western invention in much the same way as French-suited playing cards and their related Patience/Solitaire games, but both have their origins in China.&lt;/p&gt;

&lt;p&gt;Computer game Mahjong Solitaire was originally &lt;a href=&quot;https://forest-flower.com/university_old/note.php?timestamp=2019-09-09+03%3A41%3A00&quot;&gt;created by Brodie Lockard in 1981 on the PLATO system and named Mah-Jongg&lt;/a&gt; after the game that uses the same tiles for play. Lockard &lt;a href=&quot;https://www.salon.com/2017/11/19/how-a-little-known-computer-network-system-changed-the-history-of-the-internet/&quot;&gt;claimed that it was based on a centuries-old Chinese game called “the Turtle”&lt;/a&gt; that he had been shown whilst in hospital after a serious accident that left him paralysed from the neck down.&lt;/p&gt;

&lt;p&gt;Not only did Brodie create the PLATO version in 1981 using only his mouth to type the code and draw the graphics, but he went on to make the Apple Macintosh version which was published in 1986 by Activision, as Shanghai, which sold millions of copies. Shanghai II came in 1989 and a whole new genre of games was born.&lt;/p&gt;

&lt;h2 id=&quot;wherefore-art-thou-macintosh&quot;&gt;Wherefore art thou Macintosh?&lt;/h2&gt;

&lt;p&gt;An interesting point about the first two versions of the computer game: they both had monochrome displays (also referred to as 1-bit). &lt;a href=&quot;https://forest-flower.com/university_old/note.php?timestamp=2019-09-09+03%3A41%3A00&quot;&gt;PLATO was orange on black&lt;/a&gt;, and &lt;a href=&quot;https://macgui.com/downloads/?file_id=14895&quot;&gt;Macintosh was black on white&lt;/a&gt;. Funnily enough Playdate is also monochrome: dark grey on pale grey. Upon seeing the Sharp Memory LCD display that is used in the Playdate I was instantly reminded of the original Apple Macintosh. I mean, I still use &lt;a href=&quot;/2021/04/17/turning-an-ipad-pro-into-the-ultimate-classic-macintosh/&quot;&gt;System 7 on an iPad Pro&lt;/a&gt; and have a 1991 Macintosh Classic at home so it did not take much to remind me, but it did.&lt;/p&gt;

&lt;p&gt;My initial explorations around 1-bit graphics happened on Macintosh, long before I had ever held a Playdate in my hands. I explored fill patterns, dithering algorithms (eventually discovering and popularising a &lt;a href=&quot;https://hbfs.wordpress.com/2013/12/31/dithering/&quot;&gt;little known algorithm&lt;/a&gt;), old drawing software optimised for 1-bit graphics, and diving head first into old clip art collections. Sparrow Solitaire could be considered the culmination of all of this exploration and gathered knowledge presented in a single game.&lt;/p&gt;

&lt;p&gt;Once I got hold of a Playdate I started thinking about implementation details. Coding prototypes, figuring out sizes, textures, shadows, frame rate, tricks, optimisations, but with no particular game in mind. Just sailing free across a sea of ideas.&lt;/p&gt;

&lt;p&gt;I struck upon a novel way to generate patterns of dots. Instead of using organised Beyer dither patterns, I used error diffusion dithering and fed it a solid colour. When a shade of grey is run through Burkes dithering algorithm it produces organic-looking patterns of dots that are ever so pleasing to the eye. Interestingly, other dithering algorithms don’t exhibit the same result. This image became a catalyst and the core of Sparrow Solitaire.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/sparrow-release-dots.png&quot; alt=&quot;PNG&quot; title=&quot;A shade of grey fed into the &amp;lt;em&amp;gt;Burkes&amp;lt;/em&amp;gt; dithering algorithm&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After this I drew the traditional Chinese mahjong tile set, and figured out a tile size that would allow the standard Mahjong Solitaire layouts to fit fully on screen. Unlike the PLATO and Macintosh games, which used border thickness to denote the height of a tile in the layout, I offset the tiles in a sort of isometric view and added a repeating shadow dither pattern. After this I drew regional variations for Japan, Europe and America. I drew tile sets inspired by the original late-90s Japanese Emoji set, others based on Egyptian and Toki Pona heiroglyphs, and an alphanumeric tile set I thought might be easiest for newcomers. I even recorded sounds of my own mahjong tiles.&lt;/p&gt;

&lt;h2 id=&quot;sunset&quot;&gt;Sunset&lt;/h2&gt;

&lt;p&gt;I found (and promptly forgot about until just now!) royalty free sound effects of sparrows, rain, button presses, confirmation tones. During all of this, amongst my normal music listening, I stumbled across a song that I thought sounded really great: it was the track &lt;a href=&quot;https://soundcloud.com/iiyume/starry-dish&quot;&gt;“Starry Dish” by Yuyake Monster&lt;/a&gt; a Japanese music producer. The track is a sort of bouncy low-fi video-game hip-hop and piano thing. I wanted to hear more by this musician, so I clicked on their profile and listened to the next track. It was “Herbal Remedies” and I liked it even more! It featured plucked strings and a melodic bass line and… about half way through… bird song! And these tunes were already tagged as royalty free, so it was meant to be. When I reached out to Yuyake Monster they were super happy to have their music used in a game.&lt;/p&gt;

&lt;p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;166&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; allow=&quot;autoplay&quot; src=&quot;https://w.soundcloud.com/player/?url=https%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F946034860&amp;amp;auto_play=false&amp;amp;show_artwork=true&amp;amp;visual=true&quot;&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It was at this point that I released a prototype with graphics, sounds and animation of the layout dealing. I quickly realised that it would take a lot of time and effort to finish the game, so I made the difficult decision to abandon it. But work resumed later on with the help of &lt;a href=&quot;https://twitter.com/vogelscript&quot;&gt;Mac Vogelsang&lt;/a&gt;, as you can read about in &lt;a href=&quot;/2022/07/13/sparrow-solitaire-for-playdate/&quot;&gt;my earlier blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;one-vision&quot;&gt;One vision&lt;/h2&gt;

&lt;p&gt;Mac had taken on the majority of the programming, slowly eroding what remained of my prototype code and refactoring things into a more solid foundation. I was concentrating on graphics and animation. We shared game design decisions. By this point we had both played a ton of mahjong solitaire games from the original Shanghai on Macintosh, through versions on almost every home console, handheld and home computer platform, to versions on the latest Nintendo consoles. We had a thorough understanding of things that worked well with a mouse, what did not work so well with a game controller, what worked best on screen and what limitations these old platforms imposed on the games.&lt;/p&gt;

&lt;p&gt;Comparing Playdate to the best handhelds from the 90s it became obvious that we were working with a much more capable device, in terms of both CPU power and graphical fidelity. We could lean into the strengths of the device and provide a tailored experience that made use of the Playdate’s unique control mechanism - the crank - as well as its excellent sound and graphics hardware. I hope Mac will write more about getting the most out of the device, writing code that made the most of Playdate performance to do things that have never been done before in a mahjong solitaire game. Sparrow Solitaire has truly ground-breaking features some of which are only possible on Playdate.&lt;/p&gt;

&lt;p&gt;Meanwhile, with the prompting and encouragement of Mac, I researched classic Chinese and Japanese painting techniques and drew some Eurasian tree sparrows and cherry blossom in the classic ink and watercolour style.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/sparrow-release-strokes.png&quot; alt=&quot;PNG&quot; title=&quot;Vector drawings of tree sparrow and blossom, using only strokes&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These illustrations were used to create the game’s launch animation. I started to create the animation the traditional way, but quickly changed tact and created it programatically instead.&lt;/p&gt;

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

&lt;h2 id=&quot;content-matters&quot;&gt;Content matters&lt;/h2&gt;

&lt;p&gt;For the full version of the game I created additional backgrounds, cursors, grids, animations, tools to aid development and more besides. I created several fonts, all carefully kerned and tweaked to look their best on the Playdate. I’d &lt;a href=&quot;/2020/10/03/found-whilst-backing-up-an-old-pc/&quot;&gt;created a popular font in my teens&lt;/a&gt; and it was fun to get back to that. More backgrounds, more tile sets including my favourite “zen”. Even now, when I see this tile set in the game, I often shake my head in disbelief. How was I capable of drawing these symbols so well at such a small size? My &lt;a href=&quot;https://www.youtube.com/watch?v=y4-2iTJW-2Y&quot;&gt;Susan Kare&lt;/a&gt; moment, if you will. If ever there was a lesson that you should believe in your own abilities, this was it for me.&lt;/p&gt;

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

&lt;h2 id=&quot;hidden-in-plain-sight&quot;&gt;Hidden in plain sight&lt;/h2&gt;

&lt;p&gt;The influence of the Macintosh and the nostalgia in the final look and feel of the game is something I wear proudly. Sparrow Solitaire is the sum total of all my passions and interests, wrapped up in a neat little game that will provide hundreds of hours of fun and relaxation.&lt;/p&gt;

&lt;p&gt;But what about if you look a little deeper? You will find brush/fill patterns from &lt;a href=&quot;https://en.wikipedia.org/wiki/MacPaint&quot;&gt;MacPaint&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Atkinson_dithering&quot;&gt;Atkinson dithering&lt;/a&gt; as first seen in ThunderScan, elements drawn in &lt;a href=&quot;/2021/04/06/ultrapaint-manual/&quot;&gt;UltraPaint&lt;/a&gt;, pixels generated using &lt;a href=&quot;https://en.wikipedia.org/wiki/Dave_Theurer&quot;&gt;DeBabelizer&lt;/a&gt;, and so much more. Heck, we even use &lt;a href=&quot;https://en.wikipedia.org/wiki/Garamond&quot;&gt;Garamond&lt;/a&gt; as our brand typeface which is the same one Apple used in their classic Macintosh advertising, the most well known of which is the “Think different” campaign. Sparrow Solitaire is my love letter to the Macintosh, but runs on a device that fits in the palm of your hand.&lt;/p&gt;

&lt;p&gt;One of my favourite “&lt;a href=&quot;https://en.wikipedia.org/wiki/Easter_egg_(media)&quot;&gt;Easter Eggs&lt;/a&gt;” in Sparrow Solitaire is some clip art from the &lt;a href=&quot;https://macintoshgarden.org/author/enzan-hoshigumi-co&quot;&gt;Scroll collections&lt;/a&gt;, published in 1986/87 by Japanese Macintosh specialists Enzan-Hoshigumi. Specifically I have used a border for the manual/credits overlay, and several sections of artwork for our patterned backgrounds. These sections of artwork were not bundled as brushes/fills, but are nevertheless easy to cut out and use as a repeating fill in an image editor. They are based on repeating elements that are much larger than the 16x16 pixel patterns of MacPaint, so they allow for patterns that are not as garish and easier on the eyes. Plus, I love the fact that in 2023 Enzan-Hoshigumi finally have their first video game credit, almost &lt;a href=&quot;/2021/12/16/tomoya-ikeda-macintosh-artist/&quot;&gt;40 years after working on their first video game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/sparrow-release-enzan-hoshigumi.png#playdate&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-great-wave-of-mahjong&quot;&gt;The Great Wave of Mahjong&lt;/h2&gt;

&lt;p&gt;Finally, to come full circle back to the image seen in the trailer at the top of the page: “The Great Wave of Mahjong”. I came up with the concept for the image and commissioned the wonderful &lt;a href=&quot;https://www.instagram.com/vxclhd/&quot;&gt;Vxcl&lt;/a&gt; to create it, leaving them free to interpret the brief. They pretty much nailed it first time, and there were only a handful of small notes from me - in the form of &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1646262994727321602?s=20&quot;&gt;screen grabs with coloured highlighter markup&lt;/a&gt; - to tweak the alignment of details of a few elements. This is the &lt;a href=&quot;/2021/08/23/daily-driver-teaser-artwork/&quot;&gt;second such render&lt;/a&gt; I’ve commissioned, a look inspired by 1980s Japanese PC/game mags.&lt;/p&gt;

&lt;p&gt;You can download a high resolution version of this image at &lt;a href=&quot;https://vogelscript.itch.io/sparrow-solitaire&quot;&gt;itch.io&lt;/a&gt; to use as PC/phone wallpaper.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/great-wave-of-mahjong-by-vxcl.jpg&quot; alt=&quot;JPG&quot; title=&quot;&amp;lt;em&amp;gt;&amp;#x201C;The Great Wave of Mahjong&amp;#x201D;&amp;lt;/em&amp;gt;, by Vxcl&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;update&quot;&gt;Update!&lt;/h2&gt;

&lt;p&gt;Less than two weeks after version 1.0 we released an update to address a few small issues and add a bunch more content! It features dynamic weather effects, three new tile sets and more cool stuff. You can &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1649809550239846405&quot;&gt;read about version 1.1 in this Twitter thread&lt;/a&gt;.&lt;/p&gt;

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

&lt;ul&gt;
  &lt;li&gt;2024-03-08—&lt;a href=&quot;https://play.date/games/community-awards-2023/&quot;&gt;Playdate Community Awards 2023: Best Puzzle Game&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Thu, 13 Apr 2023 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/04/13/sparrow-solitaire-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/04/13/sparrow-solitaire-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>Sparrow Solitaire for Playdate (Early Access)</title>
          <description>&lt;p&gt;This post is a history of Sparrow Solitaire, my latest game for Playdate. It started out as a prototype and was later taken on by Mac Vogelsang. We’re working together to make the best ever mahjong solitaire game.&lt;/p&gt;

&lt;h3 id=&quot;2021-january-10th&quot;&gt;2021, January 10th&lt;/h3&gt;
&lt;p&gt;I’m really into Hanafuda (Japanese flower cards) and found that the 1991 Macintosh game Shanghai featured tiles based on Hanafuda. I booted it up &lt;a href=&quot;https://archive.org/details/ShanghaiIIDragonsEye&quot;&gt;at Internet Archive&lt;/a&gt; and had a quick play.&lt;/p&gt;

&lt;p&gt;For the next week or so I went down the Shanghai rabbit hole. Its history is &lt;a href=&quot;http://home.halden.net/vkp/vkp/origin.html&quot;&gt;quite interesting&lt;/a&gt; and fraught with drama so I won’t repeat it here.&lt;/p&gt;

&lt;h3 id=&quot;2021-january-19th&quot;&gt;2021, January 19th&lt;/h3&gt;

&lt;p&gt;This is the date on the first screenshot I captured of my prototype mahjong solitaire game for Playdate.&lt;/p&gt;

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

&lt;p&gt;I tested different size tiles until I found the sweet spot that could fit a whole layout on the Playdate’s 400x240 screen.&lt;/p&gt;

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

&lt;p&gt;The following day I drew a set of tiles and it started to look like a real game! I called it &lt;em&gt;Sparrow Solitaire&lt;/em&gt; because the sound of mahjong tiles rubbing together is said to sound like sparrows chirping. In fact, the original Chinese name for mahjong reused the word for sparrow. So this thing had a name at least!&lt;/p&gt;

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

&lt;p&gt;I spent the next month or so, on-and-off, programming object-oriented Lua to manage the game state, tiles, layout, dealing, sound effects and I drew a handful more tile sets.&lt;/p&gt;

&lt;p&gt;Daily Driver was my focus and this little puzzle game was supposed to be a short palate cleanser to keep things interesting. When it turned out to be more involved I lost momentum and abandoned it. My todo list at that time was as follows:&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;Todo
[ ] calc free data per half position
[ ] sound based on free data
[ ] controls to move prev/next tiles
[ ] controls to select tiles
[ ] match tiles
[ ] face down all tiles
[ ] face up free tiles
[ ] editor
[ ] optimise animations

Done
[X] sound effects
[X] define layouts to fill screen
[X] animate tiles
[X] multi-dimension array (as autotable)
[X] ability to reinit table
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What an optimistically short set of todo items! That’s game programmers for you.&lt;/p&gt;

&lt;h3 id=&quot;2022-april-15th&quot;&gt;2022, April 15th&lt;/h3&gt;

&lt;p&gt;When Playdate launched I bundled the game in my pack of Prototypes and tried to forget about it.&lt;/p&gt;

&lt;h3 id=&quot;2022-may-12th&quot;&gt;2022, May 12th&lt;/h3&gt;

&lt;p&gt;I couldn’t stop thinking about the game and it seemed a shame that I had put so much effort into it and then abandoned it. But I’m a realist and I knew that I would never finish it myself. So I thought it would be fun to offer it out to any interested developers and see if they wanted to finish it. No replies to &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1524724007827914752&quot;&gt;this tweet&lt;/a&gt;, but the same message on the Playdate Squad Discord server had some responses. I decided to hand over to Mac Vogelsang (&lt;a href=&quot;https://twitter.com/vogelscript&quot;&gt;@vogelscript&lt;/a&gt;) as I had really enjoyed his Bird and Beans game. So a few days later he began work “finishing off” my programming. What an understatement.&lt;/p&gt;

&lt;h3 id=&quot;2022-july-13th&quot;&gt;2022, July 13th&lt;/h3&gt;

&lt;p&gt;For the last two months Mac has worked his magic on the game and I’ve tagged along for the ride. Having worked on my own for so long I wondered what it would be like working with somebody else. It was more fun and productive than I ever could have expected! Mac brought the best out of me, pushing me to achieve better results than I would have otherwise been happy with. Mac also pushed himself, creating what I believe to be a world-first method of cursor control in a mahjong solitaire game, made possible by two properties of Playdate: its fast processing speed and its lack of touch screen!&lt;/p&gt;

&lt;p&gt;Out of this partnership not only comes a great game, but some new offshoot projects like an Animation tool (from me), a board game (from Mac), and a Game Options class (Mac’s magic reworking of one of my old classes) and several new fonts some of which are used in Sparrow Solitaire. These projects should all release in the near future.&lt;/p&gt;

&lt;p&gt;At this point Sparrow Solitaire is really Mac’s game, albeit one that happens to have my graphics and some of my programming DNA in it, so it’s fitting that it will live on his itch.io page.&lt;/p&gt;

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

&lt;h2 id=&quot;launch-animation&quot;&gt;Launch Animation&lt;/h2&gt;

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

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

&lt;p&gt;Update, April 2023: the full version of Sparrow Solitaire for Playdate is &lt;a href=&quot;https://vogelscript.itch.io/sparrow-solitaire&quot;&gt;available now on itch.io&lt;/a&gt; and you can also &lt;a href=&quot;https://vogelscript.itch.io/sparrow-solitaire/devlog/515286/sparrow-solitaire-v10&quot;&gt;read about what to went into the full game&lt;/a&gt;.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 13 Jul 2022 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2022/07/13/sparrow-solitaire-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2022/07/13/sparrow-solitaire-for-playdate/</guid>
        </item>
      
    
      
        <item>
          <title>Circular for Playdate</title>
          <description>&lt;p&gt;This concept was a typical prototype: the core action came together quickly but then making that into a game took a while.&lt;/p&gt;

&lt;p&gt;At some point soon I’ll write a devlog about the problems, solutions, questions and answers involved in its development.&lt;/p&gt;

&lt;p&gt;I’m really proud of this little gem. It’s my first time putting a lot of effort into score balancing in the style of my favourite Japanese arcade games, and I hope you have fun trying to reach 1 million points. There are sure to be some surprises along the way.&lt;/p&gt;

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

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

&lt;p&gt;Circular for Playdate is &lt;a href=&quot;https://gingerbeardman.itch.io/circular&quot;&gt;available now on itch.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have fun!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 30 May 2022 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2022/05/30/circular-for-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2022/05/30/circular-for-playdate/</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>Aquaplus P/ECE: Game Reviews Vol. 2</title>
          <description>&lt;p&gt;If you’ve not yet read &lt;a href=&quot;/2021/08/19/aquaplus-piece-vs-panic-playdate/&quot;&gt;my review of the P/ECE&lt;/a&gt; hardware comparing it to Playdate, now is the time to catch up! It includes my 6 favourite games on the system.&lt;/p&gt;

&lt;p&gt;Below I cover more of the interesting and unique games that I enjoy on the system.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;h3 id=&quot;delta-star-by-black-ftz&quot;&gt;Delta Star, by BLACK FTZ&lt;/h3&gt;

&lt;p&gt;A new game for P/ECE in 2021! Well, technically it’s a re-release but I’ll take what I can get after all these years. Especially for only &lt;a href=&quot;https://booth.pm/en/items/3223530&quot;&gt;500¥ at BOOTH&lt;/a&gt; (pixiv account required).&lt;/p&gt;

&lt;p&gt;At first glance you might think this is a simple Asteroids clone, but you’d be wrong. Whilst it does share much with that concept it adds in enough of its own ideas to make it unique.&lt;/p&gt;

&lt;p&gt;First, the big differences: you can pass through enemies if you’re careful, and you can shoot down enemy bullets. That’s right, these aren’t lifeless hunks of rock floating around in space! There are multiple types of enemies some of which will fire either normal bullets or homing missiles. Your ship has forwards and reverse thrust, forward dash (a predictable and much more enjoyable equivalent of Asteroid’s teleport), sideways strafing (which I should use more often), and emergency braking. You can fire multiple shots at once, as expected, but they’re limited in number and have a short range of roughly a quarter of the width of the screen.&lt;/p&gt;

&lt;p&gt;There are 52 levels, with 4 boss encounters, and continue points. Gameplay is against the clock with remaining time being converted into points. Extra lives are awarded at key point milestones and greeted with one of many great sound effects. The initial levels are easy and can be completed quickly, almost like a tutorial, and after that difficulty ramps up quite nicely. As an example, enemy bullets aren’t seen until level 9. And I adore for the motivational messages at the end of each level that let you know how you did “don’t mind, nobody is perfect”.&lt;/p&gt;

&lt;p&gt;The game features smooth 60fps vector graphics with sub-pixel accuracy, plus some nice effects using multiple lines of different shades of grey. And amazing chip tunes. Listen to the high score entry tune at the end of the above video!&lt;/p&gt;

&lt;p&gt;I’ve been playing the free “Gratuitous Version” which has only one boss type and lacks music, so I’m excited to spend more time with this fleshed out version now that it’s available once again. Awesome work by BLACK FTZ team, what a lovely surprise for 2021.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-speed-barricade.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;speed-barricade-by-kenta-cho-aba-games&quot;&gt;Speed Barricade, by Kenta Cho (ABA Games)&lt;/h3&gt;

&lt;p&gt;This was previously shown in my blog post comparing the P/ECE with Playdate, but I neglected to mention one cool thing…&lt;/p&gt;

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

&lt;h3 id=&quot;speed-barricade-live-by-kenta-cho-aba-games&quot;&gt;Speed Barricade LIVE, by Kenta Cho (ABA Games)&lt;/h3&gt;

&lt;p&gt;If you play with your P/ECE connected using USB cable to your Windows PC you can use a special client app, &lt;a href=&quot;http://www.asahi-net.or.jp/\~cs8k-cyu/piece/sbrg.html&quot;&gt;Speed Barricade LIVE&lt;/a&gt;, to show real-time 3D footage of your game in full colour at high resolution! A true spectator sport.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;h3 id=&quot;majang-project-by-hiroshi-makabe&quot;&gt;Majang Project, by Hiroshi Makabe&lt;/h3&gt;

&lt;p&gt;This game came out of the first of two P/ECE Hand Books written by &lt;a href=&quot;https://twitter.com/sinpen&quot;&gt;Hiroshi Makabe&lt;/a&gt;. The development of the game was outlined in the book and on an &lt;a href=&quot;https://web.archive.org/web/20021215064406/http://www.jc2.co.jp/p/contents2.html&quot;&gt;accompanying website&lt;/a&gt;. It has impressive greyscale graphics, nice animation, wonderful chip tune music, sampled voice clips, win/lose scenes, and hosts a nice VS CPU game of Mahjong. And that’s Riichi/Japanese Mahjong, not the Solitaire type you might associate with the name. I’d say this game is definitely of retail-release quality and was a good showcase for what is possible on the platform.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;h3 id=&quot;alert-アラート-by-jumpei-isshiki-hello-world-project&quot;&gt;ALERT (アラート), by Jumpei Isshiki (HELLO WORLD PROJECT)&lt;/h3&gt;

&lt;p&gt;A nice little barrage shmup with procedurally generated boss fights! Prize winner in the &lt;a href=&quot;https://www.coremagazine.co.jp/megastore/piece/result.html&quot;&gt;Megastore Cup official contest&lt;/a&gt;. There’s a lot going on here, not just the bullets but also the gameplay logic and music. Remaining enemy bullets turn to points which offers an engaging risk/reward mechanic. Three game modes, Easy, Hard, and ENDLESS. Let’s go for the high score!&lt;/p&gt;

&lt;p&gt;I read that there was once a &lt;a href=&quot;https://mao.5ch.net/test/read.cgi/linux/1262615084&quot;&gt;port of this game&lt;/a&gt; to Dingux-based devices, and found it on a &lt;a href=&quot;https://www.axfc.net/u/976888.zip&quot;&gt;download site&lt;/a&gt; (I guessed “alert” was the password, how lucky!).&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-delivery-bird.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;delivery-bird-by-hafupon&quot;&gt;Delivery Bird, by Hafupon&lt;/h3&gt;

&lt;p&gt;This is a Sokoban that shifts the view to side-on rather than top-down, which allows for height and gravity to enter into the puzzles. As with many of the best games on P/ECE, it was entered into one of the official game development &lt;a href=&quot;https://aquaplus.jp/piece/contest/index.html&quot;&gt;contests run by Aquaplus&lt;/a&gt;. This game won a runner-up prize.&lt;/p&gt;

&lt;p&gt;There’s also a &lt;a href=&quot;https://www.vector.co.jp/magazine/softnews/050608/n0506085.html&quot;&gt;Windows version&lt;/a&gt; that expands on the concept.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-norito.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;norito-by-gorujii&quot;&gt;Norito, by Gorujii&lt;/h3&gt;

&lt;p&gt;Another runner-up in the &lt;a href=&quot;https://www.coremagazine.co.jp/megastore/piece/result.html&quot;&gt;Megastore Cup official contest&lt;/a&gt;. A great looking game featuring fluid wire-frame 3D with dithering for distant objects. It’s a strange sort of shmup that makes me think of both Jumping Flash and Battlezone. Controls take some getting used to but after that score attack is quite a challenge. The game system and stages are intertwined with a story, and things like the “one-shot” fortune-telling add extra flair.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;In the next volume of reviews I’ll cover the official bundled games in addition to some homebrew ports of popular games from other systems.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 28 Aug 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/08/28/aquaplus-piece-game-reviews-vol-2/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/08/28/aquaplus-piece-game-reviews-vol-2/</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>Aquaplus P/ECE (vs Panic Playdate)</title>
          <description>&lt;p&gt;The P/ECE was a Japan-only handheld/mobile gaming console released in late-2001. It was created by Aquaplus, a company better known for visual novels, so it carries with it a certain oddball charm. After 20 years I find it heart-warming to see that the &lt;a href=&quot;https://aquaplus.jp/piece/&quot;&gt;official website&lt;/a&gt; is still online!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece.jpg&quot; alt=&quot;Aquaplus P/ECE&quot; title=&quot; Aquaplus P/ECE, launch edition&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-is-it&quot;&gt;What is it?&lt;/h2&gt;

&lt;p&gt;An &lt;a href=&quot;https://game.watch.impress.co.jp/docs/20011203/piece.htm&quot;&gt;early preview of the P/ECE&lt;/a&gt; referred to the P/ECE as a portable game console though the water was somewhat muddied by the fact that one of the built in apps, Picket, was a PIM (Personal Information Manager). This means it was often referred to as a PDA. It has a d-pad, start and select buttons, and you can play games on it—that makes it a console in my eyes. But a console where each unit could be used as a development device by connecting it to a desktop computer.&lt;/p&gt;

&lt;p&gt;Looking back at the P/ECE it was ahead of its time in many ways. To get a feel for what was going on when it came out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the most popular phone manufacturers were Nokia and Sony Ericsson&lt;/li&gt;
  &lt;li&gt;the battle between the PlayStation 2 and Dreamcast was ongoing&lt;/li&gt;
  &lt;li&gt;ICO was the latest PlayStation 2 game&lt;/li&gt;
  &lt;li&gt;Rez was the latest Dreamcast game&lt;/li&gt;
  &lt;li&gt;the iPod and Game Boy Advance had both just launched&lt;/li&gt;
  &lt;li&gt;YouTube and Facebook had yet to be created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…it really was a different era!&lt;/p&gt;

&lt;h2 id=&quot;discovery&quot;&gt;Discovery&lt;/h2&gt;

&lt;p&gt;I learned of the P/ECE several years ago when looking at the output of one of my favourite video game developers, Kuniake “kuni” Watanabe. He’s probably best known for &lt;a href=&quot;https://www.siliconera.com/panekit-the-infinitive-crafting-toy-case-game-finally-sees-profits-after-13-years/&quot;&gt;Panekit&lt;/a&gt;, and open-world sandbox game on PlayStation. Though my favourite of his games is &lt;a href=&quot;/2013/06/29/maboshi/&quot;&gt;MaBoShi&lt;/a&gt; on Wii (and DS via download play) which is a razor sharp focussed set of three games that interact with each other in an innovative (and &lt;a href=&quot;https://www.google.com/patents/US20090093314&quot;&gt;patented&lt;/a&gt;) way. I’d go so far as to say that &lt;a href=&quot;https://www.youtube.com/watch?v=MiVbBi0jdhw&quot;&gt;Circle mode in MaBoShi&lt;/a&gt; is my favourite game concept of all time.&lt;/p&gt;

&lt;p&gt;The same preview article I mentioned above also says “in terms of specs, (P/ECE) comes above the PocketStation and below the Game Boy” though I think that’s selling the P/ECE a little short. The CPU was quite fast and efficient for its time, and the fact that it had no specific graphics hardware meant that people had to get creative and optimise. Some of the results, seen below, would have been very tricky or impossible to achieve on the original Game Boy.&lt;/p&gt;

&lt;p&gt;Over its short term of popularity the P/ECE platform had a vibrant homebrew scene and received ports or demakes of everything from Mitchell Corp’s masterpiece Polarium, through arcade gems like Flipull (aka Plotting), to console classics Panel de Pon (aka Tetris Attack) and more besides. It was home to proper Japanese RPGs and small arcade games and the number of games stretch into triple figures.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;selected-games-by-kuniake-watanabe&quot;&gt;Selected Games by Kuniake Watanabe&lt;/h2&gt;

&lt;p&gt;Anyway! During his indie days, Kuni developed for a &lt;a href=&quot;https://k-u.hatenadiary.org/entries/1970/01/01&quot;&gt;range of platforms&lt;/a&gt; and in a variety of languages and wrote several games for the P/ECE. Here are my favourites:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-spout.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;spout&quot;&gt;spout&lt;/h3&gt;

&lt;p&gt;A lunar lander style game where your thrust can destroy the scenery. Your task is to get as high as possible, though that is much easier said than done as the scenery becomes gradually more complex and time is always ticking away.&lt;/p&gt;

&lt;p&gt;This is perhaps the most famous P/ECE game as it was released with source code for an SDL version and received ports to GP32 and related platforms. There’s a &lt;a href=&quot;http://microtrip-game.com/spout/&quot;&gt;modern remake for iOS and Android&lt;/a&gt; by a different developer, though it keeps the same title.&lt;/p&gt;

&lt;p&gt;edit: &lt;a href=&quot;https://twitter.com/k_u/status/1429654871532212224?s=21&quot;&gt;kuni’s game design notes&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-fencer.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;fencer&quot;&gt;fencer&lt;/h3&gt;

&lt;p&gt;A snake type game where your head has momentum and your tail length depends on your speed, allowing fine and fluid movement. The goal is to avoid bombs and use your tail as a barrier that will destroy them. Chaining together multiple explosions is the key to high scores.&lt;/p&gt;

&lt;p&gt;There was an official updated version for iOS, titled fencell, but it’s not longer on the App Store due to Apple removing all 32-bit games back in 2017. Yet the 20-year-old P/ECE version is &lt;a href=&quot;http://www.susami.co.jp/kuni/junk/junk.htm&quot;&gt;still available for download&lt;/a&gt;. There’s a lesson for us all.&lt;/p&gt;

&lt;p&gt;edit: &lt;a href=&quot;https://twitter.com/k_u/status/1429654873574830080?s=21&quot;&gt;kuni’s game design notes&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-interground.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;interground&quot;&gt;interground&lt;/h3&gt;

&lt;p&gt;In this game you must use a rotating stick to push sand around in an attempt to bury the little people running on top of it. If the stick touches a moving enemy or you run out of time that means game over.&lt;/p&gt;

&lt;p&gt;edit: &lt;a href=&quot;https://twitter.com/k_u/status/1429654875755802631?s=21&quot;&gt;kuni’s game design notes&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;selected-games-by-kenta-cho-aba-games&quot;&gt;Selected Games by Kenta Cho (ABA Games)&lt;/h2&gt;

&lt;p&gt;Whilst digging into P/ECE forgotten history, I also found several old games by Kenta Cho (then: Saba, now: ABA Games) who even today continues to &lt;a href=&quot;http://www.asahi-net.or.jp/\~cs8k-cyu/browser.html&quot;&gt;crank out brilliant little games&lt;/a&gt; at an inspiring rate.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-barrage-reactor.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;barrage-reactor&quot;&gt;Barrage Reactor&lt;/h3&gt;

&lt;p&gt;A twin-stick shmup played with a d-pad and two buttons! The d-pad moves your ship around and the buttons rotate your aim as you fire automatically. Waves of enemies appear and a surprisingly tactical game ensues.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-speed-barricade.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;speed-barricade&quot;&gt;Speed Barricade&lt;/h3&gt;

&lt;p&gt;A 3D “Tron” light cycles game where the aim is to stay alive for as long as possible whilst outwitting an increasing number of computer controlled players. Quick reflexes are definitely needed for this one!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-re.gif#piece&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;re-w32badtrance&quot;&gt;Re: W32/Badtrance&lt;/h3&gt;

&lt;p&gt;A 3D demake of Rez where the Microsoft Outlook(!) icon produces waves of enemies that need to be shot down as efficiently as possible by locking on to multiple enemies before firing your missiles. With the strap line “Gentlemen, open your Outlook. Go to Cyberterrorism.” maybe Kenta Cho was sick of email back in 2001?&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;If there’s enough interest I’ll feature more P/ECE games in a future blog post. Let me know on the comments link at the bottom of the page.&lt;/p&gt;

&lt;h2 id=&quot;20-years-later&quot;&gt;20 years later&lt;/h2&gt;

&lt;p&gt;Here we are in 2021 and Panic’s Playdate will soon be shipping. By now I’m sure you’ve spotted several similarities between the P/ECE and Playdate. The main one being that both devices offer immediate, unfettered access to developers wishing to make their own games, and those games can be side-loaded.&lt;/p&gt;

&lt;p&gt;Of course, 20 years is a long time in technology so the Playdate has many improvements compared to what the P/ECE achieved in 2001. More storage, faster CPU, better connectivity, plus a bigger and better screen. Playdate offers cross-platform development using either C or Lua and, and you won’t need a device to develop for the platform. In contrast, P/ECE offered development only for Windows and only in C, plus you needed a device to test your code. A &lt;a href=&quot;https://github.com/autch/piemu&quot;&gt;homebrew emulator&lt;/a&gt; was eventually released, which is what I’ve used for the screen recordings shown earlier.&lt;/p&gt;

&lt;p&gt;Oh, and both devices have names that make web searches a bit tricky. 😅&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/piece-vs-playdate.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;specifications-comparison&quot;&gt;Specifications Comparison&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Playdate&lt;/th&gt;
      &lt;th&gt;P/ECE&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Manufacturer&lt;/td&gt;
      &lt;td&gt;Panic&lt;/td&gt;
      &lt;td&gt;Aquaplus&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release date&lt;/td&gt;
      &lt;td&gt;2021&lt;/td&gt;
      &lt;td&gt;2001&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Battery (active)&lt;/td&gt;
      &lt;td&gt;8h&lt;/td&gt;
      &lt;td&gt;2h&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Battery (standby)&lt;/td&gt;
      &lt;td&gt;2w&lt;/td&gt;
      &lt;td&gt;1w&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Battery (type)&lt;/td&gt;
      &lt;td&gt;Rechargeable internal battery&lt;/td&gt;
      &lt;td&gt;1×AA&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPU (type)&lt;/td&gt;
      &lt;td&gt;STMicroelectronics STM32F746&lt;br /&gt;(ARM Cortex-M7F)&lt;/td&gt;
      &lt;td&gt;EPSON S1C33209&lt;br /&gt;(32-bit RISC)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPU (speed)&lt;/td&gt;
      &lt;td&gt;180 MHz&lt;/td&gt;
      &lt;td&gt;24 Mhz&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Storage (RAM)&lt;/td&gt;
      &lt;td&gt;16MB&lt;/td&gt;
      &lt;td&gt;256KB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Storage (Flash)&lt;/td&gt;
      &lt;td&gt;4GB (3.9GB usable)&lt;/td&gt;
      &lt;td&gt;512KB (348KB usable)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Device (colour)&lt;/td&gt;
      &lt;td&gt;Yellow&lt;/td&gt;
      &lt;td&gt;Silver&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Device (dimensions)&lt;/td&gt;
      &lt;td&gt;76×74×9mm&lt;/td&gt;
      &lt;td&gt;101×65×17mm&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Device (inputs)&lt;/td&gt;
      &lt;td&gt;D-pad, A, B, Menu, Sleep, Accelerometer, Crank&lt;/td&gt;
      &lt;td&gt;D-pad, A, B, Start, Select&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Device (weight)&lt;/td&gt;
      &lt;td&gt;85g&lt;/td&gt;
      &lt;td&gt;92g&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Data connection (wired)&lt;/td&gt;
      &lt;td&gt;USB-C to A&lt;/td&gt;
      &lt;td&gt;USB-B to A&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Data connection (wireless)&lt;/td&gt;
      &lt;td&gt;Wi-Fi &amp;amp; Bluetooth&lt;/td&gt;
      &lt;td&gt;Infrared (IR)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sound (type)&lt;/td&gt;
      &lt;td&gt;Software synthesis, Digital audio&lt;/td&gt;
      &lt;td&gt;Software synthesis&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sound (speaker)&lt;/td&gt;
      &lt;td&gt;Mono&lt;/td&gt;
      &lt;td&gt;Mono&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sound (headphone)&lt;/td&gt;
      &lt;td&gt;Stereo&lt;/td&gt;
      &lt;td&gt;Mono&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Display (technology)&lt;/td&gt;
      &lt;td&gt;Sharp Memory LCD&lt;/td&gt;
      &lt;td&gt;FSTN LCD&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Display (size)&lt;/td&gt;
      &lt;td&gt;59×35mm&lt;br /&gt;(2.7” diagonal)&lt;/td&gt;
      &lt;td&gt;45×31mm&lt;br /&gt;(2.15” diagonal)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Display (resolution)&lt;/td&gt;
      &lt;td&gt;400×240&lt;/td&gt;
      &lt;td&gt;128×88&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Display (colours)&lt;/td&gt;
      &lt;td&gt;2 (1-bit)&lt;/td&gt;
      &lt;td&gt;4 (2-bit greyscale)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Display (rendering)&lt;/td&gt;
      &lt;td&gt;Software&lt;/td&gt;
      &lt;td&gt;Software&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Software (bundled)&lt;/td&gt;
      &lt;td&gt;24&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Software (SDK)&lt;/td&gt;
      &lt;td&gt;C &amp;amp; Lua&lt;/td&gt;
      &lt;td&gt;C&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Launch price&lt;/td&gt;
      &lt;td&gt;$179 (¥19000)&lt;/td&gt;
      &lt;td&gt;¥11000 ($100)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related Posts&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/08/28/aquaplus-piece-game-reviews-vol-2/&quot;&gt;Aquaplus P/ECE: Game Reviews Vol. 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Thu, 19 Aug 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/08/19/aquaplus-piece-vs-panic-playdate/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/08/19/aquaplus-piece-vs-panic-playdate/</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>Playdate 1-bit illustration postmortem</title>
          <description>&lt;p&gt;Yesterday saw the opening of pre-orders for &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt;, a new handheld gaming device that I’m developing some games for. To celebrate the occasion I drew an illustration in 1-bit with dither patterns using Macintosh System 7.&lt;/p&gt;

&lt;p&gt;This kind of style is most associated with HyperCard, which provides a bunch of great drawing tools and modes, and can be seen in many period pieces of interactive media, animations, art and video games.&lt;/p&gt;

&lt;p&gt;I’ve been revisiting the artwork of &lt;a href=&quot;https://archive.org/search.php?query=creator%3A%22Thoru+Yamamoto%22&amp;amp;sort=titleSorter&quot;&gt;Thoru Yamamoto&lt;/a&gt; recently, which I think influenced the style of this piece.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toolbox&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My tool of choice on System 7 is &lt;a href=&quot;/2021/04/25/mixing-external-tools-across-deneba-software/&quot;&gt;Deneba artWORKS&lt;/a&gt; because it supports multiple layers, a ton of external tools (plugins), and has a good workflow for vector and pixels. Plus, I use it on &lt;a href=&quot;/2021/04/17/turning-an-ipad-pro-into-the-ultimate-classic-macintosh/&quot;&gt;iPad Pro with Apple Pencil&lt;/a&gt; which makes the whole experience an absolute pleasure.&lt;/p&gt;

&lt;p&gt;Some established Macintosh artists have questioned both my use of modern technology and my drawing techniques. Hopefully this post will give a little more insight into how I go about things.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/playdate-during.jpg&quot; alt=&quot;Using Deneba artWORKS on iPad Pro&quot; title=&quot;Deneba artWORKS in Macintosh System 7 running on iPad Pro&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;playdate&quot;&gt;Playdate&lt;/h3&gt;

&lt;p&gt;I first drew the body of the Playdate using Rounded Rectangles for the perimeter, screen, crank, d-pad, speaker grille and using Ovals for the buttons and corner screws. This was all done by eye, no measuring.&lt;/p&gt;

&lt;p&gt;Afterwards I grouped all those shapes and used the Point Rotate external tool to rotate the group by an arbitrary amount until it looked like a fun enough jaunty angle.&lt;/p&gt;

&lt;p&gt;Next, in the Layers Manager I set the shape layer to be greyed out and created a new layer to do some drawing. I used the Paintbrush tool with small circular brush to draw over the shapes by hand, erasing any mistakes and redrawing some sections of lines until I was happy.&lt;/p&gt;

&lt;h3 id=&quot;colouring&quot;&gt;Colouring&lt;/h3&gt;

&lt;p&gt;Given the nature of 1-bit there is no concept of colours only patterns of black and white to simulate different shades of grey. I could have drawn in colour and had the system convert to dither patterns, but I want to have manual control over the shades.&lt;/p&gt;

&lt;p&gt;I first picked the dither pattern for the main “yellow” body and then picked the paler, more sparse, dither patterns for the steel crank handle, drop shadows and background. Fills were applied to closed areas using the Paint Can (flood fill) tool.&lt;/p&gt;

&lt;h3 id=&quot;details&quot;&gt;Details&lt;/h3&gt;

&lt;p&gt;I used the Paintbrush and Pencil tools to add in some details like the dots on the buttons and my hand-drawn version of the Playdate logo. The shadow was drawn freehand using the Paintbrush tool with a larger circular brush size. The background pattern is a simple sparse dither pattern applied using the Paint Can tool.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/playdate-before@2x.png&quot; alt=&quot;Just the Playdate&quot; title=&quot;Just the Playdate looking a little bemused&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;party-up&quot;&gt;Party Up!&lt;/h2&gt;

&lt;p&gt;For pre-order day I thought it would be fun to add an extra layer to the illustration making it more celebratory. I had the idea to replicate the 🥳 emoji (Face with Party Horn and Party Hat) with the Playdate I’d already drawn.&lt;/p&gt;

&lt;h3 id=&quot;horn&quot;&gt;Horn&lt;/h3&gt;

&lt;p&gt;This was drawn using a combination of freehand Brush tool and filled vector Polygons. Some extra shading is done using the Brush tool and a sparse dither pattern. I must have drawn a handful of different style horns, settling on the one that looked least like Playdate was smoking!&lt;/p&gt;

&lt;h3 id=&quot;hat&quot;&gt;Hat&lt;/h3&gt;

&lt;p&gt;This was drawn as a vector path using the Polygon tool, and filled with a dithered pattern using the Gradient Fill tool. The frilly stuff on top of the hat is a handful of Arcs drawn in various directions, always starting from the tip.&lt;/p&gt;

&lt;h3 id=&quot;confetti&quot;&gt;Confetti&lt;/h3&gt;

&lt;p&gt;For the confetti I didn’t want to draw each one by hand, and I wanted them to be vectors rather than pixels so they remained as editable as possible. I found that drawing very short curved strokes using the Pressure Pen tool produced shapes that looked a lot like tiny squares of paper bent at some 3D angle. Using this tool without any pressure means it performs as a dynamic, vector calligraphic pen. A nice little trick!&lt;/p&gt;

&lt;p&gt;Afterwards I applied one of the many standard fill patterns to each piece of confetti to give them a bit of individual flavour. Any confetti overlaying the Playdate body was duplicated, made white, and offset by a pixel or two to help make it stand out from the body. You can see this most clearly on the coil shape at the right side of the Playdate screen.&lt;/p&gt;

&lt;h3 id=&quot;jazz&quot;&gt;Jazz&lt;/h3&gt;

&lt;p&gt;The extra geometric shapes in amongst the confetti are my attempt at bringing a bit of 1980s Memphis Group design aesthetic to the illustration. I use a variety of different external tools for this: Spiral (length 2), Fractals (default wiggle and density), Coil and Resistor. The later two tools are meant for drawing electrical circuits! The moral of the story: know your tools and use them!&lt;/p&gt;

&lt;h3 id=&quot;positioning&quot;&gt;Positioning&lt;/h3&gt;

&lt;p&gt;After adding all the confetti and jazz, I adjusted the positions and sizes of many of the objects to produce what I thought was the most pleasing distribution. The benefits of all of these objects being vectors becomes clear at this point!&lt;/p&gt;

&lt;h3 id=&quot;motion&quot;&gt;Motion&lt;/h3&gt;

&lt;p&gt;I used the Arc tool to add motion lines to the party horn and crank. Lastly I added some straight lines to imply that the light on the power button is flashing.&lt;/p&gt;

&lt;h2 id=&quot;pixel-or-vector&quot;&gt;Pixel or Vector?&lt;/h2&gt;

&lt;p&gt;Looking at the objects that make up the drawing, I’d say that the split is around 75% vector to 25% pixel, which is the same across most of my drawings.&lt;/p&gt;

&lt;p&gt;Pixels make up large freeform areas, filled vector shapes make up large defined areas. Small objects that will remain static use pixels, and those that I think will need to be moved or resized use vectors.&lt;/p&gt;

&lt;p&gt;I tend to draw on the base layer with pixels and then use higher layers as rough groups for sets of vector shapes. For example, the confetti was all drawn on one layer.&lt;/p&gt;

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

&lt;p&gt;I saved out the image as a GIF (another external tool provides this capability) and loaded it into DeBabelizer, or sometimes Photoshop 3.0. Here I resized using simple sampling to 990px square from the original size of 330px square.&lt;/p&gt;

&lt;p&gt;Finally, I copied the file to BasiliskII emulator File Sharing drive at which point it became accessible in Files in iPadOS, and from here I sent it to my iPhone via AirDrop, after which I used the Twitter app to add the image to a tweet and hit send!&lt;/p&gt;

&lt;p&gt;Job done.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/playdate-after@2x.png&quot; alt=&quot;Celebrate good times!&quot; title=&quot;🥳 Playdate with Party Horn and Party Hat&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;keyboard-craving&quot;&gt;Keyboard craving&lt;/h2&gt;

&lt;p&gt;I drew this late at night and was too lazy to go and find my Bluetooth keyboard. Next time I’ll definitely make the effort to find it. That way I can use my spare hand do keyboard shortcuts and to nudge positions using the cursor keys. I should really add cursor keys to the emulator’s custom keyboard.&lt;/p&gt;

&lt;p&gt;Let me know your thoughts on Twitter using the comments link below.&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Fri, 30 Jul 2021 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2021/07/30/playdate-1-bit-illustration-postmortem/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2021/07/30/playdate-1-bit-illustration-postmortem/</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>
