<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Game Development - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Game Development - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 18 May 2026 10:47:22 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/game-development/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Reusable Modular Unity Packages to Speed Up Development ]]>
                </title>
                <description>
                    <![CDATA[ How many times have you rewritten the same systems across different Unity projects? Or copied entire folders from an old project, only to spend hours fixing references, renaming namespaces, and adapti ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-reusable-modular-unity-packages-to-speed-up-development/</link>
                <guid isPermaLink="false">6998ee96a20b74e093d7e671</guid>
                
                    <category>
                        <![CDATA[ unity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ C# ]]>
                    </category>
                
                    <category>
                        <![CDATA[ modularity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Talha Cagatay ISIK ]]>
                </dc:creator>
                <pubDate>Fri, 20 Feb 2026 23:30:30 +0000</pubDate>
                <media:content url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/5e1e335a7a1d3fcc59028c64/7a3eeaef-4bab-403c-b9ef-3ebc785efb2f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>How many times have you rewritten the same systems across different Unity projects? Or copied entire folders from an old project, only to spend hours fixing references, renaming namespaces, and adapting the code to fit a slightly different architecture?</p>
<p>That repetition doesn’t just waste time – it slows down your development and creates maintenance headaches across projects.</p>
<p>This guide walks you through a set of reusable, modular Unity packages you can drop into any project to speed up development. You’ll build them once as packages and install them via Git wherever you need them, instead of reimplementing the same systems every time.</p>
<p>The article covers four core packages:</p>
<ol>
<li><p><strong>com.core.initializer</strong> – Finds and initializes your game controllers at runtime (MVC-style) so startup and dependencies centralized.</p>
</li>
<li><p><strong>com.core.data</strong> – Handles local (and optionally cloud) save data using MemoryPack binary serialization and a provider abstraction so you can switch or extend storage backends.</p>
</li>
<li><p><strong>com.core.ui</strong> – Manages popups and full-screen UIs in a consistent way so you can show dialogs, panels, and screens without duplicating logic.</p>
</li>
<li><p><strong>com.core.dotween</strong> – Wraps the DoTween tween engine as a Unity package so <strong>com.core.ui</strong> (and other packages) can reference it for animations.</p>
</li>
</ol>
<p>In this tutorial, we’ll build all four packages step by step. The Initializer, Data, UI, and DoTween packages are all documented so you can easily follow along. All packages are also upload to github which you can find the links at the end.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-what-you-will-learn">What You Will Learn</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-set-up-your-development-project">Set Up Your Development Project</a></p>
</li>
<li><p><a href="#heading-package-1-the-initializer">Package 1: The Initializer Package</a></p>
</li>
<li><p><a href="#heading-package-2-the-data-package">Package 2: The Data Package</a></p>
</li>
<li><p><a href="#heading-package-3-comcoredotween">Package 3: The DoTween Package</a></p>
</li>
<li><p><a href="#heading-package-4-the-ui-package">Package 4: The UI Package</a></p>
</li>
<li><p><a href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-what-you-will-learn"><strong>What You Will Learn</strong></h2>
<ul>
<li><p>How to create a Unity package with the Package Manager</p>
</li>
<li><p>How to set up the package structure</p>
</li>
<li><p>How to built a centralized initialization flow for your packages</p>
</li>
<li><p>How to use UniTask for async initialization on Unity's main thread</p>
</li>
<li><p>How the Data package uses <code>IDataProvider</code> and <strong>MemoryPack</strong> for local save/load</p>
</li>
<li><p>How the UI package uses popups (stack) and screens (single active) with DoTween animations</p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before you start, make sure you have:</p>
<ul>
<li><p>Unity installed (any Long Term Support version compatible with the packages. The referenced packages target Unity 6000.3)</p>
</li>
<li><p>Git installed</p>
</li>
<li><p>Jetbrains Rider or Visual Studio Community installed</p>
</li>
<li><p>Know how to use Unity game engine</p>
</li>
<li><p>Know C# and async/await structures</p>
</li>
</ul>
<p>You will use the Unity Package Manager to create packages, then upload and install them via Git in other projects.</p>
<h2 id="heading-set-up-your-development-project"><strong>Set Up Your Development Project</strong></h2>
<p>Create a new Unity project if you don’t already have one. We’ll use it as a playground to build and test your packages. This project (and not the packages themselves) is your development environment.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770938731026/c2afbae6-69a7-4cd2-a2c3-2d43b0c68fe6.png" alt="Creating a new unity project via Unity Hub" width="2113" height="1344" loading="lazy">

<h2 id="heading-package-1-the-initializer"><strong>Package 1: The Initializer</strong></h2>
<p>The first package is <strong>com.core.initializer</strong>. It finds all your controllers, runs their async initialization before the first scene loads, and lets the rest of the game access them via a single handler.</p>
<h3 id="heading-what-comcoreinitializer-does">What com.core.initializer Does</h3>
<ul>
<li><p><strong>Early initialization</strong>: Runs at <code>RuntimeInitializeLoadType.BeforeSceneLoad</code>, so all controllers exist and are initialized before the first scene loads.</p>
</li>
<li><p><strong>Central access</strong>: Stores each controller by its type and exposes them through a type-safe getter.</p>
</li>
<li><p><strong>Completion signaling</strong>: Exposes a static event (<code>ControllersInitialized</code>) and a <code>UniTaskCompletionSource</code> (<code>InitializationCompleted</code>) so you can run code only after all controllers have finished initializing.</p>
</li>
</ul>
<h3 id="heading-create-the-initializer-package">Create the Initializer Package</h3>
<p>To start, open Window → Package Manager (under Package Management). Then click the + button and choose Create package.</p>
<p>Name the package <code>com.core.initializer</code> (or your preferred name).</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770939190680/3d9d3c0e-94f7-40bf-b5b7-d3d05ff90301.png" alt="Opening Unity Package Manager" width="907" height="630" loading="lazy">

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771030017622/bc551fb9-69d5-4fa6-8309-293fd34d6cc5.png" alt="Creating a new package" width="928" height="579" loading="lazy">

<p>Unity creates the essential package files for you.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770939568125/86637464-89c7-4dc6-861b-cb071cc2d942.png" alt="Example package folders and files" width="607" height="563" loading="lazy">

<p>You can edit <code>package.json</code> and the assembly definition (asmdef) names to match your naming. The full set of changes is not listed here. The final package is available on GitHub (linked at the end).</p>
<h3 id="heading-add-the-unitask-dependency">Add the UniTask Dependency</h3>
<p>Unity runs on the main thread, and C# Tasks can be problematic in that context. They don’t know about the Unity Editor's play state and keep running after exiting play mode for example. So you’ll need to handle these cases manually. For these reasons, this package uses <a href="https://github.com/Cysharp/UniTask">UniTask</a> instead of C# Tasks for async operations.</p>
<p>Git-based dependencies are supported at the project level but not at the package level. Add UniTask via OpenUPM by following the <a href="https://openupm.com/packages/com.cysharp.unitask/#modal-manualinstallation">manual installation steps</a>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770988159264/9d2db898-9e1f-4514-aeda-53fb4a190045.png" alt="Adding OpenUPM and UniTask to scoped registries" width="1006" height="746" loading="lazy">

<p>Add UniTask as a dependency in package.json of initializer package:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/698d2f3c217bebae73c3f5e8/035b9242-f111-4c96-8483-4b02452a13bc.png" alt="package.json file of initializer package" width="377" height="292" loading="lazy">

<pre><code class="language-json">"dependencies": {
  "com.cysharp.unitask": "2.5.10"
}
</code></pre>
<p>Also reference UniTask in the asmdef file.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771255181446/9812cea2-d278-4920-88e5-43e89cd41a91.png" alt="Referencing UniTask within Initializer package's asmdef file" width="882" height="470" loading="lazy">

<p>You’ll need to follow this dependency flow for the rest of the packages you create.</p>
<h3 id="heading-implement-the-package-structure">Implement the Package Structure</h3>
<p>The initializer uses an MVC-like architecture. Controllers handle both initialization and game logic. You could later split initialization into services and keep business logic in controllers (for example, an MVCS-style setup). This tutorial keeps things simple and uses only controllers.</p>
<p>Target layout:</p>
<ul>
<li><p><strong>Runtime/Interface/</strong> – <code>IController</code></p>
</li>
<li><p><strong>Runtime/Helper/</strong> – <code>Creator</code> (reflection-based instance creation)</p>
</li>
<li><p><strong>Runtime/</strong> – <code>ControllerHandler</code> (orchestrates initialization)</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771255216106/90741270-2166-4694-874f-fcc7aec539fe.png" alt="Initializer package final folders and files" width="434" height="543" loading="lazy">

<p>All controllers implement the <code>IController</code> interface. The <code>Initialize</code> method returns a <code>UniTask</code> so initialization can be async on the main thread.</p>
<pre><code class="language-csharp">using Cysharp.Threading.Tasks;

namespace com.core.initializer
{
    public interface IController
    {
        bool IsInitialized { get; }

        UniTask Initialize();
    }
}
</code></pre>
<h3 id="heading-create-the-creator-helper">Create the Creator Helper</h3>
<p>You’ll need to find all types that implement <code>IController</code>, create instances of them at runtime, and also find MonoBehaviours that implement <code>IController</code> (since those cannot be created via reflection). The <code>Creator</code> class does both.</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace com.core.initializer
{
    public static class Creator
    {
        /// &lt;summary&gt;
        /// Creates instances of every type which inherits from T interface.
        /// &lt;/summary&gt;
        public static IEnumerable&lt;T&gt; CreateInstancesOfType&lt;T&gt;(params Type[] exceptTypes)
        {
            var interfaceType = typeof(T);

            var result = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x =&gt; x.GetTypes()).Where
                (
                 x =&gt; interfaceType.IsAssignableFrom(x) &amp;&amp;
                      !x.IsInterface                    &amp;&amp;
                      !x.IsAbstract                     &amp;&amp;
                      exceptTypes.All(type =&gt; !x.IsSubclassOf(type) &amp;&amp; x != type)
                ).Select(Activator.CreateInstance);

            return result.Cast&lt;T&gt;();
        }

        public static IEnumerable&lt;T&gt; GetMonoControllers&lt;T&gt;() =&gt; UnityEngine.Object.FindObjectsByType&lt;MonoBehaviour&gt;(FindObjectsInactive.Include, FindObjectsSortMode.None).OfType&lt;T&gt;();
    }
}
</code></pre>
<p>Using reflection at runtime has some overhead. You can optimize this later with baking the reflection in editor and using it at runtime. For MonoBehaviours, <code>FindObjectsByType</code> is used here for simplicity. In a larger project, you might use a dependency injection solution such as <a href="https://github.com/hadashiA/VContainer">VContainer</a> or <a href="https://github.com/gustavopsantos/Reflex">Reflex</a>.</p>
<h3 id="heading-implement-the-controllerhandler">Implement the ControllerHandler</h3>
<p>The <code>ControllerHandler</code> uses <code>Creator</code> to gather all <code>IController</code> instances (both reflection-created and MonoBehaviours), initializes them in order, and exposes an event and a <strong>UniTaskCompletionSource</strong> so the rest of the game can wait for startup to finish.</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace com.core.initializer
{
    public class ControllerHandler
    {
        public static readonly UniTaskCompletionSource InitializationCompleted = new();

        public static event Action ControllersInitialized;

        private static Dictionary&lt;Type, IController&gt; _controllers = new();

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        public static async void Initialize()
        {
            var controllers = Creator.CreateInstancesOfType&lt;IController&gt;(typeof(MonoBehaviour)).ToList();
            controllers.AddRange(Creator.GetMonoControllers&lt;IController&gt;());

            Debug.Log("Initializing controller");

            foreach (var controller in controllers)
            {
                Debug.Log($"&lt;color=yellow&gt;Initializing {controller.GetType().Name}&lt;/color&gt;");
                await controller.Initialize();
                _controllers.Add(controller.GetType(), controller);
                Debug.Log($"&lt;color=green&gt;Initialized {controller.GetType().Name}&lt;/color&gt;");
            }

            Debug.Log("&lt;color=green&gt;All controllers are initialized&lt;/color&gt;");
            ControllersInitialized?.Invoke();
            InitializationCompleted?.TrySetResult();
        }

        public static T GetController&lt;T&gt;() where T : class, IController =&gt; _controllers[typeof(T)] as T;
    }
}
</code></pre>
<h3 id="heading-how-to-test-the-initializer">How to Test the Initializer</h3>
<ol>
<li><p>Create a class that implements <code>IController</code> (or a MonoBehaviour that implements it).</p>
</li>
<li><p>Implement <code>Initialize()</code> with your setup logic (you can use <code>await</code> and UniTask).</p>
</li>
<li><p>After initialization, access the controller with:<br><code>var myController = ControllerHandler.GetController&lt;MyController&gt;();</code></p>
</li>
</ol>
<p>You can also subscribe to <code>ControllerHandler.ControllersInitialized</code> or await <code>ControllerHandler.InitializationCompleted.Task</code> to run code after all controllers are ready.</p>
<p>There are some limitations here. First, it can be hard to handle dependencies between different controllers. Second, it’s easy to run into circular dependencies.</p>
<p>When you check the <a href="https://github.com/TalhaCagatay/com.core.initializer">GitHub repo</a>, a DI framework may already be implemented to address these limitations. Make sure to open an issue if you want this feature.</p>
<h2 id="heading-package-2-the-data-package">Package 2: The Data Package</h2>
<p>com.core.data handles local saving with a binary serializer. The package uses <a href="https://github.com/Cysharp/MemoryPack">MemoryPack</a> for fast, binary serialization and defines an <code>IDataProvider</code> interface so you can plug in different providers (local, cloud, or hybrid).</p>
<p><strong>Package dependency:</strong> <code>com.cysharp.memorypack</code> (version 1.10.0). Add it to your project via OpenUPM and add it to package.json and the asmdef for com.core.data.</p>
<p>Create a new package and name it com.core.data. Use a structure such as:</p>
<ul>
<li><p><strong>Runtime/Interface/</strong> – <code>IDataProvider</code></p>
</li>
<li><p><strong>Runtime/Providers/</strong> – <code>LocalDataProvider</code></p>
</li>
<li><p><strong>Runtime/</strong> – optional sample <code>DataController</code> (in your game assembly) that implements <code>IController</code> and uses an <code>IDataProvider</code></p>
</li>
</ul>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/698d2f3c217bebae73c3f5e8/b6192b39-04d1-4c5a-90e6-b0ed9f1a4ce4.png" alt="Adding MemoryPack to scoped registries" style="display:block;margin:0 auto" width="1214" height="747" loading="lazy">

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771266043719/9cd79a74-4a72-4882-b803-8c53ca95eaa0.png" alt="Data package final folders and files" width="353" height="467" loading="lazy">

<p>All data providers use the IDataProvider interface.</p>
<pre><code class="language-csharp">using Cysharp.Threading.Tasks;

namespace com.core.data
{
    public interface IDataProvider
    {
        bool     IsInitialized { get; }
        void     Save&lt;T&gt;(string key, T data);
        T        Load&lt;T&gt;(string key, T defaultValue = default);
        bool     HasKey(string  key);
        string[] GetKeys();
        UniTask  Delete(string key);
        UniTask  DeleteAll();
    }
}
</code></pre>
<p>You will use UniTask in the data package as well. Add com.cysharp.unitask to package.json since both IController and IDataProvider uses it.</p>
<p>Here’s the DataController:</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Text;
using com.core.data;
using com.core.initializer;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace com.core.data
{
    public class DataController : IController
    {
        private const string PLAYER_VERSION_KEY = "player-version-key";

        private IDataProvider _provider;

        public bool IsInitialized { get; private set; }

        public UniTask Initialize()
        {
            _provider = new LocalDataProvider();
            SaveUserVersion();
            IsInitialized = true;
            return UniTask.CompletedTask;
        }

        private void SaveUserVersion()
        {
            var versionHistory = Load(PLAYER_VERSION_KEY, new List&lt;string&gt;());
            var sb             = new StringBuilder();
            versionHistory.ForEach(vh =&gt; sb.Append(vh + Environment.NewLine));
            Debug.Log($"[DataController] Player version history: {sb}");

            if (!versionHistory.Contains(Application.version))
            {
                Debug.Log($"[DataController] Player's current version: {Application.version}");
                versionHistory.Add(Application.version);
                Save(PLAYER_VERSION_KEY, versionHistory);
            }
        }

        public void Save&lt;T&gt;(string key, T data) =&gt; _provider.Save(key, data);

        public T Load&lt;T&gt;(string key, T defaultValue = default) =&gt; _provider.Load(key, defaultValue);

        public bool         HasKey(string key)  =&gt; _provider.HasKey(key);
        public string[]     GetKeys()           =&gt; _provider.GetKeys();
        public UniTask      Delete(string key)  =&gt; _provider.Delete(key);
        public UniTask      DeleteAll()         =&gt; _provider.DeleteAll();
        public List&lt;string&gt; GetVersionHistory() =&gt; Load(PLAYER_VERSION_KEY, new List&lt;string&gt;());
    }
}
</code></pre>
<p>The controller delegates all logic to LocalDataProvider, which uses MemoryPack to serialize and write bytes to a "Data" folder under <code>Application.persistentDataPath</code>. The controller also keeps the user's app version history, which is useful for upgrade prompts or blocking old versions.</p>
<p>Here’s the full implementation:</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Cysharp.Threading.Tasks;
using MemoryPack;
using UnityEngine;

namespace com.core.data
{
    public class LocalDataProvider : IDataProvider
    {
        private const    string StorageFolderName = "Data";
        private readonly string _storagePath;

        private static readonly UTF8Encoding KeyEncoding = new(false);

        public bool IsInitialized =&gt; true;

        public LocalDataProvider()
        {
            _storagePath = Path.Combine(Application.persistentDataPath, StorageFolderName);
            EnsureStorageDirectoryExists();
        }

        public void Save&lt;T&gt;(string key, T data)
        {
            if (string.IsNullOrEmpty(key)) { Debug.LogWarning("[LocalDataProvider] Save called with null or empty key."); return; }
            if (data == null) { Debug.LogWarning("[LocalDataProvider] Save called with null data."); Delete(key).Forget(); return; }
            try
            {
                byte[] bin = MemoryPackSerializer.Serialize(data);
                WriteBytes(key, bin);
            }
            catch (Exception ex) { Debug.LogError($"[LocalDataProvider] Save failed for key '{key}': {ex.Message}"); throw; }
        }

        public T Load&lt;T&gt;(string key, T defaultValue = default)
        {
            if (string.IsNullOrEmpty(key)) { Debug.LogWarning("[LocalDataProvider] Load called with null or empty key."); return defaultValue; }
            if (!ReadBytes(key, out byte[] bin)) return defaultValue;
            try { return MemoryPackSerializer.Deserialize&lt;T&gt;(bin) ?? defaultValue; }
            catch (Exception ex) { Debug.LogError($"[LocalDataProvider] Load failed for key '{key}': {ex.Message}"); return defaultValue; }
        }

        public bool HasKey(string key) =&gt; !string.IsNullOrEmpty(key) &amp;&amp; File.Exists(GetFilePath(key));

        public UniTask Delete(string key)
        {
            if (string.IsNullOrEmpty(key)) return UniTask.CompletedTask;
            try { var filePath = GetFilePath(key); if (File.Exists(filePath)) File.Delete(filePath); }
            catch (Exception ex) { Debug.LogError($"[LocalDataProvider] Delete failed for key '{key}': {ex.Message}"); }
            return UniTask.CompletedTask;
        }

        public UniTask DeleteAll()
        {
            try
            {
                if (!Directory.Exists(_storagePath)) return UniTask.CompletedTask;
                foreach (string file in Directory.GetFiles(_storagePath))
                {
                    try { File.Delete(file); }
                    catch (Exception ex) { Debug.LogWarning($"[LocalDataProvider] Could not delete file '{file}': {ex.Message}"); }
                }
            }
            catch (Exception ex) { Debug.LogError($"[LocalDataProvider] DeleteAll failed: {ex.Message}"); }
            return UniTask.CompletedTask;
        }

        public string[] GetKeys()
        {
            if (!Directory.Exists(_storagePath)) return Array.Empty&lt;string&gt;();
            var keys = new List&lt;string&gt;();
            foreach (string file in Directory.GetFiles(_storagePath))
            {
                try { string key = DecodeKeyFromFileName(Path.GetFileName(file)); if (key != null) keys.Add(key); }
                catch { Debug.LogWarning($"[LocalDataProvider] Could not read file '{file}'."); }
            }
            return keys.ToArray();
        }

        private void EnsureStorageDirectoryExists() { if (!Directory.Exists(_storagePath)) Directory.CreateDirectory(_storagePath); }
        private void WriteBytes(string key, byte[] bin) { EnsureStorageDirectoryExists(); File.WriteAllBytes(GetFilePath(key), bin); }
        private bool ReadBytes(string key, out byte[] bin) { string filePath = GetFilePath(key); if (!File.Exists(filePath)) { bin = null; return false; } bin = File.ReadAllBytes(filePath); return true; }
        private string GetFilePath(string key) =&gt; Path.Combine(_storagePath, EncodeKeyToFileName(key));
        private static string EncodeKeyToFileName(string key) { byte[] bytes = KeyEncoding.GetBytes(key); string base64 = Convert.ToBase64String(bytes); return base64.Replace('+', '-').Replace('/', '_'); }
        private static string DecodeKeyFromFileName(string fileName) { try { string base64 = fileName.Replace('-', '+').Replace('_', '/'); return KeyEncoding.GetString(Convert.FromBase64String(base64)); } catch { return null; } }
    }
}
</code></pre>
<p>You can implement another IDataProvider (for example JSON via <a href="https://openupm.com/packages/com.newtonsoft.json/">Newtonsoft</a> or <a href="https://docs.unity3d.com/6000.3/Documentation/PlayerPrefs.html">PlayerPrefs</a>) and swap it in your DataController.</p>
<p>In production, you might use a local and a cloud provider and sync data based on the player's online status. The com.core.data <a href="https://github.com/TalhaCagatay/com.core.data">GitHub repo</a> may be updated with cloud saving and syncing. You can open an issue if you need that feature.</p>
<h2 id="heading-package-3-comcoredotween"><strong>Package 3: com.core.dotween</strong></h2>
<p>com.core.dotween is a Unity package wrapper around <a href="https://dotween.demigiant.com/">DOTween</a> (Demigiant). DoTween is a widely used, production-ready tween engine. The UI package uses it for popup show/hide animations.</p>
<p>DOTween is not available on OpenUPM, so you’ll typically need to download it from the <a href="https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676">Unity Asset Store</a> and import it into your project (for example, under Assets/Plugins).</p>
<p>Then you’ll run the DOTween setup window(it should pop automatically after importing) and click Create ASMDEF so assembly definition files are generated and you can reference them from other packages.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/698d2f3c217bebae73c3f5e8/1d2a1ea1-1cc4-4bc0-95b8-5e23d22d3f17.png" alt="1d2a1ea1-1cc4-4bc0-95b8-5e23d22d3f17" width="547" height="632" loading="lazy">

<p>Next, create a new package (for example, com.core.dotween) and move the Demigiant folder from Assets/Plugins into the package folder so that com.core.ui (or any other package) can depend on com.core.dotween via Package Manager or Git.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771252964839/36d9e2f0-31a1-445f-b536-8560d0987131.png" alt="Dotween package folders and files" width="221" height="167" loading="lazy">

<p>After that, reference com.core.dotween in com.core.ui's asmdef(You will create the UI package right after this) so you can use <code>DG.Tweening</code> namespace in BasePopup and other UI scripts.</p>
<p>com.core.dotween has no external dependencies – it only wraps the DOTween asset.</p>
<h2 id="heading-package-4-the-ui-package">Package 4: The UI Package</h2>
<p>com.core.ui centralizes how you show popups and full-screen UIs. It contains:</p>
<ul>
<li><p><strong>Popups</strong> – Modal or non-modal dialogs (confirm/cancel, alerts, forms) with a consistent API so you don’t duplicate panel logic in every screen. Popups are stacked – the top one is closed first.</p>
</li>
<li><p><strong>Screens</strong> – Full-screen panels (for example, loading, main menu). Only one screen is active at a time, and switching a screen hides the current one and shows the new one.</p>
</li>
</ul>
<p>Create a new package named com.core.ui with a structure such as:</p>
<ul>
<li><p><strong>Runtime/Interface/</strong> – <code>IUI</code></p>
</li>
<li><p><strong>Runtime/UIs/</strong> – <code>BasePopup</code>, <code>BaseScreen</code></p>
</li>
<li><p><strong>Runtime/</strong> – <code>UIController</code>, <code>UIParent</code></p>
</li>
<li><p><strong>Runtime/Resources/UIPrefabs/</strong> – <code>UIParent</code> prefab, and subfolders Popups and Screens</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771254115546/a71fd415-901c-472c-b46b-f919b5e6ec82.png" alt="UI package folders and files" width="353" height="649" loading="lazy">

<p>Add com.core.dotween, com.core.initializer, and com.cysharp.unitask as dependencies in package.json and asmdef.</p>
<p>Popups work like a stack: each popup is shown on top of the previous one and closed from top to bottom. Screens are single-active: only one screen is visible at a time. UIController hides the current one when you show another. UIController is the single point of contact for both screens and popups and handles stacking, caching, and background placement.</p>
<h3 id="heading-iui-interface">IUI Interface</h3>
<p>Both BasePopup and BaseScreen implement IUI:</p>
<pre><code class="language-csharp">using System;
using Cysharp.Threading.Tasks;

namespace com.core.ui
{
    public interface IUI
    {
        event Action&lt;IUI&gt; Showed;
        event Action&lt;IUI&gt; Hidden;

        UniTask ShowAsync();
        UniTask HideAsync();
    }
}
</code></pre>
<p>IUI is a very basic interface with 2 async Show/Hide methods and 2 events to be fired upon completion of those methods.</p>
<h3 id="heading-basepopup">BasePopup</h3>
<p>BasePopup uses DoTween to animate scale on show/hide and exposes events and overridable animation methods:</p>
<pre><code class="language-csharp">using System;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;

namespace com.core.ui
{
    public abstract class BasePopup : MonoBehaviour, IUI
    {
        public event Action&lt;IUI&gt; StartedShowing;
        public event Action&lt;IUI&gt; Showed;
        public event Action&lt;IUI&gt; Hidden;

        [SerializeField] private float   openingTime  = 0.35f;
        [SerializeField] private float   closingTime  = 0.25f;
        [SerializeField] private Vector3 initialScale = new(0.75f, 0.75f, 0.75f);
        [SerializeField] private Ease   openingEase  = Ease.OutBack;
        [SerializeField] private Ease   closingEase  = Ease.InBack;

        protected virtual Tweener PlayShowAnimation(Action completedCallback) =&gt; transform.DOScale(Vector3.one, openingTime).SetEase(openingEase).SetUpdate(true).OnComplete(() =&gt; completedCallback?.Invoke());
        protected virtual Tweener PlayHideAnimation(Action completedCallback) =&gt; transform.DOScale(initialScale, closingTime).SetEase(closingEase).SetUpdate(true).OnComplete(() =&gt; completedCallback?.Invoke());

        public UniTask ShowAsync()
        {
            StartedShowing?.Invoke(this);
            transform.SetAsLastSibling();
            var utcs = new UniTaskCompletionSource();
            transform.localScale = initialScale;
            gameObject.SetActive(true);

            PlayShowAnimation(() =&gt; OnAnimationShowed(utcs));
            return utcs.Task;
        }

        public void Show() =&gt; ShowAsync().Forget();

        public UniTask HideAsync()
        {
            var utcs = new UniTaskCompletionSource();
            PlayHideAnimation(() =&gt; OnAnimationCompleted(utcs));
            return utcs.Task;
        }

        private void OnAnimationShowed(UniTaskCompletionSource utcs)
        {
            Showed?.Invoke(this);
            utcs.TrySetResult();
        }

        private void OnAnimationCompleted(UniTaskCompletionSource utcs)
        {
            transform.SetAsFirstSibling();
            gameObject.SetActive(false);
            Hidden?.Invoke(this);
            utcs.TrySetResult();
        }

        public void Hide() =&gt; HideAsync().Forget();
    }
}
</code></pre>
<p>You can see that DoTween is the core component in this class and a lot of parameters are configurable like opening/closing times and easings. Notice how DoTween completion callbacks are utilized to fire Showed and Hidden events.</p>
<p>Another important thing here is the usage of SetUpdate(true) for animations. This makes animations to ignore timescale. I found that most of the time it's best to ignore the timescale for animations but if you don't want this then feel free to change it.</p>
<h3 id="heading-basescreen"><strong>BaseScreen</strong></h3>
<p>BaseScreen only enables/disables the GameObject and raises events. There’s no animation:</p>
<pre><code class="language-csharp">using System;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace com.core.ui
{
    public abstract class BaseScreen : MonoBehaviour, IUI
    {
        public event Action&lt;IUI&gt; Showed;
        public event Action&lt;IUI&gt; Hidden;

        public virtual UniTask ShowAsync()
        {
            gameObject.SetActive(true);
            Showed?.Invoke(this);
            return UniTask.CompletedTask;
        }

        public UniTask HideAsync()
        {
            gameObject.SetActive(false);
            Hidden?.Invoke(this);
            return UniTask.CompletedTask;
        }
    }
}
</code></pre>
<p>You will notice that BaseScreen doesn't need UniTask since it is not performing any async operations but it is still beneficial to have this since it doesn't cause any overheads and if we ever need an animation for any of our screens like popups, we have it ready.</p>
<h3 id="heading-uicontroller"><strong>UIController</strong></h3>
<p>UIController implements <code>IController</code>, instantiates UIParent from Resources, and provides APIs to push/pop popups and show/hide screens. It caches popup and screen instances by type and loads prefabs from Resources:</p>
<ul>
<li><p><strong>UIParent</strong>: <code>Resources/UIPrefabs/UIParent</code></p>
</li>
<li><p><strong>Popups</strong>: <code>Resources/UIPrefabs/Popups/&lt;TypeName&gt;.prefab</code></p>
</li>
<li><p><strong>Screens</strong>: <code>Resources/UIPrefabs/Screens/&lt;TypeName&gt;.prefab</code></p>
</li>
</ul>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using com.core.initializer;
using Cysharp.Threading.Tasks;
using UnityEngine;
using Object = UnityEngine.Object;

namespace com.core.ui
{
    public class UIController : IController
    {
        private const string UI_PARENT_PATH    = "UIPrefabs/UIParent";
        private const string POPUPS_RESOURCES  = "UIPrefabs/Popups";
        private const string SCREENS_RESOURCES = "UIPrefabs/Screens";

        private readonly Stack&lt;BasePopup&gt;             _popupStack  = new();
        private readonly Dictionary&lt;Type, BasePopup&gt;  _popupCache  = new();
        private readonly Dictionary&lt;Type, BaseScreen&gt; _ScreenCache = new();

        private BaseScreen _currentScreen;
        private UIParent   _uiParent;

        public bool IsInitialized { get; private set; }

        public UniTask Initialize()
        {
            var uiParentPrefab = Resources.Load&lt;UIParent&gt;(UI_PARENT_PATH);
            if (uiParentPrefab == null)
            {
                Debug.LogError($"[UIController] UIParent prefab not found at {UI_PARENT_PATH}. Ensure it exists in a Resources folder.");
                return UniTask.CompletedTask;
            }

            _uiParent      = Object.Instantiate(uiParentPrefab);
            _uiParent.name = "UIParent";

            Object.DontDestroyOnLoad(_uiParent.gameObject);

            IsInitialized = true;
            return UniTask.CompletedTask;
        }

        public async UniTask&lt;TPopup&gt; PushPopupAsync&lt;TPopup&gt;() where TPopup : BasePopup
        {
            var popup = GetOrCreatePopup&lt;TPopup&gt;();
            _popupStack.Push(popup);
            var popupTask = popup.ShowAsync();
            UpdateBackgroundForTopPopup();
            await popupTask;
            return popup;
        }

        public void PushPopup&lt;TPopup&gt;() where TPopup : BasePopup =&gt; PushPopupAsync&lt;TPopup&gt;().Forget();

        public async UniTask PopPopupAsync()
        {
            if (_popupStack.Count == 0)
            {
                Debug.LogWarning("[UIController] PopPopup called but popup stack is empty.");
                return;
            }

            var top = _popupStack.Pop();
            await top.HideAsync();
            UpdateBackgroundForTopPopup();
        }

        public void PopPopup() =&gt; PopPopupAsync().Forget();

        public BasePopup PeekPopup() =&gt; _popupStack.Count &gt; 0 ? _popupStack.Peek() : null;

        public async UniTask&lt;TScreen&gt; ShowScreenAsync&lt;TScreen&gt;() where TScreen : BaseScreen
        {
            var screen = GetOrCreateScreen&lt;TScreen&gt;();

            if (_currentScreen == screen &amp;&amp; screen.gameObject.activeSelf) return screen;
            if (_currentScreen != null   &amp;&amp; _currentScreen != screen &amp;&amp; _currentScreen.gameObject.activeSelf) await _currentScreen.HideAsync();

            _currentScreen = screen;
            await screen.ShowAsync();
            return screen;
        }

        public async UniTask HideScreenAsync&lt;TScreen&gt;() where TScreen : BaseScreen
        {
            var type = typeof(TScreen);
            if (!_ScreenCache.TryGetValue(type, out var screen))
            {
                Debug.LogWarning($"[UIController] HideScreenAsync&lt;{type.Name}&gt; called but Screen was never shown (not in cache).");
                return;
            }

            await screen.HideAsync();

            if (_currentScreen == screen) _currentScreen = null;
        }

        public void ShowScreen&lt;TScreen&gt;() where TScreen : BaseScreen =&gt; ShowScreenAsync&lt;TScreen&gt;().Forget();
        public void HideScreen&lt;TScreen&gt;() where TScreen : BaseScreen =&gt; HideScreenAsync&lt;TScreen&gt;().Forget();

        private TPopup GetOrCreatePopup&lt;TPopup&gt;() where TPopup : BasePopup
        {
            var type = typeof(TPopup);
            if (_popupCache.TryGetValue(type, out var cached) &amp;&amp; cached != null) return (TPopup)cached;

            var prefab = LoadPopupPrefab&lt;TPopup&gt;();
            if (prefab == null)
            {
                Debug.LogError($"[UIController] Popup prefab for {type.Name} not found at {POPUPS_RESOURCES}/{type.Name}. Ensure the prefab exists in a Resources folder.");
                return null;
            }

            var instance = UnityEngine.Object.Instantiate(prefab, _uiParent.PopupParent);
            instance.gameObject.SetActive(false);

            var popup = instance.GetComponent&lt;TPopup&gt;();
            _popupCache[type] = popup;

            return popup;
        }

        private TScreen GetOrCreateScreen&lt;TScreen&gt;() where TScreen : BaseScreen
        {
            var type = typeof(TScreen);
            if (_ScreenCache.TryGetValue(type, out var cached) &amp;&amp; cached != null) return (TScreen)cached;

            var prefab = LoadScreenPrefab&lt;TScreen&gt;();
            if (prefab == null)
            {
                Debug.LogError($"[UIController] Screen prefab for {type.Name} not found at {SCREENS_RESOURCES}/{type.Name}. Ensure the prefab exists in a Resources folder.");
                return null;
            }

            var instance = UnityEngine.Object.Instantiate(prefab, _uiParent.ScreenParent);
            instance.gameObject.SetActive(false);

            var screen = instance.GetComponent&lt;TScreen&gt;();
            _ScreenCache[type] = screen;

            return screen;
        }

        private TPopup  LoadPopupPrefab&lt;TPopup&gt;() where TPopup : BasePopup     =&gt; LoadViewPrefab&lt;TPopup&gt;(POPUPS_RESOURCES);
        private TScreen LoadScreenPrefab&lt;TScreen&gt;() where TScreen : BaseScreen =&gt; LoadViewPrefab&lt;TScreen&gt;(SCREENS_RESOURCES);

        private static T LoadViewPrefab&lt;T&gt;(string resourcesPath) where T : MonoBehaviour
        {
            var type = typeof(T);
            var path = $"{resourcesPath}/{type.Name}";
            var go   = Resources.Load&lt;GameObject&gt;(path);
            return go != null ? go.GetComponent&lt;T&gt;() : null;
        }

        private void UpdateBackgroundForTopPopup()
        {
            var backgroundGO = _uiParent.BackgroundGO;
            if (backgroundGO == null) return;

            if (_popupStack.Count &gt; 0)
            {
                var topPopup = _popupStack.Peek();
                if (topPopup != null &amp;&amp; topPopup.gameObject.activeSelf)
                {
                    // Position background one step behind the top popup
                    var topPopupIndex = topPopup.transform.GetSiblingIndex();
                    backgroundGO.SetActive(true);
                    backgroundGO.transform.SetSiblingIndex(Mathf.Max(0, topPopupIndex - 1));
                }
                else
                {
                    // Top popup is inactive, hide background
                    backgroundGO.SetActive(false);
                }
            }
            else
            {
                // No popups in stack, hide background
                backgroundGO.SetActive(false);
            }
        }
    }
}
</code></pre>
<p>Usage:</p>
<pre><code class="language-csharp">var ui = ControllerHandler.GetController&lt;UIController&gt;();
await ui.PushPopupAsync&lt;MyPopup&gt;();
await ui.PopPopupAsync();
await ui.ShowScreenAsync&lt;MainMenuScreen&gt;();
await ui.HideScreenAsync&lt;LoadingScreen&gt;();
</code></pre>
<p>UIController also updates the background GameObject so it indexes behind the top popup.</p>
<p>It uses <code>GetOrCreatePopup&lt;TPopup&gt;()</code> / <code>GetOrCreateScreen&lt;TScreen&gt;()</code> to load and cache prefabs from <code>Resources/UIPrefabs/Popups/&lt;TypeName&gt;</code> and <code>Resources/UIPrefabs/Screens/&lt;TypeName&gt;</code>, pushes popups onto a stack, and shows/hides screens with async methods.</p>
<p>UIParent is a MonoBehaviour that holds references to ScreenParent, PopupParent, and BackgroundGO:</p>
<pre><code class="language-csharp">using UnityEngine;

namespace com.core.ui
{
    public class UIParent : MonoBehaviour
    {
        [SerializeField] private Transform  screensParent;
        [SerializeField] private Transform  popupsParent;
        [SerializeField] private GameObject backgroundGO;

        public Transform  ScreenParent =&gt; screensParent;
        public Transform  PopupParent  =&gt; popupsParent;
        public GameObject BackgroundGO =&gt; backgroundGO;
    }
}
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771288099657/6104e31e-e8e0-489c-bca5-592d5daec174.png" alt="UIParent prefab hierarchy" width="360" height="230" loading="lazy">

<p>UIParent prefab has a canvas and UIParent script attached:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/698d2f3c217bebae73c3f5e8/cb79381a-35c3-49ac-af50-da33b0f7f988.png" alt="UIParent prefab components" style="display:block;margin:0 auto" width="1243" height="1444" loading="lazy">

<p>There are a couple limitations here as well. First, popups and screens must live under Resources/UIPrefabs/Popups and Resources/UIPrefabs/Screens. Second, prefab name must match the script/type name (for example, TestPopup prefab name for a script named TestPopup).</p>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>In this article, you set up a Unity project and walked through creating four reusable packages:</p>
<ul>
<li><p><strong>com.core.initializer</strong> – You created the package, added <strong>UniTask</strong>, defined <code>IController</code>, used the <code>Creator</code> helper to find and create controllers (including MonoBehaviours), and implemented <code>ControllerHandler</code> to run initialization at <code>BeforeSceneLoad</code> and expose controllers via <code>GetController&lt;T&gt;()</code>.</p>
</li>
<li><p><strong>com.core.data</strong> – You use <code>IDataProvider</code> and <code>LocalDataProvider</code> with MemoryPack for binary local save/load, and the <code>DataController</code> that implements <code>IController</code> and tracks version history.</p>
</li>
<li><p><strong>com.core.dotween</strong> – You wrap the <strong>DoTween</strong> asset as a package so other packages (like <strong>com.core.ui</strong>) can reference it for animations.</p>
</li>
<li><p><strong>com.core.ui</strong> – You created a modular UI package that you can use in your games to handle popups and full size screens. You can also extend this to add other UI types in the future.</p>
</li>
</ul>
<p>Building these as separate packages keeps your code modular and speeds up development across projects. You can install each package via Git into any Unity project.</p>
<p>Example manifest.json file to implement these packages:</p>
<pre><code class="language-json">{
  "dependencies"    : {
	"com.core.data"       : "https://github.com/TalhaCagatay/com.core.data.git#v0.1.0",
	"com.core.initializer": "https://github.com/TalhaCagatay/com.core.initializer.git#v0.1.0",
	"com.core.dotween"    : "https://github.com/TalhaCagatay/com.core.dotween.git#v0.1.0",
	"com.core.ui"         : "https://github.com/TalhaCagatay/com.core.ui.git#v0.1.0"
  },
  "scopedRegistries": [
	{
	  "name"  : "package.openupm.com",
	  "url"   : "https://package.openupm.com",
	  "scopes": [
		"com.cysharp.unitask",
		"com.cysharp.memorypack"
	  ]
	}
  ]
}
</code></pre>
<p><strong>Resources:</strong></p>
<ul>
<li><p>com.core.initializer on <a href="https://github.com/TalhaCagatay/com.core.initializer">GitHub</a></p>
</li>
<li><p>com.core.data on <a href="https://github.com/TalhaCagatay/com.core.data">GitHub</a></p>
</li>
<li><p>com.core.dotween on <a href="https://github.com/TalhaCagatay/com.core.dotween">Github</a></p>
</li>
<li><p>com.core.ui on <a href="https://github.com/TalhaCagatay/com.core.ui">Github</a></p>
</li>
<li><p><a href="https://play.google.com/store/apps/details?id=com.Focus.Matchingham">Example game</a> that I developed and used many modular systems like the ones you built in this article.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Game Developer’s Guide to Understanding Screen Resolution ]]>
                </title>
                <description>
                    <![CDATA[ Every game developer obsesses over performance, textures, and frame rates, but resolution is the quiet foundation that makes or breaks visual quality.  Whether you are building a pixel-art indie game or a high-fidelity 3D world, understanding how res... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-game-developers-guide-to-understanding-screen-resolution/</link>
                <guid isPermaLink="false">691de96a0dec4f292a0f8ff0</guid>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 19 Nov 2025 15:59:38 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763567809746/3fb2c926-9602-4765-9ef4-5ea565e0e148.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every game developer obsesses over performance, textures, and frame rates, but resolution is the quiet foundation that makes or breaks visual quality. </p>
<p>Whether you are building a pixel-art indie game or a high-fidelity 3D world, understanding how resolution works is essential. </p>
<p>It affects how your art assets scale, how your UI appears, and how your game feels on different screens. Yet, many developers still treat resolution as a simple number instead of a design decision.</p>
<p>Let’s learn what resolutions are and why it matters for game developers. </p>
<h2 id="heading-what-we-will-cover">What we will Cover</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-resolution-really-means">What Resolution Really Means</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-evolution-of-resolution-in-gaming">The Evolution of Resolution in Gaming</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dpi-scaling-and-texture-clarity">DPI, Scaling, and Texture Clarity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resolution-vs-performance">Resolution vs. Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-aspect-ratio-and-display-diversity">Aspect Ratio and Display Diversity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-art-of-testing-in-4k-and-hdr">The Art of Testing in 4K and HDR</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preparing-for-next-gen-displays">Preparing for Next-Gen Displays</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-resolution-really-means">What Resolution Really Means</h2>
<p>Resolution defines how many pixels a screen can display horizontally and vertically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763470514266/2ba4689a-6e8d-423d-8da7-694bf7bc6d9e.png" alt="Screen Resolution Sizes" class="image--center mx-auto" width="1000" height="428" loading="lazy"></p>
<p>A monitor labelled 1920x1080 has 1920 pixels across and 1080 down, which equals over two million pixels in total. More pixels mean more visual detail but also more rendering work for the GPU.</p>
<p>In game development, that tradeoff is constant. Rendering at higher resolutions improves clarity but reduces frame rates unless your code and assets are optimized. </p>
<p>Many developers solve this by offering resolution scaling options in their games, letting players balance visual quality and performance.</p>
<p>It’s also important to distinguish between screen size and resolution. A 27-inch monitor and a 15-inch laptop can both run at 1080p, but the larger display will have bigger, less dense pixels. </p>
<p>This is where pixel density comes in. High-density displays pack more pixels per inch, creating smoother edges and sharper textures even at the same resolution.</p>
<h2 id="heading-the-evolution-of-resolution-in-gaming">The Evolution of Resolution in Gaming</h2>
<p>Games have evolved alongside display technology. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763514379811/7a5bef4e-5441-4b40-99cb-3d925865ac87.jpeg" alt="Gameplay Resolution" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<p>Early consoles ran at 240p, then 480p during the SD era. The jump to HD with 720p and 1080p transformed game visuals. Suddenly, developers had to think about anti-aliasing, texture resolution, and UI scaling in new ways.</p>
<p>Today, 4K and HDR have become the standard for modern consoles and PCs. Developers now design with higher fidelity in mind, baking in lighting systems, shaders, and art pipelines that scale up to Ultra HD. </p>
<p>That’s why testing on different display resolutions isn’t just good practice, it’s critical for consistent player experience.</p>
<p>If you want to see how your game performs on large high-resolution displays, try testing it on a modern TV for PS5. These screens are optimized for 4K and 120Hz refresh rates, giving you a realistic look at how your game will appear in a living-room setup. </p>
<p>They also help you spot UI scaling issues, frame pacing problems, and HDR color mismatches that might go unnoticed on a typical monitor.</p>
<h2 id="heading-dpi-scaling-and-texture-clarity">DPI, Scaling, and Texture Clarity</h2>
<p>For web developers, <a target="_blank" href="https://en.wikipedia.org/wiki/Dots_per_inch">DPI</a> mostly affects how images scale. But for game developers, DPI connects directly to texture resolution and how art assets are perceived at different screen sizes. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763470672635/57795a33-7700-4aee-8dd4-aceb8b71dd49.jpeg" alt="DPI Levels" class="image--center mx-auto" width="1200" height="675" loading="lazy"></p>
<p>A sprite that looks crisp on a 1080p monitor might appear tiny or blurry on a 4K display if not properly scaled. Engines like <a target="_blank" href="https://www.freecodecamp.org/news/game-development-for-beginners-unity-course/">Unity</a> and Unreal handle this with dynamic scaling options, but understanding the underlying math helps. </p>
<p>When your display density doubles, each asset needs four times as many pixels to appear at the same size and sharpness. If you do not plan for this, your carefully crafted textures might look soft or misaligned on higher-resolution displays.</p>
<p>This is why UI systems in modern engines rely on resolution-independent units. In Unity, Canvas Scaler helps ensure your interface looks the same on every device. In Unreal, DPI scaling rules allow developers to maintain consistent HUD layouts. Getting this right means your game remains legible on everything from handhelds to 8K TVs.</p>
<h2 id="heading-resolution-vs-performance">Resolution vs Performance</h2>
<p>The biggest cost of higher resolution is GPU load. Rendering in 4K means pushing four times as many pixels as 1080p. Without proper optimization, frame rates can drop sharply. </p>
<p>That’s why many <a target="_blank" href="https://en.wikipedia.org/wiki/AAA_%28video_game_industry%29">AAA games</a> use resolution scaling techniques like temporal upsampling or DLSS. These methods render frames at a lower resolution and then use AI or interpolation to upscale them without losing clarity.</p>
<p>As a developer, you should test your game across multiple resolutions and aspect ratios. This helps ensure your render pipeline, shaders, and assets adapt smoothly. Tools like <a target="_blank" href="https://developer.nvidia.com/nsight-systems">NVIDIA Nsight</a> or Unreal’s built-in profiler show how resolution affects frame time and GPU usage.</p>
<p>If your game includes video content or cinematic sequences, also remember that video compression behaves differently at higher resolutions. Encoding 4K video requires significantly more bandwidth and storage, which can affect your build size and performance during playback.</p>
<h2 id="heading-aspect-ratio-and-display-diversity">Aspect Ratio and Display Diversity</h2>
<p>Aspect ratio determines the shape of the display.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763476458560/52decf37-c4f4-4927-96b8-1c6fd9be074c.jpeg" alt="Aspect Ratios" class="image--center mx-auto" width="2560" height="977" loading="lazy"></p>
<p>Most modern games target 16:9, but 21:9 ultrawide and 32:9 super-ultrawide displays are becoming more popular. Developers must ensure their camera framing and UI layouts adapt accordingly.</p>
<p>When a game is locked to one ratio, black bars or stretching can occur. To fix this, adjust your camera’s field of view dynamically or provide safe viewport settings.</p>
<p>Engines like Unreal let you script these adjustments easily, while Unity’s Cinemachine system handles FOV scaling automatically.</p>
<p>Even TVs now vary in aspect ratio capabilities, especially with new mini LED and OLED technologies. Testing across multiple ratios ensures your game looks balanced and cinematic on every screen.</p>
<h2 id="heading-the-art-of-testing-in-4k-and-hdr">The Art of Testing in 4K and HDR</h2>
<p>4K and HDR introduce new layers of visual complexity. HDR displays show a wider range of brightness and color depth, which means lighting and textures can look completely different compared to SDR monitors. To handle this, calibrate your color grading pipeline and use tone mapping tools within your engine.</p>
<p>When working with HDR assets, always test your output on real hardware. Emulators and monitors often fail to reproduce true HDR contrast. A proper HDR-certified TV helps you identify overexposure, color clipping, and banding issues before release.</p>
<h2 id="heading-preparing-for-next-gen-displays">Preparing for Next-Gen Displays</h2>
<p>The display industry continues to evolve fast. 8K and high refresh rate panels are already entering mainstream markets. </p>
<p>For developers, this means thinking ahead. Designing scalable rendering systems, supporting dynamic resolution, and maintaining flexible UI layouts are now essential parts of modern game design.</p>
<p>As displays get sharper, player expectations rise too. Textures, shaders, and post-processing all need to support higher levels of detail without compromising performance. By understanding how resolution interacts with your pipeline, you can future-proof your games for years to come.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Resolution is more than a number on a settings menu. It is a design constraint, a performance factor, and a creative opportunity. As a game developer, mastering resolution helps you build experiences that look sharp, play smoothly, and scale across every device.</p>
<p>The next time you polish your textures or fine-tune your rendering settings, remember that every pixel counts. Understanding how resolution, scaling, and density interact will not only make your games more beautiful but also more accessible to every player, whether they’re gaming on a laptop, a monitor, or the living-room tv that brings your visuals to life in stunning detail.</p>
<p><em>Hope you enjoyed this article. Find me on</em> <a target="_blank" href="https://linkedin.com/in/manishmshiva"><em>Linkedin</em></a> <em>or</em> <a target="_blank" href="https://manishshivanandhan.com/"><em>visit my website</em></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Improve Your Programming Skills by Building Games ]]>
                </title>
                <description>
                    <![CDATA[ When most people think about learning to code, they imagine building websites or automating small tasks. Few think of building games as a serious way to improve programming skills.  But creating even a simple game can teach lessons that no tutorial e... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/improve-your-programming-skills-by-building-games/</link>
                <guid isPermaLink="false">690364c01022fd77927ddcbd</guid>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Tips ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Thu, 30 Oct 2025 13:14:40 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761829724019/2dc484e9-e0d2-4632-85ff-8ed39233fb51.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When most people think about learning to code, they imagine building websites or automating small tasks. Few think of building games as a serious way to improve programming skills. </p>
<p>But creating even a simple game can teach lessons that no tutorial ever could. Games force you to think about performance, user input, structure, and creative problem-solving all at once.</p>
<p>When I started building small <a target="_blank" href="https://www.freecodecamp.org/news/how-to-build-a-snake-game-using-phaserjs/">2D games</a> as weekend projects, I didn’t realize how much they would sharpen my overall coding skills. From learning how to organize complex systems to handling real-time input, every part of game development stretched my thinking. </p>
<p>Whether you’re a web developer, mobile engineer, or hobby coder, building games will make you a stronger problem solver.</p>
<p>Here are ten programming skills you’ll learn along the way.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-thinking-in-systems">1. Thinking in Systems</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-writing-event-driven-code">2. Writing Event-Driven Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-optimizing-for-performance">3. Optimizing for Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-debugging-complex-states">4. Debugging Complex States</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-handling-user-input-responsively">5. Handling User Input Responsively</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-building-reusable-game-loops-and-engines">6. Building Reusable Game Loops and Engines</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-7-managing-complexity-through-components">7. Managing Complexity Through Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-8-learning-the-math-that-actually-matters">8. Learning the Math That Actually Matters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-9-sharpening-your-design-and-ux-instincts">9. Sharpening Your Design and UX Instincts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-10-embracing-creative-problem-solving">10. Embracing Creative Problem Solving</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-1-thinking-in-systems"><strong>1. Thinking in Systems</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761568833173/febffe00-1c5d-47cf-8c0a-a172a7d273f1.png" alt="Systems thinking" class="image--center mx-auto" width="648" height="339" loading="lazy"></p>
<p>Every game is a set of systems working together. You might have a physics system that controls movement, a rendering system that draws the visuals, and an AI system that decides how enemies react. </p>
<p>Each one depends on the others, but they must remain separate enough to be managed and improved without breaking the rest of the game.</p>
<p>This is exactly what developers deal with in larger software projects. Building a game helps you understand modular design and why separating logic into smaller, independent parts makes everything easier to scale and debug. </p>
<p>You stop writing long scripts that try to do everything and instead start thinking in terms of systems that talk to each other through clear rules.</p>
<h2 id="heading-2-writing-event-driven-code"><strong>2. Writing Event-Driven Code</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761568856531/4e18c861-8cd8-45cf-9f4b-4b876f8e41a3.png" alt="Event-Driven Programming" class="image--center mx-auto" width="1000" height="625" loading="lazy"></p>
<p>Games live and breathe on events. A button press, a collision, or a timer hitting zero are all events that trigger actions. </p>
<p>When you code a game, you quickly learn to think in event loops. This helps you understand how asynchronous code works in real life.</p>
<p>If you’ve struggled with JavaScript event listeners or backend message queues, building a small game is the perfect way to get comfortable with them. </p>
<p>Every time a player jumps, attacks, or collects an item, you’re writing code that listens for an event and reacts in real time. That experience makes you a better developer, even outside of gaming.</p>
<h2 id="heading-3-optimizing-for-performance"><strong>3. Optimizing for Performance</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761568898214/e086f416-0b25-489f-86fd-8dbdaba200b4.png" alt="Performance Optimisation" class="image--center mx-auto" width="1000" height="587" loading="lazy"></p>
<p>Unlike websites, games can’t afford to lag. A delay of even a few milliseconds can break the experience. </p>
<p>When you write games, you learn to measure performance constantly. You start thinking about memory usage, CPU load, and rendering time.</p>
<p>You might experiment with how often to update physics calculations or how to reuse textures instead of loading them every frame. </p>
<p>Those small optimizations become second nature, and later, when you’re building a web app or a backend service, you’ll know exactly where to look when something feels slow.</p>
<h2 id="heading-4-debugging-complex-states"><strong>4. Debugging Complex States</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761568916767/4a084536-2076-4065-bf67-674e53f5b28e.png" alt="Debugging" class="image--center mx-auto" width="1000" height="564" loading="lazy"></p>
<p>Games are full of moving parts that interact in unpredictable ways. Maybe a character disappears after jumping twice, or a power-up triggers twice because of overlapping timers. These problems force you to learn structured debugging.</p>
<p>You’ll get used to adding logs, reproducing edge cases, and isolating bugs by breaking large systems into smaller ones. The patience and process you develop while debugging a tricky game bug translate perfectly to real-world software. </p>
<p>You become the kind of developer who doesn’t panic when something goes wrong because you’ve already handled far more chaotic code in your side projects.</p>
<h2 id="heading-5-handling-user-input-responsively"><strong>5. Handling User Input Responsively</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761568965695/990963ce-4474-4609-aca3-28f27901bee4.jpeg" alt="Handling user input" class="image--center mx-auto" width="1000" height="563" loading="lazy"></p>
<p>When you build a game, user input becomes one of your main concerns. You want the player’s actions to feel instant. </p>
<p>That means learning how to manage input devices like keyboards, mice, or <a target="_blank" href="https://www.eneba.com/hub/gaming-gear/best-pc-controller/">best PC controllers</a>. You’ll discover how to debounce actions, prevent lag, and detect simultaneous keypresses. You might even test your code with the best PC controller to make sure it feels smooth and accurate. </p>
<p>This focus on responsiveness changes how you approach every future project. You begin to see every button click or touch gesture as part of a feedback loop that should feel immediate and natural.</p>
<h2 id="heading-6-building-reusable-game-loops-and-engines"><strong>6. Building Reusable Game Loops and Engines</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761568992859/a5a94ae0-5899-476a-816d-74883b5ac259.png" alt="Reusable Loops" class="image--center mx-auto" width="970" height="622" loading="lazy"></p>
<p>After writing a few games, you’ll realize that many parts of your code repeat. The main loop that updates the world, the input handlers, and the collision checks all follow patterns. This realization leads to a powerful skill: abstraction.</p>
<p>You start building small frameworks or reusable components that handle these repetitive tasks. In doing so, you learn the same lessons that professional developers learn when they design APIs or internal tools. </p>
<p>The discipline of turning messy scripts into organized, reusable code teaches you about structure and design in a way that theory never can.</p>
<h2 id="heading-7-managing-complexity-through-components"><strong>7. Managing Complexity Through Components</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761569009038/cf7d045e-a1d2-4dde-94e5-90f3b84f41b5.jpeg" alt="Managing Complexity" class="image--center mx-auto" width="1000" height="525" loading="lazy"></p>
<p>Game developers often use something called an <a target="_blank" href="https://en.wikipedia.org/wiki/Entity_component_system">Entity-Component-System (ECS) architecture</a>. It’s a way of organizing objects in a game so they can share behavior without heavy inheritance trees. For example, a player and an enemy might both have movement and health components, but different AI logic.</p>
<p>This pattern is very similar to how modern front-end frameworks work. If you use React, you already think in components. Building games strengthens that habit. </p>
<p>You start to see every system, UI, physics, AI, as a component that can be composed and reused. It’s one of the most powerful ways to manage complexity in any large codebase.</p>
<h2 id="heading-8-learning-the-math-that-actually-matters"><strong>8. Learning the Math That Actually Matters</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761569027607/b9c2453b-a8dc-401b-b824-b200b6d0555f.jpeg" alt="Learning Math" class="image--center mx-auto" width="1000" height="667" loading="lazy"></p>
<p>Many developers shy away from math, but games make it practical. When you need to move a character along a curve, calculate projectile motion, or detect collisions, you’re forced to use geometry, trigonometry, and vectors.</p>
<p>The best part is that you learn it through doing, not memorizing formulas. You begin to understand how angles, distances, and forces interact in a way that feels visual and intuitive. Later, when you face algorithmic problems or data visualizations, that math background helps you approach them with confidence.</p>
<h2 id="heading-9-sharpening-your-design-and-ux-instincts"><strong>9. Sharpening Your Design and UX Instincts</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761569045237/f59d54e0-bf26-49ae-8c4c-838ac624c9e7.jpeg" alt="Design Thinking" class="image--center mx-auto" width="1000" height="506" loading="lazy"></p>
<p>Good games feel right. The jump height, the delay between actions, the feedback when you collect a coin, every small detail affects how enjoyable the game feels. </p>
<p>When you design these experiences, you’re learning about user experience design without even realizing it.</p>
<p>You begin to think about things like timing, feedback, and accessibility. You learn how to make interactions satisfying and clear. </p>
<p>The same mindset applies when you build apps or websites. You start designing not just for functionality but for how it feels to use.</p>
<h2 id="heading-10-embracing-creative-problem-solving"><strong>10. Embracing Creative Problem Solving</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761569066107/7a9aab1e-1814-4a50-b837-ba5129f49e49.jpeg" alt="Creative Problem Solving" class="image--center mx-auto" width="695" height="452" loading="lazy"></p>
<p>Games are rarely built in a straight line. You’ll face problems that don’t have clear answers. </p>
<p>Maybe you need a way to fake physics without heavy computation or make AI feel smarter than it is. These challenges train you to think creatively.</p>
<p>You’ll often come up with unconventional but clever solutions. That kind of flexible problem-solving becomes one of your most valuable programming skills. </p>
<p>When something breaks in production or a feature seems impossible under current constraints, you’ll know how to find a creative way around it because you’ve done it before in your own projects.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Building games is more than a hobby. It’s an accelerated crash course in becoming a better developer. You’ll write cleaner code, understand systems thinking, and develop a sharp sense for performance and design. You’ll also have fun in the process, which keeps your motivation alive longer than any tutorial series can.</p>
<p>Each project you build will teach you something new about programming. The lessons won’t come from books but from the moments you struggle, test, and finally see your creation come to life. Build something that teaches you back, and you’ll grow as both a coder and a creator.</p>
<p>Hope you enjoyed this article. Connect with me <a target="_blank" href="https://www.linkedin.com/in/manishmshiva/?originalSubdomain=in">on Linkedin</a> or <a target="_blank" href="https://manishshivanandhan.com/">visit my website</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Top Frameworks for Game Developers ]]>
                </title>
                <description>
                    <![CDATA[ Game development has never been more exciting than it is today. With the rise of mobile phones, powerful PCs, and even browser-based platforms, the demand for high-quality games continues to grow at a fast pace.  Developers now have access to a wide ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/top-frameworks-for-game-developers/</link>
                <guid isPermaLink="false">68f92b264b456457fa60faa6</guid>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 22 Oct 2025 19:06:14 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761159950014/3c7e5e1c-5e1e-4c9b-b299-53dc20606f12.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Game development has never been more exciting than it is today. With the rise of mobile phones, powerful PCs, and even browser-based platforms, the demand for high-quality games continues to grow at a fast pace. </p>
<p>Developers now have access to a wide variety of frameworks that make it easier to design, test, and launch games without having to build everything from scratch. Choosing the right framework can save months of effort, improve performance, and help you reach a wider audience.</p>
<p>In this article, we will look at the top five game development frameworks that stand out in the industry. Each one has its own strengths, community support, and use cases. </p>
<p>Whether you are a beginner making your first project or a studio working on the next big release, these frameworks can help you create world-class games.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-unity">Unity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-unreal-engine">Unreal Engine</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-phaser">Phaser</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-godot-engine">Godot Engine</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cocos2d-x">Cocos2d-x</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-choosing-the-right-framework">Choosing the Right Framework</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-unity"><strong>Unity</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760709073685/7dbe0e3e-823b-42da-a476-2f7efa2af46f.png" alt="Unity" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<p><a target="_blank" href="https://unity.com/">Unity</a> is one of the most popular game development frameworks in the world. It supports both 2D and 3D game development and is used by indie developers as well as large studios. </p>
<p>Unity’s biggest advantage is its flexibility. You can build a small mobile game, a full PC or console title, or even virtual reality and augmented reality experiences.</p>
<p>One of the reasons Unity is so widely used is its massive asset store. Developers can purchase or download free models, animations, scripts, and tools, which reduces the amount of custom work required. </p>
<p>The engine uses <a target="_blank" href="https://www.freecodecamp.org/news/learn-csharp-book/">C# as its primary scripting language</a>, which makes it approachable for developers with a background in programming.</p>
<p>Unity also offers strong cross-platform support. A single project can be exported to multiple platforms, including Android, iOS, Windows, macOS, PlayStation, Xbox, and even the web. For studios that want to maximize their reach, this is a critical feature.</p>
<p>Unity’s community and learning resources are another major strength. With thousands of tutorials, courses, and forums, new developers rarely feel stuck for long. This strong ecosystem makes Unity one of the best choices for anyone serious about building world-class games.</p>
<h2 id="heading-unreal-engine"><strong>Unreal Engine</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760709119001/fc31828d-8c10-4f71-a296-9d39d22d5499.png" alt="Unreal Engine" class="image--center mx-auto" width="1200" height="675" loading="lazy"></p>
<p>If Unity is known for flexibility, <a target="_blank" href="https://www.unrealengine.com/en-US">Unreal Engine</a> is famous for raw power and graphical quality. </p>
<p>Unreal, developed by Epic Games, is widely used in AAA game studios and is the framework behind many of the most visually stunning games on the market. Its advanced rendering capabilities, physics system, and blueprint visual scripting make it a top choice for developers aiming for high-end graphics and performance.</p>
<p>Unreal Engine uses C++ for programming but also provides a node-based scripting system called Blueprints. This allows designers and artists who are not strong in coding to still build complex gameplay mechanics. For many teams, this blend of power and accessibility is ideal.</p>
<p>Unreal is also well-suited for projects outside of traditional games. It is used in industries such as film, architecture, and automotive design because of its ability to create realistic real-time 3D environments. This versatility makes it more than just a game engine.</p>
<p>The licensing model is another interesting factor. Unreal Engine can be used for free until your game earns a certain amount of revenue, making it accessible to both small and large developers. For anyone targeting high-quality visuals and professional-grade features, Unreal Engine is a natural choice.</p>
<h2 id="heading-phaser"><strong>Phaser</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760709155240/10c006b2-630d-418f-a6e8-8a2921d95810.png" alt="Phaser" class="image--center mx-auto" width="3000" height="2500" loading="lazy"></p>
<p><a target="_blank" href="https://phaser.io/">Phaser</a> is a JavaScript-based framework designed for making 2D browser games. While Unity and Unreal focus on large-scale projects, Phaser excels in simplicity and speed for smaller games.</p>
<p>It allows developers to create interactive, fast, and <a target="_blank" href="https://www.freecodecamp.org/news/how-to-build-a-snake-game-using-phaserjs/">lightweight games</a> that can run directly in web browsers without needing downloads or installations.</p>
<p>Phaser’s strength is in 2D game development. It has built-in features for animations, physics, input handling, and asset management. Because it uses JavaScript, it’s easy to pick up for web developers who want to expand into game creation. </p>
<p>Many indie developers and educational platforms use Phaser to teach programming through games, which shows how approachable it is.</p>
<p>Another advantage of Phaser is its active community and open-source nature. Developers share plugins, examples, and tutorials that make it easier for beginners to learn. Phaser games can also be ported to mobile devices using tools like Cordova or Capacitor, which extends their reach beyond browsers.</p>
<p>For those looking to create casual, educational, or lightweight games that can reach millions through browsers, Phaser is one of the best frameworks available.</p>
<h2 id="heading-godot-engine"><strong>Godot Engine</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760709195779/048ae409-3d28-488a-8937-279420fdb656.jpeg" alt="Godot" class="image--center mx-auto" width="2560" height="1440" loading="lazy"></p>
<p><a target="_blank" href="https://godotengine.org/">Godot</a> is an open-source game development framework that has quickly gained popularity. It stands out because of its flexibility, ease of use, and strong community-driven growth.</p>
<p>One of Godot’s unique features is its custom scripting language, GDScript, which is similar to Python and easy to learn. Developers can also use C# and C++ if they prefer.</p>
<p>Godot supports both <a target="_blank" href="https://www.freecodecamp.org/news/learn-game-development-by-building-your-first-platformer-with-godot/">2D and 3D game development</a>, and it has a well-designed scene system that makes organizing complex projects much easier. The engine is lightweight, which means it can run smoothly on low-end hardware, making it an attractive option for indie developers working on limited resources.</p>
<p>Another benefit of Godot is its licensing. Since it’s open-source and licensed under the MIT license, there are no royalties or fees required, even for commercial projects. This makes it a cost-effective choice for small studios and independent creators.</p>
<p>The Godot community has been growing rapidly, with many developers contributing new features and improvements. Tutorials, documentation, and community support have also improved, making it much easier for beginners to get started.</p>
<p>For developers who value freedom, low cost, and strong tools, Godot is a powerful option.</p>
<h2 id="heading-cocos2d-x"><strong>Cocos2d-x</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760709416277/afa0384f-b8f9-47d1-9da8-2c7ae9933f75.jpeg" alt="afa0384f-b8f9-47d1-9da8-2c7ae9933f75" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<p><a target="_blank" href="https://www.cocos.com/en/cocos2d-x">Cocos2d-x</a> is another popular open-source framework, mainly known for 2D game development. It’s written in C++ but also supports Lua and JavaScript scripting. Many mobile games, especially in Asia, have been built using Cocos2d-x because of its lightweight structure and fast performance.</p>
<p>Cocos2d-x is especially well-suited for mobile platforms like Android and iOS. It offers built-in physics, animations, audio, and scene management, making it a full solution for 2D game developers. Its focus on performance ensures smooth gameplay, even on devices with limited processing power.</p>
<p>Another reason developers choose Cocos2d-x is its flexibility in design. Since it’s open-source, teams can modify the engine itself to fit their project’s needs. This level of control is not always possible with larger commercial engines.</p>
<p>Cocos2d-x also benefits from an active community and strong documentation. Many developers find it easier to learn compared to larger frameworks, which makes it an excellent starting point for those focused on 2D games.</p>
<h2 id="heading-choosing-the-right-framework"><strong>Choosing the Right Framework</strong></h2>
<p>Selecting the best framework depends on your goals and resources. </p>
<p>If you want maximum reach across platforms and a wide range of features, Unity is a safe bet. For high-end graphics and AAA-level projects, Unreal Engine shines. </p>
<p>If you are aiming for <a target="_blank" href="https://www.freecodecamp.org/news/how-to-build-a-tic-tac-toe-game-with-phaserjs/">browser-based casual games</a>, Phaser is an excellent choice. For developers who value open-source freedom and a supportive community, Godot is growing fast. And if your focus is mobile 2D development, Cocos2d-x provides the performance and control you need.</p>
<p>One often overlooked factor in game development is the hardware you use for testing and building your projects. While high-end systems can help speed up workflows, many developers start with modest setups. </p>
<p>Thanks to the growing availability of budget-friendly components like <a target="_blank" href="https://www.eneba.com/hub/gaming-gear/best-budget-motherboard-for-gaming/">cheap motherboards</a> for gaming, even small studios can assemble a capable machine for development without breaking the bank. This means that powerful tools like Unity or Unreal are no longer limited to large companies with expensive equipment.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Game development has become more accessible than ever before. With frameworks like Unity, Unreal Engine, Phaser, Godot, and Cocos2d-x, developers at all levels have the tools needed to create world-class games.</p>
<p>Each framework has its own advantages, whether it is high-end graphics, simplicity, or open-source freedom. The key is to choose the one that best matches your project goals, team skills, and target platforms.</p>
<p>Hope you enjoyed this article. Connect with me <a target="_blank" href="https://www.linkedin.com/in/manishmshiva/?originalSubdomain=in">on Linkedin</a> or <a target="_blank" href="https://manishshivanandhan.com/">visit my website</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Snake Game using Phaser.js ]]>
                </title>
                <description>
                    <![CDATA[ If you’ve ever wanted to make a small game that runs in the browser, Phaser.js is a great place to start. It’s a simple JavaScript library that helps you build interactive 2-D games that you can play in the browser. In this guide, you’ll learn what P... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-snake-game-using-phaserjs/</link>
                <guid isPermaLink="false">68b1c9406e763941de20cda2</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Fri, 29 Aug 2025 15:37:36 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756416799827/d337175f-cbf1-40d1-8228-e5f3933ba3d1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you’ve ever wanted to make a small game that runs in the browser, Phaser.js is a great place to start. It’s a simple JavaScript library that helps you build interactive 2-D games that you can play in the browser.</p>
<p>In this guide, you’ll learn what Phaser is and then use it to build the popular Snake game. The snake moves on a grid. It eats food to grow. It dies if it hits a wall or itself. The full project fits in two files, and the code is split into small blocks so it’s easy to follow.</p>
<p>By the end, you’ll be able to understand and copy the code, run it, and tweak it. You’ll also learn why each part exists and how it fits into the Phaser way of doing things.</p>
<p><a target="_blank" href="https://manishshivanandhan.com/snake-game-with-phaser/">Play the game</a> here to get a feel for what you’ll be building.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-phaserjs">What is Phaser.js?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-grid-colors-and-game-config">Grid, Colors, and Game Config</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-scene-state-and-helper-functions">Scene State and Helper Functions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preload-and-create">Preload and Create</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-initialize-the-game">Initialize the Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reading-input-each-frame">Reading Input Each Frame</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-stepping-the-snake-eating-and-drawing">Stepping the Snake, Eating, and Drawing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-spawning-food-and-increasing-speed">Spawning Food and Increasing Speed</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-game-over-and-restart">Game Over and Restart</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-the-whole-thing-fits-together">How the Whole Thing Fits Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-useful-next-steps">Useful Next Steps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-what-is-phaserjs">What is Phaser.js?</h2>
<p><a target="_blank" href="https://phaser.io/">Phaser</a> is a free JavaScript library for 2D games. You write plain JavaScript and let Phaser do the heavy lifting. You don’t need a build system or a game engine installer. You can start with a single HTML file and one JavaScript file.</p>
<p>Phaser organizes code into scenes. A scene has three common steps. You load assets in <code>preload</code>, you set up images and variables in <code>create</code>, and you update your game each frame in <code>update</code>. That small loop is the core of most arcade games.</p>
<p>Now let’s setup the project.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Create a folder with two files named <code>index.html</code> and <code>main.js</code>. The HTML page loads Phaser from a CDN and then loads your script.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- index.html --&gt;</span>
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Phaser Snake<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css"><span class="hljs-selector-tag">body</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>; <span class="hljs-attribute">background</span>: <span class="hljs-number">#111</span>; }</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"main.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This page adds a container div and includes Phaser 3 from jsDelivr. Your <code>main.js</code> file will create the game and attach it to that div.</p>
<h2 id="heading-grid-colors-and-game-config">Grid, Colors, and Game Config</h2>
<p>Snake is easiest on a grid. You choose a tile size and a number of tiles wide and high. You also define colors for the background, the snake, and the food. Then you create a Phaser game config that points to your scene functions.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 1)</span>

<span class="hljs-comment">// Size of one grid tile in pixels</span>
<span class="hljs-keyword">const</span> TILE = <span class="hljs-number">16</span>;

<span class="hljs-comment">// Number of tiles across (columns) and down (rows)</span>
<span class="hljs-comment">// Game area = 40 * 16px wide (640px) and 30 * 16px tall (480px)</span>
<span class="hljs-keyword">const</span> COLS = <span class="hljs-number">40</span>;                 
<span class="hljs-keyword">const</span> ROWS = <span class="hljs-number">30</span>;                 

<span class="hljs-comment">// Total pixel width and height of the game canvas</span>
<span class="hljs-keyword">const</span> WIDTH = COLS * TILE;
<span class="hljs-keyword">const</span> HEIGHT = ROWS * TILE;

<span class="hljs-comment">// Colors for background, snake head, snake body, and food</span>
<span class="hljs-keyword">const</span> COLORS = {
  <span class="hljs-attr">bg</span>: <span class="hljs-number">0x1d1d1d</span>,   <span class="hljs-comment">// dark gray background</span>
  <span class="hljs-attr">head</span>: <span class="hljs-number">0x30c452</span>, <span class="hljs-comment">// bright green head</span>
  <span class="hljs-attr">body</span>: <span class="hljs-number">0x2aa04a</span>, <span class="hljs-comment">// darker green body</span>
  <span class="hljs-attr">food</span>: <span class="hljs-number">0xe94f37</span>, <span class="hljs-comment">// red food</span>
};

<span class="hljs-comment">// Directions represented as x and y offsets on the grid</span>
<span class="hljs-comment">// For example, moving left means x decreases by 1, y stays the same</span>
<span class="hljs-keyword">const</span> DIR = {
  <span class="hljs-attr">left</span>:  { <span class="hljs-attr">x</span>: <span class="hljs-number">-1</span>, <span class="hljs-attr">y</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'left'</span>  },
  <span class="hljs-attr">right</span>: { <span class="hljs-attr">x</span>:  <span class="hljs-number">1</span>, <span class="hljs-attr">y</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'right'</span> },
  <span class="hljs-attr">up</span>:    { <span class="hljs-attr">x</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">-1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'up'</span>    },
  <span class="hljs-attr">down</span>:  { <span class="hljs-attr">x</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>:  <span class="hljs-number">1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'down'</span>  },
};

<span class="hljs-comment">// Phaser game configuration</span>
<span class="hljs-comment">// - type: Phaser will use WebGL if possible, otherwise Canvas</span>
<span class="hljs-comment">// - parent: attach game canvas to &lt;div id="game"&gt;</span>
<span class="hljs-comment">// - width/height: set canvas size</span>
<span class="hljs-comment">// - backgroundColor: dark background from COLORS</span>
<span class="hljs-comment">// - scene: defines which functions run during preload, create, and update</span>
<span class="hljs-keyword">const</span> config = {
  <span class="hljs-attr">type</span>: Phaser.AUTO,
  <span class="hljs-attr">parent</span>: <span class="hljs-string">'game'</span>,
  <span class="hljs-attr">width</span>: WIDTH,
  <span class="hljs-attr">height</span>: HEIGHT,
  <span class="hljs-attr">backgroundColor</span>: COLORS.bg,
  <span class="hljs-attr">scene</span>: { preload, create, update }
};

<span class="hljs-comment">// Create a new Phaser game with the config</span>
<span class="hljs-keyword">new</span> Phaser.Game(config);
</code></pre>
<p>The <code>config</code> tells Phaser to create a canvas, set its size, and use your scene functions. <code>Phaser.AUTO</code> selects WebGL if possible and falls back to Canvas.</p>
<h2 id="heading-scene-state-and-helper-functions">Scene State and Helper Functions</h2>
<p>You need to store the snake’s body as grid cells, the rectangles that draw those cells, the direction of travel, the queued input, the food cell, the score, and the movement timer. A few helper functions keep the math clean.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 2)</span>

<span class="hljs-comment">// Snake state</span>
<span class="hljs-keyword">let</span> snake;           <span class="hljs-comment">// Array of grid cells [{x, y}, ...]; index 0 = head</span>
<span class="hljs-keyword">let</span> snakeRects;      <span class="hljs-comment">// Array of Phaser rectangles drawn at snake cell positions</span>
<span class="hljs-keyword">let</span> direction;       <span class="hljs-comment">// Current direction of snake movement (object from DIR)</span>
<span class="hljs-keyword">let</span> nextDirection;   <span class="hljs-comment">// Next direction chosen by player input (applied on step)</span>
<span class="hljs-keyword">let</span> food;            <span class="hljs-comment">// Current food cell {x, y}</span>
<span class="hljs-keyword">let</span> score = <span class="hljs-number">0</span>;       <span class="hljs-comment">// Current score count</span>
<span class="hljs-keyword">let</span> scoreText;       <span class="hljs-comment">// Phaser text object that displays the score</span>
<span class="hljs-keyword">let</span> moveEvent;       <span class="hljs-comment">// Phaser timer event to move snake at fixed intervals</span>
<span class="hljs-keyword">let</span> speedMs = <span class="hljs-number">130</span>;   <span class="hljs-comment">// Delay in milliseconds between moves (lower = faster)</span>

<span class="hljs-comment">// Input state</span>
<span class="hljs-keyword">let</span> cursors;         <span class="hljs-comment">// Phaser helper object for arrow keys</span>
<span class="hljs-keyword">let</span> spaceKey;        <span class="hljs-comment">// Phaser Key object for Space bar (restart the game)</span>

<span class="hljs-comment">/**
 * Convert a grid cell (x,y) to its pixel center (px,py) on the canvas.
 * Example: (0,0) -&gt; (8,8) if TILE=16. Ensures rectangles are centered.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">gridToPixelCenter</span>(<span class="hljs-params">x, y</span>) </span>{
  <span class="hljs-keyword">return</span> { <span class="hljs-attr">px</span>: x * TILE + TILE / <span class="hljs-number">2</span>, <span class="hljs-attr">py</span>: y * TILE + TILE / <span class="hljs-number">2</span> };
}

<span class="hljs-comment">/**
 * Pick a random grid cell that is not occupied by any cell in excludeCells.
 * - Creates a Set of occupied cells as "x,y" strings for fast lookup.
 * - Keeps generating random cells until it finds a free one.
 * Used to place food so it never spawns on the snake.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">randomFreeCell</span>(<span class="hljs-params">excludeCells</span>) </span>{
  <span class="hljs-keyword">const</span> occupied = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(excludeCells.map(<span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span> <span class="hljs-string">`<span class="hljs-subst">${c.x}</span>,<span class="hljs-subst">${c.y}</span>`</span>));
  <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
    <span class="hljs-keyword">const</span> x = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * COLS);
    <span class="hljs-keyword">const</span> y = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * ROWS);
    <span class="hljs-keyword">if</span> (!occupied.has(<span class="hljs-string">`<span class="hljs-subst">${x}</span>,<span class="hljs-subst">${y}</span>`</span>)) <span class="hljs-keyword">return</span> { x, y };
  }
}

<span class="hljs-comment">/**
 * Check if direction 'a' is exactly the opposite of direction 'b'.
 * Example: left vs right, or up vs down.
 * This prevents the snake from instantly turning 180° into itself.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isOpposite</span>(<span class="hljs-params">a, b</span>) </span>{
  <span class="hljs-keyword">return</span> a.x === -b.x &amp;&amp; a.y === -b.y;
}
</code></pre>
<p><code>gridToPixelCenter</code> converts a grid cell to the center point in pixels so rectangles line up. <code>randomFreeCell</code> finds a cell not used by the snake. <code>isOpposite</code> helps block instant reversals that would cause a crash.</p>
<h2 id="heading-preload-and-create">Preload and Create</h2>
<p>This game uses simple vector rectangles, so there are no images to load. You still define the scene functions since Phaser calls them by name from your config.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 3)</span>

<span class="hljs-comment">/**
 * preload()
 * Runs once before the game starts.
 * Used for loading images, sounds, and other assets.
 * In this version, we use simple colored rectangles (no assets needed).
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">preload</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// No assets to load in this version.</span>
}

<span class="hljs-comment">/**
 * create()
 * Runs once after preload. Sets up the game scene.
 * - Prepares keyboard input
 * - Calls initGame() to build the snake, food, score UI, and start movement
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Phaser helper that gives us arrow key input (up, down, left, right)</span>
  cursors = <span class="hljs-built_in">this</span>.input.keyboard.createCursorKeys();

  <span class="hljs-comment">// Register the Space bar key to restart the game later</span>
  spaceKey = <span class="hljs-built_in">this</span>.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);

  <span class="hljs-comment">// Initialize the game state (snake, food, score, timer)</span>
  <span class="hljs-comment">// Using call(this) so that initGame runs in the context of the scene</span>
  initGame.call(<span class="hljs-built_in">this</span>);
}
</code></pre>
<p>The <code>create</code> step sets up keyboard input. It then calls <code>initGame</code> to build the first snake, place food, and start the timer. The <code>call(this)</code> ensures the helper function can use the scene’s <code>this</code>.</p>
<h2 id="heading-initialize-the-game">Initialize the Game</h2>
<p>On a fresh start or a restart, you need to clear old timers and shapes, reset the score, build a short snake in the middle, draw it, place food, and start the movement loop.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 4)</span>

<span class="hljs-comment">/**
 * initGame()
 * Called when the game first starts or after pressing Space to restart.
 * - Clears old state (snake, food, timers)
 * - Resets score
 * - Creates a new snake in the center of the grid
 * - Spawns the first food
 * - Sets up score text
 * - Starts the timed loop that moves the snake
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// If an old movement timer exists, stop it (avoid multiple timers running)</span>
  <span class="hljs-keyword">if</span> (moveEvent) moveEvent.remove(<span class="hljs-literal">false</span>);

  <span class="hljs-comment">// If old snake rectangles exist, destroy them to clean the screen</span>
  <span class="hljs-keyword">if</span> (snakeRects) snakeRects.forEach(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> r.destroy());

  <span class="hljs-comment">// Reset the score and snake direction</span>
  score = <span class="hljs-number">0</span>;
  direction = DIR.right;     <span class="hljs-comment">// Snake starts moving to the right</span>
  nextDirection = DIR.right; <span class="hljs-comment">// Player input queue also points right</span>

  <span class="hljs-comment">// Find the starting position near the center of the grid</span>
  <span class="hljs-keyword">const</span> startX = <span class="hljs-built_in">Math</span>.floor(COLS / <span class="hljs-number">2</span>);
  <span class="hljs-keyword">const</span> startY = <span class="hljs-built_in">Math</span>.floor(ROWS / <span class="hljs-number">2</span>);

  <span class="hljs-comment">// Snake starts with 3 segments, head + 2 body pieces</span>
  snake = [
    { <span class="hljs-attr">x</span>: startX,     <span class="hljs-attr">y</span>: startY },     <span class="hljs-comment">// head (middle)</span>
    { <span class="hljs-attr">x</span>: startX - <span class="hljs-number">1</span>, <span class="hljs-attr">y</span>: startY },     <span class="hljs-comment">// body segment left of head</span>
    { <span class="hljs-attr">x</span>: startX - <span class="hljs-number">2</span>, <span class="hljs-attr">y</span>: startY },     <span class="hljs-comment">// tail further left</span>
  ];

  <span class="hljs-comment">// Create rectangle objects in Phaser to visually draw the snake</span>
  snakeRects = snake.map(<span class="hljs-function">(<span class="hljs-params">cell, i</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(cell.x, cell.y); <span class="hljs-comment">// convert grid to pixel center</span>
    <span class="hljs-keyword">const</span> color = i === <span class="hljs-number">0</span> ? COLORS.head : COLORS.body;    <span class="hljs-comment">// head is a brighter green</span>
    <span class="hljs-keyword">const</span> rect = <span class="hljs-built_in">this</span>.add.rectangle(px, py, TILE - <span class="hljs-number">2</span>, TILE - <span class="hljs-number">2</span>, color); <span class="hljs-comment">// slightly smaller for spacing</span>
    rect.setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0.5</span>);                             <span class="hljs-comment">// center anchor point</span>
    <span class="hljs-keyword">return</span> rect;
  });

  <span class="hljs-comment">// Spawn food at a random free cell (not overlapping snake)</span>
  food = randomFreeCell(snake);
  <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(food.x, food.y);

  <span class="hljs-comment">// If food already exists from a previous run, remove it first</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.foodRect) <span class="hljs-built_in">this</span>.foodRect.destroy();

  <span class="hljs-comment">// Draw the new food as a red rectangle</span>
  <span class="hljs-built_in">this</span>.foodRect = <span class="hljs-built_in">this</span>.add.rectangle(px, py, TILE - <span class="hljs-number">2</span>, TILE - <span class="hljs-number">2</span>, COLORS.food);

  <span class="hljs-comment">// If score text does not exist yet, create it</span>
  <span class="hljs-comment">// Otherwise (on restart), just reset its value</span>
  <span class="hljs-keyword">if</span> (!scoreText) {
    scoreText = <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">8</span>, <span class="hljs-number">6</span>, <span class="hljs-string">'Score: 0'</span>, { <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">'monospace'</span>, <span class="hljs-attr">fontSize</span>: <span class="hljs-number">18</span>, <span class="hljs-attr">color</span>: <span class="hljs-string">'#fff'</span> });
    <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">8</span>, <span class="hljs-number">28</span>, <span class="hljs-string">'Arrows to move. Space to restart.'</span>, { <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">'monospace'</span>, <span class="hljs-attr">fontSize</span>: <span class="hljs-number">14</span>, <span class="hljs-attr">color</span>: <span class="hljs-string">'#aaa'</span> });
  } <span class="hljs-keyword">else</span> {
    scoreText.setText(<span class="hljs-string">'Score: 0'</span>);
  }

  <span class="hljs-comment">// Reset speed and create a repeating timer</span>
  <span class="hljs-comment">// Every "speedMs" milliseconds, stepSnake() will run to move the snake</span>
  speedMs = <span class="hljs-number">130</span>;
  moveEvent = <span class="hljs-built_in">this</span>.time.addEvent({
    <span class="hljs-attr">delay</span>: speedMs,
    <span class="hljs-attr">loop</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">callback</span>: <span class="hljs-function">() =&gt;</span> stepSnake.call(<span class="hljs-built_in">this</span>) <span class="hljs-comment">// .call(this) keeps Phaser scene context</span>
  });

  <span class="hljs-comment">// If a "Game Over" message exists from the last run, remove it</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.gameOverText) {
    <span class="hljs-built_in">this</span>.gameOverText.destroy();
    <span class="hljs-built_in">this</span>.gameOverText = <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<p>The rectangles are one or two pixels smaller than the tile so you get a neat gap between cells. The time event calls <code>stepSnake</code> on a fixed rhythm. This rhythm is separate from the frame rate, which keeps movement stable across machines.</p>
<h2 id="heading-reading-input-each-frame">Reading Input Each Frame</h2>
<p>You will read the arrow keys in <code>update</code>. You don’t move the snake here. You only set the next direction. Movement happens on the timer so the game has a steady pace.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 5)</span>

<span class="hljs-comment">/**
 * update()
 * This runs every frame (Phaser’s game loop).
 * - Reads player input from arrow keys
 * - Updates "nextDirection" so the snake will turn on the next step
 * - Listens for Space bar press to restart the game if it’s over
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Check if LEFT arrow is pressed AND it’s not the opposite of current direction</span>
  <span class="hljs-keyword">if</span> (cursors.left.isDown &amp;&amp; !isOpposite(DIR.left, direction)) {
    nextDirection = DIR.left;

  <span class="hljs-comment">// Check if RIGHT arrow is pressed</span>
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cursors.right.isDown &amp;&amp; !isOpposite(DIR.right, direction)) {
    nextDirection = DIR.right;

  <span class="hljs-comment">// Check if UP arrow is pressed</span>
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cursors.up.isDown &amp;&amp; !isOpposite(DIR.up, direction)) {
    nextDirection = DIR.up;

  <span class="hljs-comment">// Check if DOWN arrow is pressed</span>
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cursors.down.isDown &amp;&amp; !isOpposite(DIR.down, direction)) {
    nextDirection = DIR.down;
  }

  <span class="hljs-comment">// If the game is over (a "Game Over" text exists)</span>
  <span class="hljs-comment">// AND the Space bar was just pressed → restart the game</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.gameOverText &amp;&amp; Phaser.Input.Keyboard.JustDown(spaceKey)) {
    initGame.call(<span class="hljs-built_in">this</span>); <span class="hljs-comment">// Reset everything (snake, food, score, timer)</span>
  }
}
</code></pre>
<p>This pattern prevents the snake from skipping cells if the frame rate spikes. It also makes the game feel fair. Your key presses get picked up, but the body moves on the beat set by the timer.</p>
<h2 id="heading-stepping-the-snake-eating-and-drawing">Stepping the Snake, Eating, and Drawing</h2>
<p>This function is the heart of the game. It picks up the queued input, computes the next head cell, checks collisions, moves or grows the snake, updates the score, and refreshes colors.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 6)</span>

<span class="hljs-comment">/**
 * stepSnake()
 * This function runs every "tick" (based on the timer).
 * - Moves the snake forward by one cell
 * - Checks for collisions (wall or self)
 * - Handles eating food (grow + score)
 * - Updates the snake's rectangles on the screen
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stepSnake</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Apply the direction chosen in update() (queued by player input)</span>
  direction = nextDirection;

  <span class="hljs-comment">// Get the current head of the snake</span>
  <span class="hljs-keyword">const</span> head = snake[<span class="hljs-number">0</span>];

  <span class="hljs-comment">// Create a new head position by moving one cell in the current direction</span>
  <span class="hljs-keyword">const</span> newHead = { <span class="hljs-attr">x</span>: head.x + direction.x, <span class="hljs-attr">y</span>: head.y + direction.y };

  <span class="hljs-comment">// === Collision Check #1: Wall ===</span>
  <span class="hljs-comment">// If the new head is outside the grid, the game ends</span>
  <span class="hljs-keyword">if</span> (newHead.x &lt; <span class="hljs-number">0</span> || newHead.x &gt;= COLS || newHead.y &lt; <span class="hljs-number">0</span> || newHead.y &gt;= ROWS) {
    <span class="hljs-keyword">return</span> endGame.call(<span class="hljs-built_in">this</span>);
  }

  <span class="hljs-comment">// === Collision Check #2: Self ===</span>
  <span class="hljs-comment">// If the new head overlaps any snake cell, the game ends</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; snake.length; i++) {
    <span class="hljs-keyword">if</span> (snake[i].x === newHead.x &amp;&amp; snake[i].y === newHead.y) {
      <span class="hljs-keyword">return</span> endGame.call(<span class="hljs-built_in">this</span>);
    }
  }

  <span class="hljs-comment">// === Check if food was eaten ===</span>
  <span class="hljs-keyword">const</span> ate = newHead.x === food.x &amp;&amp; newHead.y === food.y;

  <span class="hljs-comment">// Add the new head cell to the front of the snake array</span>
  snake.unshift(newHead);

  <span class="hljs-keyword">if</span> (!ate) {
    <span class="hljs-comment">// Case: Snake did NOT eat food → keep length the same</span>
    <span class="hljs-comment">// Remove last cell from snake array (tail)</span>
    snake.pop();

    <span class="hljs-comment">// Reuse the last rectangle object for performance</span>
    <span class="hljs-keyword">const</span> tailRect = snakeRects.pop();
    <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(newHead.x, newHead.y);
    tailRect.setPosition(px, py);       <span class="hljs-comment">// Move it to the new head position</span>
    snakeRects.unshift(tailRect);       <span class="hljs-comment">// Put it at the front of the rectangle list</span>
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Case: Snake DID eat food → grow longer</span>
    <span class="hljs-comment">// Create a new rectangle for the new head (since length increases)</span>
    <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(newHead.x, newHead.y);
    <span class="hljs-keyword">const</span> headRect = <span class="hljs-built_in">this</span>.add.rectangle(px, py, TILE - <span class="hljs-number">2</span>, TILE - <span class="hljs-number">2</span>, COLORS.head);
    snakeRects.unshift(headRect);

    <span class="hljs-comment">// Increase score and update text</span>
    score += <span class="hljs-number">10</span>;
    scoreText.setText(<span class="hljs-string">`Score: <span class="hljs-subst">${score}</span>`</span>);

    <span class="hljs-comment">// Place new food somewhere else</span>
    placeFood.call(<span class="hljs-built_in">this</span>);

    <span class="hljs-comment">// Speed up slightly as difficulty curve</span>
    maybeSpeedUp.call(<span class="hljs-built_in">this</span>);
  }

  <span class="hljs-comment">// === Update Colors ===</span>
  <span class="hljs-comment">// Ensure only index 0 is drawn as the "head" (bright green),</span>
  <span class="hljs-comment">// and the next segment becomes part of the "body"</span>
  <span class="hljs-keyword">if</span> (snakeRects[<span class="hljs-number">1</span>]) snakeRects[<span class="hljs-number">1</span>].setFillStyle(COLORS.body);
  snakeRects[<span class="hljs-number">0</span>].setFillStyle(COLORS.head);
}
</code></pre>
<p>The movement rule is simple. Add a head in the current direction. If you did not eat, remove the tail. If you ate, keep the tail to grow by one. To draw this, you reuse the last rectangle when you only move. You move it to the head’s new pixel location and put it at the front of the list. When you grow, you create a new rectangle for the new head.</p>
<h2 id="heading-spawning-food-and-increasing-speed">Spawning Food and Increasing Speed</h2>
<p>After eating, you need to place food in a new free cell. You can also nudge the speed so the game gets harder over time.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 7)</span>

<span class="hljs-comment">/**
 * placeFood()
 * Spawns food at a new random cell that is NOT part of the snake.
 * - Uses randomFreeCell() to avoid collisions with the snake
 * - Moves the existing red rectangle (foodRect) to the new spot
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">placeFood</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Pick a random free cell on the grid (not occupied by the snake)</span>
  food = randomFreeCell(snake);

  <span class="hljs-comment">// Convert that cell into pixel coordinates</span>
  <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(food.x, food.y);

  <span class="hljs-comment">// Move the existing food rectangle to the new position</span>
  <span class="hljs-built_in">this</span>.foodRect.setPosition(px, py);
}

<span class="hljs-comment">/**
 * maybeSpeedUp()
 * Makes the game a little harder each time food is eaten.
 * - Decreases the move delay (snake moves faster)
 * - Restarts the timer with the new speed
 * - Stops speeding up once a lower bound (70ms) is reached
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">maybeSpeedUp</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Only speed up if current speed is above the minimum threshold</span>
  <span class="hljs-keyword">if</span> (speedMs &gt; <span class="hljs-number">70</span>) {
    <span class="hljs-comment">// Make the snake faster by reducing the delay</span>
    speedMs -= <span class="hljs-number">3</span>;

    <span class="hljs-comment">// Remove the old movement timer</span>
    moveEvent.remove(<span class="hljs-literal">false</span>);

    <span class="hljs-comment">// Create a new timer with the updated speed</span>
    moveEvent = <span class="hljs-built_in">this</span>.time.addEvent({
      <span class="hljs-attr">delay</span>: speedMs,            <span class="hljs-comment">// shorter delay = faster movement</span>
      <span class="hljs-attr">loop</span>: <span class="hljs-literal">true</span>,                <span class="hljs-comment">// repeat forever until game over</span>
      <span class="hljs-attr">callback</span>: <span class="hljs-function">() =&gt;</span> stepSnake.call(<span class="hljs-built_in">this</span>) <span class="hljs-comment">// keep "this" as the scene</span>
    });
  }
}
</code></pre>
<p>There is a lower bound for speed so the game does not become unreadable. You can tune these numbers to match the feel you want.</p>
<h2 id="heading-game-over-and-restart">Game Over and Restart</h2>
<p>When the snake hits a wall or itself, the run ends. You stop the timer and show a message. Pressing space restarts the game.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js (part 8)</span>

<span class="hljs-comment">/**
 * endGame()
 * Called when the snake hits a wall or itself.
 * - Stops the movement timer (snake no longer moves)
 * - Displays a "Game Over" message with the final score
 * - Waits for the player to press Space (handled in update()) to restart
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">endGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Stop the movement timer so the snake no longer steps forward</span>
  moveEvent.remove(<span class="hljs-literal">false</span>);

  <span class="hljs-comment">// Define text style for the "Game Over" message</span>
  <span class="hljs-keyword">const</span> style = {
    <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">'monospace'</span>,
    <span class="hljs-attr">fontSize</span>: <span class="hljs-number">28</span>,
    <span class="hljs-attr">color</span>: <span class="hljs-string">'#fff'</span>,
    <span class="hljs-attr">align</span>: <span class="hljs-string">'center'</span>
  };

  <span class="hljs-comment">// Message shows "Game Over", final score, and restart instructions</span>
  <span class="hljs-keyword">const</span> msg = <span class="hljs-string">`Game Over\nScore: <span class="hljs-subst">${score}</span>\nPress Space to Restart`</span>;

  <span class="hljs-comment">// Add text to the center of the screen</span>
  <span class="hljs-comment">// .setOrigin(0.5, 0.5) makes the text anchor at its center</span>
  <span class="hljs-built_in">this</span>.gameOverText = <span class="hljs-built_in">this</span>.add.text(WIDTH / <span class="hljs-number">2</span>, HEIGHT / <span class="hljs-number">2</span>, msg, style).setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0.5</span>);
}
</code></pre>
<p>The <code>update</code> function you wrote earlier listens for space using <code>JustDown</code>, so the restart happens only once per key press.</p>
<h2 id="heading-how-the-whole-thing-fits-together">How the Whole Thing Fits Together</h2>
<p>You now have the full loop of a Phaser scene. The <code>preload</code> is empty in this version because rectangles do not need assets. The <code>create</code> step connects keyboard input and calls <code>initGame</code>. The timer created in <code>initGame</code> keeps time for movement.</p>
<p>The <code>update</code> step reads keys every frame and sets a future direction. The <code>stepSnake</code> function runs on the timer. It moves the head, checks for a crash, handles growth, reuses shapes for performance, and updates the score. When the run ends, <code>endGame</code> stops the timer and shows a clear message. A single key press calls <code>initGame</code> to start fresh.</p>
<p>This style maps well to many other small games. If you can express your game state as data and move it on a schedule, you can draw it with simple shapes or sprites. Phaser’s time events give you a clean heartbeat. Its input system gives you easy key handling. Its drawing API makes it quick to show rectangles, text, or images.</p>
<h2 id="heading-useful-next-steps">Useful Next Steps</h2>
<p>There are many small features you can add without changing the core. You can store a high score in <code>localStorage</code>. You can add simple sounds when you eat or when the game ends by loading audio in <code>preload</code> and calling <code>this.sound.play</code> in the right places.</p>
<p>You can make the world wrap at the edges by replacing the wall check with modulo math so the snake appears on the opposite side. You can theme the game by swapping rectangle colors or replacing rectangles with images.</p>
<p>Each of these additions builds on the same base. Keep the grid logic simple. Keep the state in clear arrays and objects. Move on a fixed timer. Draw based on the state. That’s the pattern.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>You started with a blank page and ended with a working browser game. You set up Phaser, built a scene, and used a timer to drive movement. You learned how to handle input in a safe way, how to grow the snake, how to detect collisions, and how to draw with rectangles. You kept the code tidy with small helper functions and clear names.</p>
<p>From here, you can branch out to other grid games like Tetris or Minesweeper, or you can try a different style like Pong or Breakout. The structure will be similar. A scene to set things up, a timer or physics step to move things along, and a few rules that define the fun. That’s the beauty of Phaser for beginners.</p>
<p>If you’re into online gaming, <a target="_blank" href="https://gameboost.com/">GameBoost</a> is the place to be. Discover <a target="_blank" href="https://gameboost.com/grand-theft-auto-v/accounts">GTA Modded Accounts</a> packed with exclusive upgrades. You’ll also find accounts for other fan-favorite titles like Fortnite, Grow a Garden, Clash of Clans, and more – all in one trusted marketplace.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Tic Tac Toe Game with Phaser.js ]]>
                </title>
                <description>
                    <![CDATA[ Tic-Tac-Toe is a great project for beginners who want to learn how to build games. It’s simple to understand but gives you the chance to learn about game state, player turns, winning logic, and user input. In this tutorial, you’ll learn how to build ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-tic-tac-toe-game-with-phaserjs/</link>
                <guid isPermaLink="false">68a62b1746588cd24055077f</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 20 Aug 2025 20:07:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755720429987/ba7670ba-12b0-4c0a-a09b-80936ba1a023.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Tic-Tac-Toe is a great project for beginners who want to learn how to build games. It’s simple to understand but gives you the chance to learn about game state, player turns, winning logic, and user input.</p>
<p>In this tutorial, you’ll learn how to build tic-tac-toe using <a target="_blank" href="https://phaser.io/">Phaser.js</a>, a fast, fun, and open source framework for making 2D games in the browser.</p>
<p>If you’re new to Phaser.js, don’t worry. We’ll walk through everything step-by-step. By the end, you’ll have a working game that you can play, share, or build upon.</p>
<p>You can <a target="_blank" href="https://manishshivanandhan.com/phaser-tic-tac-toe">play the game here</a> to get a feel of what you are going to build.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-phaserjs">What is Phaser.js?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-game-configuration">How to Set Up the Game Configuration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-preload-assets">How to Preload Assets</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-game-scene">How to Create the Game Scene</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-draw-the-grid">How to Draw the Grid</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-initialize-and-reset-the-game">How to Initialize and Reset the Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-player-input">How to Handle Player Input</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-place-marks-on-the-board">How to Place Marks on the Board</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-check-for-a-winner">How to Check for a Winner</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-detect-a-draw">How to Detect a Draw</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-draw-the-winning-line">How to Draw the Winning Line</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-what-is-phaserjs">What is Phaser.js?</h2>
<p>Phaser.js is a free and open-source JavaScript game framework. It helps developers create HTML5 games that work across web browsers. Phaser handles things like rendering graphics, detecting input, and running the game loop.</p>
<p>You can use Phaser to make simple games like Pong and Tic-Tac-Toe or advanced platformers and role playing games. It supports both Canvas and WebGL rendering, so your games will run smoothly on most devices.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Create a folder for your project and add two files: <code>index.html</code> and <code>game.js</code>. The HTML file loads Phaser and the JavaScript file contains the game logic. <a target="_blank" href="https://github.com/manishmshiva/phaser-tic-tac-toe">Here is the repository</a> with the finished code.</p>
<p>Here’s what the <code>index.html</code> file should look like:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Tic Tac Toe — Phaser 3<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width,initial-scale=1"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-tag">html</span>, <span class="hljs-selector-tag">body</span> { <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>; <span class="hljs-attribute">background</span>: <span class="hljs-number">#0f172a</span>; <span class="hljs-attribute">display</span>: grid; <span class="hljs-attribute">place-items</span>: center; }
    <span class="hljs-selector-id">#game</span> { <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-number">30px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,.<span class="hljs-number">35</span>); <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>; <span class="hljs-attribute">overflow</span>: hidden; }
    <span class="hljs-selector-class">.hint</span> { <span class="hljs-attribute">color</span>: <span class="hljs-number">#e2e8f0</span>; <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">10px</span>; <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>; <span class="hljs-attribute">text-align</span>: center; <span class="hljs-attribute">opacity</span>: .<span class="hljs-number">85</span>; }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hint"</span>&gt;</span>Click a cell to play. Tap “Restart” to start over.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./game.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This sets up a simple HTML page, loads Phaser from a CDN, and points to your <code>game.js</code> file. The <code>#game</code> container is where Phaser will insert the game canvas.</p>
<h2 id="heading-how-to-set-up-the-game-configuration">How to Set Up the Game Configuration</h2>
<p>Phaser games are built from a configuration object that defines things like width, height, background color, and which functions to call for loading, creating, and updating the game.</p>
<pre><code class="lang-js">(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> GRID = <span class="hljs-number">3</span>;
  <span class="hljs-keyword">const</span> CELL = <span class="hljs-number">120</span>;
  <span class="hljs-keyword">const</span> BOARD = GRID * CELL;
  <span class="hljs-keyword">const</span> HUD = <span class="hljs-number">72</span>;
  <span class="hljs-keyword">const</span> WIDTH = BOARD;
  <span class="hljs-keyword">const</span> HEIGHT = BOARD + HUD;

  <span class="hljs-keyword">let</span> scene;
  <span class="hljs-keyword">let</span> board;
  <span class="hljs-keyword">let</span> currentPlayer;
  <span class="hljs-keyword">let</span> gameOver;

  <span class="hljs-keyword">let</span> gridGfx;
  <span class="hljs-keyword">let</span> overlayGfx;
  <span class="hljs-keyword">let</span> marks = [];
  <span class="hljs-keyword">let</span> statusText;
  <span class="hljs-keyword">let</span> restartText;

  <span class="hljs-keyword">const</span> config = {
    <span class="hljs-attr">type</span>: Phaser.AUTO,
    <span class="hljs-attr">parent</span>: <span class="hljs-string">"game"</span>,
    <span class="hljs-attr">width</span>: WIDTH,
    <span class="hljs-attr">height</span>: HEIGHT,
    <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#ffffff"</span>,
    <span class="hljs-attr">scale</span>: { <span class="hljs-attr">mode</span>: Phaser.Scale.FIT, <span class="hljs-attr">autoCenter</span>: Phaser.Scale.CENTER_BOTH },
    <span class="hljs-attr">scene</span>: { preload, create, update }
  };

  <span class="hljs-keyword">new</span> Phaser.Game(config);
</code></pre>
<p>We start by defining constants for the grid size and cell size. The <code>config</code> object tells Phaser to create a game with these dimensions and use the <code>preload</code>, <code>create</code>, and <code>update</code> functions we will define.</p>
<h2 id="heading-how-to-preload-assets">How to Preload Assets</h2>
<p>Since we are drawing everything with Phaser’s graphics and text tools, we do not need to load any external images or sounds.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">preload</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// No assets to load</span>
  }
</code></pre>
<p>This is a placeholder that lets Phaser call <code>preload</code> before the game starts.</p>
<h2 id="heading-how-to-create-the-game-scene">How to Create the Game Scene</h2>
<p>The <code>create</code> function runs once at the start of the scene. Here we draw the grid, set up the initial state, and add UI elements.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"></span>) </span>{
    scene = <span class="hljs-built_in">this</span>;

    gridGfx = scene.add.graphics({ <span class="hljs-attr">lineStyle</span>: { <span class="hljs-attr">width</span>: <span class="hljs-number">4</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0x000000</span> } });
    overlayGfx = scene.add.graphics();
    drawGrid();

    initGame();

    statusText = scene.add.text(WIDTH / <span class="hljs-number">2</span>, BOARD + <span class="hljs-number">12</span>, <span class="hljs-string">"Player X's turn"</span>, {
      <span class="hljs-attr">fontSize</span>: <span class="hljs-string">"20px"</span>,
      <span class="hljs-attr">color</span>: <span class="hljs-string">"#111"</span>,
      <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">"Arial, Helvetica, sans-serif"</span>
    }).setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0</span>);

    restartText = scene.add.text(WIDTH / <span class="hljs-number">2</span>, BOARD + <span class="hljs-number">38</span>, <span class="hljs-string">"Restart"</span>, {
      <span class="hljs-attr">fontSize</span>: <span class="hljs-string">"18px"</span>,
      <span class="hljs-attr">color</span>: <span class="hljs-string">"#2563eb"</span>,
      <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">"Arial, Helvetica, sans-serif"</span>
    }).setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0</span>).setInteractive({ <span class="hljs-attr">useHandCursor</span>: <span class="hljs-literal">true</span> });

    restartText.on(<span class="hljs-string">"pointerup"</span>, hardReset);

    scene.input.on(<span class="hljs-string">"pointerdown"</span>, onPointerDown, scene);
  }
</code></pre>
<p>We created two <code>Graphics</code> objects: one for the static grid and another for the win line. Then we called <code>drawGrid()</code> and <code>initGame()</code> to set up the game board. The status text and restart button are placed below the grid. We also listened for clicks on the board with <code>pointerdown</code>.</p>
<h2 id="heading-how-to-draw-the-grid">How to Draw the Grid</h2>
<p>The grid is made up of two vertical and two horizontal lines.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGrid</span>(<span class="hljs-params"></span>) </span>{
    gridGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(CELL, <span class="hljs-number">0</span>, CELL, BOARD));
    gridGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(CELL * <span class="hljs-number">2</span>, <span class="hljs-number">0</span>, CELL * <span class="hljs-number">2</span>, BOARD));
    gridGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(<span class="hljs-number">0</span>, CELL, BOARD, CELL));
    gridGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(<span class="hljs-number">0</span>, CELL * <span class="hljs-number">2</span>, BOARD, CELL * <span class="hljs-number">2</span>));
  }
</code></pre>
<p>We use <code>Phaser.Geom.Line</code> to define the start and end points for each line and then draw them with <code>strokeLineShape</code>.</p>
<h2 id="heading-how-to-initialize-and-reset-the-game">How to Initialize and Reset the Game</h2>
<p>The <code>initGame</code> function sets up a new game, and <code>hardReset</code> is called when the restart button is clicked.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initGame</span>(<span class="hljs-params"></span>) </span>{
    board = <span class="hljs-built_in">Array</span>.from({ <span class="hljs-attr">length</span>: GRID }, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Array</span>(GRID).fill(<span class="hljs-string">""</span>));
    currentPlayer = <span class="hljs-string">"X"</span>;
    gameOver = <span class="hljs-literal">false</span>;
    overlayGfx.clear();
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> t <span class="hljs-keyword">of</span> marks) t.destroy();
    marks = [];
    setStatus(<span class="hljs-string">"Player X's turn"</span>);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hardReset</span>(<span class="hljs-params"></span>) </span>{
    initGame();
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setStatus</span>(<span class="hljs-params">msg</span>) </span>{
    statusText &amp;&amp; statusText.setText(msg);
  }
</code></pre>
<p>The board is represented by a 2D array filled with empty strings. The current player starts as X, and the <code>marks</code> array keeps track of text objects so we can clear them on reset.</p>
<h2 id="heading-how-to-handle-player-input">How to Handle Player Input</h2>
<p>When the player clicks a cell, we determine its row and column and check if the move is valid.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onPointerDown</span>(<span class="hljs-params">pointer</span>) </span>{
    <span class="hljs-keyword">if</span> (gameOver) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">if</span> (pointer.y &gt; BOARD) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">const</span> col = <span class="hljs-built_in">Math</span>.floor(pointer.x / CELL);
    <span class="hljs-keyword">const</span> row = <span class="hljs-built_in">Math</span>.floor(pointer.y / CELL);
    <span class="hljs-keyword">if</span> (!inBounds(row, col)) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">if</span> (board[row][col] !== <span class="hljs-string">""</span>) <span class="hljs-keyword">return</span>;

    placeMark(row, col, currentPlayer);

    <span class="hljs-keyword">const</span> win = checkWin(board);
    <span class="hljs-keyword">if</span> (win) {
      gameOver = <span class="hljs-literal">true</span>;
      drawWinLine(win);
      setStatus(<span class="hljs-string">`Player <span class="hljs-subst">${currentPlayer}</span> wins!`</span>);
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">if</span> (isFull(board)) {
      gameOver = <span class="hljs-literal">true</span>;
      setStatus(<span class="hljs-string">"Draw! No more moves."</span>);
      <span class="hljs-keyword">return</span>;
    }

    currentPlayer = currentPlayer === <span class="hljs-string">"X"</span> ? <span class="hljs-string">"O"</span> : <span class="hljs-string">"X"</span>;
    setStatus(<span class="hljs-string">`Player <span class="hljs-subst">${currentPlayer}</span>'s turn`</span>);
  }
</code></pre>
<p>This ensures that we only act if the game is not over, the click is inside the board, and the chosen cell is empty. After placing a mark, we check for a win or draw before switching turns.</p>
<h2 id="heading-how-to-place-marks-on-the-board">How to Place Marks on the Board</h2>
<p>We display an X or O at the center of the clicked cell.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">inBounds</span>(<span class="hljs-params">r, c</span>) </span>{
    <span class="hljs-keyword">return</span> r &gt;= <span class="hljs-number">0</span> &amp;&amp; r &lt; GRID &amp;&amp; c &gt;= <span class="hljs-number">0</span> &amp;&amp; c &lt; GRID;
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">placeMark</span>(<span class="hljs-params">row, col, player</span>) </span>{
    board[row][col] = player;
    <span class="hljs-keyword">const</span> cx = col * CELL + CELL / <span class="hljs-number">2</span>;
    <span class="hljs-keyword">const</span> cy = row * CELL + CELL / <span class="hljs-number">2</span>;
    <span class="hljs-keyword">const</span> t = scene.add.text(cx, cy, player, {
      <span class="hljs-attr">fontSize</span>: <span class="hljs-built_in">Math</span>.floor(CELL * <span class="hljs-number">0.66</span>) + <span class="hljs-string">"px"</span>,
      <span class="hljs-attr">color</span>: <span class="hljs-string">"#111111"</span>,
      <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">"Arial, Helvetica, sans-serif"</span>
    }).setOrigin(<span class="hljs-number">0.5</span>);
    marks.push(t);
  }
</code></pre>
<p>The coordinates are calculated so the text is centered in the cell. We store the text object in the <code>marks</code> array so it can be removed when resetting.</p>
<h2 id="heading-how-to-check-for-a-winner">How to Check for a Winner</h2>
<p>We check rows, columns, and diagonals to see if the current player has three in a row.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkWin</span>(<span class="hljs-params">b</span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> r = <span class="hljs-number">0</span>; r &lt; GRID; r++) {
      <span class="hljs-keyword">if</span> (b[r][<span class="hljs-number">0</span>] &amp;&amp; b[r][<span class="hljs-number">0</span>] === b[r][<span class="hljs-number">1</span>] &amp;&amp; b[r][<span class="hljs-number">1</span>] === b[r][<span class="hljs-number">2</span>]) {
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">kind</span>: <span class="hljs-string">"row"</span>, <span class="hljs-attr">index</span>: r };
      }
    }
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> c = <span class="hljs-number">0</span>; c &lt; GRID; c++) {
      <span class="hljs-keyword">if</span> (b[<span class="hljs-number">0</span>][c] &amp;&amp; b[<span class="hljs-number">0</span>][c] === b[<span class="hljs-number">1</span>][c] &amp;&amp; b[<span class="hljs-number">1</span>][c] === b[<span class="hljs-number">2</span>][c]) {
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">kind</span>: <span class="hljs-string">"col"</span>, <span class="hljs-attr">index</span>: c };
      }
    }
    <span class="hljs-keyword">if</span> (b[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] &amp;&amp; b[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] === b[<span class="hljs-number">1</span>][<span class="hljs-number">1</span>] &amp;&amp; b[<span class="hljs-number">1</span>][<span class="hljs-number">1</span>] === b[<span class="hljs-number">2</span>][<span class="hljs-number">2</span>]) {
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">kind</span>: <span class="hljs-string">"diag"</span> };
    }
    <span class="hljs-keyword">if</span> (b[<span class="hljs-number">0</span>][<span class="hljs-number">2</span>] &amp;&amp; b[<span class="hljs-number">0</span>][<span class="hljs-number">2</span>] === b[<span class="hljs-number">1</span>][<span class="hljs-number">1</span>] &amp;&amp; b[<span class="hljs-number">1</span>][<span class="hljs-number">1</span>] === b[<span class="hljs-number">2</span>][<span class="hljs-number">0</span>]) {
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">kind</span>: <span class="hljs-string">"anti"</span> };
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }
</code></pre>
<p>If a win is found, we return an object describing the winning line so it can be drawn.</p>
<h2 id="heading-how-to-detect-a-draw">How to Detect a Draw</h2>
<p>If every cell is filled and there is no winner, the game ends in a draw.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isFull</span>(<span class="hljs-params">b</span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> r = <span class="hljs-number">0</span>; r &lt; GRID; r++) {
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> c = <span class="hljs-number">0</span>; c &lt; GRID; c++) {
        <span class="hljs-keyword">if</span> (b[r][c] === <span class="hljs-string">""</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
      }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  }
</code></pre>
<p>This loops over every cell and returns false if any are empty.</p>
<h2 id="heading-how-to-draw-the-winning-line">How to Draw the Winning Line</h2>
<p>A red line is drawn over the winning cells.</p>
<pre><code class="lang-js">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawWinLine</span>(<span class="hljs-params">res</span>) </span>{
    overlayGfx.clear();
    overlayGfx.lineStyle(<span class="hljs-number">6</span>, <span class="hljs-number">0xef4444</span>, <span class="hljs-number">1</span>);
    <span class="hljs-keyword">const</span> pad = <span class="hljs-number">14</span>;
    <span class="hljs-keyword">const</span> half = CELL / <span class="hljs-number">2</span>;

    <span class="hljs-keyword">if</span> (res.kind === <span class="hljs-string">"row"</span>) {
      <span class="hljs-keyword">const</span> y = res.index * CELL + half;
      overlayGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(pad, y, BOARD - pad, y));
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (res.kind === <span class="hljs-string">"col"</span>) {
      <span class="hljs-keyword">const</span> x = res.index * CELL + half;
      overlayGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(x, pad, x, BOARD - pad));
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (res.kind === <span class="hljs-string">"diag"</span>) {
      overlayGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(pad, pad, BOARD - pad, BOARD - pad));
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (res.kind === <span class="hljs-string">"anti"</span>) {
      overlayGfx.strokeLineShape(<span class="hljs-keyword">new</span> Phaser.Geom.Line(BOARD - pad, pad, pad, BOARD - pad));
    }
  }
})();
</code></pre>
<p>The coordinates are calculated based on the type of win to ensure the line passes through the correct cells.</p>
<p>Great. Now open <code>index.html</code> and you can start playing the game!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754903555585/eac6d264-a50a-4bc6-8acc-6cdf7fc214eb.png" alt="Final Game" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>You have now built a complete Tic Tac Toe game in Phaser.js. This includes a 3x3 grid, alternating turns, win detection with a highlight line, draw detection, and a restart button. The code uses core game development concepts like input handling, game state management, and rendering, which you can use in larger projects.</p>
<p>If you enjoy online games, check out <a target="_blank" href="https://gameboost.com/">GameBoost</a>, the ultimate marketplace for gamers. You can find <a target="_blank" href="https://gameboost.com/fortnite/accounts">Fortnite accounts</a> with exclusive skins, along with options for other popular games like Grow a Garden, Clash of Clans, and more.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Powerful JavaScript Frameworks for Game Developers ]]>
                </title>
                <description>
                    <![CDATA[ Getting into game development with JavaScript can be a blast. JS is fast, flexible, and works right in the browser.  Whether you’re making a small puzzle game or a full 3D experience, JavaScript has the tools to help you bring your ideas to life.  Bu... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/javascript-frameworks-for-game-developers/</link>
                <guid isPermaLink="false">686316a6a2a907c72bebdef0</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Mon, 30 Jun 2025 22:58:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751324311029/630e6ee2-5e00-4341-a0be-5bd426cf87a4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Getting into game development with JavaScript can be a blast. JS is fast, flexible, and works right in the browser. </p>
<p>Whether you’re making a small puzzle game or a full 3D experience, JavaScript has the tools to help you bring your ideas to life. </p>
<p>But with so many libraries and frameworks out there, it’s easy to feel overwhelmed. So let’s break it down. </p>
<p>Here are five of the best JavaScript frameworks for game development, each with its own strengths and ideal use cases. All the frameworks are fully free and open source, so you can use them without worrying about the costs.</p>
<h2 id="heading-phaser"><strong>Phaser</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751029417614/a7e492ee-a210-4a0a-ab26-bc80caed56f9.png" alt="Phaser.js" class="image--center mx-auto" width="3000" height="2500" loading="lazy"></p>
<p><a target="_blank" href="https://phaser.io/">Phaser</a> is often the first name that comes up when people talk about JavaScript game engines, and for good reason. </p>
<p>It’s designed to build 2D games that run in the browser or on mobile devices. </p>
<p>Phaser is lightweight but powerful. It has all the features you need to make a complete game, including physics, animations, input handling, sound, and asset management.</p>
<p>Phaser provides a gentle introduction to game development if you're just starting out. You don’t need to worry about rendering pipelines or low-level graphics APIs. It handles the complex stuff behind the scenes so you can focus on making your game fun. </p>
<p>Phaser uses <a target="_blank" href="https://github.com/pixijs/pixijs">Pixi.js</a> under the hood for rendering, which means it’s optimized for performance and compatible with older browsers too. You can also export your game to mobile platforms using wrappers like Cordova or Capacitor. </p>
<p>This makes Phaser a fantastic choice for indie developers and hobbyists who want to make and share games quickly.</p>
<h2 id="heading-pixijs"><strong>Pixi.js</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751029429477/7267a68f-365e-48fc-8783-7b892464dbcc.jpeg" alt="Pixi.js" class="image--center mx-auto" width="1200" height="675" loading="lazy"></p>
<p><a target="_blank" href="https://pixijs.com/">Pixi.js</a> isn’t a full game engine like Phaser. Instead, it’s a high-speed 2D and <a target="_blank" href="https://creamyanimation.com/what-is-2-5d-animation/">2.5D animation</a> rendering engine that gives you fine-grained control over how things appear on screen. </p>
<p>If you’re working on a game that involves a lot of components, animations, or visual effects, Pixi.js gives you the tools to make it look amazing.</p>
<p>Because it focuses purely on rendering, Pixi.js is extremely fast. It uses <a target="_blank" href="https://en.wikipedia.org/wiki/WebGL">WebGL</a> when available and falls back to Canvas if needed. This makes it a great option for UI-heavy games or experiences where you need to squeeze out every bit of performance. </p>
<p>Since Pixi.js doesn’t include game logic, physics, or input systems like a full game engine, if you need those, you’ll have to add them yourself.</p>
<p>For example, you can use <a target="_blank" href="https://brm.io/matter-js/">Matter.js</a> for physics, which handles collision detection and rigid body physics in 2D. Or you can use <a target="_blank" href="https://colyseus.io/">Colyseus</a> for multiplayer logic.</p>
<p>If you want more control or already have your own game logic written, Pixi.js might be the perfect fit.</p>
<h2 id="heading-threejs"><strong>Three.js</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751029442764/c05fabb3-a8ce-4ae9-a573-a36a9683a297.webp" alt="Three.js" class="image--center mx-auto" width="724" height="366" loading="lazy"></p>
<p>Now let’s talk about 3D. <a target="_blank" href="https://threejs.org/">Three.js</a> is the most popular JavaScript library for rendering 3D graphics in the browser using WebGL. </p>
<p>It gives you a powerful set of tools for working with scenes, lights, cameras, meshes, and materials. If you’ve ever seen a 3D demo or game in a browser, chances are it used Three.js.</p>
<p>Three.js is incredibly flexible. You can use it to build full games, data visualizations, interactive art, or virtual reality scenes. </p>
<p>But with that flexibility comes a steeper learning curve. You need to understand some basic 3D concepts like coordinate systems, shading, and rendering loops. The good news is that there are plenty of examples, and the community is active and helpful.</p>
<p>One of the coolest things about Three.js is how well it integrates with other tools. You can load models from <a target="_blank" href="https://www.freecodecamp.org/news/blender-three-js-react-js/">Blender</a>, add post-processing effects, and even connect it to VR headsets. </p>
<p>If your dream is to build an interactive 3D world you can explore in the browser, Three.js gives you everything you need to make that happen.</p>
<h2 id="heading-babylonjs"><strong>Babylon.js</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751029467162/3b8ba124-ff32-49fa-b570-f2baf120b874.jpeg" alt="Babylon.js" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<p>While Three.js is all about flexibility, <a target="_blank" href="https://www.babylonjs.com/">Babylon.js</a> aims to be more of an all-in-one 3D game engine. </p>
<p>It includes a physics engine, collision detection, animation tools, and support for advanced features like real-time shadows, reflections, and virtual reality. </p>
<p>What makes Babylon.js stand out is its performance and developer experience. It’s optimized for modern browsers and devices, and the documentation is excellent. </p>
<p>There’s even a web-based playground where you can test and share code snippets live. That’s great for learning and debugging.</p>
<p>Let’s say you want to build a first-person shooter or a multiplayer 3D arena game. Babylon.js gives you all the structure you need including scene management, game loop handling, input systems, and more. You don’t have to piece together different libraries to make things work  –  it’s all built in.</p>
<p>Another plus is its strong <a target="_blank" href="https://en.wikipedia.org/wiki/WebXR">WebXR</a> support to create virtual reality or augmented reality experiences right in the browser. If that’s part of your plan, Babylon is definitely worth a look.</p>
<h2 id="heading-playcanvas"><strong>PlayCanvas</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751029474985/023baf2e-cd41-407a-a0d8-e4f48a669150.webp" alt="PlayCanvas" class="image--center mx-auto" width="1080" height="567" loading="lazy"></p>
<p>If you want to create 3D games but don’t want to dive deep into code right away, <a target="_blank" href="https://playcanvas.com/">PlayCanvas</a> offers a different approach. It’s a cloud-based 3D engine with a visual editor you can use directly in your browser. </p>
<p>You can drag and drop assets, write scripts, and preview changes in real time, all from a web interface.</p>
<p>This makes PlayCanvas great for teams or classroom settings where collaboration is key. You don’t need to set up a local development environment or deal with complex build tools. Just log in, open your project, and start building.</p>
<p>Under the hood, PlayCanvas is still a powerful engine, and you can get under the hood when you need to. It supports WebGL, physics, and even VR. It’s used by companies like Snapchat and Disney for lightweight 3D experiences.</p>
<p>Playcanvas also comes with a cloud solution with a generous free tier, so if you want to host your games in the cloud, check out their <a target="_blank" href="https://playcanvas.com/plans">pricing page</a>.</p>
<h2 id="heading-so-which-game-framework-should-you-pick"><strong>So, which game framework should you pick?</strong></h2>
<p>It depends on what kind of game you want to make. </p>
<p>If you’re just starting out and want to build a fun 2D game quickly, go with Phaser. It’s simple, forgiving, and has everything you need in one place.</p>
<p>If your game is more about visuals and speed, especially for 2D, Pixi.js might be a better fit. It gives you great rendering power without a lot of overhead.</p>
<p>For 3D projects, the choice comes down to complexity and flexibility. If you want full control and are comfortable managing your own systems, Three.js is perfect. If you want more built-in features and a smoother on-ramp, Babylon.js is a great choice.</p>
<p>And if you’re working with a team or prefer visual tools, PlayCanvas offers a modern, web-based way to build 3D games. </p>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>No matter which one you choose, the best way to learn is by building something small. Pick a simple idea like a top-down shooter, a 3D maze, or a basic puzzle, and try to finish it. You’ll learn a lot, and you’ll gain the confidence to tackle bigger projects later on.</p>
<p>JavaScript might not be the first language people think of for game development, but it’s more than capable. With the right framework, you can create beautiful, responsive games that run anywhere. So pick your tool, fire up your editor, and start building.</p>
<p>Hope you enjoyed this article. If you're into game development, check out the <a target="_blank" href="https://www.eldorado.gg/">Eldorado marketplace</a> – a platform for buying and selling in-game goods. You can also create dedicated e-commerce pages for your games, like <a target="_blank" href="https://www.eldorado.gg/roblox-grow-a-garden-items/i/243">Grow A Garden Shop</a>, where players can purchase tools to enhance their gameplay experience.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Godot – Course for Beginners in Spanish ]]>
                </title>
                <description>
                    <![CDATA[ Godot is an open-source, lightweight, and powerful game engine. This course will teach you how to use it to bring your game ideas to life. We just published a course on the freeCodeCamp.org Spanish YouTube channel that will guide you step by step thr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-godot-course-for-beginners-in-spanish/</link>
                <guid isPermaLink="false">68402e7af9ce257b34574bed</guid>
                
                    <category>
                        <![CDATA[ Godot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Estefania Cassingena Navone ]]>
                </dc:creator>
                <pubDate>Wed, 04 Jun 2025 11:31:06 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748954260255/3d544e9f-2191-409a-b52e-bcf24ceab5a6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Godot is an open-source, lightweight, and powerful game engine. This course will teach you how to use it to bring your game ideas to life.</p>
<p>We just published a course on the <a target="_blank" href="https://www.youtube.com/freecodecampespanol">freeCodeCamp.org Spanish YouTube channel</a> that will guide you step by step through the fundamentals of Godot. You’ll learn the core concepts that you need to get started.</p>
<p>If you have Spanish-speaking friends, you are welcome to share the <a target="_blank" href="https://www.freecodecamp.org/espanol/news/aprende-godot-curso-desde-cero"><strong>Spanish version of this article</strong></a> with them.</p>
<p>Luis Canary created this course. He is the Principal Gameplay Programmer at Pendulo Studios, a Madrid-based video game development company. He has taught courses at universities, schools, and companies and loves to share his passion for game development on his YouTube channel.</p>
<p>Before we dive into the course content, let's see what Godot is, and why you should learn it.</p>
<h2 id="heading-what-is-godot">What is Godot?</h2>
<p>Godot is an open-source game engine that you can use to build 2D and 3D games.</p>
<p>It has become very popular in the last few years, with robust features that make it a great tool in the game development world, without the heavy resource demands of some other game engines. This makes it a practical choice for creators looking for efficiency and high performance.</p>
<p>Its open-source license is great for independent developers and small teams, as it doesn’t come with any licensing fees. You’ll retain full ownership and control over the games and projects that you create with Godot.</p>
<p>This flexibility makes game development more approachable for everyone.</p>
<p>Godot also has its own user-friendly scripting language and node-based system. Its scripting language, called GDScript, is similar to Python and is easy to learn for beginners. Its intuitive node-based system simplifies the process of creating both 2D and 3D games.</p>
<p>By learning Godot, you’ll be taking your first steps into a career in game development.</p>
<p>💡 <strong>Tip:</strong> This course is perfect for anyone who wants to start developing video games. It covers core concepts in GDScript.</p>
<h2 id="heading-godot-course-in-spanish">Godot Course in Spanish</h2>
<p>Great. Now that you know more about Godot, let's see what you’ll learn during the course:</p>
<ul>
<li><p>Introduction</p>
</li>
<li><p>What is Godot?</p>
</li>
<li><p>Download and install Godot</p>
</li>
<li><p>Lighting</p>
</li>
<li><p>Materials and physics</p>
</li>
<li><p>UI</p>
</li>
<li><p>Movement</p>
</li>
<li><p>Player</p>
</li>
<li><p>First script</p>
</li>
<li><p>Update project</p>
</li>
<li><p>Change inputs for a 3D project</p>
</li>
<li><p>3D platforms and respawn player</p>
</li>
<li><p>Collect coins</p>
</li>
<li><p>3D camera rotation</p>
</li>
<li><p>Character model and animations</p>
</li>
<li><p>Music and sounds</p>
</li>
<li><p>Export video game .exe</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/espanol/news/content/images/2025/06/image.png" alt="Godot Fundamentals. Movement and Rotation." width="1920" height="1080" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/espanol/news/content/images/2025/06/image-1.png" alt="Practical project." width="1920" height="1080" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/espanol/news/content/images/2025/06/image-2.png" alt="Adding a character." width="1920" height="1080" loading="lazy"></p>
<p>By the end of the course, you’ll be familiar with the fundamentals of Godot and you’ll be ready to start developing video games.</p>
<p>If you’re ready to start learning Godot, check out the course on the <a target="_blank" href="https://www.youtube.com/freecodecampespanol">freeCodeCamp.org Spanish YouTube channel</a>:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/7898KcoAmLE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>✍️ Course created by Luis Canary.</p>
<ul>
<li><p>YouTube: <a target="_blank" href="https://www.youtube.com/channel/UC_XaEmy0Rz49GkrhtpzqWlw">@LuisCanary</a></p>
</li>
<li><p>Instagram: <a target="_blank" href="https://www.instagram.com/luiscanary_/">@luiscanary_</a></p>
</li>
<li><p>Twitter: <a target="_blank" href="https://x.com/luiscanary">@luiscanary</a></p>
</li>
<li><p>TikTok: <a target="_blank" href="https://www.tiktok.com/@luiscanary?lang=es">@luiscanary</a></p>
</li>
<li><p>Twitch: <a target="_blank" href="https://www.twitch.tv/luiscanary">LuisCanary</a></p>
</li>
<li><p>Discord: <a target="_blank" href="https://discord.com/invite/BEQ2UZY">Invitación</a></p>
</li>
<li><p>Facebook: <a target="_blank" href="https://www.facebook.com/LuisCanaryy/">LuisCanaryy</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Code a Crossy Road Game Clone with React Three Fiber ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you’ll learn how to create a clone of the mobile game Crossy Road with React Three Fiber. In a previous tutorial, I taught you how to build this game using Three.js and vanilla JavaScript. And here, you’ll learn how to make the same... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-code-a-crossy-road-game-clone-with-react-three-fiber/</link>
                <guid isPermaLink="false">67bf859b948d001fe6c9ab99</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ reactthreefiber ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hunor Márton Borbély ]]>
                </dc:creator>
                <pubDate>Wed, 26 Feb 2025 21:20:27 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740599557930/dcbb214e-c6d2-400e-8b2a-25fd81ac3c47.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you’ll learn how to create a clone of the mobile game Crossy Road with React Three Fiber. In a previous <a target="_blank" href="https://www.freecodecamp.org/news/how-to-code-a-crossy-road-game-clone-with-threejs/">tutorial</a>, I taught you how to build this game using Three.js and vanilla JavaScript. And here, you’ll learn how to make the same game with React Three Fiber instead.</p>
<p>The goal of this game is to move a character through an endless path of static and moving obstacles. You have to go around trees and avoid getting hit by cars.</p>
<p>There's a lot to cover in this tutorial: we will start with setting up the scene, the camera, and the lights. Then you’ll learn how to draw the player and the map with the trees and the cars. We’ll also cover how to animate the vehicles, and we’ll add event handlers to move the player through the map. Finally, we’ll add hit detection between the cars and the player.</p>
<p>This article is a shortened version of the Crossy Road tutorial from my site <a target="_blank" href="https://javascriptgametutorials.com/">JavaScriptGameTutorials.com</a>. The extended tutorial is also available as a video on <a target="_blank" href="https://www.youtube.com/watch?v=ccYrSACDNsw&amp;ab_channel=HunorM%C3%A1rtonBorb%C3%A9ly">YouTube</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-react-three-fiber-vs-threejs">React Three Fiber vs Three.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-game">How to Set Up the Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-render-a-map">How to Render a Map</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-animate-the-cars">How to Animate the Cars</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-move-the-player">How to Move the Player</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hit-detection">Hit Detection</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ol>
<h2 id="heading-react-three-fiber-vs-threejs">React Three Fiber vs Three.js</h2>
<p>So you might be wondering – what is React Three Fiber, and how does it compare to Three.js? React Three Fiber uses Three.js under the hood, but it gives us a different way to build up our game with React. It’s also easier to to set up, as React Three Fiber comes with sensible defaults for things like the camera.</p>
<p>React has became a leading front-end framework, and React Three Fiber lets you define a 3D scene using React's well-established patterns. You can break down the game into React components and use hooks for animation, event handling, and hit detection.</p>
<p>Under the hood, React Three Fiber still uses Three.js objects. In fact, in some cases, we will access the underlying Three.js objects and manipulate them directly for better performance. But as we build up the game, we use the familiar React patterns.</p>
<p>So which one should you use? If you are already familiar with React, then React Three Fiber might give more structure to your games. And after reading through this and building along with me, you’ll be better equipped to choose.</p>
<h2 id="heading-how-to-set-up-the-game"><strong>How to Set Up the Game</strong></h2>
<p>In this chapter, we’ll set up the drawing canvas, camera, and lights and render a box representing our player.</p>
<h3 id="heading-initializing-the-project"><strong>Initializing the Project</strong></h3>
<p>I recommend using Vite to initialize the project. To do so, go to your terminal and type <code>npm create vite</code>, which will create an initial project for you.</p>
<p>When generating the project, select <strong>React</strong> (because React Three Fiber uses React).</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create app</span>
npm create vite my-crossy-road-game
<span class="hljs-comment"># Select React as framework</span>
<span class="hljs-comment"># Select JavaScript</span>

<span class="hljs-comment"># Navigate to the project</span>
<span class="hljs-built_in">cd</span> my-crossy-road-game

<span class="hljs-comment"># Update react and react-dom</span>
npm install react@latest react-dom@latest

<span class="hljs-comment"># Install dependencies</span>
npm install three @react-three/fiber

<span class="hljs-comment"># Start development server</span>
npm run dev
</code></pre>
<p>At the time of writing this article, Vite will use React 18 by default. Meanwhile, React 19 is out, and the latest version of React Three Fiber is only compatible with React 19. So let’s update React and react-dom with <code>npm install react@latest react-dom@latest</code>.</p>
<p>After initializing the project, navigate to the project folder and install the additional dependencies. We will use Three.js and React Three Fiber with <code>npm install three @react-three/fiber</code>.</p>
<p>Finally, you can go to the terminal and type <code>npm run dev</code> to start a development server. This way, you can see live the result of your coding in the browser.</p>
<h3 id="heading-the-drawing-canvas"><strong>The Drawing Canvas</strong></h3>
<p>Let’s create a new component called <code>src/Game.jsx</code>. This will be the root of our game.</p>
<p>The <code>Scene</code> component will contain the drawing canvas, the camera, and the lights. We’ll pass on the <code>Player</code> component as its child, which will render a box. Later, we will add the <code>Map</code> component, including the trees, cars, and trucks. This component is also where the score indicator and the controls come later.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Scene } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Scene"</span>;
<span class="hljs-keyword">import</span> { Player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Player"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Game</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Scene</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Player</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Scene</span>&gt;</span></span>
  );
}
</code></pre>
<h3 id="heading-the-mainjsx-file"><strong>The main.jsx file</strong></h3>
<p>To use the new <code>Game</code> component as our root, we need to replace the original <code>App</code> component in the <code>src/main.jsx</code> file.</p>
<p>This will give you an error for now because we didn’t implement the <code>Scene</code> and <code>Player</code> components.</p>
<p>Now that we’ve replaced the <code>App</code> component, we can delete the original <code>App.jsx</code>, <code>App.css</code>, and the <code>assets</code> folder.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { StrictMode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> Game <span class="hljs-keyword">from</span> <span class="hljs-string">"./Game.jsx"</span>;

createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)).render(
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">StrictMode</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Game</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">StrictMode</span>&gt;</span></span>
);
</code></pre>
<p>Let’s also update the <code>index.css</code> file to make sure our drawing canvas fills the entire screen.</p>
<pre><code class="lang-javascript">body {
  <span class="hljs-attr">margin</span>: <span class="hljs-number">0</span>;
  display: flex;
  min-height: <span class="hljs-number">100</span>vh;
}

#root {
  <span class="hljs-attr">width</span>: <span class="hljs-number">100</span>%;
}
</code></pre>
<h3 id="heading-the-player"><strong>The Player</strong></h3>
<p>Let's start adding the necessary objects to render the first scene. Let's add a simple box to represent the player. We already added the player to the scene, so let's see how to define this player.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739804077435/9667f94f-5005-41f8-a6ed-faa6706f0be1.png" alt="The player" width="3840" height="2160" loading="lazy"></p>
<p>The player will be a simple box. To draw a 3D object, we’ll define a geometry and a material. The geometry defines the object's shape, and the material defines its appearance. Here, we’re using box geometry to define a box. The box geometry takes three arguments: the width, depth, and height of the box along the x, y, and z axes.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Player</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">group</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position</span>=<span class="hljs-string">{[0,</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">10</span>]}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[15,</span> <span class="hljs-attr">15</span>, <span class="hljs-attr">20</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0xffffff}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">group</span>&gt;</span></span>
  );
}
</code></pre>
<p>We have different options for the material. The main difference between them is how they react to light, if at all. Here, we're using <code>meshLambertMaterial</code>, a simple material that responds to light. We set the color property to white.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739804142917/7b6a49e2-30df-40d8-bd4c-7ac1a2725931.png" alt="Different light options" width="3840" height="2160" loading="lazy"></p>
<p>Then, we wrap the geometry and the material into a mesh, which we can add to the scene. We can also position this mesh by setting its X, Y, and Z positions. In the case of a box, these set the center position. By setting the Z position of this box, we’re elevating it above the ground by half its height. As a result, the bottom of the box will be standing on the ground.</p>
<p>We also wrap the mesh into a group element. This is not necessary at this point, but having this structure will be handy when animating the player. When it comes to player animation, we want to separate the horizontal and vertical movement. We want this to be able to follow the player with the camera as it moves but not to move the camera up and down when the player is jumping. We will move the group horizontally along the XY plane together with the camera and move the mesh vertically.</p>
<h3 id="heading-the-camera"><strong>The Camera</strong></h3>
<p>Now, let's look into different camera options. There are two main camera options: the perspective camera, as you can see on the left in the image below, and the orthographic camera, which you can see on the right.</p>
<p>The perspective camera is the default camera in Three.js and is the most common camera type across all video games. It creates a perspective projection, which makes things further away appear smaller and things right in front of the camera appear bigger.</p>
<p>On the other hand, the orthographic camera creates parallel projections, which means that objects are the same size regardless of their distance from the camera. We’ll use an orthographic camera here to give our game more of an arcade look.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739800656673/d2643544-b44c-418e-a475-2844e6626dbb.png" alt="Perspective vs orthographic camera" width="2420" height="1224" loading="lazy"></p>
<p>In Three.js, we place the 3D objects along the X, Y, and Z axes. We define the coordinate system in a way where the ground is on the XY plane so the player can move left and right along the x-axis, forward and backward along the y-axis, and when the player is jumping, it will go up along the z-axis.</p>
<p>We place the camera in this coordinate system to the right along the x-axis, behind the player along the y-axis, and above the ground. Then, the camera will look back at the origin of the coordinate system to the 0,0,0 coordinate, where the player will be placed initially.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739803742848/d202d09f-1c1b-4246-be23-2a6f6af52423.png" alt="The coordinate system" width="1576" height="892" loading="lazy"></p>
<h3 id="heading-the-lights"><strong>The Lights</strong></h3>
<p>There are many types of lights in Three.js. Here, we're going to use an ambient light and a directional light.</p>
<p>You can see the result of ambient light only on the left side of the below image. The ambient light brightens the entire scene. It doesn't have a specific position or direction. You can think of it like the light on a cloudy day when it's bright, but there are no shadows. The ambient light is used to simulate indirect light.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739800781538/9e140ef9-4133-4151-9fc5-b629504259a8.png" alt="Ambient vs directional light" width="2420" height="1224" loading="lazy"></p>
<p>Now, let's look at the directional light that you can see on the right of the image above. A directional light has a position and a target. It shines light in a specific direction with parallel light rays. Even though it has a position, you can rather think of it as the sun that is shining from very far away. The position here is more to define the direction of the light, but then all the other light rays are also parallel with this light ray. So you can think of it like the sun.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739805160031/4742e1de-5dc0-4443-9716-af18581bfa1e.png" alt="The directional light shines with parallel light rays" width="3840" height="2160" loading="lazy"></p>
<p>That's why we're combining an ambient light (so that we have a base brightness all around the scene) with a directional light (to illuminate specific sides of our objects with a brighter color).</p>
<h3 id="heading-the-scene"><strong>The Scene</strong></h3>
<p>After reviewing the different camera and light options, let’s put them together in the <code>Scene</code> component. We set up the canvas with an orthographic camera and lights.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Canvas } <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-three/fiber"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Scene = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Canvas</span>
      <span class="hljs-attr">orthographic</span>=<span class="hljs-string">{true}</span>
      <span class="hljs-attr">camera</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">up:</span> [<span class="hljs-attr">0</span>, <span class="hljs-attr">0</span>, <span class="hljs-attr">1</span>],
        <span class="hljs-attr">position:</span> [<span class="hljs-attr">300</span>, <span class="hljs-attr">-300</span>, <span class="hljs-attr">300</span>],
      }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ambientLight</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">directionalLight</span> <span class="hljs-attr">position</span>=<span class="hljs-string">{[-100,</span> <span class="hljs-attr">-100</span>, <span class="hljs-attr">200</span>]} /&gt;</span>
      {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">Canvas</span>&gt;</span></span>
  );
};
</code></pre>
<p>We use the <code>Canvas</code> component from <code>@react-three/fiber</code>. This component will contain every 3D object on the scene, so it has a <code>children</code> prop.</p>
<p>We set the <code>orthographic</code> prop to <code>true</code> to use an orthographic camera and the <code>camera</code> prop to define the camera’s position and orientation. The camera props require vectors or coordinates that are defined by the x, y, and z values.</p>
<p>The <code>up</code> prop sets the camera’s up vector. We set it to <code>[0, 0, 1]</code> to make the z-axis the up vector. The <code>position</code> prop sets the camera’s position. We move the camera to the right along the x-axis, backward along the y-axis, and up along the z-axis.</p>
<p>We also add the lights. We can use React Three Fiber-specific elements within the <code>Canvas</code> element. We add the <code>ambientLight</code> and <code>directionalLight</code> components to add lights to the scene. We position the directional light to the left along the x-axis, backward along the y-axis, and up along the z-axis.</p>
<p>This is how our first scene comes together. We rendered a simple box.</p>
<h2 id="heading-how-to-render-a-map"><strong>How to Render a Map</strong></h2>
<p>Now, let's add all the other objects to the scene. In this chapter, we’ll define the map. The map will consist of multiple rows, each described by metadata. Each row can be a forest, a car, or a truck lane. We’ll go through each type and define the 3D objects representing them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739803813951/3796e0c0-6f02-4c82-979b-a5192d8c3b8c.png" alt="The different row types" width="2560" height="1442" loading="lazy"></p>
<p>The map can be broken down into rows, and each row can be broken down into multiple tiles. The player will move from tile to tile. Trees are also placed on a distinct tile. Cars, on the other hand, do not relate to tiles. They move freely through the lane.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739803829719/38318821-8496-43d1-9b1c-a5b36d78af14.png" alt="A row can be broken down into a tile" width="2560" height="1442" loading="lazy"></p>
<p>We define a file for the constants. Here, we define the number of tiles in each row. In this case, there are 17 ties per row, going from -8 to +8. The player will start in the middle at tile zero.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> minTileIndex = <span class="hljs-number">-8</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> maxTileIndex = <span class="hljs-number">8</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> tilesPerRow = maxTileIndex - minTileIndex + <span class="hljs-number">1</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> tileSize = <span class="hljs-number">42</span>;
</code></pre>
<h3 id="heading-the-starting-row"><strong>The Starting Row</strong></h3>
<p>First, let's add the starting row. We’ll define a couple of components that we’re going to use to render the map, and we’ll render the initial row.</p>
<p>Let's create a new component called <code>Map</code>. Soon, we will add this group to the scene.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Map</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Grass</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{0}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}
</code></pre>
<p>Then, we set the map's content. Later, we will generate the 3D objects based on the metadata and use it to render the map. For now, let's just use the Grass component. We call the Grass component with the row index, so the grass component will position itself based on this row index.</p>
<p>Now, let's define the Grass component. The Grass component is the foundation and container of the forest rows and is also used for the starting row. It renders a group containing a flat, wide, green box. The dimensions of this box are determined by the constants <code>tileSize</code> and <code>tilesPerRow</code>. The box also has some height, so it sticks out compared to the road, which will be completely flat.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { tilesPerRow, tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Grass</span>(<span class="hljs-params">{ rowIndex, children }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">group</span> <span class="hljs-attr">position-y</span>=<span class="hljs-string">{rowIndex</span> * <span class="hljs-attr">tileSize</span>}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[tilesPerRow</span> * <span class="hljs-attr">tileSize</span>, <span class="hljs-attr">tileSize</span>, <span class="hljs-attr">3</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0xbaf455}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
      {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">group</span>&gt;</span></span>
  );
}
</code></pre>
<p>The grass can serve as a container for the trees in the row. That's why we wrap the green box into a group so that later, we can also add children to this group. We position the group along the y-axis based on the row index that we received from the Map component. For the initial lane, this is zero, but as we're going to have multiple lanes, we need to place them according to this position.</p>
<p>Now that we have the map container and the grass component, we can finally add the map to the scene.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Scene } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Scene"</span>;
<span class="hljs-keyword">import</span> { Player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Player"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-built_in">Map</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Map"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Game</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Scene</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Player</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Map</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Scene</span>&gt;</span></span>
  );
}
</code></pre>
<h3 id="heading-how-to-add-a-forest-row"><strong>How to Add a Forest Row</strong></h3>
<p>Now that we have an empty forest, let's add another row containing trees. We define the map's metadata and render the rows based on this metadata.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739806992172/139197f0-f2c1-4f11-aacf-f1fedeeaf9b0.png" alt="A forest row" width="2560" height="1442" loading="lazy"></p>
<p>Let's define the map's metadata. The metadata is an array of objects that contain information about each row. Each row will contain a type that will determine the kind of the row and the rest of the properties depending on the row type.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> rows = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"forest"</span>,
    <span class="hljs-attr">trees</span>: [
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-3</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
    ],
  },
];
</code></pre>
<p>The metadata for a forest includes the type of “forest” and a list of trees. Each tree has a tile index, which represents which tile it is standing on. In this case, we have 17 tiles per row, going from -8 to +8. The trees also have a height, which is actually the height of the crown.</p>
<p>To render the rows, let’s extend the <code>Map</code> component to render the rows based on the metadata. We import the metadata and map each row to a separate <code>Row</code> component.</p>
<p>Note that the <code>rowIndex</code> is off by one compared to the array index because the first item in the metadata array will become the second row (after the starting row).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { rows } <span class="hljs-keyword">from</span> <span class="hljs-string">"../metadata"</span>;
<span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;
<span class="hljs-keyword">import</span> { Row } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Row"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Map</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Grass</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{0}</span> /&gt;</span>

      {rows.map((rowData, index) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">Row</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{index</span> + <span class="hljs-attr">1</span>} <span class="hljs-attr">rowData</span>=<span class="hljs-string">{rowData}</span> /&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}
</code></pre>
<p>Now, let’s define the <code>Row</code> component. The <code>Row</code> component is essentially a switch case that renders the correct row based on the <code>type</code> property of the row. We only support the <code>forest</code> type for now, but we will extend this file later to support car and truck lanes.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Forest } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Forest"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Row</span>(<span class="hljs-params">{ rowIndex, rowData }</span>) </span>{
  <span class="hljs-keyword">switch</span> (rowData.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"forest"</span>: {
      <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Forest</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{rowIndex}</span> <span class="hljs-attr">rowData</span>=<span class="hljs-string">{rowData}</span> /&gt;</span></span>;
    }
  }
}
</code></pre>
<p>The <code>Forest</code> component contains the row’s foundation, a <code>Grass</code> component, and the trees in the row.</p>
<p>The <code>Grass</code> component can receive children. We map trees’ metadata to <code>Tree</code> components and pass them on as children to the <code>Grass</code> component. Each tree gets its <code>tileIndex</code>, which will be used for positioning the tree within the row, and its <code>height</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;
<span class="hljs-keyword">import</span> { Tree } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Tree"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Forest</span>(<span class="hljs-params">{ rowIndex, rowData }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grass</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{rowIndex}</span>&gt;</span>
      {rowData.trees.map((tree, index) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">Tree</span>
          <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>
          <span class="hljs-attr">tileIndex</span>=<span class="hljs-string">{tree.tileIndex}</span>
          <span class="hljs-attr">height</span>=<span class="hljs-string">{tree.height}</span>
        /&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">Grass</span>&gt;</span></span>
  );
}
</code></pre>
<p>Forest rows also have trees. For each item in the trees array, we render a tree. The Tree component will render a 3D object representing the tree. We pass on to this component the tile index that we will use to position the tree within the row and the height.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807310182/40cbb90b-0356-4db5-97bb-cdbe0c8e8cb8.png" alt="A tree" width="2560" height="1442" loading="lazy"></p>
<p>Since we’ve already added the map to the scene, the forest will appear on the screen. But first, we need to define how to render a tree. We are going to represent a tree with two boxes. We're going to have a box for the trunk and one for the crown.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Tree</span>(<span class="hljs-params">{ tileIndex, height }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">group</span> <span class="hljs-attr">position-x</span>=<span class="hljs-string">{tileIndex</span> * <span class="hljs-attr">tileSize</span>}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position-z</span>=<span class="hljs-string">{height</span> / <span class="hljs-attr">2</span> + <span class="hljs-attr">20</span>}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[30,</span> <span class="hljs-attr">30</span>, <span class="hljs-attr">height</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0x7aa21d}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position-z</span>=<span class="hljs-string">{10}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[15,</span> <span class="hljs-attr">15</span>, <span class="hljs-attr">20</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0x4d2926}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">group</span>&gt;</span></span>
  );
}
</code></pre>
<p>These are both simple boxes, just like we had before with the player and also in the Grass component. The trunk is placed on top of the ground. We lift it along the Z-axis by half of its height, and the crown is placed on top of the trunk. The crown's height is also based on the height property. These two meshes are wrapped together into a group, and then we position this group along the X-axis based on the tile index property.</p>
<h3 id="heading-car-lanes"><strong>Car Lanes</strong></h3>
<p>Now, let's add another row type: car lanes. The process of adding car lanes will follow a similar structure. We define the lanes' metadata, including the vehicles, and then map them into 3D objects.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807532566/1c9cfdc9-2c45-476c-9ada-09f7abf79328.png" alt="The car lane" width="2560" height="1442" loading="lazy"></p>
<p>In the metadata, let's replace the first row with a car lane. The car lane will contain a single red car moving to the left. We have a direction property, which is a boolean flag. If this is true, that means the cars are moving to the right in the lane, and if it's false, then the vehicles are moving to the left. We also have a speed property, which defines how many units each vehicle takes every second.</p>
<p>Finally, we have an array of vehicles. Each car will have an initial tile index, which represents only its initial position because the cars will move later. Each car will also have a color property, which is a hexadecimal color value.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> rows = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"car"</span>,
    <span class="hljs-attr">direction</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">speed</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">vehicles</span>: [{ <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xff0000</span> }],
  },
];
</code></pre>
<p>Now, to render this lane type, we have to extend our logic to support car lanes. Let’s extend the <code>Row</code> Component with support for car lanes. If the type of a row is <code>car</code> we map it to a <code>CarLane</code> component.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Forest } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Forest"</span>;
<span class="hljs-keyword">import</span> { CarLane } <span class="hljs-keyword">from</span> <span class="hljs-string">"./CarLane"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Row</span>(<span class="hljs-params">{ rowIndex, rowData }</span>) </span>{
  <span class="hljs-keyword">switch</span> (rowData.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"forest"</span>: {
      <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Forest</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{rowIndex}</span> <span class="hljs-attr">rowData</span>=<span class="hljs-string">{rowData}</span> /&gt;</span></span>;
    }
    <span class="hljs-keyword">case</span> <span class="hljs-string">"car"</span>: {
      <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">CarLane</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{rowIndex}</span> <span class="hljs-attr">rowData</span>=<span class="hljs-string">{rowData}</span> /&gt;</span></span>;
    }
  }
}
</code></pre>
<p>The <code>CarLane</code> component renders the cars on the road. It has a similar structure to the <code>Forest</code> component.</p>
<p>It receives a <code>rowData</code> object as a prop, which contains the cars to be rendered. It wraps the cars in a <code>Road</code> component and maps over the <code>rowData.vehicles</code> array to render each car.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Road } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Road"</span>;
<span class="hljs-keyword">import</span> { Car } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Car"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CarLane</span>(<span class="hljs-params">{ rowIndex, rowData }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Road</span> <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{rowIndex}</span>&gt;</span>
      {rowData.vehicles.map((vehicle, index) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">Car</span>
          <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>
          <span class="hljs-attr">rowIndex</span>=<span class="hljs-string">{rowIndex}</span>
          <span class="hljs-attr">initialTileIndex</span>=<span class="hljs-string">{vehicle.initialTileIndex}</span>
          <span class="hljs-attr">direction</span>=<span class="hljs-string">{rowData.direction}</span>
          <span class="hljs-attr">speed</span>=<span class="hljs-string">{rowData.speed}</span>
          <span class="hljs-attr">color</span>=<span class="hljs-string">{vehicle.color}</span>
        /&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">Road</span>&gt;</span></span>
  );
}
</code></pre>
<p>The Road and Car functions are new here, so let's examine them next. The Road function returns the foundation and container of the car and truck lanes. Similar to the Grass component, it also returns a group containing a gray plane.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807755293/e731a758-ac20-40f5-819b-82a5ae81e65f.png" alt="The Road component" width="2560" height="1442" loading="lazy"></p>
<p>The size of the plane is also determined by the constants <code>tileSize</code> and <code>tilesPerRow</code>. Unlike the Grass component, though, it doesn't have any height. It's completely flat. The road will also serve as a container for the cars and trucks in the row, so that's why we wrap the plane into a group – so that we can add children to it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { tilesPerRow, tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Road</span>(<span class="hljs-params">{ rowIndex, children }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">group</span> <span class="hljs-attr">position-y</span>=<span class="hljs-string">{rowIndex</span> * <span class="hljs-attr">tileSize</span>}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">planeGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[tilesPerRow</span> * <span class="hljs-attr">tileSize</span>, <span class="hljs-attr">tileSize</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0x454a59}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
      {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">group</span>&gt;</span></span>
  );
}
</code></pre>
<p>Now, let's look at the Car. The Car function returns a very simple 3D car model.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807975022/dc04c3c1-de94-49fd-ad85-ade999c8862a.png" alt="A car" width="2560" height="1442" loading="lazy"></p>
<p>It contains a box for the body and a smaller box for the top part. We also have two wheel meshes. Because we never see the cars from underneath, we don't need to separate the wheels into left and right. We can just use one long box for the front wheels and another one for the back wheels.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Car</span>(<span class="hljs-params">{
  rowIndex,
  initialTileIndex,
  direction,
  speed,
  color,
}</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">group</span>
      <span class="hljs-attr">position-x</span>=<span class="hljs-string">{initialTileIndex</span> * <span class="hljs-attr">tileSize</span>}
      <span class="hljs-attr">rotation-z</span>=<span class="hljs-string">{direction</span> ? <span class="hljs-attr">0</span> <span class="hljs-attr">:</span> <span class="hljs-attr">Math.PI</span>}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position</span>=<span class="hljs-string">{[0,</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">12</span>]}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[60,</span> <span class="hljs-attr">30</span>, <span class="hljs-attr">15</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{color}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position</span>=<span class="hljs-string">{[-6,</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">25.5</span>]}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[33,</span> <span class="hljs-attr">24</span>, <span class="hljs-attr">12</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0xffffff}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position</span>=<span class="hljs-string">{[-18,</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">6</span>]}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[12,</span> <span class="hljs-attr">33</span>, <span class="hljs-attr">12</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0x333333}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position</span>=<span class="hljs-string">{[18,</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">6</span>]}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[12,</span> <span class="hljs-attr">33</span>, <span class="hljs-attr">12</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0x333333}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">group</span>&gt;</span></span>
  );
}
</code></pre>
<p>We group all these elements, position them based on the <code>initialTileIndex</code> property, and turn them based on the <code>direction</code> property. If the car goes to the left, we rotate it by 180°. When we set rotation values in Three.js. We have to set them in radians, so that's why we set it to Math.Pi, which is equivalent to 180°.</p>
<p>You can also find a more extended version of how to draw this car with textures <a target="_blank" href="https://www.freecodecamp.org/news/three-js-tutorial/">in this article</a>.</p>
<p>Based on the metadata, we can now render a map with several rows. Here’s an example with a few more lanes. Of course, feel free to define your own map.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> rows = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"car"</span>,
    <span class="hljs-attr">direction</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">speed</span>: <span class="hljs-number">188</span>,
    <span class="hljs-attr">vehicles</span>: [
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">-4</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xbdb638</span> },
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">-1</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0x78b14b</span> },
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">4</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xa52523</span> },
    ],
  },
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"forest"</span>,
    <span class="hljs-attr">trees</span>: [
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-5</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
    ],
  },
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"car"</span>,
    <span class="hljs-attr">direction</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">speed</span>: <span class="hljs-number">125</span>,
    <span class="hljs-attr">vehicles</span>: [
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">-4</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0x78b14b</span> },
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xbdb638</span> },
    ],
  },
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"forest"</span>,
    <span class="hljs-attr">trees</span>: [
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-8</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-3</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
    ],
  },
];
</code></pre>
<p>This article does not cover truck lanes, but they follow a similar structure. The code for it can be found at <a target="_blank" href="http://javascriptgametutorials.com/">JavaScriptGameTutorials.com</a>.</p>
<h2 id="heading-how-to-animate-the-cars"><strong>How to Animate the Cars</strong></h2>
<p>Let's move on and animate the cars in their lanes according to their speed and direction.</p>
<p>This is where things start to diverge from how you would typically use React. The React way would be to update a state or a prop and let React re-render the whole component. This is fast when working with HTML elements, but it is not very effective when working with 3D objects. We want to <a target="_blank" href="https://r3f.docs.pmnd.rs/advanced/pitfalls#avoid-setstate-in-loops">avoid re-rendering</a> the whole scene and, instead, update the position of the underlying objects directly.</p>
<p>We only use React to set up the scene and the objects, and then we let Three.js do the heavy lifting. React Three Fiber is just a thin layer on top of Three.js, so we can access the underlying Three.js objects directly to update the position of the cars and trucks.</p>
<p>We are going to use a custom hook, <code>useVehicleAnimation</code><strong>,</strong> to animate the vehicles. This hook will need a reference to the 3D object it should manipulate. Before defining this hook, let’s get a reference to the Three.js group, which represents the car. We use React’s <code>useRef</code> hook to store the reference and bind it to the <code>group</code> element.</p>
<p>Then, we pass on this reference to the <code>useVehicleAnimation</code> hook, along with the direction and speed of the car.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;
<span class="hljs-keyword">import</span> useVehicleAnimation <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useVehicleAnimation"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Car</span>(<span class="hljs-params">{
  rowIndex,
  initialTileIndex,
  direction,
  speed,
  color,
}</span>) </span>{
  <span class="hljs-keyword">const</span> car = useRef(<span class="hljs-literal">null</span>);
  useVehicleAnimation(car, direction, speed);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">group</span>
      <span class="hljs-attr">position-x</span>=<span class="hljs-string">{initialTileIndex</span> * <span class="hljs-attr">tileSize</span>}
      <span class="hljs-attr">rotation-z</span>=<span class="hljs-string">{direction</span> ? <span class="hljs-attr">0</span> <span class="hljs-attr">:</span> <span class="hljs-attr">Math.PI</span>}
      <span class="hljs-attr">ref</span>=<span class="hljs-string">{car}</span>
    &gt;</span>
      . . . 
    <span class="hljs-tag">&lt;/<span class="hljs-name">group</span>&gt;</span></span>
  );
}
</code></pre>
<p>Let’s implement the <code>useVehicleAnimation</code> hook to animate the vehicles. It moves them based on their speed and direction until the end of the lane and then re-spawns them at the other end. This way, the vehicles move in an infinite loop.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739808359961/eca84295-09d7-463a-8d29-e5f8069bb673.png" alt="The cars move in an infinite loop" width="3840" height="2160" loading="lazy"></p>
<p>This hook uses the <code>useFrame</code> hook that React Three Fiber provides. This hook is similar to <code>setAnimationLoop</code> in Three.js. It runs a function on every animation frame.</p>
<p>Conveniently, this function receives the time <code>delta</code>—the time that passed since the previous animation frame. We multiply this value by the vehicle’s <code>speed</code> to get the distance the car took during this time.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useFrame } <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-three/fiber"</span>;
<span class="hljs-keyword">import</span> { tileSize, minTileIndex, maxTileIndex } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useVehicleAnimation</span>(<span class="hljs-params">ref, direction, speed</span>) </span>{
  useFrame(<span class="hljs-function">(<span class="hljs-params">state, delta</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (!ref.current) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">const</span> vehicle = ref.current;

    <span class="hljs-keyword">const</span> beginningOfRow = (minTileIndex - <span class="hljs-number">2</span>) * tileSize;
    <span class="hljs-keyword">const</span> endOfRow = (maxTileIndex + <span class="hljs-number">2</span>) * tileSize;

    <span class="hljs-keyword">if</span> (direction) {
      vehicle.position.x =
        vehicle.position.x &gt; endOfRow
          ? beginningOfRow
          : vehicle.position.x + speed * delta;
    } <span class="hljs-keyword">else</span> {
      vehicle.position.x =
        vehicle.position.x &lt; beginningOfRow
          ? endOfRow
          : vehicle.position.x - speed * delta;
    }
  });
}
</code></pre>
<p>We directly update the <code>position.x</code> property of the underlying Three.js group. If the vehicle reaches the end of the lane, we re-spawn it at the other end.</p>
<p>Note that the reference passed to the hook might be <code>null</code> because it is only set after the first render. If the reference is not set, we return early from the function. Then, the animation starts in the next frame.</p>
<h2 id="heading-how-to-move-the-player"><strong>How to Move the Player</strong></h2>
<p>Now, let's move on to animating the player. Moving the player on the map is more complex than moving the vehicles. The player can move in all directions, bump into trees, or get hit by cars, and it shouldn't be able to move outside the map.</p>
<p>In this chapter, we are focusing on two parts: collecting user inputs and executing the movement commands. Player movement is not instant – we need to collect the movement commands into a queue and execute them one by one. We are going to collect user inputs and put them into a queue.</p>
<h3 id="heading-collecting-user-inputs"><strong>Collecting User Inputs</strong></h3>
<p>To track the movement commands, we create a store for the player. We do not use a state management library, as we don’t need a reactive store. We simply define our state in a regular JavaScript file.</p>
<p>The store will keep track of the player’s position and movement queue. The player starts at the middle of the first row, and the move queue is initially empty.</p>
<p>We will also export two functions: <strong>queueMove</strong> adds the movement command to the end of the move queue, and the <strong>stepCompleted</strong> function removes the first movement command from the queue and updates the player's position accordingly.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> state = {
  <span class="hljs-attr">currentRow</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">currentTile</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">movesQueue</span>: [],
};

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">queueMove</span>(<span class="hljs-params">direction</span>) </span>{
  state.movesQueue.push(direction);
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stepCompleted</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> direction = state.movesQueue.shift();

  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"forward"</span>) state.currentRow += <span class="hljs-number">1</span>;
  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"backward"</span>) state.currentRow -= <span class="hljs-number">1</span>;
  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"left"</span>) state.currentTile -= <span class="hljs-number">1</span>;
  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"right"</span>) state.currentTile += <span class="hljs-number">1</span>;
}
</code></pre>
<p>Now we can add event listeners for keyboard events to listen to the arrow keys. The <code>useEventListeners</code> hook listens to the arrow keys and calls the <code>queueMove</code> function of the player store with the corresponding direction.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { queueMove } <span class="hljs-keyword">from</span> <span class="hljs-string">"../stores/player"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useEventListeners</span>(<span class="hljs-params"></span>) </span>{
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> handleKeyDown = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowUp"</span>) {
        queueMove(<span class="hljs-string">"forward"</span>);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowDown"</span>) {
        queueMove(<span class="hljs-string">"backward"</span>);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowLeft"</span>) {
        queueMove(<span class="hljs-string">"left"</span>);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowRight"</span>) {
        queueMove(<span class="hljs-string">"right"</span>);
      }
    };

    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"keydown"</span>, handleKeyDown);

    <span class="hljs-comment">// Cleanup function to remove the event listener</span>
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">window</span>.removeEventListener(<span class="hljs-string">"keydown"</span>, handleKeyDown);
    };
  }, []);
}
</code></pre>
<p>After defining the event listeners, we also have to import them into the Game component so that they work.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Scene } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Scene"</span>;
<span class="hljs-keyword">import</span> { Player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Player"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-built_in">Map</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Map"</span>;
<span class="hljs-keyword">import</span> useEventListeners <span class="hljs-keyword">from</span> <span class="hljs-string">"./hooks/useEventListeners"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Game</span>(<span class="hljs-params"></span>) </span>{
  useEventListeners();

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Scene</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Player</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Map</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Scene</span>&gt;</span></span>
  );
}
</code></pre>
<h3 id="heading-executing-movement-commands"><strong>Executing Movement Commands</strong></h3>
<p>So far, we have collected user inputs and put each command into the <strong>movesQueue</strong> array in the player component. Now, it's time to execute these commands one by one and animate the player.</p>
<p>Let's create a new hook called <strong>usePlayerAnimation</strong>. Its main goal is to take each move command from the <strong>moveQueue</strong> one by one, calculate the player's progress toward executing a step, and position the player accordingly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739808997988/8adb70d2-bf5d-4e65-8ed9-29e8f297b487.png" alt="The player movement" width="1590" height="892" loading="lazy"></p>
<p>This hook animates the player frame by frame. It uses the <code>useFrame</code> hook, just like the <code>useVehicleAnimation</code> hook. This time, however, we use a separate <code>moveClock</code> that measures each step individually. We pass on <code>false</code> to the clock constructor so it doesn’t start automatically. The clock starts at the beginning of a step. At each animation frame, first, we check if there are any more steps to take, and if there are and we’re not currently processing a step, we start the clock.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { useFrame } <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-three/fiber"</span>;
<span class="hljs-keyword">import</span> { state, stepCompleted } <span class="hljs-keyword">from</span> <span class="hljs-string">"../stores/player"</span>;
<span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">usePlayerAnimation</span>(<span class="hljs-params">ref</span>) </span>{
  <span class="hljs-keyword">const</span> moveClock = <span class="hljs-keyword">new</span> THREE.Clock(<span class="hljs-literal">false</span>);

  useFrame(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!ref.current) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">if</span> (!state.movesQueue.length) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">const</span> player = ref.current;

    <span class="hljs-keyword">if</span> (!moveClock.running) moveClock.start();

    <span class="hljs-keyword">const</span> stepTime = <span class="hljs-number">0.2</span>; <span class="hljs-comment">// Seconds it takes to take a step</span>
    <span class="hljs-keyword">const</span> progress = <span class="hljs-built_in">Math</span>.min(
      <span class="hljs-number">1</span>,
      moveClock.getElapsedTime() / stepTime
    );

    setPosition(player, progress);

    <span class="hljs-comment">// Once a step has ended</span>
    <span class="hljs-keyword">if</span> (progress &gt;= <span class="hljs-number">1</span>) {
      stepCompleted();
      moveClock.stop();
    }
  });
}

. . .
</code></pre>
<p>We use the move clock to calculate the progress between the two tiles. The progress indicator can be a number between zero and one. Zero means that the player is still at the beginning of the step, and one means that it’s arrived at its new position.</p>
<p>At each animation frame, we call the <strong>setPosition</strong> function to set the player’s position according to the progress. Once we finish a step, we call the <strong>stepCompleted</strong> function to update the player's position and stop the clock. If there are any more move commands in the <strong>movesQueue</strong>, the clock will restart in the following animation frame.</p>
<p>Now that we know how to calculate the progress for each step, let's look into how to set the player's position based on the progress. The player will jump from tile to tile. Let's break this down into two parts: the movement's horizontal and vertical components.</p>
<p>The player moves from the current tile to the next tile in the direction of the move command. We calculate the player's start and end position based on the current tile and the direction of the move command. Then, we use linear interpolation with a utility function that Three.js provides. This will interpolate between the start and end positions based on the progress.</p>
<pre><code class="lang-javascript">. . .

function setPosition(player, progress) {
  <span class="hljs-keyword">const</span> startX = state.currentTile * tileSize;
  <span class="hljs-keyword">const</span> startY = state.currentRow * tileSize;
  <span class="hljs-keyword">let</span> endX = startX;
  <span class="hljs-keyword">let</span> endY = startY;

  <span class="hljs-keyword">if</span> (state.movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"left"</span>) endX -= tileSize;
  <span class="hljs-keyword">if</span> (state.movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"right"</span>) endX += tileSize;
  <span class="hljs-keyword">if</span> (state.movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"forward"</span>) endY += tileSize;
  <span class="hljs-keyword">if</span> (state.movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"backward"</span>) endY -= tileSize;

  player.position.x = THREE.MathUtils.lerp(startX, endX, progress);
  player.position.y = THREE.MathUtils.lerp(startY, endY, progress);
  player.children[<span class="hljs-number">0</span>].position.z = <span class="hljs-built_in">Math</span>.sin(progress * <span class="hljs-built_in">Math</span>.PI) * <span class="hljs-number">8</span> + <span class="hljs-number">10</span>;
}
</code></pre>
<p>For the vertical component, we use a sine function to make it look like jumping. We are basically mapping the progress to the first part of a sine wave.</p>
<p>Below you can see what a sine wave looks like. It goes from 0 to 2 Pi. So if you multiply the progress value, which is going from 0 to 1 with Pi, then the progress will map into the first half of this sine wave. The sign function then will give us a value between zero and one.</p>
<p>To make the jump look higher, we can multiply this with a value. In this case, we multiply the result of the sine function by eight, so as a result, the player will have a jump where the maximum height of the jump will be eight units.</p>
<p>We also need to add the original Z position to the value – otherwise, the player will sink halfway into the ground after the first step.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739810397620/89119f6e-ced5-4cdb-8254-96fbf66479ed.png" alt="For the vertical movement we use a sine wave" width="2032" height="1146" loading="lazy"></p>
<p>It’s finally time to update the <code>Player</code> component to make it all come together. We create a new reference with <code>useRef</code> and assign it to the <code>group</code> element. Finally, we pass this reference to the <code>usePlayerAnimation</code> hook we just implemented.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> usePlayerAnimation <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/usePlayerAnimation"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Player</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> player = useRef(<span class="hljs-literal">null</span>);
  usePlayerAnimation(player);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">group</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{player}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mesh</span> <span class="hljs-attr">position</span>=<span class="hljs-string">{[0,</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">10</span>]} <span class="hljs-attr">castShadow</span> <span class="hljs-attr">receiveShadow</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">boxGeometry</span> <span class="hljs-attr">args</span>=<span class="hljs-string">{[15,</span> <span class="hljs-attr">15</span>, <span class="hljs-attr">20</span>]} /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meshLambertMaterial</span> <span class="hljs-attr">color</span>=<span class="hljs-string">{0xffffff}</span> <span class="hljs-attr">flatShading</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">mesh</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">group</span>&gt;</span></span>
  );
}
</code></pre>
<p>If you did everything right, the player should be able to move around the game board, moving forward, backward, left, and right. But we haven't added any hit detection. So far, the player can move through trees and vehicles and even get off the game board. Let's fix these issues in the following steps.</p>
<h3 id="heading-follow-the-player-with-the-camera">Follow the Player with the Camera</h3>
<p>We defined the camera in the <code>Scene</code> component. By default, it has a static position. Instead of that, we want to move it with the player. We could adjust its position at every animation frame just like the player, but it’s easier to attach the camera to the <code>Player</code> component so that they move together.</p>
<p>We can access the camera using the <code>useThree</code> hook from <code>@react-three/fiber</code>. This returns a Three.js camera object that we can add to the player group.</p>
<p>We already have a reference to the group representing the player. We can attach the camera to the player by adding it as a child of the player group. Because the player reference is undefined on the first render, we need to use the <code>useEffect</code> hook to attach the camera only once the player reference is set.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useThree } <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-three/fiber"</span>;
<span class="hljs-keyword">import</span> usePlayerAnimation <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/usePlayerAnimation"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Player</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> player = useRef(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> camera = useThree(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.camera);

  usePlayerAnimation(player);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!player.current) <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// Attach the camera to the player</span>
    player.current.add(camera);
  });

  <span class="hljs-keyword">return</span> (
    . . .
  );
}
</code></pre>
<h3 id="heading-restricting-player-movement"><strong>Restricting Player Movement</strong></h3>
<p>Let’s make sure that the player can’t end up in a position that’s invalid. We will check if a move is valid by calculating where it will take the player. If the player would end up in a position outside of the map or in a tile occupied by a tree, we will ignore that move command.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739810555283/310afd32-6e5a-4143-bbe2-e0da558bd6d7.png" alt="Calculating where the player will end up" width="1662" height="1146" loading="lazy"></p>
<p>First, we need to calculate where the player would end up if they made a particular move. Whenever we add a new move to the queue, we need to calculate where the player would end up if they made all the moves in the queue and take the current move command. We create a utility function that takes the player's current position and an array of moves and returns the player's final position.</p>
<p>For instance, if the player's current position is 0,0, staying in the middle of the first row, and the moves are forward and left, then the final position will be row 1 tile -1.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateFinalPosition</span>(<span class="hljs-params">currentPosition, moves</span>) </span>{
  <span class="hljs-keyword">return</span> moves.reduce(<span class="hljs-function">(<span class="hljs-params">position, direction</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"forward"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex + <span class="hljs-number">1</span>,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex,
      };
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"backward"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex - <span class="hljs-number">1</span>,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex,
      };
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"left"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex - <span class="hljs-number">1</span>,
      };
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"right"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex + <span class="hljs-number">1</span>,
      };
    <span class="hljs-keyword">return</span> position;
  }, currentPosition);
}
</code></pre>
<p>Now that we have this utility function to calculate where the player will end up after taking a move, let's create another utility function to calculate whether the player would end up in a valid or invalid position. In this function, we use the <strong>calculateFinalPosition</strong> function that we just created. Then, we’ll check if the player would end up outside the map or on a tile occupied by a tree.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739810684106/71c45122-b10c-4623-b575-184c1cf1fecb.png" alt="Check if the player bumps into a tree" width="1846" height="1146" loading="lazy"></p>
<p>If the move is invalid, we return false. First, we check if the final position is before the starting row or if the tile number is outside the range of the tiles. Then, we check the metadata of the row the player will end up in. Here, the index is off by one because the row metadata doesn't include the starting row. If we end up in a forest row, we check whether a tree occupies the tile we move to. If any of this is true, we return false.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { calculateFinalPosition } <span class="hljs-keyword">from</span> <span class="hljs-string">"./calculateFinalPosition"</span>;
<span class="hljs-keyword">import</span> { minTileIndex, maxTileIndex } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;
<span class="hljs-keyword">import</span> { rows } <span class="hljs-keyword">from</span> <span class="hljs-string">"../metadata"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">endsUpInValidPosition</span>(<span class="hljs-params">currentPosition, moves</span>) </span>{
  <span class="hljs-comment">// Calculate where the player would end up after the move</span>
  <span class="hljs-keyword">const</span> finalPosition = calculateFinalPosition(
    currentPosition,
    moves
  );

  <span class="hljs-comment">// Detect if we hit the edge of the board</span>
  <span class="hljs-keyword">if</span> (
    finalPosition.rowIndex === <span class="hljs-number">-1</span> ||
    finalPosition.tileIndex === minTileIndex - <span class="hljs-number">1</span> ||
    finalPosition.tileIndex === maxTileIndex + <span class="hljs-number">1</span>
  ) {
    <span class="hljs-comment">// Invalid move, ignore move command</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-comment">// Detect if we hit a tree</span>
  <span class="hljs-keyword">const</span> finalRow = rows[finalPosition.rowIndex - <span class="hljs-number">1</span>];
  <span class="hljs-keyword">if</span> (
    finalRow &amp;&amp;
    finalRow.type === <span class="hljs-string">"forest"</span> &amp;&amp;
    finalRow.trees.some(
      <span class="hljs-function">(<span class="hljs-params">tree</span>) =&gt;</span> tree.tileIndex === finalPosition.tileIndex
    )
  ) {
    <span class="hljs-comment">// Invalid move, ignore move command</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>Finally, let's extend the player's <strong>queueMove</strong> function with the <strong>endsUpInValidPosition</strong> function to check if a move is valid. If the <strong>endsUpInValidPosition</strong> function returns false, we cannot take this step. In this case, we return early from the function before the move is added to the <strong>movesQueue</strong> array. So we are ignoring the move.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { endsUpInValidPosition } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utilities/endsUpInValidPosition"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> state = {
  <span class="hljs-attr">currentRow</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">currentTile</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">movesQueue</span>: [],
};

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">queueMove</span>(<span class="hljs-params">direction</span>) </span>{
  <span class="hljs-keyword">const</span> isValidMove = endsUpInValidPosition(
    { <span class="hljs-attr">rowIndex</span>: state.currentRow, <span class="hljs-attr">tileIndex</span>: state.currentTile },
    [...state.movesQueue, direction]
  );

  <span class="hljs-keyword">if</span> (!isValidMove) <span class="hljs-keyword">return</span>; <span class="hljs-comment">// Ignore move</span>

  state.movesQueue.push(direction);
}

. . .
</code></pre>
<p>This way, as you can see, you can move around the map – but you can never move before the first row, you can't go too far to the left or too far to the right, and you also can’t go through a tree anymore.</p>
<h2 id="heading-hit-detection"><strong>Hit Detection</strong></h2>
<p>To finish the game, let's add hit detection. We check if the player gets hit by a vehicle, and if so, we show an alert popup.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739826639155/1497d588-bf60-4fdc-a185-a01a781beafb.png" alt="Calculating bounding boxes for hit detection" width="1846" height="1146" loading="lazy"></p>
<p>We add a new hook that checks from the vehicles’ perspective if they hit the player. So far, the player and the vehicles have handled their own movement independently. They have no notion of each other. To handle hit detection, either the player needs to know about the vehicles or the vehicles need to know about the player.</p>
<p>We’ll choose the former approach because this way, we only need to store one reference to the player in the store, and all the vehicles can check against this reference. Let’s extend the player store with a <code>ref</code> property to store the player object’s reference. We also expose a <code>setRef</code> method that sets this reference.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { endsUpInValidPosition } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utilities/endsUpInValidPosition"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> state = {
  <span class="hljs-attr">currentRow</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">currentTile</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">movesQueue</span>: [],
  <span class="hljs-attr">ref</span>: <span class="hljs-literal">null</span>,
};

. . .

export <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setRef</span>(<span class="hljs-params">ref</span>) </span>{
  state.ref = ref;
}
</code></pre>
<p>Then, we call the <code>setRef</code> method in the <code>Player</code> component to set the reference to the player object. We already have the <code>player</code> reference, so we can pass its value to the <code>setRef</code> method in the <code>useEffect</code> hook once it is set.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useThree } <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-three/fiber"</span>;
<span class="hljs-keyword">import</span> usePlayerAnimation <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/usePlayerAnimation"</span>;
<span class="hljs-keyword">import</span> { setRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"../stores/player"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Player</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> player = useRef(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> camera = useThree(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.camera);

  usePlayerAnimation(player);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!player.current) <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// Attach the camera to the player</span>
    player.current.add(camera);

    <span class="hljs-comment">// Set the player reference in the store</span>
    setRef(player.current);
  });

  <span class="hljs-keyword">return</span> (
    . . .
  );
}
</code></pre>
<p>Then, let’s define another hook to handle hit detection. We check if the player intersects with any of the vehicles. If they do, we end the game.</p>
<p>This hook is from the perspective of a vehicle. It receives the <code>vehicle</code> reference and the <code>rowIndex</code>. We check if the vehicle intersects with the player if the player is in the same row, the row before, or the row after the vehicle. We use the <code>useFrame</code> hook to run the hit detection logic on every frame.</p>
<p>Then we create bounding boxes for the player and the vehicle to check for an intersection. This might be a bit overkill, as the shape of our objects is known, but it is a nice generic way to handle hit detection.</p>
<p>If the bounding boxes intersect, we show an alert. Once the user clicks OK on the alert, we reload the page.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { useFrame } <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-three/fiber"</span>;
<span class="hljs-keyword">import</span> { state <span class="hljs-keyword">as</span> player } <span class="hljs-keyword">from</span> <span class="hljs-string">"../stores/player"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useHitDetection</span>(<span class="hljs-params">vehicle, rowIndex</span>) </span>{
  useFrame(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!vehicle.current) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">if</span> (!player.ref) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">if</span> (
      rowIndex === player.currentRow ||
      rowIndex === player.currentRow + <span class="hljs-number">1</span> ||
      rowIndex === player.currentRow - <span class="hljs-number">1</span>
    ) {
      <span class="hljs-keyword">const</span> vehicleBoundingBox = <span class="hljs-keyword">new</span> THREE.Box3();
      vehicleBoundingBox.setFromObject(vehicle.current);

      <span class="hljs-keyword">const</span> playerBoundingBox = <span class="hljs-keyword">new</span> THREE.Box3();
      playerBoundingBox.setFromObject(player.ref);

      <span class="hljs-keyword">if</span> (playerBoundingBox.intersectsBox(vehicleBoundingBox)) {
        <span class="hljs-built_in">window</span>.alert(<span class="hljs-string">"Game over!"</span>);
        <span class="hljs-built_in">window</span>.location.reload();
      }
    }
  });
}
</code></pre>
<p>Finally, we call this hook in the vehicle components. In the <code>Car</code> component, we pass the <code>car</code> reference and the <code>rowIndex</code> to the <code>useHitDetection</code> hook.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;
<span class="hljs-keyword">import</span> useVehicleAnimation <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useVehicleAnimation"</span>;
<span class="hljs-keyword">import</span> useHitDetection <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useHitDetection"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Car</span>(<span class="hljs-params">{
  rowIndex,
  initialTileIndex,
  direction,
  speed,
  color,
}</span>) </span>{
  <span class="hljs-keyword">const</span> car = useRef(<span class="hljs-literal">null</span>);
  useVehicleAnimation(car, direction, speed);
  useHitDetection(car, rowIndex);

  <span class="hljs-keyword">return</span> (
    . . .
  );
}
</code></pre>
<h2 id="heading-next-steps"><strong>Next Steps</strong></h2>
<p>Congratulations, you’ve reached the end of this tutorial, and we’ve covered all the main features of the game. We rendered a map, animated the vehicles, added event handling for the player, and added hit detection.</p>
<p>I hope you had great fun creating this game. This game, of course, is far from perfect, and there are various improvements you can make if you’d like to keep working on it.</p>
<p>You can find the extended tutorial with interactive demos on <a target="_blank" href="http://javascriptgametutorials.com/">JavaScriptGameTutorials.com</a>. There, we also cover how to add shadows and truck lanes and how to generate an infinite number of rows as the player moves forward. We also add UI elements for the controls and the score indicator, and we add a result screen with a button to reset the game.</p>
<p>Alternatively, you can find the extended tutorial on <a target="_blank" href="https://www.youtube.com/watch?v=ccYrSACDNsw&amp;ab_channel=HunorM%C3%A1rtonBorb%C3%A9ly">YouTube</a>.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/ccYrSACDNsw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Code a Crossy Road Game Clone with Three.js ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you’ll learn how to create a clone of the mobile game Crossy Road with Three.js. The goal of this game is to move a character through an endless path of static and moving obstacles. You have to go around trees and avoid getting hit ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-code-a-crossy-road-game-clone-with-threejs/</link>
                <guid isPermaLink="false">67b7bad6bcd9ef47aba6001d</guid>
                
                    <category>
                        <![CDATA[ ThreeJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ javascript game ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hunor Márton Borbély ]]>
                </dc:creator>
                <pubDate>Thu, 20 Feb 2025 23:29:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740094139765/282432bf-1a5b-40e4-8b74-7aa854fd20ac.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you’ll learn how to create a clone of the mobile game Crossy Road with Three.js. The goal of this game is to move a character through an endless path of static and moving obstacles. You have to go around trees and avoid getting hit by cars.</p>
<p>There's a lot to cover in this tutorial: we will start with setting up the scene, the camera, and the lights. Then you’ll learn how to draw the player and the map with the trees and the cars. We’ll also cover how to animate the vehicles, and we’ll add event handlers to move the player through the map. Finally, we’ll add hit detection between the cars and the player.</p>
<p>This article is a shortened version of the Crossy Road tutorial from my site <a target="_blank" href="https://javascriptgametutorials.com/">JavaScriptGameTutorials.com</a>. The extended tutorial is also available as a video on <a target="_blank" href="https://www.youtube.com/watch?v=vNr3_hQ3Bws&amp;ab_channel=HunorM%C3%A1rtonBorb%C3%A9ly">YouTube</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-game">How to Set Up the Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-render-a-map">How to Render a Map</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-animate-the-cars">How to Animate the Cars</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-move-the-player">How to Move the Player</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hit-detection">Hit Detection</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ol>
<h2 id="heading-how-to-set-up-the-game">How to Set Up the Game</h2>
<p>In this chapter, we’ll set up the drawing canvas, camera, and lights and render a box representing our player.</p>
<h3 id="heading-initializing-the-project">Initializing the Project</h3>
<p>I recommend using Vite to initialize the project. To do so, go to your terminal and type <code>npm create vite</code>, which will create an initial project for you.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create app</span>
npm create vite my-crossy-road-game

<span class="hljs-comment"># Navigate to the project</span>
<span class="hljs-built_in">cd</span> my-crossy-road-game

<span class="hljs-comment"># Install dependencies</span>
npm install three

<span class="hljs-comment"># Start development server</span>
npm run dev
</code></pre>
<p>When generating the project, select <strong>Vanilla</strong> because we won't use any front-end framework for this project. Then navigate to the project folder Vite just created for you and install Three.js with <code>npm install three</code>. Finally, you can go to the terminal and type <code>npm run dev</code> to start a development server. This way, you can see live the result of your coding in the browser.</p>
<h3 id="heading-the-drawing-canvas">The Drawing Canvas</h3>
<p>Now, let's look into this project. The entry point of this project is the <strong>index.html</strong> file in the root folder. Let's replace the div element with a canvas element with the ID <strong>game</strong>. This is the drawing canvas that Three.js will use to render the scene. This file also has a script tag that points to the main JavaScript file.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/vite.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Vite App<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/src/main.ts"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h3 id="heading-the-mainjs-file">The main.js file</h3>
<p>The <strong>main.js</strong> file is the root of our game. Let's replace its content. We’ll define a Three.js scene containing all the 3D elements, including the player, that we will soon define. The scene also includes a camera that we’ll use together with the renderer to render a static frame of it. We’ll define these in the following steps.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Renderer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Renderer"</span>;
<span class="hljs-keyword">import</span> { Camera } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Camera"</span>;
<span class="hljs-keyword">import</span> { player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;

<span class="hljs-keyword">const</span> scene = <span class="hljs-keyword">new</span> THREE.Scene();
scene.add(player);

<span class="hljs-keyword">const</span> camera = Camera();
player.add(camera);

<span class="hljs-keyword">const</span> renderer = Renderer();
renderer.render(scene, camera);
</code></pre>
<h3 id="heading-the-player">The Player</h3>
<p>Let's start adding the necessary objects to render the first scene. Let's add a simple box to represent the player. We already added the player to the scene in the main file, so let's see how to define this player.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739804077435/9667f94f-5005-41f8-a6ed-faa6706f0be1.png" alt="The player" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<p>In this file, we write a function that creates a 3D object and exports a property containing the player instance. The player is a singleton. There is only one player object in the game, and every other file can access it through this export.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> player = Player();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Player</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> player = <span class="hljs-keyword">new</span> THREE.Group();

  <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">15</span>, <span class="hljs-number">15</span>, <span class="hljs-number">20</span>),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-string">"white"</span> })
  );
  body.position.z = <span class="hljs-number">10</span>;
  player.add(body);

  <span class="hljs-keyword">return</span> player;
}
</code></pre>
<p>Initially, the player will be a simple box. To draw a 3D object, we’ll define a geometry and a material. The geometry defines the object's shape, and the material defines its appearance. Here, we’re using box geometry to define a box. The box geometry takes three arguments: the width, depth, and height of the box along the x, y, and z axes.</p>
<p>We have different options for the material. The main difference between them is how they react to light, if at all. Here, we're using <strong>MeshLambertMaterial</strong>, a simple material that responds to light. We set the color property to white.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739804142917/7b6a49e2-30df-40d8-bd4c-7ac1a2725931.png" alt="Different light options" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<p>Then, we wrap the geometry and the material into a mesh, which we can add to the scene. We can also position this mesh by setting its X, Y, and Z positions. In the case of a box, these set the center position. By setting the Z position of this box, we’re elevating it above the ground by half its height. As a result, the bottom of the box will be standing on the ground.</p>
<p>We also wrap the mesh into a group element. This is not necessary at this point, but having this structure will be handy when animating the player. When it comes to player animation, we want to separate the horizontal and vertical movement. We want this to be able to follow the player with the camera as it moves but not to move the camera up and down when the player is jumping. We will move the group horizontally along the XY plane together with the camera and move the mesh vertically.</p>
<h3 id="heading-the-camera">The Camera</h3>
<p>Now, let's look into different camera options. There are two main camera options: the perspective camera, as you can see on the left in the image below, and the orthographic camera, which you can see on the right.</p>
<p>The perspective camera is the default camera in Three.js and is the most common camera type across all video games. It creates a perspective projection, which makes things further away appear smaller and things right in front of the camera appear bigger.</p>
<p>On the other hand, the orthographic camera creates parallel projections, which means that objects are the same size regardless of their distance from the camera. We’ll use an orthographic camera here to give our game more of an arcade look.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739800656673/d2643544-b44c-418e-a475-2844e6626dbb.png" alt="Perspective vs orthographic camera" class="image--center mx-auto" width="2420" height="1224" loading="lazy"></p>
<p>In Three.js, we place the 3D objects along the X, Y, and Z axes. We define the coordinate system in a way where the ground is on the XY plane so the player can move left and right along the x-axis, forward and backward along the y-axis, and when the player is jumping, it will go up along the z-axis.</p>
<p>We place the camera in this coordinate system to the right along the x-axis, behind the player along the y-axis, and above the ground. Then, the camera will look back at the origin of the coordinate system to the 0,0,0 coordinate, where the player will be placed initially.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739803742848/d202d09f-1c1b-4246-be23-2a6f6af52423.png" alt="The coordinate system" class="image--center mx-auto" width="1576" height="892" loading="lazy"></p>
<p>With all this theory in mind, let's define our camera. We create a new file for the camera and export the camera function, which returns an orthographic camera that we'll use to render the scene.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Camera</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> size = <span class="hljs-number">300</span>;
  <span class="hljs-keyword">const</span> viewRatio = <span class="hljs-built_in">window</span>.innerWidth / <span class="hljs-built_in">window</span>.innerHeight;
  <span class="hljs-keyword">const</span> width = viewRatio &lt; <span class="hljs-number">1</span> ? size : size * viewRatio;
  <span class="hljs-keyword">const</span> height = viewRatio &lt; <span class="hljs-number">1</span> ? size / viewRatio : size;

  <span class="hljs-keyword">const</span> camera = <span class="hljs-keyword">new</span> THREE.OrthographicCamera(
    width / <span class="hljs-number">-2</span>, <span class="hljs-comment">// left</span>
    width / <span class="hljs-number">2</span>, <span class="hljs-comment">// right</span>
    height / <span class="hljs-number">2</span>, <span class="hljs-comment">// top</span>
    height / <span class="hljs-number">-2</span>, <span class="hljs-comment">// bottom</span>
    <span class="hljs-number">100</span>, <span class="hljs-comment">// near</span>
    <span class="hljs-number">900</span> <span class="hljs-comment">// far</span>
  );

  camera.up.set(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>);
  camera.position.set(<span class="hljs-number">300</span>, <span class="hljs-number">-300</span>, <span class="hljs-number">300</span>);
  camera.lookAt(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);

  <span class="hljs-keyword">return</span> camera;
}
</code></pre>
<p>To define a camera, we need to define a camera frustum. This will determine how to project the 3D elements onto the screen. In the case of an orthographic camera, we define a box. Everything in the scene within this box will be projected onto the screen. In the image below, the green dot represents the camera position and the gray box around the scene represents the camera frustum.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739804878454/3af6d9ba-1d35-4b98-8731-95af5889a1ed.png" alt="The camera frustum" class="image--center mx-auto" width="1720" height="1292" loading="lazy"></p>
<p>In this function, we set up the camera frustum to fill the browser window, and the width or height will be 300 units, depending on the aspect ratio. The smaller value between width and height will be 300 units, and the other one will fill the available space. If the width is larger than the height, then the height is 300 units. If the height is larger, then the width is 300 units.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739805040485/c7b9aa02-3c5d-4a2e-a1ff-e9941ce83c39.png" alt="Sizing the scene" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<p>Then, we set the camera's position. We move the camera to the right along the x-axis with 300 units, then behind the player along the y-axis with -300 units, and finally above the ground. We also look back to the origin of the coordinate system, to the 0,0,0 coordinate, where the player is positioned initially. Finally, we set which axis is pointing upwards. Here, we set the z-axis to point upwards.</p>
<h3 id="heading-the-lights">The Lights</h3>
<p>After setting up the camera, let's set up the lights. There are many types of lights in Three.js. Here, we're going to use an ambient light and a directional light.</p>
<p>You can see the result of ambient light only on the left side of the below image. The ambient light brightens the entire scene. It doesn't have a specific position or direction. You can think of it like the light on a cloudy day when it's bright, but there are no shadows. The ambient light is used to simulate indirect light.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739800781538/9e140ef9-4133-4151-9fc5-b629504259a8.png" alt="Ambient vs directional light" class="image--center mx-auto" width="2420" height="1224" loading="lazy"></p>
<p>Now, let's look at the directional light that you can see on the right of the image above. A directional light has a position and a target. It shines light in a specific direction with parallel light rays. Even though it has a position, you can rather think of it as the sun that is shining from very far away. The position here is more to define the direction of the light, but then all the other light rays are also parallel with this light ray. So you can think of it like the sun.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739805160031/4742e1de-5dc0-4443-9716-af18581bfa1e.png" alt="The directional light shines with parallel light rays" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<p>That's why we're combining an ambient light (so that we have a base brightness all around the scene) with a directional light (to illuminate specific sides of our objects with a brighter color).</p>
<p>After seeing what the lights look like, let's add an ambient and directional light to the scene in our main file. We also position the directional light to the left along the x-axis, behind the player along the y-axis, and above the ground. By default, the target of the directional light is going to be the 0,0,0 coordinate. We don't have to set that.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Renderer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Renderer"</span>;
<span class="hljs-keyword">import</span> { Camera } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Camera"</span>;
<span class="hljs-keyword">import</span> { player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;

<span class="hljs-keyword">const</span> scene = <span class="hljs-keyword">new</span> THREE.Scene();
scene.add(player);

<span class="hljs-keyword">const</span> ambientLight = <span class="hljs-keyword">new</span> THREE.AmbientLight();
scene.add(ambientLight);

<span class="hljs-keyword">const</span> dirLight = <span class="hljs-keyword">new</span> THREE.DirectionalLight();
dirLight.position.set(<span class="hljs-number">-100</span>, <span class="hljs-number">-100</span>, <span class="hljs-number">200</span>);
scene.add(dirLight);

<span class="hljs-keyword">const</span> camera = Camera();
player.add(camera);

<span class="hljs-keyword">const</span> renderer = Renderer();
renderer.render(scene, camera);
</code></pre>
<p>Note that we add the lights to the scene, but we add the camera to the player. This way, when we animate the player, the camera will follow the player.</p>
<h3 id="heading-the-renderer">The Renderer</h3>
<p>We have defined many things, but we still don’t see anything on the screen. As a final piece, we need to have a renderer to render the scene. A renderer renders the 3D scene into a canvas element.</p>
<p>In this function, we get the canvas element we defined in the HTML and set it as the drawing context. We also set a couple more parameters. We make the background of the 3D scene transparent with the alpha flag, set the pixel ratio, and set the size of the canvas to fill the entire screen.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Renderer</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"canvas.game"</span>);
  <span class="hljs-keyword">if</span> (!canvas) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Canvas not found"</span>);

  <span class="hljs-keyword">const</span> renderer = <span class="hljs-keyword">new</span> THREE.WebGLRenderer({
    <span class="hljs-attr">alpha</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">antialias</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">canvas</span>: canvas,
  });
  renderer.setPixelRatio(<span class="hljs-built_in">window</span>.devicePixelRatio);
  renderer.setSize(<span class="hljs-built_in">window</span>.innerWidth, <span class="hljs-built_in">window</span>.innerHeight);

  <span class="hljs-keyword">return</span> renderer;
}
</code></pre>
<p>This is how our first scene comes together. We rendered a simple box.</p>
<h2 id="heading-how-to-render-a-map">How to Render a Map</h2>
<p>Now, let's add all the other objects to the scene. In this chapter, we’ll define the map. The map will consist of multiple rows, each described by metadata. Each row can be a forest, a car, or a truck lane. We’ll go through each type and define the 3D objects representing them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739803813951/3796e0c0-6f02-4c82-979b-a5192d8c3b8c.png" alt="The different row types" class="image--center mx-auto" width="2560" height="1442" loading="lazy"></p>
<p>The map can be broken down into rows, and each row can be broken down into multiple tiles. The player will move from tile to tile. Trees are also placed on a distinct tile. Cars, on the other hand, do not relate to tiles. They move freely through the lane.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739803829719/38318821-8496-43d1-9b1c-a5b36d78af14.png" alt="A row can be broken down into a tile" class="image--center mx-auto" width="2560" height="1442" loading="lazy"></p>
<p>We define a file for the constants. Here, we define the number of tiles in each row. In this case, there are 17 ties per row, going from -8 to +8. The player will start in the middle at tile zero.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> minTileIndex = <span class="hljs-number">-8</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> maxTileIndex = <span class="hljs-number">8</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> tilesPerRow = maxTileIndex - minTileIndex + <span class="hljs-number">1</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> tileSize = <span class="hljs-number">42</span>;
</code></pre>
<h3 id="heading-the-starting-row">The Starting Row</h3>
<p>First, let's add the starting row. We’ll define a couple of components that we’re going to use to render the map, and we’ll render the initial row.</p>
<p>Let's create a new component called Map. This file will expose the map's metadata and the 3D objects representing it. Let's export a group called map. This container will contain all the 3D objects for each row. Soon, we will add this group to the scene.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> map = <span class="hljs-keyword">new</span> THREE.Group();

<span class="hljs-keyword">const</span> grass = Grass(<span class="hljs-number">0</span>);
map.add(grass);
</code></pre>
<p>Then, we set the map's content. Later, we will generate the 3D objects based on the metadata and use it to render the map. For now, let's just call the Grass function, which will return another Three.js group. We call the grass function with the row index, so the grass component will position itself based on this row index. Then, we add the returned group to the map.</p>
<p>Now, let's define the Grass component. The Grass function returns the foundation and container of the forest rows and is also used for the starting row. It returns a group containing a flat, wide, green box. The dimensions of this box are determined by the constants <strong>tileSize</strong> and <strong>tilesPerRow</strong>. The box also has some height, so it sticks out compared to the road, which will be completely flat.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { tilesPerRow, tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"./constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Grass</span>(<span class="hljs-params">rowIndex</span>) </span>{
  <span class="hljs-keyword">const</span> grass = <span class="hljs-keyword">new</span> THREE.Group();
  grass.position.y = rowIndex * tileSize;

  <span class="hljs-keyword">const</span> foundation = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(tilesPerRow * tileSize, tileSize, <span class="hljs-number">3</span>),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0xbaf455</span> })
  );
  foundation.position.z = <span class="hljs-number">1.5</span>;
  grass.add(foundation);

  <span class="hljs-keyword">return</span> grass;
}
</code></pre>
<p>The grass can serve as a container for the trees in the row. That's why we wrap the green box into a group so that later, we can also add children to this group. We position the group along the y-axis based on the row index that we received from the Map component. For the initial lane, this is zero, but as we're going to have multiple lanes, we need to place them according to this position.</p>
<p>Now that we have the map container and the grass component, we can finally add the map to the scene.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Renderer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Renderer"</span>;
<span class="hljs-keyword">import</span> { Camera } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Camera"</span>;
<span class="hljs-keyword">import</span> { player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;
<span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;

<span class="hljs-keyword">const</span> scene = <span class="hljs-keyword">new</span> THREE.Scene();
scene.add(player);
scene.add(map);

<span class="hljs-keyword">const</span> ambientLight = <span class="hljs-keyword">new</span> THREE.AmbientLight();
scene.add(ambientLight);

<span class="hljs-keyword">const</span> dirLight = <span class="hljs-keyword">new</span> THREE.DirectionalLight();
dirLight.position.set(<span class="hljs-number">-100</span>, <span class="hljs-number">-100</span>, <span class="hljs-number">200</span>);
scene.add(dirLight);

<span class="hljs-keyword">const</span> camera = Camera();
scene.add(camera);

<span class="hljs-keyword">const</span> renderer = Renderer();
renderer.render(scene, camera);
</code></pre>
<h3 id="heading-how-to-add-a-forest-row">How to Add a Forest Row</h3>
<p>Now that we have an empty forest, let's add another row containing trees. We define the map's metadata and render the rows based on this metadata.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739806992172/139197f0-f2c1-4f11-aacf-f1fedeeaf9b0.png" alt="A forest row" class="image--center mx-auto" width="2560" height="1442" loading="lazy"></p>
<p>Back in the Map component, let's define the map's metadata. The metadata is an array of objects that contain information about each row. Each row will contain a type that will determine the kind of the row and the rest of the properties depending on the row type.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"forest"</span>,
    <span class="hljs-attr">trees</span>: [
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-3</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
    ],
  },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> map = <span class="hljs-keyword">new</span> THREE.Group();

<span class="hljs-keyword">const</span> grass = Grass(<span class="hljs-number">0</span>);
map.add(grass);
</code></pre>
<p>The metadata for a forest includes the type of “forest” and a list of trees. Each tree has a tile index, which represents which tile it is standing on. In this case, we have 17 tiles per row, going from -8 to +8. The trees also have a height, which is actually the height of the crown.</p>
<p>To render the rows, we loop over this array and generate 3D objects for each row based on the row type. For the forest type, it calls the Grass function again, which will return a Three.js group. We call this Grass function with the row index so the Grass function can position itself along the y-axis. The row index is off by one compared to the array index because the first item in the metadata will become the second row right after the starting row, which is not part of the metadata.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;
<span class="hljs-keyword">import</span> { Tree } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Tree"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"forest"</span>,
    <span class="hljs-attr">trees</span>: [
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-3</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
    ],
  },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> map = <span class="hljs-keyword">new</span> THREE.Group();

<span class="hljs-keyword">const</span> grass = Grass(<span class="hljs-number">0</span>);
map.add(grass);

metadata.forEach(<span class="hljs-function">(<span class="hljs-params">rowData, index</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> rowIndex = index + <span class="hljs-number">1</span>;

  <span class="hljs-keyword">if</span> (rowData.type === <span class="hljs-string">"forest"</span>) {
    <span class="hljs-keyword">const</span> row = Grass(rowIndex);

    rowData.trees.forEach(<span class="hljs-function">(<span class="hljs-params">{ tileIndex, height }</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> three = Tree(tileIndex, height);
      row.add(three);
    });

    map.add(row);
  }
});
</code></pre>
<p>Forest rows also have trees. For each item in the trees array, we render a tree. The Tree function will return a 3D object representing the tree.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807310182/40cbb90b-0356-4db5-97bb-cdbe0c8e8cb8.png" alt="A tree" class="image--center mx-auto" width="2560" height="1442" loading="lazy"></p>
<p>We pass on to this function the tile index that we will use to position the tree within the row and the height. We add the trees to the group the Grass function returned, and then we add the whole group returned by the Grass function to the map.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Tree</span>(<span class="hljs-params">tileIndex, height</span>) </span>{
  <span class="hljs-keyword">const</span> tree = <span class="hljs-keyword">new</span> THREE.Group();
  tree.position.x = tileIndex * tileSize;

  <span class="hljs-keyword">const</span> trunk = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">15</span>, <span class="hljs-number">15</span>, <span class="hljs-number">20</span>),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0x4d2926</span> })
  );
  trunk.position.z = <span class="hljs-number">10</span>;
  tree.add(trunk);

  <span class="hljs-keyword">const</span> crown = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">30</span>, <span class="hljs-number">30</span>, height),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0x7aa21d</span> })
  );
  crown.position.z = height / <span class="hljs-number">2</span> + <span class="hljs-number">20</span>;
  tree.add(crown);

  <span class="hljs-keyword">return</span> tree;
}
</code></pre>
<p>Since we’ve already added the map to the scene, the forest will appear on the screen. But first, we need to define how to render a tree. We are going to represent a tree with two boxes. We're going to have a box for the trunk and one for the crown.</p>
<p>These are both simple boxes, just like we had before with the player and also in the Grass component. The trunk is placed on top of the ground. We lift it along the Z-axis by half of its height, and the crown is placed on top of the trunk. The crown's height is also based on the height property. These two meshes are wrapped together into a group, and then we position this group along the X-axis based on the tile index property.</p>
<h3 id="heading-car-lanes">Car Lanes</h3>
<p>Now, let's add another row type: car lanes. The process of adding car lanes will follow a similar structure. We define the lanes' metadata, including the vehicles, and then map them into 3D objects.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807532566/1c9cfdc9-2c45-476c-9ada-09f7abf79328.png" alt="The car lane" class="image--center mx-auto" width="2560" height="1442" loading="lazy"></p>
<p>In the Map component in the metadata, let's replace the first row with a car lane. The car lane will contain a single red car moving to the left. We have a direction property, which is a boolean flag. If this is true, that means the cars are moving to the right in the lane, and if it's false, then the vehicles are moving to the left. We also have a speed property, which defines how many units each vehicle takes every second.</p>
<p>Finally, we have an array of vehicles. Each car will have an initial tile index, which represents only its initial position because the cars will move later. Each car will also have a color property, which is a hexadecimal color value.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;
<span class="hljs-keyword">import</span> { Tree } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Tree"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"car"</span>,
    <span class="hljs-attr">direction</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">speed</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">vehicles</span>: [{ <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xff0000</span> }],
  },
];

. . .
</code></pre>
<p>Now, to render this lane type, we have to extend our logic to support car lanes. We add another if block that is very similar to the rendering of the forest. In the case of a car type, we call the Road function, which will also return a Three.js group. We also call this function with the row index to position the group according to the lane.</p>
<p>Then, for each item in the vehicles array, we create a 3D object representing the car with the Car function. We add the cars to the group returned by the Road function, and we add the whole group to the map. For the car function, we also pass on the initial tile index that we will use to position the car within the row, the direction, and the color.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Grass } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Grass"</span>;
<span class="hljs-keyword">import</span> { Road } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Road"</span>;
<span class="hljs-keyword">import</span> { Tree } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Tree"</span>;
<span class="hljs-keyword">import</span> { Car } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Car"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"car"</span>,
    <span class="hljs-attr">direction</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">speed</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">vehicles</span>: [{ <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xff0000</span> }],
  },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> map = <span class="hljs-keyword">new</span> THREE.Group();

<span class="hljs-keyword">const</span> grass = Grass(<span class="hljs-number">0</span>);
map.add(grass);

metadata.forEach(<span class="hljs-function">(<span class="hljs-params">rowData, index</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> rowIndex = index + <span class="hljs-number">1</span>;

  <span class="hljs-keyword">if</span> (rowData.type === <span class="hljs-string">"forest"</span>) {
    <span class="hljs-keyword">const</span> row = Grass(rowIndex);

    rowData.trees.forEach(<span class="hljs-function">(<span class="hljs-params">{ tileIndex, height }</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> three = Tree(tileIndex, height);
      row.add(three);
    });

    map.add(row);
  }

  <span class="hljs-keyword">if</span> (rowData.type === <span class="hljs-string">"car"</span>) {
    <span class="hljs-keyword">const</span> row = Road(rowIndex);

    rowData.vehicles.forEach(<span class="hljs-function">(<span class="hljs-params">vehicle</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> car = Car(
        vehicle.initialTileIndex,
        rowData.direction,
        vehicle.color
      );
      row.add(car);
    });

    map.add(row);
  }
});
</code></pre>
<p>The Road and Car functions are new here, so let's examine them next. The Road function returns the foundation and container of the car and truck lanes. Similar to the Grass function, it also returns a group containing a gray plane.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807755293/e731a758-ac20-40f5-819b-82a5ae81e65f.png" alt="The Road component" class="image--center mx-auto" width="2560" height="1442" loading="lazy"></p>
<p>The size of the plane is also determined by the constants <strong>tileSize</strong> and <strong>tilesPerRow</strong>. Unlike the grass function, though, it doesn't have any height. It's completely flat. The road will also serve as a container for the cars and trucks in the row, so that's why we wrap the plane into a group – so that we can add children to it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { tilesPerRow, tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Road</span>(<span class="hljs-params">rowIndex</span>) </span>{
  <span class="hljs-keyword">const</span> road = <span class="hljs-keyword">new</span> THREE.Group();
  road.position.y = rowIndex * tileSize;

  <span class="hljs-keyword">const</span> foundation = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.PlaneGeometry(tilesPerRow * tileSize, tileSize),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0x454a59</span> })
  );
  road.add(foundation);

  <span class="hljs-keyword">return</span> road;
}
</code></pre>
<p>Now, let's look at the Car. The Car function returns a very simple 3D car model.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739807975022/dc04c3c1-de94-49fd-ad85-ade999c8862a.png" alt="A car" class="image--center mx-auto" width="2560" height="1442" loading="lazy"></p>
<p>It contains a box for the body and a smaller box for the top part. We also have two wheel meshes. Because we never see the cars from underneath, we don't need to separate the wheels into left and right. We can just use one long box for the front wheels and another one for the back wheels.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"./constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Car</span>(<span class="hljs-params">initialTileIndex, direction, color</span>) </span>{
  <span class="hljs-keyword">const</span> car = <span class="hljs-keyword">new</span> THREE.Group();
  car.position.x = initialTileIndex * tileSize;
  <span class="hljs-keyword">if</span> (!direction) car.rotation.z = <span class="hljs-built_in">Math</span>.PI;

  <span class="hljs-keyword">const</span> main = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">60</span>, <span class="hljs-number">30</span>, <span class="hljs-number">15</span>),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ color })
  );
  main.position.z = <span class="hljs-number">12</span>;
  car.add(main);

  <span class="hljs-keyword">const</span> cabin = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">33</span>, <span class="hljs-number">24</span>, <span class="hljs-number">12</span>),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-string">"white"</span> })
  );
  cabin.position.x = <span class="hljs-number">-6</span>;
  cabin.position.z = <span class="hljs-number">25.5</span>;
  car.add(cabin);

  <span class="hljs-keyword">const</span> frontWheel = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">12</span>, <span class="hljs-number">33</span>, <span class="hljs-number">12</span>),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0x333333</span> })
  );
  frontWheel.position.x = <span class="hljs-number">18</span>;
  frontWheel.position.z = <span class="hljs-number">6</span>;
  car.add(frontWheel);

  <span class="hljs-keyword">const</span> backWheel = <span class="hljs-keyword">new</span> THREE.Mesh(
    <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">12</span>, <span class="hljs-number">33</span>, <span class="hljs-number">12</span>),
    <span class="hljs-keyword">new</span> THREE.MeshLambertMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0x333333</span> })
  );
  backWheel.position.x = <span class="hljs-number">-18</span>;
  backWheel.position.z = <span class="hljs-number">6</span>;
  car.add(backWheel);

  <span class="hljs-keyword">return</span> car;
}
</code></pre>
<p>We group all these elements, position them based on the <strong>initialTileIndex</strong> property, and turn them based on the <strong>direction</strong> property. If the car goes to the left, we rotate it by 180°. When we set rotation values in Three.js. We have to set them in radians, so that's why we set it to Math.Pi, which is equivalent to 180°.</p>
<p>You can also find a more extended version of how to draw this car with textures <a target="_blank" href="https://www.freecodecamp.org/news/three-js-tutorial/">in this article</a>.</p>
<p>Based on the metadata, we can now render a map with several rows. Here’s an example with a few more lanes. Of course, feel free to define your own map.</p>
<pre><code class="lang-javascript">. . .

export <span class="hljs-keyword">const</span> metadata = [
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"car"</span>,
    <span class="hljs-attr">direction</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">speed</span>: <span class="hljs-number">188</span>,
    <span class="hljs-attr">vehicles</span>: [
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">-4</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xbdb638</span> },
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">-1</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0x78b14b</span> },
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">4</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xa52523</span> },
    ],
  },
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"forest"</span>,
    <span class="hljs-attr">trees</span>: [
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-5</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
    ],
  },
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"car"</span>,
    <span class="hljs-attr">direction</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">speed</span>: <span class="hljs-number">125</span>,
    <span class="hljs-attr">vehicles</span>: [
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">-4</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0x78b14b</span> },
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xbdb638</span> },
      { <span class="hljs-attr">initialTileIndex</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">color</span>: <span class="hljs-number">0xbdb638</span> },
    ],
  },
  {
    <span class="hljs-attr">type</span>: <span class="hljs-string">"forest"</span>,
    <span class="hljs-attr">trees</span>: [
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-8</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">-3</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">50</span> },
      { <span class="hljs-attr">tileIndex</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">30</span> },
    ],
  },
];

. . .
</code></pre>
<p>This article does not cover truck lanes, but they follow a similar structure. The code for it can be found at <a target="_blank" href="http://JavaScriptGameTutorials.com">JavaScriptGameTutorials.com</a>.</p>
<h2 id="heading-how-to-animate-the-cars">How to Animate the Cars</h2>
<p>Let's move on and animate the cars in their lanes according to their speed and direction. To move the vehicles, we first need to be able to access them. So far, we have added them to the scene, and theoretically, we could traverse the scene and figure out which object represents a vehicle. But it's much easier to collect their references in our metadata and access them through these references.</p>
<p>Let's modify the Map generation. After generating a car, we not only add them to the container group but also save the reference together with their metadata. After this, we can go to the metadata and access each vehicle in the scene.</p>
<pre><code class="lang-javascript">. . .

metadata.forEach(<span class="hljs-function">(<span class="hljs-params">rowData, index</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> rowIndex = index + <span class="hljs-number">1</span>;

  <span class="hljs-keyword">if</span> (rowData.type === <span class="hljs-string">"forest"</span>) {
    <span class="hljs-keyword">const</span> row = Grass(rowIndex);

    rowData.trees.forEach(<span class="hljs-function">(<span class="hljs-params">{ tileIndex, height }</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> three = Tree(tileIndex, height);
      row.add(three);
    });

    map.add(row);
  }

  <span class="hljs-keyword">if</span> (rowData.type === <span class="hljs-string">"car"</span>) {
    <span class="hljs-keyword">const</span> row = Road(rowIndex);

    rowData.vehicles.forEach(<span class="hljs-function">(<span class="hljs-params">vehicle</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> car = Car(
        vehicle.initialTileIndex,
        rowData.direction,
        vehicle.color
      );
      vehicle.ref = car; <span class="hljs-comment">// Add a reference to the car object in metadata</span>
      row.add(car);
    });

    map.add(row);
  }
});
</code></pre>
<p>Next, let's go to the main file and define an animate function that will be called on every animation frame. For now, we only call the <strong>animateVehicles</strong> function, which we will define next. Later, we will extend this function with logic to animate the player and to have hit detection.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Renderer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Renderer"</span>;
<span class="hljs-keyword">import</span> { Camera } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Camera"</span>;
<span class="hljs-keyword">import</span> { player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;
<span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;
<span class="hljs-keyword">import</span> { animateVehicles } <span class="hljs-keyword">from</span> <span class="hljs-string">"./animateVehicles"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;

<span class="hljs-keyword">const</span> scene = <span class="hljs-keyword">new</span> THREE.Scene();
scene.add(player);
scene.add(map);

<span class="hljs-keyword">const</span> ambientLight = <span class="hljs-keyword">new</span> THREE.AmbientLight();
scene.add(ambientLight);

<span class="hljs-keyword">const</span> dirLight = <span class="hljs-keyword">new</span> THREE.DirectionalLight();
dirLight.position.set(<span class="hljs-number">-100</span>, <span class="hljs-number">-100</span>, <span class="hljs-number">200</span>);
scene.add(dirLight);

<span class="hljs-keyword">const</span> camera = Camera();
scene.add(camera);

<span class="hljs-keyword">const</span> renderer = Renderer();
renderer.setAnimationLoop(animate);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params"></span>) </span>{
  animateVehicles();

  renderer.render(scene, camera);
}
</code></pre>
<p>We also move the renderer render call here to render the scene on every animation loop. To call this function on every frame, we pass it on to the renderer’s <strong>setAnimationLoop</strong> function. This is similar to <strong>requestAnimationFrame</strong> in plain JavaScript, except that it calls itself at the end of the function, so we don't have to call it again.</p>
<p>Now, let's implement the <strong>animateVehicles</strong> function. As this function is part of the animate function, this function is called on every animation frame. Here, we use a Three.js clock to calculate how much time passed between the animation frames. Then, we loop over the metadata, take every vehicle from every car or truck lane, and move them along the x-axis based on their speed, direction, and the time passed.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { metadata <span class="hljs-keyword">as</span> rows } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;
<span class="hljs-keyword">import</span> { minTileIndex, maxTileIndex, tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"./constants"</span>;

<span class="hljs-keyword">const</span> clock = <span class="hljs-keyword">new</span> THREE.Clock();

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animateVehicles</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> delta = clock.getDelta();

  <span class="hljs-comment">// Animate cars and trucks</span>
  rows.forEach(<span class="hljs-function">(<span class="hljs-params">rowData</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (rowData.type === <span class="hljs-string">"car"</span> || rowData.type === <span class="hljs-string">"truck"</span>) {
      <span class="hljs-keyword">const</span> beginningOfRow = (minTileIndex - <span class="hljs-number">2</span>) * tileSize;
      <span class="hljs-keyword">const</span> endOfRow = (maxTileIndex + <span class="hljs-number">2</span>) * tileSize;

      rowData.vehicles.forEach(<span class="hljs-function">(<span class="hljs-params">{ ref }</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (!ref) <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Vehicle reference is missing"</span>);

        <span class="hljs-keyword">if</span> (rowData.direction) {
          ref.position.x =
            ref.position.x &gt; endOfRow
              ? beginningOfRow
              : ref.position.x + rowData.speed * delta;
        } <span class="hljs-keyword">else</span> {
          ref.position.x =
            ref.position.x &lt; beginningOfRow
              ? endOfRow
              : ref.position.x - rowData.speed * delta;
        }
      });
    }
  });
}
</code></pre>
<p>If a car reaches the end of the lane, we respawn it at the other end, depending on its direction. This creates an infinite loop in which cars go from left to right or right to left, depending on their direction. Once they reach the end of the lane, they start over from the beginning. With this function, we should have a scene where all the cars are moving in their lanes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739808359961/eca84295-09d7-463a-8d29-e5f8069bb673.png" alt="The cars move in an infinite loop" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<h2 id="heading-how-to-move-the-player">How to Move the Player</h2>
<p>Now, let's move on to animating the player. Moving the player on the map is more complex than moving the vehicles. The player can move in all directions, bump into trees, or get hit by cars, and it shouldn't be able to move outside the map.</p>
<p>In this chapter, we are focusing on two parts: collecting user inputs and executing the movement commands. Player movement is not instant – we need to collect the movement commands into a queue and execute them one by one. We are going to collect user inputs and put them into a queue.</p>
<h3 id="heading-collecting-user-inputs">Collecting User Inputs</h3>
<p>To check the movement commands, let's extend the player component with state. We keep track of the player's position and the movement queue. The player starts at the middle of the first row, and the move queue is initially empty.</p>
<p>We will also export two functions: <strong>queueMove</strong> adds the movement command to the end of the move queue, and the <strong>stepCompleted</strong> function removes the first movement command from the queue and updates the player's position accordingly.</p>
<pre><code class="lang-javascript">. . .

export <span class="hljs-keyword">const</span> position = {
  <span class="hljs-attr">currentRow</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">currentTile</span>: <span class="hljs-number">0</span>,
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> movesQueue = [];

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">queueMove</span>(<span class="hljs-params">direction</span>) </span>{
  movesQueue.push(direction);
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stepCompleted</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> direction = movesQueue.shift();

  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"forward"</span>) position.currentRow += <span class="hljs-number">1</span>;
  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"backward"</span>) position.currentRow -= <span class="hljs-number">1</span>;
  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"left"</span>) position.currentTile -= <span class="hljs-number">1</span>;
  <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"right"</span>) position.currentTile += <span class="hljs-number">1</span>;
}
</code></pre>
<p>Now, we can add event listeners for keyboard events to listen to the arrow keys. They all call the player's <strong>queueMove</strong> function, which we just defined for the player with the corresponding direction.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { queueMove } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;

<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"keydown"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowUp"</span>) {
    queueMove(<span class="hljs-string">"forward"</span>);
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowDown"</span>) {
    queueMove(<span class="hljs-string">"backward"</span>);
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowLeft"</span>) {
    queueMove(<span class="hljs-string">"left"</span>);
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === <span class="hljs-string">"ArrowRight"</span>) {
    queueMove(<span class="hljs-string">"right"</span>);
  }
});
</code></pre>
<p>After defining the event listeners, we also have to import them into the main file so that they work.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Renderer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Renderer"</span>;
<span class="hljs-keyword">import</span> { Camera } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Camera"</span>;
<span class="hljs-keyword">import</span> { player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;
<span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;
<span class="hljs-keyword">import</span> { animateVehicles } <span class="hljs-keyword">from</span> <span class="hljs-string">"./animateVehicles"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./collectUserInput"</span>; <span class="hljs-comment">// Import event listeners</span>

. . .
</code></pre>
<h3 id="heading-executing-movement-commands">Executing Movement Commands</h3>
<p>So far, we have collected user inputs and put each command into the <strong>movesQueue</strong> array in the player component. Now, it's time to execute these commands one by one and animate the player.</p>
<p>Let's create a new function called <strong>animatePlayer</strong>. Its main goal is to take each move command from the <strong>moveQueue</strong> one by one, calculate the player's progress toward executing a step, and position the player accordingly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739808997988/8adb70d2-bf5d-4e65-8ed9-29e8f297b487.png" alt="The player movement" class="image--center mx-auto" width="1590" height="892" loading="lazy"></p>
<p>This function animates the player frame by frame. It will also be part of the animate function. We also use a separate move clock that measures each step individually. We pass on false to the clock constructor so it doesn't start automatically. The clock only starts at the beginning of a step. At each animation frame, first, we check if there are any more steps to take, and if there are and we don't currently process a step, then we can start the clock. Once the clock is ticking we animate the player from tile to tile with each step.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { movesQueue, stepCompleted } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;

<span class="hljs-keyword">const</span> moveClock = <span class="hljs-keyword">new</span> THREE.Clock(<span class="hljs-literal">false</span>);

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animatePlayer</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (!movesQueue.length) <span class="hljs-keyword">return</span>;

  <span class="hljs-keyword">if</span> (!moveClock.running) moveClock.start();

  <span class="hljs-keyword">const</span> stepTime = <span class="hljs-number">0.2</span>; <span class="hljs-comment">// Seconds it takes to take a step</span>
  <span class="hljs-keyword">const</span> progress = <span class="hljs-built_in">Math</span>.min(<span class="hljs-number">1</span>, moveClock.getElapsedTime() / stepTime);

  setPosition(progress);

  <span class="hljs-comment">// Once a step has ended</span>
  <span class="hljs-keyword">if</span> (progress &gt;= <span class="hljs-number">1</span>) {
    stepCompleted();
    moveClock.stop();
  }
}

. . .
</code></pre>
<p>We use the move clock to calculate the progress between the two tiles. The progress indicator can be a number between zero and one. Zero means that the player is still at the beginning of the step, and one means that it’s arrived at its new position.</p>
<p>At each animation frame, we call the <strong>setPosition</strong> function to set the player’s position according to the progress. Once we finish a step, we call the <strong>stepCompleted</strong> function to update the player's position and stop the clock. If there are any more move commands in the <strong>movesQueue</strong>, the clock will restart in the following animation frame.</p>
<p>Now that we know how to calculate the progress for each step, let's look into how to set the player's position based on the progress. The player will jump from tile to tile. Let's break this down into two parts: the movement's horizontal and vertical components.</p>
<p>The player moves from the current tile to the next tile in the direction of the move command. We calculate the player's start and end position based on the current tile and the direction of the move command. Then, we use linear interpolation with a utility function that Three.js provides. This will interpolate between the start and end positions based on the progress.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> {
  player,
  position,
  movesQueue,
  stepCompleted,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Player"</span>;
<span class="hljs-keyword">import</span> { tileSize } <span class="hljs-keyword">from</span> <span class="hljs-string">"./constants"</span>;

. . .

function setPosition(progress) {
  <span class="hljs-keyword">const</span> startX = position.currentTile * tileSize;
  <span class="hljs-keyword">const</span> startY = position.currentRow * tileSize;
  <span class="hljs-keyword">let</span> endX = startX;
  <span class="hljs-keyword">let</span> endY = startY;

  <span class="hljs-keyword">if</span> (movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"left"</span>) endX -= tileSize;
  <span class="hljs-keyword">if</span> (movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"right"</span>) endX += tileSize;
  <span class="hljs-keyword">if</span> (movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"forward"</span>) endY += tileSize;
  <span class="hljs-keyword">if</span> (movesQueue[<span class="hljs-number">0</span>] === <span class="hljs-string">"backward"</span>) endY -= tileSize;

  player.position.x = THREE.MathUtils.lerp(startX, endX, progress);
  player.position.y = THREE.MathUtils.lerp(startY, endY, progress);
  player.children[<span class="hljs-number">0</span>].position.z = <span class="hljs-built_in">Math</span>.sin(progress * <span class="hljs-built_in">Math</span>.PI) * <span class="hljs-number">8</span> + <span class="hljs-number">10</span>;
}
</code></pre>
<p>For the vertical component, we use a sine function to make it look like jumping. We are basically mapping the progress to the first part of a sine wave.</p>
<p>Below you can see what a sine wave looks like. It goes from 0 to 2 Pi. So if you multiply the progress value, which is going from 0 to 1 with Pi, then the progress will map into the first half of this sine wave. The sign function then will give us a value between zero and one.</p>
<p>To make the jump look higher, we can multiply this with a value. In this case, we multiply the result of the sine function by eight, so as a result, the player will have a jump where the maximum height of the jump will be eight units.</p>
<p>We also need to add the original Z position to the value – otherwise, the player will sink halfway into the ground after the first step.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739810397620/89119f6e-ced5-4cdb-8254-96fbf66479ed.png" alt="For the vertical movement we use a sine wave" class="image--center mx-auto" width="2032" height="1146" loading="lazy"></p>
<p>Now that we’ve defined the <strong>animatePlayer</strong> function, let's add it to the animate loop.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Renderer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Renderer"</span>;
<span class="hljs-keyword">import</span> { Camera } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Camera"</span>;
<span class="hljs-keyword">import</span> { DirectionalLight } <span class="hljs-keyword">from</span> <span class="hljs-string">"./DirectionalLight"</span>;
<span class="hljs-keyword">import</span> { player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;
<span class="hljs-keyword">import</span> { map, initializeMap } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;
<span class="hljs-keyword">import</span> { animateVehicles } <span class="hljs-keyword">from</span> <span class="hljs-string">"./animateVehicles"</span>;
<span class="hljs-keyword">import</span> { animatePlayer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./animatePlayer"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./collectUserInput"</span>;

. . .

function animate() {
  animateVehicles();
  animatePlayer();

  renderer.render(scene, camera);
}
</code></pre>
<p>If you did everything right, the player should be able to move around the game board, moving forward, backward, left, and right. But we haven't added any hit detection. So far, the player can move through trees and vehicles and even get off the game board. Let's fix these issues in the following steps.</p>
<h3 id="heading-restricting-player-movement">Restricting Player Movement</h3>
<p>Let’s make sure that the player can’t end up in a position that’s invalid. We will check if a move is valid by calculating where it will take the player. If the player would end up in a position outside of the map or in a tile occupied by a tree, we will ignore that move command.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739810555283/310afd32-6e5a-4143-bbe2-e0da558bd6d7.png" alt="Calculating where the player will end up" class="image--center mx-auto" width="1662" height="1146" loading="lazy"></p>
<p>First, we need to calculate where the player would end up if they made a particular move. Whenever we add a new move to the queue, we need to calculate where the player would end up if they made all the moves in the queue and take the current move command. We create a utility function that takes the player's current position and an array of moves and returns the player's final position.</p>
<p>For instance, if the player's current position is 0,0, staying in the middle of the first row, and the moves are forward and left, then the final position will be row 1 tile -1.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateFinalPosition</span>(<span class="hljs-params">currentPosition, moves</span>) </span>{
  <span class="hljs-keyword">return</span> moves.reduce(<span class="hljs-function">(<span class="hljs-params">position, direction</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"forward"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex + <span class="hljs-number">1</span>,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex,
      };
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"backward"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex - <span class="hljs-number">1</span>,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex,
      };
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"left"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex - <span class="hljs-number">1</span>,
      };
    <span class="hljs-keyword">if</span> (direction === <span class="hljs-string">"right"</span>)
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">rowIndex</span>: position.rowIndex,
        <span class="hljs-attr">tileIndex</span>: position.tileIndex + <span class="hljs-number">1</span>,
      };
    <span class="hljs-keyword">return</span> position;
  }, currentPosition);
}
</code></pre>
<p>Now that we have this utility function to calculate where the player will end up after taking a move, let's create another utility function to calculate whether the player would end up in a valid or invalid position. In this function, we use the <strong>calculateFinalPosition</strong> function that we just created. Then, we’ll check if the player would end up outside the map or on a tile occupied by a tree.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739810684106/71c45122-b10c-4623-b575-184c1cf1fecb.png" alt="Check if the player bumps into a tree" class="image--center mx-auto" width="1846" height="1146" loading="lazy"></p>
<p>If the move is invalid, we return false. First, we check if the final position is before the starting row or if the tile number is outside the range of the tiles. Then, we check the metadata of the row the player will end up in. Here, the index is off by one because the row metadata doesn't include the starting row. If we end up in a forest row, we check whether a tree occupies the tile we move to. If any of this is true, we return false.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { calculateFinalPosition } <span class="hljs-keyword">from</span> <span class="hljs-string">"./calculateFinalPosition"</span>;
<span class="hljs-keyword">import</span> { minTileIndex, maxTileIndex } <span class="hljs-keyword">from</span> <span class="hljs-string">"./constants"</span>;
<span class="hljs-keyword">import</span> { metadata <span class="hljs-keyword">as</span> rows } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">endsUpInValidPosition</span>(<span class="hljs-params">currentPosition, moves</span>) </span>{
  <span class="hljs-comment">// Calculate where the player would end up after the move</span>
  <span class="hljs-keyword">const</span> finalPosition = calculateFinalPosition(
    currentPosition,
    moves
  );

  <span class="hljs-comment">// Detect if we hit the edge of the board</span>
  <span class="hljs-keyword">if</span> (
    finalPosition.rowIndex === <span class="hljs-number">-1</span> ||
    finalPosition.tileIndex === minTileIndex - <span class="hljs-number">1</span> ||
    finalPosition.tileIndex === maxTileIndex + <span class="hljs-number">1</span>
  ) {
    <span class="hljs-comment">// Invalid move, ignore move command</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-comment">// Detect if we hit a tree</span>
  <span class="hljs-keyword">const</span> finalRow = rows[finalPosition.rowIndex - <span class="hljs-number">1</span>];
  <span class="hljs-keyword">if</span> (
    finalRow &amp;&amp;
    finalRow.type === <span class="hljs-string">"forest"</span> &amp;&amp;
    finalRow.trees.some(
      <span class="hljs-function">(<span class="hljs-params">tree</span>) =&gt;</span> tree.tileIndex === finalPosition.tileIndex
    )
  ) {
    <span class="hljs-comment">// Invalid move, ignore move command</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>Finally, let's extend the player's <strong>queueMove</strong> function with the <strong>endsUpInValidPosition</strong> function to check if a move is valid. If the <strong>endsUpInValidPosition</strong> function returns false, we cannot take this step. In this case, we return early from the function before the move is added to the <strong>movesQueue</strong> array. So we are ignoring the move.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { endsUpInValidPosition } <span class="hljs-keyword">from</span> <span class="hljs-string">"./endsUpInValidPosition"</span>;

. . .

export <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">queueMove</span>(<span class="hljs-params">direction</span>) </span>{
  <span class="hljs-keyword">const</span> isValidMove = endsUpInValidPosition(
    {
      <span class="hljs-attr">rowIndex</span>: position.currentRow,
      <span class="hljs-attr">tileIndex</span>: position.currentTile,
    },
    [...movesQueue, direction]
  );

  <span class="hljs-keyword">if</span> (!isValidMove) <span class="hljs-keyword">return</span>; <span class="hljs-comment">// Return if the move is invalid</span>

  movesQueue.push(direction);
}

. . .
</code></pre>
<p>This way, as you can see, you can move around the map – but you can never move before the first row, you can't go too far to the left or too far to the right, and you also can’t go through a tree anymore.</p>
<h2 id="heading-hit-detection">Hit Detection</h2>
<p>To finish the game, let's add hit detection. We check if the player gets hit by a vehicle, and if so, we show an alert popup.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739826639155/1497d588-bf60-4fdc-a185-a01a781beafb.png" alt="Calculating bounding boxes for hit detection" class="image--center mx-auto" width="1846" height="1146" loading="lazy"></p>
<p>Let's define another function to define hit detection. We check if the player intersects with any of the vehicles. In this function, we check which row the player is currently in. The index is off by one because the row metadata does not include the starting row. If the player is in the starting row, we get undefined. We ignore that case. If the player is in a car or truck lane, we loop over the vehicles in the row and check if they intersect with the player. We create bounding boxes for the player and the vehicle to check for intersections.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { metadata <span class="hljs-keyword">as</span> rows } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;
<span class="hljs-keyword">import</span> { player, position } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hitTest</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> row = rows[position.currentRow - <span class="hljs-number">1</span>];
  <span class="hljs-keyword">if</span> (!row) <span class="hljs-keyword">return</span>;

  <span class="hljs-keyword">if</span> (row.type === <span class="hljs-string">"car"</span> || row.type === <span class="hljs-string">"truck"</span>) {
    <span class="hljs-keyword">const</span> playerBoundingBox = <span class="hljs-keyword">new</span> THREE.Box3();
    playerBoundingBox.setFromObject(player);

    row.vehicles.forEach(<span class="hljs-function">(<span class="hljs-params">{ ref }</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (!ref) <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Vehicle reference is missing"</span>);

      <span class="hljs-keyword">const</span> vehicleBoundingBox = <span class="hljs-keyword">new</span> THREE.Box3();
      vehicleBoundingBox.setFromObject(ref);

      <span class="hljs-keyword">if</span> (playerBoundingBox.intersectsBox(vehicleBoundingBox)) {
        <span class="hljs-built_in">window</span>.alert(<span class="hljs-string">"Game over!"</span>);
        <span class="hljs-built_in">window</span>.location.reload();
      }
    });
  }
}
</code></pre>
<p>If the bounding boxes intersect, we show an alert. Once the user clicks OK on the alert, we reload the page. We call this function in the <strong>animate</strong> function, which will run it on every frame.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> THREE <span class="hljs-keyword">from</span> <span class="hljs-string">"three"</span>;
<span class="hljs-keyword">import</span> { Renderer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Renderer"</span>;
<span class="hljs-keyword">import</span> { Camera } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Camera"</span>;
<span class="hljs-keyword">import</span> { player } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Player"</span>;
<span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Map"</span>;
<span class="hljs-keyword">import</span> { animateVehicles } <span class="hljs-keyword">from</span> <span class="hljs-string">"./animateVehicles"</span>;
<span class="hljs-keyword">import</span> { animatePlayer } <span class="hljs-keyword">from</span> <span class="hljs-string">"./animatePlayer"</span>;
<span class="hljs-keyword">import</span> { hitTest } <span class="hljs-keyword">from</span> <span class="hljs-string">"./hitTest"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./collectUserInput"</span>;

. . .

function animate() {
  animateVehicles();
  animatePlayer();
  hitTest(); <span class="hljs-comment">// Add hit detection</span>

  renderer.render(scene, camera);
}
</code></pre>
<h2 id="heading-next-steps">Next Steps</h2>
<p>Congratulations, you’ve reached the end of this tutorial, and we’ve covered all the main features of the game. We rendered a map, animated the vehicles, added event handling for the player, and added hit detection.</p>
<p>I hope you had great fun creating this game. This game, of course, is far from perfect, and there are various improvements you can make if you’d like to keep working on it.</p>
<p>You can find the extended tutorial with interactive demos on <a target="_blank" href="http://JavaScriptGameTutorials.com">JavaScriptGameTutorials.com</a>. There, we also cover how to add shadows and truck lanes and how to generate an infinite number of rows as the player moves forward. We also add UI elements for the controls and the score indicator, and we add a result screen with a button to reset the game.</p>
<p>Alternatively, you can find the extended tutorial on <a target="_blank" href="https://www.youtube.com/watch?v=vNr3_hQ3Bws&amp;ab_channel=HunorM%C3%A1rtonBorb%C3%A9ly">YouTube</a>.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/vNr3_hQ3Bws" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Game Development with JavaScript and Kaplay ]]>
                </title>
                <description>
                    <![CDATA[ Game development is an exciting way to bring your creative ideas to life. If you're new to coding or want to expand your skills, JavaScript is one of the best programming languages to learn. JavaScript is versatile and widely used for web-based games... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-game-development-with-javascript-and-kaplay/</link>
                <guid isPermaLink="false">67644277d968c624aba9ad8b</guid>
                
                    <category>
                        <![CDATA[ kaplay ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 19 Dec 2024 15:57:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734623838531/8083f2dd-65f7-49c6-81b0-0a1107d2ee9a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Game development is an exciting way to bring your creative ideas to life. If you're new to coding or want to expand your skills, JavaScript is one of the best programming languages to learn.</p>
<p>JavaScript is versatile and widely used for web-based games, making it a fantastic entry point for aspiring game developers. Also, libraries like Kaplay make the process even easier by simplifying game development tasks.</p>
<p>Whether you want to create simple 2D games or dive into more advanced game mechanics, learning JavaScript and Kaplay will give you a strong foundation.</p>
<p>We just published a JavaScript crash course on the freeCodeCamp.org YouTube channel that will teach you how to code in JavaScript and use the Kaplay library for streamlined game development. Created by JSLegendDev, this course covers essential programming concepts and practical game development techniques.</p>
<h2 id="heading-what-youll-learn-in-this-course">What You’ll Learn in This Course</h2>
<p>The course is divided into two main parts: learning JavaScript fundamentals and applying those skills to build games using the Kaplay library.</p>
<h3 id="heading-javascript-fundamentals-for-game-development">JavaScript Fundamentals for Game Development</h3>
<p>In the first part of the course, you'll get a solid grounding in JavaScript by covering core concepts necessary for building games.</p>
<p>You’ll start by understanding why JavaScript is a great choice for game development and learn how to set up your coding environment. The course covers key concepts such as variables, constants, conditional statements, boolean operations, complex data types (arrays and objects), loops, and functions. It also explores parameter passing, local and global scoping, comments, and how JavaScript's import/export system works.</p>
<p>You’ll also set up a modern development environment using Node.js, NPM, and Vite. With these tools in place, you'll be ready to build your first complete JavaScript game. The course then guides you through the process of exporting and deploying your game to a platform like <a target="_blank" href="http://itch.io"><strong>itch.io</strong></a>, allowing you to share your creations with others.</p>
<h3 id="heading-game-development-with-the-kaplay-library">Game Development with the Kaplay Library</h3>
<p>In the second part of the course, you’ll learn how to use the Kaplay library to simplify game development.</p>
<p>Kaplay provides a user-friendly framework for creating and managing game components. You’ll learn how to install Kaplay, initialize projects, and load assets. The course introduces you to key concepts like scenes, game objects, and their parent-child relationships.</p>
<p>You’ll work with essential Kaplay components, including sprites, areas, bodies, and text elements. The course also covers collision handling, user input management, and advanced features such as animations using the <code>tween()</code> function. Additionally, you’ll get a brief introduction to creating game maps, building AI with state machines, and using custom events to enhance your games.</p>
<h2 id="heading-why-this-course-is-perfect-for-you">Why This Course Is Perfect for You</h2>
<p>This crash course is beginner-friendly and doesn’t require prior coding experience. Each concept is explained clearly with practical examples and hands-on projects. It covers everything from JavaScript fundamentals to using the Kaplay library, giving you a comprehensive understanding of game development. By building real projects, you’ll gain practical experience and learn how to deploy your games online.</p>
<p>By the end of this course, you’ll have the skills to create your own JavaScript games and the confidence to explore more advanced game development techniques. With the Kaplay library, you’ll also have a powerful tool to speed up your development process and make your games more interactive.</p>
<p>If you're ready to get started with game development, check out the <a target="_blank" href="https://youtu.be/KHxX0CgMGs4">course on the freeCodeCamp.org YouTube channel</a> (4-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/KHxX0CgMGs4" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Memory Card Game Using React ]]>
                </title>
                <description>
                    <![CDATA[ Recently, while watching my kid 🧒🏻 playing free memory games on her tablet, I noticed her struggling with an overwhelming number of ads and annoying pop-up banners. This inspired me to build a similar game for her. Since she's currently into anime,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-memory-card-game-using-react/</link>
                <guid isPermaLink="false">674755a7cc41aab7422d5afb</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kids ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Anime ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mihail Gaberov ]]>
                </dc:creator>
                <pubDate>Wed, 27 Nov 2024 17:23:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732517515517/1ddfb635-6188-492a-9216-4b35ffb92096.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Recently, while watching my kid 🧒🏻 playing free memory games on her tablet, I noticed her struggling with an overwhelming number of ads and annoying pop-up banners.</p>
<p>This inspired me to build a similar game for her. Since she's currently into anime, I decided to create the game using cute anime-style images.</p>
<p>In this article, I'll walk you through the process of building the game for yourself or your kids 🎮.</p>
<p>We'll begin by exploring the game features, then cover the tech stack and project structure—both of which are straightforward. Finally, we'll discuss optimizations and ensuring smooth gameplay on mobile devices 📱.</p>
<p>If you want to skip the reading, <a target="_blank" href="https://github.com/mihailgaberov/memory-card">here</a> 💁 is the GitHub repository 🙌. And <a target="_blank" href="https://memory-card-blush-pi.vercel.app/">here</a> you can see the live demo.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-project-description">Project Description</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-build-the-game">Let’s Build the Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-the-app">Testing the App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-api-fallback-mechanism">API Fallback Mechanism</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-optimizations">Optimizations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-future-improvements">Future Improvements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-project-description">Project Description</h2>
<p>In this tutorial, we’ll build a challenging memory card game with React that tests your recall abilities. Your goal is to click unique anime images without clicking the same one twice. Each unique click earns you points, but be careful—clicking an image twice resets your progress.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732517597049/8677428e-ebd6-4f0b-a1f6-2a2d9f5f2dd2.png" alt="Memory Card game screenshot" class="image--center mx-auto" width="2900" height="1496" loading="lazy"></p>
<h3 id="heading-game-features">Game features:</h3>
<ul>
<li><p>🎯 Dynamic gameplay that challenges your memory</p>
</li>
<li><p>🔄 Cards shuffle after each click to increase difficulty</p>
</li>
<li><p>🏆 Score tracking with best score persistence</p>
</li>
<li><p>😺 Adorable anime images from The Nekosia API</p>
</li>
<li><p>✨ Smooth loading transitions and animations</p>
</li>
<li><p>📱 Responsive design for all devices</p>
</li>
<li><p>🎨 Clean, modern UI</p>
</li>
</ul>
<p>The game will help you test your memory skills while enjoying cute anime pictures. Can you achieve the perfect score?</p>
<h3 id="heading-how-to-play">How to Play</h3>
<ol>
<li><p>Click on any card to start</p>
</li>
<li><p>Remember which cards you've clicked</p>
</li>
<li><p>Try to click all cards exactly once</p>
</li>
<li><p>Watch your score grow with each unique selection</p>
</li>
<li><p>Then keep playing to try to beat your best score</p>
</li>
</ol>
<h3 id="heading-the-tech-stack">The Tech Stack</h3>
<p>Here’s a list of the main technologies we’ll be using:</p>
<ul>
<li><p><strong>NPM</strong> – A package manager for JavaScript that helps manage dependencies and scripts for the project.</p>
</li>
<li><p><strong>Vite</strong> – A build tool that provides a fast development environment, particularly optimized for modern web projects.</p>
</li>
<li><p><strong>React</strong> – A popular JavaScript library for building user interfaces, enabling efficient rendering and state management.</p>
</li>
<li><p><strong>CSS Modules</strong> – A styling solution that scopes CSS to individual components, preventing style conflicts and ensuring maintainability.</p>
</li>
</ul>
<h2 id="heading-lets-build-the-game">Let’s Build the Game</h2>
<p>From this point onward, I will guide you through the process I followed when building this game.</p>
<h3 id="heading-project-structure-and-architecture">Project Structure and Architecture</h3>
<p>When building this memory card game, I carefully organized the codebase to ensure maintainability, scalability, and clear separation of concerns. Let's explore the structure and the reasoning behind each decision:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732517663524/648124f0-aa8c-4c50-9292-4bdbd1c9c4db.png" alt="Project Structure" class="image--center mx-auto" width="398" height="1070" loading="lazy"></p>
<h4 id="heading-component-based-architecture">Component-Based Architecture</h4>
<p>I chose a component-based architecture for several reasons:</p>
<ul>
<li><p><strong>Modularity</strong>: Each component is self-contained with its own logic and styles</p>
</li>
<li><p><strong>Reusability</strong>: Components like <code>Card</code> and <code>Loader</code> can be reused across the application</p>
</li>
<li><p><strong>Maintainability</strong>: Easier to debug and modify individual components</p>
</li>
<li><p><strong>Testing</strong>: Components can be tested in isolation</p>
</li>
</ul>
<h4 id="heading-component-organization">Component Organization</h4>
<ol>
<li>Card Component</li>
</ol>
<ul>
<li><p>Separated into its own directory because it's a core game element</p>
</li>
<li><p>Contains both JSX and SCSS modules for encapsulation</p>
</li>
<li><p>Handles individual card rendering, loading states, and click events</p>
</li>
</ul>
<ol start="2">
<li>CardsGrid Component</li>
</ol>
<ul>
<li><p>Manages the game board layout</p>
</li>
<li><p>Handles card shuffling and distribution</p>
</li>
<li><p>Controls the responsive grid layout for different screen sizes</p>
</li>
</ul>
<ol start="3">
<li>Loader Component</li>
</ol>
<ul>
<li><p>Reusable loading indicator</p>
</li>
<li><p>Improves user experience during image loading</p>
</li>
<li><p>Can be used by any component that needs loading states</p>
</li>
</ul>
<ol start="4">
<li>Header/Footer/Subtitle Components</li>
</ol>
<ul>
<li><p>Structural components for app layout</p>
</li>
<li><p>Header displays game title and scores</p>
</li>
<li><p>Footer shows copyright and version info</p>
</li>
<li><p>Subtitle provides game instructions</p>
</li>
</ul>
<h4 id="heading-css-modules-approach">CSS Modules Approach</h4>
<p>I used CSS Modules (<code>.module.scss</code> files) for several benefits:</p>
<ul>
<li><p><strong>Scoped Styling</strong>: Prevents style leaks between components</p>
</li>
<li><p><strong>Name Collisions</strong>: Automatically generates unique class names</p>
</li>
<li><p><strong>Maintainability</strong>: Styles are co-located with their components</p>
</li>
<li><p><strong>SCSS Features</strong>: Leverages SCSS features while keeping styles modular</p>
</li>
</ul>
<h4 id="heading-custom-hooks">Custom Hooks</h4>
<p>The <code>hooks</code> directory contains custom hooks like useFetch:</p>
<ul>
<li><p><strong>Separation of Concerns</strong>: Isolates data fetching logic</p>
</li>
<li><p><strong>Reusability</strong>: Can be used by any component needing image data</p>
</li>
<li><p><strong>State Management</strong>: Handles loading, error, and data states</p>
</li>
<li><p><strong>Performance</strong>: Implements optimizations like image size control</p>
</li>
</ul>
<h4 id="heading-root-level-files">Root Level Files</h4>
<h4 id="heading-appjsx">App.jsx:</h4>
<ul>
<li><p>Acts as the application's entry point</p>
</li>
<li><p>Manages global state and routing (if needed)</p>
</li>
<li><p>Coordinates component composition</p>
</li>
<li><p>Handles top-level layouts</p>
</li>
</ul>
<h4 id="heading-performance-considerations">Performance Considerations</h4>
<p>The structure supports performance optimizations:</p>
<ul>
<li><p><strong>Code Splitting</strong>: Components can be lazy-loaded if needed</p>
</li>
<li><p><strong>Memoization</strong>: Components can be memoized effectively</p>
</li>
<li><p><strong>Style Loading</strong>: CSS Modules enable efficient style loading</p>
</li>
<li><p><strong>Asset Management</strong>: Images and resources are properly organized</p>
</li>
</ul>
<h4 id="heading-scalability">Scalability</h4>
<p>This structure allows for easy scaling:</p>
<ul>
<li><p>New features can be added as new components</p>
</li>
<li><p>Additional hooks can be created for new functionality</p>
</li>
<li><p>Styles remain maintainable as the app grows</p>
</li>
<li><p>Testing can be implemented at any level</p>
</li>
</ul>
<h4 id="heading-development-experience">Development Experience</h4>
<p>The structure enhances developer experience:</p>
<ul>
<li><p>Clear file organization</p>
</li>
<li><p>Intuitive component locations</p>
</li>
<li><p>Easy to find and modify specific features</p>
</li>
<li><p>Supports efficient collaboration</p>
</li>
</ul>
<p>This architecture proved particularly valuable when optimizing the game for tablet use, as it allowed me to:</p>
<ol>
<li><p>Easily identify and optimize performance bottlenecks</p>
</li>
<li><p>Add tablet-specific styles without affecting other devices</p>
</li>
<li><p>Implement loading states for better mobile experience</p>
</li>
<li><p>Maintain clean separation between game logic and UI components</p>
</li>
</ol>
<p>Alright, now let’s get coding.</p>
<h2 id="heading-step-by-step-build-guide">Step-by-Step Build Guide</h2>
<h3 id="heading-1-project-setup">1. Project Setup</h3>
<p><strong>Set Up the Development Environment</strong></p>
<p>In order to start with a clean React project, open your terminal app and run the following commands (you may name your project folder as you like – in my case the name is ‘memory-card’):</p>
<pre><code class="lang-bash">npm create vite@latest memory-card -- --template react
<span class="hljs-built_in">cd</span> memory-card
npm install
</code></pre>
<p><strong>Install the Required Dependencies</strong></p>
<p>The only dependencies we will use in this project are the hook package from UI.dev (by the way, <a target="_blank" href="https://ui.dev/why-react-renders">here</a> you can find a well-explained article on how rendering in React works).</p>
<p>The other dependency is the famous CSS preprocessor, <a target="_blank" href="https://sass-lang.com/">SASS</a>, that we’ll need to be able to write our CSS modules in SASS instead of regular CSS.</p>
<pre><code class="lang-javascript">npm install @uidotdev/usehooks sass
</code></pre>
<p><strong>Configure Vite and Project Setting</strong></p>
<p>When setting up our project, we need to make some specific configuration adjustments to handle SASS warnings and improve our development experience. Here's how you can configure Vitest:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// vitest.config.js</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vitest/config'</span>;
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">plugins</span>: [react()],
  <span class="hljs-attr">test</span>: {
    <span class="hljs-attr">environment</span>: <span class="hljs-string">'jsdom'</span>,
    <span class="hljs-attr">globals</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">setupFiles</span>: [<span class="hljs-string">'./src/setupTests.js'</span>],
    <span class="hljs-attr">css</span>: {
      <span class="hljs-attr">modules</span>: {
        <span class="hljs-attr">classNameStrategy</span>: <span class="hljs-string">'non-scoped'</span>
      }
    },
    <span class="hljs-attr">preprocessors</span>: {
      <span class="hljs-string">'**/*.scss'</span>: <span class="hljs-string">'sass'</span>
    },
    <span class="hljs-attr">coverage</span>: {
      <span class="hljs-attr">provider</span>: <span class="hljs-string">'v8'</span>,
      <span class="hljs-attr">reporter</span>: [<span class="hljs-string">'text'</span>, <span class="hljs-string">'json'</span>, <span class="hljs-string">'html'</span>],
      <span class="hljs-attr">exclude</span>: [
        <span class="hljs-string">'node_modules/'</span>,
        <span class="hljs-string">'src/setupTests.js'</span>,
        <span class="hljs-string">'src/main.jsx'</span>,
        <span class="hljs-string">'src/vite-env.d.ts'</span>,
      ],
    },
  },
  <span class="hljs-attr">css</span>: {
    <span class="hljs-attr">preprocessorOptions</span>: {
      <span class="hljs-attr">scss</span>: {
        <span class="hljs-attr">quietDeps</span>: <span class="hljs-literal">true</span>,  <span class="hljs-comment">// Silences SASS dependency warnings</span>
        <span class="hljs-attr">charset</span>: <span class="hljs-literal">false</span>    <span class="hljs-comment">// Prevents charset warning in recent SASS versions</span>
      }
    }
  }
});
</code></pre>
<p>Keep in mind that most of these configurations are auto-generated for you when you create the project with Vite. Here’s what’s going on:</p>
<ol>
<li><p><strong>SASS Configuration</strong>:</p>
<ul>
<li><p><code>quietDeps: true</code>: This silences the warnings about deprecated dependencies in SASS modules. Particularly useful when working with third-party SASS/SCSS files.</p>
</li>
<li><p><code>charset: false</code>: Prevents the "@charset" warning that appears in newer versions of SASS when using special characters in your stylesheets.</p>
</li>
</ul>
</li>
<li><p><strong>Test Configuration</strong>:</p>
<ul>
<li><p><code>globals: true</code>: Makes test functions globally available in test files</p>
</li>
<li><p><code>environment: 'jsdom'</code>: Provides a DOM environment for testing</p>
</li>
<li><p><code>setupFiles</code>: Points to our test setup file</p>
</li>
</ul>
</li>
</ol>
<p>These configurations help create a cleaner development experience by removing unnecessary warning messages in the console, setting up proper test environment configurations, and ensuring SASS/SCSS processing works smoothly.</p>
<p>You might see warnings in your console without these configurations when:</p>
<ul>
<li><p>Using SASS/SCSS features or importing SASS files</p>
</li>
<li><p>Running tests that require DOM manipulation</p>
</li>
<li><p>Using special characters in your stylesheets</p>
</li>
</ul>
<h3 id="heading-2-building-the-components"><strong>2. Building the Components</strong></h3>
<p><strong>Create the Card Component</strong></p>
<p>First, let's create our basic card component that will display individual images:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/components/Card/Card.jsx</span>
<span class="hljs-keyword">import</span> React, { useState, useCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Loader <span class="hljs-keyword">from</span> <span class="hljs-string">"../Loader"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"./Card.module.scss"</span>;

<span class="hljs-keyword">const</span> Card = React.memo(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Card</span>(<span class="hljs-params">{ imgUrl, imageId, categoryName, processTurn }</span>) </span>{
  <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">true</span>);

  <span class="hljs-keyword">const</span> handleImageLoad = useCallback(<span class="hljs-function">() =&gt;</span> {
    setIsLoading(<span class="hljs-literal">false</span>);
  }, []);

  <span class="hljs-keyword">const</span> handleClick = useCallback(<span class="hljs-function">() =&gt;</span> {
    processTurn(imageId);
  }, [processTurn, imageId]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleClick}</span>&gt;</span>
      {isLoading &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.loaderContainer}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Loader</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Loading..."</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      )}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
        <span class="hljs-attr">src</span>=<span class="hljs-string">{imgUrl}</span>
        <span class="hljs-attr">alt</span>=<span class="hljs-string">{categoryName}</span>
        <span class="hljs-attr">onLoad</span>=<span class="hljs-string">{handleImageLoad}</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`${<span class="hljs-attr">styles.image</span>} ${<span class="hljs-attr">isLoading</span> ? <span class="hljs-attr">styles.hidden</span> <span class="hljs-attr">:</span> ''}`}
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Card;
</code></pre>
<p>The Card component is a fundamental building block of our game. It's responsible for displaying individual images and handling player interactions. Let's break down its implementation:</p>
<h4 id="heading-props-breakdown"><strong>Props breakdown:</strong></h4>
<ol>
<li><p><code>image</code>: (string)</p>
<ul>
<li><p>The URL of the image to be displayed that’s received from our API service.</p>
</li>
<li><p>It’s used directly in the img tag's src attribute.</p>
</li>
</ul>
</li>
<li><p><code>id</code>: (string)</p>
<ul>
<li><p>Unique identifier for each card that’s critical for tracking which cards have been clicked.</p>
</li>
<li><p>It’s passed to the <code>processTurn</code> callback when a card is clicked.</p>
</li>
</ul>
</li>
<li><p><code>category</code>: (string)</p>
<ul>
<li><p>Describes the type of image (for example, "anime", "neko"), and it’s used in the alt attribute for better accessibility.</p>
</li>
<li><p>It helps with SEO and screen readers.</p>
</li>
</ul>
</li>
<li><p><code>processTurn</code>: (function)</p>
<ul>
<li><p>Callback function passed from the parent component that handles the game logic when a card is clicked.</p>
</li>
<li><p>It also manages score updates and game state changes and determines if a card has been clicked before.</p>
</li>
</ul>
</li>
<li><p><code>isLoading</code>: (boolean)</p>
<ul>
<li><p>Controls whether to show a loading state. When true, it displays a Loader component instead of the image.</p>
</li>
<li><p>It improves the user experience during image loading.</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-component-styling"><strong>Component styling:</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/components/Card/Card.module.scss</span>
.container {
  <span class="hljs-attr">display</span>: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: rgba(<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0.8</span>);
  border: <span class="hljs-number">1</span>px solid rgba(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.8</span>);
  padding: <span class="hljs-number">20</span>px;
  font-size: <span class="hljs-number">30</span>px;
  text-align: center;
  min-height: <span class="hljs-number">200</span>px;
  position: relative;
  cursor: pointer;
  transition: transform <span class="hljs-number">0.2</span>s ease;

  &amp;:hover {
    <span class="hljs-attr">transform</span>: scale(<span class="hljs-number">1.02</span>);
  }

  .image {
    <span class="hljs-attr">width</span>: <span class="hljs-number">10</span>rem;
    height: auto;
    opacity: <span class="hljs-number">1</span>;
    transition: opacity <span class="hljs-number">0.3</span>s ease;

    &amp;.hidden {
      <span class="hljs-attr">opacity</span>: <span class="hljs-number">0</span>;
    }
  }

  .loaderContainer {
    <span class="hljs-attr">position</span>: absolute;
    top: <span class="hljs-number">50</span>%;
    left: <span class="hljs-number">50</span>%;
    transform: translate(<span class="hljs-number">-50</span>%, <span class="hljs-number">-50</span>%);
  }
}
</code></pre>
<h4 id="heading-usage-in-the-component"><strong>Usage in the component:</strong></h4>
<pre><code class="lang-javascript">&lt;Card
    key={getKey()}
    imgUrl={item?.image?.original?.url || <span class="hljs-string">""</span>}
    imageId={item?.id}
    categoryName={item?.category}
    processTurn={<span class="hljs-function">(<span class="hljs-params">imageId</span>) =&gt;</span> processTurn(imageId)} 
/&gt;
</code></pre>
<h4 id="heading-key-features"><strong>Key features:</strong></h4>
<ol>
<li><p><strong>Performance Optimization</strong>:</p>
<ul>
<li><p>Uses <code>React.memo</code> to prevent unnecessary re-renders</p>
</li>
<li><p>Implements <code>useCallback</code> for event handlers</p>
</li>
<li><p>Manages loading state internally for better UX</p>
</li>
</ul>
</li>
<li><p><strong>Loading State Management</strong>:</p>
<ul>
<li><p>Internal <code>isLoading</code> state tracks image loading</p>
</li>
<li><p>Shows a Loader component with a message while loading</p>
</li>
<li><p>Hides the image until it's fully loaded using CSS classes</p>
</li>
</ul>
</li>
<li><p><strong>Event Handling</strong>:</p>
<ul>
<li><p><code>handleImageLoad</code>: Manages the loading state transition</p>
</li>
<li><p><code>handleClick</code>: Processes player moves via the <code>processTurn</code> callback</p>
</li>
</ul>
</li>
</ol>
<p><strong>Build the CardsGrid Component</strong></p>
<p>This is our main game component that manages the game state, scoring logic, and card interactions. Let's break down its implementation:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// src/components/CardsGrid/CardsGrid.jsx</span>
<span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useLocalStorage } <span class="hljs-keyword">from</span> <span class="hljs-string">"@uidotdev/usehooks"</span>;
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">"../Card"</span>;
<span class="hljs-keyword">import</span> Loader <span class="hljs-keyword">from</span> <span class="hljs-string">"../Loader"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"./CardsGrid.module.scss"</span>;
<span class="hljs-keyword">import</span> useFetch <span class="hljs-keyword">from</span> <span class="hljs-string">"../../hooks/useFetch"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CardsGrid</span>(<span class="hljs-params">data</span>) </span>{
  <span class="hljs-comment">// State Management</span>
  <span class="hljs-keyword">const</span> [images, setImages] = useState(data?.data?.images || []);
  <span class="hljs-keyword">const</span> [clickedImages, setClickedImages] = useLocalStorage(<span class="hljs-string">"clickedImages"</span>, []);
  <span class="hljs-keyword">const</span> [score, setScore] = useLocalStorage(<span class="hljs-string">"score"</span>, <span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> [bestScore, setBestScore] = useLocalStorage(<span class="hljs-string">"bestScore"</span>, <span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(!data?.data?.images?.length);

  <span class="hljs-comment">// Custom hook for fetching images</span>
  <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: fetchedData, fetchData, error } = useFetch();

  <span class="hljs-comment">// Update images when new data is fetched</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (fetchedData?.images) {
      setImages(fetchedData.images);
      setIsLoading(<span class="hljs-literal">false</span>);
      <span class="hljs-comment">// Reset clicked images when new batch is loaded</span>
      setClickedImages([]);
    }
  }, [fetchedData]);

  <span class="hljs-comment">// Helper function to update best score</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateBestScore</span>(<span class="hljs-params">currentScore</span>) </span>{
    <span class="hljs-keyword">if</span> (currentScore &gt; bestScore) {
      setBestScore(currentScore);
    }
  }

  <span class="hljs-comment">// Core game logic</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processTurn</span>(<span class="hljs-params">imageId</span>) </span>{
    <span class="hljs-keyword">const</span> newClickedImages = [...clickedImages, imageId];
    setClickedImages(newClickedImages);

    <span class="hljs-comment">// If clicking the same image twice, reset everything</span>
    <span class="hljs-keyword">if</span> (clickedImages.includes(imageId)) {
      <span class="hljs-comment">// Update the best score if necessary</span>
      updateBestScore(score);

      setClickedImages([]);
      setScore(<span class="hljs-number">0</span>);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// Handle successful card selection</span>
      <span class="hljs-keyword">const</span> newScore = score + <span class="hljs-number">1</span>;
      setScore(newScore);

      <span class="hljs-comment">// Check for perfect score (all cards clicked once)</span>
       <span class="hljs-keyword">if</span> (newClickedImages.length === images.length) {
        updateBestScore(newScore);
        fetchData();
        setClickedImages([]);
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Shuffle the images</span>
        <span class="hljs-keyword">const</span> shuffled = [...images].sort(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Math</span>.random() - <span class="hljs-number">0.5</span>);
        setImages(shuffled);
      }
    }
  }

 <span class="hljs-keyword">if</span> (error) {
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Failed to fetch data<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
  }

  <span class="hljs-keyword">if</span> (isLoading) {
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Loader</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Loading new images..."</span> /&gt;</span></span>;
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      {images.map((item) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">Card</span>
          <span class="hljs-attr">key</span>=<span class="hljs-string">{getKey()}</span>
          <span class="hljs-attr">imgUrl</span>=<span class="hljs-string">{item?.image?.original?.url</span> || ""}
          <span class="hljs-attr">imageId</span>=<span class="hljs-string">{item?.id}</span>
          <span class="hljs-attr">categoryName</span>=<span class="hljs-string">{item?.category}</span>
          <span class="hljs-attr">processTurn</span>=<span class="hljs-string">{(imageId)</span> =&gt;</span> processTurn(imageId)}
        /&gt;
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> React.memo(CardsGrid);
</code></pre>
<h4 id="heading-component-styling-1"><strong>Component styling:</strong></h4>
<pre><code class="lang-scss"><span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">display</span>: grid;
  gap: <span class="hljs-number">1rem</span> <span class="hljs-number">1rem</span>;
  grid-template-<span class="hljs-attribute">columns</span>: auto; <span class="hljs-comment">/* Default: one column for mobile-first */</span>
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.7rem</span>;
  <span class="hljs-attribute">cursor</span>: pointer;
}

<span class="hljs-keyword">@media</span> (min-width: <span class="hljs-number">481px</span>) {
  <span class="hljs-selector-class">.container</span> {
    grid-template-<span class="hljs-attribute">columns</span>: auto auto; <span class="hljs-comment">/* Two columns for tablets and up */</span>
  }
}

<span class="hljs-keyword">@media</span> (min-width: <span class="hljs-number">769px</span>) {
  <span class="hljs-selector-class">.container</span> {
    grid-template-<span class="hljs-attribute">columns</span>: auto auto auto; <span class="hljs-comment">/* Three columns for desktops and larger */</span>
  }
}
</code></pre>
<h4 id="heading-key-features-breakdown"><strong>Key Features Breakdown:</strong></h4>
<ol>
<li><p><strong>State Management</strong>:</p>
<ul>
<li><p>Uses <code>useState</code> for component-level state</p>
</li>
<li><p>Implements <code>useLocalStorage</code> for persistent game data:</p>
<ul>
<li><p><code>clickedImages</code>: Tracks which cards have been clicked</p>
</li>
<li><p><code>score</code>: Current game score</p>
</li>
<li><p><code>bestScore</code>: Highest score achieved</p>
</li>
</ul>
</li>
<li><p>Manages loading state for image fetching</p>
</li>
<li><p>Shuffle the cards</p>
</li>
</ul>
</li>
<li><p><strong>Game Logic</strong>:</p>
<ul>
<li><p><code>processTurn</code>: Handles player moves</p>
<ul>
<li><p>Tracks duplicate clicks</p>
</li>
<li><p>Updates scores</p>
</li>
<li><p>Manages perfect score scenarios</p>
</li>
</ul>
</li>
<li><p><code>updateBestScore</code>: Updates high score when necessary</p>
</li>
<li><p>Automatically fetches new images when a round is completed</p>
</li>
</ul>
</li>
<li><p><strong>Data Fetching</strong>:</p>
<ul>
<li><p>Uses custom <code>useFetch</code> hook for image data</p>
</li>
<li><p>Handles loading and error states</p>
</li>
<li><p>Updates images when new data is fetched</p>
</li>
</ul>
</li>
<li><p><strong>Performance Optimization</strong>:</p>
<ul>
<li><p>Component wrapped in <code>React.memo</code></p>
</li>
<li><p>Efficient state updates</p>
</li>
<li><p>Responsive grid layout</p>
</li>
</ul>
</li>
<li><p><strong>Persistence</strong>:</p>
<ul>
<li><p>Game state persists across page reloads</p>
</li>
<li><p>Best score tracking</p>
</li>
<li><p>Current game progress saving</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-usage-example"><strong>Usage Example:</strong></h4>
<pre><code class="lang-javascript">...
...

function App() {
  <span class="hljs-keyword">const</span> { data, loading, error } = useFetch();

  <span class="hljs-keyword">if</span> (loading) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Loader</span> /&gt;</span></span>;
  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Error: {error}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Subtitle</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">CardsGrid</span> <span class="hljs-attr">data</span>=<span class="hljs-string">{data}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Footer</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>The CardsGrid component serves as the heart of our memory card game, managing:</p>
<ul>
<li><p>Game state and logic</p>
</li>
<li><p>Score tracking</p>
</li>
<li><p>Card interactions</p>
</li>
<li><p>Image loading and display</p>
</li>
<li><p>Responsive layout</p>
</li>
<li><p>Data persistence</p>
</li>
</ul>
<p>This implementation provides a smooth gaming experience while maintaining code readability and maintainability through clear separation of concerns and proper state management.</p>
<h3 id="heading-3-implementing-the-api-layer"><strong>3.</strong> Implementing the API Layer</h3>
<p>Our game uses a robust API layer with multiple fallback options to ensure reliable image delivery. Let's implement each service and the fallback mechanism.</p>
<p><strong>Set Up the Primary API Service:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/services/api/nekosiaApi.js</span>
<span class="hljs-keyword">const</span> NEKOSIA_API_URL = <span class="hljs-string">"https://api.nekosia.cat/api/v1/images/catgirl"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchNekosiaImages</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
    <span class="hljs-string">`<span class="hljs-subst">${NEKOSIA_API_URL}</span>?count=21&amp;additionalTags=white-hair,uniform&amp;blacklistedTags=short-hair,sad,maid&amp;width=300`</span>
  );

  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Nekosia API error: <span class="hljs-subst">${response.status}</span>`</span>);
  }

  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> response.json();

  <span class="hljs-keyword">if</span> (!result.images || !<span class="hljs-built_in">Array</span>.isArray(result.images)) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Invalid response format from Nekosia API'</span>);
  }

  <span class="hljs-keyword">const</span> validImages = result.images.filter(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> item?.image?.original?.url);

  <span class="hljs-keyword">if</span> (validImages.length === <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No valid images received from Nekosia API'</span>);
  }

  <span class="hljs-keyword">return</span> { ...result, <span class="hljs-attr">images</span>: validImages };
}
</code></pre>
<p><strong>Create the First Fallback API Service:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/services/api/nekosBestApi.js</span>
<span class="hljs-keyword">const</span> NEKOS_BEST_API_URL = <span class="hljs-string">"https://nekos.best/api/v2/neko?amount=21"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchNekosBestImages</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(NEKOS_BEST_API_URL, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
    <span class="hljs-attr">mode</span>: <span class="hljs-string">"no-cors"</span>
  });

  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Nekos Best API error: <span class="hljs-subst">${response.status}</span>`</span>);
  }

  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> response.json();

  <span class="hljs-comment">// Transform the response to match our expected format</span>
  <span class="hljs-keyword">const</span> transformedImages = result.results.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> ({
    <span class="hljs-attr">id</span>: item.url.split(<span class="hljs-string">'/'</span>).pop().split(<span class="hljs-string">'.'</span>)[<span class="hljs-number">0</span>], <span class="hljs-comment">// Extract UUID from URL</span>
    <span class="hljs-attr">image</span>: {
      <span class="hljs-attr">original</span>: {
        <span class="hljs-attr">url</span>: item.url
      }
    },
    <span class="hljs-attr">artist</span>: {
      <span class="hljs-attr">name</span>: item.artist_name,
      <span class="hljs-attr">href</span>: item.artist_href
    },
    <span class="hljs-attr">source</span>: item.source_url
  }));

  <span class="hljs-keyword">return</span> { <span class="hljs-attr">images</span>: transformedImages };
}
</code></pre>
<p><strong>Create the Second Fallback API Service:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/services/api/nekosApi.js</span>
<span class="hljs-keyword">const</span> NEKOS_API_URL = <span class="hljs-string">"https://api.nekosapi.com/v3/images/random?limit=21&amp;rating=safe"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchNekosImages</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(NEKOS_API_URL, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
  });

  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Nekos API error: <span class="hljs-subst">${response.status}</span>`</span>);
  }

  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> response.json();

  <span class="hljs-comment">// Transform the response to match our expected format</span>
  <span class="hljs-keyword">const</span> transformedImages = result.items.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> ({
    <span class="hljs-attr">id</span>: item.id,
    <span class="hljs-attr">image</span>: {
      <span class="hljs-attr">original</span>: {
        <span class="hljs-attr">url</span>: item.image_url
      }
    }
  }));

  <span class="hljs-keyword">return</span> { <span class="hljs-attr">images</span>: transformedImages };
}
</code></pre>
<p><strong>Build the API Fallback Mechanism:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/services/api/imageService.js</span>
<span class="hljs-keyword">import</span> { fetchNekosiaImages } <span class="hljs-keyword">from</span> <span class="hljs-string">"./nekosiaApi"</span>;
<span class="hljs-keyword">import</span> { fetchNekosImages } <span class="hljs-keyword">from</span> <span class="hljs-string">"./nekosApi"</span>;
<span class="hljs-keyword">import</span> { fetchNekosBestImages } <span class="hljs-keyword">from</span> <span class="hljs-string">"./nekosBestApi"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchImages</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Try primary API first</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> fetchNekosiaImages();
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"Primary API failed, trying fallback:"</span>, error);

    <span class="hljs-comment">// Try first fallback API</span>
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> fetchNekosBestImages();
    } <span class="hljs-keyword">catch</span> (fallbackError) {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"First fallback API failed, trying second fallback:"</span>, fallbackError);

      <span class="hljs-comment">// Try second fallback API</span>
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> fetchNekosImages();
      } <span class="hljs-keyword">catch</span> (secondFallbackError) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"All image APIs failed:"</span>, secondFallbackError);
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"All image APIs failed"</span>);
      }
    }
  }
}
</code></pre>
<p><strong>Use the Image Service:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/hooks/useFetch.js</span>
<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { fetchImages } <span class="hljs-keyword">from</span> <span class="hljs-string">"../services/api/imageService"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useFetch</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [data, setData] = useState([]);
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
    setLoading(<span class="hljs-literal">true</span>);
    setError(<span class="hljs-literal">null</span>);

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> fetchImages();
      setData(result);
    } <span class="hljs-keyword">catch</span> (err) {
      setError(err.message || <span class="hljs-string">'An error occurred'</span>);
    } <span class="hljs-keyword">finally</span> {
      setLoading(<span class="hljs-literal">false</span>);
    }
  };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetchData();
  }, []);

  <span class="hljs-keyword">return</span> {
    data,
    loading,
    error,
    fetchData,
  };
}
</code></pre>
<h4 id="heading-key-features-of-our-api-implementation"><strong>Key Features of Our API Implementation:</strong></h4>
<ol>
<li><p><strong>Multiple API Sources</strong>:</p>
<ul>
<li><p>Primary API (Nekosia): Provides high-quality anime images</p>
</li>
<li><p>First Fallback (Nekos Best): Includes artist information</p>
</li>
<li><p>Second Fallback (Nekos): Simple and reliable backup</p>
</li>
</ul>
</li>
<li><p><strong>Consistent Data Format</strong>:</p>
<ul>
<li>All APIs transform their responses to match our expected format:</li>
</ul>
</li>
</ol>
<pre><code class="lang-json">    {
      images: [
        {
          id: string,
          image: {
            original: {
              url: string
            }
          }
        }
      ]
    }
</code></pre>
<ol start="3">
<li><p><strong>Robust Error Handling</strong>:</p>
<ul>
<li><p>Validates API responses</p>
</li>
<li><p>Checks for valid image URLs</p>
</li>
<li><p>Provides detailed error messages</p>
</li>
<li><p>Graceful fallback mechanism</p>
</li>
</ul>
</li>
<li><p><strong>Safety Features</strong>:</p>
<ul>
<li><p>Safe content filtering (<code>rating=safe</code>)</p>
</li>
<li><p>Image count limitation (21 images)</p>
</li>
<li><p>URL validation</p>
</li>
<li><p>Response format validation</p>
</li>
</ul>
</li>
<li><p><strong>Performance Considerations</strong>:</p>
<ul>
<li><p>Optimized image sizes</p>
</li>
<li><p>Filtered content tags</p>
</li>
<li><p>Efficient data transformation</p>
</li>
<li><p>Minimal API calls</p>
</li>
</ul>
</li>
</ol>
<p>This implementation ensures our game has a reliable source of images while handling potential API failures gracefully. The consistent data format across all APIs makes it easy to switch between them without affecting the game's functionality.</p>
<h2 id="heading-testing-the-app">Testing the App</h2>
<p>Testing is a crucial part of any application development, and for our Memory Card Game, we implemented a comprehensive testing strategy using modern tools and practices. Let's dive into how we structured our tests and some key testing patterns we used.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732517719141/8b034f36-761e-4433-acfd-82a2c4cffffc.png" alt="Running Tests" class="image--center mx-auto" width="942" height="780" loading="lazy"></p>
<h3 id="heading-testing-stack">Testing Stack</h3>
<ul>
<li><p><strong>Vitest</strong>: Our primary testing framework, chosen for its speed and seamless integration with Vite</p>
</li>
<li><p><strong>React Testing Library</strong>: For testing React components with a user-centric approach</p>
</li>
<li><p><strong>@testing-library/user-event</strong>: For simulating user interactions</p>
</li>
<li><p><strong>jsdom</strong>: For creating a DOM environment in our tests</p>
</li>
</ul>
<h3 id="heading-key-testing-patterns">Key Testing Patterns</h3>
<p>Testing was a crucial part of ensuring the reliability and maintainability of this Memory Card Game. I implemented a comprehensive testing strategy using React Testing Library and Vitest, focusing on several key areas:</p>
<h4 id="heading-1-component-testing">1. Component Testing</h4>
<p>I wrote extensive tests for my React components to ensure they render correctly and behave as expected. For example, the <code>CardsGrid</code> component, which is the heart of the game, has thorough test coverage including:</p>
<ul>
<li><p>Initial rendering states</p>
</li>
<li><p>Loading states</p>
</li>
<li><p>Error handling</p>
</li>
<li><p>Score tracking</p>
</li>
<li><p>Card interaction behavior</p>
</li>
</ul>
<h4 id="heading-2-test-mocking">2. Test Mocking</h4>
<p>To ensure reliable and fast tests, I implemented several mocking strategies:</p>
<ul>
<li><p>Local storage operations using useLocalStorage hook</p>
</li>
<li><p>API calls using the <code>useFetch</code> hook</p>
</li>
<li><p>Event handlers and state updates</p>
</li>
</ul>
<h4 id="heading-3-testing-best-practices">3. Testing Best Practices</h4>
<p>Throughout my testing implementation, I followed several best practices:</p>
<ul>
<li><p>Using <code>beforeEach</code> and <code>afterEach</code> hooks to reset state between tests</p>
</li>
<li><p>Testing user interactions using <code>fireEvent</code> from React Testing Library</p>
</li>
<li><p>Writing tests that resemble how users interact with the app</p>
</li>
<li><p>Testing both success and error scenarios</p>
</li>
<li><p>Isolating tests using proper mocking</p>
</li>
</ul>
<h4 id="heading-4-testing-tools">4. Testing Tools</h4>
<p>The project leverages modern testing tools and libraries:</p>
<ul>
<li><p><strong>Vitest</strong>: As the test runner</p>
</li>
<li><p><strong>React Testing Library</strong>: For testing React components</p>
</li>
<li><p><strong>@testing-library/jest-dom</strong>: For enhanced DOM testing assertions</p>
</li>
<li><p><strong>@testing-library/user-event</strong>: For simulating user interactions</p>
</li>
</ul>
<p>This comprehensive testing approach helped me catch bugs early, ensured code quality, and made refactoring safer and more manageable.</p>
<h2 id="heading-optimizations">Optimizations</h2>
<p>To ensure smooth performance, especially on mobile devices, we implemented several optimization techniques:</p>
<ol>
<li><p><strong>Response Transformation</strong></p>
<ul>
<li><p>Standardized data format across all APIs</p>
</li>
<li><p>Efficient ID extraction from URLs</p>
</li>
<li><p>Structured image metadata for quick access</p>
</li>
</ul>
</li>
<li><p><strong>Network Optimization</strong></p>
<ul>
<li><p>Using <code>no-cors</code> mode where appropriate to handle CORS issues efficiently</p>
</li>
<li><p>Error handling with specific status codes for better debugging</p>
</li>
<li><p>Consistent response structure across all API implementations</p>
</li>
</ul>
</li>
<li><p><strong>Mobile-First Considerations</strong></p>
<ul>
<li><p>Optimized image loading strategy</p>
</li>
<li><p>Efficient error handling to prevent unnecessary retries</p>
</li>
<li><p>Streamlined data transformation to reduce processing overhead</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-future-improvements"><strong>Future Improvements</strong></h2>
<p>There are a few ways that we could further improve this project:</p>
<ol>
<li><p><strong>API Response Caching</strong></p>
<ul>
<li><p>Implement local storage caching for frequently used images</p>
</li>
<li><p>Add cache invalidation strategy for fresh content</p>
</li>
<li><p>Implement progressive image loading</p>
</li>
</ul>
</li>
<li><p><strong>Performance Optimizations</strong></p>
<ul>
<li><p>Add image lazy loading for better initial load time</p>
</li>
<li><p>Implement request queuing for better bandwidth management</p>
</li>
<li><p>Add response compression for faster data transfer</p>
</li>
</ul>
</li>
<li><p><strong>Reliability Enhancements</strong></p>
<ul>
<li><p>Add API health checking before attempts</p>
</li>
<li><p>Implement retry mechanisms with exponential backoff</p>
</li>
<li><p>Add circuit breaker pattern for failing APIs</p>
</li>
</ul>
</li>
<li><p><strong>Analytics and Monitoring</strong></p>
<ul>
<li><p>Track API success rates</p>
</li>
<li><p>Monitor response times</p>
</li>
<li><p>Implement automatic API switching based on performance metrics</p>
</li>
</ul>
</li>
</ol>
<p>This robust implementation ensures that our game remains functional and performant even under adverse network conditions or API unavailability, while still maintaining room for future improvements and optimizations.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building this Memory Card Game has been more than just creating a fun, ad-free alternative for kids—it's been an exercise in implementing modern web development best practices while solving a real-world problem.</p>
<p>The project demonstrates how combining thoughtful architecture, robust testing, and reliable fallback mechanisms can result in a production-ready application that's both entertaining and educational.</p>
<h3 id="heading-key-takeaways">🗝️ Key Takeaways</h3>
<ol>
<li><p><strong>User-Centric Development</strong></p>
<ul>
<li><p>Started with a clear problem (ad-filled games affecting user experience)</p>
</li>
<li><p>Implemented features that enhance gameplay without interruptions</p>
</li>
<li><p>Maintained focus on performance and reliability across devices</p>
</li>
</ul>
</li>
<li><p><strong>Technical Excellence</strong></p>
<ul>
<li><p>Leveraged modern React patterns and hooks for clean, maintainable code</p>
</li>
<li><p>Implemented comprehensive testing strategy ensuring reliability</p>
</li>
<li><p>Created a robust API fallback system for uninterrupted gameplay</p>
</li>
</ul>
</li>
<li><p><strong>Performance First</strong></p>
<ul>
<li><p>Adopted mobile-first approach with responsive design</p>
</li>
<li><p>Optimized image loading and handling</p>
</li>
<li><p>Implemented efficient state management and caching strategies</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-learning-outcomes">📚 Learning Outcomes</h3>
<p>This project showcases how seemingly simple games can be excellent vehicles for implementing complex technical solutions. From component architecture to API fallbacks, each feature was built with scalability and maintainability in mind, proving that even hobby projects can maintain professional-grade code quality.</p>
<h3 id="heading-moving-forward">🔮 Moving Forward</h3>
<p>While the game successfully achieves its primary goal of providing an ad-free, enjoyable experience, the documented future improvements provide a clear roadmap for evolution. Whether it's implementing additional optimizations or adding new features, the foundation is solid and ready for expansion.</p>
<p>The Memory Card Game stands as a testament to how personal projects can both solve real-world problems and serve as platforms for implementing best practices in modern web development. Feel free to explore the <a target="_blank" href="https://github.com/mihailgaberov/memory-card">code</a>, contribute, or use it as inspiration for your own projects!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Code a Sonic Infinite Runner Game in JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ Infinite runner games have been a favorite for gamers and developers alike due to their fast-paced action and replayability. These games often feature engaging mechanics like endless levels, smooth character movement, and dynamic environments that ke... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/code-a-sonic-infinite-runner-game-in-javascript/</link>
                <guid isPermaLink="false">671140909e01688d7732fd36</guid>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 17 Oct 2024 16:51:28 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729182842440/b8ec758a-735c-4e23-84c5-aaf15ec7bf75.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Infinite runner games have been a favorite for gamers and developers alike due to their fast-paced action and replayability. These games often feature engaging mechanics like endless levels, smooth character movement, and dynamic environments that keep players hooked. If you're a fan of classic video games and want to try your hand at game development, we have a course for you.</p>
<p>We just published a course on the <a target="_blank" href="http://freeCodeCamp.org">freeCodeCamp.org</a> YouTube channel that will teach you how to build an infinite runner game using JavaScript! In this course, JSLegendDev guides you through the process of creating a Sonic-inspired infinite runner game, complete with all the exciting elements you'd expect. You’ll be working with the Kaplay game development library, a powerful yet beginner-friendly tool that simplifies the process of building 2D games in JavaScript. By the end of this course, you'll know how to implement core game mechanics such as gameplay, enemy logic, parallax scrolling, scoring systems, and more.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729183762973/712fe20b-2c35-496f-8482-6ab228523fa5.gif" alt="Sonic game." class="image--center mx-auto" width="772" height="430" loading="lazy"></p>
<p>The course covers everything from project setup and loading assets to more advanced features like creating interactive game objects and handling game states. You'll start by setting up your development environment, loading the assets for your game, and building a main menu scene. Next, you'll learn to create the Sonic game object, which will serve as the player's character, and learn how to manage its movement and interactions within the game world.</p>
<p>As the game takes shape, you'll implement key gameplay mechanics, such as enemy logic, where you'll learn how to create obstacles that challenge the player, and a scoring system that tracks rings collected during the run. The course also explores parallax scrolling, which adds depth to the game by making background elements move at different speeds, giving the illusion of a 3D environment.</p>
<p>Towards the end of the course, you'll build a game over scene, polish the game, and package it for distribution, ensuring it's ready for others to play and enjoy.</p>
<p>Watch the full course on the freeCodeCamp.org YouTube channel (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/EmMO0yQ7eeY" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript GameDev with Kaboom.js ]]>
                </title>
                <description>
                    <![CDATA[ Creating a game can be one of the most rewarding experiences in programming, and building a Metroidvania-style game takes that excitement to another level. These games are known for their intricate maps, challenging enemies, and complex mechanics tha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/javascript-gamedev-with-kaboomjs/</link>
                <guid isPermaLink="false">667ee4f50378ea76f4ff5785</guid>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Fri, 28 Jun 2024 16:29:41 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719592156998/4ba8f0f3-9662-480a-b1ee-682a49d5ed42.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Creating a game can be one of the most rewarding experiences in programming, and building a Metroidvania-style game takes that excitement to another level. These games are known for their intricate maps, challenging enemies, and complex mechanics that keep players engaged. If you’ve ever wanted to create your own Metroidvania game, now is the perfect time to start.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you all about using JavaScript and Kaboom.js to build a Metroidvania-style game from scratch. This comprehensive tutorial covers everything you need to know, from setting up your development environment to implementing complex game mechanics like enemy AI and boss battles. Plus, all the game assets you’ll need are provided, so you can focus on learning and creating. JSLegendDev created this course.</p>
<p>The course begins with an introductory section that gives you a high-level overview of what you’ll be building. You'll then move on to setting up your development environment, ensuring you have all the tools necessary to begin coding. The next steps involve initializing Kaboom.js, a JavaScript game programming library, and loading the various assets that will make up your game world.</p>
<p>Once your environment is set up and your assets are loaded, you'll dive into defining scenes, an essential part of structuring your game. Understanding game objects in Kaboom.js is crucial, as these are the building blocks of your game. You’ll learn how to implement logic to load and display the game map and place colliders that control the interaction between your player and the environment.</p>
<p>The course doesn’t just stop at the basics. You’ll also implement player logic, manage global state, and set up a dynamic camera system that follows the player. Enemy AI is a big part of what makes Metroidvania games challenging, so you'll learn how to implement a drone enemy and a formidable boss barrier. To make your game even more engaging, the tutorial covers the creation of a boss battle, complete with health cartridges and a health bar to track the player's status.</p>
<p>Moreover, you'll ensure your game handles player respawns correctly when they fall out of bounds and link multiple rooms to create a seamless game world. The finishing touches will polish your game, making it feel complete and professional. Finally, you’ll receive general guidance on deploying your project, so you can share your creation with the world.</p>
<p>Whether you are an experienced developer looking to expand your skills or a beginner eager to dive into game development, this course is designed to be accessible and comprehensive.</p>
<p>Watch the full course on <a target="_blank" href="https://youtu.be/iM1iSvloMlo">the freeCodeCamp.org YouTube channel</a> (5-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/iM1iSvloMlo" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript Game Dev Tutorial – Build Gorillas with HTML Canvas + JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ In this JavaScript game tutorial, you'll learn how to create a modern version of the 1991 classic game Gorillas using plain JavaScript and the HTML canvas element. In this game, two gorillas throw explosive bananas at each other, and the first one to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/gorillas-game-in-javascript/</link>
                <guid isPermaLink="false">66c4c805e7521bfd6862b3b0</guid>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hunor Márton Borbély ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jan 2024 18:18:55 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-16.02.31-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this JavaScript game tutorial, you'll learn how to create a modern version of the 1991 classic game Gorillas using plain JavaScript and the HTML canvas element.</p>
<p>In this game, two gorillas throw explosive bananas at each other, and the first one to hit the other one wins the game.</p>
<p>We'll build out the whole game from scratch here. First, you'll learn how to draw on a canvas element with JavaScript. You'll see how to draw the background, the buildings, the gorillas, and the bomb. We won't use any images here – we'll draw everything using code.</p>
<p>Then we'll add some interactions and add event handlers. We'll also cover how to aim, how to animate the bomb across the sky, and how to detect if the bomb hit the other gorilla, or a building.</p>
<p>Throughout the tutorial, we'll be using plain JavaScript. To get the most out of this tutorial, you should have a basic understanding of JavaScript. But even if you are a beginner, you can still follow along and learn as you go.</p>
<p>In this article, we'll simplify a couple of steps. For more detail, you can also watch the <a target="_blank" href="https://www.youtube.com/watch?v=2q5EufbUEQk">extended tutorial on YouTube</a>. In the YouTube version, we also cover how to make the buildings destructible, how to animate the hand of the gorilla to follow the drag movement while aiming, have nicer graphics, and we add AI logic, so you can play against the computer.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/2q5EufbUEQk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>If you got stuck, you can also find the final source code of the game we are about to create on <a target="_blank" href="https://github.com/HunorMarton/gorillas">GitHub</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents:</h2>
<ol>
<li><a class="post-section-overview" href="#heading-background-on-the-game">Background on the Game</a></li>
<li><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></li>
<li><a class="post-section-overview" href="#heading-overview-of-the-game-logic">Overview of the Game Logic</a></li>
<li><a class="post-section-overview" href="#heading-how-to-draw-the-scene">How to Draw the Scene</a></li>
<li><a class="post-section-overview" href="#heading-how-to-turn-the-coordinate-system-upside-down">How to Turn the Coordinate System Upside Down</a></li>
<li><a class="post-section-overview" href="#heading-how-to-draw-the-game-elements">How to Draw the Game Elements</a></li>
<li><a class="post-section-overview" href="#heading-how-to-fit-the-size-of-the-city-to-the-browser-window">How to Fit the Size of the City to the Browser Window</a></li>
<li><a class="post-section-overview" href="#heading-how-the-gorilla-can-throw-the-bomb">How the Gorilla Can Throw the Bomb</a></li>
<li><a class="post-section-overview" href="#heading-how-the-gorilla-can-throw-the-bomb">How to Animate the Incoming Bomb</a></li>
<li><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></li>
</ol>
<h2 id="heading-background-on-the-game">Background on the Game</h2>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Gorillas_%28video_game%29">Gorillas</a> is a game from 1991. In this game, two gorillas are standing on the top of randomly generated buildings and take turns throwing explosive bananas at each other.</p>
<p>In each round, the players set the angle and velocity of the throw, and keep refining it, until they hit the other gorilla. The flying banana bomb is affected by gravity and the wind.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-07-at-22.58.23-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The original Gorillas game from 1991 (source: retrogames.cz)</em></p>
<p>We are going to implement a modern version of this game. The original game did not support a mouse. Every round, the players had to type in the angle and the velocity with a keyboard. We are going to implement it with mouse support, and nicer graphics.</p>
<p>You can try out the extended version of the game on <a target="_blank" href="https://codepen.io/HunorMarton/pen/jOJZqvp">CodePen</a>. Give it a try before we get into it.</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/HunorMarton/embed/jOJZqvp" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<h2 id="heading-project-setup">Project Setup</h2>
<p>To implement this game, we are going to have a simple HTML, CSS, and JavaScript file. You can break down the JavaScript logic into multiple files if you want, but for the sake of simplicity, we have everything in one place.</p>
<p>Because we're using plain JavaScript and don’t use any libraries and third-party tools, we don’t need any compilers or builders. We can run things directly in the browser.</p>
<p>To simplify the process, I recommend installing the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer">Live Server</a> VS Code extension. With this extension installed, you can simply right-click the HTML file and select ‘Open with Live Server’. This will run a live version of the game in the browser.</p>
<p>This means we don’t have to hit refresh in the browser every time we make a change in the code. It’s enough that we save the changes in the file and the browser will refresh automatically.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-18-at-21.29.22-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Running the game with the Live Server VS Code extension</em></p>
<h2 id="heading-overview-of-the-game-logic">Overview of the Game Logic</h2>
<p>Before we get to the details, let's walk through the main parts of the game.</p>
<p>The game is driven by the game <code>state</code>. This is a JavaScript object that serves as metadata for the game. From the size of buildings to the current position of the bomb, it includes many things.</p>
<p>The game state includes things like whose turn it is, are we aiming now, or is the bomb already flying in the air? And so on. These are all variables that we need to keep track of. When we draw the game scene, we'll draw it based on the game state.</p>
<p>Then we have the <code>draw</code> function. This function will draw almost everything we have on the screen. It paints the background, the buildings, the gorillas, and the banana bombs. This function paints the entire screen from top to bottom every time we call it.</p>
<p>We are going to add event handling to aim the bombs and we'll implement the <code>throwBomb</code> function that kicks off the animation loop. The <code>animate</code> function moves the bombs across the sky.</p>
<p>This function will be responsible for calculating the exact position of the bomb as it flies through the air at every animation cycle. On top of that, it also has to figure out when the movement ends. With every movement, we check if we hit a building or an enemy, or if the bomb got off-screen. We’ll add hit detection as well.</p>
<p>Now let's walk through our initial files.</p>
<h3 id="heading-the-initial-html-file">The initial HTML file</h3>
<p>Our initial HTML file will be very simple. In the header, we'll add a link to our stylesheet and our JavaScript file. Note that I’m using the <code>defer</code> keyword to make sure the script only executes once the rest of the document is parsed.</p>
<p>In the body, we'll add a <code>canvas</code> element. We are going to paint on this element with JavaScript. Almost everything we can see on the screen will be in this canvas element. Here's the code:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Gorillas<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"index.css"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"index.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Later we are going to add more things into this file. We'll add info panels to show the angle and velocity of the current throw. But for now, that’s all we have.</p>
<h3 id="heading-the-initial-css-file">The initial CSS file</h3>
<p>Initially, our CSS is also very simple. We can’t style anything inside the canvas element, so here we only style the other elements we have.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
}
</code></pre>
<p>‌Later, when we add more elements in HTML, we will also update this file. For now, let’s make sure that our canvas can fit the whole screen. By default, browsers tend to add a little margin or padding around the body. Let’s remove that.</p>
<h3 id="heading-the-main-parts-of-our-javascript-file">The Main Parts of Our JavaScript File</h3>
<p>Most of the logic will be in our JavaScript file. Let's walk through the main parts of this file, and define a few placeholder functions:</p>
<ul>
<li>We declare the game <code>state</code> object and a bunch of utility variables. This will contain the metadata of our game. For now, it's an empty object. We will initialize its value when we get to the <code>newGame</code> function.</li>
<li>Then we have references to every HTML element that we need to access from JavaScript. For now, we only have a reference to the <code>&lt;canvas&gt;</code> element. We access this element by ID.</li>
<li>We initialize the game state and paint the scene by calling the <code>newGame</code> function. This is the only top-level function call. This function is responsible for both initializing the game and resetting it.</li>
<li>We define the <code>draw</code> function that draws the whole scene on the canvas element based on the game state. We will draw the background, the buildings, the gorillas, and the bomb.</li>
<li>We set up event handlers for the <code>mousedown</code>, <code>mousemove</code>, and <code>mouseup</code> events. We are going to use these for aiming.</li>
<li>The <code>mouseup</code> event will trigger the <code>throwBomb</code> function that kicks off the main animation loop. The <code>animate</code> function will manipulate the state in every animation cycle and call the <code>draw</code> function to update the screen.</li>
</ul>
<pre><code class="lang-js"><span class="hljs-comment">// The state of the game</span>
<span class="hljs-keyword">let</span> state = {};
<span class="hljs-comment">// ...</span>

<span class="hljs-comment">// References to HTML elements</span>
<span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"game"</span>);
<span class="hljs-comment">// ...</span>

newGame();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Initialize game state</span>
  state = {
    <span class="hljs-comment">// ...</span>
  };

  <span class="hljs-comment">// ...</span>

  draw();
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-comment">// Event handlers</span>
<span class="hljs-comment">// ...</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">throwBomb</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params">timestamp</span>) </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>‌We will have a couple more utility functions, but they are less important. We'll discuss them as we go.</p>
<h3 id="heading-game-phases">Game Phases</h3>
<p>In the next step, we'll set up our initial game <code>state</code>. Before we get to the different parts of the state, let's talk about one of its most important properties: the game <code>phase</code> property.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/game-phases.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>The three game phases: the <code>aiming</code> phase, the <code>in flight</code> phase, and the <code>celebrating</code> phase</em></p>
<p>The game has three different phases. The game starts in the <code>aiming</code> phase, when the bomb is in the hand of a gorilla and the event handlers are active. ‌‌‌‌Then once you throw the bomb, the game moves on to the <code>in flight</code> phase. In this phase, the event handlers are deactivated and the <code>animate</code> function moves the bomb across the sky. We also add hit detection to know when should we stop the animation.</p>
<p>These two game phases repeat each other over and over again, until one of the gorillas hits the other. Once we hit the enemy, the game moves on to the <code>celebrating</code> phase. We draw a winning gorilla, show the congratulations screen, and a button to restart the game.</p>
<h3 id="heading-how-to-initialize-the-game">How to initialize the Game</h3>
<p>The game is initialized by the <code>newGame</code> function. This resets the game <code>state</code>, generates a new level, and calls the <code>draw</code> function to draw the whole scene.</p>
<p>Let’s walk through what we have initially in the <code>state</code> object:</p>
<ul>
<li>First, we have the game <code>phase</code> property that can be either <code>aiming</code>, <code>in flight</code>, or <code>celebrating</code>.</li>
<li>Then, the <code>currentPlayer</code> property tells us whose turn it is – the player on the left or the player on the right.</li>
<li>The <code>bomb</code> object describes the bomb’s current position and its velocity. Its initial position has to be aligned with the second building so we only set it once the level is generated.</li>
<li>The <code>buildings</code> array defines the position and size of the buildings that appear on the screen. We generate the metadata of the buildings with a utility function that we'll discuss later.</li>
</ul>
<pre><code class="lang-js"><span class="hljs-comment">// The state of the game</span>
<span class="hljs-keyword">let</span> state = {};

. . .

newGame();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Initialize game state</span>
  state = {
    <span class="hljs-attr">phase</span>: <span class="hljs-string">"aiming"</span>, <span class="hljs-comment">// aiming | in flight | celebrating</span>
    <span class="hljs-attr">currentPlayer</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">bomb</span>: {
      <span class="hljs-attr">x</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">y</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
    },
    <span class="hljs-attr">buildings</span>: generateBuildings(),
  };

  initializeBombPosition();

  draw();
}
</code></pre>
<p>‌We'll discuss the utility functions used above (<code>generateBuildings</code> and <code>initializeBombPosition</code>) in the next chapter as we draw the buildings and the bomb. For now, let's just add some placeholder functions to make sure we don't get an error from JavaScript.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateBuildings</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initializeBombPosition</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Now that we have a skeleton of our application and we've initialized some of the states, let’s switch gears and start drawing on the canvas as we fill in the missing pieces of the state.</p>
<h2 id="heading-how-to-draw-the-scene">How to Draw the Scene</h2>
<p>The <code>draw</code> function paints the whole canvas based on the state. It draws the background and the buildings, draws the gorillas, and draws the bomb. The function can also draw different gorilla variations depending on the state. The gorilla looks different while aiming, celebrating, or waiting for impact.</p>
<p>We will use this function both for painting the initial scene and throughout our main animation loop.</p>
<p>For the initial paint, some of the features we cover here won't be necessary. For instance, we'll also cover how to draw the celebrating gorilla, while we'll only see that once the game is over. But we'll still cover it because this way we won’t have to get back to this function once we start animating the state.</p>
<p>Everything we draw in this function is based on the state, and it doesn't matter to the function if the game is in the initial state, or if we are further into the game.</p>
<p>We defined a <code>&lt;canvas&gt;</code> element in HTML. How do we paint things on it?</p>
<pre><code class="lang-html">. . .

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

. . .
</code></pre>
<p>In JavaScript first, we get the canvas element by ID. Then we set the size of the canvas to fill the whole browser window. And finally, we get its drawing context.</p>
<p>This is a built-in API with many methods and properties that we can use to draw on the canvas. Let's see a few examples of how to use this API.</p>
<pre><code class="lang-js">. . . 

<span class="hljs-comment">// The canvas element and its drawing context </span>
<span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"game"</span>); 
canvas.width = <span class="hljs-built_in">window</span>.innerWidth; 
canvas.height = <span class="hljs-built_in">window</span>.innerHeight; 
<span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">"2d"</span>); 

. . . 

function draw() {
  <span class="hljs-comment">// ... </span>
} 

. . .
</code></pre>
<h3 id="heading-example-drawing-a-rectangle">Example: Drawing a Rectangle</h3>
<p>Let’s look at a few quick examples. These are not part of our game yet, they'll just serve as an introduction.</p>
<p>The most basic thing we can do is to fill a rectangle.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-23.00.23-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Using the <code>fillRect</code> method to fill a rectangle</em></p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"game"</span>);
canvas.width = <span class="hljs-built_in">window</span>.innerWidth;
canvas.height = <span class="hljs-built_in">window</span>.innerHeight;
<span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">"2d"</span>);

ctx.fillStyle = <span class="hljs-string">"#58A8D8"</span>;

ctx.fillRect(<span class="hljs-number">200</span>, <span class="hljs-number">200</span>, <span class="hljs-number">440</span>, <span class="hljs-number">320</span>);
</code></pre>
<p>With the <code>fillRect</code> method, we specify the top left coordinate of our rectangle (200, 200), and we set its width and height (440, 320).</p>
<p>By default, the fill color is going to be black. We can change it by setting the <code>fillStyle</code> property.</p>
<p>The way canvas works is that we have to set up drawing parameters before we paint, and not the other way around. It’s not like we paint a rectangle, and then we can change its color. Once something is on the canvas, it stays as it is.</p>
<p>You can think of it like a real canvas, where you also pick the color with your brush before you start painting with it. Then once you've painted something you can either cover it, by painting something over it, or you can try to clear the canvas. But you can’t change existing parts, really. That’s why we set the color here up front and not afterward.</p>
<p>We are going to draw rectangles to fill the background and to show the buildings.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-19.30.36.png" alt="Image" width="600" height="400" loading="lazy">
<em>The background and the buildings are simple rectangles</em></p>
<h3 id="heading-example-filling-a-path">Example: Filling a Path</h3>
<p>We can of course draw more complicated shapes as well. We can define a path, like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-23.01.23-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Filling a path</em></p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"game"</span>);
canvas.width = <span class="hljs-built_in">window</span>.innerWidth;
canvas.height = <span class="hljs-built_in">window</span>.innerHeight;
<span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">"2d"</span>);

ctx.fillStyle = <span class="hljs-string">"#58A8D8"</span>;

ctx.beginPath();
ctx.moveTo(<span class="hljs-number">200</span>, <span class="hljs-number">200</span>);
ctx.lineTo(<span class="hljs-number">500</span>, <span class="hljs-number">350</span>);
ctx.lineTo(<span class="hljs-number">200</span>, <span class="hljs-number">500</span>);
ctx.fill();
</code></pre>
<p>Paths start with the <code>beginPath</code> method and end with either calling the <code>fill</code> or the <code>stroke</code> method – or both. In between, we build the path by calling path-building methods.</p>
<p>In this example, we draw a triangle. We move to the <code>300,300</code> coordinate with the <code>moveTo</code> method. Then we call the <code>lineTo</code> method to move to the right side of our shape. And then we continue the path, by calling the <code>lineTo</code> method again to <code>300,400</code>.</p>
<p>None of this would be visible if we didn’t end with the <code>fill</code> method to fill the path we just built.</p>
<p>We are going to fill paths to draw our gorilla and our bomb.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/paths-gorilla-drawing.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>We will use paths to draw the main body of the gorilla</em></p>
<h3 id="heading-example-drawing-a-stroke">‌Example: Drawing a Stroke</h3>
<p>In a very similar way, we can also draw a line. Here, we'll start with the <code>beginPath</code> method again. We'll also build up the shape with a <code>moveTo</code> and two <code>lineTo</code> methods. The coordinates here are the same. But in the end, we don’t call the <code>fill</code> but the <code>stroke</code> method. This, instead of filling the shape, will draw the line we built.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-23.02.34-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Drawing a stroke</em></p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"game"</span>);
canvas.width = <span class="hljs-built_in">window</span>.innerWidth;
canvas.height = <span class="hljs-built_in">window</span>.innerHeight;
<span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">"2d"</span>);

ctx.strokeStyle = <span class="hljs-string">"#58A8D8"</span>;
ctx.lineWidth = <span class="hljs-number">30</span>;

ctx.beginPath();
ctx.moveTo(<span class="hljs-number">200</span>, <span class="hljs-number">200</span>);
ctx.lineTo(<span class="hljs-number">500</span>, <span class="hljs-number">350</span>);
ctx.lineTo(<span class="hljs-number">200</span>, <span class="hljs-number">500</span>);
ctx.stroke();
</code></pre>
<p>Strokes have different styling properties. Instead of the <code>fillStyle</code> property, we set <code>strokeStyle</code>. To this property – and also to <code>fillStyle</code> – we can assign any color value that is valid in CSS. To set the line width we use the <code>lineWidth</code> property.</p>
<p>We can also build more complex paths. In the example below, we draw a curve. We are going to cover this in a bit more detail when we draw the arms of the gorillas.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-23.05.04-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Drawing a curve</em></p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"game"</span>);
canvas.width = <span class="hljs-built_in">window</span>.innerWidth;
canvas.height = <span class="hljs-built_in">window</span>.innerHeight;
<span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">"2d"</span>);

ctx.strokeStyle = <span class="hljs-string">"#58A8D8"</span>;
ctx.lineWidth = <span class="hljs-number">30</span>;

ctx.beginPath();
ctx.moveTo(<span class="hljs-number">200</span>, <span class="hljs-number">300</span>);
ctx.quadraticCurveTo(<span class="hljs-number">500</span>, <span class="hljs-number">400</span>, <span class="hljs-number">800</span>, <span class="hljs-number">300</span>);
ctx.stroke();
</code></pre>
<p>We are going to use the <code>stroke</code> method to draw the arms and the face of the gorilla.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/gorilla-stroke-drawing.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>We use the <code>stroke</code> method to draw the arms and the face of the gorilla</em></p>
<p>‌Now that we're finished with this introduction, let’s go back to our game and see what’s inside the <code>draw</code> function.</p>
<h2 id="heading-how-to-turn-the-coordinate-system-upside-down">How to Turn the Coordinate System Upside Down</h2>
<p>When we use canvas, we have a coordinate system with the origin at the top-left corner of the browser window that grows to the right and downwards. This is aligned with how websites work in general. Things go from left to right and top to bottom. This is the default, but we can change this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-23.09.35-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The default coordinate system</em></p>
<p>When we talk about games, it is more convenient to go from the bottom to the top. For instance, when we draw buildings, they can start at the bottom, and we don’t have to figure out where the bottom of the window is.</p>
<p>We can use the <code>translate</code> method to shift the entire coordinate system to the bottom-left corner. We just need to move the coordinate system down along the Y-axis by the size of the browser window.</p>
<p>Once we do this, the Y-coordinate is still growing downwards. We can flip it using the <code>scale</code> method. Setting a negative number for the vertical direction will flip the entire coordinate system upside down.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-23.09.32-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The coordinate system upside down</em></p>
<pre><code class="lang-js"><span class="hljs-comment">// The canvas element and its drawing context </span>
<span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"game"</span>); 
canvas.width = <span class="hljs-built_in">window</span>.innerWidth; 
canvas.height = <span class="hljs-built_in">window</span>.innerHeight; 
<span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">"2d"</span>); 

. . . 

function draw() { 
  ctx.save(); 
  <span class="hljs-comment">// Flip coordinate system upside down </span>
  ctx.translate(<span class="hljs-number">0</span>, <span class="hljs-built_in">window</span>.innerHeight); 
  ctx.scale(<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>); 

  <span class="hljs-comment">// Draw scene </span>
  drawBackground(); 
  drawBuildings();
  drawGorilla(<span class="hljs-number">1</span>);
  drawGorilla(<span class="hljs-number">2</span>);
  drawBomb(); 

  <span class="hljs-comment">// Restore transformation </span>
  ctx.restore(); 
}
</code></pre>
<p>‌Now let's implement the <code>draw</code> function. One of the first things we do is calling the <code>translate</code> and the <code>scale</code> methods to flip the coordinate system upside down.</p>
<p>We have to do this before we paint anything on the canvas because the <code>translate</code> and <code>scale</code> methods do not actually move anything on the canvas. If we painted anything on the canvas before, it would stay as it was.</p>
<p>Technically these methods change the transformation matrix. You can think of it as changing the coordinate system. Anything we paint after these methods will be painted according to this new coordinate system.</p>
<p>We also need to restore these transformations once we draw by calling the <code>restore</code> method. The <code>restore</code> method comes in a pair with the <code>save</code> method. <code>save</code> serves as a checkpoint that the <code>restore</code> method can get back to.</p>
<p>It's a common practice to start a drawing block by calling the <code>save</code> method and ending it with the <code>restore</code> method when we use transformations in between.</p>
<p>We need to call these two functions because the <code>translate</code> and <code>scale</code> methods accumulate. We are going to call the <code>draw</code> function multiple times. Without the <code>save</code> and <code>restore</code> methods, the coordinate system would keep on moving downwards each time we call the <code>draw</code> function, and it would eventually get completely off-screen.</p>
<p>Drawing the entire scene includes many parts. We'll break it down into separate draw functions. Now let’s start drawing by implementing these functions:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBackground</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBuildings</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorilla</span>(<span class="hljs-params">player</span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBomb</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<h2 id="heading-how-to-draw-the-game-elements">How to Draw the Game Elements</h2>
<h3 id="heading-how-to-draw-the-background">How to draw the background</h3>
<p>When we draw on a canvas, the order matters. We'll start with the background, then we'll go layer by layer. In our case, the background is a simple rectangle.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-16.00.14.png" alt="Image" width="600" height="400" loading="lazy">
<em>The background sky</em></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBackground</span>(<span class="hljs-params"></span>) </span>{
  ctx.fillStyle = <span class="hljs-string">"#58A8D8"</span>;
  ctx.fillRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-built_in">window</span>.innerWidth, <span class="hljs-built_in">window</span>.innerHeight);
}
</code></pre>
<p>We draw this rectangle the same way we did it in our introduction. First, we set the fill style, then we draw a rectangle with the <code>fillRect</code> method. Here we set the starting coordinates in the corner and set the size to fill the whole browser window.</p>
<p>We could also add a moon to the sky. Unfortunately, there's no fill circle method that would easily do this, so we'll skip it for now. On <a target="_blank" href="https://codepen.io/HunorMarton/full/jOJZqvp">CodePen</a> you can find a version that also draws a moon in the <code>drawBackground</code> function.</p>
<h3 id="heading-how-to-draw-the-buildings">How to draw the buildings</h3>
<p>Drawing the buildings has two parts. First, we need to generate metadata for the buildings when we initialize the level. Then we implement the <code>drawBuildings</code> function that paints the buildings based on this metadata.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-17.02.52-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The buildings and their metadata</em></p>
<h3 id="heading-buildings-metadata">Buildings Metadata</h3>
<p>In the <code>newGame</code> function, we called the <code>generateBuildings</code> function to initialize the <code>buildings</code> property in our game <code>state</code>. We haven't implemented this function yet.</p>
<pre><code class="lang-js">  . . .

  state = {
    <span class="hljs-attr">phase</span>: <span class="hljs-string">"aiming"</span>, <span class="hljs-comment">// aiming | in flight | celebrating</span>
    <span class="hljs-attr">currentPlayer</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">bomb</span>: {
      <span class="hljs-attr">x</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">y</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
    },
    <span class="hljs-attr">buildings</span>: generateBuildings(),
  };

  . . .
</code></pre>
<p>‌Let's see how this function works. Each building is defined by its <code>x</code> position, its <code>width</code>, and its <code>height</code>.</p>
<p>The <code>x</code> coordinate is always aligned to the previous building. We check where the previous building ends, and we add a little gap. In case there is no previous building – because we're adding the first one – then we start at the beginning of the screen at 0.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateBuildings</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> buildings = [];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> index = <span class="hljs-number">0</span>; index &lt; <span class="hljs-number">8</span>; index++) {
    <span class="hljs-keyword">const</span> previousBuilding = buildings[index - <span class="hljs-number">1</span>];

    <span class="hljs-keyword">const</span> x = previousBuilding
      ? previousBuilding.x + previousBuilding.width + <span class="hljs-number">4</span>
      : <span class="hljs-number">0</span>;

    <span class="hljs-keyword">const</span> minWidth = <span class="hljs-number">80</span>;
    <span class="hljs-keyword">const</span> maxWidth = <span class="hljs-number">130</span>;
    <span class="hljs-keyword">const</span> width = minWidth + <span class="hljs-built_in">Math</span>.random() * (maxWidth - minWidth);

    <span class="hljs-keyword">const</span> platformWithGorilla = index === <span class="hljs-number">1</span> || index === <span class="hljs-number">6</span>;

    <span class="hljs-keyword">const</span> minHeight = <span class="hljs-number">40</span>;
    <span class="hljs-keyword">const</span> maxHeight = <span class="hljs-number">300</span>;
    <span class="hljs-keyword">const</span> minHeightGorilla = <span class="hljs-number">30</span>;
    <span class="hljs-keyword">const</span> maxHeightGorilla = <span class="hljs-number">150</span>;

    <span class="hljs-keyword">const</span> height = platformWithGorilla
      ? minHeightGorilla + <span class="hljs-built_in">Math</span>.random() * (maxHeightGorilla - minHeightGorilla)
      : minHeight + <span class="hljs-built_in">Math</span>.random() * (maxHeight - minHeight);

    buildings.push({ x, width, height });
  }
  <span class="hljs-keyword">return</span> buildings;
}
</code></pre>
<p>Then the function generates a random building <code>width</code> within a predefined range. We set the minimum and maximum width, and pick a random number in between.</p>
<p>We generate a random building <code>height</code> in a similar way with one difference: the <code>height</code> of a building also depends on whether a gorilla is standing on top of it or not.</p>
<p>If a gorilla is standing on top of the building, then the height range is smaller. We want to have relatively higher buildings in between the two gorillas so that they can’t just see each other in a straight line.</p>
<p>We'll know if a gorilla is standing on the building because they always stand on top of the same buildings. The second one from the left, and the second to last. If the building index matches these positions, we set the height based on a different range.</p>
<p>Then we push these three values as an object into the <code>buildings</code> array, and at the final line we return this array from the function. This will set be <code>buildings</code> array in our game <code>state</code>.</p>
<h3 id="heading-how-to-draw-the-the-buildings">How to draw the the buildings</h3>
<p>Now that we have the metadata for the buildings, we can draw them on the screen.</p>
<p>The <code>drawBuildings</code> function is a very simple one. We iterate over the array we just generated and draw a simple rectangle for each. We use the same <code>fillRect</code> method we used to draw the sky. We call this function with the attributes of the building (the Y position is 0 because the building starts at the bottom of the screen).</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBuildings</span>(<span class="hljs-params"></span>) </span>{
  state.buildings.forEach(<span class="hljs-function">(<span class="hljs-params">building</span>) =&gt;</span> {
    ctx.fillStyle = <span class="hljs-string">"#152A47"</span>;
    ctx.fillRect(building.x, <span class="hljs-number">0</span>, building.width, building.height);
  });
}
</code></pre>
<p>Once we are done with this, we should see a line of buildings. The metadata is regenerated every time we start the game. Every time we refresh the browser window, we see a different background.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-16.57.16-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Randomly generated buildings</em></p>
<h3 id="heading-how-to-draw-the-gorillas">‌How to draw the gorillas</h3>
<p>Drawing the gorillas is one of the most complicated and most fun parts of this game. Finally, we are not just drawing rectangles – we're drawing paths.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/gorilla-variations.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>The different variations of the gorillas</em></p>
<p>Gorillas also have different variations depending on the game state. The gorilla looks different while aiming, waiting for the incoming bomb, and celebrating after a successful hit.</p>
<p>In the <code>draw</code> function, we call the <code>drawGorilla</code> function twice. We draw two gorillas: one on the second rooftop and one on the second to last rooftop. They are mostly identical, but they mirror each other while aiming. When the left one is aiming, it raises its left hand, and when the right one is aiming, it raises its right hand.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw</span>(<span class="hljs-params"></span>) </span>{
  ctx.save();
  <span class="hljs-comment">// Flip coordinate system upside down</span>
  ctx.translate(<span class="hljs-number">0</span>, <span class="hljs-built_in">window</span>.innerHeight);
  ctx.scale(<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>);

  <span class="hljs-comment">// Draw scene</span>
  drawBackground();
  drawBuildings();
  drawGorilla(<span class="hljs-number">1</span>);
  drawGorilla(<span class="hljs-number">2</span>);
  drawBomb();

  <span class="hljs-comment">// Restore transformation</span>
  ctx.restore();
}
</code></pre>
<p>‌We'll break down drawing the gorilla into multiple steps as well. We'll use different functions to draw the main body, to draw the arms, and to draw the face of the gorilla.</p>
<p>To make things easier, we'll <code>translate</code> our coordinate system again. We <code>translate</code> the coordinate system to the middle of the rooftop the gorilla stands on. This way we can draw both gorillas the same way. We only need to translate the origin of our coordinate system to a different building.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-16.58.41.png" alt="Image" width="600" height="400" loading="lazy">
<em>Moving the origin of the coordinate system to the top of the building the gorilla stands on</em></p>
<p>‌As an argument, the <code>drawGorilla</code> function receives which <code>player</code> are we currently drawing. To draw the gorilla one on the left, we translate to the top of the second building, and to draw the one on the right we translate to the top of the second to last building.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorilla</span>(<span class="hljs-params">player</span>) </span>{
  ctx.save();
  <span class="hljs-keyword">const</span> building =
    player === <span class="hljs-number">1</span>
      ? state.buildings.at(<span class="hljs-number">1</span>) <span class="hljs-comment">// Second building</span>
      : state.buildings.at(<span class="hljs-number">-2</span>); <span class="hljs-comment">// Second last building</span>

  ctx.translate(building.x + building.width / <span class="hljs-number">2</span>, building.height);

  drawGorillaBody();
  drawGorillaLeftArm(player);
  drawGorillaRightArm(player);
  drawGorillaFace();

  ctx.restore();
}
</code></pre>
<p>‌Because we're using the <code>translate</code> method, this function starts with saving the current coordinate system and ends with restoring it.</p>
<p>Now let's look into the functions that draw the different parts of a gorilla.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaBody</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaLeftArm</span>(<span class="hljs-params">player</span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaRightArm</span>(<span class="hljs-params">player</span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaFace</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<h4 id="heading-how-to-draw-the-body-of-the-gorilla">How to draw the body of the gorilla</h4>
<p>We draw the body of the gorilla as a path. We drew a path in our introduction to drawing on a canvas. We use the <code>moveTo</code> and a bunch of <code>lineTo</code> methods to draw the main part of the gorilla.</p>
<p>We set the fill style to black, and then we begin a path. We move to a coordinate in the middle and then we draw straight lines to draw the silhouette of the gorilla. Once we're finished, we fill the shape with the <code>fill</code> method.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/gorilla-path-fill.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>The body, the head, and the legs of the gorilla. The image on the right shows the path we're filling.</em></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaBody</span>(<span class="hljs-params"></span>) </span>{
  ctx.fillStyle = <span class="hljs-string">"black"</span>;

  ctx.beginPath(); 

  <span class="hljs-comment">// Starting Position</span>
  ctx.moveTo(<span class="hljs-number">0</span>, <span class="hljs-number">15</span>); 

  <span class="hljs-comment">// Left Leg</span>
  ctx.lineTo(<span class="hljs-number">-7</span>, <span class="hljs-number">0</span>);
  ctx.lineTo(<span class="hljs-number">-20</span>, <span class="hljs-number">0</span>); 

  <span class="hljs-comment">// Main Body</span>
  ctx.lineTo(<span class="hljs-number">-13</span>, <span class="hljs-number">77</span>);
  ctx.lineTo(<span class="hljs-number">0</span>, <span class="hljs-number">84</span>);
  ctx.lineTo(<span class="hljs-number">13</span>, <span class="hljs-number">77</span>); 

  <span class="hljs-comment">// Right Leg</span>
  ctx.lineTo(<span class="hljs-number">20</span>, <span class="hljs-number">0</span>);
  ctx.lineTo(<span class="hljs-number">7</span>, <span class="hljs-number">0</span>);

  ctx.fill();
}
</code></pre>
<p>In case you are wondering how I came up with these coordinates, I actually started with an initial sketch with pen and paper. I tried to estimate the coordinates, tried them with code, and then adjusted them until they started getting the right shape. Of course, you might have other methods as well.</p>
<h4 id="heading-how-to-draw-the-arms-of-the-gorilla">How to draw the arms of the gorilla</h4>
<p>While the body was a relatively simple part of the gorilla, the hands are a bit more complicated. They come in different variations and we draw them as a curve.</p>
<p>Let’s start with the left arm. The main part of this is actually only two lines. We'll use the <code>moveTo</code> method to move to the shoulder of the gorilla, then from there, we'll draw the arm as a quadratic curve with the <code>quadraticCurveTo</code> method.</p>
<p>A quadratic curve is a simple curve with one control point. As the curve goes from the starting point (which we'll set with <code>moveTo</code>), the curve bends towards this control point (set as the first two arguments of the <code>quadraticCurveTo</code> method) as it reaches its end position (set as the last two arguments).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-17.33.01-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The different coordaintes of a curve</em></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaLeftArm</span>(<span class="hljs-params">player</span>) </span>{
  ctx.strokeStyle = <span class="hljs-string">"black"</span>;
  ctx.lineWidth = <span class="hljs-number">18</span>;

  ctx.beginPath();
  ctx.moveTo(<span class="hljs-number">-13</span>, <span class="hljs-number">50</span>);

  <span class="hljs-keyword">if</span> (
    (state.phase === <span class="hljs-string">"aiming"</span> &amp;&amp; state.currentPlayer === <span class="hljs-number">1</span> &amp;&amp; player === <span class="hljs-number">1</span>) ||
    (state.phase === <span class="hljs-string">"celebrating"</span> &amp;&amp; state.currentPlayer === player)
  ) {
    ctx.quadraticCurveTo(<span class="hljs-number">-44</span>, <span class="hljs-number">63</span>, <span class="hljs-number">-28</span>, <span class="hljs-number">107</span>);
  } <span class="hljs-keyword">else</span> {
    ctx.quadraticCurveTo(<span class="hljs-number">-44</span>, <span class="hljs-number">45</span>, <span class="hljs-number">-28</span>, <span class="hljs-number">12</span>);
  }

  ctx.stroke();
}
</code></pre>
<p>What makes this function complicated is that it has two variations of the same curve. By default, the hands go down next to the body (second case above).</p>
<p>If we are in the <code>aiming</code> phase, the <code>currentPlayer</code> is player number 1, and we are drawing <code>player</code> 1, then the left hand goes up (first case above). The left hand also goes up in case the we draw the <code>celebrating</code> gorilla (also first case above).</p>
<p>In these cases, we start the from the same point (the curve starts with the same <code>moveTo</code> method), but we set different coordinates for the control point and end point of the curve.</p>
<p>We draw the hands as strokes. So instead of ending the path with the <code>fill</code> method, we use the <code>stroke</code> method instead.</p>
<p>We also set it up differently. Instead of using the <code>fillStyle</code> property, here we set the color with <code>strokeStyle</code> and give thickness to the arm with the <code>lineWidth</code> property.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/gorilla-arm-variations.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>The different arm variations of the gorillas in <code>aiming</code>, <code>in flight</code>, and <code>celebrating</code> phase</em></p>
<p>Drawing the right arm is the same, except the horizontal coordinates and some conditions are flipped. We could merge these two functions, but for clarity, I kept them separate.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaRightArm</span>(<span class="hljs-params">player</span>) </span>{
  ctx.strokeStyle = <span class="hljs-string">"black"</span>;
  ctx.lineWidth = <span class="hljs-number">18</span>;

  ctx.beginPath();
  ctx.moveTo(+<span class="hljs-number">13</span>, <span class="hljs-number">50</span>);

  <span class="hljs-keyword">if</span> (
    (state.phase === <span class="hljs-string">"aiming"</span> &amp;&amp; state.currentPlayer === <span class="hljs-number">2</span> &amp;&amp; player === <span class="hljs-number">2</span>) ||
    (state.phase === <span class="hljs-string">"celebrating"</span> &amp;&amp; state.currentPlayer === player)
  ) {
    ctx.quadraticCurveTo(+<span class="hljs-number">44</span>, <span class="hljs-number">63</span>, +<span class="hljs-number">28</span>, <span class="hljs-number">107</span>);
  } <span class="hljs-keyword">else</span> {
    ctx.quadraticCurveTo(+<span class="hljs-number">44</span>, <span class="hljs-number">45</span>, +<span class="hljs-number">28</span>, <span class="hljs-number">12</span>);
  }

  ctx.stroke();
}
</code></pre>
<p>As a result, our gorillas should start to gain shape. They still don’t have a face, but they have hands now. And to reflect our game state, the one on the left has his hands up, preparing to throw the bomb.</p>
<p>You can test our solution by changing the game state. You can change the <code>currentPlayer</code> and the game <code>phase</code> properties to see the different variations.</p>
<h4 id="heading-how-to-draw-the-face-of-the-gorilla">How to draw the face of the gorilla</h4>
<p>The face of the gorilla comes together from a few straight lines. We'll draw the two eyes and the mouth as a straight line. For each, we'll use pair of <code>moveTo</code> and a <code>lineTo</code> method. Because each line segment uses the same <code>strokeStyle</code> and <code>lineWidth</code>, we can draw them as one single path.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-17.08.58.png" alt="Image" width="600" height="400" loading="lazy">
<em>The finished gorillas</em></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawGorillaFace</span>(<span class="hljs-params"></span>) </span>{
  ctx.strokeStyle = <span class="hljs-string">"lightgray"</span>;
  ctx.lineWidth = <span class="hljs-number">3</span>;

  ctx.beginPath();

  <span class="hljs-comment">// Left Eye</span>
  ctx.moveTo(<span class="hljs-number">-5</span>, <span class="hljs-number">70</span>);
  ctx.lineTo(<span class="hljs-number">-2</span>, <span class="hljs-number">70</span>);

  <span class="hljs-comment">// Right Eye</span>
  ctx.moveTo(<span class="hljs-number">2</span>, <span class="hljs-number">70</span>);
  ctx.lineTo(<span class="hljs-number">5</span>, <span class="hljs-number">70</span>);

  <span class="hljs-comment">// Mouth</span>
  ctx.moveTo(<span class="hljs-number">-5</span>, <span class="hljs-number">62</span>);
  ctx.lineTo(<span class="hljs-number">5</span>, <span class="hljs-number">62</span>);

  ctx.stroke();
}
</code></pre>
<p>With this, we have our finished gorillas with all the variations. There’s only one thing that’s missing from the screen: the banana bomb.</p>
<h3 id="heading-how-to-draw-the-bomb">How to draw the bomb</h3>
<p>Now we'll draw the bomb. The bomb is going to be a simple circle. But before we get to draw it, first we have to figure out where it is.</p>
<h4 id="heading-how-to-initialize-the-bombs-position">How to initialize the bomb’s position</h4>
<p>If we look back at our <code>newGame</code> function, we can see that the metadata of the bomb has a position and a velocity. The position so far is still <code>undefined</code>. Before we get to drawing the bomb, first let’s figure out its position.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Initialize game state</span>
  state = {
    <span class="hljs-attr">phase</span>: <span class="hljs-string">"aiming"</span>, <span class="hljs-comment">// aiming | in flight | celebrating</span>
    <span class="hljs-attr">currentPlayer</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">bomb</span>: {
      <span class="hljs-attr">x</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">y</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
    },
    <span class="hljs-attr">buildings</span>: generateBuildings(),
  };

  initializeBombPosition();

  draw();
}
</code></pre>
<p>‌At the end of the <code>newGame</code> function, we also call the <code>initializeBombPosition</code> function before drawing the scene. Let’s implement this function.</p>
<p>The <code>initializeBombPosition</code> places the bomb in the hand of the gorilla that throws the bomb this turn. We have to call this function after we generate our building metadata because the bomb’s position depends on the position of the gorilla, and that depends on the building it stands on.</p>
<p>First, we look up which <code>building</code> we need to align with. If it’s the first player’s turn, then the bomb has to be in the left hand of the gorilla on the left. And if it’s the second player’s turn, then it has to be in the right hand of the gorilla on the right.</p>
<p>First, we'll calculate the midpoint of the rooftop we need (<code>gorillaX</code> and <code>gorillaY</code>), then offset the position to match the left or right hand of the gorilla.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-17.00.32.png" alt="Image" width="600" height="400" loading="lazy">
<em>Calculating the position of the bomb</em></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initializeBombPosition</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> building =
    state.currentPlayer === <span class="hljs-number">1</span>
      ? state.buildings.at(<span class="hljs-number">1</span>) <span class="hljs-comment">// Second building</span>
      : state.buildings.at(<span class="hljs-number">-2</span>); <span class="hljs-comment">// Second last building</span>

  <span class="hljs-keyword">const</span> gorillaX = building.x + building.width / <span class="hljs-number">2</span>;
  <span class="hljs-keyword">const</span> gorillaY = building.height;

  <span class="hljs-keyword">const</span> gorillaHandOffsetX = state.currentPlayer === <span class="hljs-number">1</span> ? <span class="hljs-number">-28</span> : <span class="hljs-number">28</span>;
  <span class="hljs-keyword">const</span> gorillaHandOffsetY = <span class="hljs-number">107</span>;

  state.bomb.x = gorillaX + gorillaHandOffsetX;
  state.bomb.y = gorillaY + gorillaHandOffsetY;
  state.bomb.velocity.x = <span class="hljs-number">0</span>;
  state.bomb.velocity.y = <span class="hljs-number">0</span>;
}
</code></pre>
<p>We'll also initialize the velocity again here. Later we are going to call this function at the beginning of every turn and then we need it to initialize these values.</p>
<h4 id="heading-how-to-draw-the-bomb-1">How to draw the bomb</h4>
<p>Now that the bomb is in the right place, let’s draw it. Unfortunately, we don’t have a simple fill circle method, as we have in the case of rectangles. We have to draw an <code>arc</code> instead.</p>
<p>An <code>arc</code> method can be called as part of a path. We'll start with the <code>beginPath</code> method and end with the <code>fill</code> method.</p>
<p>The <code>arc</code> method has a lot of properties. This might look a bit scary, but we only need to focus on the first 3 when drawing circles:</p>
<ul>
<li>The first two arguments are <code>x</code> and <code>y</code>, the center coordinates of the arc. We'll set them to match the position of the bomb that we know from the <code>state</code>.</li>
<li>The third argument is the <code>radius</code>. Here we'll set it to 6.</li>
<li>Then the last two arguments are the <code>startAngle</code> and the <code>endAngle</code> of the arc in radians. Because here we want to have a full circle and not an arc, we'll start with 0 and end at a full circle. A full circle in radians is two times Pi.</li>
</ul>
<p>If these last two properties are confusing, don't worry about it. What's important is that when we draw circles, they are always <code>0</code> and <code>2 * Math.Pi</code>.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBomb</span>(<span class="hljs-params"></span>) </span>{
  ctx.fillStyle = <span class="hljs-string">"white"</span>;
  ctx.beginPath();
  ctx.arc(state.bomb.x, state.bomb.y, <span class="hljs-number">6</span>, <span class="hljs-number">0</span>, <span class="hljs-number">2</span> * <span class="hljs-built_in">Math</span>.PI);
  ctx.fill();
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-17.08.58-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The final scene with the bomb in place</em></p>
<p>Now we have everything on the screen. We drew the background, the buildings, the gorillas, and the bomb. But things are not centered on the screen. Let’s fix that.</p>
<h2 id="heading-how-to-fit-the-size-of-the-city-to-the-browser-window">How to Fit the Size of the City to the Browser Window</h2>
<p>So far, we've aligned everything to the left side of the screen. Because the size of the buildings is random, the entire size of the city might be shorter or wider than the size of the browser window. It could even happen that we don’t see the second gorilla because it’s entirely off-screen. Or if the browser window is too wide, then we have a huge gap on the right side of the window.</p>
<p>To fix this, let’s match the size of the city with the width of the browser window.</p>
<p>To do this, let’s add a <code>scale</code> property to our state. In the <code>newGame</code> function, let’s add the <code>scale</code> property to the <code>state</code> object, and call a new function named <code>calculateScale</code> to set its value. This function call has to come after we generate our buildings because the scaling depends on the size of the city. It also has to come before we initialize the bomb position, because later that will depend on the scaling.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Initialize game state</span>
  state = {
    <span class="hljs-attr">scale</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">phase</span>: <span class="hljs-string">"aiming"</span>, <span class="hljs-comment">// aiming | in flight | celebrating</span>
    <span class="hljs-attr">currentPlayer</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">bomb</span>: {
      <span class="hljs-attr">x</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">y</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
    },
    <span class="hljs-attr">buildings</span>: generateBuildings(),
  };

  calculateScale();

  initializeBombPosition();

  draw();
}
</code></pre>
<p>The <code>calculateScale</code> function is relatively simple. We calculate the total width of the city and divide the inner width of the browser window by this value. This will give us a ratio. It will tell how the width of our city relates to the width of the browser window.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-17.42.09-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Calculating the ratio between the size of the city and the size of the browser window</em></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateScale</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> lastBuilding = state.buildings.at(<span class="hljs-number">-1</span>);
  <span class="hljs-keyword">const</span> totalWidthOfTheCity = lastBuilding.x + lastBuilding.width;

  state.scale = <span class="hljs-built_in">window</span>.innerWidth / totalWidthOfTheCity;
}
</code></pre>
<p>Then we have to use this new <code>scale</code> property at a few places. The most important is to change the scaling of the entire game in the <code>draw</code> function.</p>
<p>At the beginning of this function – where we flip the coordinate system – now we have another <code>scale</code> method call that applies this scaling. Because this is at the beginning of the <code>draw</code> function, everything we draw after this will be scaled.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw</span>(<span class="hljs-params"></span>) </span>{
  ctx.save();
  <span class="hljs-comment">// Flip coordinate system upside down</span>
  ctx.translate(<span class="hljs-number">0</span>, <span class="hljs-built_in">window</span>.innerHeight);
  ctx.scale(<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>);
  ctx.scale(state.scale, state.scale);

  <span class="hljs-comment">// Draw scene</span>
  drawBackground();
  drawBuildings();
  drawGorilla(<span class="hljs-number">1</span>);
  drawGorilla(<span class="hljs-number">2</span>);
  drawBomb();

  <span class="hljs-comment">// Restore transformation</span>
  ctx.restore();
}
</code></pre>
<p>‌And finally, we need to adjust how we draw our background. Earlier when we drew the background, we filled the entire screen by setting its width and height based on the window’s <code>innerWidth</code> and <code>innerHeight</code> properties. Now as everything is scaled, they don’t match the size of the browser window anymore. Whenever we use these properties, we need to adjust them by our scaling factor.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBackground</span>(<span class="hljs-params"></span>) </span>{
  ctx.fillStyle = <span class="hljs-string">"#58A8D8"</span>;
  ctx.fillRect(
    <span class="hljs-number">0</span>,
    <span class="hljs-number">0</span>,
    <span class="hljs-built_in">window</span>.innerWidth / state.scale,
    <span class="hljs-built_in">window</span>.innerHeight / state.scale
  );
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-17.45.05-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Matching the size of the city to the size of the browser window</em></p>
<h3 id="heading-how-to-resize-the-window">How to resize the window</h3>
<p>Now the 8 buildings we drew fit nicely with the size of our screen – but what happens if we resize the window? We are going to have the same problems we had before.</p>
<p>As a finishing touch, let’s handle the window <code>resize</code> event. This event handler resizes the canvas element to fit the new window size, recalculates the scaling, readjusts the position of the bomb, and redraws the entire scene based on the new scaling.</p>
<p>Readjusting the bomb position does not make a difference yet, but later we will update this function and it will rely on the new scaling.</p>
<pre><code class="lang-js"><span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"resize"</span>, <span class="hljs-function">() =&gt;</span> {
  canvas.width = <span class="hljs-built_in">window</span>.innerWidth;
  canvas.height = <span class="hljs-built_in">window</span>.innerHeight;
  calculateScale();
  initializeBombPosition();
  draw();
});
</code></pre>
<p>So far we've just drawn a static scene. It’s time to make things interactive.</p>
<h2 id="heading-how-the-gorilla-can-throw-the-bomb">How the Gorilla Can Throw the Bomb</h2>
<p>Throwing the bomb has two different parts. First, we have to aim. We grab the bomb with the mouse and drag it to set the throwing angle and the velocity. While dragging, we need to display the angle and velocity in info panels at the top of the screen. We'll also paint the throw trajectory on the screen. This is the <code>aiming</code> phase.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-16.27.06-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>The <code>aiming</code> phase</em></p>
<p>Then once we release the mouse, the bomb flies across the sky. We are going to have an animation loop that moves the bomb across the sky. This is the <code>in flight</code> phase.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-16.27.09-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>The <code>in flight</code> phase</em></p>
<p>‌In this phase, this animation loop will also include hit detection. We need to know if the bomb hit the enemy, a building, or went out of the screen. If we hit a building, or the bomb goes off-screen, we switch players and get back to the <code>aiming</code> phase again. If we hit the enemy, then we get to the <code>celebrating</code> phase. We then draw the celebrating variant of the current player’s gorilla and we show the congratulations screen.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-16.27.11-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>The <code>celebrating</code> phase</em></p>
<p>Now let’s go through these parts in detail.</p>
<h3 id="heading-how-to-aim">How to aim</h3>
<p>In the <code>aiming</code> phase, we can drag the bomb to set its angle and velocity – like we throw a bird in Angry Birds. We are going to set up event handlers for this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screen-Recording-2024-01-21-at-16.52.12--3--1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Aiming</em></p>
<p>While aiming, we are going to show the current angle and velocity at the top-left or the top-right corner of the screen (depending on the player). We are also going to draw the throw trajectory on the screen to show in which direction the bomb will fly.</p>
<h3 id="heading-info-panels-showing-the-angle-and-the-velocity">Info panels showing the angle and the velocity</h3>
<p>In the original 1991 game, we had to type in the angle and velocity with the keyboard, as it had no mouse support. Here, we are going to aim with the mouse – but we still add the same UI elements on the screen. We'll update these fields as we are moving the mouse while dragging.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-16.02.31-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Info panels in the top left and top right corner of the screen</em></p>
<p>We'll add these info panels in HTML. We could draw these text fields on the canvas as well, but it might be easier to use plain old HTML and CSS.</p>
<p>We'll add two divs, one for player 1 and one for player 2. Both include a header with the player number. We'll also add two paragraphs for the angle and the velocity. The info panels must come after the canvas element because otherwise, the canvas would cover them.</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Gorillas<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"index.css"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"index.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info-left"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Player 1<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Angle: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"angle"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>°<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Velocity: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"velocity"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info-right"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Player 2<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Angle: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"angle"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>°<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Velocity: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"velocity"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>We'll assign two different IDs and appropriate class names. We'll use these names both for styling and for accessing these elements in JavaScript.</p>
<p>In the CSS, we'll move these panels into position and set some styling for the texts. We also want to ensure the user cannot select the text on the screen. Otherwise, you might end up accidentally selecting these text fields while aiming.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">font-family</span>: monospace;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">user-select</span>: none;
  <span class="hljs-attribute">-webkit-user-select</span>: none;
}
<span class="hljs-selector-id">#info-left</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">25px</span>;
}
<span class="hljs-selector-id">#info-right</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">25px</span>;
  <span class="hljs-attribute">text-align</span>: right;
}
</code></pre>
<p>Then finally, in JavaScript we'll add some references to angle and velocity fields somewhere at the beginning of our file.</p>
<pre><code class="lang-js">. . .

<span class="hljs-comment">// Left info panel</span>
<span class="hljs-keyword">const</span> angle1DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-left .angle"</span>);
<span class="hljs-keyword">const</span> velocity1DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-left .velocity"</span>);

<span class="hljs-comment">// Right info panel</span>
<span class="hljs-keyword">const</span> angle2DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-right .angle"</span>);
<span class="hljs-keyword">const</span> velocity2DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-right .velocity"</span>);

. . .
</code></pre>
<p>When we add event handling, we'll update the content of these elements with the mouse movement. For now, let's just leave it like that.</p>
<h3 id="heading-the-grab-area-of-the-bomb">The grab area of the bomb</h3>
<p>We are going to set up event handlers to drag the bomb. But what can we even drag? The whole canvas element is one single element. We could attach an event handler to it, but then we could start dragging the bomb by clicking anywhere on the screen. We don’t want that.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-17.04.21.png" alt="Image" width="600" height="400" loading="lazy">
<em>The grab area of the bomb</em></p>
<p>Instead, we define another element in HTML that will serve as the grab area. We add the <code>bomb-grab-area</code> element. This is not going to include the bomb that we see on screen – that is already part of the canvas – but it will be an invisible area around it, serving as an event target.</p>
<pre><code class="lang-html">  . . .

  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info-left"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Player 1<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Angle: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"angle"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>°<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Velocity: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"velocity"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info-right"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Player 2<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Angle: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"angle"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>°<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Velocity: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"velocity"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"bomb-grab-area"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

  . . .
</code></pre>
<p>We'll also add some styling to it with CSS. We want it to have an absolute position and to be an invisible circle with a slightly bigger radius than the bomb (so it’s easier to grab it). We can set the exact position in JavaScript.</p>
<p>We'll also change the cursor once the mouse is on top of it to <code>grab</code>. While this element is invisible, you can still notice that your mouse is at the right place by the changing cursor.</p>
<pre><code class="lang-css">. . .

<span class="hljs-selector-id">#bomb-grab-area</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">30px</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">30px</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">background-color</span>: transparent;
  <span class="hljs-attribute">cursor</span>: grab;
}
</code></pre>
<p>Then finally in JavaScript, we need to do two things before we set up event handling. First, get a reference to it somewhere at the beginning of the file.</p>
<pre><code class="lang-js">. . .

<span class="hljs-comment">// The bomb's grab area </span>
<span class="hljs-keyword">const</span> bombGrabAreaDOM = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"bomb-grab-area"</span>);

. . .
</code></pre>
<p>Then we need to move it to the correct position. We already have a function that initializes the bomb position on the canvas. We can extend the <code>initializeBombPosition</code> function to position this HTML element as well.</p>
<p>We'll move the grab area to the same place as the bomb on the screen, but its coordinates are a bit different. The content of the canvas is scaled, but any other HTML element is not. We need to adjust the coordinates by the scaling. Also, note that we'll set the <code>grabAreaRadius</code> variable to be half of the size we defined for this element in CSS.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initializeBombPosition</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> building =
    state.currentPlayer === <span class="hljs-number">1</span>
      ? state.buildings.at(<span class="hljs-number">1</span>) <span class="hljs-comment">// Second building</span>
      : state.buildings.at(<span class="hljs-number">-2</span>); <span class="hljs-comment">// Second last building</span>

  <span class="hljs-keyword">const</span> gorillaX = building.x + building.width / <span class="hljs-number">2</span>;
  <span class="hljs-keyword">const</span> gorillaY = building.height;

  <span class="hljs-keyword">const</span> gorillaHandOffsetX = state.currentPlayer === <span class="hljs-number">1</span> ? <span class="hljs-number">-28</span> : <span class="hljs-number">28</span>;
  <span class="hljs-keyword">const</span> gorillaHandOffsetY = <span class="hljs-number">107</span>;

  state.bomb.x = gorillaX + gorillaHandOffsetX;
  state.bomb.y = gorillaY + gorillaHandOffsetY;
  state.bomb.velocity.x = <span class="hljs-number">0</span>;
  state.bomb.velocity.y = <span class="hljs-number">0</span>;

  <span class="hljs-comment">// Initialize the position of the grab area in HTML</span>
  <span class="hljs-keyword">const</span> grabAreaRadius = <span class="hljs-number">15</span>;
  <span class="hljs-keyword">const</span> left = state.bomb.x * state.scale - grabAreaRadius;
  <span class="hljs-keyword">const</span> bottom = state.bomb.y * state.scale - grabAreaRadius;
  bombGrabAreaDOM.style.left = <span class="hljs-string">`<span class="hljs-subst">${left}</span>px`</span>;
  bombGrabAreaDOM.style.bottom = <span class="hljs-string">`<span class="hljs-subst">${bottom}</span>px`</span>;
}
</code></pre>
<p>‌Once we've added this, we won’t see any difference on the screen, because this item is invisible. But once we hover over the bomb, we'll see that the cursor changes to grab.</p>
<h3 id="heading-event-handling">Event handling</h3>
<p>Now with everything set up, we can finally set up the event handling. This is going to be a simple drag-and-drop implementation where we listen to the <code>mousedown</code>, <code>mousemove</code>, and <code>mouseup</code> events.</p>
<p>First, we'll set up some top-level variables somewhere at the beginning of the file. We have a boolean variable that tells us if we are currently dragging or not, and two variables that tell us where the drag started, in case we are dragging.</p>
<pre><code class="lang-js">. . .

let isDragging = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">let</span> dragStartX = <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">let</span> dragStartY = <span class="hljs-literal">undefined</span>;

. . .
</code></pre>
<p>We add the first event handler for the <code>mousedown</code> event on the grab area of the bomb. Being able to set this event handler is the reason we added the <code>bomb-grab-area</code> element earlier.</p>
<p>This event handler only does anything if we are in the <code>aiming</code> phase. If that is true, we set the <code>isDragging</code> variable to true, save the current mouse position, and set the mouse cursor permanently to <code>grabbing</code> (so that the cursor stays as grabbing even if we leave the grab area).</p>
<pre><code class="lang-js">bombGrabAreaDOM.addEventListener(<span class="hljs-string">"mousedown"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">e</span>) </span>{
  <span class="hljs-keyword">if</span> (state.phase === <span class="hljs-string">"aiming"</span>) {
    isDragging = <span class="hljs-literal">true</span>;

    dragStartX = e.clientX;
    dragStartY = e.clientY;

    <span class="hljs-built_in">document</span>.body.style.cursor = <span class="hljs-string">"grabbing"</span>;
  }
});
</code></pre>
<p>Then we'll add an event handler for the <code>mousemove</code> event. Note that now the event target is not the grab area of the bomb, but the <code>window</code> object. This is because while we are dragging we can easily move the mouse outside the grab area – or even outside the browser window – and we still want this event handler to work.</p>
<p>This event handler first checks if we are currently dragging. If not, then it doesn’t do anything. If we are dragging, then it calculates the delta of the mouse position since the <code>mousedown</code> event and sets it as the velocity of the bomb.</p>
<pre><code class="lang-js"><span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"mousemove"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">e</span>) </span>{
  <span class="hljs-keyword">if</span> (isDragging) {
    <span class="hljs-keyword">let</span> deltaX = e.clientX - dragStartX;
    <span class="hljs-keyword">let</span> deltaY = e.clientY - dragStartY;

    state.bomb.velocity.x = -deltaX;
    state.bomb.velocity.y = +deltaY;
    setInfo(deltaX, deltaY);

    draw();
  }
});
</code></pre>
<p>When we release the mouse, we want the bomb to move the opposite way as we drag the mouse, so we need to set the horizontal and vertical velocity with a negative sign. But then with a double twist, we switch back the vertical velocity (the Y coordinate), to have a positive sign because we flipped the coordinate system.</p>
<p>Event handlers still assume that the coordinate system grows downwards. Within the canvas, it goes in the opposite direction.</p>
<p>This event handler also calls a utility function to show the angle and the velocity at the info panels we just added in HTML. Then we call the <code>draw</code> function again. For now, calling the <code>draw</code> function here does not change anything on the screen, but soon we will update the <code>drawBomb</code> function to draw the throwing trajectory.</p>
<p>The <code>setInfo</code> button updates the UI elements we defined in HTML. We already have references to these elements at the top of our file, so here we only need to update their content as we drag the bomb.</p>
<pre><code class="lang-js">. . .

<span class="hljs-comment">// Left info panel</span>
<span class="hljs-keyword">const</span> angle1DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-left .angle"</span>);
<span class="hljs-keyword">const</span> velocity1DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-left .velocity"</span>);

<span class="hljs-comment">// Right info panel</span>
<span class="hljs-keyword">const</span> angle2DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-right .angle"</span>);
<span class="hljs-keyword">const</span> velocity2DOM = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#info-right .velocity"</span>);

. . .
</code></pre>
<p>‌But there is a bit of a complication. From the <code>mousemove</code> event, we got the vertical and horizontal components of the velocity. But on the UI we want to display the angle and the total velocity of the throw. We need to use some trigonometry to calculate these values.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-20.56.06-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Calculating the total <code>velocity</code> and the <code>angle</code> from the horizontal and verical movement of the mouse</em></p>
<p>‌For the velocity, we'll calculate the <code>hypotenuse</code> of an imaginary triangle made up of the horizontal and vertical components of the velocity. For the <code>angle</code>, we'll use the arc sine function (the inverse of the sine function). We'll also make sure to update the correct info panel depending on whose turn it is, and we'll round the values.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setInfo</span>(<span class="hljs-params">deltaX, deltaY</span>) </span>{
  <span class="hljs-keyword">const</span> hypotenuse = <span class="hljs-built_in">Math</span>.sqrt(deltaX ** <span class="hljs-number">2</span> + deltaY ** <span class="hljs-number">2</span>);
  <span class="hljs-keyword">const</span> angleInRadians = <span class="hljs-built_in">Math</span>.asin(deltaY / hypotenuse);
  <span class="hljs-keyword">const</span> angleInDegrees = (angleInRadians / <span class="hljs-built_in">Math</span>.PI) * <span class="hljs-number">180</span>;

  <span class="hljs-keyword">if</span> (state.currentPlayer === <span class="hljs-number">1</span>) {
    angle1DOM.innerText = <span class="hljs-built_in">Math</span>.round(angleInDegrees);
    velocity1DOM.innerText = <span class="hljs-built_in">Math</span>.round(hypotenuse);
  } <span class="hljs-keyword">else</span> {
    angle2DOM.innerText = <span class="hljs-built_in">Math</span>.round(angleInDegrees);
    velocity2DOM.innerText = <span class="hljs-built_in">Math</span>.round(hypotenuse);
  }
}
</code></pre>
<p>At this point, while the scene should stay the same as it is, the values in the left info panel should update as we drag.</p>
<p>Finally, we'll add the event handler for the <code>mouseup</code> event, again on the <code>window</code> object. It only does anything if we are currently dragging. Then it notes that we are not dragging anymore, changes the cursor back to default, and calls the <code>throwBomb</code> function.</p>
<pre><code class="lang-js"><span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"mouseup"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (isDragging) {
    isDragging = <span class="hljs-literal">false</span>;

    <span class="hljs-built_in">document</span>.body.style.cursor = <span class="hljs-string">"default"</span>;

    throwBomb();
  }
});
</code></pre>
<p>The <code>throwBomb</code> function switches the game phase to <code>in flight</code> and kicks off the animation of the bomb. We are going to cover this function in the next chapter, but before we get there, let's update one more thing.</p>
<h3 id="heading-how-to-draw-the-throw-trajectory">How to draw the throw trajectory</h3>
<p>While we are aiming, we can also draw the throwing trajectory. The throwing trajectory is the visualized form of the velocity and the angle.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-16.27.06-5.png" alt="Image" width="600" height="400" loading="lazy">
<em>The throwing trajectory shows the angle and velocity in a visual form</em></p>
<p>‌To do this, let’s go back to the <code>drawBomb</code> function and make some changes. If we are in the <code>aiming</code> phase, we'll draw a straight line from the center of the bomb to the velocity.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawBomb</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Draw throwing trajectory</span>
  <span class="hljs-keyword">if</span> (state.phase === <span class="hljs-string">"aiming"</span>) {
    ctx.strokeStyle = <span class="hljs-string">"rgba(255, 255, 255, 0.7)"</span>;
    ctx.setLineDash([<span class="hljs-number">3</span>, <span class="hljs-number">8</span>]);
    ctx.lineWidth = <span class="hljs-number">3</span>;

    ctx.beginPath();
    ctx.moveTo(state.bomb.x, state.bomb.y);
    ctx.lineTo(
      state.bomb.x + state.bomb.velocity.x,
      state.bomb.y + state.bomb.velocity.y
    );
    ctx.stroke();
  }

  <span class="hljs-comment">// Draw circle</span>
  ctx.fillStyle = <span class="hljs-string">"white"</span>;
  ctx.beginPath();
  ctx.arc(state.bomb.x, state.bomb.y, <span class="hljs-number">6</span>, <span class="hljs-number">0</span>, <span class="hljs-number">2</span> * <span class="hljs-built_in">Math</span>.PI);
  ctx.fill();
}
</code></pre>
<p>We'll draw this line as a path, as we did before. We'll start it with the <code>beginPath</code> method and end it with the <code>stroke</code> method. In between we'll use the <code>moveTo</code> and the <code>lineTo</code> method.</p>
<p>There’s one new thing here though: the <code>setLineDash</code> method. With this setting, we can draw a dotted line. We'll set that after every 3 pixels of line, and we want to have 8 pixels of gap. The 3-pixel dash matches the <code>lineWidth</code>, so the dashes will look like dots.</p>
<p>Now we've finished everything about the <code>aiming</code> phase. It’s time to throw the bomb.</p>
<h2 id="heading-how-to-animate-the-incoming-bomb">How to Animate the Incoming Bomb</h2>
<p>Once we release the mouse after the aim, the bomb flies across the sky. We'll add an animation loop that moves the bomb, calculates its position with every animation cycle, and checks if we hit something.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screen-Recording-2024-01-21-at-17.06.18-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Animating the bomb</em></p>
<p>Earlier, we saw that the event handler of the <code>mouseup</code> event ends with calling the <code>throwBomb</code> function. Let's implement this function.</p>
<p>This function first kicks off the <code>in flight</code> phase. Then it resets the <code>previousAnimationTimestamp</code> variable. This is a new utility variable needed for the animation loop. We'll cover it in a second. Then we start the animation loop, by calling <code>requestAnimationFrame</code> with the <code>animate</code> function as an argument. Let’s dig deeper into this animate function.</p>
<pre><code class="lang-js">. . . 

let previousAnimationTimestamp = <span class="hljs-literal">undefined</span>; 

. . .

function throwBomb() {
  state.phase = <span class="hljs-string">"in flight"</span>;
  previousAnimationTimestamp = <span class="hljs-literal">undefined</span>;
  requestAnimationFrame(animate);
}
</code></pre>
<h3 id="heading-the-animation-loop">The animation loop</h3>
<p>The <code>animate</code> function moves the bomb and calls the <code>draw</code> function to repaint the screen over and over again until we hit the enemy, a building, or the bomb goes off the screen.</p>
<p>By calling this function with <code>requestAnimationFrame</code> the way you see below, the animate function will run around 60 times every second. The constant repainting of the screen will appear as a continuous animation. Because this function is running so frequently, we're only moving the bomb little by little each time.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Gorillas-Keynote.006.png" alt="Image" width="600" height="400" loading="lazy">
<em>The animation loop</em></p>
<p>‌This function keeps track of how much time has passed since its last call. We are going to use this information to precisely calculate how much should we move the bomb.</p>
<p>Functions invoked with the <code>requestAnimationFrame</code> function receive the current <code>timestamp</code> as an attribute. At the end of every cycle, we save this <code>timestamp</code> value into the <code>previousAnimationTimestamp</code> variable, so that in the next cycle we can calculate how much time has passed between two cycles. In the code below, this is the <code>elapsedTime</code> variable.</p>
<p>The first cycle is an exception because, at that point, we didn’t have a previous cycle yet. At the beginning of each throw, the value of <code>previousAnimationTimestamp</code> is <code>undefined</code> (we made sure it's <code>undefined</code> in the <code>throwBomb</code> function). In this case, we skip a render and we'll only render the scene on the second cycle, where we already have all the values we need. This is the part at the very beginning of the <code>animate</code> function.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params">timestamp</span>) </span>{
  <span class="hljs-keyword">if</span> (previousAnimationTimestamp === <span class="hljs-literal">undefined</span>) {
    previousAnimationTimestamp = timestamp;
    requestAnimationFrame(animate);
    <span class="hljs-keyword">return</span>;
  }
  <span class="hljs-keyword">const</span> elapsedTime = timestamp - previousAnimationTimestamp;

  moveBomb(elapsedTime); 

  <span class="hljs-comment">// Hit detection</span>
  <span class="hljs-keyword">let</span> miss = <span class="hljs-literal">false</span>; <span class="hljs-comment">// Bomb hit a building or got out of the screen</span>
  <span class="hljs-keyword">let</span> hit = <span class="hljs-literal">false</span>; <span class="hljs-comment">// Bomb hit the enemy</span>

  <span class="hljs-comment">// Handle the case when we hit a building or the bomb got off-screen</span>
  <span class="hljs-keyword">if</span> (miss) {
    <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">return</span>;
  } <span class="hljs-comment">// Handle the case when we hit the enemy</span>
  <span class="hljs-keyword">if</span> (hit) {
    <span class="hljs-comment">// ... </span>
    <span class="hljs-keyword">return</span>;
  }

  draw();

  <span class="hljs-comment">// Continue the animation loop</span>
  previousAnimationTimestamp = timestamp;
  requestAnimationFrame(animate);
}
</code></pre>
<p>‌Inside the function, we are going to move the bomb every cycle by calling the <code>moveBomb</code> function. We'll pass on the <code>elapsedTime</code> variable so it can precisely calculate by how much should it move the bomb.</p>
<p>In every cycle, we also detect if we hit an enemy, or if we missed and the bomb hit a building or got off screen. If none of that happens, then we need to repaint the scene and request another animation frame to continue the animation loop. But if we missed the enemy or got a hit, then we'll stop the animation loop by returning from the function. By returning early from the function, we never reach the last line, which would trigger the next animation cycle. The loop stops.</p>
<h3 id="heading-how-to-move-the-bomb">How to move the bomb</h3>
<p>We move the bomb by calling the <code>moveBomb</code> function in the animation loop. This function calculates the new <code>x</code> and <code>y</code> position of the bomb in every cycle.</p>
<p>The new <code>x</code> and <code>y</code> values are calculated based on the velocity. But the vertical and horizontal velocity can be a relatively high number. </p>
<p>We don’t want the bomb to cross the screen with lightning speed, so to slow down the movement we'll multiply the values by a very small number. This <code>multiplier</code> also takes into consideration the elapsed time, so the animation will look consistent even if the animation cycles are not triggered at equal intervals.</p>
<p>With every cycle, the velocity of the bomb is also adjusted by gravity. We'll gradually increase the motion of the bomb towards the ground. We'll also adjust the vertical velocity by a small constant that also depends on the time passed.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">moveBomb</span>(<span class="hljs-params">elapsedTime</span>) </span>{
  <span class="hljs-keyword">const</span> multiplier = elapsedTime / <span class="hljs-number">200</span>; <span class="hljs-comment">// Adjust trajectory by gravity</span>

  state.bomb.velocity.y -= <span class="hljs-number">20</span> * multiplier; <span class="hljs-comment">// Calculate new position</span>

  state.bomb.x += state.bomb.velocity.x * multiplier;
  state.bomb.y += state.bomb.velocity.y * multiplier;
}
</code></pre>
<h3 id="heading-hit-detection">Hit detection</h3>
<p>Our animation loop keeps moving the bomb to infinity. But once we hit a building, the enemy, or the bomb got off-screen, we should stop this motion.</p>
<p>We need to detect these cases and handle them appropriately. If the bomb gets off-screen or we hit a building we should move on to the next player and get back to the <code>aiming</code> phase. In case we hit the enemy, we should move on to the <code>celebrating</code> phase and announce the winner.</p>
<p>In all these cases, we can stop the loop by returning early from the <code>animate</code> function. In these cases, the animate function does not reach its last line which would trigger another animation cycle.</p>
<h3 id="heading-how-to-improve-hit-detection-precision">How to improve hit detection precision</h3>
<p>While we already slowed down the movement of the bomb to have a nice animation on screen, the bomb can still be a bit too fast for hit detection. </p>
<p>When the bomb is in flight, it can move by more than 10 pixels at a time. If we do hit detections only once per animation cycle, then we are completely blind to what happens during these 10-pixel movements. The bomb can easily go through parts of the gorilla without us noticing that we should have had a hit, or go through the corner of a building without any impact.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-23.19.47-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>We need to break down each animation cycle into multiple segments to improve hit detection</em></p>
<p>To solve this, we are not only going to do hit detection once per every animation cycle, but multiple times. We'll break down every movement into smaller segments, and with every tiny movement, we need to check if we have a hit. </p>
<p>We'll still render the scene once per animation cycle, but before calling the <code>draw</code> function, we'll break down the movement into 10 segments with a loop.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-17.10.02.png" alt="Image" width="600" height="400" loading="lazy">
<em>Break down movement and hit detection to 10 segments in each animation cycle</em></p>
<p>If we break down each animation cycle into ten segments, that also means that now we call the <code>moveBomb</code> function ten times more. We need to slow it down even more. Because this function moves the bomb according to the time passed, it’s enough if we divide its time attribute also by ten.</p>
<p>This way the bomb moves across the sky with the same speed, but we have ten times more precision with hit detection. In the example below, we wrap calling the <code>moveBomb</code> function and the hit detection logic into a for loop.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params">timestamp</span>) </span>{
  <span class="hljs-keyword">if</span> (!previousAnimationTimestamp) {
    previousAnimationTimestamp = timestamp;
    requestAnimationFrame(animate);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">const</span> elapsedTime = timestamp - previousAnimationTimestamp;

  <span class="hljs-keyword">const</span> hitDetectionPrecision = <span class="hljs-number">10</span>;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; hitDetectionPrecision; i++) {
    moveBomb(elapsedTime / hitDetectionPrecision);

    <span class="hljs-comment">// Hit detection</span>
    <span class="hljs-keyword">const</span> miss = checkFrameHit() || checkBuildingHit();
    <span class="hljs-keyword">const</span> hit = checkGorillaHit();

    <span class="hljs-comment">// Handle the case when we hit a building or the bomb got off-screen</span>
    <span class="hljs-keyword">if</span> (miss) {
      <span class="hljs-comment">// ...</span>
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Handle the case when we hit the enemy</span>
    <span class="hljs-keyword">if</span> (hit) {
      <span class="hljs-comment">// ...</span>
      <span class="hljs-keyword">return</span>;
    }
  }

  draw();

  <span class="hljs-comment">// Continue the animation loop</span>
  previousAnimationTimestamp = timestamp;
  requestAnimationFrame(animate);
}
</code></pre>
<p>In the example above we've also introduced some utility functions for hit detection. In the next steps let’s implement these functions.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkFrameHit</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkBuildingHit</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkGorillaHit</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<h3 id="heading-how-to-detect-if-the-bomb-is-off-the-screen">How to detect if the bomb is off the screen</h3>
<p>We've missed the target if we reach the edge of the screen or if we hit a building. Checking if we've reached the edge of the screen is a very easy thing to do.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-17.07.56.png" alt="Image" width="600" height="400" loading="lazy">
<em>Checking if the bomb went off-screen</em></p>
<p>‌We only need to check if the bomb went out on the left side, the bottom, or the right side of the screen. If the bomb goes through the top of the screen, that’s okay. Gravity will eventually pull it back. </p>
<p>Note that because of the scaling, the right side of the screen is not the same as the window’s <code>innerWidth</code> value. We have to adjust that by the scaling factor.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkFrameHit</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (
    state.bomb.y &lt; <span class="hljs-number">0</span> ||
    state.bomb.x &lt; <span class="hljs-number">0</span> ||
    state.bomb.x &gt; <span class="hljs-built_in">window</span>.innerWidth / state.scale
  ) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; <span class="hljs-comment">// The bomb is off-screen</span>
  }
}
</code></pre>
<p>If the bomb went out of the screen, we return <code>true</code> to signal to the <code>animate</code> function that we can stop the animation loop.</p>
<h3 id="heading-how-to-detect-if-the-bomb-hit-a-building">How to detect if the bomb hit a building</h3>
<p>We also miss the target if the bomb hits a building. We can iterate over the buildings array and check if any side of the bomb is within the rectangle of the building. We need to check that:</p>
<ul>
<li>The right side of the bomb is on the right of the left side of the building,</li>
<li>The left side of the bomb is on the left of the right side of the building,</li>
<li>And that the bottom of the bomb is below the top of the building.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/detecting-bomb-building.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Detecting if the bomb hit a building</em></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkBuildingHit</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; state.buildings.length; i++) {
    <span class="hljs-keyword">const</span> building = state.buildings[i];
    <span class="hljs-keyword">if</span> (
      state.bomb.x + <span class="hljs-number">4</span> &gt; building.x &amp;&amp;
      state.bomb.x - <span class="hljs-number">4</span> &lt; building.x + building.width &amp;&amp;
      state.bomb.y - <span class="hljs-number">4</span> &lt; <span class="hljs-number">0</span> + building.height
    ) {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; <span class="hljs-comment">// Building hit</span>
    }
  }
}
</code></pre>
<p>If all this is true, then we know that the bomb hit the building. If we get a hit, the function ends with returning true. This will also signal to the <code>animate</code> function that we can stop the animation loop.</p>
<h3 id="heading-how-to-detect-if-the-bomb-hit-the-enemy">How to detect if the bomb hit the enemy</h3>
<p>Now that we can detect if the bomb went off-screen and we have hit detection for buildings, it’s time to detect if we hit the enemy. </p>
<p>We are going to have a different approach this time. Detecting if we got a hit with traditional geometric calculations would be much more complicated, because the gorillas are not built with basic rectangles and circles.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-23.49.34-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of the game once we hit the enemy</em></p>
<p>Instead, we can use the <code>isPointInPath</code> method that conveniently tells us if a point – in this case, the center of the bomb – is within a path. </p>
<p>This method tells us if a point is within the current or previous path. So before calling the method, we need to recreate the gorilla. The gorilla is not only one single path, so we have to test against the most relevant path, the main body of the gorilla.</p>
<p>Conveniently – and with some smart planning – we already have a function that paints the body of the gorilla as a single path. We call the <code>drawGorillaBody</code> function right before <code>isPointInPath</code>. But the <code>drawGorillaBody</code> function paints with coordinates relative to the rooftop of a building, so before calling it, we need to translate the coordinate system. </p>
<p>Depending on the current player, we'll calculate which building the enemy stands on, and translate the coordinate system to top of that. Because of this translation, we also use the <code>save</code> and <code>restore</code> methods.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkGorillaHit</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> enemyPlayer = state.currentPlayer === <span class="hljs-number">1</span> ? <span class="hljs-number">2</span> : <span class="hljs-number">1</span>;
  <span class="hljs-keyword">const</span> enemyBuilding =
    enemyPlayer === <span class="hljs-number">1</span>
      ? state.buildings.at(<span class="hljs-number">1</span>) <span class="hljs-comment">// Second building</span>
      : state.buildings.at(<span class="hljs-number">-2</span>); <span class="hljs-comment">// Second last building</span>

  ctx.save();

  ctx.translate(
    enemyBuilding.x + enemyBuilding.width / <span class="hljs-number">2</span>,
    enemyBuilding.height
  );

  drawGorillaBody();
  <span class="hljs-keyword">let</span> hit = ctx.isPointInPath(state.bomb.x, state.bomb.y);

  drawGorillaLeftArm(enemyPlayer);
  hit ||= ctx.isPointInStroke(state.bomb.x, state.bomb.y);

  drawGorillaRightArm(enemyPlayer);
  hit ||= ctx.isPointInStroke(state.bomb.x, state.bomb.y);

  ctx.restore();

  <span class="hljs-keyword">return</span> hit;
}
</code></pre>
<p>Similarly, we can also detect if we hit one of the arms of the gorilla. The arms are drawn as a stroke, so in this case instead of <code>isPointInPath</code> we'll use the <code>isPointInStroke</code> method. This detection would not have worked if we did not increase the hit detection precision earlier, because the bomb could easily jump over the arm.</p>
<p>With this function, we have every piece of the hit detection. The animation loop stops in case we hit a building, the enemy, or the bomb gets off-screen. It’s time to handle what’s next in these cases.</p>
<h3 id="heading-how-to-handle-the-result-of-hit-detection">How to handle the result of hit detection</h3>
<p>Once we have proper hit detection, it's time to finally handle the cases when we hit a building, the enemy, or the bomb goes off-screen. We'll update the <code>animate</code> function one last time. The only thing new below is the code block of the two <code>if</code> statements inside the loop.</p>
<p>If we hit the edge of the screen or a building, that means we've missed the target. In this case, we'll switch players and get back to the <code>aiming</code> phase. We'll also re-initialize the bomb position to move it to the new player’s hand.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-20-at-00.39.59-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Once we've missed the enemy, we switch players</em></p>
<p>Then we'll simply call the <code>draw</code> function to handle the rest. The beauty of this structure is that we only need to change the game <code>state</code> and the <code>draw</code> function can repaint the whole scene according to it.</p>
<p>Then we'll <code>return</code> from the animate function to stop the animation loop. The event handlers are active again (because we are back in the <code>aiming</code> phase), and the next player can take a shot.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params">timestamp</span>) </span>{
  <span class="hljs-keyword">if</span> (!previousAnimationTimestamp) {
    previousAnimationTimestamp = timestamp;
    requestAnimationFrame(animate);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">const</span> elapsedTime = timestamp - previousAnimationTimestamp;

  <span class="hljs-keyword">const</span> hitDetectionPrecision = <span class="hljs-number">10</span>;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; hitDetectionPrecision; i++) {
    moveBomb(elapsedTime / hitDetectionPrecision); <span class="hljs-comment">// Hit detection</span>

    <span class="hljs-keyword">const</span> miss = checkFrameHit() || checkBuildingHit();
    <span class="hljs-keyword">const</span> hit = checkGorillaHit();

    <span class="hljs-comment">// Handle the case when we hit a building or the bomb got off-screen</span>
    <span class="hljs-keyword">if</span> (miss) {
      state.currentPlayer = state.currentPlayer === <span class="hljs-number">1</span> ? <span class="hljs-number">2</span> : <span class="hljs-number">1</span>; <span class="hljs-comment">// Switch players</span>
      state.phase = <span class="hljs-string">"aiming"</span>;
      initializeBombPosition();
      draw();
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Handle the case when we hit the enemy</span>
    <span class="hljs-keyword">if</span> (hit) {
      state.phase = <span class="hljs-string">"celebrating"</span>;
      announceWinner();
      draw();
      <span class="hljs-keyword">return</span>;
    }
  }

  draw();

  <span class="hljs-comment">// Continue the animation loop</span>
  previousAnimationTimestamp = timestamp;
  requestAnimationFrame(animate);
}
</code></pre>
<p>If we hit the enemy, we need to switch to the <code>celebrating</code> phase. We'll announce the winner with a function that we are about to cover in the next section. Then we repaint the scene with a celebrating gorilla and return to stop the animation loop.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-19-at-23.49.34-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Once we hit the enemy the <code>draw</code> function repaints the scene with a celebrating gorilla</em></p>
<p>‌To make sure our code does not throw an error, let's add a placeholder for the <code>announceWinner</code> function.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">announceWinner</span>(<span class="hljs-params"></span>) </span>{ 
    <span class="hljs-comment">// ... </span>
}
</code></pre>
<h3 id="heading-how-to-announce-the-winner">How to announce the winner</h3>
<p>Once we hit the enemy, we should announce the winner.  For this, we'll add another info panel in HTML that includes who won the game and a restart button. At the bottom of the now-finished HTML file, you can find the <code>congratulations</code> panel.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-21-at-16.27.11-6.png" alt="Image" width="600" height="400" loading="lazy">
<em>The <code>congratulations</code> panel announcing the winner</em></p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Gorillas<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"index.css"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"index.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info-left"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Player 1<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Angle: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"angle"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>°<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Velocity: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"velocity"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info-right"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Player 2<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Angle: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"angle"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>°<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Velocity: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"velocity"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"bomb-grab-area"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"congratulations"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"winner"</span>&gt;</span>?<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span> won!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"new-game"</span>&gt;</span>New Game<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This panel is hidden by default and it only shows up at the end of the game. When it shows up, it should be in the middle of the screen. We'll update our CSS file according to this. </p>
<p>Note that we added <code>display: flex</code> to the body element with a few more properties to center everything on the screen. Then we set <code>position: absolute</code> for the <code>congratulations</code> element and hide it by default.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">font-family</span>: monospace;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">user-select</span>: none;
  <span class="hljs-attribute">-webkit-user-select</span>: none;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
}

<span class="hljs-selector-id">#info-left</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">25px</span>;
}

<span class="hljs-selector-id">#info-right</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">25px</span>;
  <span class="hljs-attribute">text-align</span>: right;
}

<span class="hljs-selector-id">#bomb-grab-area</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">30px</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">30px</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">background-color</span>: transparent;
  <span class="hljs-attribute">cursor</span>: grab;
}

<span class="hljs-selector-id">#congratulations</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">visibility</span>: hidden;
}
</code></pre>
<p>Then finally we can use this panel to announce the winner once the game ends in JavaScript. We already call the <code>announceWinner</code> function in our <code>animate</code> function so it’s time to implement it.</p>
<p>First, somewhere at the beginning of the file, we'll set up some references for the <code>congratulations</code> panel itself and the <code>winner</code> field.</p>
<pre><code class="lang-js">. . .

<span class="hljs-comment">// Congratulations panel</span>
<span class="hljs-keyword">const</span> congratulationsDOM = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"congratulations"</span>);
<span class="hljs-keyword">const</span> winnerDOM = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"winner"</span>);

. . .
</code></pre>
<p>Then in the <code>announceWinner</code> function, we'll set the content of the winner field to say the current player and show the congratulations panel.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">announceWinner</span>(<span class="hljs-params"></span>) </span>{
  winnerDOM.innerText = <span class="hljs-string">`Player <span class="hljs-subst">${state.currentPlayer}</span>`</span>;
  congratulationsDOM.style.visibility = <span class="hljs-string">"visible"</span>;
}
</code></pre>
<p>With this piece, we finally can play the game until the end. We can aim, the bomb flies across the sky, and the gorillas take turns until one of them wins the game. The only missing piece is resetting the game for another round.</p>
<h3 id="heading-how-to-reset-for-another-round">How to reset for another round</h3>
<p>As a final finishing touch, let’s add an event handler for the 'New Game' button on our congratulations panel to be able to reset the game. We've already added a button with the ID <code>new-game</code> in our HTML.</p>
<p>In JavaScript first, we'll create a reference to this button and then add an event handler for it. This event handler simply calls the <code>newGame</code> function.</p>
<pre><code class="lang-js">. . .

<span class="hljs-comment">// Congratulations panel</span>
<span class="hljs-keyword">const</span> congratulationsDOM = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"congratulations"</span>);
<span class="hljs-keyword">const</span> winnerDOM = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"winner"</span>);
<span class="hljs-keyword">const</span> newGameButtonDOM = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"new-game"</span>);

. . .

newGameButtonDOM.addEventListener(<span class="hljs-string">"click"</span>, newGame);
</code></pre>
<p>The <code>newGame</code> function should reset everything and generate a new level so we can start a new game. At this point, though, our <code>newGame</code> function does not reset everything. It does not reset the HTML elements we introduced in the meantime.</p>
<p>As a very last step, we'll make sure that the <code>congratulations</code> element is hidden once we start a new game, and reset the angle and velocity values in the left and right info panels to 0.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newGame</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Initialize game state</span>
  state = {
    <span class="hljs-attr">scale</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">phase</span>: <span class="hljs-string">"aiming"</span>, <span class="hljs-comment">// aiming | in flight | celebrating</span>
    <span class="hljs-attr">currentPlayer</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">bomb</span>: {
      <span class="hljs-attr">x</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">y</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
    },
    <span class="hljs-attr">buildings</span>: generateBuildings(),
  };

  calculateScale();

  initializeBombPosition();

  <span class="hljs-comment">// Reset HTML elements</span>
  congratulationsDOM.style.visibility = <span class="hljs-string">"hidden"</span>;
  angle1DOM.innerText = <span class="hljs-number">0</span>;
  velocity1DOM.innerText = <span class="hljs-number">0</span>;
  angle2DOM.innerText = <span class="hljs-number">0</span>;
  velocity2DOM.innerText = <span class="hljs-number">0</span>;

  draw();
}
</code></pre>
<h2 id="heading-next-steps">Next Steps</h2>
<p>While we now have a full two-player game, we can do a lot more. On <a target="_blank" href="https://youtu.be/2q5EufbUEQk?si=9IlOu7Ds-UeeNbIh">YouTube</a> I also cover how to make the buildings destructible, how to animate the hand of the gorilla to follow the drag movement while aiming, and we spend more time adding detailed graphics. There's also a whole chapter on how to add computer logic to the game, so that you can play against the computer. </p>
<p>Check it out to learn more:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/2q5EufbUEQk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-subscribe-to-my-channel-for-more-javascript-game-development-tutorials">Subscribe to my channel for more JavaScript game development tutorials:</h3>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/undefined" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
