<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[JSLegendDev’s Substack]]></title><description><![CDATA[I write about programming, game development and game design.]]></description><link>https://jslegenddev.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!bYsl!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png</url><title>JSLegendDev’s Substack</title><link>https://jslegenddev.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 08 Aug 2026 19:48:39 GMT</lastBuildDate><atom:link href="https://jslegenddev.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[JSLegendDev]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[jslegenddev@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[jslegenddev@substack.com]]></itunes:email><itunes:name><![CDATA[JSLegendDev]]></itunes:name></itunes:owner><itunes:author><![CDATA[JSLegendDev]]></itunes:author><googleplay:owner><![CDATA[jslegenddev@substack.com]]></googleplay:owner><googleplay:email><![CDATA[jslegenddev@substack.com]]></googleplay:email><googleplay:author><![CDATA[JSLegendDev]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[My Biggest Gripe With YouTube]]></title><description><![CDATA[3 years ago, I started a YouTube channel called JSLegendDev where I uploaded tutorials teaching the JavaScript programming language through the development of 2D games.]]></description><link>https://jslegenddev.substack.com/p/my-biggest-gripe-with-youtube</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/my-biggest-gripe-with-youtube</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Sat, 30 May 2026 15:02:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZuR2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>3 years ago, I started a YouTube channel called JSLegendDev where I uploaded tutorials teaching the JavaScript programming language through the development of 2D games.</p><p>The state of the space around the time I started was as follows :</p><ul><li><p>Tutorials inferior to an hour in length were not in demand. They made very little views.</p></li><li><p>Tutorials divided into multiple parts where dead on arrival. You were guaranteed dwindling views on every new upload.</p></li></ul><p>To adapt, other content creators started uploading longer, multi-hour, often project based tutorials which translated to more views. Seeing the shift, I also decided to follow suit and uploaded tutorials reaching the 4-10 hour mark. I saw some success doing this. Therefore, I kept at it for a while. However, as time passed, I got tired of recording extremely long tutorials and they, in general, started to make less views.</p><p>There are many hypotheses as to why YouTube&#8217;s algorithm started serving tutorial content less. The advent of AI could&#8217;ve been the likely cause but also a general shift in YouTube becoming more of an entertainment focused platform to the detriment of educational content. Something you now put on TV to relax. In the programming space, channel producing content that can be watched passively like tech news, tech drama, tech history, high level discussions, etc&#8230; continued to thrive.</p><p>Seeing this new shift and because I was genuinely tired of making YouTube tutorials, I published my first scripted video titled &#8220;How do Devs Make Levels Without Game Engines&#8221; which was first published as an article.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;184d01f2-fd6c-4b1b-9991-1f52304f5986&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How do Devs Make Levels Without Game Engines?&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-08T00:47:02.958Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!ICbs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-do-devs-make-levels-without-game&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:172615663,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:5,&quot;comment_count&quot;:1,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>In that piece, I told the story of how I discovered a convenient way to design levels for my games using an external editor called Tiled in conjunction with my editor-less game framework. At the end of that video, I promoted a paid tutorial I made teaching the exact steps needed to achieve what was presented.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H7RC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H7RC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 424w, https://substackcdn.com/image/fetch/$s_!H7RC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 848w, https://substackcdn.com/image/fetch/$s_!H7RC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 1272w, https://substackcdn.com/image/fetch/$s_!H7RC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!H7RC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png" width="514" height="381.7960088691796" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:670,&quot;width&quot;:902,&quot;resizeWidth&quot;:514,&quot;bytes&quot;:269832,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/199361601?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!H7RC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 424w, https://substackcdn.com/image/fetch/$s_!H7RC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 848w, https://substackcdn.com/image/fetch/$s_!H7RC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 1272w, https://substackcdn.com/image/fetch/$s_!H7RC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2ad9b74-ac26-427d-a389-9a3a75116563_902x670.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The video ended up accumulating over 30k views, which was pretty great! It took far less effort to make compared to my multi-hour tutorials and I was able to make a few sales on my paid tutorial I mentioned within. Previously, I was very unsuccessful in selling any paid courses and I didn&#8217;t quite understand why. However, the answer now hit me like a truck. Why would anyone still have the appetite for a paid course after having invested the time following a free multi-hour course? Even if the subject of the paid offering was different, they would probably be too tired to commit to another one.</p><p>Anyway, following in the footsteps of this first breakthrough, I uploaded another scripted video titled &#8220;You Can Now Make PS2 Games in JavaScript&#8221; which was again first published as an article.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;f30c4f06-cc27-4ee7-87ef-c6f0358c6469&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:14,&quot;comment_count&quot;:6,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>In that video, I told the story of how I discovered that you could make PS2 games in JavaScript and provided an overview of how the viewer could get started. Despite including very practical knowledge, the viewer was never expected to follow along and therefore could watch it passively.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!g4uP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!g4uP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 424w, https://substackcdn.com/image/fetch/$s_!g4uP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 848w, https://substackcdn.com/image/fetch/$s_!g4uP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 1272w, https://substackcdn.com/image/fetch/$s_!g4uP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!g4uP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png" width="479" height="356.0135135135135" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/198512fe-9295-49b7-88ff-225950d08f2c_888x660.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:660,&quot;width&quot;:888,&quot;resizeWidth&quot;:479,&quot;bytes&quot;:655731,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/199361601?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!g4uP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 424w, https://substackcdn.com/image/fetch/$s_!g4uP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 848w, https://substackcdn.com/image/fetch/$s_!g4uP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 1272w, https://substackcdn.com/image/fetch/$s_!g4uP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198512fe-9295-49b7-88ff-225950d08f2c_888x660.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It was a resounding success, over 100k views! Unfortunately, I didn&#8217;t sell any courses in that video because I simply didn&#8217;t have the energy to both make the video and a course. The best business decision would have been to wait before uploading. I&#8217;ll go into more details later, but my biggest gripe with YouTube is that it&#8217;s no longer a great platform to build an audience but rather it&#8217;s only good for reach and here, I had wasted a lot of reach.</p><p>After having made so many game development tutorials, I wanted to try my hand in creating an original game that I would sell on Steam. Once the project was starting to take shape, I had the idea of making a video about it to gauge interest as I wasn&#8217;t sure it would find an audience. Therefore, I had the idea of using the same format used in my two previous successful videos. However, rather than focusing on technical details, I instead would tell the story of how I came up with my game&#8217;s design covering the various iterations and challenges I faced while working on it. Therefore, I ended up uploading a video titled &#8220;Making a Small RPG&#8221; which again, was originally an article.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;8a78f152-21db-4d3d-918e-58dedb7665fa&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Making a Small RPG&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-11-19T15:08:20.256Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!663W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8702120-a5b9-49b7-abe5-6c9b2e337e82_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/making-a-small-rpg&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:178016908,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:28,&quot;comment_count&quot;:4,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RVxb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RVxb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 424w, https://substackcdn.com/image/fetch/$s_!RVxb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 848w, https://substackcdn.com/image/fetch/$s_!RVxb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 1272w, https://substackcdn.com/image/fetch/$s_!RVxb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RVxb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png" width="482" height="375.4806629834254" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:564,&quot;width&quot;:724,&quot;resizeWidth&quot;:482,&quot;bytes&quot;:316737,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/199361601?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RVxb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 424w, https://substackcdn.com/image/fetch/$s_!RVxb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 848w, https://substackcdn.com/image/fetch/$s_!RVxb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 1272w, https://substackcdn.com/image/fetch/$s_!RVxb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc35f6e3-5d15-4847-926f-f0fc7d8e7c75_724x564.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It was also a resounding success reaching barely below 100k views! However, it came with a hidden cost. That cost was the tipping point that made me realize that YouTube is no longer a good platform to build an audience on. I naively thought that if the video performed well, this would translate to subscribers and an audience eager to hear more about the project, but this wasn&#8217;t the case. I had made a big mistake by not setting up a Steam page to direct viewers to before publishing the video. On my next upload concerning the project, the fall off in terms of views was brutal. I went from 98k views to below 10k. It became clear that YouTube was acting as a gatekeeper between me and the audience I thought I had built. </p><p>After reflecting on the situation, I came to the following conclusion. The reason my 3 previous videos had performed well was due having certain characteristics that aligned with YouTube&#8217;s goal as a platform, which consists in making people watch videos for as long as possible so they can serve more ads. I listed them below :</p><ol><li><p>The subject of all three videos were remarkable which lead to people clicking on them. Something is remarkable when it obviously stands out as being interesting/noteworthy. For example, the subject of my video titled &#8220;You Can Now Make PS2 Games in JavaScript&#8221; is remarkable because the PS2 is a very popular, but now old console and you had to use a hard programming language called C++ to make games for it. Being able to now use JavaScript, a simpler but most importantly, a language originally designed for making websites and not games, makes the subject come across as immediately noteworthy. Therefore, remarkable.</p></li><li><p>The use of storytelling made people eager to watch more of the video. This can be explained by the fact that we instinctively want to know what happens next in a compelling story.</p></li><li><p>Finally, the length of the videos were all above 10 minutes and the 2 more successful ones were in the 15+ min range. This resulted in more absolute watch time compared to shorter content. For example, if 2 videos are both watched fully by the same audience. The shorter one will translate to less total time spent on the platform compared to the longer one. Therefore, YouTube will recommend the longer one instead because there&#8217;s an opportunity cost to doing otherwise.</p></li></ol><p>To understand the fall off, it&#8217;s important to first mention that usually, series on YouTube don&#8217;t work. The second video of a series ends up making less views than the first because it requires prior context before clicking. Thus reducing its appeal and limiting its reach. However, I knew this going in. I tried making the second video as independent as possible but in the end, a second video talking about the same subject was bound to be less remarkable. It didn&#8217;t help that because I summarized the content of the first video in the second one, a familiar viewer would have found it less engaging making the video further away from hitting criteria 2 and 3 that I outlined above.</p><p>Consequently, I realized I had wasted my biggest marketing ammunition regarding my small RPG game as I had no way to contact the audience hit by the first video. Like with the one on making PS2 games in JavaScript, I had wasted tremendous reach.</p><p>At this point, I realized my biggest gripe with YouTube was simply that I could not access my audience reliably. Therefore, was it really my audience? On one hand, YouTube allows someone without a following to reach millions but on the other, the link to those reached is fickle. I thought I was building an audience by gaining subscribers but instead, I was building a sand castle that could easily be carried away by the slightest algorithm waves.</p><p>YouTube wasn&#8217;t always like this. People used to subscribe to channels and seek their content in their subscriptions tab. However, the platform effectively buried this model by conditioning users to seek recommended videos on the home page and deprioritizing the Subscriptions tab to the point that it barely looks like a clickable section. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZuR2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZuR2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 424w, https://substackcdn.com/image/fetch/$s_!ZuR2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 848w, https://substackcdn.com/image/fetch/$s_!ZuR2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 1272w, https://substackcdn.com/image/fetch/$s_!ZuR2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZuR2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png" width="370" height="479.57081545064375" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8649594a-3359-4e70-a003-602a873e5c38_466x604.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:604,&quot;width&quot;:466,&quot;resizeWidth&quot;:370,&quot;bytes&quot;:41325,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/199361601?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZuR2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 424w, https://substackcdn.com/image/fetch/$s_!ZuR2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 848w, https://substackcdn.com/image/fetch/$s_!ZuR2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 1272w, https://substackcdn.com/image/fetch/$s_!ZuR2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8649594a-3359-4e70-a003-602a873e5c38_466x604.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">You have to click on the &#8220;Subscriptions&#8221; text to access your sub feed. Doesn&#8217;t look very clickable doesn&#8217;t it?</figcaption></figure></div><p>I think that we&#8217;re now entering an era where YouTube is starting to treat content creators as interchangeable much like TikTok. They saw the success TikTok had, tried to replicate it with Shorts and now YouTube long form is getting affected as well. I fear that in the future, uploading to YouTube will look no different than making posts on Reddit. You might get views, you might get comments, but they&#8217;re self contained to a specific post with no following building up and no guarantee of your next posts having the same reach.</p><h1>Conclusion</h1><p>The conclusion to all of this is that it&#8217;s not worth it to be a YouTuber. Relying on YouTube adsense and sponsorships (sponsors use views as a metric to determine how much to pay you) for your livelihood is simply not sustainable due to how fickle getting views on the platform is. Therefore, focusing so much on making YouTube content will most likely lead to your exploitation.</p><p>That said, is quitting really the answer? Considering that YouTube can give you incredible reach even if you&#8217;re a nobody as long as you make content that is remarkable, engaging (for example, through storytelling) and long enough, it would be stupid to completely walk away, at least in my case.</p><h2>A New Hope</h2><p>Therefore a new strategy appears on the horizon. It consists in building your audience outside of YouTube through a mailling list (Substack conveniently allows you to do so) and to strategically make occasional compelling YouTube content to tap into the platform&#8217;s reach potential. However, the key is to always direct viewers to the mailling list. </p><p>Why is building an audience through email so important? because it allows you to have a direct and long lasting link with your audience. It also gives you independence from social media platforms. Even in the case of Substack, where this article is currently hosted, I can export my email list and move to another platform or email sending service without my subscribers even noticing.</p><p>This shift implies that I no longer need to worry about pumping frequent content for YouTube because I&#8217;m not making money through them or worrying about doing so. By making YouTube content rarely, I get to keep most of my energy to build something compelling outside the platform like an actual game, writing interesting articles, making an in-depth course or other kinds of art/products.</p><p>This plan seems to me as more sustainable and more healthy long term. That&#8217;s about all I&#8217;ve got to share. Hope this article was insightful. If you&#8217;re curious to see where this journey will lead, I recommend subscribing! I usually write about programming, game development and game design.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>You can check some of my previous articles below.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;c3c624a8-8f33-4d4b-9089-78397d0ee0fc&quot;,&quot;caption&quot;:&quot;I recently finished a prototype for a game called HARVEST MOVE based on an arcade style game of the same name that I developed 3 years ago. In the original version, the player moves on a grid and needs to collect as many crops as possible while avoiding enemies. If they get hit, the player is presented with their current score, their best score and the &#8230;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Trick to Designing Highly Replayable Arcade and Linear Games&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-05-21T19:45:08.168Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!PGI6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-trick-to-designing-highly-replayable&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:198641150,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;0d994636-9778-485d-bc6b-8279b79a32ce&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:14,&quot;comment_count&quot;:6,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d35d186d-25ca-4d43-bae9-74d2e7134d12&quot;,&quot;caption&quot;:&quot;I recently realized that if it weren&#8217;t for the fact that making games for the web was possible, I would have probably never tried game development and stuck with it. While reflecting why this was the case, I came to the conclusion that there are certain characteristics about the web that makes it an ideal platform for beginners and more experienced game&#8230;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Should Make Web Games&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-01-30T17:28:13.979Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!kyvU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-should-make-web-games&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:185557791,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:10,&quot;comment_count&quot;:1,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[The Trick to Designing Highly Replayable Arcade and Linear Games]]></title><description><![CDATA[I recently finished a prototype for a game called HARVEST MOVE based on an arcade style game of the same name that I developed 3 years ago.]]></description><link>https://jslegenddev.substack.com/p/the-trick-to-designing-highly-replayable</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/the-trick-to-designing-highly-replayable</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Thu, 21 May 2026 19:45:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PGI6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently finished a prototype for a game called <em><strong><a href="https://jslegend.itch.io/harvest-move-prototype">HARVEST MOVE</a></strong></em> based on an arcade style game of the same name that I developed 3 years ago. In the original version, the player moves on a grid and needs to collect as many crops as possible while avoiding enemies. If they get hit, the player is presented with their current score, their best score and the ability to play again.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PGI6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PGI6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 424w, https://substackcdn.com/image/fetch/$s_!PGI6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 848w, https://substackcdn.com/image/fetch/$s_!PGI6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 1272w, https://substackcdn.com/image/fetch/$s_!PGI6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PGI6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png" width="1456" height="1189" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1189,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:242138,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/198641150?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PGI6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 424w, https://substackcdn.com/image/fetch/$s_!PGI6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 848w, https://substackcdn.com/image/fetch/$s_!PGI6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 1272w, https://substackcdn.com/image/fetch/$s_!PGI6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7620b73-514b-4876-8254-b80a5f2f07c0_1998x1632.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A screenshot of my HARVEST MOVE prototype</figcaption></figure></div><p>However, while remaking it, I took a few design decisions that altered massively the replayability of the game. I&#8217;d like to share what I did.<br><br>First, I structured the game into configuration of enemies (I&#8217;ll start using the term level rather than configuration to make things clearer) that determined what enemy types and how many where to be placed on the grid. I then, tied those levels to specific score thresholds. Therefore, once a player reached a threshold they would be seamlessly moved to the next level.</p><p>Secondly, I turned the player&#8217;s score into a currency. When they had a game over, they could spend the wealth accumulated to unlock more valuable crops that would start spawning in their next attempt. Even though players always replayed the game starting from the first level, they could accumulate wealth much faster which translated to faster progress through levels they had previously played. Additionally, the more crops they unlocked, the more likely they were to reach late game levels locked behind higher score thresholds because they simply earned more.</p><p>Now, why does this structure seemingly works in making an arcade/linear game more replayable? I have a few hypothesis to present which are listed below.</p><ol><li><p>There is now a sense of permanent progression which mitigates the feeling of loss you experience after losing in a score based game. This makes you more likely to try again.</p></li><li><p>Replaying previous levels while being more powerful/quicker gives the player a very satisfying feeling of power which feeds into an innate power fantasy of some sort.</p></li><li><p>If your game has upgrades that meaningfully alter the gameplay experience, then you tap into a novelty effect that will make the game more enjoyable for longer. For example, having unique items/weapons the player can unlock or alternative pathways in a level or an alternative level.</p></li></ol><h1>Isn&#8217;t this just a Roguelike Structure?</h1><p>I don&#8217;t think so. Roguelikes usually rely on procedural generation and randomization to keep things fresh. There is less emphasis on level design and more emphasis on making builds and testing them out. Rather, I would estimate this structure to be more similar to time loop based games like The Legend of Zelda Majora&#8217;s Mask.</p><p>In Majora&#8217;s Mask, you play through the same handcrafted environment over and over and progressively unlock things in the game allowing you to access things you couldn&#8217;t before.</p><p>However, that doesn&#8217;t mean procedural generation can&#8217;t be used. In fact, I used it in my game, but I don&#8217;t believe in it being a core aspect.</p><h1>Conclusion</h1><p>Considering I accidently discovered this through experimentation, I still think there is room for fine tuning. However, this makes me excited to test this structure again in future projects. I wonder how this would fit with an RPG or a platformer, etc&#8230;</p><p>That said, it would be nice if I could get your feedback on my game. To make things as convenient as possible, <em><strong><a href="https://forms.gle/GPxGkBxEXc25LVes9">here</a></strong></em> is a google form (link to the game is inside the form) for you to provide your feedback so you don&#8217;t have to create an account to be able to comment.</p><p>Anyway, if you&#8217;re interested in all things related to programming, game development and game design I recommend subscribing to not miss out on future posts.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;5cd7ac08-d556-4177-b131-211f5c5b137a&quot;,&quot;caption&quot;:&quot;I recently realized that if it weren&#8217;t for the fact that making games for the web was possible, I would have probably never tried game development and stuck with it. While reflecting why this was the case, I came to the conclusion that there are certain characteristics about the web that makes it an ideal platform for beginners and more experienced game&#8230;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Should Make Web Games&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-01-30T17:28:13.979Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!kyvU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-should-make-web-games&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:185557791,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:10,&quot;comment_count&quot;:1,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;3274bc9b-453a-49e5-acaa-529e723a148f&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:14,&quot;comment_count&quot;:6,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[If You Like PICO-8, You'll Love KAPLAY (Probably)]]></title><description><![CDATA[I&#8217;ve been checking out PICO-8 recently.]]></description><link>https://jslegenddev.substack.com/p/if-you-like-pico-8-youll-love-kaplay</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/if-you-like-pico-8-youll-love-kaplay</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Sun, 01 Mar 2026 03:33:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!WOaI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WOaI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WOaI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 424w, https://substackcdn.com/image/fetch/$s_!WOaI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 848w, https://substackcdn.com/image/fetch/$s_!WOaI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 1272w, https://substackcdn.com/image/fetch/$s_!WOaI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WOaI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png" width="1456" height="821" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:821,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:827559,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WOaI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 424w, https://substackcdn.com/image/fetch/$s_!WOaI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 848w, https://substackcdn.com/image/fetch/$s_!WOaI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 1272w, https://substackcdn.com/image/fetch/$s_!WOaI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3438501a-cdf3-45ef-a7bd-8a59c9002a09_2556x1442.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I&#8217;ve been checking out <a href="https://www.lexaloffle.com/pico-8.php">PICO-8</a> recently. For those unaware, It&#8217;s a nicely constrained environment for making small games in Lua. It provides a built-in editor allowing you to write code, make sprites, make tile maps and make sounds. This makes it ideal to prototype game ideas or make small games.</p><p>You know what tool is also great for prototyping game ideas or making small games? Well&#8230; <a href="https://kaplayjs.com/">KAPLAY</a>!</p><p>It&#8217;s a simple free and open source library for making games in JavaScript. I suspect there might be a sizeable overlap between people who like PICO-8 and those who would end up liking or even loving KAPLAY as well if they gave it a try.</p><p>During my PICO-8 learning journey, I came across this nice tutorial teaching you how to make a coin collecting game in 10 minutes.</p><div id="youtube2-Yu-FUnu0pXk" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;Yu-FUnu0pXk&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/Yu-FUnu0pXk?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>In this article, I&#8217;d like to teach you how to build roughly the same game in KAPLAY. This will better demonstrate in what ways this game library makes game development faster much like PICO-8.</p><p>Feel free to follow along if you wish to!</p><h1>Installation Process</h1><p>KAPLAY lacks all of the tools included in PICO-8. There is no all-in-one package you can use to write your code, make your sprites, build your maps or even make sounds.</p><p>You might be wondering, then, how KAPLAY is in any way similar to PICO-8 if it lacks all of this?</p><p>My answer : KAPLAY makes up for it by making the coding part really easy by offering you a lot logic built-in. For example, it handles collisions, physics, scene management, animations etc&#8230; for you. You&#8217;ll see some of this in action when we arrive at the part where we write the game&#8217;s code.</p><p>Now, how do we use KAPLAY? Here&#8217;s the simplest way I&#8217;ve found.</p><p>You install <a href="https://code.visualstudio.com/">VSCode</a> (a popular code editor) along with the Live Server extension (can be found in the extensions marketplace within the editor). You then create a folder that you open within VSCode.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RzXf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RzXf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 424w, https://substackcdn.com/image/fetch/$s_!RzXf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 848w, https://substackcdn.com/image/fetch/$s_!RzXf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 1272w, https://substackcdn.com/image/fetch/$s_!RzXf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RzXf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png" width="1456" height="931" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:931,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:520034,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RzXf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 424w, https://substackcdn.com/image/fetch/$s_!RzXf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 848w, https://substackcdn.com/image/fetch/$s_!RzXf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 1272w, https://substackcdn.com/image/fetch/$s_!RzXf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e87ce1-411f-4a5f-8d01-f276494aaccf_2224x1422.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Once the folder is opened, we only need to create two files. One called index.html and the other main.js.</p><p>Your index.html file should contain the following :</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;html&gt;
  &lt;body&gt;
    &lt;script src="main.js" type="module"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre></div><p>Since KAPLAY works on the web, it lives within a web page. index.html is that page. Then, we link our JavaScript file to it. We set the type to &#8220;module&#8221; so we can use import statements in our JS.</p><p>We then add the following :</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// in main.js
import kaplay from "https://unpkg.com/kaplay@3001.0.19/dist/kaplay.mjs";

kaplay();
</code></pre></div><p>Voil&#224;! We can now use the KAPLAY library. Since we installed the Live Server extension, you should now have access to a &#8220;Go Live&#8221; button at the bottom of the editor.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TieF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TieF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 424w, https://substackcdn.com/image/fetch/$s_!TieF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 848w, https://substackcdn.com/image/fetch/$s_!TieF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 1272w, https://substackcdn.com/image/fetch/$s_!TieF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TieF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png" width="1456" height="879" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:879,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:118975,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TieF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 424w, https://substackcdn.com/image/fetch/$s_!TieF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 848w, https://substackcdn.com/image/fetch/$s_!TieF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 1272w, https://substackcdn.com/image/fetch/$s_!TieF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa854b2c9-4c1f-4443-bf4e-c9bd0e2e5612_1630x984.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To actually run the game, all you have to do is click it. This will open the web page in your default browser. KAPLAY by default creates a canvas with a checkered pattern.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rMAp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rMAp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 424w, https://substackcdn.com/image/fetch/$s_!rMAp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 848w, https://substackcdn.com/image/fetch/$s_!rMAp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 1272w, https://substackcdn.com/image/fetch/$s_!rMAp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rMAp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png" width="1456" height="1057" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1057,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:62860,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rMAp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 424w, https://substackcdn.com/image/fetch/$s_!rMAp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 848w, https://substackcdn.com/image/fetch/$s_!rMAp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 1272w, https://substackcdn.com/image/fetch/$s_!rMAp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95514a80-3581-4249-b7a3-b2de416b6609_1648x1196.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>One thing pretty cool with this setup, is that every time you change something in your code and hit save (Ctrl+S or Cmd+S on a Mac), the web page reloads and you can see your latest changes instantly.</p><h1>Making The Game</h1><p>I&#8217;ve created the following spritesheet to be used in our game.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oFPZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oFPZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 424w, https://substackcdn.com/image/fetch/$s_!oFPZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 848w, https://substackcdn.com/image/fetch/$s_!oFPZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 1272w, https://substackcdn.com/image/fetch/$s_!oFPZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oFPZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png" width="600" height="200" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e86212bc-bb09-4650-af43-7e01786e2046_600x200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:200,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2455,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oFPZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 424w, https://substackcdn.com/image/fetch/$s_!oFPZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 848w, https://substackcdn.com/image/fetch/$s_!oFPZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 1272w, https://substackcdn.com/image/fetch/$s_!oFPZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe86212bc-bb09-4650-af43-7e01786e2046_600x200.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Note that since the image is transparent, the cloud to the right is not really visible. You can download the image above to follow along.</p><p>The next step is to place the image in the same folder as our HTML page and JavaScript file.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6BX6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6BX6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 424w, https://substackcdn.com/image/fetch/$s_!6BX6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 848w, https://substackcdn.com/image/fetch/$s_!6BX6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 1272w, https://substackcdn.com/image/fetch/$s_!6BX6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6BX6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png" width="1456" height="876" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:876,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120494,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6BX6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 424w, https://substackcdn.com/image/fetch/$s_!6BX6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 848w, https://substackcdn.com/image/fetch/$s_!6BX6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 1272w, https://substackcdn.com/image/fetch/$s_!6BX6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a925f1-11d6-45b5-a951-e2abbcd9d9fc_1642x988.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We&#8217;re now ready to make our game.</p><h2>Setting Up The Canvas</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// in main.js
import kaplay from "https://unpkg.com/kaplay@3001.0.19/dist/kaplay.mjs";

// kaplay will create a canvas in index.html for us
kaplay({
  width: 1000,
  height: 1000,
  letterbox: true,
});</code></pre></div><p>Here we set the width and the height of our canvas. The letterbox option is used to make sure the canvas scales according to the browser window but without losing its aspect ratio.</p><h2>Loading Assets</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
loadSprite("spritesheet", "./spritesheet.png", {
  sliceX: 3,
  sliceY: 1,
});</code></pre></div><p>We can load our spritesheet by using the loadSprite KAPLAY function. The first param is the name you want to use to refer to it elsewhere in your code. The second param is the path to get that asset. Finally, the third param is used to tell KAPLAY how to slice your image into individual frames. </p><p>Considering that in our spritesheet we have three sprites placed in a row, the sliceX property should be set to 3. Since we have only one sprite per column (because we only have one column) sliceY should be set to 1.</p><h2>Setting Key Constants and Variables Needed For The Game</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code

const GRAVITY_VALUE = 100;
const BASKET_SPEED = 500;
let score = 0;</code></pre></div><h2>Setting Gravity</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
setGravity(GRAVITY_VALUE);</code></pre></div><p>To make the coins fall from the top, we&#8217;ll use KAPLAY&#8217;s physics system. You can set the gravity by calling KAPLAY&#8217;s setGravity function.</p><h2>Making The Background Blue</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
// blue rectangle serving as our background
add([rect(1000, 1000), color(0, 170, 255)]);</code></pre></div><p>KAPLAY&#8217;s add function is used to create a game object by providing an array of components. These components are offered by KAPLAY and come with prebuilt functionality. The rect() component sets the graphics of the game object to be a rectangle with a width and height of 1000. On the other hand, the color component sets its color.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!W7Lc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!W7Lc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 424w, https://substackcdn.com/image/fetch/$s_!W7Lc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 848w, https://substackcdn.com/image/fetch/$s_!W7Lc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 1272w, https://substackcdn.com/image/fetch/$s_!W7Lc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!W7Lc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png" width="1456" height="689" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:689,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:145825,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!W7Lc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 424w, https://substackcdn.com/image/fetch/$s_!W7Lc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 848w, https://substackcdn.com/image/fetch/$s_!W7Lc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 1272w, https://substackcdn.com/image/fetch/$s_!W7Lc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc47a7daa-34cb-4525-809c-8c9537de53c4_3820x1808.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">You should have the following result at this stage.</figcaption></figure></div><h2>Creating The Basket</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
const basket = add([
  sprite("spritesheet", { frame: 0 }),
  anchor("center"),
  pos(center().x, center().y + 300),
  area({ shape: new Rect(vec2(0), 100, 100) }),
  body({ isStatic: true }),
]);</code></pre></div><p>The basket is a also a game object with several different components. Here is what each does :</p><h3>sprite()</h3><p>Sets the sprite used by the game object. The first param is for providing the name of the sprite we want to use. Since we&#8217;re using a spritesheet which contains three different sprites in the same image, we need to specify the frame to use. The basket sprite corresponds to frame 0.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!igmF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!igmF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 424w, https://substackcdn.com/image/fetch/$s_!igmF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 848w, https://substackcdn.com/image/fetch/$s_!igmF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 1272w, https://substackcdn.com/image/fetch/$s_!igmF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!igmF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png" width="1456" height="494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:494,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:170802,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!igmF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 424w, https://substackcdn.com/image/fetch/$s_!igmF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 848w, https://substackcdn.com/image/fetch/$s_!igmF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 1272w, https://substackcdn.com/image/fetch/$s_!igmF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa38bdd9-7026-464b-9823-9e894bc0b91b_2400x814.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>anchor()</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DnI9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DnI9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 424w, https://substackcdn.com/image/fetch/$s_!DnI9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 848w, https://substackcdn.com/image/fetch/$s_!DnI9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 1272w, https://substackcdn.com/image/fetch/$s_!DnI9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DnI9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png" width="1456" height="802" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/db380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:802,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:125659,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DnI9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 424w, https://substackcdn.com/image/fetch/$s_!DnI9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 848w, https://substackcdn.com/image/fetch/$s_!DnI9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 1272w, https://substackcdn.com/image/fetch/$s_!DnI9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb380781-f208-470d-8139-9c8ee8be4b8d_2044x1126.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>By default, game objects are positioned based on their top-left corner. However, I prefer having it centered. The anchor component is for this purpose.</p><h3>pos()</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">pos(center().x, center().y + 300)</code></pre></div><p>This component is used to set the position of the game object on the canvas. Here we also use center() which is a KAPLAY provided function that provides the coordinates of the center of the canvas.</p><h3>area()</h3><p>This component is used to set the hitbox of a game object. This will allow KAPLAY&#8217;s physics system to handle collisions for us. There is a debug mode you can access by pressing the f1 (fn+f1 on Mac) key which will make hitboxes visible.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iIOq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iIOq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 424w, https://substackcdn.com/image/fetch/$s_!iIOq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 848w, https://substackcdn.com/image/fetch/$s_!iIOq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 1272w, https://substackcdn.com/image/fetch/$s_!iIOq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iIOq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png" width="1456" height="1459" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1459,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74490,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/189285724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iIOq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 424w, https://substackcdn.com/image/fetch/$s_!iIOq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 848w, https://substackcdn.com/image/fetch/$s_!iIOq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 1272w, https://substackcdn.com/image/fetch/$s_!iIOq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02444ecc-c2b4-4d91-b526-46ac7aeebd5f_1738x1742.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example when debug mode is on.</figcaption></figure></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">area({ shape: new Rect(vec2(0), 100, 100) }) // vec2(0) is equivalent to vec2(0,0)</code></pre></div><p>As for setting the shape of the hitbox, you can use the Rect class which takes 3 params. The first expects a vec2 (a data structure offered by KAPLAY used to set pair of values) describing where to place the hitbox relative to the game object. If set to 0, the hitbox will have the same position as the game object. The two params left are used to set its width and height.</p><h3>body()</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">body({ isStatic: true })</code></pre></div><p>Finally, the body component is used to make the game object susceptible to physics. If added alone, the game object will be affected by gravity. However, to prevent this, we can set the isStatic property to true. This is very useful, for example, in a platformer where platforms need to be static so they don&#8217;t fall off.</p><h2>Implementing Player Controls</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
onKeyDown((key) =&gt; {
  if (key === "left") basket.move(-BASKET_SPEED, 0); // .move(velX, velY)
  if (key === "right") basket.move(BASKET_SPEED, 0);
});</code></pre></div><p>Here we can use the move method available on all game objects to make the basket move in the desired direction.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;4a1fa19b-104b-4c97-92ac-a5b238512018&quot;,&quot;duration&quot;:null}"></div><h2>Spawning Coins To Collect</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
loop(1, () =&gt; {
  add([
    sprite("spritesheet", { frame: 1 }),
    anchor("center"),
    area({ shape: new Rect(vec2(0), 40, 40) }),
    body(),
    pos(randi(10, 950), -50),
    offscreen({ distance: 50, destroy: true }),
    "coin",
  ]);
});</code></pre></div><p>The loop function spawns a coin every second. We use the randi function to set a random X position between 10 and 950. The offscreen component is used to destroy the game object once it&#8217;s out of view. Finally a simple string &#8220;coin&#8221; is added alongside the array of components to tag the game object being created. This will allow us to determine which coin collided with the basket so we can destroy it and increase the score.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;7935caad-11ef-4f6e-9492-ea0ab5f03d95&quot;,&quot;duration&quot;:null}"></div><h2>Keeping Track of The Score</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
const scoreText = add([text("0"), pos(0, 0)]);

basket.onCollide("coin", (coin) =&gt; {
  destroy(coin);
  score++;
  scoreText.text = score;
});</code></pre></div><p>Text can be displayed by creating a game object with the text component. To know when a coin collides with the basket, we can use its onCollide method (available by default). The first param of that method is the tag of the game object you want to check collisions with.<br><br>Since we have multiple coins using the &#8220;coin&#8221; tag, the specific coin currently colliding with the basket will be passed as a param to the collision handler. Now we can destroy the coin, increase the score and display the new score.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;0c296993-aa69-440a-b52c-ed759f4da67f&quot;,&quot;duration&quot;:null}"></div><h2>Adding Clouds in The Background</h2><p>As mentioned earlier, KAPLAY does not have a map making tool. However, it does offer the ability to create maps using arrays of strings. For anything more complex, you should check out <a href="https://www.mapeditor.org/">Tiled</a> which is also open source and made for that purpose.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">// add this below the previous code
addLevel([
  "   #  #  ", 
  " #  #   #", 
  "#  #  #  #", 
  "  # #  # #", 
  "#  #  #"], {
  tileWidth: 200,
  tileHeight: 200,
  tiles: {
    "#": () =&gt; [sprite("spritesheet", { frame: 2 })],
  },
});</code></pre></div><p>Where we place the # character in the string array determines where clouds will be placed in the game.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;89f328ae-4363-4833-819a-6d12a7b79a7f&quot;,&quot;duration&quot;:null}"></div><h1>Full Game&#8217;s Code</h1><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">import kaplay from "https://unpkg.com/kaplay@3001.0.19/dist/kaplay.mjs";

kaplay({
  width: 1000,
  height: 1000,
  letterbox: true,
});

loadSprite("spritesheet", "./spritesheet.png", {
  sliceX: 3,
  sliceY: 1,
});

const GRAVITY_VALUE = 100;
const BASKET_SPEED = 500;
let score = 0;

setGravity(GRAVITY_VALUE);

// blue rectangle serving as our background
add([rect(1000, 1000), color(0, 170, 255)]);

const basket = add([
  sprite("spritesheet", { frame: 0 }),
  anchor("center"),
  pos(center().x, center().y + 300),
  area({ shape: new Rect(vec2(0), 100, 100) }),
  body({ isStatic: true }),
]);

onKeyDown((key) =&gt; {
  if (key === "left") basket.move(-BASKET_SPEED, 0);
  if (key === "right") basket.move(BASKET_SPEED, 0);
});

loop(1, () =&gt; {
  add([
    sprite("spritesheet", { frame: 1 }),
    anchor("center"),
    area({ shape: new Rect(vec2(0), 40, 40) }),
    body(),
    pos(randi(10, 950), -50),
    offscreen({ distance: 50, destroy: true }),
    "coin",
  ]);
});

const scoreText = add([text("0"), pos(0, 0)]);

basket.onCollide("coin", (coin) =&gt; {
  destroy(coin);
  score++;
  scoreText.text = score;
});

addLevel(["   #  #  ", " #  #   #", "#  #  #  #", "  # #  # #", "#  #  #"], {
  tileWidth: 200,
  tileHeight: 200,
  tiles: {
    "#": () =&gt; [sprite("spritesheet", { frame: 2 })],
  },
});
</code></pre></div><h1>Deployment</h1><p>Publishing a KAPLAY game is very simple. You compress your folder into a .zip archive and you upload it to itch.io or any other site you wish to. The game will be playable in the browser without players needing to download it.<br><br>Now, what if you&#8217;d like to make it downloadable as well? A very simple tool you can use is GemShell. It allows you to make executables for Windows/Mac/Linux in what amounts to a click.</p><p>You can use the lite version for free.<em> <strong>If you plan on upgrading, you can use my <a href="https://l0om.itch.io/jslegend">link</a> to get 15% off your purchase. </strong></em>To be transparent, this is an affliate link. If you end purchasing the tool using my link, I&#8217;ll get a cut of that sale.</p><h1>Conclusion</h1><p>I just scratched the surface with KAPLAY today. I hope it gave you a good idea of what it&#8217;s like to make games with it.</p><p>If you&#8217;re interested in more technical articles like this one, I recommend subscribing to not miss out on future publications.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>In the meantime, you can check out the following : </p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;f4d241cd-ed3b-4d87-aa5d-67b4e59f93b6&quot;,&quot;caption&quot;:&quot;Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes. Let&#8217;s get started!&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The KAPLAY Game Library in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-02T01:07:47.577Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169792492,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d42965b3-7b49-49c2-8a62-a15820be7ce6&quot;,&quot;caption&quot;:&quot;I went on a quest to test the 3 most popular 2D web game frameworks according to their NPM download numbers. To achieve this, I set out to build a Sonic infinite runner game in each framework. Today, I&#8217;d like to share my findings.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;I Tried 3 Web Game Frameworks (So You Don&#8217;t Have To)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-02-16T00:21:32.288Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!PY6a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/i-tried-3-web-game-frameworks-so&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:187406471,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:5,&quot;comment_count&quot;:1,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;13945ce4-1f9d-4a71-bf68-b9884d0b74d6&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Programmer's Roller Coaster (Comic)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-12-14T20:39:14.223Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!mU4e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0da1a2e-d441-412f-bf7d-2b3038989139_2560x4096.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-programmers-roller-coaster-comic&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:181603355,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:9,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;25224688-2313-477e-aff9-c15d9e1f67b8&quot;,&quot;caption&quot;:&quot;I recently realized that if it weren&#8217;t for the fact that making games for the web was possible, I would have probably never tried game development and stuck with it. While reflecting why this was the case, I came to the conclusion that there are certain characteristics about the web that makes it an ideal platform for beginners and more experienced game&#8230;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Should Make Web Games&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-01-30T17:28:13.979Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!kyvU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-should-make-web-games&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:185557791,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:8,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;98d5763d-ed33-4a0c-84ff-0b3e8153a2fd&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:13,&quot;comment_count&quot;:6,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[I Tried 3 Web Game Frameworks (So You Don’t Have To)]]></title><description><![CDATA[I went on a quest to test the 3 most popular 2D web game frameworks according to their NPM download numbers.]]></description><link>https://jslegenddev.substack.com/p/i-tried-3-web-game-frameworks-so</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/i-tried-3-web-game-frameworks-so</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Mon, 16 Feb 2026 00:21:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PY6a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PY6a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PY6a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!PY6a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!PY6a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!PY6a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PY6a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:267957,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/187406471?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PY6a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!PY6a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!PY6a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!PY6a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28db37f7-aa7c-450b-a03b-2ee285c8d67d_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I went on a quest to test the 3 most popular 2D web game frameworks according to their NPM download numbers. To achieve this, I set out to build a Sonic infinite runner game in each framework. Today, I&#8217;d like to share my findings.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CBqu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CBqu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 424w, https://substackcdn.com/image/fetch/$s_!CBqu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 848w, https://substackcdn.com/image/fetch/$s_!CBqu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 1272w, https://substackcdn.com/image/fetch/$s_!CBqu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CBqu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png" width="1456" height="463" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:463,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:155128,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/187406471?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CBqu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 424w, https://substackcdn.com/image/fetch/$s_!CBqu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 848w, https://substackcdn.com/image/fetch/$s_!CBqu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 1272w, https://substackcdn.com/image/fetch/$s_!CBqu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18551ee0-9470-4764-a7e6-56a41d45336c_2140x680.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Below are links to playable demos along with the source code for each version of the game. I recommend browsing the code of each version to get a better feel of the differences between them.</p><ul><li><p>KAPLAY version - (<a href="https://jslegend.itch.io/sonic-ring-run">live demo</a>) - (<a href="https://github.com/JSLegendDev/sonic-runner">source code</a>)</p></li><li><p>Phaser version - (<a href="https://jslegend.itch.io/sonic-ring-run-phaser-4">live demo</a>) - (<a href="https://github.com/JSLegendDev/sonic-runner-phaser-tutorial">source code</a>)</p></li><li><p>Excalibur version - (<a href="https://jslegend.itch.io/sonic-ring-run-excalibur-version">live demo</a>) - (<a href="https://github.com/JSLegendDev/sonic-runner-excalibur">source code</a>)</p></li></ul><p>Note that even though KAPLAY markets itself as a library and Excalibur as a game engine they still feel like frameworks and occupy the same space as Phaser. That&#8217;s why I took the liberty to refer to them as frameworks.</p><p>I will not beat around the bush. <em><strong>If you want stability and performance, you should pick Phaser. </strong></em>That said, overall, KAPLAY is the easiest one to learn but the least scalable of the 3. This makes it a good choice to get into game development as a beginner and to prototype ideas quickly but you&#8217;ll end up facing performance issues eventually. I&#8217;d like to note that there have been recent performance improvements. However, KAPLAY&#8217;s performance still doesn&#8217;t come anywhere close to the one offered by Phaser.</p><p>Additionally, Phaser has an AI advantage. For example, if you ask ChatGPT a question related to Phaser, more likely than not, the answer provided will be right. As for KAPLAY and Excalibur, you&#8217;ll get a lot more hallucinations. This AI advantage lowers the barrier of learning for Phaser which, in contrast, makes the easiness of KAPLAY less compelling.</p><p>While Phaser&#8217;s API is verbose, you get over it especially when you get performance and stability in exchange. Finally, Phaser is significantly more popular than the other 2 which means it will be easier to find help when needed.</p><p>If you&#8217;re looking to target mobile, Phaser is also your best bet between the 3 as the performance on Safari is also quite good and the best. This is relevant because Safari uses the Webkit web engine which is what&#8217;s forced on every browser and webview on iOS.</p><p>Finally, for multiplayer games, Phaser offers the ability to run it on the server in headless mode. This makes it much easier to handle state in a multiplayer game. To my knowledge, neither KAPLAY nor Excalibur offers something like this.</p><h1>Should You Still Use KAPLAY?</h1><p>As mentioned previously, KAPLAY might still be worth using in the context of prototyping or for game jams, as its component based approach allows you to truly iterate quickly on game design. Its simplicity also makes it an ideal candidate for teaching game development to beginners. Some have used it to teach children.</p><h1>Should You Stil Use Excalibur?</h1><p>Phaser and Excalibur felt very similar when using them as they both relied on the object oriented paradigm. Yet, I felt that Excalibur sometimes required more work. A notable example of this is when coming back to a scene after having switched away from it. The state of that scene would be left as is before transitioning over. This meant you had to write extra logic to reset the scene&#8217;s state, something you didn&#8217;t have to do in Phaser.</p><p>That said, I still felt that Excalibur&#8217;s API was on average a bit more concise than Phaser&#8217;s. <br><br>In conclusion, the issue with Excalibur is that it didn&#8217;t feel different enough from Phaser to warrant switching over to it, considering Phaser is more performant, more featureful, and more popular.</p><p>At the same time, I was impressed with how Excalibur handled its debug mode. Instead of having it within the framework, it was instead available as a browser extension. Debug mode would therefore appear in the browser devtools and could be toggled and configured from there. It was a nice experience.</p><h1>GemShell</h1><p>Regardless of the web framework you end up using, if you want to bring your web game to Steam, the easiest tool for achieving this is GemShell.</p><p>It allows you to create executables for Windows, Mac and Linux in what amounts to a single click. It also offers an integration with Steam so you can easily have access to features like Steam achievements and now also the Steam overlay. (The latter might not be available yet at the time of publishing this post.)</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;007741e0-9cf3-4cc8-98a9-e86d51c5c728&quot;,&quot;duration&quot;:null}"></div><p><em>(Video by the developer showcasing the use of Steam overlay on top of a game packaged with GemShell)</em></p><p>This tool will save you hours that would have been wasted configuring tools like Electron and Steamworks.js. Time which could have been spent working on your game instead. </p><p>To have a consistent experience across operating systems, GemShell now makes each executable Chromium based so that your game renders the same way on all platforms.</p><p>Additionally, The developer is also considering implementing the ability to export for mobile platforms as well.<br><br>Due to how useful this tool is, I have decided to partner with its developer to offer you a 15% discount if you use the following link : <a href="https://l0om.itch.io/jslegend">https://l0om.itch.io/jslegend</a></p><p>To be completely transparent, if you end up purchasing GemShell using the link above, I&#8217;ll get a cut of that sale. That said, a free lite version is available, if you want to get a feel for the tool.</p><p>I hope you enjoyed this post, I recommend subscribing to not miss out on future releases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>In the meantime, you can read the following : </p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;8f4e1341-3bc1-4b0f-a12d-04fe71a1dcf5&quot;,&quot;caption&quot;:&quot;Phaser is the most popular JavaScript/TypeScript framework for making 2D games. It&#8217;s performant, and popular games like Vampire Survivors and Pok&#233;Rogue were made with it.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Phaser Game Framework in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-01-07T15:54:55.314Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Sgvr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-phaser-game-framework-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:183141374,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:5,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;5bab9fcf-31b2-4bfc-a6f5-6b9ca691a98c&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:13,&quot;comment_count&quot;:6,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a24f906d-9e29-4aa5-a242-e75b63eecfa5&quot;,&quot;caption&quot;:&quot;Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes. Let&#8217;s get started!&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The KAPLAY Game Library in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-02T01:07:47.577Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169792492,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[You Should Make Web Games]]></title><description><![CDATA[I recently realized that if it weren&#8217;t for the fact that making games for the web was possible, I would have probably never tried game development and stuck with it.]]></description><link>https://jslegenddev.substack.com/p/you-should-make-web-games</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/you-should-make-web-games</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Fri, 30 Jan 2026 17:28:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kyvU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kyvU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kyvU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!kyvU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!kyvU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!kyvU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kyvU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:150256,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/185557791?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kyvU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!kyvU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!kyvU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!kyvU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e11b673-4a5d-4692-8a48-e43cd7e7825d_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I recently realized that if it weren&#8217;t for the fact that making games for the web was possible, I would have probably never tried game development and stuck with it. While reflecting why this was the case, I came to the conclusion that there are certain characteristics about the web that makes it an ideal platform for beginners and more experienced game developers. That&#8217;s why I&#8217;d like to convince you to start making web games if you&#8217;re not doing so already.</p><h1>Limitations That Prevents Over-Scoping</h1><p>A prevalent issue that occurs to most game devs is over-scoping. I recently came across a game dev youtuber who struggled to finish anything and jumped from one incomplete project to another. They were an Unreal Engine solo dev and it was clear that the projects they wanted to make were just too ambitious.</p><p>When making games for the web, you&#8217;re pretty limited in what you can do graphics wise and even scope wise (assuming you don&#8217;t make a browser based MMO that relies on a server).</p><p>For example, as long as you don&#8217;t rely on a server, a web game will usually save its data in the player&#8217;s browser local storage which gets cleared after a week or so of inactivity or when the browser cache is cleared by the user. Because of this, you&#8217;ll rarely, if ever, see someone making a game with above a couple of hours of playtime because they know they can&#8217;t rely on save data being reliably accessible. This dramatically reduces the scope expected of a web game.</p><h1>No Pressure in Terms of Playtime</h1><p>The fact that web games are mostly short experiences is freeing. You don&#8217;t have to worry that players will complain due to a lack of content and therefore, feel pressured to inflate the playtime.</p><p>With this worry out of the way, you can quickly go from game idea to a completed project and move on to the next idea before you even have the chance to get bored of your previous one. Due to the small scale nature of web games, you can explore different genres easily before finding what you truly like making or what garners the most attention.</p><p>This quick iteration possible with web games has a lot of similarities with making YouTube videos. You make a video, you upload it, check its stats, work on the next one where you can improve or try something different.</p><p>If you use itch.io to host your games, you get basically the same experience, you get stats where you can see how many views your game has, how many browser plays, if there are any comments, ratings, etc&#8230;</p><p>Therefore, you get a fast feedback loop which is incredibly motivating as a beginner. You can quickly incorporate improvements in your next projects. Having access to statistics allows you to know if you&#8217;re moving in the right direction.</p><h1>Reaching Players is Ridiculously Easy Even Without an Audience</h1><p>You might be wondering, how do you get feedback on web games if there are no algorithms like on YouTube to serve your game to a new audience? What if you don&#8217;t have a preexisting audience?</p><p>It&#8217;s true that if you publish your game on itch.io outside of participating in a game jam, you&#8217;ll be launching to crickets.</p><p>That said, I&#8217;d like to make the point that reaching players with a web game is still ridiculously easy even if you don&#8217;t have an audience. Let&#8217;s explore a few avenues you can try.</p><h2>Newgrounds</h2><p>Publishing your game on Newgrounds is an almost guaranteed way to get at least a few players. This is because, when publishing your game on this platform, it has to go through a rating process where the site puts your game in front of real users. After the rating process is done, you&#8217;ll get a score on 5 and a few comments. From there your game can either become more popular or stay as is.</p><h2>Hacker News</h2><p>While this is a tech forum, people do share games on there from time to time and they do end up on the front page. Usually, people there are technical so if you can also mention how your game was built, you might get more attention. For example, mentioning the programming language, framework, engine, etc&#8230; in the title of the post. That said, this is not strictly necessary.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N92W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N92W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 424w, https://substackcdn.com/image/fetch/$s_!N92W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 848w, https://substackcdn.com/image/fetch/$s_!N92W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 1272w, https://substackcdn.com/image/fetch/$s_!N92W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N92W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png" width="1456" height="114" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:114,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:65253,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/185557791?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N92W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 424w, https://substackcdn.com/image/fetch/$s_!N92W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 848w, https://substackcdn.com/image/fetch/$s_!N92W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 1272w, https://substackcdn.com/image/fetch/$s_!N92W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f6fcd64-b5c9-4e00-93ae-11ae41f0c08b_2300x180.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EMtK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EMtK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 424w, https://substackcdn.com/image/fetch/$s_!EMtK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 848w, https://substackcdn.com/image/fetch/$s_!EMtK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 1272w, https://substackcdn.com/image/fetch/$s_!EMtK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EMtK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png" width="1456" height="131" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:131,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56639,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/185557791?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EMtK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 424w, https://substackcdn.com/image/fetch/$s_!EMtK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 848w, https://substackcdn.com/image/fetch/$s_!EMtK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 1272w, https://substackcdn.com/image/fetch/$s_!EMtK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ae5dba-8fc1-4746-82f7-b8dd09e1d69b_1912x172.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OlBB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OlBB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 424w, https://substackcdn.com/image/fetch/$s_!OlBB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 848w, https://substackcdn.com/image/fetch/$s_!OlBB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 1272w, https://substackcdn.com/image/fetch/$s_!OlBB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OlBB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png" width="1456" height="155" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:155,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39193,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/185557791?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OlBB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 424w, https://substackcdn.com/image/fetch/$s_!OlBB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 848w, https://substackcdn.com/image/fetch/$s_!OlBB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 1272w, https://substackcdn.com/image/fetch/$s_!OlBB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcbf6e24-bb02-4c71-b24a-7b0353eff7ba_1696x180.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>When a link gets popular on Hacker News, you&#8217;ll often start seeing it shared elsewhere on your behalf, for example on Reddit, on X/Twitter, etc&#8230; this can transform into a snowball effect and make your game viral.</p><h2>Reddit</h2><p>It offers a dedicated subreddit for webgames like /r/WebGames where you can post a link to your game. However, try looking beyond that. For example, you can submit your game to /r/CozyGamers if you&#8217;re making a cozy game as long you follow the rules.</p><h2>YouTube/Other Social Media</h2><p>There is also nothing preventing you from using YouTube and other social media platforms to promote your game. Making devlogs or videos showcasing your game and putting the link to play it in the description is a great way to find new players. That said, at this point, you&#8217;re building an audience.</p><p>It might take a while to get traction but because these platforms have recommendation algorithms, at some point, some people will come across your game.</p><h2>The Key : Make Remarkable Games</h2><p>As with your standard PC game, the success you&#8217;ll see using the avenues I just listed depends highly on how remarkable your game is.</p><p>The strength of web games is that if you don&#8217;t see traction with one project you can quickly spin up a new one and the time you invested in building one is an order of magnitude less thant what it would take to make a Steam release. </p><p>Anyway, once one of your web games goes viral, you can invest time in making an expanded version for Steam.</p><h1>People are More Than Willing to Try a Web Game From an Unknown Developer VS a Downloadable One</h1><p>This seems obvious. Most people don&#8217;t want to download a game from an unknown developer because the risk of getting hacked is high.</p><p>In fact, the well known game marketing expert Chriz Zukowski mentions in his post about <a href="https://howtomarketagame.com/2025/05/12/benchmark-itch-io-traffic/">analyzing itch.io&#8217;s traffic</a> that people are 3 times more likely to try your game if it&#8217;s playable on the web VS having to download it.</p><p>The friction of a web game is near 0, you&#8217;ll get more people willing to try it which translates into more people giving feedback, more attention, etc&#8230; which will in turn make a beginner more encouraged to continue their game dev journey. This is also useful for more experienced developers to know which project is worth spending more time on.</p><p>The web is also not just for completed games. Let&#8217;s say you&#8217;re having trouble finishing a project, you can still publish it as cool prototype and it will garner attention if it&#8217;s good. This will in turn maybe motivate you to push through or have a better idea of what to work on next. Contrast this with making the same project available only through a download. Nobody is going to install a 15 minute game or prototype.</p><h1>You Can Expand and Bring a Web Game to Steam Easily</h1><p>Most people who ridicule web games, probably think of mobile-y casual games you find on web portals that are played by children during their computer lab classes. While there are games that serve this purpose, it&#8217;s a huge mistake to view them as just that. <em><strong>Web games can also serve as a wonderful litmus test to determine which game ideas and concepts are worth investing time into for a full release on Steam. Web games are therefore, great for validation.</strong></em></p><p>Chris Zukowksi, the game marketing expert that I mentioned previously, confirmed that this strategy actually works really well. You can check out the post were he lays this out, <a href="https://howtomarketagame.com/2025/05/12/benchmark-itch-io-traffic/">here</a>.</p><p>Anyway, I&#8217;d like to emphasize that if you&#8217;re using web technologies (HTML/CSS/JS) to make web games, you&#8217;re not stuck on the web. The process of bringing a web game to Steam has never been easier.</p><p>Before, you had to configure tools like Electron to build executables for Windows, Mac and Linux. A process that took time to get right. You also had to spend additional time to configure your game&#8217;s Steam integration so you could access features like Steam achievements.</p><p>However, now there is a convenient tool called GemShell that comes with a one-click export feature to build for Windows/Mac/Linux all at once on the same machine and an easy integration with Steam. I have partnered with its developer, to offer you a 15% discount if you purchase the tool using the following link : <a href="https://l0om.itch.io/jslegend">https://l0om.itch.io/jslegend</a>.</p><p>To be completely transparent, if you end up purchasing GemShell using the link above, I&#8217;ll get a cut of that sale.</p><p>That said, you can try the lite version for free first to see if this tool suits your needs.</p><h1>You Get To Build Skills Useful Outside of Game Development</h1><p>If you invest time in learning web technologies to make web games, the skills you&#8217;ll acquire will expand what you can build beyond just games. You&#8217;ll be able to build unique projects that sits between game development and web development. </p><p>The most prominent example of this, are the kind of projects you can find on the famous <a href="https://neal.fun/">neal.fun</a> website.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s1Bm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s1Bm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 424w, https://substackcdn.com/image/fetch/$s_!s1Bm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 848w, https://substackcdn.com/image/fetch/$s_!s1Bm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 1272w, https://substackcdn.com/image/fetch/$s_!s1Bm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s1Bm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png" width="1456" height="386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e37192ec-e690-425f-b957-1e5b8903c692_2816x746.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:386,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:641442,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/185557791?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s1Bm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 424w, https://substackcdn.com/image/fetch/$s_!s1Bm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 848w, https://substackcdn.com/image/fetch/$s_!s1Bm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 1272w, https://substackcdn.com/image/fetch/$s_!s1Bm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe37192ec-e690-425f-b957-1e5b8903c692_2816x746.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>According to what I&#8217;ve found online, Neal uses the Vue.js JavaScript framework to build his unique games and interactive sites that go regularly viral online.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!W_0Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!W_0Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 424w, https://substackcdn.com/image/fetch/$s_!W_0Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 848w, https://substackcdn.com/image/fetch/$s_!W_0Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 1272w, https://substackcdn.com/image/fetch/$s_!W_0Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!W_0Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png" width="1212" height="1052" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1052,&quot;width&quot;:1212,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:318153,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/185557791?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!W_0Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 424w, https://substackcdn.com/image/fetch/$s_!W_0Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 848w, https://substackcdn.com/image/fetch/$s_!W_0Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 1272w, https://substackcdn.com/image/fetch/$s_!W_0Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7977a32f-83bc-4334-bb04-283c6befb222_1212x1052.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For example, one of Neal&#8217;s project is a website in which you must prove you&#8217;re not a robot by completing a series of CAPTCHA mini games.</p><p>Finally, nothing prevents you from using your web skills to build apps or game dev tools that can be easily shared with others.</p><h1>Conclusion</h1><p>To conclude, making games for the web is an ideal way to get into and stick with game development because of the platform&#8217;s natural constraints which encourage small finishable projects allowing the developer room for experimentation.</p><p>Since web games don&#8217;t require a download to try them, this makes it far easier to get your games in front of players even with no prior audience by sharing them on various platforms like Newgrounds, Hacker News, Reddit, etc... Therefore, you have a quick feedback loop through views, comments, ratings allowing you to remain motivated to continue your game dev journey.</p><p>Finally, successful web games can serve as validation and be expanded into a full Steam releases.</p><p>I hope you enjoyed this post, I recommend subscribing to not miss out on future releases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>In the meantime, you can read the following :</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;bc8e6017-0e95-4d55-a0b1-8a7c53d8c4e5&quot;,&quot;caption&quot;:&quot;Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes. Let&#8217;s get started!&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The KAPLAY Game Library in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-02T01:07:47.577Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169792492,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d1b2cba8-54c7-45d5-bd1b-6c702dc461f1&quot;,&quot;caption&quot;:&quot;Phaser is the most popular JavaScript/TypeScript framework for making 2D games. It&#8217;s performant, and popular games like Vampire Survivors and Pok&#233;Rogue were made with it.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Phaser Game Framework in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-01-07T15:54:55.314Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Sgvr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-phaser-game-framework-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:183141374,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;2a30fc7a-c7f0-4781-b8b1-2f2b8d19789d&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:13,&quot;comment_count&quot;:6,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;57baf0a1-19b7-4681-aaa0-b165bfc8ac5f&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Making a Small RPG&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-11-19T15:08:20.256Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!663W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8702120-a5b9-49b7-abe5-6c9b2e337e82_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/making-a-small-rpg&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:178016908,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:26,&quot;comment_count&quot;:4,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;420bf5ae-d2b7-4ac7-9cde-3be7b998bb41&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Programmer's Roller Coaster (Comic)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-12-14T20:39:14.223Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!mU4e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0da1a2e-d441-412f-bf7d-2b3038989139_2560x4096.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-programmers-roller-coaster-comic&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:181603355,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:9,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;2617e181-4f6a-486b-960d-09b8ec8c11ac&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How do Devs Make Levels Without Game Engines?&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-08T00:47:02.958Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!ICbs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-do-devs-make-levels-without-game&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:172615663,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:5,&quot;comment_count&quot;:1,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[The Phaser Game Framework in 5 Minutes]]></title><description><![CDATA[Phaser is the most popular JavaScript/TypeScript framework for making 2D games.]]></description><link>https://jslegenddev.substack.com/p/the-phaser-game-framework-in-5-minutes</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/the-phaser-game-framework-in-5-minutes</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Wed, 07 Jan 2026 15:54:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Sgvr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Sgvr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Sgvr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!Sgvr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!Sgvr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!Sgvr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Sgvr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:590042,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/183141374?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Sgvr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!Sgvr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!Sgvr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!Sgvr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff84f4677-1e96-4f61-8f54-0d0077ac81a8_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Phaser is the most popular JavaScript/TypeScript framework for making 2D games. It&#8217;s performant, and popular games like Vampire Survivors and Pok&#233;Rogue were made with it.</p><p>Because it&#8217;s a web-native framework, games made with Phaser are lightweight and generally load and run better on the web than the web exports produced by major game engines.</p><p>For that reason, if you&#8217;re looking to make 2D web games, Phaser is a great addition to your toolbelt.</p><p>In this post, I&#8217;ll explain the framework&#8217;s core concepts in around 5 minutes.</p><div><hr></div><p><em><strong>&#8212; SPONSORED SEGMENT &#8212;</strong></em></p><p>In case you want to bring your web game to desktop platforms, today&#8217;s sponsor <em><strong>GemShell</strong></em>, allows you to build executables for Windows/Mac/Linux in what amounts to a click. It also makes Steam integration easy.</p><p>For more info, visit &#128073; <a href="https://l0om.itch.io/gemshell">https://l0om.itch.io/gemshell</a></p><p><em>You have a tool/product you want featured in a sponsored segment? Contact me at jslegend@protonmail.com</em></p><div><hr></div><h1>The Phaser Config</h1><p>A Phaser project starts with defining a config to describe how the game&#8217;s canvas should be initialized.</p><pre><code>import Phaser from "phaser";

const config = {
  width: 1920,
  height: 1080,
  backgroundColor: "#000000",
  parent: "game", // id of the parent element of our canvas
};

new Phaser.Game(config);</code></pre><pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;title&gt;my-phaser-game&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;!-- canvas will be created within div with id "game" --&gt;
    &lt;div id="game"&gt;&lt;/div&gt;
    &lt;script type="module" src="/src/main.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>To make the game scale according to the window&#8217;s size, we can set the scale property in our config.</p><pre><code>import Phaser from "phaser";

const config = {
  width: 1920,
  height: 1080,
  backgroundColor: "#000000",
  scale: {
    mode: Phaser.Scale.FIT,
    autoCenter: Phaser.Scale.CENTER_BOTH,
  },
  parent: "game",
};

new Phaser.Game(config);</code></pre><p>The mode property is set to FIT, so the canvas scales while preserving its own aspect ratio. As for keeping the canvas centered on the page, the autoCenter property is used with the CENTER_BOTH value.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/webp&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95be77f5-6b4d-4df2-b4be-3ef04b87d9b8_1424x1046.webp&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/48cda8f4-4122-4c25-90ad-2c8d21adbc40_2516x1330.png&quot;}],&quot;caption&quot;:&quot;&quot;,&quot;alt&quot;:&quot;&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80ab6a7c-d8b7-4844-8373-c659e2d36a18_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><h1>Scene Creation</h1><p>Most games are composed of multiple scenes and switching between them is expected during the course of gameplay.</p><p>Since Phaser uses the object oriented paradigm, a scene is created by defining a class that inherits from the Phaser.Scene class.</p><pre><code>export default class Game extends Phaser.Scene {
  constructor() {
    super({ key: "game" });
  }

  create() {
  
  }

  update() {

  }
}</code></pre><p>To be able to reference the scene elsewhere in our code, it&#8217;s important to give it a key. For this purpose and for being able to use the methods and properties of the parent class we need to call the super constructor and pass to it the key we want to use.</p><p>The two most important methods of a Phaser scene are the <code>create</code> and <code>update</code> methods.</p><p>The first is used for, among other things, creating game objects like text and sprites and setting things like scores. It runs once, every time the scene becomes active.</p><p>The latter which runs once per frame is, for example, used to handle movement logic. </p><h2>Hooking Up a Scene to Our Game</h2><p>Once a scene is created, we still need to add it to our game. This is done in the Phaser config under a property called scenes, which expects an array.</p><pre><code>import Phaser from "phaser";
import Game from "./scenes/Game";
import GameOver from "./scenes/GameOver";

const config = {
  width: 1920,
  height: 1080,
  backgroundColor: "#000000",
  scale: {
    mode: Phaser.Scale.FIT,
    autoCenter: Phaser.Scale.CENTER_BOTH,
  },
  parent: "game",
  scene: [Game, GameOver],
};

new Phaser.Game(config);</code></pre><p>The order of scenes in this array is important. The first element will be used as the default scene of the game.</p><h2>Switching Scenes</h2><p>To switch scenes, we can call the <code>start</code> method of the scene manager.</p><pre><code><code>export default class Game extends Phaser.Scene {
  constructor() {
    super({ key: "game" });
  }

  create() {
     /* Switch over to the game over scene
      as soon as the game scene becomes active. */
     this.scene.start("gameover");
  }

  update() {

  }
}</code></code></pre><h2>Rendering Sprites</h2><p>Before we can render sprites, we need to load them. For this purpose, a Phaser scene has access to the <code>preload</code> method where asset loading logic should be placed. To load an image, we can use the image method of the loader plugin.</p><pre><code>export default class Game extends Phaser.Scene {
  constructor() {
    super({ key: "game" });
  }

  preload() {
      // first param is the key, second param is the path.
      this.load.image("player", "./player.png");
  }

  create() {
      this.player = this.add.sprite(500, 500, "player");
      this.player.speed = 0.02;
  }

  update(_, deltaTime) {
      this.player.x -= this.player.speed * deltaTime;
  }
}</code></pre><p>Then, in the <code>create</code> method, we can render a sprite by calling the <code>sprite</code> method of the Game Object Factory plugin.</p><pre><code>this.player = this.add.sprite(500, 500, "player");</code></pre><p>The first two params are for specifying the X and Y coordinates while the third param is for providing the key of the sprite to render. </p><pre><code>create() {
    this.player = this.add.sprite(500, 500, "player");
    this.player.speed = 0.02; // created a speed property
}

update(_, deltaTime) {
    this.player.x -= this.player.speed * deltaTime;
}</code></pre><p>Because we created our sprite game object in the <code>create</code> method, we don&#8217;t have access to it in our <code>update</code> method, that&#8217;s why you&#8217;ll often see the pattern of assigning a game object to an instance field so it becomes accessible to other methods of the scene.</p><p>Finally, movement logic code is placed in the <code>update</code> method which runs every frame.</p><pre><code>update(_, deltaTime) {
    // deltaTime is used to make movement logic frame rate independent.
    this.player.x -= this.player.speed * deltaTime;
}</code></pre><h2>Rendering Text</h2><p>Rendering text is similar to sprites. Rather than the using the <code>sprite</code> method, we use the <code>text</code> method.</p><pre><code>export default class Game extends Phaser.Scene {
  constructor() {
    super({ key: "game" });
  }

  create() {
    this.add.text(100, 100, "Hello World");
    this.add.text(100, 100, "Fancier Hello World", {
      fontFamily: "Arial",
      fontSize: 32,
      resolution: 4, // Makes the text sharper. Text is a bit blurry by default. 
    });
  }
}</code></pre><h1>Entity Creation</h1><p>If you want to hold data or define custom methods for a sprite game object, a better approach is to define a class that inherits from the Phaser.GameObject.Sprite class.</p><pre><code>export default class Player extends Phaser.GameObjects.Sprite {
    constructor(scene, posX, posY, speed) {
        super(scene, posX, posY);
        this.speed = speed;
        // add the game object to the scene
        scene.add.existing(this);
    }

    // custom method.
    jump() {
        // ...
    }
}</code></pre><p>Once the class is defined, we can use it in our scene&#8217;s code.</p><pre><code>export default class Game extends Phaser.Scene {
  constructor() {
    super({ key: "game" });
  }

  create() {
    // ...
    this.player = new Player(this, 500, 500, 0.02);
    this.input.keyboard.on('keydown-SPACE', () =&gt; {
        this.player.jump();
    });
  }
}</code></pre><h1>Asset Loading</h1><p>While asset loading can be done in any Phaser scene, a better approach is to create a scene dedicated to loading assets, which then switches to the main game scene once loading is complete. This can be achieved like shown below :</p><pre><code>export default class Loader extends Phaser.Scene {
  constructor() {
    super({ key: "loader" });
  }
  preload() {
    this.load.spritesheet("ring", "./graphics/ring.png", {
      frameWidth: 16,
      frameHeight: 16,
    });
    this.load.image("platforms", "./graphics/platforms.png");
    this.load.audio("jump", "./sounds/Jump.wav");
  }

  // runs once loading logic is complete.
  create() {
      this.scene.start("game");
  }
}</code></pre><h1>Animation API</h1><p>Another important aspect of any game is the ability to play animations. Usually for 2D games, we have spritesheets containing all the needed frames to animate a character in a single image.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B9sS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B9sS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 424w, https://substackcdn.com/image/fetch/$s_!B9sS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 848w, https://substackcdn.com/image/fetch/$s_!B9sS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 1272w, https://substackcdn.com/image/fetch/$s_!B9sS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B9sS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png" width="1456" height="996" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:996,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51634,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/183141374?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!B9sS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 424w, https://substackcdn.com/image/fetch/$s_!B9sS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 848w, https://substackcdn.com/image/fetch/$s_!B9sS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 1272w, https://substackcdn.com/image/fetch/$s_!B9sS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce36e582-9ad3-48ab-a5e2-cface3620b42_1666x1140.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">An example of a spritesheet</figcaption></figure></div><p>In Phaser, we first specify the dimensions of a frame in the loading logic of the spritesheet so that the framework knows how to slice the image into individual frames.</p><pre><code>preload() {
  this.load.spritesheet("dog", "./graphics/dog.png", {
    frameWidth: 32,
    frameHeight: 32,
  });
}</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aKLu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aKLu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 424w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 848w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1272w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aKLu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png" width="1456" height="900" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aKLu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 424w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 848w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1272w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Then, we can create an animation by defining its starting and ending frames.</p><pre><code>create() {
  this.anims.create({
    key: "walk",
    frames: this.anims.generateFrameNumbers("dog", {
      start: 0,
      end: 3,
    }),
    frameRate: 8,
  });
}</code></pre><p>To provide the needed frames we call the <code>generateFrameNumbers</code> method of Phaser&#8217;s animation manager.</p><p>Finally once the animation is created, it can be played by using the <code>play</code> method of the sprite game object.</p><pre><code>this.player = this.add.sprite(500, 500, "dog");
this.player.play({key: "walk"});</code></pre><p>If you want the animation to loop back indefinitely, add the repeat property and set it to -1.</p><pre><code>this.player = this.add.sprite(500, 500, "dog");
this.player.play({key: "walk", repeat: -1});</code></pre><h1>Input Handling</h1><p>A game needs to be interactive to be called a &#8220;game&#8221;. One way to handle input is by using event listeners provided by Phaser. For keyboard input, we can use <code>this.input.keyboard.on()</code> :</p><pre><code>create() {
  const jumpLogic = () =&gt; {
    // You jump logic
  };
  this.input.keyboard.on("keydown-SPACE", jumpLogic);
}</code></pre><p>And for handling mouse and touch input we can use <code>this.input.on()</code>.</p><pre><code>create() {
  const jumpLogic = () =&gt; {
    // You jump logic
  };
  this.input.keyboard.on("keydown-SPACE", jumpLogic);
  this.input.on("pointerdown", jumpLogic);
}</code></pre><h1>Sharing Data Between Scenes</h1><p>At one point, you might need to share data between scenes. For this purpose, you can use Phaser&#8217;s registry. Here is an example of its usage.</p><pre><code>export default class Game extends Phaser.Scene {
  constructor() {
    super({ key: "game" });
  }

  create() {
    this.registry.set("score", 100);
    this.scene.start("gameover");  
  }
}</code></pre><pre><code>export default class GameOver extends Phaser.Scene {
  constructor() {
    super({ key: "gameover" });
  }

  create() {
    const currentScore = this.registry.get("score");
    // display the score
    this.add.text(0, 0, currentScore);
  }
}</code></pre><h1>Playing Sound</h1><p>To play sounds (assuming you have already loaded the sound first) you can use the <code>play</code> method of the sound manager.</p><pre><code>preload() {
   this.load.audio("jump", "./sounds/Jump.wav");
}

create() {
   this.sound.play("jump", {volume: 0.5});
}</code></pre><p>You can specify the sound&#8217;s volume in the second param of that method.</p><p>If you need to be able to stop, pause or play the same sound at a later time, you can add it to the sound manager rather than playing it immediately.</p><pre><code>create() {
  this.citySound = this.sound.add("city", { volume: 0.2, loop: true });
  this.citySound.play();


  // later in your code in some game over condition.
  if (this.isGameOver) {
    this.citySound.stop();
  }
}</code></pre><p>This comes in handy when you transition from one scene to another and you have a sound that loops indefinitely. In that case, you need to stop the sound before switching over otherwise the sound will keep playing in the next scene.</p><h1>Physics, Debug Mode, Physics Bodies and Collision Logic</h1><p>By default, Phaser offers an Arcade physics system which is not meant for complex physics simulations. However, it&#8217;s well suited for most types of games.</p><p>To enable it, you can add the following to your Phaser config.</p><pre><code>const config = {
  //... omitted for clarity
  physics: {
    default: "arcade",
    arcade: {
      gravity: { y: 4000 },
      debug: false,
    },
  },
};

new Phaser.Game(config);</code></pre><p>You can add an existing game object to the physics system the same way you add one to a scene.</p><pre><code>export default class Player extends Phaser.GameObjects.Sprite {
  constructor(scene, x, y) {
    super(scene, x, y);
    scene.add.existing(this);
    scene.physics.add.existing(this); // &lt;--
    this.body.setSize(22, 26);
  }
  // ...
}</code></pre><p>This will create a physics body for that game object which is accessible with the body instance field. You can view this body as a hitbox around your sprite if you turn on the debug mode in your project&#8217;s config.</p><pre><code>const config = {
  //... omitted for clarity
  physics: {
    default: "arcade",
    arcade: {
      gravity: { y: 4000 },
      debug: true, // &lt;--
    },
  },
};

new Phaser.Game(config);</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LHug!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LHug!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 424w, https://substackcdn.com/image/fetch/$s_!LHug!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 848w, https://substackcdn.com/image/fetch/$s_!LHug!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 1272w, https://substackcdn.com/image/fetch/$s_!LHug!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LHug!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png" width="1456" height="1059" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1059,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:318662,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/183141374?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LHug!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 424w, https://substackcdn.com/image/fetch/$s_!LHug!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 848w, https://substackcdn.com/image/fetch/$s_!LHug!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 1272w, https://substackcdn.com/image/fetch/$s_!LHug!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3943c0e-f076-490e-92b0-f00e8ca0a4e3_1628x1184.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example of a Phaser game with debug set to true</figcaption></figure></div><p>To create bodies that aren&#8217;t affected by gravity, like platforms, you can create a static group and then create and add static bodies to that group. Here&#8217;s an example :</p><pre><code>// For example, in the create method of the Game scene
create() {
  const groundGroup = this.physics.add.staticGroup();
  this.ground = groundGroup.create(960, 920); // Create a single static body. Call this multiple times to create more.
  this.ground.setSize(1920, 160);
}</code></pre><p>You can also add already existing physics bodies to a group.</p><pre><code>create() {
  this.enemies = this.add.group();
  // assuming you have defined an Enemy class similarly to the one shown previously for the player.
  this.enemies.add(new Enemy(this, 100, 100))
}</code></pre><p>Now, you might be wondering what groups are useful for? They shine in collision handling logic.</p><p>Let&#8217;s assume you have multiple enemies attacking the player. To determine when a collision occurs between any enemy and the player, you can set up the following collision handler :</p><pre><code>create() {
  this.physics.add.collider(this.player, this.enemies, (_, enemy) =&gt; {
    // enemy is the specific enemy of the enemies group colliding with the player.
    console.log(enemy);
  });
}</code></pre><h1>Project Based Tutorial</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZsDW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZsDW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 424w, https://substackcdn.com/image/fetch/$s_!ZsDW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 848w, https://substackcdn.com/image/fetch/$s_!ZsDW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 1272w, https://substackcdn.com/image/fetch/$s_!ZsDW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZsDW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png" width="1456" height="801" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:801,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZsDW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 424w, https://substackcdn.com/image/fetch/$s_!ZsDW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 848w, https://substackcdn.com/image/fetch/$s_!ZsDW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 1272w, https://substackcdn.com/image/fetch/$s_!ZsDW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bbc7259-9f62-45be-b61e-671b899c50ef_2281x1255.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There are many concepts I did not have time to cover. If you want to delve further into Phaser, I have a project based course where I guide you through the process of building a Sonic themed infinite runner game. </p><p>This is a great opportunity to put in practice what you&#8217;ve learned here. If you&#8217;re interested, you can watch it here : </p><div id="youtube2-7GlxzAcs40c" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;7GlxzAcs40c&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/7GlxzAcs40c?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h1><em><strong><br></strong></em>Conclusion</h1><p>If you enjoy technical posts like this one, I recommend subscribing to not miss out on future releases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>In the meantime, you can read the following :</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;57003457-ffcb-46e0-adb4-19487bf206ec&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Making a Small RPG&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-11-19T15:08:20.256Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!663W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8702120-a5b9-49b7-abe5-6c9b2e337e82_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/making-a-small-rpg&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:178016908,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:25,&quot;comment_count&quot;:4,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;ad2f097c-0f22-400d-9af7-4c3a62295ae6&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Programmer's Roller Coaster (Comic)&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-12-14T20:39:14.223Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!mU4e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0da1a2e-d441-412f-bf7d-2b3038989139_2560x4096.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-programmers-roller-coaster-comic&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:181603355,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:9,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;6d42cbb5-c7e7-4dab-b145-2336d6656a19&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:13,&quot;comment_count&quot;:6,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;00b5aea4-dfda-4dcb-abd5-e3d026675fb4&quot;,&quot;caption&quot;:&quot;Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes. Let&#8217;s get started!&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The KAPLAY Game Library in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-02T01:07:47.577Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169792492,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><h2></h2>]]></content:encoded></item><item><title><![CDATA[5 Pixel Art Tips for Programmers]]></title><description><![CDATA[Programmers are known to not have a strong suit for art related disciplines, pixel art is no exception.]]></description><link>https://jslegenddev.substack.com/p/5-pixel-art-tips-for-programmers-3d6</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/5-pixel-art-tips-for-programmers-3d6</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Mon, 17 Nov 2025 02:29:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!iTWI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iTWI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iTWI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!iTWI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!iTWI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!iTWI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iTWI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:334413,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/157233783?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!iTWI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!iTWI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!iTWI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!iTWI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82344f0c-7d73-433e-99ba-f60e4881ee20_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;57dd2c2e-c868-4a7b-a88e-c1676ced286b&quot;,&quot;duration&quot;:null}"></div><p>Programmers are known to not have a strong suit for art related disciplines, pixel art is no exception. In this post, I&#8217;ll share 5 tips for making good pixel art from the point of view of a programmer. This is to help other solo devs coming from a programming background to make better pixel art for their games.</p><p>Now, you might be wondering if I&#8217;m qualified to share tips related to pixel art? Below are two pixel art asset packs I made. If you like how they look, then the question is answered.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://jslegend.itch.io/kirby-like-platformer-asset-pack" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U3NK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 424w, https://substackcdn.com/image/fetch/$s_!U3NK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 848w, https://substackcdn.com/image/fetch/$s_!U3NK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 1272w, https://substackcdn.com/image/fetch/$s_!U3NK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U3NK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106375,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://jslegend.itch.io/kirby-like-platformer-asset-pack&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/157233783?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!U3NK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 424w, https://substackcdn.com/image/fetch/$s_!U3NK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 848w, https://substackcdn.com/image/fetch/$s_!U3NK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 1272w, https://substackcdn.com/image/fetch/$s_!U3NK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a6fccf-07cb-4570-a757-2ffc86268193_2940x1632.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://jslegend.itch.io/kirby-like-platformer-asset-pack">https://jslegend.itch.io/kirby-like-platformer-asset-pack</a></figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!igyi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!igyi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 424w, https://substackcdn.com/image/fetch/$s_!igyi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 848w, https://substackcdn.com/image/fetch/$s_!igyi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 1272w, https://substackcdn.com/image/fetch/$s_!igyi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!igyi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;2D Pixel Art Samurai Arena&quot;,&quot;title&quot;:&quot;2D Pixel Art Samurai Arena&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="2D Pixel Art Samurai Arena" title="2D Pixel Art Samurai Arena" srcset="https://substackcdn.com/image/fetch/$s_!igyi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 424w, https://substackcdn.com/image/fetch/$s_!igyi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 848w, https://substackcdn.com/image/fetch/$s_!igyi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 1272w, https://substackcdn.com/image/fetch/$s_!igyi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1dda42-7f38-4f3f-8ce2-7164e0555a6b_2880x1620.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://jslegend.itch.io/samurai-arena">https://jslegend.itch.io/samurai-arena</a></figcaption></figure></div><p>Here are all the asset packs I&#8217;ve made so far :</p><ul><li><p><a href="https://jslegend.itch.io/kirby-like-platformer-asset-pack">Kirby like platformer asset pack</a> (FREE)</p></li><li><p><a href="https://jslegend.itch.io/mario-like-platformer-assets">Mario-like Platformer Asset Pack</a> ($4)</p></li><li><p><a href="https://jslegend.itch.io/samurai-arena">2D Pixel Art Samurai Arena</a> ($4)</p></li></ul><p>Additionally, I&#8217;m making all the art for a small RPG I&#8217;m working on.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G8C5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G8C5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 424w, https://substackcdn.com/image/fetch/$s_!G8C5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 848w, https://substackcdn.com/image/fetch/$s_!G8C5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 1272w, https://substackcdn.com/image/fetch/$s_!G8C5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G8C5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png" width="1456" height="1451" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1451,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:305926,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/179103049?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G8C5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 424w, https://substackcdn.com/image/fetch/$s_!G8C5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 848w, https://substackcdn.com/image/fetch/$s_!G8C5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 1272w, https://substackcdn.com/image/fetch/$s_!G8C5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db1050b-1f3e-447c-9a2c-99f18bd79c51_1756x1750.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;41e56c40-b939-48b7-9861-f26fd2c02e54&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Making a Small RPG&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-11-19T15:08:20.256Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!663W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8702120-a5b9-49b7-abe5-6c9b2e337e82_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/making-a-small-rpg&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:178016908,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:9,&quot;comment_count&quot;:3,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h1>Tip #1 : Nurture Good Taste in Pixel Art</h1><p>How do you know if a sprite looks good or not? You might have assumed that you just need to look at it and your brain determines whenever it&#8217;s appealing or not. Maybe the colors clash or the pixels are not placed symmetrically, etc&#8230; all things that you should detect naturally.</p><p>However, it&#8217;s not that simple, an art piece can go against commonly held rules and still look good. It can look good to one person but not to another.</p><p>While it&#8217;s true that whether a sprite or any piece of art looks good is subjective to some extent, it&#8217;s undeniable that some of them are widely considered more appealing.</p><p>This subjective nature of art is what a lot of programmers struggle with. You might be used to having a compiler compile and run your code. If your code successfully runs then you know that at least your code works.</p><p>With art however, you need to acquire good taste that will effectively act as your own internal compiler when doing pixel art. It will guide you during your art process and allow you to realize when things don&#8217;t work (ex: Color clashes, etc&#8230;).</p><p>The question now becomes, how do you develop good taste in pixel art? The simplest way is to look at well regarded pixel art online. It can come from games where the art is praised or from pixel artists online. When observing each piece, try to notice the details. If you do this enough, you&#8217;ll build an internal understanding of what makes good pixel art.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gsZk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gsZk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 424w, https://substackcdn.com/image/fetch/$s_!gsZk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 848w, https://substackcdn.com/image/fetch/$s_!gsZk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 1272w, https://substackcdn.com/image/fetch/$s_!gsZk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gsZk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png" width="963" height="867" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:867,&quot;width&quot;:963,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Image&quot;,&quot;title&quot;:&quot;Image&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Image" title="Image" srcset="https://substackcdn.com/image/fetch/$s_!gsZk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 424w, https://substackcdn.com/image/fetch/$s_!gsZk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 848w, https://substackcdn.com/image/fetch/$s_!gsZk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 1272w, https://substackcdn.com/image/fetch/$s_!gsZk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c1550a6-10b2-474b-94c7-2909a1e5314a_963x867.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://x.com/nekocrocodile/status/1894101890406776904">https://x.com/nekocrocodile/status/1894101890406776904</a></figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5Wnr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5Wnr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 424w, https://substackcdn.com/image/fetch/$s_!5Wnr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 848w, https://substackcdn.com/image/fetch/$s_!5Wnr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 1272w, https://substackcdn.com/image/fetch/$s_!5Wnr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5Wnr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png" width="1440" height="432" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:432,&quot;width&quot;:1440,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Image&quot;,&quot;title&quot;:&quot;Image&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Image" title="Image" srcset="https://substackcdn.com/image/fetch/$s_!5Wnr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 424w, https://substackcdn.com/image/fetch/$s_!5Wnr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 848w, https://substackcdn.com/image/fetch/$s_!5Wnr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 1272w, https://substackcdn.com/image/fetch/$s_!5Wnr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4ff6530-80a1-424b-83d1-3e61bd50d28a_1440x432.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://x.com/ChrisPariano/status/1886151340541227153">https://x.com/ChrisPariano/status/1886151340541227153</a></figcaption></figure></div><h1>Tip #2 : Don&#8217;t Draw! Negotiate Your Pixels Instead</h1><p>If you come to pixel art with the mindset that you&#8217;re going to draw what you want like you would on pen and paper, you&#8217;re going to get frustrated. Pixel art at low resolutions is all about negotiating with your canvas.</p><p>It&#8217;s basically a puzzle game. You need to figure out where to place your pixels to represent what you want while still compromising on certain details.</p><p>The sooner you view pixel art like this, the less you&#8217;re going to struggle drawing the sprites you need for your games. This is because you&#8217;re now more flexible and ready to adapt when representing certain things that are challenging at low resolutions.</p><h1>Tip #3 : Reference Other Pixel Art as Much as Possible</h1><p>You can either do the hard work of figuring out how to represent an object or character in lower resolutions or you can go look at the million of available game sprites that already solved what you&#8217;re trying to represent.</p><p>That&#8217;s why I recommend using references extensively.</p><p>For example, if you want to make a top-down action adventure game a la Zelda there is no shame in looking up link&#8217;s sprites to see how he&#8217;s represented or how his sword attack animations are made. </p><p>In case you didn&#8217;t know, the most comprehensive website for looking up sprites from various retro games is <a href="https://www.spriters-resource.com/">The Spriters Resources</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SVfr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SVfr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 424w, https://substackcdn.com/image/fetch/$s_!SVfr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 848w, https://substackcdn.com/image/fetch/$s_!SVfr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 1272w, https://substackcdn.com/image/fetch/$s_!SVfr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SVfr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png" width="1456" height="779" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:779,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:818956,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/157233783?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!SVfr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 424w, https://substackcdn.com/image/fetch/$s_!SVfr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 848w, https://substackcdn.com/image/fetch/$s_!SVfr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 1272w, https://substackcdn.com/image/fetch/$s_!SVfr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063d1ad9-7c76-435a-8820-ce5c7be846a4_2826x1512.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Tip #4 : Stick to a Limited Color Palette but Expand It if Needed</h1><p>Sticking to a limited color palette is very important in pixel art due to the low amount of space available. Every color you choose will have a big impact on the appeal of the final sprite.</p><p>If you don&#8217;t want to deal with color theory, you can always pick an existing color palette. There is this website called <a href="https://lospec.com/palette-list">Lospec</a> with plenty of color palettes you can pick from. On this website, you can often see pixel art examples using the listed color palette. This will help you determine if a given palette will likely help you achieve your desired aesthetic.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CGD7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CGD7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 424w, https://substackcdn.com/image/fetch/$s_!CGD7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 848w, https://substackcdn.com/image/fetch/$s_!CGD7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 1272w, https://substackcdn.com/image/fetch/$s_!CGD7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CGD7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png" width="1456" height="936" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:936,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:249537,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/157233783?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!CGD7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 424w, https://substackcdn.com/image/fetch/$s_!CGD7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 848w, https://substackcdn.com/image/fetch/$s_!CGD7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 1272w, https://substackcdn.com/image/fetch/$s_!CGD7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bcad05-86c9-4977-ac8c-76151eab3869_2174x1398.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Another option consists in finding a pixel art piece you like online and picking the same colors to make your palette.</p><p>Finally, you might feel too constrained working with a limited amount of colors. Sometimes, the colors you have in your palette are not enough. In that case, I recommend adding colors to it. </p><p>In fact, it&#8217;s much easier to expand a given color palette one color at a time then to not limit yourself to one by choosing colors on the fly. When needing a new color, you can more easily compare your new color choice with an existing palette and see if it fits or change your choice otherwise.</p><h1>Tip #5 : Constraints Breeds Creativity and Hides Your Flaws</h1><p>I often hear the phrase &#8220;Constraints breeds creativity&#8221; and I agree because when constrained you often end up finding creative solutions. However, another lesser known benefit of constraints is that they allow you to hide your flaws.</p><p>The less constraints you have, the more ambitious you get, the higher the likelihood of going out of your comfort zone and finding yourself in an area where you do not have enough skill to pass the &#8220;professional&#8221; quality bar.</p><p>For example, if you&#8217;re someone with very limited coloring skills. I would recommend sticking to a predefined color palette of max 4 colors. This way, you can more easily reach a &#8220;professional&#8221; looking result compared to if you had to pick your own colors.</p><p><em><strong>Well chosen constraints allows you to put forward your strengths while hiding your flaws</strong></em><strong>. </strong>Another way to put it would be that constraints reduces the likelihood of shooting yourself in the foot.</p><h1>Conclusion</h1><p>While I hope these tips will help you make better pixel art, it&#8217;s no secret that practice remains a big part of what you need to do to achieve good results.</p><p>If you&#8217;re interested in reading more posts like this. I recommend subscribing to my Substack as to not miss out on future releases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>In the meantime, you can check out my previous posts.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;924cbbda-8d89-43ed-94f1-c1f3015f9089&quot;,&quot;caption&quot;:&quot;In a previous post, I mentioned having strange performance issues regarding a tiny RPG I was secretly working on.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;What Caused Performance Issues in My Small RPG&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-11-10T19:56:18.178Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Jgsb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa12d7eda-0c44-4c5f-98dd-b0aa820f9e04_1746x1738.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/what-caused-performance-issues-in&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:178384446,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;7a60e87a-8340-42d6-8db3-1909f3cccc00&quot;,&quot;caption&quot;:&quot;The story of how I started game development is quite unusual, which led me to not using game engines and allowed me to get familiar with alternative tooling.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How do Devs Make Levels Without Game Engines?&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-08T00:47:02.958Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!ICbs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-do-devs-make-levels-without-game&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:172615663,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:5,&quot;comment_count&quot;:1,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;9f13b87f-3702-4394-b34e-289ffffd6f3c&quot;,&quot;caption&quot;:&quot;I recently discovered that you could make PS2 games in JavaScript. I&#8217;m not even kidding, it&#8217;s actually possible. I was working on a project and had my phone near my desk when I received a notification. Upon further inspection, it came from itch.io which was a platform where I usually published most of my web games.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;You Can Now Make PS2 Games in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-30T14:37:05.896Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:173484675,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:4,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[You Can Now Make PS2 Games in JavaScript]]></title><description><![CDATA[I recently discovered that you could make PS2 games in JavaScript.]]></description><link>https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Tue, 30 Sep 2025 14:37:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HWto!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HWto!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!HWto!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!HWto!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!HWto!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HWto!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:938380,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/173484675?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HWto!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!HWto!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!HWto!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!HWto!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2b3cdb-3a39-465f-900e-5dca9eabf54d_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;15a492eb-67e9-495d-88d1-b30d1740df9b&quot;,&quot;duration&quot;:null}"></div><p>I recently discovered that you could make PS2 games in JavaScript. I&#8217;m not even kidding, it&#8217;s actually possible. I was working on a project and had my phone near my desk when I received a notification. Upon further inspection, it came from itch.io which was a platform where I usually published most of my web games.</p><p>Under my relatively popular <a href="https://jslegend.itch.io/sonic-ring-run">Sonic infinite runner game</a> which was made in JavaScript and developed a year ago, I received a comment from someone with the username Dev Will which claimed they had made a PS2 version of my game and provided the <a href="https://github.com/DevWill-hub/Sonic-Infinite-Runner-PS2">GitHub repo</a> of the source code.</p><p>At first, I thought that it was cool that someone took the time to remake my game for an old console that had a reputation to be hard to develop for and probably required them to write a lot of C or C++.</p><p>Out of curiosity, I opened up the GitHub repo and was astonished to see that the project was not using even a bit of C++ or C but was entirely in JavaScript!</p><p>If making PS2 games were easier than I thought since I could use a higher level language like JavaScript, I could probably try making one in a reasonable amount of time and play it on a retro handled or an actual PS2. How cool would that be?</p><p>This is where I knew I had to drop everything I was doing to investigate how this was possible.</p><h1>The AthenaEnv Project</h1><p>Since the dev behind the project was Portuguese speaking (I assume they were either from Brazil or Portugal), they wrote the Readme of the repo in Portuguese which was a language I did not understand. </p><p>Fortunately, I was still able to decipher most of what was written because I had done 3 years of Spanish in school and spoke French natively. Since Portuguese is a romance language like Spanish and French, I was fortunately not totally lost.</p><p>Anyway, The readme said that the engine used to make the PS2 version of my game was called AthenaEnv with a conveniently placed link towards it so I could learn more.</p><p>As with the Sonic Infinite Runner PS2 project, this engine was also open source and its <a href="https://github.com/DanielSant0s/AthenaEnv">repo</a> had a very detailed readme written in English.</p><p>To summarize, Athena was not what we commonly refer to as a game engine but an environment that also offered a JavaScript API for making games and apps for the PS2. It embedded a slightly modified version of QuickJS which was a small and embeddable JavaScript engine. This explained how Athena was able to run JavaScript code on the PS2.</p><p>Therefore, Athena was the PS2 native program written in C that took your JavaScript code, passed it through the QuickJS engine to interpret it and finally, ran the relevant logic on the system.</p><p>What made it compelling was not that it just ran JS on the PS2 but that it offered an API suitable for game development. It covered :</p><ul><li><p>Rendering : Allowing you to display sprites, text, shapes, etc&#8230; on the screen and animate them using a game loop.</p></li><li><p>Asset loading : Allowing you to load images, sounds, fonts, etc...</p></li><li><p>Input handling : Allowing you to receive player input from a controller, multiple ones or even from a mouse and keyboard since the PS2 supported these input methods.</p></li><li><p>File handling : Allowing you to write save files among other things.</p></li><li><p>Sound playback : For playing Sound.</p></li></ul><p>and the list goes on.</p><p>I noticed however, that the level of abstraction offered by the API was similar to something like p5.js, the HTML canvas API or Raylib. That meant that you&#8217;d still needed to implement collision detection, scene management, etc&#8230; yourself.</p><p>Now, that I got familiar with Athena, I wanted to try to run the Sonic infinite runner &#8220;port&#8221; on an emulator. According to the project&#8217;s Readme. I needed to install PCSX2 which is the most popular emulator for the PS2. Then, go into the settings and under the emulation tab, check the box &#8220;Enable host filesystem&#8221;.</p><p>Once this was done, I would need to open an athena.elf file and the game would start.</p><p>After installing and configuring the emulator, I was ready to run the game. However, there was a problem. I could not find the athena.elf file in the repo. It was nowhere to be found.</p><p>This is where I remembered to look at the &#8220;releases&#8221; section of the repo because a lot of open source projects put executables there, especially if it&#8217;s a mobile or desktop app project.</p><p>As expected, the zip attached in that section contained the athena.elf file but not only. It also contained an assets folder, a main.js file, an athena.ini file and src folder containing the rest of the game&#8217;s code.</p><p>The athena.ini file allowed you to configure the entry point of the project. Here, the entry point was set to main.js which explained how Athena would know what JavaScript to run. You could also configure if you wanted to show Athena&#8217;s logo before your game started by setting the boot_logo property to true.</p><pre><code>boot_logo = true
dark_mode = true
default_script = &#8220;main.js&#8221;

audsrv = true</code></pre><p>It now became evident why we needed to check the &#8220;Enable host filesystem&#8221; check box earlier. This was so that the emulator could allow Athena to access the assets folder and the source code that were essential for our game.</p><p>Anyway, I opened the athena.elf file in PCSX2 and surprisingly, the game actually ran with no issues. It was amazing to see that a game I wrote for the web was ported to the PS2 and I was there able to play it with a controller.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6ls2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6ls2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 424w, https://substackcdn.com/image/fetch/$s_!6ls2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 848w, https://substackcdn.com/image/fetch/$s_!6ls2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 1272w, https://substackcdn.com/image/fetch/$s_!6ls2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6ls2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png" width="1456" height="743" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:743,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1708312,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/173484675?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6ls2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 424w, https://substackcdn.com/image/fetch/$s_!6ls2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 848w, https://substackcdn.com/image/fetch/$s_!6ls2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 1272w, https://substackcdn.com/image/fetch/$s_!6ls2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b6b6320-8b56-4c1b-a34d-66c900dd9c2f_1912x976.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now, the game looked a bit blurry which was expected since this was supposed to emulate a PS2 which had a small resolution. Fortunately, I was able to make things more comfortable by upping the resolution in the graphics settings of the emulator.</p><p>The dev process also seemed quite straightforward. You would only need to open the folder containing all the relevant files (athena.elf, main.js, etc&#8230;) in a code editor like VSCode and open athena.elf in the emulator. Now, you could make changes to your JS code and once you were ready to test, you would go under the PCSX2 system tab and click on reset. This would restart the emulator and you could see the latest changes. While not as seamless as in web development with hot reloading, it still was a relatively fast iteration cycle.</p><p>It&#8217;s at that moment, that I knew had to make a post about it and share this awesome project with you. However, I still felt uneasy about one thing.</p><p>Nowadays, people download PS2 games as .iso files. For most games, you only need one .iso file that you then open in your emulator. Less technical people can therefore more easily enjoy these older titles.</p><p>However, to run the Sonic infinite runner game &#8220;port&#8221;, I needed to not only check a box in the settings but also needed the entire project&#8217;s folder containing the Athena executable and the source code.</p><p>I wondered if instead, there was a way to distribute the game as a single .iso file. This is were I simply went back to the itch.io comment section and asked if it was possible.</p><p>After a thorough back and forth that continued on Discord, the process to convert my files into a single iso, I could distribute, was now clear.</p><h1>Making an .iso File</h1><p>To make an iso you needed the following files :</p><ul><li><p>athena.elf : Which is the Athena executable.</p></li><li><p>athena.ini : For configuring the project&#8217;s entry point.</p></li><li><p>A JS file acting as the entry point of the codebase.</p></li><li><p>The rest of your source code if your code is more than one file, oftentimes it&#8217;s in a folder called src.</p></li><li><p>Two files one named ATHA_000.01 and the other SYSTEM.CNF needed to make the iso bootable.</p></li></ul><p><em>As an aside, in case you want to also get into JavaScript PS2 game development, you can check <a href="https://github.com/JSLegendDev/Athena-PS2-Template">this template I made containing all of the files needed</a>.</em></p><p>Once you had all the files, you had to make a zip archive containing them all. One issue I had, was that if I created a zip out of the folder containing the files, the resulting .iso would not work. However, if I selected the files one by one and then created the zip, I would experience no issues. This is something to keep in mind.</p><p>Now, the only step left was to convert the zip into an iso. As I was using a Mac, the only reliable way I&#8217;ve found, was to use the website <a href="https://mconverter.eu">mconverter.eu</a> and let them do the conversion.</p><p>However, the issue with this website is that you&#8217;re limited in the number of conversions you can do per day before they ask you to pay. Additionally, if your zip archive is above a certain size, you&#8217;ll also have to watch an ad before you can do the conversion.</p><p><em>If you end up finding a better way using either a CLI tool, a downloadable app or some other website, feel free to share it in the comment section.</em></p><p>Once you had the iso, you could open it up in the emulator like you would do with other PS2 games. You also didn&#8217;t need to check the &#8220;Enable host filesystem&#8221; option anymore since all the relevant files needed were included in the iso.</p><p>If the game booted correctly, then you now had a single file you could distribute which was very convenient.</p><h1>Hello World Example</h1><p>It was now time to get my feet wet. Before attempting anything too complicated, my goal was to create a simple &#8220;Hello World&#8221; example where I would :</p><ul><li><p>Load some assets (In my case a font and an image).</p></li><li><p>Set up a game loop that would run every frame.</p></li><li><p>Animate a sprite using that game loop.</p></li><li><p>Render text.</p></li><li><p>Handle player input so I could move a sprite around.</p></li></ul><h2>Setup</h2><p>Before I could achieve any of these sub-goals, in main.js, I first defined a few constants that I would end up needing.</p><pre><code>const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Screen.getMode();
const SCALE = 2;
const SPEED = 3;
const FRAME_WIDTH = 32;
const FRAME_HEIGHT = 44;</code></pre><p>This is where I learned that you could get the screen&#8217;s width and height by first using the Screen module available globally like all Athena provided modules (Meaning that no import statements were needed) and then calling the getMode method.</p><p>Then, to have a stable frame rate and accurate FPS counting, I needed to call the methods setVSync() and setFrameCounter()</p><pre><code>Screen.setVSync(true); // makes framerate stable
Screen.setFrameCounter(true); // toggles frame counting and FPS collecting.</code></pre><h2>Loading Assets</h2><p>With the setup completed, I wanted to load the font I used in my Sonic game and a Spritesheet of Sonic so that I could later animate it. I could achieve the following by creating an instance of the Font and Image classes offered by Athena.</p><pre><code>const maniaFont = new Font("./assets/mania.ttf");
const sprite = new Image("./assets/sonic.png");</code></pre><h2>Setting Up Player Input</h2><p>While I planned on handling player input later, I still needed a way to get the player&#8217;s controller so that my code could know when a given button was pressed. This was made possible by using Athena&#8217;s Pads module.</p><pre><code>// Get the first player controller
// First player -&gt; 0, Second player -&gt; 1
const pad = Pads.get(0);</code></pre><h2>Setting Up Animation Logic</h2><p>Before I could create a game loop, I needed to first write the setup code required to animate my spritesheet. Since all the frames where contained within a single image, I had to find a way to tell Athena what part of the image was to be rendered.</p><p>To achieve this, I first spent some time to get familiar with the shape of the sprite object created earlier.</p><pre><code>const sprite = new Image("./assets/sonic.png");</code></pre><p>It turned out that we could set the width and the height of the sprite by modifying the properties of the object with the same names.</p><pre><code>// for example
sprite.width = 30;
sprite.height = 40;</code></pre><p>It also turned out that you could tell Athena what portion of the image to draw by setting the startx, endx, starty, endy properties.</p><pre><code>sprite.startx = 0;
sprite.endx = 32;
sprite.starty = 0;
sprite.endy = 44;</code></pre><p>For example, if you had the following values : startx = 0, endx = 32, starty = 0 and endy = 44 you would get the first frame rendered. This is because in the spritesheet, every frame has a width of 32 and a height of 44. Also, the origin (0,0) corresponds to the top-left corner of the spritesheet.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sP4y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sP4y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 424w, https://substackcdn.com/image/fetch/$s_!sP4y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 848w, https://substackcdn.com/image/fetch/$s_!sP4y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 1272w, https://substackcdn.com/image/fetch/$s_!sP4y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sP4y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png" width="1456" height="570" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:570,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1152300,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/173484675?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sP4y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 424w, https://substackcdn.com/image/fetch/$s_!sP4y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 848w, https://substackcdn.com/image/fetch/$s_!sP4y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 1272w, https://substackcdn.com/image/fetch/$s_!sP4y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2b0e183-3d98-4b71-9724-50afd6a3208e_3088x1208.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now that I knew how to display a single frame within a wider image, I used the following logic to setup Sonic&#8217;s run animation.</p><pre><code>const spritePos = { x: SCREEN_WIDTH / 2, y: SCREEN_HEIGHT / 2 };
sprite.width = FRAME_WIDTH * SCALE;
sprite.height = FRAME_HEIGHT * SCALE;
// describes where each frame is located within the sprite.
const runAnimFrames = [
  { startx: 0, endx: 32, starty: 0, endy: 44 },
  { startx: 32, endx: 64, starty: 0, endy: 44 },
  { startx: 64, endx: 96, starty: 0, endy: 44 },
  { startx: 96, endx: 128, starty: 0, endy: 44 },
  { startx: 128, endx: 160, starty: 0, endy: 44 },
  { startx: 160, endx: 192, starty: 0, endy: 44 },
  { startx: 192, endx: 224, starty: 0, endy: 44 },
  { startx: 224, endx: 256, starty: 0, endy: 44 },
];
let frameIndex = 0;
const frameDuration = 30;
const timer = new Timer();</code></pre><p>I first created an object called spritePos to set the position of the sprite on the screen. This was needed to be able to move it around when the player would press directional buttons on the D-pad. More on that later.</p><p>Then I would set the sprite&#8217;s width and height to correspond to the width and height of a single frame which was 32x44 pixels. Since I wanted the sprite to appear big enough, I multiplied the width and height by a value defined by the SCALE constant we set earlier in our code.</p><p>The next step consisted in creating an array called runAnimFrames which would describe each frame of Sonic&#8217;s run animation using an object with the startx, endx, starty and endy properties. We then had a frameIndex variable which would determine the current frame to display. The frameDuration constant would be used to set how long in miliseconds to display each frame. The lower the number the higher the frame rate of the animation because we would flip through all the frames faster. </p><p>Finally, I initialized a timer coming from a custom Timer class that I added in my src folder and imported here. The full code is available in the template mentioned earlier.</p><p>The timer would end up being crucial to know when it was time to move on to displaying another frame.</p><h2>Creating our Game Loop</h2><p>Now that we had our animation logic setup done, it was time to render the animation. For this purpose, I needed a game loop that runs every frame. In Athena, we could achieve this by calling the display method available under the Screen module.</p><pre><code>Screen.display(() =&gt; {
   if (timer.get() &gt; frameDuration) {
      if (frameIndex &lt; runAnimFrames.length - 1) {
          frameIndex++;
          timer.reset();
      } else {
          frameIndex = 0;
      }
   }

   sprite.startx = runAnimFrames[frameIndex].startx;
   sprite.endx = runAnimFrames[frameIndex].endx;
   sprite.starty = runAnimFrames[frameIndex].starty;
   sprite.endy = runAnimFrames[frameIndex].endy;
   sprite.draw(spritePos.x, spritePos.y);
});</code></pre><p>In an if statement we would check if the timer had exceeded the time allocated to displaying the current frame. If it was the case we would move on to the next frame by incrementing the frameIndex as long as it was within the bounds of the runAnimFrames array, otherwise, we would set it back to 0 to display the first frame. This was to achieve a looping animation.</p><p>Then, on every iteration of the game loop we would set the sprite&#8217;s startx, endx, starty, endy properties to correspond to the ones of the current frame. Finally, to render the sprite, we needed to call its draw method and pass to it the coordinates where you wanted to display it on the screen.</p><h2>Handling Player Input</h2><p>Now that I had a game loop, I could finally handle user input by making sure that the sprite would move in different directions depending on which button was pressed. This could be easily achieved with a few if statements.</p><pre><code>Screen.display(() =&gt; {
   pad.update(); // necessary to get what buttons are currently being pressed

   if (pad.pressed(Pads.RIGHT)) {
       spritePos.x = spritePos.x + SPEED;
   }

   if (pad.pressed(Pads.LEFT)) {
       spritePos.x = spritePos.x - SPEED;
   }

   if (pad.pressed(Pads.UP)) {
       spritePos.y = spritePos.y - SPEED;
   }

   if (pad.pressed(Pads.DOWN)) {
      spritePos.y = spritePos
   }

   // rest of the code omitted for clarity
});</code></pre><p>You might be wondering where is deltaTime? For those unfamiliar, deltaTime is a value representing the time elapsed between the current frame and the previous frame in a game. It&#8217;s often used to make the movement of objects, frame rate independent. Meaning that if your game runs at a lower or higher frame rate, an object, like a character, will still move at the same rate. To achieve frame rate independence, you would usually multiply your movement code by deltaTime.</p><p>The reason it was absent here, is because when creating a game loop using the display method, this matter is taken care of under the hood.</p><h2>Mirroring a Sprite</h2><p>Now that I could move Sonic around, I still needed him to face the correct direction because at this point, he would look right even If I moved him to the left. To implement this, I decided to go with a common technique in pixel art based games, which consisted in mirroring (or flipping) the sprite.</p><p>To achieve this in Athena, you simply needed to provide a negative width or height to the sprite depending on what axis you wanted the mirroring to take effect on. For flipping a sprite horizontally, providing a negative width was enough.</p><p>However, an issue arose! If you flipped the sprite, it would not flip in place since it would flip according to the sprite&#8217;s origin which was its top-left corner.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vy5r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vy5r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 424w, https://substackcdn.com/image/fetch/$s_!vy5r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 848w, https://substackcdn.com/image/fetch/$s_!vy5r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 1272w, https://substackcdn.com/image/fetch/$s_!vy5r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vy5r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png" width="1456" height="746" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:746,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:533754,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/173484675?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vy5r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 424w, https://substackcdn.com/image/fetch/$s_!vy5r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 848w, https://substackcdn.com/image/fetch/$s_!vy5r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 1272w, https://substackcdn.com/image/fetch/$s_!vy5r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F218436f0-209c-40bf-b989-ad5cc3a32813_2320x1188.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This meant that it would move the sprite to the left after mirroring. To fix this issue, you only needed to subtract an offset to the x coordinate of the flipped sprite that corresponded to its width.</p><p>Now that the issue was solved, I created variable called spriteIsFlippedX to know when to flip or unflip the sprite. The logic can be see below :</p><pre><code>// omitted previous code for clarity
const offset = FRAME_WIDTH * SCALE;
let spriteIsFlippedX = false;

Screen.display(() =&gt; {

  pad.update();

  if (pad.pressed(Pads.RIGHT)) {
    // makes sur to flip back the sprite
    if (spriteIsFlippedX) {
      sprite.width = Math.abs(sprite.width);
      spriteIsFlippedX = false;
      spritePos.x -= offset;
    }

    spritePos.x = spritePos.x + SPEED;
  }

  if (pad.pressed(Pads.LEFT)) {
    if (!spriteIsFlippedX) {
      sprite.width = -Math.abs(sprite.width);
      spriteIsFlippedX = true;
      spritePos.x += offset;
    }

    spritePos.x = spritePos.x - SPEED;
  }

  if (pad.pressed(Pads.UP)) {
    spritePos.y = spritePos.y - SPEED;
  }

  if (pad.pressed(Pads.DOWN)) {
    spritePos.y = spritePos.y + SPEED;
  }

  // ... code omitted for clarity
});</code></pre><p>Now, when you moved sonic to the left, he would face left and face right when moved to the right.</p><h2>Rendering Text</h2><p>There was still one thing I wanted to try out before wrapping up my Hello World example and that was text rendering. The first thing I wanted to render onto the screen was an FPS counter. It turned out that the FPS counter in the PCSX2 emulator is not accurate, however, Athena provides the getFPS() method available on the Screen module to accurately determine the frame rate.</p><p>To display some text, you needed to first create a font object using the Font constructor. It would take either a path to a font that can be in a .ttf format or the string &#8220;default&#8221; if you wanted to use the default font available on the system.</p><p>Once created, the font object had a print method that you could use within the game loop to tell the PS2 what to render and where on the screen.</p><pre><code>const font = new Font("default");
Screen.display(() =&gt; {
    // Here getFPS() will provide an updated FPS count every 10ms.
    font.print(10,10, Math.round(Screen.getFPS(10)));
});</code></pre><pre><code>const maniaFont = new Font("./assets/mania.ttf");
Screen.display(() =&gt; {
    font.print(10,10, "Hello World!");
});</code></pre><p>Finally, my Hello World example was finished.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iHMb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iHMb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 424w, https://substackcdn.com/image/fetch/$s_!iHMb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 848w, https://substackcdn.com/image/fetch/$s_!iHMb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 1272w, https://substackcdn.com/image/fetch/$s_!iHMb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iHMb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png" width="1456" height="743" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:743,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:470324,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/173484675?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iHMb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 424w, https://substackcdn.com/image/fetch/$s_!iHMb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 848w, https://substackcdn.com/image/fetch/$s_!iHMb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 1272w, https://substackcdn.com/image/fetch/$s_!iHMb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73c41646-137d-4ea2-b0a7-c0328f119829_1912x976.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now that you&#8217;ve been introduced to Athena, you might be tempted to try it out for yourself. In that case, I really recommend looking at the Sonic infinite runner Athena port&#8217;s code as you&#8217;ll learn a lot about concepts that I did not have time to cover here.</p><p>Link to the repo here : <a href="https://github.com/DevWill-hub/Sonic-Infinite-Runner-PS2">https://github.com/DevWill-hub/Sonic-Infinite-Runner-PS2</a></p><p>Link to my Athena template : <a href="https://github.com/JSLegendDev/Athena-PS2-Template">https://github.com/JSLegendDev/Athena-PS2-Template</a></p><p>Link to the Athena project : <a href="https://github.com/DanielSant0s/AthenaEnv">https://github.com/DanielSant0s/AthenaEnv</a></p><p>Additionally, I recommend joining the official Athena discord where you&#8217;ll be more likely to receive help when stuck. You can join here : <a href="https://discord.gg/cZUH5U93US">https://discord.gg/cZUH5U93US</a></p><h1>How About 3D?</h1><p>Before wrapping up this post, you might have found strange that nothing was mentioned about 3D considering that the PS2 was mostly known for its 3D games.</p><p>This is for 2 reasons. First, I&#8217;m a novice in terms of 3D game develoment, I have never done it before. Second, to my understanding, Athena has both 2D and 3D capabilities but version 4 which has more of a 3D focus is currently in development. I thought it would have been preferable to wait until v4 was stable before diving into PS2 3D gamedev in JavaScript.</p><p>However, there are a few 3D demos you can check if you&#8217;re interested. </p><p>Links down below.</p><ul><li><p><a href="https://github.com/ps2devnoob/3D-Robot-DEMO-PS2">https://github.com/ps2devnoob/3D-Robot-DEMO-PS2</a></p></li><li><p><a href="https://github.com/ps2devnoob/BurnTrack-PS2-DEMO">https://github.com/ps2devnoob/BurnTrack-PS2-DEMO</a></p></li><li><p><a href="https://github.com/ps2devnoob/AthenaENV-PS2-Samples">https://github.com/ps2devnoob/AthenaENV-PS2-Samples</a></p></li></ul><h1>Conclusion</h1><p>To conclude, Athena is a cool project allowing you to make real PS2 games in JavaScript. If you learned something new and enjoy technical posts like this one, I recommend subscribing to not miss out on future releases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>In the meantime, if you feel inclined, you can read the post below.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;bb237b06-2e8e-4ef5-ae75-2e15b728daba&quot;,&quot;caption&quot;:&quot;The story of how I started game development is quite unusual, which led me to not using game engines and allowed me to get familiar with alternative tooling.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How do Devs Make Levels Without Game Engines?&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-09-08T00:47:02.958Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!ICbs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-do-devs-make-levels-without-game&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:172615663,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:2004012,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[How do Devs Make Levels Without Game Engines?]]></title><description><![CDATA[The story of how I started game development is quite unusual, which led me to not using game engines and allowed me to get familiar with alternative tooling.]]></description><link>https://jslegenddev.substack.com/p/how-do-devs-make-levels-without-game</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/how-do-devs-make-levels-without-game</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Mon, 08 Sep 2025 00:47:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ICbs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ICbs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ICbs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!ICbs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!ICbs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!ICbs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ICbs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:171624,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ICbs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!ICbs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!ICbs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!ICbs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ae0fd1-4ffd-4728-9729-b2cbd30db9cb_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;5a6622c5-c6c8-4736-bdb0-b48e02f2f248&quot;,&quot;duration&quot;:null}"></div><p>The story of how I started game development is quite unusual, which led me to not using game engines and allowed me to get familiar with alternative tooling. </p><p>When I reached the age to go to university, I chose computer science as my major not to learn to make games but with the aim of studying AI. However, by the time I ended up graduating, my interest for AI had completely vanished. The math needed for it did not interest me.</p><p>Instead, I used to spend my time building web related projects which I then put on my resume and was able to land a job as a software developer. In that job, we used a JavaScript and TypeScript based stack.</p><p>It was during that time that I came across a nice little library called Kaboom.js. It would end up later as KAPLAY. This library would allow you to make 2D games quickly using either JavaScript or TypeScript. As someone with no real experience in game dev, this was right up my alley as I already had the prerequisites to learn this library quickly.</p><p>Considering that this was a library and not a game engine, the development setup was quite familiar with what I was used to in my day job (at the very least the frontend portion of it). You would write your code in an editor and look at the preview in your browser.</p><p>My first few games were simple, they would take place in a single scene and I would hard code the positions of game objects into my code.</p><p>As my projects got bigger, I started to use a feature provided by Kaboom.js allowing me to describe the layout of a level using an array of strings. For each character, you could tie a specific game object that would be spawned according to where the character was located in the string array.</p><pre><code>// An example
const myLevel = add([
    level(
        [
            "         ",
            "         ",
            "  @  ^ $$",
            "  =======",
        ],
        {
            tileWidth: 64,
            tileHeight: 64,
            tiles: {
                "@": () =&gt; [
                    sprite("bean"),
                ],
                "=": () =&gt; [
                    sprite("grass"),
                ],
                $: () =&gt; [
                    sprite("coin")
                ],
                "^": () =&gt; [
                  sprite("spike")
                ],
            },
        },
    ),
]);</code></pre><p>While this was much better than hard coding coordinates, it became tedious when I wanted to make maps with multiple layers since each layer was represented with its own array of strings. Therefore, I wouldn&#8217;t know how the map would really look like unless I ran the code. It also felt annoying to reposition objects by adding spaces in a string or removing them.</p><p>Dissatisfied with the string-based approach, I decided to search how others tackled the problem.</p><p>I first got familiar with how game engine users did things. For Unity, Godot or Unreal they were all provided a built-in editor where they could easily place objects around and run their games.</p><p>It dawned upon me why people preferred to use ready made game engines due to how convenient this was but I didn&#8217;t feel like spending the time to learn an engine just for this. However, if I couldn&#8217;t find a solution that was better then what I had, I would probably bite the bullet and start learning game dev from scratch with one of the major engines.</p><p>I then came across developers with an approach that seemed a bit crazy. They just decided to invest time in building their own tooling for map/level making. As a programmer, while I appreciate the exercise, I felt that this was not worth my time unless, I had really no options and with the goal of making an editor that was general enough that I could sell as a product to other developers.</p><p>This is where I wondered: Has no one made a general-purpose editor for map-making that can be used by anyone working with libraries and game frameworks rather than engines? This prompted me to do some research, I came across 3 options :</p><ul><li><p><a href="https://ogmo-editor-3.github.io/">Ogmo Editor</a></p></li><li><p><a href="https://ldtk.io/">LDTK</a></p></li><li><p><a href="https://www.mapeditor.org/">Tiled</a></p></li></ul><p>I first found out about Ogmo when I tried learning the HaxeFlixel game framework. In the official tutorial, they used Ogmo to design the tutorial game&#8217;s map. Since, I didn&#8217;t end up pursuing HaxeFlixel much further. I kind of forgot about Ogmo and moved on.</p><p>As for LDTK, it was an editor made by the developers behind the popular game Dead Cells. It looked nice, but I ended up using Tiled because it was the most popular.</p><p>According to their home page, Tiled was used to design the levels of popular indie games like Shovel Knight and Axiom Verge amongst others. However, the reason that really pushed me over to use it over other options, was the fact that someone had written a plugin to integrate maps made with Tiled with the library I was using.</p><h1>Learning Tiled</h1><p>At first, Tiled&#8217;s interface looked ugly and not very intuitive but ultimately the way it worked was relatively simple.</p><p>You first started by creating a map by setting the size of each tiles and its width and height.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NAoJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NAoJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 424w, https://substackcdn.com/image/fetch/$s_!NAoJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 848w, https://substackcdn.com/image/fetch/$s_!NAoJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 1272w, https://substackcdn.com/image/fetch/$s_!NAoJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NAoJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png" width="1456" height="730" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:730,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:479445,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NAoJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 424w, https://substackcdn.com/image/fetch/$s_!NAoJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 848w, https://substackcdn.com/image/fetch/$s_!NAoJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 1272w, https://substackcdn.com/image/fetch/$s_!NAoJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc55d181e-704b-43d5-a7ac-7c34af8a6974_2970x1490.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You then, imported your tileset and finally created a first tile layer on which you could draw using tiles from that tileset.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Um7R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Um7R!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 424w, https://substackcdn.com/image/fetch/$s_!Um7R!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 848w, https://substackcdn.com/image/fetch/$s_!Um7R!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 1272w, https://substackcdn.com/image/fetch/$s_!Um7R!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Um7R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png" width="1456" height="737" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:737,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1175323,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Um7R!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 424w, https://substackcdn.com/image/fetch/$s_!Um7R!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 848w, https://substackcdn.com/image/fetch/$s_!Um7R!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 1272w, https://substackcdn.com/image/fetch/$s_!Um7R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe57f0b25-779c-491c-a714-096dabfe39e9_2906x1470.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>An arbitrary number of layers could then be created to appear on top of the first one allowing you to design complex looking maps.</p><p>In the inspector, you could rename layers and change their order, which would determine the order in which they would be rendered.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RMrC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RMrC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 424w, https://substackcdn.com/image/fetch/$s_!RMrC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 848w, https://substackcdn.com/image/fetch/$s_!RMrC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 1272w, https://substackcdn.com/image/fetch/$s_!RMrC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RMrC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png" width="1456" height="670" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:670,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1276426,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RMrC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 424w, https://substackcdn.com/image/fetch/$s_!RMrC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 848w, https://substackcdn.com/image/fetch/$s_!RMrC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 1272w, https://substackcdn.com/image/fetch/$s_!RMrC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26300566-da4a-4e23-8631-6c7e4f094606_2434x1120.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In addition to being able to create tile layers, you could create a second type of layer called an object group. This layer type could be used to place collider shapes which you could use in your code to determine which parts of the map were walls. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ff3C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ff3C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 424w, https://substackcdn.com/image/fetch/$s_!Ff3C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 848w, https://substackcdn.com/image/fetch/$s_!Ff3C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 1272w, https://substackcdn.com/image/fetch/$s_!Ff3C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ff3C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png" width="1456" height="670" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d810500c-c013-4368-9d4c-b8956716252d_2434x1120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:670,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1085398,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ff3C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 424w, https://substackcdn.com/image/fetch/$s_!Ff3C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 848w, https://substackcdn.com/image/fetch/$s_!Ff3C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 1272w, https://substackcdn.com/image/fetch/$s_!Ff3C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd810500c-c013-4368-9d4c-b8956716252d_2434x1120.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Alternatively, you could also place pins that could be used to position players, enemies and other game objects in your map.</p><p>Finally, you would be able to export your map into various formats depending on your needs.</p><p>After having learned how to use Tiled, I had finished creating a test map and was eager to test the plugin that would allow me to render it in my game. Unfortunately, I realized that the plugin didn&#8217;t work!</p><p>I concluded that the plugin expected an older deprecated version of the library explaining why it didn&#8217;t work. Kaboom.js (now KAPLAY) also didn&#8217;t have an official way of importing maps made with Tiled and still doesn&#8217;t.</p><p>I therefore felt pretty much stuck. I tried reading the code of the plugin but didn&#8217;t understand much of it.</p><p>I was about to give up feeling frustrated of not being able to achieve my goals because the people maintaining the software I was using did not take into account my use case and why would they? They maintained the library for free. They wrote the plugin for free. It was being dependent to them that felt annoying. I did not want to wait for someone else to do the work before I could continue my projects.</p><p>I also wasn&#8217;t your average software user anymore. Software is often thought as a black box. Developers know the internals and users just use the software from the outside via an interface. </p><p>However, I had a computer science degree, I knew how to code, why should I wait and not fix my pain point myself.</p><p>That&#8217;s because jumping into a new codebase as a dev is often still intimidating. You don&#8217;t have the context that led to various decisions around the code and you need to get familiar with a lot at once which always made me wary of trying to fix things myself for open source software I was using. I really needed a good reason to justify the effort.</p><p>However, this time was different, I was motivated and didn&#8217;t even need to jump into a codebase. What I needed to do was to learn how the Tiled export file worked so I could parse it in my game code and render the map.</p><p>I was initially intimidated by the various file formats you could use as exports. However, I was relieved to discover that you could export your map as a JSON file.</p><p>For those unfamiliar, JSON is a relatively simple format composed of an object with an arbitrary amount of key value pairs where keys are called properties. It&#8217;s inspired by the way objects are created in JavaScript and that&#8217;s why it&#8217;s called JavaScript Object Notation (JSON).</p><p>It&#8217;s an incredibly common format used a lot in web development. For example, it&#8217;s often used by backends and third party APIs to send data upon being requested by  frontends or users of those APIs.</p><p>Knowing that we could export the map as a JSON file allowed me to answer a big question I had in mind. How to import a map made with Tiled in my codebase?</p><p>As with regular API requests, I simply needed to use the <code>fetch()</code> JavaScript function to fetch the file and then convert its response using the <code>.json()</code> method which allowed me to load the JSON into an actual JavaScript object I could use in my code.</p><p>With this out of the way, the next step was to figure out how the export file was structured so I could know how to render the map.</p><pre><code>// map.json
{ 
 "compressionlevel":-1,
 "height":30,
 "infinite":false,
 "layers":[...],
 "nextlayerid":6,
 "nextobjectid":71,
 "orientation":"orthogonal",
 "renderorder":"right-down",
 "tiledversion":"1.11.0",
 "tileheight":16,
 "tilesets":[
        {
         "firstgid":1,
         "source":"tileset.tsj"
        }],
 "tilewidth":16,
 "type":"map",
 "version":"1.10",
 "width":140
}</code></pre><p>At first the <code>height</code>, <code>width</code>, <code>tilewidth</code> and <code>tileheight</code> properties stood out. They would allow me to compute how many tiles per row and per columns needed to be rendered. In the end, however, only the <code>width</code> and the <code>tilewidth</code> properties where really needed since I would render the tiles from left to right, top to bottom. I could get by only knowing how many tiles were required per row. When I ended up rendering the expected number of tiles per row, I would move down by a tile span to start rendering the next row below.</p><p>Since the <code>tilewidth</code> and <code>tileheight</code> properties had the same values due to the tiles being squares, I only needed to use one to know where to place the next tile and by how much to move down to render the next row.</p><p>However, the most important property was the one called <code>layers</code>. As the name implied, its value contained an array of objects containing all the needed data for each layer.</p><pre><code>// map.json
{ 
 //... omitted for clarity
 "layers":[{...}, {...}, ...],
 //... omitted for clarity
}</code></pre><p>A tile layer was represented with an object that had the <code>data</code> property. Its value was an array of numbers. At first I did not understand what it meant but then it became obvious.</p><pre><code>// map.json
{ 
 //...
 "layers":[
   {
      "data":[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 12, 14, 0, 0, 0, 0, 0, 6, 3, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 12, 12, 12, 12, 12, 12, 12, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      "type":"tilelayer",
      // ... other properties omitted for clarity
   }, 
   {...}, 
   ...
  ],
 //...
}</code></pre><p>In Tiled, each tile in the imported tileset will be assigned a number starting with 1 and then increasing from left to right, top to bottom.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NbUL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NbUL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 424w, https://substackcdn.com/image/fetch/$s_!NbUL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 848w, https://substackcdn.com/image/fetch/$s_!NbUL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 1272w, https://substackcdn.com/image/fetch/$s_!NbUL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NbUL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png" width="1456" height="677" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:677,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:92083,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NbUL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 424w, https://substackcdn.com/image/fetch/$s_!NbUL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 848w, https://substackcdn.com/image/fetch/$s_!NbUL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 1272w, https://substackcdn.com/image/fetch/$s_!NbUL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff60c1c76-0359-4e7a-96ff-844817c2cc5d_2482x1154.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The array assigned to the &#8220;data&#8221; property would contain 0 if no tile needed to be displayed therefore denoting an empty space. Otherwise, the number would determine which tile from the tileset should be rendered for a given tile in the map.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!baku!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!baku!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 424w, https://substackcdn.com/image/fetch/$s_!baku!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 848w, https://substackcdn.com/image/fetch/$s_!baku!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 1272w, https://substackcdn.com/image/fetch/$s_!baku!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!baku!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png" width="1456" height="907" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:907,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:460692,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!baku!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 424w, https://substackcdn.com/image/fetch/$s_!baku!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 848w, https://substackcdn.com/image/fetch/$s_!baku!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 1272w, https://substackcdn.com/image/fetch/$s_!baku!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fede1cce9-a59f-4144-8d13-02d363796732_2338x1456.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If I wanted to render a tile layer in my game, I would simply need to iterate through that array. Each iteration, I would compute the position where the tile needed to be by computing the result of the previous tile position + the tilewidth. Then, I would determine, which tile from the tileset needed to be drawn there. If the number was 0, nothing would be rendered and we would proceed to the next iteration.</p><p>While writing the rendering logic for tile layers, I experienced a problem with the way Tiled numbered tiles in the tileset compared to Kaboom.js. In Tiled, numbering starts from 1 since 0 is an empty tile. However, in Kaboom.js, numbering starts from 0.</p><p>I was able to get around this issue relatively easily by just making sure that when I needed to draw a tile, I would subtract the number in the data array by 1 to get the correct tile to display. As for when 0 occurred, as mentioned previously, I would just skip to the next element of the array after having updated the next tile position as to avoid the issue of trying to display a tile numbered 0 - 1 = -1, which wasn&#8217;t a valid index.</p><p>Finally, because I knew how many tiles were needed per row due to having access to the <code>width</code> and <code>tilewidth</code> properties, I was able to determine when to move to the next row despite the <code>data</code> array being one dimensional.</p><p>After having tested my code, I was able to successfully render the map onto the game&#8217;s canvas and felt overjoyed and relieved that it wasn&#8217;t as complicated as I thought it was going to be.</p><p>Finally, the last step was to render the object group layers which I would use to implement walls, obstacles and where to spawn game objects like enemies.</p><p>The object used to describe an object group layer in the &#8220;layers&#8221; array had a different shape than the one for tile layers.</p><pre><code>// map.json
{ 
 //... omitted for clarity
 "layers":[
   {...}, 
   {
      "objects":[
        {
          "height":223.833333333333,
          "id":3,
          "name":"",
          "rotation":0,
          "type":"",
          "visible":true,
          "width":254.333333333333,
          "x":48.9166666666667,
          "y":256.166666666667
        }, 
        ...
       ],
       "type":"objectgroup",
       //... other properties omitted for clarity
      }, 
   }, 
   ...
 ],
 //... omitted for clarity
}</code></pre><p>Rather than having a <code>data</code> property, we had an <code>objects</code> property, which instead of referencing an array of numbers, was referencing an array of objects. Each of these objects would have important properties like <code>width</code>, <code>height</code> and <code>x</code>, <code>y</code> which was all I needed to determine where to place them in my game. I would therefore, simply iterate through the <code>objects</code> array and create the needed game objects with the needed width and height and place them according to the x and y coordinates provided.</p><p>Since I set them up to be invisible, the player wouldn&#8217;t be able to see them, however, since Kaboom offered a debug mode, you could see their outline by pressing the f1 key.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U79W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U79W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 424w, https://substackcdn.com/image/fetch/$s_!U79W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 848w, https://substackcdn.com/image/fetch/$s_!U79W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 1272w, https://substackcdn.com/image/fetch/$s_!U79W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U79W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png" width="1456" height="1057" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1057,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:151720,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/172615663?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U79W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 424w, https://substackcdn.com/image/fetch/$s_!U79W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 848w, https://substackcdn.com/image/fetch/$s_!U79W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 1272w, https://substackcdn.com/image/fetch/$s_!U79W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddbc8758-c506-4697-8192-e45568f5438e_2338x1698.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I had finally completed the task I set out to do, and now had a more flexible way of making levels for my games. I still use this workflow to this day.</p><h1>Conclusion</h1><p>To conclude, game devs not using game engines, either write their own map editor or use an already existing one like Tiled to design and make their levels more efficiently.</p><p>If you&#8217;re interested in using JavaScript and the same library (now called KAPLAY) to make games, <a href="https://www.youtube.com/@JSLegendDev">I have plenty of tutorials you can watch on my channel</a>.</p><p>I also want to mention, that I made an exclusive step-by-step tutorial available on Patreon where I teach you how to render maps made in Tiled in your KAPLAY games in a performant and modular fashion so that you can use the same code in other KAPLAY projects. </p><p>This 40 minutes tutorial, will tackle more nuanced topics. For example, how to render polygonal colliders (useful for slopes) which differ from regular rectangular colliders. In addition, I have also bundled my paid <a href="https://jslegend.itch.io/mario-like-platformer-assets">mario-like asset pack</a> as part of this tutorial. </p><p>You can access the tutorial <a href="https://www.patreon.com/posts/how-to-design-in-134962354">here</a>.</p><p>If you liked this post and want to see more of it, I recommend subscribing as to not miss out on future posts.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>In the meantime, you can read this next.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;9613e4db-3f66-4bb1-bc81-a1fcf54c24fb&quot;,&quot;caption&quot;:&quot;The story begins after I&#8217;d spent some time making web games in JavaScript. At the time, I was looking at the various monetization options that were available.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Struggle of Wrapping a JavaScript Game for Desktop&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-27T03:33:06.914Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HTUt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cccb09d-65c2-472e-80e5-9c4247c068b8_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-struggle-of-wrapping-a-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:171619371,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[The Struggle of Packaging a JavaScript Game for PC]]></title><description><![CDATA[The story begins after I&#8217;d spent some time making web games in JavaScript.]]></description><link>https://jslegenddev.substack.com/p/the-struggle-of-wrapping-a-javascript</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/the-struggle-of-wrapping-a-javascript</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Wed, 27 Aug 2025 03:33:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!t4Kc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t4Kc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t4Kc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!t4Kc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!t4Kc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!t4Kc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t4Kc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:386942,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/171619371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t4Kc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!t4Kc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!t4Kc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!t4Kc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10b61527-78e4-4b3a-b6d1-d178ca1480a6_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The story begins after I&#8217;d spent some time making web games in JavaScript. At the time, I was looking at the various monetization options that were available. </p><p>Unfortunately, I came to realize that the only &#8220;realistic&#8221; option was to make casual games for web game portals and monetize via ads. Something I did not find enticing. I also wanted to make deeper games which required saving user data. While you can save your player&#8217;s data in the user&#8217;s browser, this wasn&#8217;t reliable as the data could get deleted when the browser cache was cleared. I also didn&#8217;t want to pay for servers just to save user data for a single player game.</p><p>I also didn&#8217;t want to switch to other tools for 2 reasons :</p><p>The first, was that I was getting more and more proficient and comfortable with making games in JavaScript using the KAPLAY game library. Every other option I looked into would&#8217;ve taken me longer to achieve the same result than in KAPLAY. I also didn&#8217;t want to start from scratch learning a new set of tools as it would hinder my game dev progress. I wanted to just build games.</p><p>As for the second reason, I felt that making games in JavaScript wasn&#8217;t a disadvantage since they would immediately and smoothly run in the browser allowing me to reduce considerably the friction to get new players. My strategy was therefore, making the game available on the web as a demo to gauge interest while putting the full release on Steam.</p><p>This is where things got complicated. How would I convert my web game into something that can be installed and played offline on one&#8217;s PC?</p><p>After doing some research, I came across 5 technologies I could potentially use :</p><ul><li><p>Electron</p></li><li><p>Tauri</p></li><li><p>Wails</p></li><li><p>Neutralino</p></li><li><p>NW.js</p></li></ul><h1>Popular but Bloated</h1><p>Electron is a technology that allowed developers to build desktop apps using HTML, CSS and JavaScript. Which was perfect since, my games are based on these technologies.</p><p>To achieve this, Electron would package your app with a whole Chromium browser. Your app would run in that browser.</p><p>That meant every time you installed an app made with Electron, you were also installing a Chromium browser.</p><p>Since, Chromium also consumed a lot of memory when running, apps made with it gained a reputation of being bloated. Some users started complaining but still, it did not prevent Electron to gain market share. However, this is irrelevant for games since people are more forgiving when it comes to resource usage.</p><p>Most major apps built today that are cross platform (meaning works on Mac, Windows, and Linux) are likely made with Electron. This is the case for VSCode (arguably the most popular code editor), Discord, Slack and many others.</p><p>You might be wondering why it&#8217;s this popular? This can be boiled down to three points. </p><ol><li><p>Web developers can use a familiar set of tools to build cross-platform desktop apps rather than having to learning tools specific to each operating system.</p></li><li><p>You have access to most packages and libraries you can find on NPM which means you can build apps really fast.</p></li><li><p>The fact that it packages a specific Chromium version with your app implies that you can rest assured that your app will render and behave the same way regardless of the user&#8217;s operating system. This means less testing is required and therefore less headache on your part.</p></li></ol><p>Despite all of these conveniences, when trying Electron, I found the dev experience quite annoying.</p><p>First of all, the way Electron is designed, makes you separate your app into two portions. You could view this as your &#8220;backend&#8221; and &#8220;frontend&#8221;.</p><p>The &#8220;frontend&#8221; portion, runs JavaScript that can run in the browser and interacts with the HTML.</p><p>The &#8220;backend&#8221; portion, runs JavaScript within Node.js and does not have access to the UI.</p><p>If you check my previous post : <a href="https://jslegenddev.substack.com/p/how-to-start-making-games-in-javascript">How to Start Making Games in JavaScript with No Experience</a>, I explain in more details how JavaScript runs in two major environments.</p><p>I recommend checking it out for more details (Video version is also available <a href="https://www.youtube.com/watch?v=91E1Enzdxuk">here</a>.) but to summarize, JavaScript could only be used in the browser before Node.js was invented. </p><p>With the advent of Node.js, you could now use JavaScript for backend applications that could run directly on a computer rather than within a browser.</p><p>While Electron&#8217;s approach of separating these two environments makes sense for more professional desktop app development, it does make wrapping a web game as a desktop app more tedious.</p><p>The reason is that to communicate between your &#8220;frontend&#8221; JS and your &#8220;backend&#8221; JS within an Electron app, you would need to use a IPC brigde to share only what is needed between the two.</p><p>I mostly wanted to do simple things like write my user&#8217;s save data in a file on their computer and read that same file. I found doing this in Electron more complex than what other alternatives would end up offering. I didn&#8217;t know this at the time. However, The reason I left Electron was due to the whole packaging and building executables process which I found cumbersome.</p><p>I tried using Electron Forge which was supposed to help you set up a full build pipeline. I tried using this tool because it was the one linked in the official Electron tutorial docs. What happened after trying to set it up, is that I could only build the app for the platform I was developing on. Since I was developing on a Mac and I&#8217;m making a game, having at least a Windows executable is mandatory. (Most gamers use Windows and even Linux since the advent of the Steam Deck and not Macs. I use a Mac mainly for the development experience which I find nicer than on Windows.)</p><p>Now, Electron Forge is supposed to allow you to build for the 3 major operating systems if you&#8217;re on a Mac. However, I experienced all kinds of issues for the Windows build as I needed to install WINE (which is a compatibility layer for running Windows software on Unix-like systems).</p><p>In the end, I was left with two choices, either continue to try to make it work (maybe I would have succeeded with other tools like Electron Builder) or look for alternatives.</p><p>I decided to look for alternatives and only come back if nothing else suited my needs.</p><h1>Interesting but I Don&#8217;t Want to Write Rust</h1><p>The second option I explored was Tauri. It was gaining popularity online and I found the way it approached desktop app development unique.</p><p>Instead of wrapping a browser with your app like Electron did, it would simply use the webview available on the user&#8217;s operating system.</p><p>At first, this sounded great, you get apps that are less bloated in size and trying it out, I found it easy to read/write files to the filesystem.</p><p>This was due to Tauri having a JavaScript API you could use for simple things like that. However, for anything more complex, you would have to write Rust.</p><p>Rust is a programming language that&#8217;s quite complicated. It&#8217;s mainly used for lower level development like operating system kernels.</p><p>I didn&#8217;t want to learn Rust as I had no use for it. Initially, I thought that just using the JS API would be enough. Unfortunately, I didn&#8217;t consider something very important.</p><p>This whole exercise I was engaged with consisted in packaging a web game to put it on Steam, therefore integrating with the Steam SDK would have been highly desirable.</p><p>With Electron, I could have achieved this using the steamwork.js package but with Tauri I couldn&#8217;t use this package since the &#8220;backend&#8221; portion of the app required me to write Rust for anything more complicated than what the JS API offered.</p><p>That was a deal breaker. </p><p>The fact that it used the default webview to render the app also meant that it wouldn&#8217;t render the same across platforms since every operating system had a webview based on a different engine. For Windows, it&#8217;s based on Edge which is now Chromium based. For Mac, it&#8217;s based on WebKit which is what is used under the hood for Safari. For Linux, it&#8217;s based on WebKitGTK which is probably similar to WebKit, but I&#8217;m not sure.</p><p>Regardless, the way a Tauri app was rendered would force me to do extensive testing on all three platforms to make sure the game was rendered the same way which would just add to my workload compared to something like Electron. </p><p>That was another deal breaker.</p><p>The last deal breaker was that, when I last used Tauri, you could only generate the executable for the current platform you were developing on. Therefore, I would have to run a Linux distro to build for Linux, run MacOS to build for Mac and run Windows to build for Windows.</p><p>It was time to look for something else.</p><h1>A Similar Approach to Tauri</h1><p>While I was looking up other alternatives, I came across Neutralinojs and Wails. While they didn&#8217;t end up fitting my needs in much the same way as Tauri, I thought it would be interesting to mention them.</p><h2>Neutralinojs</h2><p>Neutralinojs is like Tauri but without Rust. It has a JavaScript API for doing simple things like writing to a file, reading a file, etc&#8230; For anything more complex, you could hook up a separate backend in any language you want using web sockets.</p><h2>Wails</h2><p>Wails is also like Tauri but with Golang instead of Rust. Due to Go&#8217;s portability, it was a breeze to set up and to build executables even though the last time I tried it, I was not able to build for Linux, just Windows and MacOS. Golang is also much more approachable as a language than Rust. It doesn&#8217;t have a JS API you can use unfortunately. Integrating your game with the Steam SDK is a big question mark since Golang isn&#8217;t very used in game dev. Maybe there is a dedicated Go module for this?</p><h1>The Solution</h1><p>After getting a bit frustrated with all the options I tried, I was about to go back to Electron when suddenly, I had an epiphany.</p><p>Why not look up what existing JavaScript based games on Steam where using? I proceeded to go on the SteamDB website which compiled Steam related data. </p><p>Among the data gathered was a list of engines and tools used by released Steam games.</p><p>The most popular JavaScript related tool was NWJS with over 5700 games using it. </p><p>For reference:  </p><ul><li><p>There are over 55000 games made with Unity.</p></li><li><p>There are over 16900 games made with Unreal.</p></li><li><p>There are over 2600 games made with Godot.</p></li></ul><p>The info mentioned above can be found <a href="https://steamdb.info/tech/">here</a>.</p><p>I was initially caught off guard by this data because it seemed to indicate, at first glance, that JavaScript was really popular on Steam. This was unexpected because JavaScript is still viewed as a niche language amongst game developers.</p><p>After doing some research, I figured out the reason NW.js was used as much as seen on SteamDB was because RPG Maker and Construct were both popular (the former more than the latter) no-code game engines built on web technologies (JavaScript). They would use NW.js to allow their users to export their games as desktop executables.</p><p>This implicitly meant that this technology was suitable to use since it was battle tested with so many examples in the wild.</p><p>I had initially written off NW.js as a deprecated (even though still maintained) version of Electron since it was created before it and used the same principle of bundling a Chromium instance with your app. However, after having tried it. It&#8217;s really simple to use compared to Electron.</p><p>First of all, it does not make a separation between the JavaScript that runs in the browser and the one that runs in Node.js. There is no &#8220;backend&#8221; and &#8220;frontend&#8221; to worry about and no IPC bridge to set up between the two. Writing logic to save data to some file and reading that file back is really simple.</p><p>Secondly, building executables for all 3 platforms is also very easy. When you go on the official NW.js website, you download the needed pre-built binaries for the platforms you need to build for.</p><p>Then, you place it in a folder and create a new folder at the same level as the executable with the name <code>package.nw</code>. Finally, you put your source code in that folder.</p><p>When you click on the executable, it runs your app using the source code located in the <code>package.nw</code> folder. That&#8217;s it.</p><p>Now, you can run a command to merge the folder with the executable so it appears as a single .exe that you click and run. What&#8217;s left is just to rename the executable with the name of your game and maybe also change the icon with a third-party tool.</p><p>You might be wondering, doesn&#8217;t this process make it easy for someone to just pirate your game? The answer is yes but it doesn&#8217;t matter.</p><p>The Love2D game framework also works similarly. It didn&#8217;t prevent Balatro, a popular game using this framework to make millions even though the source code was easily accessible.</p><h1>Conclusion</h1><p>After trying all of the technologies mentioned previously, I came to the conclusion that NW.js is the easiest technology by far for wrapping a JavaScript based game for desktop.</p><p>You&#8217;re thinking of giving it a try? In that case, I have an exclusive 1-hour tutorial teaching you how to use NW.js to make a space shooter game for Mac, Windows and Linux.</p><p>In this tutorial, I&#8217;ll teach you :</p><ul><li><p>How to use Vite and NW.js together, so that you can develop for the web (therefore benefiting from hot reloading) while still being able to build an executable that will work as is. We can achieve this by using Vite&#8217;s environment variables allowing us to gate code which will run in the desktop version exclusively. This is useful when it comes to writing and reading files from/to the disk which is code that should run only in the desktop version.</p></li><li><p>How to use NW-Builder to create a script to build executables programmatically rather than having to manually download binaries from the NW.js website.</p></li><li><p>How to make a space shooter game using KAPLAY.</p></li></ul><p>If you&#8217;re interested, check it out <a href="https://www.patreon.com/posts/learn-to-build-136112954">here</a>.</p><p></p><p>If you enjoyed this post and want more like it, I recommend subscribing so you won&#8217;t miss out on future publications.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>Meanwhile, you can check these out.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;65cf4849-5d1c-4195-ab3d-1babc9858008&quot;,&quot;caption&quot;:&quot;It&#8217;s been a while since I started making web games in JavaScript. In this post, I&#8217;d like to share tips that would be helpful for beginners wanting to do the same.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How to Start Making Games in JavaScript with No Experience&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-19T01:24:34.022Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!oYkb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-to-start-making-games-in-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:170456472,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:9,&quot;comment_count&quot;:1,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;bbf1812e-1425-4e1f-965d-5f5ebba9e7a1&quot;,&quot;caption&quot;:&quot;Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes. Let&#8217;s get started!&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The KAPLAY Game Library in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-02T01:07:47.577Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169792492,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p>]]></content:encoded></item><item><title><![CDATA[How to Start Making Games in JavaScript with No Experience]]></title><description><![CDATA[It&#8217;s been a while since I started making web games in JavaScript.]]></description><link>https://jslegenddev.substack.com/p/how-to-start-making-games-in-javascript</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/how-to-start-making-games-in-javascript</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Tue, 19 Aug 2025 01:24:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oYkb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oYkb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oYkb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!oYkb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!oYkb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!oYkb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oYkb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png" width="1280" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:197303,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/170456472?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oYkb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!oYkb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!oYkb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!oYkb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa0097-a1b5-434b-bca1-1395ec91736e_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It&#8217;s been a while since I started making web games in JavaScript. In this post, I&#8217;d like to share tips that would be helpful for beginners wanting to do the same.</p><h1>Learn JavaScript Outside of Game Development Alongside HTML and CSS</h1><p>This might sound obvious, but I really recommend learning to program before learning game dev. For JavaScript, that means learning the fundamentals of the language and how it integrates with HTML and CSS.</p><p>Considering that JavaScript is primarily used on the web to make websites and web apps, I recommend that your first few projects be web related and not game related. Game development has a lot of domain specific knowledge to grasp. A beginner would easily be overwhelmed with having to learn programming, programming in JavaScript, HTML and CSS to make the web page on which the JavaScript will run and game development all at once.</p><p>You don&#8217;t have to learn everything in JavaScript either, just the core fundamentals. You&#8217;ll learn more obscure features during practice when building projects.</p><h1>Learn That There are Two JavaScripts, The one That Runs in Node.js and The one That Runs on The Web</h1><p>For some time, JavaScript was relegated to a scripting language used for making web pages built using HTML and CSS, interactive. However, when the Node.js runtime was released, it allowed devs to run JavaScript outside of the browser on their machines, similarly to other languages like C#, Python, etc&#8230; This enabled JavaScript to be used for more than just websites.</p><p>There were now two major environments where JavaScript could run, in the browser and in Node.js.</p><p>When creating a server side or command line application, you would write JavasScript that would run directly on the user&#8217;s machine with Node.js. However, even for JavaScript that was intended to run on a web page, you would still use Node.js, not to run the code but to install tools useful for transforming your JavaScript before it runs on a web page.</p><p>This is due to an innovation that was brought with Node.js: The ability to download packages directly from a package manager called NPM (stands for Node Package Manager). The days where you needed to import a library by linking it using a script tag in your HTML were over.</p><p>These tools, called bundlers would bundle the libraries you installed via NPM alongside your JavaScript code and compile them into a single compact JavaScript file that you could run within a web page. </p><p>Developers would also write a more ergonomic version of JavaScript that had features not supported by browsers since they relied on the bundler to transpile whatever they wrote into JavaScript that was browser supported.</p><p>For example, React, arguably the most popular library for building web UIs, allows you to author UI components in an easy fashion by using an HTML-like syntax within your JavaScript called JSX. However, JSX is not valid in JavaScript. That&#8217;s why the bundler will transform your JavaScript code using JSX into JavaScript that doesn&#8217;t so it can run in the browser while retaining the same functionality. At the end of the day, what can be done with JSX can still be done in regular JS but in a more verbose way. That&#8217;s why we let the bundlers deal with it to make our lives easier.</p><p>All this to say, that today, most JavaScript developers, use a dev setup built around using Node.js and NPM even if the intended code is to run in the browser and not in Node.js.</p><p>For gamedev, that implies installing Node, using a build tool like Vite, installing your game frameworks/libraries via NPM, and compiling/transpiling your code to a version that can run on the web. This is often called &#8220;your build&#8221;. This build is what you deploy on your website or on platforms like itch.io.</p><p>You get a more streamlined experience from this setup since your Node.js based project keeps track of your libraries&#8217; versions via a file called package.json (updating a library is one command away), you have access to hot reloading (meaning every time you modify your code, the change is immediately reflected which is a game changer for gamedev since this allows you to iterate quickly) and a local server is spun up automatically for you so that you can preview your project easily.</p><p>The old way of doing things is still available if you wish to avoid that complexity. For example, you can download the JavaScript file for the library, link it to you HTML using a script tag and finally install an extension like live server in VSCode to benefit from a local server and hot reloading. However, you&#8217;d still need to manually keep track of what version of the library you&#8217;re using and manually have to download new versions and replace the existing one in your project&#8217;s folder which can become tedious.</p><h1>Stick to 2D, Since 3D is More Complicated</h1><p>While using JavaScript for 2D games is viable, for 3D, it&#8217;s a different story. It&#8217;s very hard to compete with modern engines like Unity and Unreal which are more suited for creating 3D games and abstract away a lot of the complexity that comes with 3D.</p><p>However, there are still 3D focused libraries like Three.js but there are more suited for 3D experiences that lives on a web page rather than full fledge games. I concede that it&#8217;s still possible to make a good looking 3D game if you&#8217;re able to come up with a unique art direction. As an idea you could try replicating the HD-2D artstyle of Square Enix. Putting 2D sprites into a low poly 3D world and add post processing effects.</p><h1>Pick a Framework/Library</h1><p>Games made in JavaScript are rendered within the HTML canvas element of a web page. By default, you have access to the canvas API allowing you to render graphics. For those unfamiliar, it&#8217;s similar to PyGame or Love2D where you have to basically write most things from scratch.</p><p>While this is very good for learning and you&#8217;ll learn knowledge that is transferable to other lower level game dev environment, I don&#8217;t think it&#8217;s the way to go for beginners.</p><p>At least, it depends on why you&#8217;re doing game dev. If you like the technical challenges that comes with making a game, using no libraries could be more fulfilling but unfortunately time consuming. However, if you care about results, meaning having finished games, it would be wiser to use a framework or a library that offers a lot out of the box. As a beginner you&#8217;ll be more likely to finish projects which will in turn increase your motivation and increase the likelihood that you stick with game dev long term.</p><p>However, as opposed to using an engine like Unity, Godot, Unreal, using a frameworks still allows to you architect your codebase with a greater degree of freedom and prevents you from spending too much time learning how specific game engine workflows and UIs work.</p><p>For JavaScript, I recommend going with <a href="https://kaplayjs.com">KAPLAY</a> due to it&#8217;s simplicity and intuitive API. <a href="https://www.youtube.com/watch?v=JaEBkTsgXiQ">I have a video explaining the library in 5 minutes that you can watch next.</a> For something more established, <a href="https://phaser.io/">Phaser</a> is the dominant player and is more performant even though it has a steeper learning curve.</p><h1>Use a Map Editor like Tiled/LDTK</h1><p>Manually placing objects in your game via code will get tedious quickly and make you wish you used a proper game engine. Fortunately, there is a solution. You can use a map editor like <a href="https://www.mapeditor.org/">Tiled</a> or <a href="https://ldtk.io/">LDTK</a> to create your game&#8217;s levels/maps visually like you would do in game engine.</p><p>I really recommend investing the time to learn how to use one.</p><h1>Stick to One Tutorial, Start to Finish</h1><p>When getting started, you might be tempted to follow a project based tutorial. There&#8217;s nothing wrong with that. Just stick with it from start to finish. Don&#8217;t hop between different tutorials. Doing this will only slow down your progress.</p><p>Once you have completed following along, you can start building an original project that heavily leverages what was taught in the tutorial. It&#8217;s at that stage that you actually learn. Before that, you&#8217;re only getting exposed to various concepts without them being consolidated in your mind.</p><p>You can check some of my tutorials <a href="https://www.youtube.com/@JSLegendDev">here</a>.</p><h1>Learn Pixel Art</h1><p>Your game will sooner or later need graphics. At first, there is nothing wrong with using ready made asset packs. However, I think it&#8217;s worth the investment to learn how to make good pixel art since you&#8217;ll be able to make the sprites you need without having be dependent on an asset pack existing for your particular use case.</p><p>A nice intermediate step between using asset packs and making your own sprites from scratch is to modifiy existing asset packs. This is actually very helpful in gradually developing an understanding of what makes good pixel art.</p><p>Learning to modify asset packs well, is also very useful when you need to use multiple ones for a single project as it will allow you to make everything look consistent.</p><p>In terms of software I use is Aseprite. It&#8217;s the most popular option. However, it doesn&#8217;t really matter what software you use as long as it works for you. You can check some of the pixel art I make on my <a href="https://jslegend.itch.io">itch.io</a> page.</p><p>Pixel art is an art form where you have the highest likelihood of making something that passes the professional quality bar in a reasonable amount of time. That&#8217;s why it&#8217;s my go-to art style.</p><p><a href="https://www.youtube.com/watch?v=Fgoj4nj9P8M">I recommend checking my pixel art for programmers video for more tips.</a></p><h1>Make Small Games First</h1><p>This is now the standard advice parroted online but by making small games, you&#8217;re more likely to finish a project. This will in turn motivate you to continue game development and increase your skills for the next project, so on and so forth.</p><p>If you lack ideas, try remaking existing simple games like pong, duck hunt, etc&#8230; I have tutorials on my channel you can follow.</p><h1>Deploying Your Games</h1><p>I recommend publishing your games not only on your own website (if you have one) but on platform like itch.io where people interested in games congregate. For itch.io, you might find it hard to find players if you just upload your game. That&#8217;s why I recommend joining a game jam as you&#8217;ll be more likely to get feedback on your games that way.</p><p>Unfortunately, ways to successfully monetize web game development are rather limited. However you&#8217;re not limited to the web, you can transform your JavaScript web game into a desktop app that can be sold on Steam.</p><p>The simplest way I&#8217;ve found of achieving this was to use the NW.js technology which is similar to the more popular Electron but much simpler to use.</p><p>I have an exclusive step by step tutorial on Patreon teaching you how to make a downloadable desktop space shooter game with KAPLAY and NW.js for Mac, Windows and Linux.</p><p><a href="https://www.patreon.com/posts/learn-to-build-136112954">Check it out here!</a></p><h1>Conclusion</h1><p>Hope these tips where useful in your JavaScript game development journey. If you&#8217;re interested in more content like this, consider subscribing to not miss out when I post something new.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;6555c20a-3912-45f4-b6d0-3849d5001630&quot;,&quot;caption&quot;:&quot;The story begins after I&#8217;d spent some time making web games in JavaScript. At the time, I was looking at the various monetization options that were available.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The Struggle of Wrapping a JavaScript Game for Desktop&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-27T03:33:06.914Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!HTUt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cccb09d-65c2-472e-80e5-9c4247c068b8_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-struggle-of-wrapping-a-javascript&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:171619371,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;87d23f96-d950-4b8a-a15f-b7b2c95f5c79&quot;,&quot;caption&quot;:&quot;Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes. Let&#8217;s get started!&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The KAPLAY Game Library in 5 Minutes&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-02T01:07:47.577Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169792492,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[The KAPLAY Game Library in 5 Minutes]]></title><description><![CDATA[Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes.]]></description><link>https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/the-kaplay-game-library-in-5-minutes</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Sat, 02 Aug 2025 01:07:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cKTz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cKTz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!cKTz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!cKTz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!cKTz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cKTz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:395201,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cKTz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!cKTz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!cKTz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!cKTz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F767276fd-c486-4e09-afce-3f886bbe6b94_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hi, in this post, I&#8217;ll try to explain what is KAPLAY and what are its major concepts in 5 minutes. Let&#8217;s get started!</p><p>First of all, KAPLAY is an <em>open source library for making games in JavaScript and TypeScrip</em>t.</p><p>It has built-in support for rendering, asset loading, input handling, animations, audio playback, collision detection and physics.</p><p>Contrary to Phaser, KAPLAY isn&#8217;t OOP (Object-Oriented-Programming) based, instead it offers an entity component system (ECS) where you build game objects using components. Each component enables the game object to have access to certain features, properties and methods.</p><p>In KAPLAY, a game object is created by passing an array of components to the <em>add</em> function. For example, using the <em>sprite</em> component will allow the game object to be rendered as a sprite. The <em>area</em> component will create a hitbox useful for collision detection. It can be seen using KAPLAY&#8217;s debug mode by pressing the <code>f1</code> key or the <code>fn+f1</code> keys on a Mac. Finally, the <em>pos</em> component is used to set its position on the canvas.</p><p>KAPLAY offers a bunch of ready-made components, increasing development speed. However, you can create your own components if you wish to.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2te0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2te0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 424w, https://substackcdn.com/image/fetch/$s_!2te0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 848w, https://substackcdn.com/image/fetch/$s_!2te0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 1272w, https://substackcdn.com/image/fetch/$s_!2te0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2te0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png" width="1456" height="610" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:610,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:213071,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2te0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 424w, https://substackcdn.com/image/fetch/$s_!2te0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 848w, https://substackcdn.com/image/fetch/$s_!2te0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 1272w, https://substackcdn.com/image/fetch/$s_!2te0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0a620d6-d16f-43db-aa6b-a63a6f2587f8_2724x1142.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Additionally, passing a JavaScript object to the array of components, allows us to define custom properties and methods which removes the need to create classes like in the OOP (Object-Oriented Programming) paradigm.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZPvu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZPvu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 424w, https://substackcdn.com/image/fetch/$s_!ZPvu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 848w, https://substackcdn.com/image/fetch/$s_!ZPvu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 1272w, https://substackcdn.com/image/fetch/$s_!ZPvu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZPvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png" width="1456" height="657" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:657,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:216427,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZPvu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 424w, https://substackcdn.com/image/fetch/$s_!ZPvu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 848w, https://substackcdn.com/image/fetch/$s_!ZPvu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 1272w, https://substackcdn.com/image/fetch/$s_!ZPvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fcaf78c-da56-46bb-a7b4-9142d5769905_2724x1230.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As for animations, KAPLAY offers an intuitive animation API. To load a spritesheet use the <em>loadSprite</em> function, pass to it a name you want to use to refer to the spritesheet, the path where the image is located and finally an object telling KAPLAY how to slice it into single frames. This object, should have the <em>sliceX</em> property determining the number of frames per row, the <em>sliceY</em> property determining the number of frames per column and an <em>anims</em> property containing an object for configuring animation data.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aKLu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aKLu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 424w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 848w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1272w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aKLu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png" width="1456" height="900" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aKLu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 424w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 848w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1272w, https://substackcdn.com/image/fetch/$s_!aKLu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MwsY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MwsY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 424w, https://substackcdn.com/image/fetch/$s_!MwsY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 848w, https://substackcdn.com/image/fetch/$s_!MwsY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 1272w, https://substackcdn.com/image/fetch/$s_!MwsY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MwsY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png" width="1456" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:181719,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MwsY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 424w, https://substackcdn.com/image/fetch/$s_!MwsY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 848w, https://substackcdn.com/image/fetch/$s_!MwsY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 1272w, https://substackcdn.com/image/fetch/$s_!MwsY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866dd73a-f792-4e79-b7f3-ae9af0144055_2148x926.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To define an animation, you can pass a key object pair to the <em>anims</em> object. The key determines the name of the animation. The value can be an object with the <em>from</em> property containing the number of the frame where the animation starts, the <em>to</em> property for determining the last frame of the animation and optionally, a <em>loop</em> property containing a boolean where you can set the animation to loop indefinitely and a <em>speed</em> property for setting the speed at which the animation will play.</p><p>Finally, to play an animation, you can either use the play method available on any game object that uses the <em>sprite</em> component or pass the animation you want to play as a second param to the <em>sprite</em> component where the game object is defined.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!r5dQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!r5dQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 424w, https://substackcdn.com/image/fetch/$s_!r5dQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 848w, https://substackcdn.com/image/fetch/$s_!r5dQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 1272w, https://substackcdn.com/image/fetch/$s_!r5dQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!r5dQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png" width="1456" height="393" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:393,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64039,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!r5dQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 424w, https://substackcdn.com/image/fetch/$s_!r5dQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 848w, https://substackcdn.com/image/fetch/$s_!r5dQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 1272w, https://substackcdn.com/image/fetch/$s_!r5dQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c87ea6-62b9-480e-b3f4-e665b474fecc_1602x432.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mzmq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mzmq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 424w, https://substackcdn.com/image/fetch/$s_!Mzmq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 848w, https://substackcdn.com/image/fetch/$s_!Mzmq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 1272w, https://substackcdn.com/image/fetch/$s_!Mzmq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mzmq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png" width="1456" height="498" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:498,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:80129,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Mzmq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 424w, https://substackcdn.com/image/fetch/$s_!Mzmq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 848w, https://substackcdn.com/image/fetch/$s_!Mzmq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 1272w, https://substackcdn.com/image/fetch/$s_!Mzmq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90748409-38e0-465d-9ab4-e7e4e26d0433_1602x548.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;ec3f44d8-58ca-4424-9d14-e71e2d4a4599&quot;,&quot;duration&quot;:null}"></div><p>Another important concept of KAPLAY are scenes. To create a scene you need to call the <em>scene</em> function and pass a string defining the scene&#8217;s name and a function that will run the scene&#8217;s logic. You can use the <em>go</em> function to navigate from one scene to another and even pass data to other scenes.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;d844fbd4-10fa-47b5-ba33-5dbc902fda9f&quot;,&quot;duration&quot;:null}"></div><p>KAPLAY also offers functions for handling user input with support for controller, mouse and keyboard. </p><p>You can define virtual buttons in the KAPLAY context where your game is initially configured. Once done, you can use the <em>onButtonPress</em>, <em>onButtonDown</em>, <em>onButtonReleased</em> methods for handling input. For example, the <em>onButtonPress</em> function will be provided a function which will run when any button is pressed. The name of the button being pressed will be passed to that function which you can use to determine whenever to run some logic.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;87fdecc5-8b4e-407a-a61a-bd829f9801e9&quot;,&quot;duration&quot;:null}"></div><p>In addition to components and JavaScript objects, you can pass a string to the array of components to give an identifier to a game object. These are called tags and they&#8217;re very useful. </p><p>A prime example of their use, is for collision detection. Using KAPLAY&#8217;s <em>onCollide</em> function you can use tags to handle collisions between two games objects or a game object and a group of game objects or two groups.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;e4b4d5ae-15c2-4cae-ba24-befa6e623c10&quot;,&quot;duration&quot;:null}"></div><p>Tags are also useful for querying game objects. Using KAPLAY&#8217;s <em>get</em> function you can provide it with a tag and it will return all game objects having that tag.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gf53!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gf53!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 424w, https://substackcdn.com/image/fetch/$s_!gf53!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 848w, https://substackcdn.com/image/fetch/$s_!gf53!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 1272w, https://substackcdn.com/image/fetch/$s_!gf53!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gf53!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png" width="1456" height="585" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:585,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:109822,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gf53!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 424w, https://substackcdn.com/image/fetch/$s_!gf53!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 848w, https://substackcdn.com/image/fetch/$s_!gf53!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 1272w, https://substackcdn.com/image/fetch/$s_!gf53!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bafa080-765c-45b3-93de-f295fe274ce5_1542x620.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>KAPLAY also has a physics system included which is very easy to use. You can set the gravity per scene by using the <em>setGravity</em> function. To make a game object susceptible to gravity, use the <em>body</em> component. As for a game object acting as a platform, you can within the <em>body</em> component, pass an object with an <em>isStatic</em> property set to <em>true</em> to prevent it from falling off or moving. Additionally, using this component enables the game object to use KAPLAY&#8217;s built in <em>jump</em> method. Therefore, you don&#8217;t have to implement a jump mechanic yourself.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;ce641ef9-e565-4d1a-8f7d-e303ba59dcc2&quot;,&quot;duration&quot;:null}"></div><p>Another very useful KAPLAY concept is the ability of creating child game objects using the add method available on the parent. You can use the <em>pos</em> component to offset the child&#8217;s position vis-a-vis the parent. This makes implementing certain mechanics easier.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;0df69e7a-b035-45bb-b921-10b2abf428d9&quot;,&quot;duration&quot;:null}"></div><p>Sometimes, you need to run logic every frame. For this purpose, you can create an update loop by using the <em>onUpdate</em> function. Alternatively, you can attach an update loop to a specific game object so that if it ends up destroyed, the update loop is cancelled.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;eed4e34b-c746-4a51-a519-cb260f6849e4&quot;,&quot;duration&quot;:null}"></div><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;ec6e687a-0f0c-4b5b-8929-b5d845f19e37&quot;,&quot;duration&quot;:null}"></div><p>Finally, KAPLAY can be installed like any JavaScript library using a script tag or with NPM but you can also use its web based editor called KAPLAYGROUND which is free.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rnML!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rnML!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 424w, https://substackcdn.com/image/fetch/$s_!rnML!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 848w, https://substackcdn.com/image/fetch/$s_!rnML!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 1272w, https://substackcdn.com/image/fetch/$s_!rnML!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rnML!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png" width="1456" height="755" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:755,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:375469,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/169792492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rnML!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 424w, https://substackcdn.com/image/fetch/$s_!rnML!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 848w, https://substackcdn.com/image/fetch/$s_!rnML!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 1272w, https://substackcdn.com/image/fetch/$s_!rnML!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F674fa489-8b38-4f62-bc37-ab32483d0715_2658x1378.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There are many KAPLAY concepts I haven&#8217;t covered here but I hope it gave you a quick overview of the library and helped you determine if you want to invest your time learning it.</p><p>If you want to dive deeper, check my project based tutorials using KAPLAY on <a href="https://youtube.com/@jslegenddev">YouTube</a>. You can also check the API reference and examples in the <a href="https://kaplayjs.com">KAPLAY docs</a>.</p><p>Additionally, I offer exclusive KAPLAY tutorials on Patreon. Link is <a href="https://patreon.com/jslegend">here</a>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;fbbc9395-dd5a-4c95-93ea-a5d529d633d3&quot;,&quot;caption&quot;:&quot;In this tutorial, I&#8217;ll teach you how to make a simple Sonic themed infinite runner game in TypeScript using the KAPLAY game library.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How to Build a Sonic Themed Infinite Runner Game in TypeScript With KAPLAY - Part 1/2&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-05-06T21:19:08.647Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!fb2O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/learn-to-build-a-sonic-themed-infinite&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:162589188,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:4,&quot;comment_count&quot;:1,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!bYsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[My Sonic TypeScript KAPLAY Game Dev Tutorial Now Available as a Nicely Formatted PDF]]></title><description><![CDATA[I have made available a PDF version of my Sonic TypeScript KAPLAY tutorial.]]></description><link>https://jslegenddev.substack.com/p/my-sonic-typescript-kaplay-tutorial</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/my-sonic-typescript-kaplay-tutorial</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Mon, 19 May 2025 18:40:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Qq62!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Qq62!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Qq62!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 424w, https://substackcdn.com/image/fetch/$s_!Qq62!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 848w, https://substackcdn.com/image/fetch/$s_!Qq62!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 1272w, https://substackcdn.com/image/fetch/$s_!Qq62!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Qq62!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1342816,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163946469?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Qq62!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 424w, https://substackcdn.com/image/fetch/$s_!Qq62!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 848w, https://substackcdn.com/image/fetch/$s_!Qq62!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 1272w, https://substackcdn.com/image/fetch/$s_!Qq62!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6aad231c-8f45-41d5-a386-b9cf57403cc4_2536x1416.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I have made available a PDF version of my Sonic TypeScript KAPLAY tutorial.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;29ee5c17-4383-4ac2-b42a-86fc582f9d5b&quot;,&quot;caption&quot;:&quot;In this tutorial, I&#8217;ll teach you how to make a simple Sonic themed infinite runner game in TypeScript using the KAPLAY game library.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How to Build a Sonic Themed Infinite Runner Game in TypeScript With KAPLAY - Part 1/2&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-05-06T21:19:08.647Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/learn-to-build-a-sonic-themed-infinite&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:162589188,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>With the PDF, you get :</p><ul><li><p>Nice formatting, making the text very easy to read.</p></li><li><p>Everything in a single 34 pages document.</p></li><li><p>Syntax highlighting for code.</p></li><li><p>Easy to navigate table of contents.</p></li></ul><p>Here is a preview of the PDF.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;0b59e829-d61b-406e-8f55-ebe5c91473da&quot;,&quot;duration&quot;:null}"></div><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.patreon.com/jslegend/shop/how-to-build-sonic-runner-game-in-with-1651923&quot;,&quot;text&quot;:&quot;Buy it for 5$&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.patreon.com/jslegend/shop/how-to-build-sonic-runner-game-in-with-1651923"><span>Buy it for 5$</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[A List of Games Made With KAPLAY]]></title><description><![CDATA[KAPLAY is a recent JavaScript/TypeScript library for making games.]]></description><link>https://jslegenddev.substack.com/p/a-list-of-games-made-with-kaplay</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/a-list-of-games-made-with-kaplay</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Sat, 17 May 2025 04:02:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!F-MX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F-MX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F-MX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!F-MX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!F-MX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!F-MX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F-MX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;https://kaplayjs.com/kaplayjs.png&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="https://kaplayjs.com/kaplayjs.png" title="https://kaplayjs.com/kaplayjs.png" srcset="https://substackcdn.com/image/fetch/$s_!F-MX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!F-MX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!F-MX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!F-MX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2bdc9f3-28db-4314-b8c9-8465e6f2da29_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>KAPLAY is a recent JavaScript/TypeScript library for making games. It originated from the now defunct Kaboom.js library. (In fact, it&#8217;s a direct fork of Kaboom)</p><p>Since the library is new, it is often overshadowed by more popular libraries/frameworks like Phaser, Pixi.js, Three.js, etc&#8230;</p><p>While writing about the merits of KAPLAY might convince some to give it a try, I think listing interesting games made with it, might be more effective.</p><p>Here is a list of games made with KAPLAY in no particular order.</p><h1>Pharaoh's Tomb HD &#8211; A Remake Made in KAPLAY</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!stdZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!stdZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 424w, https://substackcdn.com/image/fetch/$s_!stdZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 848w, https://substackcdn.com/image/fetch/$s_!stdZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 1272w, https://substackcdn.com/image/fetch/$s_!stdZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!stdZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png" width="1456" height="745" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:745,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:161662,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163754145?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!stdZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 424w, https://substackcdn.com/image/fetch/$s_!stdZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 848w, https://substackcdn.com/image/fetch/$s_!stdZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 1272w, https://substackcdn.com/image/fetch/$s_!stdZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba30d799-d491-442a-8db8-e69bf789342f_3460x1770.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6omC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6omC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 424w, https://substackcdn.com/image/fetch/$s_!6omC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 848w, https://substackcdn.com/image/fetch/$s_!6omC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 1272w, https://substackcdn.com/image/fetch/$s_!6omC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6omC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png" width="1456" height="745" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:745,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:169834,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163754145?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6omC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 424w, https://substackcdn.com/image/fetch/$s_!6omC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 848w, https://substackcdn.com/image/fetch/$s_!6omC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 1272w, https://substackcdn.com/image/fetch/$s_!6omC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a659c56-d3aa-4d18-a91a-fc0fe276d4f1_3460x1770.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is a remake of the old MS-DOS game called Pharaoh&#8217;s Tomb.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://pt-hd.iocaihost.me/&quot;,&quot;text&quot;:&quot;Play Game&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://pt-hd.iocaihost.me/"><span>Play Game</span></a></p><h1>Clickery Hexagon</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WpLG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WpLG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 424w, https://substackcdn.com/image/fetch/$s_!WpLG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 848w, https://substackcdn.com/image/fetch/$s_!WpLG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 1272w, https://substackcdn.com/image/fetch/$s_!WpLG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WpLG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png" width="1024" height="575" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:575,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WpLG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 424w, https://substackcdn.com/image/fetch/$s_!WpLG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 848w, https://substackcdn.com/image/fetch/$s_!WpLG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 1272w, https://substackcdn.com/image/fetch/$s_!WpLG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea6fabb-4e85-4a94-87fb-15817081c951_1024x575.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Tlmy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Tlmy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 424w, https://substackcdn.com/image/fetch/$s_!Tlmy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 848w, https://substackcdn.com/image/fetch/$s_!Tlmy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 1272w, https://substackcdn.com/image/fetch/$s_!Tlmy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Tlmy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png" width="1029" height="575" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:575,&quot;width&quot;:1029,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Tlmy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 424w, https://substackcdn.com/image/fetch/$s_!Tlmy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 848w, https://substackcdn.com/image/fetch/$s_!Tlmy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 1272w, https://substackcdn.com/image/fetch/$s_!Tlmy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1d6c403-68be-40c7-8061-4e2754bc07cf_1029x575.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TNnc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TNnc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 424w, https://substackcdn.com/image/fetch/$s_!TNnc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 848w, https://substackcdn.com/image/fetch/$s_!TNnc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 1272w, https://substackcdn.com/image/fetch/$s_!TNnc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TNnc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png" width="1026" height="572" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:572,&quot;width&quot;:1026,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TNnc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 424w, https://substackcdn.com/image/fetch/$s_!TNnc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 848w, https://substackcdn.com/image/fetch/$s_!TNnc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 1272w, https://substackcdn.com/image/fetch/$s_!TNnc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ccadf7-5b12-4830-bac1-7ffa9fd0e7e5_1026x572.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Clickery Hexagone is a very polished clicker style game. The game is relatively popular on Newgrounds (a Web game portal).</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.newgrounds.com/portal/view/946392&quot;,&quot;text&quot;:&quot;Play Game&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.newgrounds.com/portal/view/946392"><span>Play Game</span></a></p><h1>Golf Course: Ocean Wave Edition</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UP1m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UP1m!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 424w, https://substackcdn.com/image/fetch/$s_!UP1m!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 848w, https://substackcdn.com/image/fetch/$s_!UP1m!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 1272w, https://substackcdn.com/image/fetch/$s_!UP1m!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UP1m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png" width="1136" height="704" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:704,&quot;width&quot;:1136,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UP1m!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 424w, https://substackcdn.com/image/fetch/$s_!UP1m!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 848w, https://substackcdn.com/image/fetch/$s_!UP1m!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 1272w, https://substackcdn.com/image/fetch/$s_!UP1m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb248988-3db7-4f3f-a6b1-ca76a7d04737_1136x704.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!W4XN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!W4XN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 424w, https://substackcdn.com/image/fetch/$s_!W4XN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 848w, https://substackcdn.com/image/fetch/$s_!W4XN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 1272w, https://substackcdn.com/image/fetch/$s_!W4XN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!W4XN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png" width="1352" height="850" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:850,&quot;width&quot;:1352,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!W4XN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 424w, https://substackcdn.com/image/fetch/$s_!W4XN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 848w, https://substackcdn.com/image/fetch/$s_!W4XN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 1272w, https://substackcdn.com/image/fetch/$s_!W4XN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F339f7d98-ebea-444d-96af-0cc516ebd43a_1352x850.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A short mini-golf game set in the ocean. This game could easily be on Steam considering how much attention to detail there is.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://erikgxdev.itch.io/golf-course&quot;,&quot;text&quot;:&quot;Play Game&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://erikgxdev.itch.io/golf-course"><span>Play Game</span></a></p><h1>Duck Hunter</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!V_9k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!V_9k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 424w, https://substackcdn.com/image/fetch/$s_!V_9k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 848w, https://substackcdn.com/image/fetch/$s_!V_9k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!V_9k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!V_9k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png" width="1455" height="1228" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1228,&quot;width&quot;:1455,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!V_9k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 424w, https://substackcdn.com/image/fetch/$s_!V_9k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 848w, https://substackcdn.com/image/fetch/$s_!V_9k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!V_9k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49d99953-64c8-4059-b663-d171f3c96be7_1455x1228.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A clone of the classic game Duck Hunt. I made this one.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegend.itch.io/duck-hunter&quot;,&quot;text&quot;:&quot;Play Game&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegend.itch.io/duck-hunter"><span>Play Game</span></a></p><h1>Soil Defender</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GL-m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GL-m!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 424w, https://substackcdn.com/image/fetch/$s_!GL-m!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 848w, https://substackcdn.com/image/fetch/$s_!GL-m!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 1272w, https://substackcdn.com/image/fetch/$s_!GL-m!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GL-m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png" width="1456" height="1137" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1137,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:121332,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163754145?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GL-m!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 424w, https://substackcdn.com/image/fetch/$s_!GL-m!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 848w, https://substackcdn.com/image/fetch/$s_!GL-m!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 1272w, https://substackcdn.com/image/fetch/$s_!GL-m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ed973c9-ad40-441a-b5e2-be9755fbc673_2024x1580.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A Plant VS Zombie-like game developed as part of the Earth Journalism Network. Not common to see games used for journalism.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://earthjournalism.net/resources/courses-and-newsgames/soil-defender-a-newsgame&quot;,&quot;text&quot;:&quot;Play Game&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://earthjournalism.net/resources/courses-and-newsgames/soil-defender-a-newsgame"><span>Play Game</span></a></p><h1>Conclusion</h1><p>I hope this list gave you an idea of what you can achieve with KAPLAY. If you&#8217;re aware of other games you think should be showcased here, don&#8217;t hesitate leaving a comment below.</p><p></p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;8be4a75b-c103-4f62-84d6-7631feaca84a&quot;,&quot;caption&quot;:&quot;In this tutorial, I&#8217;ll teach you how to make a simple Sonic themed infinite runner game in TypeScript using the KAPLAY game library.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How to Build a Sonic Themed Infinite Runner Game in TypeScript With KAPLAY - Part 1/2&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-05-06T21:19:08.647Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/learn-to-build-a-sonic-themed-infinite&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:162589188,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[How to Build a Sonic Themed Infinite Runner Game in TypeScript With KAPLAY - Part 2/2]]></title><description><![CDATA[In the previous part of the tutorial, we finished implementing Sonic&#8217;s movement and jumping logic.]]></description><link>https://jslegenddev.substack.com/p/how-to-build-a-sonic-themed-infinite</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/how-to-build-a-sonic-themed-infinite</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Fri, 16 May 2025 18:59:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fb2O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fb2O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fb2O!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fb2O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:92993,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162589188?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!fb2O!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the previous part of the tutorial, we finished implementing Sonic&#8217;s movement and jumping logic. We also implemented platforms and background infinite scrolling.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d5e36b9b-0bcb-4228-b10d-6bf89ce57fb8&quot;,&quot;caption&quot;:&quot;In this tutorial, I&#8217;ll teach you how to make a simple Sonic themed infinite runner game in TypeScript using the KAPLAY game library.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Learn to Build a Sonic Themed Infinite Runner Game in TypeScript With KAPLAY - Part 1/2&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-05-06T21:19:08.647Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/learn-to-build-a-sonic-themed-infinite&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:162589188,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>In this part, we will finish what remains to be implemented.</p><h1>Table of Contents</h1><ul><li><p><a href="https://jslegenddev.substack.com/i/163279066/implementing-rings-for-sonic-to-collect">Implementing Rings for Sonic to Collect</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/163279066/implementing-a-scoring-system">Implementing a Scoring System</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/163279066/adding-enemies">Adding Enemies</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/163279066/implementing-collision-logic-with-enemies">Implementing Collision Logic With Enemies</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/163279066/finishing-the-scoring-ui">Finishing The Scoring UI</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/163279066/implementing-the-game-over-scene">Implementing The Game Over Scene</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/163279066/deployment">Deployment</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/163279066/conclusion">Conclusion</a></p></li></ul><h1>Implementing Rings for Sonic to Collect</h1><p>In the <code>entities.ts</code> file, add the following code :</p><pre><code>// ... previous code omitted for clarity

export function makeRing(position: Vec2) {
  return k.add([
    k.sprite("ring", { anim: "spin" }),
    k.area(),
    k.scale(3),
    k.anchor("center"),
    k.pos(position),
    k.offscreen(),
    "ring",
  ]);
}</code></pre><p>Import the needed assets in <code>main.ts</code>.</p><pre><code>// ... last loadSprite call
k.loadSprite("ring", "graphics/ring.png", {
  sliceX: 16,
  sliceY: 1,
  anims: {
    spin: { from: 0, to: 15, loop: true, speed: 30 },
  },
});
// ... loadSound call</code></pre><p>The <code>makeRing</code> function creates a ring game object. The <code>offscreen</code> component adds an <code>onExitScreen</code> method to that game object (used later in the tutorial) to destroy it once it leaves the screen.</p><p>&#8220;ring&#8221; is a tag used to identify the game object in collision logic which we will later cover. Multiple game objects can have the same tag.</p><p>In <code>main.ts</code>, add the following logic in the game scene to spawn rings.</p><pre><code>// ... previous import statement
import { makeSonic, makeRing } from "./entities";

k.scene("game", () =&gt; {

  // ... code omitted for clarity

  const spawnRing = () =&gt; {
    const ring = makeRing(k.vec2(1280, 610));
    ring.onUpdate(() =&gt; {
      ring.move(-gameSpeed, 0);
    });
    ring.onExitScreen(() =&gt; {
      k.destroy(ring);
    });

    const waitTime = k.rand(0.5, 3);

    k.wait(waitTime, spawnRing);
  };

  spawnRing();

  // k.onUpdate(() =&gt; ...) code omitted for clarity
});</code></pre><p>We create a recursive function called <code>spawnRing</code>. When called, it first creates a ring by calling <code>makeRing</code>. In KAPLAY, you can set an <code>onUpdate</code> loop specific to a game object that will be destroyed if the game object is destroyed. In that update loop, we make the ring move to the left at the same rate as the game&#8217;s speed. This will give the illusion that Sonic is approaching the ring while in reality, it&#8217;s the contrary.</p><p>We use the <code>onExitScreen</code> method to destroy the ring when it exits the screen to the left.</p><p>Using KAPLAY&#8217;s <code>rand</code> function we&#8217;re able to get a random number between 0.5 and 3 representing the time to wait before spawning another ring.</p><p>KAPLAY&#8217;s <code>wait</code> function is used to only call the <code>spawnRing</code> function once the wait time is elapsed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6Gk3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6Gk3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 424w, https://substackcdn.com/image/fetch/$s_!6Gk3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 848w, https://substackcdn.com/image/fetch/$s_!6Gk3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 1272w, https://substackcdn.com/image/fetch/$s_!6Gk3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6Gk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png" width="1456" height="824" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:824,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:414375,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163279066?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6Gk3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 424w, https://substackcdn.com/image/fetch/$s_!6Gk3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 848w, https://substackcdn.com/image/fetch/$s_!6Gk3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 1272w, https://substackcdn.com/image/fetch/$s_!6Gk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d22d0-cf08-4b9a-a467-941b48972b5d_2664x1508.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Implementing a Scoring System</h1><p>Now that the rings are spawned we need to write the logic for Sonic to collect them. Which implies needing to keep track of the score.</p><p>In <code>main.ts</code>, under our game scene, add the following code :</p><pre><code>// ... asset loading logic
k.loadFont("mania", "fonts/mania.ttf");

k.scene("game", () =&gt; {
  // ... gameSpeed loop  

  let score = 0;
  let scoreMultiplier = 0;
  const scoreText = k.add([
    k.text("SCORE : 0", { font: "mania", size: 48 }),
    k.pos(20, 20),
    k.z(2),
  ]);
  
  // ... rest of the code omitted for clarity
});</code></pre><p>In addition to creating variables related to the score, we created a game object acting as our score UI. Using the <code>text</code> component, we&#8217;re able to display text on the screen. The second param of that component is used to set the font and sizing needed.</p><p>Finally, we use the <code>z</code> component to make sure the score UI is always displayed on top of other game objects by setting it&#8217;s <code>z</code> layer to 2.</p><p>You should have the following result.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MTw7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MTw7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 424w, https://substackcdn.com/image/fetch/$s_!MTw7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 848w, https://substackcdn.com/image/fetch/$s_!MTw7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 1272w, https://substackcdn.com/image/fetch/$s_!MTw7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MTw7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png" width="1456" height="817" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:817,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:477524,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163279066?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MTw7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 424w, https://substackcdn.com/image/fetch/$s_!MTw7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 848w, https://substackcdn.com/image/fetch/$s_!MTw7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 1272w, https://substackcdn.com/image/fetch/$s_!MTw7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63a0610c-1ba0-4e68-8a58-96f299b23ede_3122x1752.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now, let&#8217;s update the score every time Sonic collides with a ring. Add the following code in our game scene :</p><pre><code>import { GameObj } from "kaplay";

// asset loading logic
k.loadSound("ring", "sounds/Ring.wav");

k.scene("game", () =&gt; {

  // after makeRing();

  sonic.onCollide("ring", (ring: GameObj) =&gt; {
    k.play("ring", { volume: 0.5 });
    k.destroy(ring);
    score++;
    scoreText.text = `SCORE : ${score}`;
  });

  // before k.onUpdate(() =&gt; ...)

});</code></pre><p>We used Sonic&#8217;s built-in <code>onCollide</code> method which takes as the first param the tag of a game object you want to check collisions with. The second param is a function that will run in case a collision does occur. Here, we play the &#8220;ring&#8221; sound and then destroy the ring game object Sonic collided with. Finally, we increment the score and change the score UI&#8217;s text to reflect the new score.</p><p>If you run the game now, you should see the score updating every time Sonic collides with a ring.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JYQH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JYQH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 424w, https://substackcdn.com/image/fetch/$s_!JYQH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 848w, https://substackcdn.com/image/fetch/$s_!JYQH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 1272w, https://substackcdn.com/image/fetch/$s_!JYQH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JYQH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png" width="1456" height="817" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:817,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:465938,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163279066?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JYQH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 424w, https://substackcdn.com/image/fetch/$s_!JYQH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 848w, https://substackcdn.com/image/fetch/$s_!JYQH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 1272w, https://substackcdn.com/image/fetch/$s_!JYQH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0f9d0d6-543f-4e80-bfab-258c4584f979_3122x1752.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Adding Enemies</h1><p>The code needed for adding enemies to our game is going to be very similar to the one for adding rings. The only difference is that, contrary to rings, if Sonic touches an enemy, it&#8217;s game over. However, if Sonic jumps on that enemy, the enemy gets destroyed.</p><p>In <code>entities.ts</code>, add the following code :</p><pre><code>// ... previous code omitted for clarity

export function makeMotobug(position: Vec2) {
  return k.add([
    k.sprite("motobug", { anim: "run" }),
    k.area({ shape: new k.Rect(k.vec2(-5, 0), 32, 32) }),
    k.scale(3),
    k.anchor("center"),
    k.pos(position),
    k.offscreen(),
    "enemy",
  ]);
}</code></pre><p>Here, we defined a function for creating our enemy, the &#8220;Motobug&#8221;. We used components that should now be familiar to you. However, you might have noticed that we pass an object to the area component. This is something you can do to define a custom hitbox shape. Here, we&#8217;re setting the shape of the hitbox to be a rectangle using KAPLAY&#8217;s Rect constructor. It allows you to set the hitbox&#8217;s origin relative to the game object. If you pass k.vec2(0,0), the origin will be the same as the game object&#8217;s. The second and third param of the constructor are used to set the width and the height of the hitbox.</p><p>Once we will add enemies to the game, you&#8217;ll be able to use the debug mode to view how our hitbox configuration for Motobug is rendered.</p><p>Add the following code to <code>main.ts</code> : </p><pre><code>// ... import statements omitted for clarity
import { makeSonic, makeRing, makeMotobug } from "./entities";

// ... previous asset loading statements omitted for clarity
k.loadSprite("motobug", "graphics/motobug.png", {
  sliceX: 5,
  sliceY: 1,
  anims: {
    run: { from: 0, to: 4, loop: true, speed: 8 },
  },
});

k.scene("game", () =&gt; {
  // ... previous code omitted for clarity

  const spawnMotoBug = () =&gt; {
    const motobug = makeMotobug(k.vec2(1280, 595));
    motobug.onUpdate(() =&gt; {
      if (gameSpeed &lt; 3000) {
        motobug.move(-(gameSpeed + 300), 0);
        return;
      }
      motobug.move(-gameSpeed, 0);
    });

    motobug.onExitScreen(() =&gt; {
      k.destroy(motobug);
    });

    const waitTime = k.rand(0.5, 2.5);

    k.wait(waitTime, spawnMotoBug);
  };

  spawnMotoBug();

  // k.onUpdate(() =&gt; ...);
});</code></pre><p>The logic for spawning &#8220;Motobugs&#8221; is mostly the same compared to the one for &#8220;rings&#8221;. However, the &#8220;Motobug&#8221;s update loop is slightly different.</p><pre><code>motobug.onUpdate(() =&gt; {
  if (gameSpeed &lt; 3000) {
    motobug.move(-(gameSpeed + 300), 0);
    return;
  }
  motobug.move(-gameSpeed, 0);
});</code></pre><p>When the game&#8217;s speed is inferior to 3000 we make the &#8220;Motobug&#8221; move faster than the scrolling of the platforms so that it appears as moving on the platforms towards Sonic. Otherwise, it would look like Sonic is the one moving towards stationary &#8220;Motobugs&#8221;.</p><p>However, when the game&#8217;s speed gets really fast, it isn&#8217;t possible to really tell the difference. In that case, we simply make the &#8220;Motobug&#8221; move at the same rate as the scrolling platforms.</p><p>At this point, you should see enemies spawn in your game.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i4_i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i4_i!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 424w, https://substackcdn.com/image/fetch/$s_!i4_i!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 848w, https://substackcdn.com/image/fetch/$s_!i4_i!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!i4_i!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i4_i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png" width="1456" height="829" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:829,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:351862,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163279066?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!i4_i!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 424w, https://substackcdn.com/image/fetch/$s_!i4_i!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 848w, https://substackcdn.com/image/fetch/$s_!i4_i!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!i4_i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d67355-a67d-4e7d-bddc-e6bbd7334ff1_2112x1202.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Implementing Collision Logic With Enemies</h2><p>At the moment, if Sonic collides with an enemy, nothing happens. Likewise, if he jumps on one.</p><p>Let&#8217;s add the following code in <code>main.ts</code> :</p><pre><code>// ... previous asset loading logic omitted for clarity
k.loadSound("hyper-ring", "sounds/HyperRing.wav");
k.loadSound("destroy", "sounds/Destroy.wav");
k.loadSound("hurt", "sounds/Hurt.wav");

k.scene("game", () =&gt; {
   // ... previous code omitted for clarity

   sonic.onCollide("enemy", (enemy) =&gt; {
    if (!sonic.isGrounded()) {
      k.play("destroy", { volume: 0.5 });
      k.play("hyper-ring", { volume: 0.5 });
      k.destroy(enemy);
      sonic.play("jump");
      sonic.jump();
      scoreMultiplier += 1;
      score += 10 * scoreMultiplier;
      scoreText.text = `SCORE : ${score}`;
      return;
    }

    k.play("hurt", { volume: 0.5 });
    k.setData("current-score", score);
    k.go("game-over");
  });

  // reset multiplier when Sonic hits the ground
  sonic.onGround(() =&gt; {
    scoreMultiplier = 0;
  });
  // k.onUpdate(() =&gt; ...)

});</code></pre><p>If you run the game now, you should be able to jump on enemies and if Sonic hits an enemy while grounded, you will be transitioned over to an empty game over screen.</p><p>You&#8217;ll notice that we added logic to multiply the player&#8217;s score if they jump on multiple enemies before hitting the ground.</p><p>We&#8217;re also registering the current player score in local storage so we can display it later in the game over scene.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;e4cb97ee-763a-44fa-adb8-9d3450b60fbd&quot;,&quot;duration&quot;:null}"></div><h1>Finishing The Scoring UI</h1><p>Since our game is very fast paced, it&#8217;s hard for players to keep track of how many rings they&#8217;re collecting. They would have to look up to the top left of the screen while risking not seeing an enemy in time to avoid it/jump on it.</p><p>To mitigate this and to give the player a better sense of what they&#8217;re doing, I opted to display the number of rings collected after every collision with a ring or a jump on an enemy. This will also make combos easier to understand.</p><p>Add the following code in <code>main.ts</code> to implement this feature :</p><pre><code>k.scene("game", () =&gt; {
  // ... previous code omitted for clarity
  // sonic.setEvents();

  const ringCollectUI = sonic.add([
    k.text("", { font: "mania", size: 18 }),
    k.color(255, 255, 0),
    k.anchor("center"),
    k.pos(30, -10),
  ]);

  // ...
  sonic.onCollide("ring", (ring: GameObj) =&gt; {
    //... previous code omitted for clarity
    ringCollectUI.text = "+1";
    k.wait(1, () =&gt; {
     ringCollectUI.text = "";
    });
  });
  
  // ...
  sonic.onCollide("enemy", (enemy) =&gt; {
    if (!sonic.isGrounded()) {
      // ... previous code omitted for clarity
      if (scoreMultiplier === 1)
        ringCollectUI.text = `+${10 * scoreMultiplier}`;
      if (scoreMultiplier &gt; 1) ringCollectUI.text = `x${scoreMultiplier}`;
      k.wait(1, () =&gt; {
        ringCollectUI.text = "";
      });
      return;
    }
    // ...
  });
});</code></pre><p>Now, if you run the game, you should see a +1 appear every time Sonic collides with a ring and a +10, x2, x3, etc&#8230; when he jumps on one or many &#8220;Motobugs&#8221;.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;322f570f-b64d-4dcb-8020-8d877f3ebe87&quot;,&quot;duration&quot;:null}"></div><p>An important concept present in the code above, is that game objects can have child game objects assigned to them in KAPLAY. This is what we do here :</p><pre><code>const ringCollectUI = sonic.add([
  k.text("", { font: "mania", size: 18 }),
  k.color(255, 255, 0),
  k.anchor("center"),
  k.pos(30, -10),
]);</code></pre><p>Instead of calling the <code>add</code> function to create a game object, we can call the <code>add</code> method to create a child game object of an existing game object. Here, we create the <code>ringCollectUI</code> as a child of Sonic so that its position is relative to him.</p><h1>Implementing The Game Over Scene</h1><p>Finally, for our game over screen, let&#8217;s display the player current VS best score and allow them to try the game again if they wish to.</p><p>In the game over scene code in <code>main.ts</code>, add the following :</p><pre><code>k.scene("game-over", () =&gt; {
  let bestScore = k.getData("best-score") || 0;
  const currentScore = k.getData("current-score") || 0;

  if (currentScore &amp;&amp; bestScore &lt; currentScore) {
    k.setData("best-score", currentScore);
    bestScore = currentScore;
  }

  k.add([
    k.text("GAME OVER", { font: "mania", size: 64 }),
    k.anchor("center"),
    k.pos(k.center().x, k.center().y - 300),
  ]);
  k.add([
    k.text(`BEST SCORE : ${bestScore}`, {
      font: "mania",
      size: 32,
    }),
    k.anchor("center"),
    k.pos(k.center().x - 400, k.center().y - 200),
  ]);
  k.add([
    k.text(`CURRENT SCORE : ${currentScore}`, {
      font: "mania",
      size: 32,
    }),
    k.anchor("center"),
    k.pos(k.center().x + 400, k.center().y - 200),
  ]);

  k.wait(1, () =&gt; {
    k.add([
      k.text("Press Space/Click/Touch to Play Again", {
        font: "mania",
        size: 32,
      }),
      k.anchor("center"),
      k.pos(k.center().x, k.center().y + 200),
    ]);
    k.onButtonPress("jump", () =&gt; k.go("game"));
  });
});</code></pre><p>While it should be relatively easy to figure out what the code above does, I&#8217;d like to explain what we do here :</p><pre><code>let bestScore: number = k.getData("best-score") || 0;
const currentScore: number = k.getData("current-score") || 0;

if (bestScore &lt; currentScore) {
  k.setData("best-score", currentScore);
  bestScore = currentScore;
}</code></pre><p>Using KAPLAY&#8217;s <code>getData</code> function we&#8217;re able to get the data we previously set in local storage. However, when the player plays the game for the first time, they will not have a best score. That&#8217;s why we set <code>bestScore</code> to be 0 if <code>k.getData(&#8220;best-score&#8221;)</code> returns null which is possible. We do the same with currentScore.</p><p>Now, if you run the project, you should have the following game over screen appear after getting hit by an enemy. After, 1 sec you should be able to press the &#8220;Jump&#8221; button (in our case click or press the space key) to play the game again.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R0HA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R0HA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 424w, https://substackcdn.com/image/fetch/$s_!R0HA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 848w, https://substackcdn.com/image/fetch/$s_!R0HA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 1272w, https://substackcdn.com/image/fetch/$s_!R0HA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R0HA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:165591,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/163279066?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R0HA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 424w, https://substackcdn.com/image/fetch/$s_!R0HA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 848w, https://substackcdn.com/image/fetch/$s_!R0HA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 1272w, https://substackcdn.com/image/fetch/$s_!R0HA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1eabe9eb-6aae-4dd0-b8e4-6f38b6670ce4_3456x1728.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Deployment</h1><p>Assuming you want to be able to publish the game in web portals like itch.io, you can make a build by creating a vite.config.ts file at the root of your project&#8217;s folder and specifying the base as <code>./</code>.</p><pre><code>import { defineConfig } from "vite";

export default defineConfig({
  base: "./",
});</code></pre><p>Now, run the command <code>npm run build</code> and you should see a <code>dist</code> folder appear in your project files. Make sure your game still works by testing the build using <code>npm run preview</code>. Finally, once ready to publish, zip your <code>dist</code> folder and upload it to itch.io or to other web game platforms of your liking.</p><h1>Conclusion</h1><p>Hope you enjoyed learning how to make games in TypeScript with KAPLAY. If you&#8217;re interested in seeing more web developement and game development tutorials from me. I recommend subscribing to not miss out on future releases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>If you&#8217;re up for it, you can check out my beginner React.js tutorial.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;ac14acaa-0270-4cdc-98de-1e0d5e1bb802&quot;,&quot;caption&quot;:&quot;I recently published a beginner React.js tutorial on building a game search app as a series of free substack posts.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;My React.js Beginner Tutorial is Now Available as a Nicely Formatted PDF&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-04-26T20:48:35.865Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/my-reactjs-beginner-tutorial-is-now&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:162221561,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:4,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p>]]></content:encoded></item><item><title><![CDATA[How to Build a Sonic Themed Infinite Runner Game in TypeScript With KAPLAY - Part 1/2]]></title><description><![CDATA[In this tutorial, I&#8217;ll teach you how to make a simple Sonic themed infinite runner game in TypeScript using the KAPLAY game library.]]></description><link>https://jslegenddev.substack.com/p/learn-to-build-a-sonic-themed-infinite</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/learn-to-build-a-sonic-themed-infinite</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Tue, 06 May 2025 21:19:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fb2O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fb2O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fb2O!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fb2O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:92993,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162589188?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fb2O!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!fb2O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this tutorial, I&#8217;ll teach you how to make a simple Sonic themed infinite runner game in TypeScript using the KAPLAY game library. </p><p>You can play the game <a href="https://jslegend.itch.io/sonic-runner-typescript?secret=UUBtvauQRIzsMMnqks4w23Bmk">here</a>. The final source code for it is available <a href="https://github.com/JSLegendDev/Sonic-Runner-TypeScript/tree/main">here</a>.</p><p>In this game, you need to collect as many rings as possible. Jumping on enemies grants you a bonus of 10 rings. Continually jumping on enemies without touching the ground gives you a multiplier on each enemy. The first enemy grants you +10, the second +(10 x 2), the third +(10 x 3), etc&#8230;</p><p>The challenge comes from the game getting progressively faster, making it harder to avoid getting hit by enemies. I really recommend playing the game to have a better idea of what we&#8217;re going to build.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;bf2e64f7-ed8a-422e-87ac-8c7610634b52&quot;,&quot;duration&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Since this tutorial is published in parts, I recommend subscribing to not miss out on future releases!</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1>Prerequisites</h1><p>I expect that you have basic familiarity with TypeScript or are, at the very least, competent in JavaScript. We&#8217;re going to write the game in TypeScript, but it&#8217;s going to be very easy TypeScript code. Since we will rarely, if ever, need to write custom types. You should be familiar with Node.js and NPM and have them installed on your machine before following this tutorial. I do not expect you to know KAPLAY.</p><h1>Table of Contents</h1><ul><li><p><a href="https://jslegenddev.substack.com/i/162589188/what-is-kaplay">What is KAPLAY?</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/project-setup">Project Setup</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/initializing-the-canvas">Initializing The Canvas</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/creating-scenes">Creating Scenes</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/loading-assets">Loading Assets</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/implementing-an-infinite-scrolling-background">Implementing an Infinite Scrolling Background</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/162589188/what-are-game-objects-and-components-in-kaplay">What are Game Objects and Components in KAPLAY?</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/implementing-movement-with-an-onupdate-loop">Implementing Movement With an onUpdate Loop</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/implementing-an-infinite-scrolling-platform">Implementing an Infinite Scrolling Platform</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/implementing-the-sonic-game-object">Implementing The Sonic Game Object</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/162589188/how-animations-work-in-kaplay">How Animations Work in KAPLAY</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/how-hitboxes-are-created-in-kaplay">How Hitboxes Are Created in KAPLAY</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/how-to-add-methods-to-a-game-object">How to Add Methods to a Game Object</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/adding-sonic-to-our-game">Adding Sonic to Our Game</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/162589188/creating-a-static-body-as-the-floor">Creating a Static Body as The Floor</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/placing-sonic-in-the-game-scene">Placing Sonic in The Game Scene</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/162589188/conclusion">Conclusion</a></p></li></ul><h1>What is KAPLAY?</h1><p>KAPLAY is an open source library for making games in JavaScript and TypeScript. </p><p>Compared to the more popular Phaser JavaScript game framework, its API is a lot simpler making it easier for beginners to pick up. It also offers a lot of premade functionality out of the box. You spend therefore, less time reinventing the wheel.</p><p>For those familiar with the Godot game engine, you might be accustomed to the concept of nodes which acts as building blocks containing out of the box functionality. KAPLAY has a somewhat similar concept to nodes called components.</p><p>You can check the official KAPLAY website for more info <a href="https://kaplayjs.com/">here</a>.</p><h1>Project Setup</h1><p>Create a folder called <code>sonic-runner</code>. <code>cd</code> within using your terminal of choice and run :</p><pre><code>npm create vite@latest .</code></pre><p>This command will scaffold a TypeScript project within the existing folder using the popular build tool Vite.</p><p>Once the command runs, you&#8217;ll be presented with the following :</p><pre><code>&gt; npx
&gt; create-vite .

&#9474;
&#9670;  Select a framework:
&#9474;  &#9679; Vanilla
&#9474;  &#9675; Vue
&#9474;  &#9675; React
&#9474;  &#9675; Preact
&#9474;  &#9675; Lit
&#9474;  &#9675; Svelte
&#9474;  &#9675; Solid
&#9474;  &#9675; Qwik
&#9474;  &#9675; Angular
&#9474;  &#9675; Others
&#9492;</code></pre><p>Pick &#8220;Vanilla&#8221;. You&#8217;ll be presented with another menu to chose between JavaScript and TypeScript. Select TypeScript.</p><pre><code>&gt; npx
&gt; create-vite .

&#9474;
&#9671;  Select a framework:
&#9474;  Vanilla
&#9474;
&#9670;  Select a variant:
&#9474;  &#9679; TypeScript
&#9474;  &#9675; JavaScript
&#9492;</code></pre><p>Now let&#8217;s install the KAPLAY library. We will install the specific version called v3001.0.16. Run the following :</p><pre><code>npm install kaplay@3001.0.16</code></pre><p>This version is the latest stable version available at the time this tutorial was written. As long as you stick with this specific version, you shouldn&#8217;t have issues following this tutorial years into the future.</p><p>Once installed, take a look at your <code>package.json</code> file in your project&#8217;s folder. You&#8217;ll notice that KAPLAY was added to your dependencies.</p><pre><code>{
  "name": "sonic-runner-ts",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc &amp;&amp; vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "typescript": "~5.7.2",
    "vite": "^6.3.1"
  },
  "dependencies": {
    "kaplay": "^3001.0.16"
  }
}</code></pre><p>When we used the <code>npm create</code> command, Vite created a bunch of files. Make sure that you have the following project structure. Remove everything within the <code>src</code> folder as we&#8217;re going to write everything from scratch.</p><pre><code>- node_modules // should be git ignored in .gitignore
- public
- src
- .gitignore
- index.html
- package-lock.json
- package.json
- tsconfig.json</code></pre><p>Once everything is removed in <code>src</code>, create two files within. One named <code>kaplayCtx.ts</code> and the other <code>main.ts</code>. You should have the following project structure.</p><pre><code>- node_modules // should be git ignored in .gitignore
- public
- src
   |- kaplayCtx.ts
   |- main.ts
- .gitignore
- index.html
- package-lock.json
- package.json
- tsconfig.json</code></pre><p>Finally, the last step of our setup is to bring in our game assets. Copy the contents of the <code>public</code> folder provided in the game&#8217;s final source code available on GitHub. The repo can be accessed by clicking <a href="https://github.com/JSLegendDev/Sonic-Runner-TypeScript/tree/main/public">here</a>.</p><p>After having done so, you should have the following files in your public folder.</p><pre><code>- public
   |- fonts
        |- mania.ttf
   |- graphics
        |- chemical-bg.png
        |- motobug.png
        |- platforms.png
        |- ring.png
        |- sonic.png
   |- sounds
        |- Destroy.wav
        |- Hurt.wav
        |- HyperRing.wav
        |- Jump.wav
        |- Ring.wav
   |- vite.svg</code></pre><h1>Initializing The Canvas</h1><p>Now that our setup is complete, we can initialize an HTML canvas in which the game will be rendered. This will be done through KAPLAY by initializing our context which will also allow us to use KAPLAY functions.</p><p>In <code>kaplayCtx.ts</code>, write the following code :</p><pre><code>import kaplay from "kaplay";

const k = kaplay({
  width: 1280,
  height: 720,
  letterbox: true,
  global: false,
  buttons: {
    jump: {
      keyboard: ["space"],
      mouse: "left",
    },
  },
  touchToMouse: true,
  debug: true, // set it to false when deploying the game
  pixelDensity: window.devicePixelRatio,
});

export default k;</code></pre><p>Our context is created by calling the <code>kaplay</code> function and passing an object containing various options we want to configure for our game. We first set the width and the height of the canvas.</p><p>We set the <code>letterbox</code> property to <code>true</code>, allowing our game canvas to scale regardless of the screen size while still retaining its aspect ratio.</p><p>We set the <code>global</code> property to <code>false</code> so that KAPLAY functions can only be used via the constant <code>k</code>. This will lead to more legible code since you&#8217;ll be able to easily detect if a KAPLAY function is in use by noticing <code>k.nameOfTheFunction</code>.</p><p>We pass an object to the <code>buttons</code> property to define the <code>jump</code> action of our game. Since, we want to make the game playable on both desktop and mobile, we set the <code>jump</code> custom action to be mapped to the space key or the left mouse click. <code>jump</code> is an arbitrary name and you could have decided on something different. You can also add an arbitrary amount of actions depending on your game&#8217;s needs by adding a new property. For example :</p><pre><code>buttons: {
    jump: {
      keyboard: ["space"],
      mouse: "left",
    },
    moveLeft: {
      keyboard: ["left"],
    },
   /// etc...
},</code></pre><p>The <code>touchToMouse</code> property is used to convert touch input to mouse clicks so that the game is playable on mobile.</p><p>The <code>debug</code> property is used to toggle on/off KAPLAY&#8217;s debug mode. This mode can be accessed on the webpage the game is running on by pressing the <code>f1</code> key or the <code>fn+f1</code> keys on a Mac. It will display an fps counter along with the game objects&#8217; hitboxes which makes debugging easier. We will use it later in this tutorial. Here, we&#8217;re setting <code>debug</code> to <code>true</code> so that the debug mode is accessible. </p><p>One thing to note is that by default, debug mode is available so you don&#8217;t need use this property initially. The reason I included it here, is that when you ultimately deploy your game, you might not want your players to access this mode. Therefore, setting this property to <code>false</code> before deployment is a good idea.</p><p>Finally, to make the game display sharply regardless of the screen we set the <code>pixelDensity</code> property to correspond to the pixel ratio of the current screen. You&#8217;ll notice that not using this property will make the game render slightly blurry on certain screens.</p><p>We then export the constant <code>k</code> containing a reference to the context, so that we can use it elsewhere in our code.</p><h1>Creating Scenes</h1><p>Our game will have two scenes. The first, is where the game takes place while the second, is a game over screen displaying the player&#8217;s score.</p><p>In KAPLAY, you can create mulitple scenes using the <code>scene</code> function. In our <code>main.ts</code> file let&#8217;s import the KAPLAY context and create our scenes.</p><pre><code>import k from "./kaplayCtx";

k.scene("game", () =&gt; {
  // TODO
});

k.scene("game-over", () =&gt; {
  // TODO
});

k.go("game");
</code></pre><p>Here we created two scenes. The <code>scene</code> function takes, as the first param, the name you want to use to refer to that scene. The second param is a function that will run when the game enters that scene.</p><p>Finally, we also need to use the <code>go</code> function to tell KAPLAY which scene to go to when the game starts. This function is also used to navigate to other scenes when called within a specific scene.</p><p>We will write the logic for each scene later, now let&#8217;s run our project to see if we have correctly initialized our canvas.</p><p>In your terminal, run the command :</p><pre><code>npm run dev</code></pre><pre><code>VITE v6.3.4  ready in 229 ms

  &#10140;  Local:   http://localhost:5173/
  &#10140;  Network: use --host to expose
  &#10140;  press h + enter to show help</code></pre><p>When you open the localhost address in your browser, if everything went well, you should see a checkered canvas like shown below.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lJUs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lJUs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 424w, https://substackcdn.com/image/fetch/$s_!lJUs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 848w, https://substackcdn.com/image/fetch/$s_!lJUs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 1272w, https://substackcdn.com/image/fetch/$s_!lJUs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lJUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png" width="1456" height="811" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9113d98-711c-49fd-826e-390a39deae90_2186x1218.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:811,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:76674,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162589188?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lJUs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 424w, https://substackcdn.com/image/fetch/$s_!lJUs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 848w, https://substackcdn.com/image/fetch/$s_!lJUs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 1272w, https://substackcdn.com/image/fetch/$s_!lJUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9113d98-711c-49fd-826e-390a39deae90_2186x1218.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you try to resize your window, you&#8217;ll also notice that the canvas seems to always preserve its aspect ratio. This is exactly what we want so that the game is playable on any device.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;7c4b11f9-a387-460e-988a-3c9145a73eb4&quot;,&quot;duration&quot;:null}"></div><h1>Loading Assets</h1><p>In <code>main.ts</code> let&#8217;s load the assets we need for the background and the platforms. This will be a good introduction to how assets are loaded in KAPLAY. Add the following code :</p><pre><code>import k from "./kaplayCtx";

k.loadSprite("chemical-bg", "graphics/chemical-bg.png");
k.loadSprite("platforms", "graphics/platforms.png");

//... rest of the code omitted for clarity</code></pre><p>Here we use KAPLAY&#8217;s <code>loadSprite</code> function. It takes, as the first param, a string which corresponds to the name you want to use to refer to that specific sprite/image. For the second param, the function expects the path to the asset.</p><p>Note that we don&#8217;t need to add the <code>public</code> folder to the path since Vite makes sure to make whatever is in that folder accessible as if it was placed at the root of the project.</p><h1>Implementing an Infinite Scrolling Background</h1><p>In our game, the city background must scroll indefinitely. Let&#8217;s implement this in our game scene. Add the following code in <code>main.ts</code> :</p><pre><code>k.scene("game", () =&gt; {
  const bgPieceWidth = 2880;
  const bgPieces = [
    k.add([
      k.sprite("chemical-bg"),
      k.pos(0, 0), 
      k.opacity(0.8), 
      k.scale(1.5)
    ]),
    k.add([
      k.sprite("chemical-bg"),
      k.pos(bgPieceWidth, 0),
      k.opacity(0.8),
      k.scale(1.5),
    ]),
  ];

  k.onUpdate(() =&gt; {
    if (bgPieces[1].pos.x &lt; 0) {
      bgPieces[0].moveTo(bgPieces[1].pos.x + bgPieceWidth * 2, 0);
      const frontBgPiece = bgPieces.shift();
      // so typescript shuts up
      if (frontBgPiece) bgPieces.push(frontBgPiece);
    }

    bgPieces[0].move(-100, 0);
    bgPieces[1].moveTo(bgPieces[0].pos.x + bgPieceWidth * 2, 0);
  });
});</code></pre><p>Let&#8217;s break it down.</p><pre><code>const bgPieceWidth = 2880;
const bgPieces = [
  k.add([
    k.sprite("chemical-bg"),
    k.pos(0, 0), 
    k.opacity(0.8), 
    k.scale(1.5)
  ]),
  k.add([
    k.sprite("chemical-bg"),
    k.pos(bgPieceWidth, 0),
    k.opacity(0.8),
    k.scale(1.5),
  ]),
];</code></pre><p>To achieve an infinite scrolling background, we need need to display the same background image twice. The trick is to reposition the first image behind the second one once it leaves the screen. The second image becomes the first and the first becomes the second. The same logic is repeated indefinitely.</p><p>I previously wrote a post specific to infinite backgrounds with easy to understand visuals. You can check it out below.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;6a138b9b-59b5-4ebd-bd82-28fabe6a6266&quot;,&quot;caption&quot;:&quot;Infinite parallax scrolling backgrounds are nice to look at because they add depth to a 2D scene. In this tutorial, I&#8217;ll teach you how to implement them in JavaScript with the Kaplay library.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How to Implement Infinite Parallax Scrolling Backgrounds in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-08-16T22:19:21.621Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7feb4465-91ad-407f-9fb1-f9aea072aa21_1906x1065.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-to-implement-infinite-parallax&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:147752193,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:10,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>In the code above, we first set a constant holding the width of the background image when scaled by 1.5, which corresponds to 1920 x 1.5 = 2880 since the original image has a width of 1920. This is needed so that we can position the second copy right after the first one without leaving any gaps. We will display the background at 1.5 its original scale so that it looks nice within our game.</p><p>We then set an array containing the two copies. Each copy is created using KAPLAY&#8217;s <code>add</code> function which creates a game object, one of KAPLAY&#8217;s major concepts.</p><h2>What are Game Objects and Components in KAPLAY?</h2><p>A game object represents an entity rendered on the canvas and is composed of components. These components determine various methods and features that it has access to. KAPLAY provides these components out of the box but you can also create custom ones if you wish to (We won&#8217;t in this tutorial).</p><pre><code>k.add([
  k.sprite("chemical-bg"),
  k.pos(0, 0), 
  k.opacity(0.8), 
  k.scale(1.5)
]),</code></pre><p>The <code>add</code> function used to create game objects, takes an array of KAPLAY components. Let&#8217;s break down the 4 we use here :</p><ul><li><p><code>sprite</code> comp : Renders a game object as a visible sprite/image.</p></li><li><p><code>pos</code> comp : Sets the position of a game object on the canvas using <code>(x, y) </code>coordinates. In KAPLAY and in many other game libraries, the <code>x</code> coordinate increases the further you go right and the <code>y</code> coordinate increases the further you go down. The origin <code>(X = 0, Y = 0)</code> is set at the top-left corner of the canvas.</p></li><li><p><code>opacity</code> comp : Sets the opacity of a game object using a value between 0 and 1. The closer the value is to 0 the more it becomes transparent.</p></li><li><p><code>scale</code> comp : Sets the scale of a game object. Is often used alongside the <code>sprite</code> comp to change the sprite/image&#8217;s size on the canvas.</p></li></ul><pre><code>k.add([
  k.sprite("chemical-bg"),
  k.pos(bgPieceWidth, 0), 
  k.opacity(0.8), 
  k.scale(1.5)
]),</code></pre><p>As you can see, the second copy uses the same components. However, we set the position differently so that it is rendered right behind the first one without leaving any gaps.</p><h2>Implementing Movement With an onUpdate Loop</h2><p>While creating game objects using the <code>add</code> function will render them on the canvas, no movement will occur without an update loop. </p><p>The update loop runs every frame and is used to update our game&#8217;s logic. If your game runs at 60fps, the loop will run 60 times per second. In KAPLAY, we use the <code>onUpdate</code> function to set our update loop.</p><pre><code>k.onUpdate(() =&gt; {
  if (bgPieces[1].pos.x &lt; 0) {
    bgPieces[0].moveTo(bgPieces[1].pos.x + bgPieceWidth, 0);
    const frontBgPiece = bgPieces.shift();
    // so typescript shuts up
    if (frontBgPiece) bgPieces.push(frontBgPiece);
  }

  bgPieces[0].move(-100, 0);
  bgPieces[1].moveTo(bgPieces[0].pos.x + bgPieceWidth, 0);
});</code></pre><p>We first check if the background image second copy&#8217;s <code>x</code> position is &lt; 0. In that case, we can assume that the first copy is offscreen and therefore, we can safely attempt to move it behind the second copy. This is achieved by setting its position using the <code>moveTo</code> KAPLAY method made available due to using the <code>pos</code> comp when the game object was defined.</p><p>Using the <code>shift</code> array method we delete the first element from the array and return it. Finally, we push it back to the array so that the first copy is now placed as the second element of the array making it become the second copy. Another if statement is used to make sure the result of <code>shift()</code> is not <code>undefined</code> before pushing it to the array again. Otherwise, TypeScript will complain and rightly so.</p><p>Regardless of all of the above, on each iteration of the loop, we move both copies to the left by using the <code>move</code> KAPLAY method on the first copy and the <code>moveTo</code> method on the second copy to follow the first copy. Both these methods exists because we used the <code>pos</code> component when creating each respective game object.</p><p>The <code>move</code> method takes two params. The first is for setting the velocity of the game object on the <code>x</code> axis while the second sets the velocity on the <code>y</code> axis. By setting the <code>x</code> velocity to -100 and the <code>y</code> velocity to 0, we&#8217;re moving only to the left at a velocity of 100 pixels/second.</p><p>If you&#8217;re accustomed to other game engines/frameworks, you might have noticed the absence of <code>deltaTime</code> (corresponds to the time elapsed since the last frame in our game loop) being used to make our movement frame rate independent. This is because the <code>move</code> method takes care of it under the hood.</p><p>Now, looking at your browser tab (Assuming that you have localhost running) you should see the city background scrolling indefinitely.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;14183086-e87f-4aa5-83a1-7d396281442e&quot;,&quot;duration&quot;:null}"></div><p>To make the background less distracting during gameplay, I opted to set its opacity to 0.8 instead of 1. Now, if you go back to your <code>kaplayCtx.ts</code>, we can set the background of the whole webpage to be black. This will result in the city background becoming darker and therefore less distracting during gameplay. </p><pre><code>const k = kaplay({
  width: 1280,
  height: 720,
  letterbox: true,
  global: false,
  buttons: {
    jump: {
      keyboard: ["space"],
      mouse: "left",
    },
  },
  touchToMouse: true,
  debug: true,
  pixelDensity: window.devicePixelRatio,
  background: [0, 0, 0], // &lt;--- new line
});</code></pre><p>We provide to the <code>background</code> property an array of three elements each representing one of the RGB color channels. Each element&#8217;s value can vary between 0 to 255. This is what&#8217;s used to represent color. If you check your browser tab, you should notice that the city background is now darker.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yd4R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yd4R!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 424w, https://substackcdn.com/image/fetch/$s_!yd4R!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 848w, https://substackcdn.com/image/fetch/$s_!yd4R!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 1272w, https://substackcdn.com/image/fetch/$s_!yd4R!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yd4R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:146913,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162589188?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yd4R!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 424w, https://substackcdn.com/image/fetch/$s_!yd4R!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 848w, https://substackcdn.com/image/fetch/$s_!yd4R!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 1272w, https://substackcdn.com/image/fetch/$s_!yd4R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91fa7273-4bee-4c3b-b6fb-51effea5ffcf_1796x1006.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Implementing an Infinite Scrolling Platform</h1><p>Our game is based on one big illusion. Sonic isn&#8217;t actually running. The platform on which Sonic stands isn&#8217;t actually moving. The only things moving are the background and the platform&#8217;s image which are made to scroll indefinitely.</p><p>If we were to remove all of the game&#8217;s graphics, we would be left with a game object representing the player that can jump up and land on a static floor.</p><p>Our goal at the moment, is to implement infinite scrolling platforms the same way we did for the city background. The only difference is that the scrolling speed will increase the further the game progresses.</p><p>In <code>main.ts</code> add the following code :</p><pre><code>// previous code omitted for clarity
k.scene("game", () =&gt; {
  let gameSpeed = 100;
  k.loop(1, () =&gt; {
    gameSpeed += 50;
  });

  // code for the city background omitted for clarity

  const platformWidth = 2560;
  const platforms = [
    k.add([k.sprite("platforms"), k.pos(0, 450), k.scale(2)]),
    k.add([k.sprite("platforms"), k.pos(2560, 450), k.scale(2)]),
  ];

  k.onUpdate(() =&gt; {
    // city background update logic omitted for clarity

    if (platforms[1].pos.x &lt; 0) {
      platforms[0].moveTo(
        platforms[1].pos.x + platformWidth,
        platforms[1].pos.y
      );
      const frontPlatform = platforms.shift();
      if (frontPlatform) platforms.push(frontPlatform);
    }

    platforms[0].move(-gameSpeed, 0);
    platforms[1].moveTo(platforms[0].pos.x + platformWidth, platforms[0].pos.y);
  });
});</code></pre><p>We first set a <code>gameSpeed</code> variable that is incremented every second using KAPLAY&#8217;s <code>loop</code> function. This is similar to JavaScript/TypeScript&#8217;s <code>setInterval</code> function. The first param is for setting the time between each call and the second param is the function that will be called.</p><p>Similarly to the city background, we create two copies of the platforms&#8217; image so that we can achieve infinite scrolling. <code>platformWidth</code> corresponds to the final width of the platforms&#8217; image after scaling it twice. This is because we need to make the platforms bigger in our game to make it look good. We use this constant to know where to place the second copy behind the first one without leaving any gaps.</p><p>The update logic is also similar, the only difference this time is that the <code>move</code> function takes the <code>gameSpeed</code> variable as the <code>x</code> velocity allowing for it to increase the platforms speed through time.</p><p>If you run the project, you should notice the platforms scrolling slowly and then gradually increase in speed as time passes.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;40984a48-4287-45e9-b9c2-7d87db9ebef6&quot;,&quot;duration&quot;:null}"></div><h1>Implementing The Sonic Game Object</h1><p>Now that we have our game scene mostly done, we&#8217;re ready to work on adding Sonic to our game.</p><p>Sonic, the rings he must collect and the enemies are all entities. Let&#8217;s create a file named <code>entities.ts</code> in the <code>src</code> folder. Then, let&#8217;s create a game object that will hold Sonic&#8217;s logic.</p><pre><code>src
|- kaplayCtx.ts
|- main.ts
|- entities.ts</code></pre><p>In <code>entities.ts</code> add the following code.</p><pre><code>import k from "./kaplayCtx";
import { Vec2, GameObj } from "kaplay";

export function makeSonic(position: Vec2) {
  return k.add([
    k.sprite("sonic", { anim: "run" }),
    k.scale(3),
    k.area(),
    k.anchor("center"),
    k.pos(position),
    k.body({ jumpForce: 1700 }),
    {
      setControls(this: GameObj) {
        k.onButtonPress("jump", () =&gt; {
          if (this.isGrounded()) {
            this.play("jump");
            this.jump();
            k.play("jump", { volume: 0.5 });
          }
        });
      },
      setEvents(this: GameObj) {
        this.onGround(() =&gt; {
          this.play("run");
        });
      },
    },
  ]);
}</code></pre><p>Let&#8217;s break it down.</p><pre><code>import k from "./kaplayCtx";
import { Vec2, GameObj } from "kaplay";</code></pre><p>We first import the KAPLAY context to be able to use the library&#8217;s functions in this file. We then import 2 types. <code>Vec2</code> and <code>GameObj</code>. As you can see, KAPLAY offers TypeScript types you can use, making development in TypeScript smoother.</p><pre><code>export function makeSonic(position: Vec2) {
  return k.add([
    k.sprite("sonic", { anim: "run" }),
    k.scale(3),
    k.area(),
    k.anchor("center"),
    k.pos(position),
    k.body({ jumpForce: 1700 }),
    {
      setControls(this: GameObj) {
        k.onButtonPress("jump", () =&gt; {
          if (this.isGrounded()) {
            this.play("jump");
            this.jump();
            k.play("jump", { volume: 0.5 });
          }
        });
      },
      setEvents(this: GameObj) {
        this.onGround(() =&gt; {
          this.play("run");
        });
      },
    },
  ]);
}</code></pre><p>We then create a function who&#8217;s sole purpose is to create and return our Sonic game object. You can think of it as our constructor. We enable the function&#8217;s caller to set the position of the game object before it&#8217;s created. Positions in KAPLAY are <em>Vec2</em>s (stands for vector 2). It&#8217;s a data structure offered by KAPLAY which has two elements, <code>x</code> and <code>y</code>.</p><p>We use the <code>sprite</code>, <code>scale</code>, <code>area</code>, <code>anchor</code>, <code>pos</code> and <code>body</code> components to compose our game object.</p><p>Note that generally the <code>pos</code> comp can be passed the <code>x</code> and <code>y</code> coordinates as two distinct params or be passed a <code>Vec2</code> containing both at once. In our code above, we opted for the latter while with our background and platforms game objects, we opted for the former.</p><p>In the case of the <code>sprite</code> comp, we not only display a <code>sprite</code> but set an animation. You might be wondering where does this &#8220;sonic&#8221; sprite and &#8220;run&#8221; anim comes from since we haven&#8217;t loaded anything like that at the moment? Indeed, we put the cart before the horse in this case. Let&#8217;s add the asset loading logic in <code>main.ts</code>. It will be a great opportunity to explain how animations work in KAPLAY.</p><h2>How Animations Work in KAPLAY</h2><p>In main.ts, add the following :</p><pre><code>// place this behind last loadSprite call at the top of the file.
k.loadSprite("sonic", "graphics/sonic.png", {
  sliceX: 8,
  sliceY: 2,
  anims: {
    run: { from: 0, to: 7, loop: true, speed: 30 },
    jump: { from: 8, to: 15, loop: true, speed: 100 },
  },
});</code></pre><p>The <code>loadSprite</code> function can take an object as an optional third param used for telling KAPLAY how to split an image into individual frames. When an image contains multiple sprites, it is often referred to as a spritesheet.</p><p>In our case, Sonic&#8217;s spritesheet looks like this.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P5HT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P5HT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 424w, https://substackcdn.com/image/fetch/$s_!P5HT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 848w, https://substackcdn.com/image/fetch/$s_!P5HT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 1272w, https://substackcdn.com/image/fetch/$s_!P5HT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P5HT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png" width="1456" height="519" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:519,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51944,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162589188?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!P5HT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 424w, https://substackcdn.com/image/fetch/$s_!P5HT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 848w, https://substackcdn.com/image/fetch/$s_!P5HT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 1272w, https://substackcdn.com/image/fetch/$s_!P5HT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fc3923f-e725-49a4-9cea-ae118f5ff8a0_1536x548.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We have a total of 16 frames which can be viewed as a grid of 2 rows and 8 columns. The object passed to <code>loadSprite</code> must first tell KAPLAY how to slice the image using the <code>sliceX</code> and <code>sliceY</code> properties. <code>sliceX</code> corresponds to the number of columns while <code>sliceY</code> corresponds to the number of rows.</p><p>Then, an <code>anims</code> object can be configured to set the animations we need and tell KAPLAY which animation is composed of which frames in our spritesheet.</p><pre><code>run: { from: 0, to: 7, loop: true, speed: 30 },</code></pre><p>If we take a look at the definition for the <code>run</code> animation, the <code>from</code> property holds the number of the starting frame of the anim while the <code>to</code> property holds the number of the last frame.</p><p>When KAPLAY slices the image into individual frames, it assigns a number to each of them starting from 0 and counting from left to right, top to bottom.</p><p>The <code>loop</code> property set to <code>true</code> makes the anim run indefinitely unless manually stopped while the <code>speed</code> property is used to set its frame rate.</p><p>The frame rate values used in the code above are arbitrary. I came up with these values based on what looked good during gameplay after tweaking values.</p><p>For more details on how animations work in KAPLAY check the post below.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;c95e9070-ba93-490a-93be-391d2790e9de&quot;,&quot;caption&quot;:&quot;This posts assumes basic knowledge of the KAPLAY library, a library for making games in JavaScript. If you want to learn it, check out my YouTube channel for project based tutorials here and/or check out the library&#8217;s docs here.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How Animations Work in KAPLAY&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-02-05T16:06:16.505Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cffdcef-dc40-4f75-86d4-fdcfb50ce866_2262x1398.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-animations-work-in-kaplay&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:156530360,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><pre><code>k.sprite("sonic", { anim: "run" }),
k.scale(3),
k.area(),
k.anchor("center"),
k.pos(position),
k.body({ jumpForce: 1700 }),</code></pre><p>Looking back at the components used for creating the Sonic game object in <code>entities.ts</code>, you&#8217;ll notice that the <code>sprite</code> comp takes as the first param the name of the image you want to display while the second param can be used to set the default running animation.</p><p>We use the <code>scale</code> comp to increase Sonic&#8217;s sprite size by 3. Which is what looked good during gameplay.</p><h2>How Hitboxes Are Created in KAPLAY</h2><p>The <code>area</code> comp is very useful because it allows us to easily create a hitbox for our player enabling us to call methods like <code>onCollide</code>, <code>onCollideEnd</code>, etc&#8230; useful for dealing with collisions. </p><p>In KAPLAY, just passing the <code>area</code> comp to the components array is enough to create a box surronding the player&#8217;s sprite if the <code>sprite</code> comp is also used. You might need in some cases to configure the position, width and height of the hitbox but in our game, this isn&#8217;t necessary at the moment. We will tackle this later.</p><p>Now that we have used the <code>area</code> comp, we can use the <code>anchor</code> comp to set the origin of our game object. By default, game objects are rendered starting from their top-left corner. This might not be very intuitive for game objects representing characters, that&#8217;s why I like using the <code>anchor</code> comp in those cases to set the center as the origin instead.</p><p>Finally, the <code>body</code> comp is used to give the game object a physics body allowing them to be affected by gravity. Using the <code>body</code> comp is crucial since it will give us access to the <code>jump</code> method which allows the game object to jump. In our <code>body</code> comp usage we set the game object&#8217;s jump force to be 1700. This is an arbitrary value which I arrived at after extensive testing during gameplay.</p><h2>How to Add Methods to a Game Object</h2><p>I explained earlier that a game object is composed of components. However, the <code>add</code> function which takes in an array of components can also be passed, to that same array, a JS object that you can use to set custom properties and methods for the resulting game object.</p><pre><code>{
  setControls(this: GameObj) {
    k.onButtonPress("jump", () =&gt; {
      if (this.isGrounded()) {
        this.play("jump");
        this.jump();
        k.play("jump", { volume: 0.5 });
      }
    });
  },
  setEvents(this: GameObj) {
    this.onGround(() =&gt; {
      this.play("run");
    });
  },
},</code></pre><p>We have an object containing two custom methods. The first one is used to set the player controls while the second is used to set what happens when Sonic hits the ground after jumping.</p><p>In these methods, we can use the <code>this</code> keyword, to access the sonic game object. In TypeScript, we need to specify what <code>this</code> is. This is done by creating a <code>this</code> param and typing it using the <code>GameObj</code> type definition imported from KAPLAY. When TypeScript is compiled to vanilla JS, this param will be removed.</p><pre><code>setControls(this: GameObj) {
  k.onButtonPress("jump", () =&gt; {
    if (this.isGrounded()) {
      this.play("jump");
      this.jump();
      k.play("jump", { volume: 0.5 });
    }
  });
},</code></pre><p>Taking a closer look at our custom <code>setControls</code> function, we can see that we call an <code>onButtonPress</code> KAPLAY function. As the name implies, this is used for handling when a specific button is pressed. It takes, as the first param, the name of the &#8220;button&#8221; you want to listen on. The second param is the function that will fire when that &#8220;button&#8221; is clicked.</p><p>If you remember, the &#8220;jump&#8221; &#8220;button&#8221; was defined in our KAPLAY context. This is where you need to define all the &#8220;buttons&#8221; you need for your game.</p><pre><code>const k = kaplay({
  //... omitted for clarity
  buttons: {
    jump: {
      keyboard: ["space"],
      mouse: "left",
    },
  },
//... rest of the code omitted for clarity</code></pre><pre><code>if (this.isGrounded()) {
  this.play("jump");
  this.jump();
  k.play("jump", { volume: 0.5 });
}</code></pre><p>Within our <code>onButtonPress</code> we check if the player is on top of a static game object using the <code>isGrounded</code> method which is available because Sonic has a <code>body</code> comp. At the moment, we haven&#8217;t defined any static game objects but we will create one later.</p><p>When the player is indeed grounded, we can set the jump animation to play, make the player jump with the <code>jump</code> method and finally play a jump sound using KAPLAY&#8217;s <code>play</code> function which is used to play audio.</p><p>Unfortunately, it&#8217;s very easy to confuse the <code>play</code> method which is used on a game object for playing animations VS the <code>play</code> function which is used for sound. </p><p>By the way, we need to load the jump sound in <code>main.ts</code> for it to work here. Add the following :</p><pre><code>// place this behind last loadSprite call at the top of the file.
k.loadSound("jump", "sounds/Jump.wav");</code></pre><p>The reason why we make sure the player is grounded before allowing any jump logic to run is so that the player can&#8217;t jump indefinitely making them able to fly away.</p><pre><code>setEvents(this: GameObj) {
 this.onGround(() =&gt; {
   this.play("run");
 });
},</code></pre><p>Our second custom method is used to set event logic. In KAPLAY, when a game object has the <code>body</code> component, you have access to the <code>onGround</code> event method. That method runs a function every time the player hits the ground (meaning collides on top of a static game object).</p><p>When the player jumps, the jump animation plays indefinitely. We want to switch back to the run animation as soon as the player lands back. The code above achieves this.</p><p>To reiterate, <code>setControls</code> and <code>setEvents</code> are custom methods we decided to create on our Sonic game object. They could have been named differently. We could have decided to only have one method that does all of the required logic. This is up to you when making your own KAPLAY games.</p><h1>Adding Sonic to Our Game</h1><p>Let&#8217;s create a floor on which to place Sonic.</p><h2>Creating a Static Body as The Floor</h2><p>Add the following code in main.ts :</p><pre><code>// previous code omitted for clarity
// const platforms = [...]
  
// static body for the platforms
k.add([
  k.rect(1280, 200),
  k.opacity(0),
  k.pos(0, 641),
  k.area(),
  k.body({ isStatic: true }),
]);

// k.onUpdate(() =&gt; ...)
// rest of the code omitted for clarity</code></pre><p>To create a static game object, you simply need to create a game object with an <code>area</code> and a <code>body</code> component. Within the <code>body</code> comp, set the <code>isStatic</code> property to <code>true</code>.</p><p>We use the <code>rect</code> comp to create a rectangular shape and we used the <code>opacity</code> comp set to 0 to make it invisible. We also don&#8217;t assign the game object to a constant since we don&#8217;t need a reference.</p><p>If you look at the webpage where the game is running (assuming your project is running), you will see that nothing has changed. However, if you activate the debug mode by pressing the <code>f1</code> key (or <code>fn+f1</code> keys on a Mac), you&#8217;ll see an outline representing the hitbox game object we just created.</p><p>For fun, you can try to put the opacity back to 1 and you&#8217;ll see a white rectangle at the bottom of the canvas.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CMHI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CMHI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 424w, https://substackcdn.com/image/fetch/$s_!CMHI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 848w, https://substackcdn.com/image/fetch/$s_!CMHI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 1272w, https://substackcdn.com/image/fetch/$s_!CMHI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CMHI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png" width="1456" height="863" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:863,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:324569,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162589188?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CMHI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 424w, https://substackcdn.com/image/fetch/$s_!CMHI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 848w, https://substackcdn.com/image/fetch/$s_!CMHI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 1272w, https://substackcdn.com/image/fetch/$s_!CMHI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe144d749-dfa2-4487-b43d-fa92416abb02_1646x976.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Placing Sonic in The Game Scene</h2><p>In <code>main.ts</code>, add the following :</p><pre><code>// previous import statements omitted for clarity
import { makeSonic } from "./entities";

// ...

k.scene("game", () =&gt; {
  // ... platforms game object definition

  const sonic = makeSonic(k.vec2(100, 100));
  sonic.setControls();
  sonic.setEvents();
  
  // ... static body for the platforms game object definition
});

// ...</code></pre><p>We call <code>makeSonic</code> and set a position using the <code>vec2</code> KAPLAY function, assign the result to a constant called Sonic which enables us to call our custom methods <code>setControls</code> and <code>setEvents</code>, enabling our core player game logic. Everything should work now, but wait! Why is Sonic hanging in the air?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IjFz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IjFz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 424w, https://substackcdn.com/image/fetch/$s_!IjFz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 848w, https://substackcdn.com/image/fetch/$s_!IjFz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 1272w, https://substackcdn.com/image/fetch/$s_!IjFz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IjFz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png" width="1456" height="844" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:844,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:176977,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162589188?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IjFz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 424w, https://substackcdn.com/image/fetch/$s_!IjFz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 848w, https://substackcdn.com/image/fetch/$s_!IjFz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 1272w, https://substackcdn.com/image/fetch/$s_!IjFz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40d8031a-df8d-4fbc-89d5-34eb4c26f65a_1922x1114.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That&#8217;s because we haven&#8217;t set our game&#8217;s gravity. In KAPLAY, gravity is set per scene using the <code>setGravity</code> function and passing to it a number determining how strong it will be.</p><p>Add the following code in our &#8220;game&#8221; scene definition :</p><pre><code>k.scene("game", () =&gt; {
  k.setGravity(3100);
  // ...
});
// ...</code></pre><p>I came up with 3100 after trial and error. You need to test during gameplay to see if the jump is floaty or not and that&#8217;s how I came up with this value.</p><p>Now, if you run the project. You should see Sonic initially fall on the platforms and immediately start running. You can now jump and when you do, Sonic should curl into a ball and you should hear a jump sound. However, as soon as he hits the ground, the run animation should be the one playing.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;9a59a07c-008f-4f8a-8d27-f3c585885d28&quot;,&quot;duration&quot;:null}"></div><h1>Conclusion</h1><p>Up to this point we covered a lot of KAPLAY specific concepts and we made quite a bit of progress on our Sonic game. However, there&#8217;s still work to do. In the next part of the tutorial we will :</p><ul><li><p>Add rings for Sonic to collect.</p></li><li><p>Implement a scoring system.</p></li><li><p>Add Motobug enemies that Sonic can jump on for more rings.</p></li><li><p>Implement a combo system allowing Sonic to earn extra rings by continuously jumping on Motobugs before hitting the ground again.</p></li></ul><p>Update : Part 2 is now available!</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;f3e039f9-b243-431e-a250-f72d8592a319&quot;,&quot;caption&quot;:&quot;In the previous part of the tutorial, we finished implementing Sonic&#8217;s movement and jumping logic. We also implemented platforms and background infinite scrolling.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How to Build a Sonic Themed Infinite Runner Game in TypeScript With KAPLAY - Part 2/2&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-05-16T18:59:55.489Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05bfeab4-aedf-4f33-9b6d-eb2dd97bc015_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/how-to-build-a-sonic-themed-infinite&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:163279066,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[Where to Find Inspiration For Making Small Games]]></title><description><![CDATA[In my opinion, the most approachable way to get into game development is to make small games.]]></description><link>https://jslegenddev.substack.com/p/where-to-find-inspiration-for-making</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/where-to-find-inspiration-for-making</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Mon, 28 Apr 2025 21:34:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!3qsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3qsl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3qsl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 424w, https://substackcdn.com/image/fetch/$s_!3qsl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 848w, https://substackcdn.com/image/fetch/$s_!3qsl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 1272w, https://substackcdn.com/image/fetch/$s_!3qsl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3qsl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png" width="1108" height="449" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:449,&quot;width&quot;:1108,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!3qsl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 424w, https://substackcdn.com/image/fetch/$s_!3qsl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 848w, https://substackcdn.com/image/fetch/$s_!3qsl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 1272w, https://substackcdn.com/image/fetch/$s_!3qsl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bf21444-fa87-4b9d-a632-df306ec2dccf_1108x449.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In my opinion, the most approachable way to get into game development is to make small games. However, making the usual Pong or Flappy bird clone is boring.</p><p>In this post, I&#8217;ll share where I find inspiration for building interesting small games.</p><h1>Playing PICO-8 Games</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LojD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LojD!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 424w, https://substackcdn.com/image/fetch/$s_!LojD!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 848w, https://substackcdn.com/image/fetch/$s_!LojD!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 1272w, https://substackcdn.com/image/fetch/$s_!LojD!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LojD!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif" width="320" height="320" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:256,&quot;width&quot;:256,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;PICO-8 Fantasy Console&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="PICO-8 Fantasy Console" title="PICO-8 Fantasy Console" srcset="https://substackcdn.com/image/fetch/$s_!LojD!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 424w, https://substackcdn.com/image/fetch/$s_!LojD!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 848w, https://substackcdn.com/image/fetch/$s_!LojD!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 1272w, https://substackcdn.com/image/fetch/$s_!LojD!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57f0122a-7971-4c8f-bae0-0b2f34978164_256x256.gif 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>PICO-8 is a fantasy console, meaning a console that doesn&#8217;t actually exist. In practice it&#8217;s a virtual machine that has a bunch of artificial limitations (number of colors it can display, resolution, limited sprites, etc&#8230;).</p><p>There are two components to it. The first one is for developers where you can develop your game entirely in PICO-8 using the lua programming language. It has a built-in code editor, sprite editor, level editor and sound editor.</p><p>Since this fantasy console is so limited in what it can do, developers can easily avoid making games that are too big in scope.</p><p>The second component is the console aspect, where you can play games made in PICO-8.</p><p>While I think limitations breeds creativity, I think PICO-8&#8217;s limitations are a bit too much for me as a developer, however, I think it&#8217;s worth playing games made in it. You&#8217;ll get very inspired by how creative these small games are.</p><p>I recommend installing the <a href="https://p8go.gg/">P8GO app</a> which allows you to discover PICO-8 games in a TikTok style app, allowing you to cover a lot of games quickly.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HqYi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HqYi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 424w, https://substackcdn.com/image/fetch/$s_!HqYi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 848w, https://substackcdn.com/image/fetch/$s_!HqYi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!HqYi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HqYi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png" width="1456" height="992" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:992,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:452992,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162174686?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HqYi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 424w, https://substackcdn.com/image/fetch/$s_!HqYi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 848w, https://substackcdn.com/image/fetch/$s_!HqYi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!HqYi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa744ab97-4950-4af5-942a-3206cefdc9bc_2164x1474.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h1>Revisiting Old Games</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!M680!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!M680!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!M680!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!M680!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!M680!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!M680!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg" width="535" height="401.25" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1200,&quot;resizeWidth&quot;:535,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;TURN TO CHANNEL 3: The original NES 'Castlevania' still slays almost 30  years later | NEPA Scene&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="TURN TO CHANNEL 3: The original NES 'Castlevania' still slays almost 30  years later | NEPA Scene" title="TURN TO CHANNEL 3: The original NES 'Castlevania' still slays almost 30  years later | NEPA Scene" srcset="https://substackcdn.com/image/fetch/$s_!M680!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!M680!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!M680!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!M680!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba0a32dd-e21c-4d75-9370-ad34285a6ce1_1200x900.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There are a lot of games released in the past that are easy to remake today. I recommend looking up and trying old arcade, NES and Gameboy games. A good exercise is to try remaking them while simplifying or improving the user experience since many of these games were obtuse as gaming was relatively young back then. A lot of the established quality of life features we&#8217;re used to today weren&#8217;t common.</p><h1>Take an Aspect of An Existing Game and Make a Small Game About it</h1><p>In the startup world, you&#8217;ll often see successful businesses emerge by taking one of the many features an establish company already has and focusing exclusively on building that one feature as their product. You can take the same approach in game dev.</p><h1>Example 1</h1><p>In The Legend of Zelda Link&#8217;s Awakening, there is a part of the game where you need to do a trading sequence. Meaning, exchange an item with an NPC to get another item you can exchange with another NPC until you obtain the item you need.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CnV0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CnV0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!CnV0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!CnV0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!CnV0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CnV0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Link's Awakening Boomerang and item trading guide | Polygon&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Link's Awakening Boomerang and item trading guide | Polygon" title="Link's Awakening Boomerang and item trading guide | Polygon" srcset="https://substackcdn.com/image/fetch/$s_!CnV0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!CnV0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!CnV0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!CnV0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ebd2058-7bfd-4331-8968-0ca34ce9208c_1920x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N58T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N58T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N58T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N58T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N58T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N58T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg" width="900" height="506" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:506,&quot;width&quot;:900,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Link's Awakening Switch Trading Sequence Guide&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Link's Awakening Switch Trading Sequence Guide" title="Link's Awakening Switch Trading Sequence Guide" srcset="https://substackcdn.com/image/fetch/$s_!N58T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N58T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N58T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N58T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F316c0988-323b-4fce-8a66-c6496cce2b54_900x506.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>What if you made a game that was exclusively about trading things with NPCs to get an item the player needs?</p><p>While that might not sound particularly fun, you can always take inspiration from things outside of game dev to spice up your game concept. </p><p>For example, Rayan Trahan, a popular YouTuber, made a series called the penny challenge. He starts with a penny and needs to cross through America using only that as his starting budget. The YouTuber then proceeds to buy, sell and trade items enabling them to gradually increase their wealth so they can pay for transportation and other expenses to achieve their goal.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zidb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zidb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 424w, https://substackcdn.com/image/fetch/$s_!zidb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 848w, https://substackcdn.com/image/fetch/$s_!zidb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 1272w, https://substackcdn.com/image/fetch/$s_!zidb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zidb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png" width="1456" height="456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:856401,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162174686?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zidb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 424w, https://substackcdn.com/image/fetch/$s_!zidb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 848w, https://substackcdn.com/image/fetch/$s_!zidb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 1272w, https://substackcdn.com/image/fetch/$s_!zidb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf5a9cc0-9e65-431b-9905-8e9b76fec059_1902x596.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now, let&#8217;s modify the game idea a bit.</p><p>What if you made a game where the player starts in a small island/town and wants to leave that town. To do so, they must buy an expensive ticket for a cruise that will enable them to leave. However, they&#8217;re broke and need to explore the town for items, and trade them with NPCs, until they make enough money to buy the ticket and leave.</p><p>Add to this, challenging aspects, like some NPCs only being interested in certain items, and you&#8217;ve got a nice little game. You can add a bit of randomness with which NPC wants what and this will make each run different allowing your game to be highly replayable.</p><h1>Example 2</h1><p>In the popular game franchise Pok&#233;mon, you often need to pass by a Pok&#233;mart to buy various items useful in your adventure. What if you made a game solely about managing a Pok&#233;mart?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NT2Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NT2Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 424w, https://substackcdn.com/image/fetch/$s_!NT2Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 848w, https://substackcdn.com/image/fetch/$s_!NT2Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!NT2Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NT2Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg" width="1280" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Pok&#233;mon Ruby / Sapphire / Emerald - Pok&#233;Mart (FireRed &amp; LeafGreen Sounds)&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Pok&#233;mon Ruby / Sapphire / Emerald - Pok&#233;Mart (FireRed &amp; LeafGreen Sounds)" title="Pok&#233;mon Ruby / Sapphire / Emerald - Pok&#233;Mart (FireRed &amp; LeafGreen Sounds)" srcset="https://substackcdn.com/image/fetch/$s_!NT2Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 424w, https://substackcdn.com/image/fetch/$s_!NT2Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 848w, https://substackcdn.com/image/fetch/$s_!NT2Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!NT2Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ef516fb-2f16-4628-ad48-52d67253847e_1280x720.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You don&#8217;t have to use the Pok&#233;mon IP, you could replace it with your own monster catching IP. Instead of selling Pok&#233;balls, you would sell CaptureCubes or something to that effect.</p><p>The whole game would take place within the mart. Every time a new customer enters, they can either buy from your inventory or you can buy from them. Which could be your only way of getting certain items in demand by other customers. Your goal is to stay in business since you have expenses, while keeping customers happy by having the items they need so that they keep coming to your mart.</p><p>I could go on, but I think you get the point.</p><h1>Look for Inspiration Outside of Gaming</h1><p>In the first example of the previous section, I mentioned taking inspiration from a YouTuber&#8217;s video series. This is an example of taking inspiration from outside of gaming and I&#8217;d argue that the truely novel ideas can be found there.</p><p>What if you made a game about managing a library? A game about being a moderator of a social media website? A game about being a lumberjack? A game about managing a retro handled company? What if you made a game based on a book, how would you adapt it so that the game is fun? The list goes on.</p><h1>Conclusion</h1><p>In the end, there are a lot of ways to find good small game ideas. In this post, I shared what the inspiration sources I use. If you want to learn game development, web development or game developement using web dev tools, I recommend subscribing to not miss out on future posts and tutorials.</p><p>Thanks for reading!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://jslegenddev.substack.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>In the meantime, you can check out my previous content.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;e344c01f-6749-4f41-91ef-34e6ce05c409&quot;,&quot;caption&quot;:&quot;I released a small web game which is a clone of the classic game Duck Hunt. (You can play it here.) Now that the game has been released for a while, I think it&#8217;s the perfect moment to reflect on what I learned building this project.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;What I Learned Making a Duck Hunt Clone in JavaScript&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-02-22T18:44:15.686Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd3dd095-787f-46f2-9f8f-99e10ea04d20_1455x1228.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/what-i-learned-making-a-duck-hunt&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:156566296,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:1,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;411600be-aece-445c-a464-5a491e535e96&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Learn React.js by Building a Game Search App | Part 1/3 - Project Setup&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-04-03T21:21:47.765Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe01d7bb7-8457-4bd2-ab26-d737bd9f62e5_2940x1522.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:160376861,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:6,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item><item><title><![CDATA[My React.js Beginner Tutorial is Now Available as a Nicely Formatted PDF]]></title><description><![CDATA[I recently published a beginner React.js tutorial on building a game search app as a series of free substack posts.]]></description><link>https://jslegenddev.substack.com/p/my-reactjs-beginner-tutorial-is-now</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/my-reactjs-beginner-tutorial-is-now</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Sat, 26 Apr 2025 20:48:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!VMdb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VMdb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VMdb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!VMdb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!VMdb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!VMdb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VMdb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png" width="1280" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c99238bb-0835-4649-b268-b0227b47c46e_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1005578,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/162221561?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VMdb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!VMdb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!VMdb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!VMdb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc99238bb-0835-4649-b268-b0227b47c46e_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I recently published a beginner React.js tutorial on building a game search app as a series of free substack posts.</p><p>However, I would like to announce that you can now purchase the tutorial as a nicely formatted PDF.</p><p><em>The advantage of the PDF is that you have everything in one place and it&#8217;s nicely formatted with an easy to navigate table of contents. It&#8217;s 5$ and you can buy it <a href="https://www.patreon.com/jslegend/shop/learn-react-js-by-building-game-search-1530315">here</a>.</em></p><p>Here is a preview showcasing how the PDF looks like.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;a83de213-4500-4bab-9dab-3e26433c9f0b&quot;,&quot;duration&quot;:null}"></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Learn React.js by Building a Game Search App | Part 3/3 - Finishing The App]]></title><description><![CDATA[In the previous part (Here are links to part 1 and part 2), we finished setting up the logic to fetch data from our backend.]]></description><link>https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game-eb9</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game-eb9</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Thu, 24 Apr 2025 02:59:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ra4I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ra4I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ra4I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 424w, https://substackcdn.com/image/fetch/$s_!ra4I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 848w, https://substackcdn.com/image/fetch/$s_!ra4I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 1272w, https://substackcdn.com/image/fetch/$s_!ra4I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ra4I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png" width="1456" height="754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:754,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5433375,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/161312730?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ra4I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 424w, https://substackcdn.com/image/fetch/$s_!ra4I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 848w, https://substackcdn.com/image/fetch/$s_!ra4I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 1272w, https://substackcdn.com/image/fetch/$s_!ra4I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the previous part (<em>Here are links to <a href="https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game">part 1</a> and <a href="https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game-68d">part 2</a></em>), we finished setting up the logic to fetch data from our backend. Now it&#8217;s time to display the data in a nice looking UI.</p><h1>Table of Contents</h1><ul><li><p><a href="https://jslegenddev.substack.com/i/161312730/creating-the-game-card-component">Creating The Game Card Component</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/161312730/conditional-rendering-in-react">Conditional Rendering in React</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/how-to-render-lists-in-react">How to Render Lists in React</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/rendering-a-list-of-game-cards">Rendering a List of Game Cards</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/handling-edge-cases-and-improving-the-user-experience">Handling Edge Cases and Improving The User Experience</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/161312730/displaying-error-messages-in-the-ui">Displaying Error Messages in The UI</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/adding-a-loading-spinner">Adding a Loading Spinner</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/building-the-game-details-component">Building The Game Details Component</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/161312730/creating-a-new-api-endpoint-to-fetch-more-data">Creating a New API Endpoint to Fetch More Data</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/rendering-the-game-details-component">Rendering The Game Details Component</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/creating-the-screenshot-carousel">Creating The Screenshot Carousel</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/161312730/conclusion">Conclusion</a></p></li></ul><h1>Creating The Game Card Component</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zzgP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zzgP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 424w, https://substackcdn.com/image/fetch/$s_!zzgP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 848w, https://substackcdn.com/image/fetch/$s_!zzgP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 1272w, https://substackcdn.com/image/fetch/$s_!zzgP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zzgP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png" width="582" height="521.8288770053476" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1006,&quot;width&quot;:1122,&quot;resizeWidth&quot;:582,&quot;bytes&quot;:1510456,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/161312730?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zzgP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 424w, https://substackcdn.com/image/fetch/$s_!zzgP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 848w, https://substackcdn.com/image/fetch/$s_!zzgP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 1272w, https://substackcdn.com/image/fetch/$s_!zzgP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5882af2b-79cf-4aa7-a6ff-caa3577092ae_1122x1006.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After the user makes a search, we want to display a list of relevant games that will be rendered as a grid of cards where each card contains the game&#8217;s title, genres, cover image and the time it takes to complete the game if the data is available.</p><p>The first step to achieve this is to build a reusable game card component. Inside the components folder, create a file called <code>GameCard.jsx</code>.</p><pre><code>src
|- components
      |- GameCard.jsx</code></pre><p>Within that file, add the following code :</p><pre><code>export default function GameCard({
  coverLink,
  name,
  playtime,
  genres,
  onClick,
}) {
  return (
    &lt;div className="flex-col relative" onClick={onClick}&gt;
      &lt;img
        src={coverLink}
        loading="lazy"
        className="w-full h-40 object-cover rounded-md"
      /&gt;

      &lt;h1 className="p-2 text-gray-300 text-lg"&gt;{name}&lt;/h1&gt;
      {playtime !== 0 &amp;&amp; (
        &lt;div className="flex items-center gap-2 mx-2 bg-gray-800 p-2 absolute top-2 right-0 rounded-md"&gt;
          &lt;img
            src="./clock-fill.svg"
            className="text-gray-300 max-w-xs max-h-xs"
          /&gt;
          &lt;p className=" text-gray-300 text-sm whitespace-nowrap"&gt;
            {playtime} Hours
          &lt;/p&gt;
        &lt;/div&gt;
      )}

      &lt;li className="flex flex-row flex-wrap gap-2 m-2 mt-auto"&gt;
        {genres.map((genre) =&gt; (
          &lt;ul className="genre-pill" key={genre.name}&gt;
            {genre.name}
          &lt;/ul&gt;
        ))}
      &lt;/li&gt;
    &lt;/div&gt;
  );
}</code></pre><p>We create a component with 5 props.</p><ul><li><p><code>coverLink</code> : The link of the cover image.</p></li><li><p><code>name</code> : The name of the game.</p></li><li><p><code>playtime</code> : How long the game takes to beat.</p></li><li><p><code>genres</code> : An array containing the game&#8217;s genres.</p></li><li><p><code>onClick</code> : A function that will run when the user clicks on a card.</p></li></ul><p>Let&#8217;s break down the JSX returned by the component.</p><pre><code>&lt;div className="flex-col relative" onClick={onClick}&gt;</code></pre><p>We the return a <code>div</code> that acts as the card container. We set is as a flex container and make it relative so that within, we will be able to place the playtime indicator at the top-right of the card using absolute positioning. We then set the <code>onClick</code> function passed as a prop to trigger when the container is clicked.</p><pre><code>&lt;img
  src={coverLink}
  className="w-full h-40 object-cover rounded-md"
/&gt;</code></pre><p>We display the game&#8217;s cover using a simple <code>img</code> tag. First, the image is set to take the full available width with <code>w-full</code> . The height is set to 40 Tailwind units using  <code>h-40</code>. </p><p>The<code> object-cover</code>  Tailwind class is used to render the image in its original aspect ratio while still fitting within the width and height of the <code>img</code> tag. <code>rounded-md</code> stands for rounded medium and makes the corner of the image noticeably rounded.</p><pre><code> &lt;h1 className="p-2 text-gray-300 text-lg"&gt;{name}&lt;/h1&gt;</code></pre><p>This <code>h1</code> tag is used to display the game&#8217;s name.</p><pre><code>{playtime !== 0 &amp;&amp; (
        &lt;div className="flex items-center gap-2 mx-2 bg-gray-800 p-2 absolute top-2 right-0 rounded-md"&gt;
          &lt;img
            src="./clock-fill.svg"
            className="text-gray-300 max-w-xs max-h-xs"
          /&gt;
          &lt;p className=" text-gray-300 text-sm whitespace-nowrap"&gt;
            {playtime} Hours
          &lt;/p&gt;
        &lt;/div&gt;
)}</code></pre><p>We use conditional rendering to render the playtime indicator only if the playtime is above 0 hours. Usually, if it&#8217;s 0 then that means the playtime data for this game isn&#8217;t available. The RAWG API returns 0 in that case.</p><p>Using absolute positioning we&#8217;re able to place the indicator at the top-right corner of the game card by using <code>absolute top-2 right-0</code>.</p><h2>Conditional Rendering in React</h2><p>There are many ways to achieve conditional rendering in React. Here are a few examples.</p><p>First example with an if statement.</p><pre><code>function myComp() {
  if (condition) {
    return (
      &lt;div&gt;
        &lt;h1&gt;B&lt;/h1&gt;
      &lt;/div&gt;
    );
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;A&lt;/h1&gt;
    &lt;/div&gt;
  );
}</code></pre><p>Second example with a boolean expression.</p><pre><code>function myComp() {
   return (
     &lt;div&gt;
       {condition &amp;&amp; &lt;h1&gt;B&lt;/h1&gt;}
       {!condition &amp;&amp; &lt;h1&gt;A&lt;/h1&gt;}
     &lt;/div&gt;
   );
}</code></pre><p>Third example, with a ternary operator.</p><pre><code>function myComp() {
   return (
     &lt;div&gt;
       {condition ? &lt;h1&gt;B&lt;/h1&gt; : &lt;h1&gt;A&lt;/h1&gt;}
     &lt;/div&gt;
   );
}</code></pre><p>If you want to achieve conditional rendering within JSX you can&#8217;t use an if statement and are forced to use either a boolean expression or a ternary operator. Between using a boolean expression or a ternary operator in the example above, the latter is better since we display &#8220;B&#8221; in the case the condition is true and &#8220;A&#8221; otherwise.</p><pre><code>{playtime !== 0 &amp;&amp; (... rest of the code)}</code></pre><p>However, in our game card component we display the playtime indicator only if its value is above 0 and nothing otherwise. Therefore, the boolean expression is more fitting for our use case.</p><pre><code>&lt;li className="flex flex-row flex-wrap gap-2 m-2 mt-auto"&gt;
 {genres.map((genre) =&gt; (
    &lt;ul className="genre-pill" key={genre.name}&gt;
      {genre.name}
    &lt;/ul&gt;
  ))}
&lt;/li&gt;</code></pre><p>Finally, we display the genres of the game. There can be more than one, that&#8217;s why we&#8217;re rendering this section as a list of items. Now has come the time to understand how lists are rendered in React.</p><p>Note that the class <code>genre-pill</code> is a custom class and you need to add it to your <code>index.css</code> file for it to work.</p><pre><code>.genre-pill {
  @apply bg-red-800 rounded-full px-2 text-gray-300 text-sm;
}</code></pre><h2>How to Render Lists in React</h2><p>It often occurs when working on Web UIs that you want to render multiple elements at once. For example, you want to render a list of search results, in our case, a bunch of game cards.</p><p>In React, you can achieve this by using the JavaScript <code>.map</code> function to iterate over an array and for each element return some JSX. At a basic level, it looks like this.</p><pre><code>function myComp() {
  const lines = ["Hello World!", "Foo", "Bar"];

  return (
    &lt;ul&gt;
      {lines.map((line) =&gt; &lt;li&gt;{line}&lt;/li&gt;)}
    &lt;/ul&gt;
  );
}</code></pre><pre><code> {lines.map((line) =&gt; &lt;li&gt;{line}&lt;/li&gt;)}</code></pre><p>However, there is something missing in the code above. When rendering multiple elements, the rendered JSX elements need to be assigned a key. This is to enable React to establish a relationship between array elements and the corresponding JSX element. This becomes very important if items of the list are later added or removed. The key enables React to make the correct updates to the DOM.</p><p>You might come across React code using the index of the element as the key.</p><pre><code> {lines.map((line, index) =&gt; &lt;li key={index}&gt;{line}&lt;/li&gt;)}</code></pre><p>This is to be avoided when the array can change later down the line. For example, if the elements of the array are sorted in a different order, the relationship between the index and the JSX element will be broken.</p><p>To avoid this from happening, it&#8217;s usually best to use a consistent but unique key. Consistent in the sense that the key doesn&#8217;t change from render to render and unique so that no two elements in the list have the same key.</p><p>In our example above, the only key that would best fulfill these two conditions are the lines themselves.</p><pre><code>{lines.map((line) =&gt; &lt;li key={line}&gt;{line}&lt;/li&gt;)}</code></pre><p>If we assume that the data you want to display comes from a source that you control, you could have decided to assign a unique id for each item before sending the data to the client.</p><p>For example, let&#8217;s assume that each line of our lines array will have an id. You would have something like this :</p><pre><code>[{id: "u90909dfj89", content: "Hello World"}, ...]</code></pre><p>You would use the id as the key.</p><pre><code>{lines.map((line) =&gt; &lt;li key={line.id}&gt;{line.content}&lt;/li&gt;)}</code></pre><p>Alternatively, with destructuring you would do :</p><pre><code>{lines.map(({id, content}) =&gt; &lt;li key={id}&gt;{content}&lt;/li&gt;)}</code></pre><p>For our genres, since the genre name is unique, we use the name for both the content of the list item and the key.</p><h1>Rendering a List of Game Cards</h1><p>Now that we have finished the game card component, let&#8217;s use it in <code>App.jsx</code> to display relevant games when the search feature is used.</p><p>Add the following code :</p><pre><code>// previous code omitted for clarity
import GameCard from "./components/GameCard";

export default function App() {
  // previous code omitted for clarity

  const { data, loading, fetchData, error, reset } = useFetch(() =&gt;
    fetchGames({ query: searchQuery })
  );

  // code omitted for clarity

  // second useEffect is no longer needed. This was just for testing purposes
  // useEffect(() =&gt; {
  //  if (data) console.log(data);
  // }, [data]);

  return (
    &lt;main className="w-full p-2 flex flex-col items-center"&gt;
      {/* code omitted for clarity */}
      &lt;div className="w-full max-w-4xl"&gt;
        &lt;Search
          query={searchQuery}
          onChange={(e) =&gt; setSearchQuery(e.target.value)}
        /&gt;
        &lt;div className="w-full"&gt;
          {data &amp;&amp; (
            &lt;div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"&gt;
              {data.results.map(
                (game) =&gt;
                  game.added &gt; 30 &amp;&amp; (
                    &lt;GameCard
                      key={game.slug}
                      name={game.name}
                      coverLink={game.background_image}
                      playtime={game.playtime}
                      genres={game.genres}
                      onClick={() =&gt; {
                        console.log("test");
                      }}
                    /&gt;
                  )
              )}
            &lt;/div&gt;
          )}
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/main&gt;
  );
}</code></pre><p>Let&#8217;s break it down.</p><pre><code>{data &amp;&amp; (
  &lt;div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"&gt;
    {data.results.map(
      (game) =&gt;
        game.added &gt; 30 &amp;&amp; (
          &lt;GameCard
            key={game.slug}
            name={game.name}
            coverLink={game.background_image}
            playtime={game.playtime}
            genres={game.genres}
            onClick={() =&gt; {
              // TODO
              console.log("test");
            }}
          /&gt;
         )
       )}
  &lt;/div&gt;
)}</code></pre><p>We only render the game cards if the data is available. In that case, we first render a <code>div</code> which acts as a container for all game cards and is going to be displayed as a CSS grid.</p><pre><code>grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3</code></pre><ul><li><p><code>grid</code> : sets the <code>div</code> as a grid container.</p></li><li><p><code>grid-cols-1</code> : sets the grid to have 1 column only.</p></li><li><p><code>gap-5</code> : sets a gap of 5 units between each elements in the grid.</p></li><li><p><code>sm:grid-cols-2</code> : sets the number of columns of the grid to be 2 on smaller screens and beyond. <code>sm</code> is a media query in Tailwind.</p></li><li><p><code>lg:grid-cols-3</code> : sets the number of columns of the grid to be 3 on larger screens and beyond. <code>lg</code> is also a media query in Tailwind.<br></p></li></ul><pre><code>{data.results.map(
  (game) =&gt;
    game.added &gt; 30 &amp;&amp; (
      &lt;GameCard
        key={game.slug}
        name={game.name}
        coverLink={game.background_image}
        playtime={game.playtime}
        genres={game.genres}
        onClick={() =&gt; {
          // TODO
          console.log("test");
        }}
      /&gt;
    )
  )
}</code></pre><p>We get the game cards data by doing <code>data.results</code> which returns an array. We then map over it to render each game card.</p><p>Before rendering a game card we make sure that the game has been added at least more than 30 times. The reason we&#8217;re doing this is that the RAWG API has the <code>added</code> property which determines how much a game was added. It doesn&#8217;t specify what <code>added</code> is exactly but I&#8217;ve noticed that if you just render what ever the array has you&#8217;ll often find yourself displaying cheap fangames and clones along side more legit titles. To show the best quality results as possible, I have figured out through testing that a game must have been added at least 30 times to be considered &#8220;legit&#8221;.</p><p>As mentioned previously when rendering an array in React you must provide a key for each element. Here is no exception. However, you might be wondering if we have to define a prop called <code>key</code> on the <code>GameCard</code> component before passing to it a key since it&#8217;s custom made and not a native JSX element? Fortunately, you don&#8217;t have to, it&#8217;s taken care of automatically by React.</p><p>Since the RAWG API returns a slug for each game, this will act as our unique key since it never changes for a specific game and is unique.</p><p>Now that we have added our game card list rendering logic, let&#8217;s test it out. Make sure to have your backend and frontend running with <code>node index.js</code> and <code>npm run dev </code>respectively.</p><p>You should have the following results.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;a07697ca-43e0-4039-a66a-3d27589cf122&quot;,&quot;duration&quot;:null}"></div><h1>Handling Edge Cases and Improving The User Experience</h1><p>While we were able to successfully display relevant results when a user makes a query there are still edge cases we aren&#8217;t handling.</p><h2>Displaying Error Messages in The UI</h2><p>For example, what should be shown to the user if there are no games relevant to what they searched for? </p><p>In that case nothing is shown which isn&#8217;t a great user experience. To fix this, let&#8217;s add a message that will be displayed when no relevant search results are found.</p><p>Modifiy the code we had previously like shown below.</p><pre><code>&lt;div className="w-full"&gt;
  {data &amp;&amp; (
    &lt;&gt;
      &lt;div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"&gt;
        {data.results.map(
          (game) =&gt;
            game.added &gt; 30 &amp;&amp; (
              &lt;GameCard
                key={game.slug}
                name={game.name}
                coverLink={game.background_image}
                playtime={game.playtime}
                genres={game.genres}
                onClick={() =&gt; {
                  console.log("test");
                }}
              /&gt;
          )
        )}
       &lt;/div&gt;
       {data.results.length === 0 &amp;&amp; (
         &lt;div className="w-full flex flex-col justify-center items-center"&gt;
           &lt;img src="./no-results-found.png" className="size-52" /&gt;
           &lt;p className="text-gray-300 text-2xl"&gt;No games found!&lt;/p&gt;
         &lt;/div&gt;
        )}
    &lt;/&gt;
  )}
&lt;/div&gt;</code></pre><p>&lt;&gt;&lt;/&gt; is called a React fragment. This is useful when you don&#8217;t want to create an unnecessary <code>div</code> to wrap two or more elements at the same level. In our case we want the JSX for the search results and the JSX for the &#8220;No games found!&#8221; message to be both children of the container <code>div</code> having the className <code>w-full</code>. There is no need to wrap them under an additional div.</p><p>Now let&#8217;s test things out.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;a10c9179-99e0-4cc1-86ac-50bf92a225d4&quot;,&quot;duration&quot;:null}"></div><p>As you can see, the message showed up.</p><p>There&#8217;s still another edge case we haven&#8217;t taken into account. What if, for some reason, the app fails to retrieve data from the backend? Currently we show nothing in that case. However, the wise thing to do is to have some kind of error message displayed to the user.</p><p>Since this error message is going to have the same structure as the one we used for the &#8220;No games found!&#8221; message, let&#8217;s create a reusable component in the <code>components</code> folder. Create the file <code>ErrorMessage.jsx</code> with the following code.</p><pre><code>export default function ErrorMessage({ message }) {
  return (
    &lt;div className="w-full  flex flex-col justify-center items-center"&gt;
      &lt;img src="./no-results-found.png" className="size-52" /&gt;
      &lt;p className="text-gray-300 text-2xl"&gt;{message}&lt;/p&gt;
    &lt;/div&gt;
  );
}</code></pre><p>Now, we can use this component in <code>App.jsx</code>. Import the <code>ErrorMessage</code> component at the top of the file.</p><pre><code>import ErrorMessage from "./components/ErrorMessage";</code></pre><p>Then, replace the JSX we used for the &#8220;No games found!&#8221; message.</p><p>From :</p><pre><code>{data.results.length === 0 &amp;&amp; (
  &lt;div className="w-full flex flex-col justify-center items-center"&gt;
    &lt;img src="./no-results-found.png" className="size-52" /&gt;
    &lt;p className="text-gray-300 text-2xl"&gt;No games found!&lt;/p&gt;
  &lt;/div&gt;
)}</code></pre><p>to :</p><pre><code>{data.results.length === 0 &amp;&amp; (
  &lt;ErrorMessage message={"No games found!"} /&gt;
)}</code></pre><p>Let&#8217;s use the same component to display an error message when the apps fail to retrieve the data from the backend.</p><pre><code>// previous code omitted for clarity
&lt;div className="w-full"&gt;
  {data &amp;&amp; (...)}
  {error &amp;&amp; &lt;ErrorMessage message={"Network Error Occured!"} /&gt;}
&lt;/div&gt;</code></pre><p>Now, when a network error occurs, you&#8217;ll get the following UI.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qU6Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qU6Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 424w, https://substackcdn.com/image/fetch/$s_!qU6Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 848w, https://substackcdn.com/image/fetch/$s_!qU6Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 1272w, https://substackcdn.com/image/fetch/$s_!qU6Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qU6Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png" width="1456" height="840" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:840,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1730253,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/161312730?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qU6Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 424w, https://substackcdn.com/image/fetch/$s_!qU6Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 848w, https://substackcdn.com/image/fetch/$s_!qU6Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 1272w, https://substackcdn.com/image/fetch/$s_!qU6Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1821a99-7c63-4f32-887c-74b38d474756_1892x1092.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Adding a Loading Spinner</h2><p>Another thing to improve upon is the fact that we give no indication to the user that the app is loading search results. Let&#8217;s add a spinner so that user doesn&#8217;t think the app has crashed.</p><p>Under the <code>components</code> folder create a file called <code>Spinner.jsx </code>and add the following code :</p><pre><code>export default function Spinner() {
  return (
    &lt;div role="status"&gt;
      &lt;svg
        aria-hidden="true"
        className="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-500"
        viewBox="0 0 100 101"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      &gt;
        &lt;path
          d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
          fill="currentColor"
        /&gt;
        &lt;path
          d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
          fill="currentFill"
        /&gt;
      &lt;/svg&gt;
      &lt;span className="sr-only"&gt;Loading...&lt;/span&gt;
    &lt;/div&gt;
  );
}</code></pre><p>This component is based upon the loading spinner example found <a href="https://flowbite.com/docs/components/spinner/">here</a>. Most of it is SVG code. I will not explain how SVG work because it&#8217;s out of the scope of this tutorial. Let&#8217;s just use that spinner as is in our app.</p><p>In <code>App.jsx</code>, add the following code :</p><pre><code>// previous code omitted for clarity
import Spinner from "./components/Spinner";</code></pre><p>Then, modify our existing code where we display the search results :</p><pre><code>&lt;div className="w-full"&gt;
  {!loading ? (
    &lt;&gt;
      &lt;div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"&gt;
        {data?.results.map(
          (game) =&gt;
            game.added &gt; 30 &amp;&amp; (
              &lt;GameCard
                key={game.slug}
                name={game.name}
                coverLink={game.background_image}
                playtime={game.playtime}
                genres={game.genres}
                onClick={() =&gt; {
                  console.log("test");
                }}
              /&gt;
            )
         )}
       &lt;/div&gt;
       {data?.results.length === 0 &amp;&amp; (
         &lt;ErrorMessage message={"No games found!"} /&gt;
       )}
    &lt;/&gt;
   ) : (
    &lt;div className="flex justify-center"&gt;
      &lt;Spinner /&gt;
    &lt;/div&gt;
  )}
  {error &amp;&amp; &lt;ErrorMessage message={"Network Error Occurred!"} /&gt;}
&lt;/div&gt;</code></pre><p>You&#8217;ll notice that we now have a ternary operator that determines whenever or not to show the spinner. If we aren&#8217;t in a loading state we render the grid or the error message. We use <code>data? </code>with the question mark as to only attempt to access the <code>results</code> property of the <code>data</code> object if it&#8217;s available.</p><p>Now, let&#8217;s test it out.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;e77aaac2-f912-4daa-9a9b-2fa014ab822a&quot;,&quot;duration&quot;:null}"></div><h1>Building The Game Details Component</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sfcl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sfcl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 424w, https://substackcdn.com/image/fetch/$s_!sfcl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 848w, https://substackcdn.com/image/fetch/$s_!sfcl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!sfcl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sfcl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png" width="1456" height="754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:754,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4718885,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160376861?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!sfcl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 424w, https://substackcdn.com/image/fetch/$s_!sfcl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 848w, https://substackcdn.com/image/fetch/$s_!sfcl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!sfcl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F132b4a28-16e7-4dab-9167-d7761e4d32a5_2928x1516.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hgaG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hgaG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 424w, https://substackcdn.com/image/fetch/$s_!hgaG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 848w, https://substackcdn.com/image/fetch/$s_!hgaG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!hgaG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hgaG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png" width="1456" height="754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:754,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4484259,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160376861?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!hgaG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 424w, https://substackcdn.com/image/fetch/$s_!hgaG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 848w, https://substackcdn.com/image/fetch/$s_!hgaG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!hgaG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb559118d-1f38-4494-9b88-27d966fb4250_2928x1516.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The final part of this project consists in building the game details component which is a component that will display a dedicated section for a specific game after having clicked on its game card. In a production app, you would probably use a router like <a href="https://reactrouter.com/">React Router</a> and make it display as its own separate page.</p><p>However, I wanted to limit the use of third-party libraries, so I opted for displaying a component on the same page instead.</p><h2>Creating a New API Endpoint to Fetch More Data</h2><p>Before we build that component, we need to write a new API endpoint in our backend to fetch data specific to a game. This endpoint will return a detailed description of the game. This wasn&#8217;t provided by the previous endpoint we had.</p><p>In your backend, add the following endpoint to your <code>index.js</code> file.</p><pre><code>app.get("/api/game", async (req, res) =&gt; {
  const { gameSlug } = req.query;

  try {
    const response = await fetch(
      `https://api.rawg.io/api/games/${gameSlug}?key=${API_KEY}`,
      {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
        },
      }
    );

    if (!response.ok) {
      throw new Error("Failed to fetch data from external API");
    }

    const data = await response.json();

    return res.json(data);
  } catch (error) {
    console.error("Error fetching data from API:", error);
    return res
      .status(500)
      .json({ error: "Failed to fetch data from external API" });
  }
});</code></pre><p>The first API endpoint returns, amongst other things, the game&#8217;s slug which we can use to query this second endpoint by passing it as a param.</p><p>Now restart the backend by doing <code>Ctrl+C</code> and running the command <code>node index.js</code>. Then, in our frontend, in the <code>api.js</code> file, add the following code :</p><pre><code>// previous code omitted for clarity

export async function fetchGameData({ gameSlug }) {
  const response = await fetch(`${API_ENDPOINT}/game?gameSlug=${gameSlug}`);
  return await response.json();
}</code></pre><p>Finally, in the <code>components</code> folder, create a new file called <code>GameDetails.jsx</code> and add the following code.</p><pre><code>import { useEffect } from "react";
import useFetch from "../hooks/useFetch";
import { fetchGameData } from "../api";

export default function GameDetails({ currentGame, goBack }) {

   const { data, loading, fetchData, error, reset } = useFetch(() =&gt;
     fetchGameData({ gameSlug: currentGame.slug })
   );

   useEffect(() =&gt; {
    (async () =&gt; {
      await fetchData();
    })();
   }, []);


   return (&lt;section&gt;&lt;/section&gt;);
}</code></pre><p>We started things simple, by writing the logic to fetch the relevant data as soon as the component is rendered. We pass two props to the component. <code>currentGame</code> which is an object that contains the data previously fetched for a specific game. It contains the game&#8217;s slug (which is its id), genres, title, etc&#8230; We use the slug to fetch more data (the game&#8217;s detailed description). </p><p>The second prop is a function used to enable the user to go back to the search results if they wish to do so. Like mentioned earlier, in a production app, use something like <a href="https://reactrouter.com/">React Router</a> so that the user can use the back button of their browser to go back. This is called navigation and isn&#8217;t built into React by default, underlining the need for a third party library.</p><p>Our backend will return a description of the game and is formatted as a single long string. If we render it as is, it will not look good in the UI. That&#8217;s why we need to add the following utility function. Place it above where the <code>GameDetails</code> component is defined.</p><pre><code>// import statements should be here

function createParagraphs(inputString) {
  // Split the string into sentences using regex to match sentence-ending punctuation (., !, ?)
  const sentences = inputString.match(/[^.!?]+[.!?]+/g) || [];

  // Initialize an array to hold the paragraphs
  const paragraphs = [];

  // Loop through the sentences and create paragraphs with a max of 3 sentences
  for (let i = 0; i &lt; sentences.length; i += 3) {
    // Join the next 3 sentences to form a paragraph
    const paragraph = sentences
      .slice(i, i + 3)
      .join(" ")
      .trim();
    paragraphs.push(paragraph);
  }

  return paragraphs;
}

export default function GameDetails({ currentGame, goBack }) {
// rest of the code omitted for clarity</code></pre><p>Now that we have this, we can modify our <code>GameDetails</code> component like so.</p><pre><code>import { useEffect, useState } from "react";
// ... other import statements

export default function GameDetails({ currentGame, goBack }) {
   const [description, setDescription] = useState([]);

   const { data, loading, fetchData, error, reset } = useFetch(() =&gt;
     fetchGameData({ gameSlug: currentGame.slug })
   );

   useEffect(() =&gt; {
    (async () =&gt; {
      await fetchData();
    })();
   }, []);

   
   useEffect(() =&gt; {
    if (data) {
      const rawDescription = data.description_raw;
      const paragraphs = createParagraphs(rawDescription);

      setDescription(paragraphs);
    }
   }, [data]);
   


   return (&lt;section&gt;&lt;/section&gt;);
}</code></pre><p>We create a state variable for holding the description and use a second <code>useEffect</code> to split the game&#8217;s description into paragraphs as soon as the data is available.</p><p>Let&#8217;s add more JSX but before, import the Spinner component we wrote previously.</p><pre><code>import Spinner from "./Spinner";</code></pre><pre><code>// previous code omitted for clarity
&lt;section&gt;
  &lt;button className="main-button flex" onClick={goBack}&gt;
    &lt;img src="./back-arrow.svg" /&gt; &lt;span className="mx-2"&gt;Go Back&lt;/span&gt;
  &lt;/button&gt;

  {!data ? (
    &lt;div className="flex justify-center mt-4"&gt;
      &lt;Spinner /&gt;
    &lt;/div&gt;
  ) : (
    &lt;div className="mt-4"&gt;
      &lt;div className="w-full relative"&gt;
        &lt;div className="absolute inset-0 bg-gradient-to-b from-black via-transparent to-transparent opacity-80"&gt;&lt;/div&gt;
        &lt;h1 className="text-gray-200 text-2xl absolute top-5 left-5"&gt;
          {currentGame.name}
        &lt;/h1&gt;
        &lt;img
          className="w-full h-40 object-cover rounded-md"
          src={currentGame.background_image}
        /&gt;
      &lt;/div&gt;
      &lt;div&gt;
       
        &lt;div className="text-gray-300"&gt;
          {description.map((paragraph, index) =&gt; (
            &lt;p className="mt-2" key={index}&gt;
              {paragraph}
            &lt;/p&gt;
          ))}
        &lt;/div&gt;
        

        &lt;div className="w-full flex flex-col items-center p-4 h-96"&gt;
             TODO: Screenshot Carousel
        &lt;/div&gt;

        &lt;div className="grid grid-cols-2 py-4"&gt;
          &lt;div className="flex flex-col"&gt;
            &lt;h2 className="text-gray-300 text-xl"&gt;Genres&lt;/h2&gt;
            &lt;ul className="flex flex-wrap gap-2 py-2"&gt;
              {currentGame.genres.map((genre) =&gt; (
                &lt;li className="genre-pill" key={genre.name}&gt;
                  {genre.name}
                &lt;/li&gt;
              ))}
            &lt;/ul&gt;
            &lt;h2 className="text-gray-300 text-xl"&gt;Platforms&lt;/h2&gt;
            &lt;ul className="flex flex-wrap gap-2 py-2"&gt;
              {currentGame.platforms.map((platform) =&gt; (
                &lt;li key={platform.platform.name} className="platform-pill"&gt;
                  {platform.platform.name}
                &lt;/li&gt;
              ))}
            &lt;/ul&gt;
          &lt;/div&gt;
          &lt;div className="flex justify-center items-center"&gt;
            &lt;button
              className="main-button flex items-center"
              onClick={() =&gt;
                window.open(
                  `https://google.com/search?q=where+to+buy+${currentGame.name}`,
                  "_blank"
                )
              }&gt;
               &lt;span className="ml-4 mr-2"&gt;Purchase&lt;/span&gt;
               &lt;img src="./shopping-cart.svg" className="size-6 mr-4" /&gt;
             &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )}
&lt;/section&gt;</code></pre><p>Let&#8217;s break it down.</p><pre><code>&lt;button className="main-button flex" onClick={goBack}&gt;
    &lt;img src="./back-arrow.svg" /&gt; &lt;span className="mx-2"&gt;Go Back&lt;/span&gt;
&lt;/button&gt;</code></pre><p>This is the button placed at the top of the component allowing user&#8217;s to go back to the search results if they wish to do so. You&#8217;ll notice that we&#8217;re using a custom class called <code>main-button</code>. Add it to you <code>index.css</code> file. Also, add the <code>platform-pill</code> custom class which is used for displaying the various platforms the game can be played on as pill shaped blue &#8220;pills&#8221;.</p><pre><code>.main-button {
  @apply bg-blue-400 rounded-md p-2;
}

.platform-pill {
  @apply bg-blue-800 rounded-full px-2 text-gray-300 text-sm;
}</code></pre><pre><code>{loading ? (
    &lt;div className="flex justify-center mt-4"&gt;
      &lt;Spinner /&gt;
    &lt;/div&gt;
) : (...)}</code></pre><p>We then use a ternary operator, to show a loading spinner while we&#8217;re fetching the description data for the game. Once loading is completed, we render some JSX containing a banner with the game&#8217;s cover and name followed by the game&#8217;s description rendered in multiple paragraphs. Then, we will render a screenshot carousel that will be its own separate component (We will write the code for it later) and finally, we render the list of genres of the game and the various platform you can play it on. Additionally, we render a button that will open up a google page with a query to determine where the user can purchase the game.</p><h1>Rendering The Game Details Component</h1><p>Before proceeding with the screenshot carousel component, let&#8217;s first try to render the <code>GameDetails</code> component when a user clicks on a game card in the search results.</p><p>Add the following code to <code>App.jsx</code>.</p><pre><code>import GameDetails from "./components/GameDetails";

function App() {
  // ... code omitted for clarity

  const [showGameDetails, setShowGameDetails] = useState(false);
  const [currentGame, setCurrentGame] = useState(null);

  // ...
 
  return (
       // ...
       &lt;div className="w-full"&gt;
           {!loading &amp;&amp; !showGameDetails ? 
             (...) : 
             showGameDetails ?  (
            &lt;GameDetails
              currentGame={currentGame}
              goBack={() =&gt; {
                setShowGameDetails(false);
                setCurrentGame(null);
              }}
            /&gt;
          ) : (
            &lt;div className="flex justify-center"&gt;
              &lt;Spinner /&gt;
            &lt;/div&gt;
          )}
       &lt;/div&gt;
       // ...

  )</code></pre><p>Then, in the <code>onClick</code> of the GameCard components in <code>App.jsx</code> add the following.</p><pre><code>{data.results.map(
  (game) =&gt;
    game.added &gt; 30 &amp;&amp; (
      &lt;GameCard
        key={game.slug}
        name={game.name}
        coverLink={game.background_image}
        playtime={game.playtime}
        genres={game.genres}
        onClick={() =&gt; {
          setCurrentGame(game); // &lt;--
          setShowGameDetails(true); // &lt;--
        }}
      /&gt;
    )
)}</code></pre><p>Now, if you run the app (make sure to restart the backend and the frontend) you should have the following results.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;eeba692d-768a-4977-9010-9d7fb7dc0e4f&quot;,&quot;duration&quot;:null}"></div><h1>Creating The Screenshot Carousel</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wGSt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wGSt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 424w, https://substackcdn.com/image/fetch/$s_!wGSt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 848w, https://substackcdn.com/image/fetch/$s_!wGSt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 1272w, https://substackcdn.com/image/fetch/$s_!wGSt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wGSt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png" width="1456" height="897" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:897,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3283892,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/161312730?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wGSt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 424w, https://substackcdn.com/image/fetch/$s_!wGSt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 848w, https://substackcdn.com/image/fetch/$s_!wGSt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 1272w, https://substackcdn.com/image/fetch/$s_!wGSt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b2e8d5-d2c9-402b-a380-cc0130ad1b70_2308x1422.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The last component of this project consists in a screenshot carousel. In the <code>components</code> folder, create a file named <code>ScreenshotCarousel.jsx</code> and add the following code :</p><pre><code>import { useState, useEffect } from "react";

export default function ScreenshotCarousel({ screenshots }) {
  const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0);

  useEffect(() =&gt; {
    // trick to preload the images
    for (const screenshot of screenshots) {
      const img = new Image();
      img.src = screenshot.image;
    }
  }, []);

  return (
    &lt;div
      className="bg-gray-800 rounded-lg w-full h-full max-h-96 overflow-hidden relative"
    &gt;
      &lt;button
        className="bg-gray-200 rounded-full p-4 absolute top-40 right-5 shadow-gray-700 shadow-xl z-10"
        onClick={() =&gt; {
          setCurrentScreenshotIndex((currentScreenshotIndex) =&gt; {
            if (currentScreenshotIndex &lt; screenshots.length - 1) {
              return currentScreenshotIndex + 1;
            }

            return 0;
          });
        }}
      &gt;&lt;img src="./arrow-right.svg" /&gt;&lt;/button&gt;
      &lt;button
        className="bg-gray-200 rounded-full p-4 absolute top-40 left-5 shadow-gray-700 shadow-xl z-10"
        onClick={() =&gt; {
          setCurrentScreenshotIndex((currentScreenshotIndex) =&gt; {
            if (currentScreenshotIndex &gt; 0) {
              return currentScreenshotIndex - 1;
            }

            return screenshots.length - 1;
          });
        }}
      &gt;&lt;img src="./arrow-left.svg" /&gt;&lt;/button&gt;

      &lt;img
        className="object-cover w-full h-full absolute z-0 blur-md opacity-50"
        src={screenshots[currentScreenshotIndex].image}
      /&gt;
      &lt;img
        className="object-contain w-full h-full absolute"
        src={screenshots[currentScreenshotIndex].image}
      /&gt;

      &lt;span className="bg-gray-900 rounded-full px-4 py-1 text-gray-300 text-xs absolute right-2 top-2 shadow-gray-700 shadow-xl"&gt;
        {currentScreenshotIndex + 1} / {screenshots.length}
      &lt;/span&gt;
    &lt;/div&gt;
  );
}</code></pre><p>Let&#8217;s break it down.</p><pre><code>export default function ScreenshotCarousel({ screenshots }) {</code></pre><p>We set a <code>screenshots </code>prop which will hold the array containing all screenshots of a given game.</p><pre><code>const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0);

useEffect(() =&gt; {
// trick to preload the images
  for (const screenshot of screenshots) {
    const img = new Image();
    img.src = screenshot.image;
  }
}, []);</code></pre><p>We first declared a state variable for holding the index of the current screenshot being displayed.</p><p>We use a <code>useEffect</code> to preload all the screenshots. This is useful, so that when the user clicks on the arrow to look at the next image, they will see it instantly and won&#8217;t have to wait for it to load.</p><pre><code>&lt;div
  className="bg-gray-800 rounded-lg w-full h-full max-h-96 overflow-hidden relative"
&gt;</code></pre><p>We return a <code>div</code> that acts as the screenshot carousel&#8217;s container. It has a background of <code>bg-gray-800</code>, rounded corners with <code>rounded-lg</code> (stands for rounded large), takes the full width available of the parent element with <code>w-full</code>, takes the full height but caps out at 96 units with <code>h-full</code> and <code>max-h-96</code>, prevents overflowing with <code>overflow-hidden</code> and is set to relative so we can absolute position the arrows.</p><pre><code>&lt;button
  className="bg-gray-200 rounded-full p-4 absolute top-40 right-5 shadow-gray-700 shadow-xl z-10"
  onClick={() =&gt; {
    setCurrentScreenshotIndex((currentScreenshotIndex) =&gt; {
      if (currentScreenshotIndex &lt; screenshots.length - 1) {
        return currentScreenshotIndex + 1;
      }

      return 0;
    });
  }}
&gt;&lt;img src="./arrow-right.svg" /&gt;&lt;/button&gt;
&lt;button
  className="bg-gray-200 rounded-full p-4 absolute top-40 left-5 shadow-gray-700 shadow-xl z-10"
  onClick={() =&gt; {
    setCurrentScreenshotIndex((currentScreenshotIndex) =&gt; {
      if (currentScreenshotIndex &gt; 0) {
        return currentScreenshotIndex - 1;
      }

      return screenshots.length - 1;
    });
  }}
&gt;&lt;img src="./arrow-left.svg" /&gt;&lt;/button&gt;  </code></pre><p>Here, we added our two arrow buttons that allow the user to move back and forth. For both buttons their <code>onClick</code> handlers updates the screenshot index properly by making sure that we don&#8217;t go out of the bounds of the screenshots array.</p><p>Once the screenshot index is set, the carousel will re-render to display the correct image which is set here.</p><pre><code>&lt;img
  className="object-cover w-full h-full absolute z-0 blur-md opacity-50"
  src={screenshots[currentScreenshotIndex].image}
/&gt;
&lt;img
  className="object-contain w-full h-full absolute"
  src={screenshots[currentScreenshotIndex].image}
/&gt;</code></pre><p>The reason we have two <code>img</code> tags, is that the first one will act as the background and will be blurred while the second <code>img</code> tag displays the screenshot visible by the user.</p><p>We use <code>object-cover</code> for the first tag, so that the image will take all the available space in the container and we use <code>object-contain</code> in the second image so that the image retain its aspect ratio. Therefore, the user will be able to see the full screenshot without having part of it cropped out.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PWyd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PWyd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 424w, https://substackcdn.com/image/fetch/$s_!PWyd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 848w, https://substackcdn.com/image/fetch/$s_!PWyd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 1272w, https://substackcdn.com/image/fetch/$s_!PWyd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PWyd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png" width="1456" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ebe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1933666,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/161312730?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PWyd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 424w, https://substackcdn.com/image/fetch/$s_!PWyd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 848w, https://substackcdn.com/image/fetch/$s_!PWyd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 1272w, https://substackcdn.com/image/fetch/$s_!PWyd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febe95a5c-d7a0-4063-ba63-6c39a4e7d7a4_2224x962.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><pre><code>&lt;span className="bg-gray-900 rounded-full px-4 py-1 text-gray-300 text-xs absolute right-2 top-2 shadow-gray-700 shadow-xl"&gt;
  {currentScreenshotIndex + 1} / {screenshots.length}
&lt;/span&gt;</code></pre><p>Finally, we have a span tag which will render as a top-right indicator of which screenshot you&#8217;re currently viewing.</p><p>Now, that we have finished the carousel component, let&#8217;s add it in <code>GameDetails.jsx</code>. </p><p>First import the carousel at the top of the file.</p><pre><code>// previous code omitted for clarity
import ScreenshotCarousel from "./ScreenshotCarousel";</code></pre><p>Then, replace this :</p><pre><code>&lt;div className="w-full flex flex-col items-center p-4 h-96"&gt;
  TODO: Screenshot Carousel
&lt;/div&gt;</code></pre><p>With :</p><pre><code>&lt;div className="w-full flex flex-col items-center p-4 h-96"&gt;
  {currentGame.short_screenshots.length !== 0 &amp;&amp; (
    &lt;ScreenshotCarousel
      screenshots={currentGame.short_screenshots.slice(0)} // exclude the first screenshot since it's the same as the cover
    /&gt;
  )}
&lt;/div&gt;</code></pre><p>Now, if you run the project, you should get the following.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;4f0ab28a-fdef-4cc9-b26c-50334c3d456c&quot;,&quot;duration&quot;:null}"></div><h1>Conclusion</h1><p>You have finished your first React project! I have not touched on deployment (there are many different ways to go about it and plenty of tutorials online) and other React topics but I hope this was a good introduction.</p><p>Now that you have a basic foundation in React, you can start building your own projects and fill in your knowledge gaps using Google or taking a look at the official <a href="https://react.dev/">React docs</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe so you don&#8217;t miss out on future posts and tutorials</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p>You might be interested in game development? You can check out this next.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;5c0ee24a-4223-41e5-9c43-2130ec34a22d&quot;,&quot;caption&quot;:&quot;On my Substack, I usually publish tutorials teaching how to implement game mechanics in JavaScript using a library called KAPLAY.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Learn The Basics of JavaScript and KAPLAY to Make Games Quickly&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-02-16T01:31:23.744Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F727f68d4-7d66-484e-9f38-8a3d4dcc79e5_800x408.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/learn-the-basics-of-javascript-and&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:157223830,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:6,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p>]]></content:encoded></item><item><title><![CDATA[Learn React.js by Building a Game Search App | Part 2/3 - Building The Search Bar and Fetching Data From The Backend]]></title><description><![CDATA[Since we have mostly finished our setup in the previous part of this tutorial (You can read part 1 here).]]></description><link>https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game-68d</link><guid isPermaLink="false">https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game-68d</guid><dc:creator><![CDATA[JSLegendDev]]></dc:creator><pubDate>Sat, 12 Apr 2025 01:57:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!pAR1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pAR1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pAR1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 424w, https://substackcdn.com/image/fetch/$s_!pAR1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 848w, https://substackcdn.com/image/fetch/$s_!pAR1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 1272w, https://substackcdn.com/image/fetch/$s_!pAR1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pAR1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png" width="1456" height="754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:754,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5433375,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160606805?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pAR1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 424w, https://substackcdn.com/image/fetch/$s_!pAR1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 848w, https://substackcdn.com/image/fetch/$s_!pAR1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 1272w, https://substackcdn.com/image/fetch/$s_!pAR1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42cef15-5a0e-4167-8f9a-c1837483afed_2940x1522.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Since we have mostly finished our setup in the previous part of this tutorial (You can read part 1 <a href="https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game">here</a>). We&#8217;re ready to start working on the search page of the app which is the main page of the application.</p><p>In that page, the user should be able to use the search bar to search for a given game title or franchise and the app should call our backend which will call the RAWG API to retrieve a list of relevant games. Then, the app should display those games as cards containing a cover, a title, genres and how long it takes to beat the game if that data is available.</p><p>Now that you know what we need to build, let&#8217;s get started. I&#8217;ll introduce relevant concepts as we go.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe to not miss out when future parts of the tutorial are released!</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h1>Table of Contents</h1><ul><li><p><a href="https://jslegenddev.substack.com/i/160606805/how-is-react-initialized-in-our-project">How is React Initialized in Our Project?</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/what-are-react-components">What are React Components?</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/react-vs-react-dom">React VS React-DOM</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/making-our-first-react-component">Making Our First React Component</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/creating-the-search-component">Creating The Search Component</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/160606805/what-are-props-in-react">What Are Props in React?</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/what-are-hooks-in-react">What Are Hooks in React?</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/160606805/the-usestate-hook">The useState Hook</a></p></li></ul></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/setting-our-backend-to-enable-search-requests-to-the-rawg-api">Setting Our Backend to Enable Search Requests to The RAWG API</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/160606805/setting-up-express">Setting Up Express</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/defining-the-apigames-route">Defining The /api/games Route</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/fetching-search-data-on-the-frontend">Fetching Search Data on The Frontend</a></p><ul><li><p><a href="https://jslegenddev.substack.com/i/160606805/what-is-the-useeffect-hook-and-how-it-works">What is The useEffect Hook and How it Works?</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/how-is-data-fetched-with-useeffect">How is Data Fetched With useEffect?</a></p></li></ul></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/creating-a-custom-hook">Creating a Custom Hook</a></p></li><li><p><a href="https://jslegenddev.substack.com/i/160606805/conclusion">Conclusion</a></p></li></ul><p></p><h1>How is React Initialized in Our Project?</h1><p>If you look at your <code>src</code> folder, you should have the following files.</p><pre><code>src
|- assets
|- App.css
|- App.jsx
|- index.css
|- main.jsx</code></pre><p>Before we write any code, let&#8217;s remove the <code>assets</code> folder and the <code>App.css</code> file. We don&#8217;t need these two. Since we&#8217;re using Tailwind, the only place we need for CSS is the <code>index.css</code> file.</p><p>Now let&#8217;s take a look at <code>main.jsx</code>.</p><pre><code>import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  &lt;StrictMode&gt;
    &lt;App /&gt;
  &lt;/StrictMode&gt;,
)</code></pre><p>For React to be used in a webpage, it needs to inject the content of your app within your HTML. To be more precise, we first need to create a root within your <code>index.html</code> file from which your React app will be rendered.</p><p>If you take a look at your <code>index.html</code> file at the root of your project&#8217;s folder. You will notice the presence of a <code>div</code> with an id of &#8220;root&#8221;. This was added automatically by Vite.</p><pre><code>&lt;!doctype html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;link rel="icon" type="image/svg+xml" href="/vite.svg" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;Vite + React&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="root"&gt;&lt;/div&gt;
    &lt;script type="module" src="/src/main.jsx"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>Looking back at our React code :</p><pre><code>createRoot(document.getElementById('root')).render(
  &lt;StrictMode&gt;
    &lt;App /&gt;
  &lt;/StrictMode&gt;,
)</code></pre><p>We can see that to inject our React app within the <code>div</code> having the id &#8220;root&#8221;  we pass that <code>div</code> as a param of the <code>createRoot </code>function. It then creates an object that has a method called <code>render</code> . We immediately call it to pass our main React component called <code>App</code> wrapped under <a href="https://react.dev/reference/react/StrictMode">StrictMode</a> (which adds useful behaviors to more easily find bugs).</p><h1>What are React Components?</h1><p>The component is the main building block when working in React. It allows you to build UIs in a modular fashion allowing you to reuse components in multiple places. It also allows you to manage complexity by placing logic specific to a component within it.</p><p>At a basic level, a React component is just a JavaScript function that returns some JSX. Which, if you remember, is syntax that looks like HTML that you can write within JavaScript in a .jsx file.</p><p>Here is an example!</p><pre><code>function App(){ 
  return (
    &lt;div&gt;
      &lt;h1&gt;Hello World!&lt;/h1&gt;
    &lt;/div&gt;
  );
}</code></pre><p>We have a React component that returns a <code>div</code> containing an <code>h1 </code>tag with the content &#8220;Hello World!&#8221;. You will surely notice that we defined this component by defining a function with its name&#8217;s first letter capitalized. The convention when defining React components is to use the PascalCase notation rather than the usual camelCase.</p><p>Another thing that makes components really useful in React is that you can place React components within React components, enabling a modular approach to building UIs. Here&#8217;s another example.</p><pre><code>function Card() {
  return &lt;section&gt;This is a card&lt;/section&gt;
}

function App() {
  return (
    &lt;div&gt;
      &lt;h1&gt;Results&lt;/h1&gt;
      &lt;div&gt;
        &lt;Card /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}</code></pre><p>As you can see, to place a component within another you can use the self closing tag notation you&#8217;re used to from HTML along with the name of that component. </p><h1>React VS React-DOM</h1><pre><code>import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'</code></pre><p>Looking back at the import statements we had in <code>main.jsx</code>. You&#8217;ll notice that we&#8217;re importing things from both <code>react</code> and <code>react-dom</code>. You might be wondering why  they are separate entities?</p><p>React is the library that allows us to build UIs using reusable blocks called components that return JSX. React-DOM on the other hand is the one taking care of rendering React components on the DOM (Document Object Model) of the webpage.</p><p>That&#8217;s why <code>createRoot</code> is imported from <code>react-dom</code> and not <code>react </code>since it&#8217;s used to render to the DOM with the <code>render</code> method. The reason for this split is that React can be used outside of the web. For example, it can be used for mobile development with React Native. Instead of the JSX being translated to HTML elements that are rendered on a webpage, they&#8217;re are translated to native mobile UI elements instead. </p><p>In addition to React being used for mobile UIs, it can also be used to make pdfs with <a href="https://react-pdf.org/">react-pdf</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UoRC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UoRC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 424w, https://substackcdn.com/image/fetch/$s_!UoRC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 848w, https://substackcdn.com/image/fetch/$s_!UoRC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 1272w, https://substackcdn.com/image/fetch/$s_!UoRC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UoRC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png" width="1456" height="745" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:745,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:904673,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160606805?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UoRC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 424w, https://substackcdn.com/image/fetch/$s_!UoRC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 848w, https://substackcdn.com/image/fetch/$s_!UoRC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 1272w, https://substackcdn.com/image/fetch/$s_!UoRC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c52452e-a909-48cc-b500-49616b618c09_2676x1370.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">react-pdf in action</figcaption></figure></div><h1>Making Our First React Component</h1><p>Now that you understand how components work, let&#8217;s define our first component, the <code>App</code> component.</p><p>If you take a look at <code>main.jsx</code> again, you&#8217;ll notice that we&#8217;re already importing this component from a file called <code>App.jsx</code>.</p><pre><code>import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx' // &lt;--

createRoot(document.getElementById('root')).render(
  &lt;StrictMode&gt;
    &lt;App /&gt;
  &lt;/StrictMode&gt;,
)</code></pre><p>Let&#8217;s start from scratch by deleting everything in the existing <code>App.jsx </code>file<code> </code>and adding the following code which will display the app&#8217;s name and logo.</p><pre><code>export default function App() {
  return (
    &lt;main className="w-full p-2 flex flex-col items-center"&gt;
      &lt;div className="flex items-center"&gt;
        &lt;h1&gt;Game Database&lt;/h1&gt;
        &lt;img src="./logo.png" className="w-25 h-25" /&gt;
      &lt;/div&gt;
    &lt;/main&gt;
  );
}</code></pre><p>The <code>main</code> tag will contain all of the content of the search page. We define it as a <code>flex</code> container with a direction set to <code>column</code>. We then center the content within that <code>main</code> tag along the cross axis of the container using <code>items-center</code>. Below is a schematic displaying how the axis are defined according to the direction of the flex container. Remember that using <code>items-center </code>will center the content of the flex container along the cross axis while <code>justify-center</code> will center its content along the main axis.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ycV3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ycV3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 424w, https://substackcdn.com/image/fetch/$s_!ycV3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 848w, https://substackcdn.com/image/fetch/$s_!ycV3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 1272w, https://substackcdn.com/image/fetch/$s_!ycV3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ycV3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png" width="1456" height="625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:625,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:134303,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160606805?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ycV3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 424w, https://substackcdn.com/image/fetch/$s_!ycV3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 848w, https://substackcdn.com/image/fetch/$s_!ycV3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 1272w, https://substackcdn.com/image/fetch/$s_!ycV3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F842fe37d-5313-4fb2-8e44-48a35609c9d9_2734x1174.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We then make the <code>main</code> tag take the full available width of the page using <code>w-full</code> and give it a padding of 2 with <code>p-2</code>. All of the styling explained above is succinctly described in Tailwind by passing the needed class names to the className property of the JSX element.</p><pre><code>&lt;main className="w-full p-2 flex flex-col items-center"&gt;</code></pre><p>Now, you might be wondering why is it <code>className</code> instead of <code>class</code> that you&#8217;re accustomed to in HTML? The reason is that there is a difference in how the attribute is named in HTML vs the DOM which the underlying model representing a webpage. Since React operates on the DOM it was chosen to use <code>className</code> instead <code>class</code> which is what the DOM uses.</p><p>Now within the <code>main</code> tag, we define a <code>div</code> which will hold the app&#8217;s name and logo.</p><pre><code>&lt;div className="flex items-center"&gt;
  &lt;h1&gt;Game Database&lt;/h1&gt;
  &lt;img src="./logo.png" className="w-25 h-25" /&gt;
&lt;/div&gt;</code></pre><p>This <code>div</code> is set as a flex container with the direction set to <code>row </code>by default, so we don&#8217;t need to specify it.  We center its content along the cross axis so that the app&#8217;s title and logo are near each other at the center. </p><p>We then use an <code>img</code> tag to display the logo. You might have noticed that the path to the image does not contain the <code>public </code>folder despite the image being in that folder. That&#8217;s because Vite will automatically make whatever is in the <code>public</code> folder as if it was available from the root of the project, so you do not need to specify the <code>public</code> folder in the path.</p><p>Finally <code>w-25</code> and <code>h-25</code> sets the size of the logo using units defined by Tailwind which are responsive.</p><p>If you now start a development server using the command <code>npm run dev</code> in your terminal, you should get the following result.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DcJP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DcJP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 424w, https://substackcdn.com/image/fetch/$s_!DcJP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 848w, https://substackcdn.com/image/fetch/$s_!DcJP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!DcJP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DcJP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png" width="1456" height="786" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:786,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:154349,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160606805?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DcJP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 424w, https://substackcdn.com/image/fetch/$s_!DcJP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 848w, https://substackcdn.com/image/fetch/$s_!DcJP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!DcJP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc54531e3-7291-4c7f-bf5d-9502d2908495_2940x1588.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Let&#8217;s makes things prettier before moving on. In <code>index.html</code> add the following Tailwind classes to the body tag.</p><pre><code> &lt;body class="bg-gray-950 bg-[url('./background.png')] bg-repeat"&gt;</code></pre><p>We&#8217;re setting the background color to be of gray-950 which is a color predefined by Tailwind that is near black. We then set the background image and set it to repeat so that it tiles indefinitely making the image cover the page regardless of the size of the viewport.</p><p>You should have the following result.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Drw3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Drw3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 424w, https://substackcdn.com/image/fetch/$s_!Drw3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 848w, https://substackcdn.com/image/fetch/$s_!Drw3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!Drw3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Drw3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png" width="1456" height="786" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:786,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3608634,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160606805?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Drw3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 424w, https://substackcdn.com/image/fetch/$s_!Drw3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 848w, https://substackcdn.com/image/fetch/$s_!Drw3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!Drw3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F112bdb87-0f54-4f9b-b347-012eac5a2212_2940x1588.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As you can see the app&#8217;s name near the logo is not visible. To fix the issue, let&#8217;s add a nice looking gradient to it. To showcase how you can create custom classes composed of Tailwind classes, let&#8217;s add the following in our <code>index.css</code> file.</p><pre><code>.text-gradient {
  @apply bg-linear-to-r from-[#5c2bd9] via-[#fb6e6e] to-[#ffe137] bg-clip-text text-transparent;
}</code></pre><p>The key here is to use the <code>@apply </code>directive and then use the Tailwind classes you need.</p><pre><code>bg-linear-to-r from-[#5c2bd9] via-[#fb6e6e] to-[#ffe137]</code></pre><p>Creates the gradient.</p><pre><code>bg-clip-text text-transparent</code></pre><p>Makes sure the gradient is applied within the text&#8217;s letters.</p><p>If you go back to our React component and apply the custom Tailwind class we created along with a few other Tailwind classes, you should get the following result.</p><pre><code>  &lt;h1 className="text-gradient text-4xl font-bold"&gt;Game Database&lt;/h1&gt;</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QRhd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QRhd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 424w, https://substackcdn.com/image/fetch/$s_!QRhd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 848w, https://substackcdn.com/image/fetch/$s_!QRhd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!QRhd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QRhd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png" width="1456" height="786" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:786,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3613474,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://jslegenddev.substack.com/i/160606805?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QRhd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 424w, https://substackcdn.com/image/fetch/$s_!QRhd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 848w, https://substackcdn.com/image/fetch/$s_!QRhd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!QRhd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F523e78f0-b1cf-4a12-a8c1-06625995417c_2940x1588.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Creating The Search Component</h1><p>In the previous section we created our main React component acting as the entrypoint of our UI. However, we need to define more than just one component in our app. To keep things organized, let&#8217;s create a <code>components</code> folder under the <code>src</code> folder. Within, let&#8217;s define our next component, the <code>Search</code> component by creating a <code>Search.jsx</code> file.</p><pre><code>src
|- components
|    |- Search.jsx
|- App.jsx
|- main.jsx
|- index.css</code></pre><p>Add the following code in <code>Search.jsx</code>.</p><pre><code>export default function Search({ query, onChange }) {
  return (
    &lt;div className="w-full mb-4"&gt;
      &lt;div className="flex my-2 rounded-md shadow-xl bg-gray-800 p-4"&gt;
        &lt;img
          className="relative top-0.5 h-5 w-5"
          src="./search.svg"
          alt="search"
        /&gt;
        &lt;input
          className="w-full focus:outline-none ml-2 text-gray-300"
          type="text"
          placeholder="Search a game"
          value={query}
          onChange={onChange}
        /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre><p>The <code>Search</code> component is essentially a search bar. You&#8217;ll probably notice that we have params that are passed to this component. These are called props. It&#8217;s another one of React&#8217;s major concepts.</p><h2>What Are Props in React?</h2><p>What makes components flexible in React is the ability to pass props to them to change how they&#8217;re rendered. This makes components usable in more than just one screen or one place in the app.</p><p>You can view props the same way as params that you pass to a function.</p><p>Here is a basic example of how props are used in React.</p><pre><code>function Title(props) {
  return &lt;h1&gt;{props.name}&lt;/h1&gt;;
}

function App() {
  return (
     &lt;div&gt;
       &lt;Title name="Hello World" /&gt;
     &lt;/div&gt;
  );
}</code></pre><p>To pass props to a component you can simply start adding arbitrary attributes to where you call this component. In the example above, I decided to use an attribute called &#8220;name&#8221; that is then available under the props object where the component is defined. That&#8217;s why I can do <code>props.name</code> and get access to &#8220;Hello World&#8221;.</p><p>You might have noticed that we use curly braces when putting prop.name within the <code>h1</code> tag. This is important, so that the actual value, &#8220;Hello World&#8221; is what&#8217;s rendered rather than having literally &#8220;prop.name&#8221; displayed.</p><p>A more convenient way of dealing with props is to use destructuring which is a JavaScript concept allowing us to directly get the properties we need.</p><pre><code>function Title({name}) {
  return &lt;h1&gt;{name}&lt;/h1&gt;
}</code></pre><p>So now, if I need more than one prop, I can do the following.</p><pre><code>function Title({name, subtitle}){
  return (
    &lt;div&gt;
      &lt;h1&gt;{name}&lt;/h1&gt;
      &lt;h2&gt;{subtitle}&lt;/h2&gt;  
    &lt;/div&gt;
  );
}

function App() {
  return (
    &lt;div&gt;
      &lt;Title name="Hello World" subtitle="Hello World Again!" /&gt;
    &lt;/div&gt;
  );
}</code></pre><p>Now that you understand what props are and how they work, let&#8217;s further analyze the code for the <code>Search </code>component.</p><pre><code>export default function Search({ query, onChange }) {
  return (
    &lt;div className="w-full mb-4"&gt;
      &lt;div className="flex my-2 rounded-md shadow-xl bg-gray-800 p-4"&gt;
        &lt;img
          className="relative top-0.5 h-5 w-5"
          src="./search.svg"
          alt="search"
        /&gt;
        &lt;input
          className="w-full focus:outline-none ml-2 text-gray-300"
          type="text"
          placeholder="Search a game"
          value={query}
          onChange={onChange}
        /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre><p>Most of the Tailwind classes here are easy to understand if you already know CSS so I will not go over each of them. I would like however, to explain a few that may not be as straightforward to figure out :</p><ul><li><p><code>mb-4 </code>: Applies 4 units to the bottom margin of the element. If you had <code>ml</code> instead, it would be applied for margin left, <code>mr</code> for margin right, etc&#8230;</p></li><li><p><code>my-2 </code>: Applies 2 units to both the top and bottom margin of the element. Therefore, along the y axis. If you where to use <code>mx</code>, the left and right margins would be increased instead since they&#8217;re along the x axis.</p></li></ul><p>Now let&#8217;s take a look at the input element.</p><pre><code>export default function Search({ query, onChange }) {
  return (
    {/* previous code omitted for clarity */}
        &lt;input
          className="w-full focus:outline-none ml-2 text-gray-300"
          type="text"
          placeholder="Search a game"
          value={query} &lt;--
          onChange={onChange} &lt;--
        /&gt;
    {/* next code omitted for clarity */}
  );
}</code></pre><p>In React, the input attribute has two properties that are very useful.</p><ol><li><p>The <code>value</code> attribute allowing us to set the value inside the field.</p></li><li><p>The <code>onChange</code> attribute which takes a function and will run it every time the user types in the field. Which also allows us the get what was typed.</p></li></ol><p>We pass to these two attributes the <code>query</code> and <code>onChange</code> props we defined for the <code>Search</code> component. Now, go back to <code>App.jsx </code>and add the following code.</p><pre><code>import { useState } from "react";
import Search from "./components/Search";

export default function App() {
  const [searchQuery, setSearchQuery] = useState("");

  return (
    &lt;main className="w-full p-2 flex flex-col items-center"&gt;
      {/* previous code omitted for clarity */}
      &lt;div className="w-full max-w-4xl"&gt;
        &lt;Search
          query={searchQuery}
          onChange={(e) =&gt; setSearchQuery(e.target.value)}
        /&gt;
      &lt;/div&gt;
    &lt;/main&gt;
  );
}</code></pre><p>What is this <code>useState</code> function we&#8217;re using? It&#8217;s called a hook and hooks are another one of React&#8217;s major concepts.</p><h2>What Are Hooks in React?</h2><p>Hooks let you use different React features from your components. You can either use the built-in hooks or combine them to build your own.</p><h3>The useState Hook</h3><pre><code> const [searchQuery, setSearchQuery] = useState("");</code></pre><p>Here in particular, we&#8217;re using a state hook to set a state variable with the goal of holding the user&#8217;s search query and to update the UI when it changes.</p><p>From React&#8217;s docs, the usefulness of the <code>useState</code> hook is made apparent.</p><blockquote><p>To update a component with new data, two things need to happen:</p><ol><li><p><strong>Retain</strong> the data between renders.</p></li><li><p><strong>Trigger</strong> React to render the component with new data (re-rendering).</p></li></ol><p>Creating a state variable using the useState hook allows us to achieve both.</p><ol><li><p>A <strong>state variable</strong> to retain the data between renders.</p></li><li><p>A <strong>state setter function</strong> to update the variable and trigger React to render the component again.</p></li></ol></blockquote><p>In our case, <code>searchQuery</code> is the state variable and <code>setSearchQuery</code> is the state setter function. Finally, we pass within <code>useState() </code>the default state value we want to use.</p><p>When creating a state variable here is the convention that is usually adopted.</p><pre><code> const [nameOfTheVariable, setNameOfTheVariable] = useState(&lt;default value. can be a number, null, a string, etc...&gt;);</code></pre><p>What makes React convenient for building UIs rather than using vanilla JS is that you don&#8217;t need to manual update the UI by changing the content of various HTML nodes. As we saw above, by using a state variable, React will take care of updating the UI automatically when the state variable changes.</p><pre><code>&lt;Search
  query={searchQuery}
  onChange={(e) =&gt; setSearchQuery(e.target.value)}
/&gt;</code></pre><p>By passing an <code>onChange</code> function that calls the state setter function <code>setSearchQuery</code> and passing to it the value of what was typed using <code>e.target.value</code>, we update the <code>searchQuery</code> state variable.</p><p>Since we&#8217;re also passing the <code>searchQuery</code> state variable to the <code>Search</code> component, React will re-render the component everytime it&#8217;s changed.</p><p>You should get the following result.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;c4306e0d-1b87-49b0-917b-8ca7dda47db0&quot;,&quot;duration&quot;:null}"></div><p>We could have defined the state variable within the <code>Search</code> component but it&#8217;s better to do it in the <code>App</code> component instead. This is a better approach because we need to have access to <code>searchQuery</code> so that we can pass it to our backend and make the RAWG API call to get relevant search results.</p><h1>Setting Our Backend to Enable Search Requests to The RAWG API</h1><p>Now that the search bar is completed, let&#8217;s do some backend work to be able to query the RAWG API so it can provide the data we need.</p><h2>Setting Up Express</h2><p>In your backend repo, add the following code to your <code>index.js</code> file.</p><pre><code>import express from "express";
import cors from "cors";
import rateLimit from "express-rate-limit";
import fetch from "node-fetch";
import dotenv from "dotenv";

dotenv.config(); 

const API_KEY = process.env.API_KEY;
if (!API_KEY) {
  throw new Error("API key not found in environment variables");
}
const app = express();

const corsOptions = {
  origin: process.env.CLIENT_DOMAIN,
  allowedHeaders: ["Content-Type", "Authorization"],
};

app.use(cors(corsOptions));

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: "Too many requests from this IP, please try again later.",
});

// Apply rate limiting to all API requests
app.use(limiter);</code></pre><p>We first import all the needed packages. I explained why we needed each of them in part 1 of this tutorial. Refer to it <a href="https://jslegenddev.substack.com/i/160376861/installing-dependencies">here</a>.</p><p>We then call <code>dotenv.config()</code> to load our environment variables. We can then access them using <code>process.env.&lt;nameofthevariable&gt;</code></p><p>We intialize the express web framework which is the core of our backend and pass it to a const called <code>app</code>.</p><p>We then set our CORS (stands for Cross Origin Resource Sharing) options which allows us to only authorized requests coming from the domain of our frontend. One thing to know about CORS is that it&#8217;s only enforced through the web browser. That means, a mobile app could still make requests to our backend despite the options we set. Learn more about CORS <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">here</a>.</p><p>Using <code>app.use(cors(corsOptions))</code>, we apply the CORS options to our Express app.</p><p>Finally we set up our rate limiter preventing potential DDoS attacks where a client makes too many requests in order to take down our backend.</p><p>To make our backend actually do something let&#8217;s add the following code.</p><pre><code>app.get("/api/games", async (req, res) =&gt; {
    // TODO
});

const port = process.env.PORT || 3000;
app.listen(port, () =&gt; {
  console.log(`Server is running on port ${port}`);
});</code></pre><p>We define our first route that the client will be able to call and then make the Express app run by using <code>app.listen</code>.</p><h2>Defining The /api/games Route</h2><p>We want this route to return a list of games that matches the query passed by the client.</p><p>In Express, a route is defined this way.</p><pre><code>app.get("/api/games", async (req, res) =&gt; {
    // TODO
});</code></pre><p>Here we&#8217;re using a GET route since we&#8217;re not modifying a resource, we&#8217;re just requesting it. The first param is the route path we want to use, while the second param contains the function that&#8217;s going to run when the request is made. <code>req.query </code>will contain the query of the client. Clientside, the query will be passed as a param to the route path. Here&#8217;s an example.</p><pre><code>/api/games?search=Zelda</code></pre><p>Will result in <code>req.query </code>containing <code>{search: &#8220;Zelda&#8221;}</code>.</p><p>Now let&#8217;s continue by filling out the logic for the route.</p><pre><code>app.get("/api/games", async (req, res) =&gt; {
  const { search: searchQuery } = req.query;
 
  try {
    const response = await fetch(
      `https://api.rawg.io/api/games?key=${API_KEY}&amp;search=${searchQuery}`,
      {
        method: "GET",
        headers: {
          //Authorization: `Bearer ${API_KEY}`, some APIs requires using the authorization header but not this one
          "Content-Type": "application/json",
        },
      }
    );

    if (!response.ok) {
      return res
      .status(500)
      .json({ error: "Failed to fetch data from external API" });
    }

    const data = await response.json();
    return res.json(data);

  } catch (error) {
    console.error(error);
    return res
      .status(500)
      .json({ error: "Failed to fetch data from external API" });
  }
});</code></pre><p>Let&#8217;s break it down.</p><pre><code>const { search: searchQuery } = req.query;</code></pre><p>By using object destructuring we rename the <code>search</code> param to <code>searchQuery</code>. We do this because the RAWG API expect a search param and it would look weird to do :</p><pre><code>`https://api.rawg.io/api/games?key=${API_KEY}&amp;search=${search}`</code></pre><p>However, this is just a matter of personal preference. You can decide to stick with <code>search</code> if you want.<br><br>Within the Try/Catch statement we make the call to the RAWG API passing our API key from our environment variables.</p><pre><code>const response = await fetch(
      `https://api.rawg.io/api/games?key=${API_KEY}&amp;search=${searchQuery}`,
      {
        method: "GET",
        headers: {
          //Authorization: `Bearer ${API_KEY}`, some APIs requires using the authorization header but not this one
          "Content-Type": "application/json",
        },
      }
    );</code></pre><p>Something to note is that the RAWG API expects you to pass the API key as a param rather than putting it in the <code>Authorization</code> header like other APIs. Also, in Node.js you can&#8217;t use fetch by default as you can on the frontend. That&#8217;s why we had to install the node-fetch package.</p><p>The rest of the code is pretty standard error handling. Either way, we always end up returning json with either an error message or the data the client requested.</p><p>Finally, remember to run the backend using the following command :</p><pre><code><code>node index.js</code></code></pre><h1>Fetching Search Data on The Frontend</h1><p>Now that we have created the backend route we needed, let&#8217;s head back to our frontend to write the logic allowing us to fetch the relevant data according to what query the user entered in the search bar.</p><p>We need to first talk about another very important hook in React called <code>useEffect</code>.</p><h2>What is The useEffect Hook and How it Works?</h2><p>To put it simply, it&#8217;s a hook that lets you synchronize a component with an external system. In our case it&#8217;s going to be our backend.</p><p>In practice, it&#8217;s a function that&#8217;s called within a component and takes usually two params. The first is the function you want to run which is your &#8220;effect&#8221; and the other is a dependency array. It will run after the component is first rendered and every time a dependency in its dependency array changes.</p><pre><code>import { useEffect } from "react";

export default function myComponent() {

  useEffect(() =&gt; {
     console.log("will run only once in prod and twice in dev");
  }, []);

  return &lt;h1&gt;Test&lt;/h1&gt;;
}</code></pre><p>Above is an example of how we use <code>useEffect</code>. We have an empty dependency array meaning that the effect will run only after the component renders the first time. This occurs once in prod but twice in development because we wrapped our app with <code>StrictMode</code>.</p><p>You can use <code>useEffect</code> without a dependency array but it will run after every render.</p><pre><code>useEffect(() =&gt; {
  console.log("This runs after every render");
});</code></pre><h2>How is Data Fetched With useEffect?</h2><pre><code>import { useState, useEffect } from "react";

function myComponent() {
  const [getNewQuote, setGetNewQuote] = useState(false);
  const [currentQuote, setCurrentQuote] = useState("");

  useEffect(() =&gt; {
    
    // prevent calling api after the component first renders
    if (!getNewQuote) return;

    // async function needed within because the function passed to useEffect cannot be async.
    const fetchData = async () =&gt; {
      const response = await fetch("https://example.com/api/quote");
      const data = await reponse.json()
      
      setCurrentQuote(data.text);
      setGetNewQuote(true);

    }
    fetchData();
  }, [getNewQuote]);

  return (
    &lt;div&gt;
      &lt;h1&gt;{currentQuote}&lt;/h1&gt;
      &lt;button onClick={() =&gt; setSearch(true)}&gt;Display New Quote&lt;/button&gt;
    &lt;/div&gt;
  );

}</code></pre><p>In the example above, we fetch and display a new quote from a fictitious API when the user clicks on a button. You&#8217;ll notice that the dependency array of our <code>useEffect</code> has the <code>getNewQuote</code> state variable. This means that the hook will run once after the component is rendered the first time (twice in dev because of StrictMode) and then every time <code>getNewQuote</code> is modified.</p><p>In practice, if you design your app in a way that you need the user to click a button to make an API call, you do not need to use <code>useEffect</code> as you could put the fetching logic within a function you pass to the button&#8217;s <code>onClick</code> handler. The example above was just to demonstrate at a basic level how this hook works.</p><p>In our app&#8217;s case, its usage is warranted since we need to make an API call when the user finishes typing something rather than clicking a button.</p><p>While using this hook like shown above is ok for toy examples, in practice when dealing with APIs, I would recommend using a library like <a href="https://tanstack.com/query/latest/docs/framework/react/overview">Tanstack Query formerly known as React Query</a> which makes fetching, caching, synchronizing and updating server state a breeze.</p><p>I recommend <a href="https://react.dev/learn/you-might-not-need-an-effect">this</a> part of the React docs regarding <code>useEffect</code>. Oftentimes, you might be tempted to use it while it&#8217;s not necessary. The docs provide helpful guidance regarding this hook.</p><p>In our app, we will still use <code>useEffect</code> but also create our own hook to deal with loading states (what to show the user while we&#8217;re fetching data) and error handling. </p><h1>Creating a Custom Hook</h1><p>While React offers hooks like<code> useEffect</code> and <code>useState</code>, nothing prevents you from creating your own hook. This is what we&#8217;re going to do now. Our hook is going to be called <code>useFetch</code> and will be responsible for fetching the data we need from our backend and dealing with loading states and errors if they occur.</p><p>Create a new folder called <code>hooks</code> within the <code>src</code> folder and within a file called <code>useFetch.js</code>.</p><pre><code>src
|- components
|- hooks
    |- useFetch.js</code></pre><p>In the file you just created, add the following code :</p><pre><code>import { useState } from "react";

export default function useFetch(fetchFunction) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchData = async () =&gt; {
    try {
      setLoading(true);
      const result = await fetchFunction();
      setData(result);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  const reset = () =&gt; {
    setData(null);
    setLoading(false);
    setError(null);
  };

  return { data, loading, error, fetchData, reset };
}</code></pre><p><code>useFetch</code> will expect a fetch function which will contain the API call and pass it to its own <code>fetchData</code> function and set the appropriate loading or error states depending ont the situation. The hook returns the data, the loading state, the error state, the fetchData function to fetch data again if needed and finally, a reset function to reset the state if needed.</p><p>Let&#8217;s create the fetch function specific to getting the list of games related to a search query. For this purpose, let&#8217;s create file called <code>api.js</code> within the <code>src</code> folder. In it, add the following code.</p><pre><code>src
|- components
|- hooks
|- api.js</code></pre><pre><code>const API_ENDPOINT = import.meta.env.VITE_API_ENDPOINT;

export async function fetchGames({ query }) {
  const response = await fetch(`${API_ENDPOINT}/games?search=${query}`);
  return await response.json();
}</code></pre><p>For the code above to work we need to define an environment variable in our frontend. This is done just to make it easy to swap in and out the domain of the backend as needed and not for security reasons.</p><p>Create at the root of your project a <code>.env</code> file containing the domain of our backend.</p><pre><code>VITE_API_ENDPOINT="http://localhost:3000/api"</code></pre><p>Now, we can go to <code>App.jsx</code> where we will write the logic to fetch the needed data when the user enters a query in the search bar.</p><pre><code>import { useEffect, useState } from "react";
import useFetch from "./hooks/useFetch";
import { fetchGames } from "./api";

export default function App() {
  const [searchQuery, setSearchQuery] = useState("");

  const { data, loading, fetchData, error, reset } = useFetch(() =&gt;
    fetchGames({ query: searchQuery })
  );

  useEffect(() =&gt; {
    const timeoutId = setTimeout(async () =&gt; {
      if (searchQuery.trim()) {
        await fetchData();
        return;
      }
    }, 500);

    return () =&gt; clearTimeout(timeoutId);
  }, [searchQuery]);

  return (
// ... rest of the code omitted for clarity</code></pre><p>Let&#8217;s break it down.</p><pre><code>const [searchQuery, setSearchQuery] = useState("");
const { data, loading, fetchData, error, reset } = useFetch(() =&gt;
    fetchGames({ query: searchQuery })
);</code></pre><p>We call <code>useFetch</code> and pass to it an anonymous function that will call our <code>fetchGames</code> function created in the <code>api.js</code> file and pass to it the user&#8217;s search query. The reason we&#8217;re wrapping it in a function is that we don&#8217;t want to call <code>fetchGames</code> immediately but rather let <code>useFetch</code> call it in its <code>fetchData</code> function. We also couldn&#8217;t pass it on its own because we wouldn&#8217;t be able to pass the search query param along.</p><p>Once called, the <code>useFetch</code> hook will return all that we need to display the relevant UI depending on the situation.</p><pre><code>useEffect(() =&gt; {
    const timeoutId = setTimeout(async () =&gt; {
      if (searchQuery.trim()) {
        await fetchData();
      }
    }, 500);

    return () =&gt; clearTimeout(timeoutId);
}, [searchQuery]);</code></pre><p>Next, we have our <code>useEffect</code>. We set a timeout of 500ms as to provide enough time after the user has typed before making a request otherwise we would be making a call every time a letter changes which would be wasteful. This is called debouncing.</p><p>Then, we have an if statement with a call to trim() that removes any whitespace at the beginning and end of a string. If the user didn&#8217;t type anything yet or just uses spaces in the search bar, an empty string will be returned which is falsy in JavaScript. Therefore, no API call will be made. </p><p>Since <code>useEffect</code> will run after the component first renders, in that case the search bar would be empty, we wouldn&#8217;t want to make an API call after all.</p><p>The function we pass to <code>useEffect</code> returns a function that clears the timeout we just created. This is called a cleanup function and is optional when using this hook but needed in our case. This is so we can avoid memory leaks. If we were to not use one here, every time the hook runs, we would create a new timer that would never be deleted.</p><p>That&#8217;s about it. The logic for fetching data is done. To make sure that it actually works, let&#8217;s <code>console.log</code> the data fetched. Below the <code>useEffect</code> we just added, create another one with the following content. (Yes, you can have more than one <code>useEffect</code> per component.)</p><pre><code>export default function App() {

  useEffect(() =&gt; {
    // code omitted ...
  }, [searchQuery]);

  useEffect(() =&gt; {
    if (data) console.log(data)
  }, [data]);

  return (
  //... code omitted
}</code></pre><p>Make sure to re-run both the frontend with <code>npm run dev</code> and the backend with <code>node index.js</code>. Now, if you try using the app. You should get the following result.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;2cb167a6-b751-4e12-aa49-985d0283491b&quot;,&quot;duration&quot;:null}"></div><h1>Conclusion</h1><p>In the last part of this tutorial, we will write the logic to display the search results in nice looking game card components and then work on displaying more details when the user clicks on a specific card.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://jslegenddev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe for more interesting tutorials!</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p>UPDATE : Part 3 is now available!</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;e39a6316-d3d6-4dd4-9a66-4d82607fb76c&quot;,&quot;caption&quot;:&quot;In the previous part (Here are links to part 1 and part 2), we finished setting up the logic to fetch data from our backend. Now it&#8217;s time to display the data in a nice looking UI.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Learn React.js by Building a Game Search App | Part 3/3 - Finishing The App&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:173229486,&quot;name&quot;:&quot;JSLegendDev&quot;,&quot;bio&quot;:&quot;Software dev&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146d4f55-ad77-459b-a94c-fe5922268673_250x250.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-04-24T02:59:11.695Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15871a19-0e95-4a23-8006-c13ef8623daa_2940x1522.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://jslegenddev.substack.com/p/learn-reactjs-by-building-a-game-eb9&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:161312730,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;JSLegendDev&#8217;s Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ffa63e4-c735-4985-8071-0c10cd1abfc6_250x250.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div>]]></content:encoded></item></channel></rss>