<?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[ reactthreefiber - 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[ reactthreefiber - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 26 Jun 2026 22:47:32 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/reactthreefiber/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <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>
        
    </channel>
</rss>
