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'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'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'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'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=250&r=x&d=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">•</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) => [ 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: () => 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 & CharacterControllerComp & 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 < 6 && 329 this.pos.x - barrel.pos.x < 1 330 ) { 331 barrel.scored = true; 332 onScoreBarrel(); 333 } 334 } 335 } 336 } 337 }, 338 }; 339}</code></pre><h2 id="art-sounds">Art & 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 & 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'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> © 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.