PageSourceSearch

https://www.supertorio.dev/post-mortem-recreating-donkey-kong-in-javascript/

html supertorio.dev collected 2026-10-04 08:03:32 UTC 37,752 bytes, 565 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4
5    <title>Post-Mortem: Recreating Donkey Kong in JavaScript</title>
6    <meta charset="utf-8" />
7    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
8    <meta name="HandheldFriendly" content="True" />
9    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
11    <link rel="preload" as="style" href="https://www.supertorio.dev/assets/built/screen.css?v=FnB2fxM57KctsQub" />
12    <link rel="preload" as="script" href="https://www.supertorio.dev/assets/built/casper.js?v=Wk5PDoc8JwHVIPbN" />
13
14    <link rel="stylesheet" type="text/css" href="https://www.supertorio.dev/assets/built/screen.css?v=FnB2fxM57KctsQub" />
15
16    <meta name="description" content="A couple of years ago, I had an idea: recreate an old sprite-based game using one of the many JavaScript game libraries available. In this article I&#x27;ll be sharing some of the experiences and pitfalls while building this classic game for the web in Kaboom.js/Kaplay.js">
17    <link rel="canonical" href="https://www.supertorio.dev/post-mortem-recreating-donkey-kong-in-javascript/">
18    <meta name="referrer" content="no-referrer-when-downgrade">
19    
20    <meta property="og:site_name" content="Supertorio Games">
21    <meta property="og:type" content="article">
22    <meta property="og:title" content="Post-Mortem: Recreating Donkey Kong in JavaScript">
23    <meta property="og:description" content="A couple of years ago, I had an idea: recreate an old sprite-based game using one of the many JavaScript game libraries available. In this article I&#x27;ll be sharing some of the experiences and pitfalls while building this classic game for the web in Kaboom.js/Kaplay.js">
24    <meta property="og:url" content="https://www.supertorio.dev/post-mortem-recreating-donkey-kong-in-javascript/">
25    <meta property="og:image" content="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Screenshot-2025-07-19-132537.png">
26    <meta property="article:published_time" content="2025-07-20T10:03:44.000Z">
27    <meta property="article:modified_time" content="2025-07-20T10:06:23.000Z">
28    <meta property="article:tag" content="Game Dev">
29    <meta property="article:tag" content="Postmortem">
30    <meta property="article:tag" content="Web Development">
31    
32    <meta name="twitter:card" content="summary_large_image">
33    <meta name="twitter:title" content="Post-Mortem: Recreating Donkey Kong in JavaScript">
34    <meta name="twitter:description" content="A couple of years ago, I had an idea: recreate an old sprite-based game using one of the many JavaScript game libraries available. In this article I&#x27;ll be sharing some of the experiences and pitfalls while building this classic game for the web in Kaboom.js/Kaplay.js">
35    <meta name="twitter:url" content="https://www.supertorio.dev/post-mortem-recreating-donkey-kong-in-javascript/">
36    <meta name="twitter:image" content="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Screenshot-2025-07-19-132537.png">
37    <meta name="twitter:label1" content="Written by">
38    <meta name="twitter:data1" content="Tori Holmes-Kirk">
39    <meta name="twitter:label2" content="Filed under">
40    <meta name="twitter:data2" content="Game Dev, Postmortem, Web Development">
41    <meta name="twitter:site" content="@ghost">
42    <meta property="og:image:width" content="993">
43    <meta property="og:image:height" content="619">
44    
45    
45<script type="application/ld+json">
46{
47    "@context": "https://schema.org",
48    "@type": "Article",
49    "publisher": {
50        "@type": "Organization",
51        "name": "Supertorio Games",
52        "url": "https://www.supertorio.dev/",
53        "logo": {
54            "@type": "ImageObject",
55            "url": "https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2023/12/supertorio-games-logo.png",
56            "width": 60,
57            "height": 60
58        }
59    },
60    "author": {
61        "@type": "Person",
62        "name": "Tori Holmes-Kirk",
63        "image": {
64            "@type": "ImageObject",
65            "url": "https://www.gravatar.com/avatar/93dfeee04f48645ed0d7987e3171f843?s=250&r=x&d=mp",
66            "width": 250,
67            "height": 250
68        },
69        "url": "https://www.supertorio.dev/author/tori/",
70        "sameAs": []
71    },
72    "headline": "Post-Mortem: Recreating Donkey Kong in JavaScript",
73    "url": "https://www.supertorio.dev/post-mortem-recreating-donkey-kong-in-javascript/",
74    "datePublished": "2025-07-20T10:03:44.000Z",
75    "dateModified": "2025-07-20T10:06:23.000Z",
76    "image": {
77        "@type": "ImageObject",
78        "url": "https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Screenshot-2025-07-19-132537.png",
79        "width": 993,
80        "height": 619
81    },
82    "keywords": "Game Dev, Postmortem, Web Development",
83    "description": "A couple of years ago, I had an idea: recreate an old sprite-based game using one of the many JavaScript game libraries available. In this article I'll be sharing some of the experiences and pitfalls while building this classic game for the web in Kaboom.js/Kaplay.js",
84    "mainEntityOfPage": "https://www.supertorio.dev/post-mortem-recreating-donkey-kong-in-javascript/"
85}
86    </script>
86
87
88    <meta name="generator" content="Ghost 6.68">
89    <link rel="alternate" type="application/rss+xml" title="Supertorio Games" href="https://www.supertorio.dev/rss/">
90    
90<script defer src="https://cdn.jsdelivr.net/ghost/portal@~2.71/umd/portal.min.js" data-i18n="true" data-ghost="https://www.supertorio.dev/" data-key="dda2735909a9dc337a5d3440d1" data-api="https://supertorio-games.ghost.io/ghost/api/content/" data-locale="en" crossorigin="anonymous"></script>
90<style id="gh-members-styles">.gh-post-upgrade-cta-content,
91.gh-post-upgrade-cta {
92    display: flex;
93    flex-direction: column;
94    align-items: center;
95    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
96    text-align: center;
97    width: 100%;
98    color: #ffffff;
99    font-size: 16px;
100}
101
102.gh-post-upgrade-cta-content {
103    border-radius: 8px;
104    padding: 40px 4vw;
105}
106
107.gh-post-upgrade-cta h2 {
108    color: #ffffff;
109    font-size: 28px;
110    letter-spacing: -0.2px;
111    margin: 0;
112    padding: 0;
113}
114
115.gh-post-upgrade-cta p {
116    margin: 20px 0 0;
117    padding: 0;
118}
119
120.gh-post-upgrade-cta small {
121    font-size: 16px;
122    letter-spacing: -0.2px;
123}
124
125.gh-post-upgrade-cta a {
126    color: #ffffff;
127    cursor: pointer;
128    font-weight: 500;
129    box-shadow: none;
130    text-decoration: underline;
131}
132
133.gh-post-upgrade-cta a:hover {
134    color: #ffffff;
135    opacity: 0.8;
136    box-shadow: none;
137    text-decoration: underline;
138}
139
140.gh-post-upgrade-cta a.gh-btn {
141    display: block;
142    background: #ffffff;
143    text-decoration: none;
144    margin: 28px 0 0;
145    padding: 8px 18px;
146    border-radius: 4px;
147    font-size: 16px;
148    font-weight: 600;
149}
150
151.gh-post-upgrade-cta a.gh-btn:hover {
152    opacity: 0.92;
153}</style>
154    
154<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/sodo-search.min.js" data-key="dda2735909a9dc337a5d3440d1" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/main.css" data-sodo-search="https://supertorio-games.ghost.io/" data-locale="en" crossorigin="anonymous"></script>
154
155    
156    <link href="https://www.supertorio.dev/webmentions/receive/" rel="webmention">
157    
157<script defer src="/public/cards.min.js?v=ShRHxgy4po8zN-Wf"></script>
157
158    <link rel="stylesheet" type="text/css" href="/public/cards.min.css?v=WwnU9jw5ancNC8Gc">
159    
159<script defer src="/public/member-attribution.min.js?v=AKG4hWena9j3yX3I"></script>
159<style>:root {--ghost-accent-color: #f15542;}</style>
160    <!-- Google Tag Manager -->
161<script>
vendor: 327 bytes, lines 161-165
161(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
162new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
163j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
164'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
165})(window,document,'script','dataLayer','
165GTM-MQD3CCN6
vendor: 3 bytes, line 165
165');
165</script>
165
166<!-- End Google Tag Manager -->
167
168<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/brands.min.css" integrity="sha512-DJLNx+VLY4aEiEQFjiawXaiceujj5GA7lIY8CHCIGQCBPfsEG0nGz1edb4Jvw1LR7q031zS5PpPqFuPA8ihlRA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
169<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css" rel="stylesheet" />
170
171<style>
172    :where(.nav, .gh-head-menu) .nav-instagram a,
173    :where(.nav, .gh-head-menu) .nav-github a,
174    :where(.nav, .gh-head-menu) .nav-threads a,
175    :where(.nav, .gh-head-menu) .nav-discord a,
176    :where(.nav, .gh-head-menu) .nav-artstation a {
177        font-size: 0 !important;
178    }
179
180    :where(.nav, .gh-head-menu) .nav-instagram a::before,
181    :where(.nav, .gh-head-menu) .nav-github a::before,
182    :where(.nav, .gh-head-menu) .nav-threads a::before,
183    :where(.nav, .gh-head-menu) .nav-discord a::before,
184    :where(.nav, .gh-head-menu) .nav-artstation a::before {
185        font-family: "Font Awesome 6 Brands";
186        display: inline-block;
187        font-size: 20px;
188        font-style: normal;
189        font-weight: normal;
190        font-variant: normal;
191        text-rendering: auto;
192        -webkit-font-smoothing: antialiased;
193    }
194
195    :where(.nav, .gh-head-menu) .nav-instagram a::before {content: '\f16d'}
196    :where(.nav, .gh-head-menu) .nav-github a::before {content: "\f09b"}
197    :where(.nav, .gh-head-menu) .nav-threads a::before {content: "\e618"}
198    :where(.nav, .gh-head-menu) .nav-discord a::before {content: "\f392"}
199    :where(.nav, .gh-head-menu) .nav-artstation a::before {content: "\f77a"}
200</style>
201
202</head>
203<body class="post-template tag-game-dev tag-postmortem tag-web-development is-head-left-logo has-cover">
204<div class="viewport">
205
206    <header id="gh-head" class="gh-head outer">
207        <div class="gh-head-inner inner">
208            <div class="gh-head-brand">
209                <a class="gh-head-logo" href="https://www.supertorio.dev">
210                        <img src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2023/12/supertorio-games-logo.png" alt="Supertorio Games">
211                </a>
212                <button class="gh-search gh-icon-btn" aria-label="Search this site" data-ghost-search><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button>
213                <button class="gh-burger" aria-label="Main Menu"></button>
214            </div>
215
216            <nav class="gh-head-menu">
217                <ul class="nav">
218    <li class="nav-home"><a href="https://www.supertorio.dev/">Home</a></li>
219    <li class="nav-about"><a href="https://www.supertorio.dev/about/">About</a></li>
220    <li class="nav-contact"><a href="https://www.supertorio.dev/contact/">Contact</a></li>
221    <li class="nav-artstation"><a href="https://www.artstation.com/supertorio">Artstation</a></li>
222    <li class="nav-instagram"><a href="https://www.instagram.com/supertoriogames/">Instagram</a></li>
223    <li class="nav-threads"><a href="https://www.threads.com/@supertoriogames">Threads</a></li>
224    <li class="nav-github"><a href="https://github.com/Supertorio-Games">Github</a></li>
225    <li class="nav-discord"><a href="https://discord.gg/UESGsWP78X">Discord</a></li>
226</ul>
227
228            </nav>
229
230            <div class="gh-head-actions">
231                    <button class="gh-search gh-icon-btn" aria-label="Search this site" data-ghost-search><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button>
232                    <div class="gh-head-members">
233                                <a class="gh-head-link" href="#/portal/signin" data-portal="signin">Sign in</a>
234                                <a class="gh-head-button" href="#/portal/signup" data-portal="signup">Subscribe</a>
235                    </div>
236            </div>
237        </div>
238    </header>
239
240    <div class="site-content">
241        
242
243
244
245<main id="site-main" class="site-main">
246<article class="article post tag-game-dev tag-postmortem tag-web-development ">
247
248    <header class="article-header gh-canvas">
249
250        <div class="article-tag post-card-tags">
251                <span class="post-card-primary-tag">
252                    <a href="/tag/game-dev/">Game Dev</a>
253                </span>
254        </div>
255
256        <h1 class="article-title">Post-Mortem: Recreating Donkey Kong in JavaScript</h1>
257
258            <p class="article-excerpt">A couple of years ago, I had an idea: recreate an old sprite-based game using one of the many JavaScript game libraries available. In this article I&#x27;ll be sharing some of the experiences and pitfalls while building this classic game for the web in Kaboom.js/Kaplay.js</p>
259
260        <div class="article-byline">
261        <section class="article-byline-content">
262
263            <ul class="author-list instapaper_ignore">
264                <li class="author-list-item">
265                    <a href="/author/tori/" class="author-avatar" aria-label="Read more of Tori Holmes-Kirk">
266                        <img class="author-profile-image" src="https://www.gravatar.com/avatar/93dfeee04f48645ed0d7987e3171f843?s&#x3D;250&amp;r&#x3D;x&amp;d&#x3D;mp" alt="Tori Holmes-Kirk" loading="eager" />
267                    </a>
268                </li>
269            </ul>
270
271            <div class="article-byline-meta">
272                <h4 class="author-name"><a href="/author/tori/">Tori Holmes-Kirk</a></h4>
273                <div class="byline-meta-content">
274                    <time class="byline-meta-date" datetime="2025-07-20">20 Jul 2025</time>
275                        <span class="byline-reading-time"><span class="bull">&bull;</span> 6 min read</span>
276                </div>
277            </div>
278
279        </section>
280        <a href="#/share" class="gh-button gh-button-share">Share</a>
281        </div>
282
283            <figure class="article-image">
284                <img
285                    class="gh-feature-image"
286                    srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w300/2025/07/Screenshot-2025-07-19-132537.png 300w,
287                            https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/07/Screenshot-2025-07-19-132537.png 600w,
288                            https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w1000/2025/07/Screenshot-2025-07-19-132537.png 1000w,
289                            https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w2000/2025/07/Screenshot-2025-07-19-132537.png 2000w"
290                    sizes="(min-width: 1400px) 1400px, 92vw"
291                    src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w2000/2025/07/Screenshot-2025-07-19-132537.png"
292                    alt="Intro Screen for Donkey Kong Kaboom"
293                    fetchpriority="high"
294                    loading="eager"
295                />
296            </figure>
297
298    </header>
299
300    <section class="gh-content gh-canvas">
301        <p>A couple of years ago, I had an idea: recreate an old sprite-based game using one of the many JavaScript game libraries available. In this article I'll be sharing some of the experiences and pitfalls while building this classic game for the web in Kaboom.js/Kaplay.js</p><p>After browsing online sprite libraries, I landed on the ubiquitous classic <strong>Donkey Kong</strong>, specifically the Atari 2600 version. My initial thought was actually to remake the famed flop, "E.T." for Atari – who would do that, right? – but I ultimately settled on a more enjoyable style of play. </p><p>I worked on this project in my spare time over several months. While I initially considered making this article a tutorial, the game's scope is likely a bit too much for a single guide. Instead, this will be a post-mortem where I share lessons learned and, hopefully, some useful tips for your next JavaScript game build.</p><blockquote><strong>You can play the game here</strong>: <a href="https://supertorio-games.github.io/kaboom-donkey-kong/?ref=supertorio.dev" rel="noopener noreferrer nofollow">https://supertorio-games.github.io/kaboom-donkey-kong/</a></blockquote><h2 id="picking-an-engine">Picking an Engine</h2><p>For the game library, I chose <strong>Kaboom.js</strong>. I'd been experimenting with it for a couple of years and found its simple, component-style architecture compelling. It also seemed like a very approachable engine for building small web games. There are many 2D web game engines out there, with Phaser.js being the most well-known. I've spent time building some prototypes in Phaser, but at the time, Kaboom felt like a good fit.</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/engine.png" class="kg-image" alt="" loading="lazy" width="876" height="232" srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/07/engine.png 600w, https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/engine.png 876w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Kaboom.js and Kaplay.js Engine Logos</span></figcaption></figure><p>Unfortunately, in June 2024, Replit, the company that sponsored and maintained the engine, ceased maintenance of the project. The community, however, decided to pick it up, creating <strong>Kaplay.js</strong> from a fork of the original Kaboom project. I've since migrated this game's codebase to the Kaplay engine. It was very straightforward, requiring only a couple of minor API and type name changes.</p><h2 id="coding">Coding</h2><p>I genuinely enjoy the component-style architecture of building games with Kaplay. It feels quite similar to how one might build games in a bigger engine, such as Unity.</p><p>
301Here's our <strong>Donkey Kong entity</strong>, which consists of a couple of built-in components—<code>Position</code> and <code>Sprite</code>—along with a custom <code>Stomp</code> component:</p><pre><code class="language-Javascript">import k from "../game";
302import stomp from "../components/stomp";
303
304import type { Vec2 } from "kaplay";
305
306export const donkeyKong = (starPos: Vec2) =&gt; [
307  k.sprite("donkeyKong", {
308    animSpeed: 0.4,
309    anim: "idle",
310  }),
311  k.pos(starPos),
312  stomp(),
313];</code></pre><p>In addition to built-in components, you can write your own custom components to add specific functionalities and behaviors. As an example, here's a custom component attached to Mario which scores jumps over barrels:</p><pre><code class="language-Javascript">export default function jumpScorer(
314  onScoreBarrel: () =&gt; void
315): JumpScorerComponent {
316  return {
317    id: "jumpScorer",
318    // Specify Required components
319    require: ["characterController"],
320    // This component activates each frame via the update hook
321    update(this: PosComp &amp; CharacterControllerComp &amp; JumpScorerComponent) {
322      if (this.characterState?.value === "jump") {
323        const allBarrels = k.get("barrel");
324        for (let barrel of allBarrels) {
325          if (!barrel.scored) {
326            if (
327              // Check if the barrel position is beneath the player position
328              this.pos.y - barrel.pos.y &lt; 6 &amp;&amp;
329              this.pos.x - barrel.pos.x &lt; 1
330            ) {
331              barrel.scored = true;
332              onScoreBarrel();
333            }
334          }
335        }
336      }
337    },
338  };
339}</code></pre><h2 id="art-sounds">Art &amp; Sounds</h2><p>For artwork, I had a couple of options: create the sprites from scratch or try to find an online sprite sheet. I ended up doing both! There are some really great resources for finding sprite sheets from old cartridge and PC games online; I found the sprites for this game on <a href="https://www.spriters-resource.com/fullview/2110/?ref=supertorio.dev" rel="noreferrer">The Spriters Resource</a></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Atari---Donkey-Kong-Atari-2600---General-Sprites.png" class="kg-image" alt="" loading="lazy" width="409" height="280"><figcaption><span style="white-space: pre-wrap;">Donkey Kong Sprite Sheet</span></figcaption></figure><p>I started by opening this image in Photoshop to cut out the individual sprites. However, due to the image size and compression, the sprites weren't as clean as I wanted. As a fix, I popped open <strong>Aseprite</strong> and, using this sheet as a guide, recreated each sprite. Now, I'm no pixel art master, but these sprites are pretty simple, and I was able to recreate them along with their animations. I've included the source files in the project repository as well.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Screenshot-2025-07-19-131742.png" class="kg-image" alt="" loading="lazy" width="1510" height="854" srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/07/Screenshot-2025-07-19-131742.png 600w, https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w1000/2025/07/Screenshot-2025-07-19-131742.png 1000w, https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Screenshot-2025-07-19-131742.png 1510w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Mario Sprites in Aseprite</span></figcaption></figure><p>While I did use sprites for the numbers, I opted for bitmap fonts for the other text, which I'll discuss in a bit.</p><p>Similarly for the audio, there are online resources where you can download ripped sound files from old games. Since I have no experience or talent for creating my own sound effects, I chose to use sound files from <a href="https://themushroomkingdom.net/media/dk-a2600/wav?ref=supertorio.dev" rel="noreferrer">The Mushroom Kingdom</a>.</p><h2 id="pitfalls-no-pun-intended-for-retro-pixel-games">
339Pitfalls (no pun intended) for Retro Pixel Games</h2><p>Building this retro remake led me to a few surprising gotchas.</p><h3 id="physics-colliders">Physics &amp; Colliders</h3><p>One of the most difficult aspects of creating this game was dealing with <strong>colliders</strong>. Originally, I had planned to use physics and colliders for the barrels rolling down the map. When I first wrote this game in the Kaboom engine, its physics support was very basic. It lacked the different effector types currently seen in Kaplay.js. </p><p>Having Mario climb through platforms via ladders and barrels fall through platforms would have required some custom physics development. Instead, I approached these issues through math (as I suspect the original game developers did). Using the known positions of each platform and ladder on the map, I calculated the vectors on which each entity should move, climb, and fall.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Screenshot-2025-07-20-112945.png" class="kg-image" alt="" loading="lazy" width="1016" height="292" srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/07/Screenshot-2025-07-20-112945.png 600w, https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w1000/2025/07/Screenshot-2025-07-20-112945.png 1000w, https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/Screenshot-2025-07-20-112945.png 1016w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Mario Jumping over a barrel</span></figcaption></figure><p>The other collider issue I struggled with was having nested colliders on an entity. Mario has his own area collision detection for checking overlaps with barrels, so when he doesn't quite make the jump, he loses a life. I wanted to add a second area collider that activated when Mario jumps, to see if a barrel passes underneath him. I wasn't able to get a secondary nested collider to work, so instead, again, I just used some math to check if any barrels were underneath him while he was in the jump state.</p><h3 id="fonts">Fonts</h3><p>For the score, I built a custom component that takes the score and uses each digit to render the correct number sprite to the screen.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/numbers.png" class="kg-image" alt="" loading="lazy" width="240" height="7"></figure><p>While I used the number sprites for the score, I needed a way to add text for instructions and other on-screen elements. Dealing with <strong>pixel fonts</strong> can be very frustrating. In upgrading from Kaboom 2000-3000, they removed the built-in fonts. Finding a suitable replacement font that's readable on a 232x232 pixel screen isn't easy. You pretty much have to use <strong>bitmap fonts</strong> as standard fonts look terrible when rendered that small and scaled up. In this case, I used <a href="http://viznut.fi/unscii/?ref=supertorio.dev">unscii</a>. Kaplay can take this bitmap font image and automatically map it to your input strings.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/unscii_8x8.png" class="kg-image" alt="" loading="lazy" width="760" height="8" srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/07/unscii_8x8.png 600w, https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/2025/07/unscii_8x8.png 760w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Bitmap font Image</span></figcaption></figure><pre><code class="language-Javascript">  // Fonts
340  k.loadBitmapFont("unscii", "fonts/unscii_8x8.png", 8, 8)</code></pre><h2 id="finishing-the-game">Finishing the Game</h2><p>As we all know, finishing a game is often the most difficult part of game development. And to be honest, I had grand plans of completing the "rivets" map and rounding out the rest of the game's features. However, seeing as this was just meant to be a learning experience and demo – not to mention also a copyrighted game and property – I decided I was happy with how it turned out and what I had learned while building it.</p><p>But you know, now that Kaboom.js has become Kaplay.js and has added new features and functionality, I'm tempted to dive back in and give the second map a go. Perhaps after I wrap up some other projects!</p><p>Full Source Code is available on github: <a href="https://github.com/Supertorio-Games/kaboom-donkey-kong?ref=supertorio.dev">https://github.com/Supertorio-Games/kaboom-donkey-kong</a></p>
341    </section>
342
343
344</article>
345</main>
346
347    <section class="footer-cta outer">
348        <div class="inner">
349            <h2 class="footer-cta-title">Sign up for more like this.</h2>
350            <a class="footer-cta-button" href="#/portal" data-portal>
351                <div class="footer-cta-input">Enter your email</div>
352                <span>Subscribe</span>
353            </a>
354        </div>
355    </section>
356
357
358
359            <aside class="read-more-wrap outer">
360                <div class="read-more inner">
361                        
362<article class="post-card post">
363
364    <a class="post-card-image-link" href="/meet-kaplayer-the-new-visual-map-editor-for-kaplayjs/">
365
366        <img class="post-card-image"
367            srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w300/2026/01/Screenshot-2026-01-21-at-15.20.31.png 300w,
368                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2026/01/Screenshot-2026-01-21-at-15.20.31.png 600w,
369                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w1000/2026/01/Screenshot-2026-01-21-at-15.20.31.png 1000w,
370                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w2000/2026/01/Screenshot-2026-01-21-at-15.20.31.png 2000w"
371            sizes="(max-width: 1000px) 400px, 800px"
372            src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2026/01/Screenshot-2026-01-21-at-15.20.31.png"
373            alt="Meet Kaplayer: The New Visual Map Editor for KaplayJS"
374            loading="lazy"
375        />
376
377
378    </a>
379
380    <div class="post-card-content">
381
382        <a class="post-card-content-link" href="/meet-kaplayer-the-new-visual-map-editor-for-kaplayjs/">
383            <header class="post-card-header">
384                <div class="post-card-tags">
385                </div>
386                <h2 class="post-card-title">
387                    Meet Kaplayer: The New Visual Map Editor for KaplayJS
388                </h2>
389            </header>
390                <div class="post-card-excerpt">With some free time as I search for my next profession role, I had the thought about putting together a course on Udemy. Specifically one where I teach how to build a platformer game in Typescript with the KaplayJS framework.
391
392Planning this course led to a ancillary project idea. A</div>
393        </a>
394
395        <footer class="post-card-meta">
396            <time class="post-card-meta-date" datetime="2026-01-21">21 Jan 2026</time>
397                <span class="post-card-meta-length">3 min read</span>
398        </footer>
399
400    </div>
401
402</article>
403                        
404<article class="post-card post">
405
406    <a class="post-card-image-link" href="/dutch-game-week-2025/">
407
408        <img class="post-card-image"
409            srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w300/2025/12/IMG_3443.JPG 300w,
410                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/12/IMG_3443.JPG 600w,
411                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w1000/2025/12/IMG_3443.JPG 1000w,
412                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w2000/2025/12/IMG_3443.JPG 2000w"
413            sizes="(max-width: 1000px) 400px, 800px"
414            src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/12/IMG_3443.JPG"
415            alt="Dutch Game Week 2025"
416            loading="lazy"
417        />
418
419
420    </a>
421
422    <div class="post-card-content">
423
424        <a class="post-card-content-link" href="/dutch-game-week-2025/">
425            <header class="post-card-header">
426                <div class="post-card-tags">
427                </div>
428                <h2 class="post-card-title">
429                    Dutch Game Week 2025
430                </h2>
431            </header>
432                <div class="post-card-excerpt">Dutch Game week is, as the name suggests, a week-long series of events happening throughout the city. The week is capped off by a two day conference held at the Chassé Theater.</div>
433        </a>
434
435        <footer class="post-card-meta">
436            <time class="post-card-meta-date" datetime="2025-12-09">09 Dec 2025</time>
437                <span class="post-card-meta-length">5 min read</span>
438        </footer>
439
440    </div>
441
442</article>
443                        
444<article class="post-card post">
445
446    <a class="post-card-image-link" href="/houdini-desert-flora-open-sourced-hdas/">
447
448        <img class="post-card-image"
449            srcset="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w300/2025/10/banner.png 300w,
450                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/10/banner.png 600w,
451                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w1000/2025/10/banner.png 1000w,
452                    https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w2000/2025/10/banner.png 2000w"
453            sizes="(max-width: 1000px) 400px, 800px"
454            src="https://storage.ghost.io/c/26/2f/262f36c1-f29c-45ea-b20d-12dfaf2d6b20/content/images/size/w600/2025/10/banner.png"
455            alt="Houdini Desert Flora - Open Sourced HDAs"
456            loading="lazy"
457        />
458
459
460    </a>
461
462    <div class="post-card-content">
463
464        <a class="post-card-content-link" href="/houdini-desert-flora-open-sourced-hdas/">
465            <header class="post-card-header">
466                <div class="post-card-tags">
467                </div>
468                <h2 class="post-card-title">
469                    Houdini Desert Flora - Open Sourced HDAs
470                </h2>
471            </header>
472                <div class="post-card-excerpt">During my time at Mythica, I developed quite a number of procedural tools. Our philosophy was that anything we made that wasn&#39;t directly for a client, we would open source. This is the first in a series of posts where I share a bit about the various Houdini</div>
473        </a>
474
475        <footer class="post-card-meta">
476            <time class="post-card-meta-date" datetime="2025-10-09">09 Oct 2025</time>
477                <span class="post-card-meta-length">3 min read</span>
478        </footer>
479
480    </div>
481
482</article>
483                </div>
484            </aside>
485
486
487
488    </div>
489
490    <footer class="site-footer outer">
491        <div class="inner">
492            <section class="copyright"><a href="https://www.supertorio.dev">Supertorio Games</a> &copy; 2026</section>
493            <div class="site-footer-center">
494                <div class="site-footer-social-links">
495                        <a href="https://x.com/ghost" target="_blank" rel="noopener" aria-label="X">
496<svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
497    <path d="M18.2439 2.25H21.5519L14.3249 10.51L22.8269 21.75H16.1699L10.9559 14.933L4.98991 21.75H1.67991L9.40991 12.915L1.25391 2.25H8.07991L12.7929 8.481L18.2439 2.25ZM17.0829 19.77H18.9159L7.08391 4.126H5.11691L17.0829 19.77Z" fill="currentColor"/>
498</svg>                        </a>
499                </div>
500                <nav class="site-footer-nav">
501                    <ul class="nav">
502    <li class="nav-sign-up"><a href="#/portal/">Sign up</a></li>
503</ul>
504
505                </nav>
506            </div>
507            <div class="gh-powered-by"><a href="https://ghost.org/" target="_blank" rel="noopener">Powered by Ghost</a></div>
508        </div>
509    </footer>
510
511</div>
512
513    <div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
514    <div class="pswp__bg"></div>
515
516    <div class="pswp__scroll-wrap">
517        <div class="pswp__container">
518            <div class="pswp__item"></div>
519            <div class="pswp__item"></div>
520            <div class="pswp__item"></div>
521        </div>
522
523        <div class="pswp__ui pswp__ui--hidden">
524            <div class="pswp__top-bar">
525                <div class="pswp__counter"></div>
526
527                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
528                <button class="pswp__button pswp__button--share" title="Share"></button>
529                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
530                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
531
532                <div class="pswp__preloader">
533                    <div class="pswp__preloader__icn">
534                        <div class="pswp__preloader__cut">
535                            <div class="pswp__preloader__donut"></div>
536                        </div>
537                    </div>
538                </div>
539            </div>
540
541            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
542                <div class="pswp__share-tooltip"></div>
543            </div>
544
545            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
546            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>
547
548            <div class="pswp__caption">
549                <div class="pswp__caption__center"></div>
550            </div>
551        </div>
552    </div>
553</div>
554<script src="https://www.supertorio.dev/assets/built/casper.js?v=Wk5PDoc8JwHVIPbN" defer></script>
554
555
556<!-- Google Tag Manager (noscript) -->
vendor: 68 bytes, lines 556-557
556
557<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
557GTM-MQD3CCN6
vendor: 83 bytes, lines 557-558
557"
558height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
559<!-- End Google Tag Manager (noscript) -->
560
561<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
vendor: 1 bytes, line 561
561
562<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
562
563
564</body>
565</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.