<?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[ ThreeJS - 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[ ThreeJS - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 18 May 2026 22:34:59 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/threejs/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Create a Cute Room Portfolio with Three.js, Blender, JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ Learn how to use Three.js and Blender to design a stunning and interactive 3D portfolio! We just posted a course on the freeCodeCamp.org YouTube channel that will take you from the foundational concepts of 3D modeling in Blender to creating a fully f... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-a-cute-room-portfolio-with-threejs-blender-javascript/</link>
                <guid isPermaLink="false">691db3b57953797388329ea6</guid>
                
                    <category>
                        <![CDATA[ Blender ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ThreeJS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 19 Nov 2025 12:10:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763546767762/f0471b3d-33da-4cec-9d70-f2dd795d687e.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Learn how to use Three.js and Blender to design a stunning and interactive 3D portfolio!</p>
<p>We just posted a course on the freeCodeCamp.org YouTube channel that will take you from the foundational concepts of 3D modeling in Blender to creating a fully functional, award-worthy portfolio website using Three.js.</p>
<p>You'll master essential techniques like proper quad topology for clean models and texture baking to optimize your scene for the web.</p>
<p>By the end, you'll have a delightful, cute, and professionally deployed Three.js project featuring all your hand-modeled creations. Andrew Woan developed this course.</p>
<p>Here are all the sections in this course:</p>
<ul>
<li><p>Introduction and Project Demo</p>
</li>
<li><p>What we will cover and not cover</p>
</li>
<li><p>What to expect for course structure</p>
</li>
<li><p>Prerequisites</p>
</li>
<li><p>A brief look at quad topology</p>
</li>
<li><p>Modeling room base and a look at blocking out</p>
</li>
<li><p>Modeling standing desk</p>
</li>
<li><p>Modeling monitor rise</p>
</li>
<li><p>Modeling computer</p>
</li>
<li><p>Modeling table pad</p>
</li>
<li><p>Modeling computer keyboard</p>
</li>
<li><p>Modeling pencils and pencil holder</p>
</li>
<li><p>Modeling flower pot and flowers</p>
</li>
<li><p>Modeling small motivational quote thing</p>
</li>
<li><p>Modeling computer speaker</p>
</li>
<li><p>Modeling headphones and headphone stand</p>
</li>
<li><p>Adjusting room</p>
</li>
<li><p>Modeling computer</p>
</li>
<li><p>Modeling cute character Mr. Whiskers</p>
</li>
<li><p>Modeling coffee mug</p>
</li>
<li><p>Modeling chair</p>
</li>
<li><p>Modeling drawer</p>
</li>
<li><p>Modeling book</p>
</li>
<li><p>Modeling flower basket</p>
</li>
<li><p>Modeling pegboard</p>
</li>
<li><p>Modeling photo frame</p>
</li>
<li><p>Modeling slippers</p>
</li>
<li><p>Modeling storage boxes</p>
</li>
<li><p>Modeling hanging plant</p>
</li>
<li><p>Modeling piano</p>
</li>
<li><p>Modeling window</p>
</li>
<li><p>Modeling microphone</p>
</li>
<li><p>Modeling egg rug</p>
</li>
<li><p>Modeling lamp</p>
</li>
<li><p>Modeling more floating shelves</p>
</li>
<li><p>Modeling sticky notes</p>
</li>
<li><p>Modeling boba plushie</p>
</li>
<li><p>Adding some more details to piano</p>
</li>
<li><p>Modeling logos</p>
</li>
<li><p>Modeling clock</p>
</li>
<li><p>Modeling mini table</p>
</li>
<li><p>Modeling pizza</p>
</li>
<li><p>Modeling soda can</p>
</li>
<li><p>Modeling another hanging plant but with particle system</p>
</li>
<li><p>Modeling eggs</p>
</li>
<li><p>Modeling wall outlet</p>
</li>
<li><p>Adding and working with text meshes</p>
</li>
<li><p>Modeling hanging lights and light bulb</p>
</li>
<li><p>Modeling floor planks</p>
</li>
<li><p>Modeling cords</p>
</li>
<li><p>Modeling room caps</p>
</li>
<li><p>An introduction to lighting and materials</p>
</li>
<li><p>Creating first procedural material as a design exploration</p>
</li>
<li><p>Creating final first procedural material with vector math</p>
</li>
<li><p>Few more tips for baking</p>
</li>
<li><p>Checking face orientations/normals</p>
</li>
<li><p>Glass material and modeling fish</p>
</li>
<li><p>Introduction to baking</p>
</li>
<li><p>UV Unwrapping for texture baking</p>
</li>
<li><p>Render out an image to share!!!</p>
</li>
<li><p>Start baking</p>
</li>
<li><p>Analyze and adjusting bakes and exporting preparations</p>
</li>
<li><p>Exporting and analysis</p>
</li>
<li><p>Setup basic three.js starter with Vite.js</p>
</li>
<li><p>Compressing model and textures</p>
</li>
<li><p>Back to setup</p>
</li>
<li><p>Setting up loaders and loading items</p>
</li>
<li><p>Room material and appearance adjustments</p>
</li>
<li><p>Glass material</p>
</li>
<li><p>Water and bubble material</p>
</li>
<li><p>Screen material</p>
</li>
<li><p>Setting starting camera position and OrbitControls target</p>
</li>
<li><p>Preparing for raycaster interactions and model/texture adjustments in Blender</p>
</li>
<li><p>UV adjusting and texture painting to quickly patch baking errors</p>
</li>
<li><p>Pushing to GitHub and deploying with Vercel</p>
</li>
<li><p>Animating fans</p>
</li>
<li><p>Raycaster click/touch interactions</p>
</li>
<li><p>Basic modal with GSAP animations</p>
</li>
<li><p>(Optional) Loading custom fonts and SCSS tips</p>
</li>
<li><p>Raycaster hovering interaction with GSAP</p>
</li>
<li><p>Customizing OrbitControls to limit pan and rotation</p>
</li>
<li><p>Intro animations with GSAP</p>
</li>
<li><p>Custom loading screen walkthrough</p>
</li>
<li><p>A brief look at audio with Howler.js</p>
</li>
<li><p>Setting a starting camera position for responsiveness</p>
</li>
<li><p>Final push to GitHub!!!</p>
</li>
<li><p>Final comments, future directions/challenges, shoutouts</p>
</li>
</ul>
<p>Watch the full course on the freeCodeCamp.org YouTube channel (9-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/tdsQwuyS6DQ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Create 3D Web Experiences with JavaScript and Three.js ]]>
                </title>
                <description>
                    <![CDATA[ Do you want to improve you JavaScript skills and create 3D web experiences? We just posted a course on the freeCodeCamp.org YouTube channel that is designed to help anyone master JavaScript and Three.js by building five impressive and practical proje... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-3d-web-experiences-with-javascript-and-threejs/</link>
                <guid isPermaLink="false">68af51354dd5e596e553184e</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ThreeJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 27 Aug 2025 18:40:53 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756299743551/aa4a1301-8f99-4eef-8e66-7c6202820b93.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Do you want to improve you JavaScript skills and create 3D web experiences?</p>
<p>We just posted a course on the <a target="_blank" href="http://freeCodeCamp.org">freeCodeCamp.org</a> YouTube channel that is designed to help anyone master JavaScript and Three.js by building five impressive and practical projects. The goal of the course is to provide a tangible portfolio of work and a clear roadmap for getting started with Three.js.</p>
<p>Each project is completely standalone, which means you can jump right to the topic that interests you most, whether that's creating fire effects or building a 3D globe. Bobby Roe developed this course. He has created many popular Three.js tutorials.</p>
<p>Here’s a look at the projects you’ll build in this series:</p>
<ul>
<li><p><strong>A Three.js Roadmap:</strong> The course begins by laying out a roadmap to help you get started with Three.js. You'll learn the fundamental concepts and build an interactive 3D scene while learning the basics.</p>
</li>
<li><p><strong>Applying Textures:</strong> You'll learn how to load and apply different textures, including a <code>normalMap</code> and <code>roughnessMap</code>, to give your 3D models realistic surface details and shininess.</p>
</li>
<li><p><strong>A 3D Globe in a Starry Sky:</strong> You'll create a 3D globe floating in a starry sky with country outlines. This project teaches you how to work with GeoJSON data to draw lines on a sphere and add effects like fog and dynamic colors to make it look truly unique.</p>
</li>
<li><p><strong>Dynamic Particle Effects:</strong> Learn to build effects like fire, smoke, and sparkles. You'll learn how to control the velocity, size, and color of particles over their lifespan to achieve different visual effects.</p>
</li>
<li><p><strong>Interactive Physics:</strong> This project is all about making things collide and interact in a realistic way. Using the Rapier physics engine, you'll build a scene where objects respond to mouse controls and each other, with polished details like HDR lighting and custom colliders.</p>
</li>
</ul>
<p>Watch the full course on <a target="_blank" href="https://youtu.be/tPkJLnDqAKk">the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/tPkJLnDqAKk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How WebGL and Three.js Power Interactive Online Stores ]]>
                </title>
                <description>
                    <![CDATA[ When online shopping first took off, product pages were built around a few static images and maybe a zoom feature. That was enough back then. But today’s customers expect far more. They want to spin a sneaker around, preview a sofa in their living ro... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-webgl-and-threejs-power-interactive-online-stores/</link>
                <guid isPermaLink="false">68ac8e669a85df841d9f286d</guid>
                
                    <category>
                        <![CDATA[ WebGL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ThreeJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ajay Kalal ]]>
                </dc:creator>
                <pubDate>Mon, 25 Aug 2025 16:25:10 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756138909378/69cae8fe-9a57-4036-817a-fde4e6a19f3b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When online shopping first took off, product pages were built around a few static images and maybe a zoom feature. That was enough back then. But today’s customers expect far more. They want to spin a sneaker around, preview a sofa in their living room, or customize the color of a water bottle, all before clicking “Add to Cart.” </p>
<p>This is where WebGL and Three.js come in. Together, they make it possible to bring interactive 3D graphics to online stores, directly inside the browser, without plugins or external apps. </p>
<p>In this article, we’ll break down how these technologies work, why they’re transforming eCommerce, and what developers need to know to build the next generation of interactive shopping experiences. </p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-webgl">What is WebGL?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-threejs-makes-webgl-developer-friendly">How Three.js Makes WebGL Developer-Friendly</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-a-simple-3d-configurator-demo">How to Build a Simple 3D Configurator Demo</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-setting-up-the-html-file">Step 1: Setting Up the HTML File</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-adding-styles-with-css">Step 2: Adding Styles with CSS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-creating-the-scene-in-scriptjs">Step 3: Creating the Scene in Script.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-adding-a-product-cube">Step 4: Adding a Product (Cube)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-making-the-cube-perspective">Step 5: Making the Cube Interactive</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-making-it-responsive">Step 6: Making It Responsive</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-the-role-of-3d-in-ecommerce">The Role of 3D in eCommerce</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-use-cases">Real-World Use Cases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-technical-challenges--best-practices">Technical Challenges &amp; Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-future-of-3d-in-online-stores">The Future of 3D in Online Stores</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">💡 Prerequisites</h3>
<p>To get the most out of this article, you should have:</p>
<ul>
<li><p>A basic understanding of JavaScript (variables, functions, imports).</p>
</li>
<li><p>Familiarity with HTML and the DOM (since we’ll be rendering into a <code>&lt;canvas&gt;</code>).</p>
</li>
<li><p>Curiosity about graphics programming – no deep math or shader knowledge is required.</p>
</li>
<li><p>Node.js and npm installed (if you want to try out the Three.js examples locally).</p>
</li>
</ul>
<p>If you’ve never worked with 3D graphics before, don’t worry. We’ll keep the examples simple and focus on concepts</p>
<h2 id="heading-what-is-webgl"><strong>What is WebGL?</strong></h2>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API"><strong>WebGL (Web Graphics Library)</strong></a> is a JavaScript API that allows you to render interactive 2D and 3D graphics in the browser using the computer’s GPU. Unlike older browser technologies (think Flash), WebGL is built directly into modern browsers, so users don’t need to install anything extra. </p>
<p>At its core, WebGL is based on OpenGL ES (a subset of the OpenGL specification), and it provides developers with a low-level API to work with shaders, vertices, and rendering pipelines. </p>
<p>A minimal WebGL example might look like this: </p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"glcanvas"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"640"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"480"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span> 

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript"> 
  <span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"glcanvas"</span>); 
  <span class="hljs-keyword">const</span> gl = canvas.getContext(<span class="hljs-string">"webgl"</span>); 

  <span class="hljs-keyword">if</span> (!gl) { 
    alert(<span class="hljs-string">"WebGL not supported by your browser"</span>); 
  } 

  <span class="hljs-comment">// Clear the canvas with a background color </span>
  gl.clearColor(<span class="hljs-number">0.0</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">1.0</span>); 
  gl.clear(gl.COLOR_BUFFER_BIT); 
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>If you run this snippet, it simply fills a canvas with a teal color. Not too exciting – but it’s happening on the GPU, and from here, you can go all the way to photorealistic 3D. </p>
<h2 id="heading-how-threejs-makes-webgl-developer-friendly"><strong>How Three.js Makes WebGL Developer-Friendly</strong></h2>
<p>While WebGL is powerful, it’s also verbose. Developers need to manage shaders, buffer objects, and projection matrices manually, which is a steep learning curve for most front-end engineers. </p>
<p>This is where <a target="_blank" href="https://threejs.org/"><strong>Three.js</strong></a> shines. It’s a popular JavaScript library that wraps around WebGL and provides a higher-level, developer-friendly API for working with 3D graphics. Instead of hundreds of lines of setup code, you can get a 3D scene up and running in a few lines. </p>
<p>Here’s a simple Three.js example that creates a rotating cube: </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-comment">// Create a scene </span>
<span class="hljs-keyword">const</span> scene = <span class="hljs-keyword">new</span> THREE.Scene(); 

<span class="hljs-comment">// Camera setup </span>
<span class="hljs-keyword">const</span> camera = <span class="hljs-keyword">new</span> THREE.PerspectiveCamera(<span class="hljs-number">75</span>, <span class="hljs-built_in">window</span>.innerWidth/<span class="hljs-built_in">window</span>.innerHeight, <span class="hljs-number">0.1</span>, <span class="hljs-number">1000</span>); 

<span class="hljs-comment">// Renderer </span>
<span class="hljs-keyword">const</span> renderer = <span class="hljs-keyword">new</span> THREE.WebGLRenderer(); 
renderer.setSize(<span class="hljs-built_in">window</span>.innerWidth, <span class="hljs-built_in">window</span>.innerHeight); 
<span class="hljs-built_in">document</span>.body.appendChild(renderer.domElement); 

<span class="hljs-comment">// Add a cube </span>
<span class="hljs-keyword">const</span> geometry = <span class="hljs-keyword">new</span> THREE.BoxGeometry(); 
<span class="hljs-keyword">const</span> material = <span class="hljs-keyword">new</span> THREE.MeshBasicMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0x00ff00</span> }); 
<span class="hljs-keyword">const</span> cube = <span class="hljs-keyword">new</span> THREE.Mesh(geometry, material); 
scene.add(cube); 

camera.position.z = <span class="hljs-number">5</span>; 

<span class="hljs-comment">// Animation loop </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params"></span>) </span>{ 
  requestAnimationFrame(animate); 
  cube.rotation.x += <span class="hljs-number">0.01</span>; 
  cube.rotation.y += <span class="hljs-number">0.01</span>; 
  renderer.render(scene, camera); 
} 
animate();
</code></pre>
<p>With just a few lines, you have an interactive 3D object rendered inside the browser. This ease of use is why Three.js has become the go-to library for developers building interactive product experiences online. </p>
<h2 id="heading-how-to-build-a-simple-3d-configurator-demo">How to Build a Simple 3D Configurator Demo</h2>
<p>To understand how these technologies translate to real-world online shopping, let’s build a tiny demo: a 3D box that rotates and changes color when a button is clicked. Think of it as the most basic version of a product previewer. </p>
<h3 id="heading-step-1-setting-up-the-html-file">Step 1: Setting Up the HTML File</h3>
<p>Let’s start with an <code>index.html file</code>. This file will contain a <code>&lt;canvas&gt;</code> function for rendering our 3D scene and a few buttons that act like product “options” (for example, choosing red, blue, or green). </p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span> 
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>3D Product Demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css"> 
    <span class="hljs-selector-tag">body</span> { 
      <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>; 
      <span class="hljs-attribute">overflow</span>: hidden; 
      <span class="hljs-attribute">font-family</span>: sans-serif; 
      <span class="hljs-attribute">background</span>: <span class="hljs-number">#f5f5f5</span>; 
    } 
    <span class="hljs-selector-tag">canvas</span> { <span class="hljs-attribute">display</span>: block; } 
    <span class="hljs-selector-class">.controls</span> { 
      <span class="hljs-attribute">position</span>: absolute; 
      <span class="hljs-attribute">top</span>: <span class="hljs-number">20px</span>; 
      <span class="hljs-attribute">left</span>: <span class="hljs-number">20px</span>; 
      <span class="hljs-attribute">display</span>: flex; 
      <span class="hljs-attribute">gap</span>: <span class="hljs-number">10px</span>; 
    } 
    <span class="hljs-selector-tag">button</span> { 
      <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">16px</span>; 
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>; 
      <span class="hljs-attribute">border</span>: none; 
      <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>; 
      <span class="hljs-attribute">cursor</span>: pointer; 
      <span class="hljs-attribute">color</span>: white; 
    } 
    <span class="hljs-selector-class">.red</span> { <span class="hljs-attribute">background</span>: <span class="hljs-number">#e63946</span>; } 
    <span class="hljs-selector-class">.blue</span> { <span class="hljs-attribute">background</span>: <span class="hljs-number">#0077ff</span>; } 
    <span class="hljs-selector-class">.green</span> { <span class="hljs-attribute">background</span>: <span class="hljs-number">#2a9d8f</span>; } 
    <span class="hljs-selector-tag">button</span><span class="hljs-selector-pseudo">:hover</span> { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.8</span>; } 
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span> 
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span> 
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span> 
  <span class="hljs-comment">&lt;!-- Controls to change product colors --&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"controls"</span>&gt;</span> 
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"red"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"setColor(0xe63946)"</span>&gt;</span>Red<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span> 
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blue"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"setColor(0x0077ff)"</span>&gt;</span>Blue<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span> 
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"green"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"setColor(0x2a9d8f)"</span>&gt;</span>Green<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span> 
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> 

  <span class="hljs-comment">&lt;!-- Import Three.js library --&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/three@0.154/build/three.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"script.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> 
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span> 
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Here’s what we’ve done: </p>
<ul>
<li><p>Added a few styled buttons for color options. </p>
</li>
<li><p>Set up some basic CSS for layout and design. </p>
</li>
<li><p>Included the Three.js library from a CDN. </p>
</li>
<li><p>Linked to a <code>script.js</code> file where we’ll write our 3D logic. </p>
</li>
</ul>
<h3 id="heading-step-2-creating-the-scene-in-scriptjs">Step 2: Creating the Scene in Script.js</h3>
<p>Now create a file called <code>script.js</code>. This is where we’ll build the 3D world. </p>
<p>The first step is to create a scene, a camera, and a renderer. Think of it like this: the <strong>scene</strong> is the stage, the <strong>camera</strong> is the viewpoint, and the <strong>renderer</strong> is what draws everything to the screen. </p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Create the scene </span>
<span class="hljs-keyword">const</span> scene = <span class="hljs-keyword">new</span> THREE.Scene(); 

<span class="hljs-comment">// Set up a camera </span>
<span class="hljs-keyword">const</span> camera = <span class="hljs-keyword">new</span> THREE.PerspectiveCamera( 
  <span class="hljs-number">75</span>, <span class="hljs-built_in">window</span>.innerWidth / <span class="hljs-built_in">window</span>.innerHeight, <span class="hljs-number">0.1</span>, <span class="hljs-number">1000</span> 
); 
camera.position.z = <span class="hljs-number">3</span>; 

<span class="hljs-comment">// Create a WebGL renderer </span>
<span class="hljs-keyword">const</span> renderer = <span class="hljs-keyword">new</span> THREE.WebGLRenderer({ <span class="hljs-attr">antialias</span>: <span class="hljs-literal">true</span> }); 
renderer.setSize(<span class="hljs-built_in">window</span>.innerWidth, <span class="hljs-built_in">window</span>.innerHeight); 
<span class="hljs-built_in">document</span>.body.appendChild(renderer.domElement);
</code></pre>
<h3 id="heading-step-3-adding-a-product-cube">Step 3: Adding a Product (Cube)</h3>
<p>For simplicity, we’ll use a cube to represent our product. Later, this could be any 3D model (like a shoe, sofa, or banner stand). </p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Create a cube geometry </span>
<span class="hljs-keyword">const</span> geometry = <span class="hljs-keyword">new</span> THREE.BoxGeometry(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>); 

<span class="hljs-comment">// Apply a material (blue color by default) </span>
<span class="hljs-keyword">let</span> material = <span class="hljs-keyword">new</span> THREE.MeshStandardMaterial({ <span class="hljs-attr">color</span>: <span class="hljs-number">0x0077ff</span> }); 

<span class="hljs-comment">// Combine geometry and material into a mesh </span>
<span class="hljs-keyword">const</span> cube = <span class="hljs-keyword">new</span> THREE.Mesh(geometry, material); 

<span class="hljs-comment">// Add the cube to the scene </span>
scene.add(cube); 

<span class="hljs-comment">// Add lighting so we can see the cube properly </span>
<span class="hljs-keyword">const</span> light = <span class="hljs-keyword">new</span> THREE.DirectionalLight(<span class="hljs-number">0xffffff</span>, <span class="hljs-number">1</span>); 
light.position.set(<span class="hljs-number">2</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>).normalize(); 
scene.add(light);
</code></pre>
<h3 id="heading-step-4-animating-the-cube">Step 4: Animating the Cube</h3>
<p>We want the cube to spin. This creates the feeling of an interactive product preview. Here’s how we can make that happen:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params"></span>) </span>{ 
  requestAnimationFrame(animate); 

  cube.rotation.x += <span class="hljs-number">0.01</span>; 
  cube.rotation.y += <span class="hljs-number">0.01</span>; 

  renderer.render(scene, camera); 
} 
animate();
</code></pre>
<p>Now, when you load the page, the cube will rotate continuously. </p>
<h3 id="heading-step-5-adding-interactivity">Step 5: Adding Interactivity</h3>
<p>Let’s connect the color buttons to the cube. Each button calls the <code>setColor()</code> function with a hex code. </p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setColor</span>(<span class="hljs-params">hex</span>) </span>{ 
  cube.material.color.setHex(hex); 
}
</code></pre>
<p>Now, when you click “Red,” “Blue,” or “Green,” the cube changes color instantly, like switching between product variations. </p>
<h3 id="heading-step-6-making-it-responsive">Step 6: Making It Responsive</h3>
<p>Finally, let’s ensure the canvas resizes properly on different devices. </p>
<pre><code class="lang-javascript"><span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"resize"</span>, <span class="hljs-function">() =&gt;</span> { 
  camera.aspect = <span class="hljs-built_in">window</span>.innerWidth / <span class="hljs-built_in">window</span>.innerHeight; 
  camera.updateProjectionMatrix(); 
  renderer.setSize(<span class="hljs-built_in">window</span>.innerWidth, <span class="hljs-built_in">window</span>.innerHeight); 
});
</code></pre>
<p>We now have a mini product/Object previewer: </p>
<ul>
<li><p>A 3D object (cube) that rotates like a real product. </p>
</li>
<li><p>Buttons that change its color, simulating product options. </p>
</li>
<li><p>Responsive rendering across screen sizes. </p>
</li>
</ul>
<p>This is, of course, a simplified demo, but the same principles are used in real-world ecommerce experiences.</p>
<h3 id="heading-example-of-3d-configurator">Example of 3D Configurator</h3>
<div class="embed-wrapper"><iframe height="523" style="width:100%" src="https://codepen.io/Petr-Hovorka-the-sans/embed/qEdEJjy?default-tab=result" title="Embedded content" loading="lazy">
  See the Pen <a href="https://codepen.io/Petr-Hovorka-the-sans/pen/qEdEJjy">
  3D Configurator 0.9</a> by Petr Hovorka (<a href="https://codepen.io/Petr-Hovorka-the-sans">@Petr-Hovorka-the-sans</a>)
  on <a href="https://codepen.io">CodePen</a>.
</iframe></div>

<h2 id="heading-the-role-of-3d-in-ecommerce">The Role of 3D in eCommerce</h2>
<p>Why should online stores invest in 3D at all? The answer lies in user engagement. Studies show that customers are far more likely to convert when they can interact with products in detail. Instead of scrolling through flat images, they rotate, zoom, and even customize products in real-time. </p>
<p>From a developer’s perspective, integrating 3D isn’t just about “making it pretty.” It’s about: </p>
<ul>
<li><p><strong>Reducing return rates</strong> (customers know exactly what they’re buying). </p>
</li>
<li><p><strong>Increasing time-on-site</strong> (3D models encourage exploration). </p>
</li>
<li><p><strong>Supporting customization workflows</strong> (colors, materials, engravings). </p>
</li>
</ul>
<h2 id="heading-real-world-use-cases">Real-World Use Cases</h2>
<p>There are a few areas where WebGL + Three.js are already changing eCommerce. <a target="_blank" href="https://www.designnbuy.com/3d-product-configurator-software/">3D product configurators</a> utilize Three.js to enable customers to customize products interactively, changing colors and textures.</p>
<p>For example, 3D product reviews where online stores let customers rotate couches, cars, or appliances to see every angle. Virtual try-ons are also becoming popular among eyewear and fashion brands. They use AR + WebGL to let customers virtually try items online. Online printers and manufacturers also let customers configure their products in 3D before purchasing them.</p>
<h2 id="heading-technical-challenges-amp-best-practices"><strong>Technical Challenges &amp; Best Practices</strong></h2>
<p>Building interactive 3D experiences isn’t without hurdles. Developers need to think about: </p>
<ul>
<li><p><strong>Performance optimization</strong> – Compressing models, using Level of Detail (LOD), and reducing texture sizes. </p>
</li>
<li><p><strong>Cross-device compatibility</strong> – Ensuring 3D experiences work smoothly on both high-end desktops and mobile devices. </p>
</li>
<li><p><strong>Loading times</strong> – Using lazy loading for textures and assets. </p>
</li>
<li><p><strong>User experience</strong> – Smooth navigation controls, fallback images for unsupported devices, and accessible interactions. </p>
</li>
</ul>
<h3 id="heading-the-future-of-3d-in-online-stores"><strong>The Future of 3D in Online Stores</strong></h3>
<p>We’re only scratching the surface of what’s possible. Some trends shaping the future include: </p>
<ul>
<li><p>WebGPU: a next-generation graphics API that promises even better performance than WebGL. </p>
</li>
<li><p>Augmented Reality (AR): blending real and digital worlds with WebXR. </p>
</li>
<li><p>AI-powered customization: automatically generating product variations or suggestions. </p>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>WebGL and Three.js are powering a new wave of interactive online shopping. What used to require native apps or heavy plugins is now achievable directly in the browser, giving customers richer experiences and businesses higher conversion rates. </p>
<p>For developers, experimenting with WebGL and Three.js opens the door to a whole range of applications, from simple product previews to full-fledged 3D configurators. And as browser technology evolves, the line between online shopping and real-world interaction will only continue to blur.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Code a Crossy Road Game Clone with Three.js ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you’ll learn how to create a clone of the mobile game Crossy Road with Three.js. The goal of this game is to move a character through an endless path of static and moving obstacles. You have to go around trees and avoid getting hit ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-code-a-crossy-road-game-clone-with-threejs/</link>
                <guid isPermaLink="false">67b7bad6bcd9ef47aba6001d</guid>
                
                    <category>
                        <![CDATA[ ThreeJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ javascript game ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hunor Márton Borbély ]]>
                </dc:creator>
                <pubDate>Thu, 20 Feb 2025 23:29:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740094139765/282432bf-1a5b-40e4-8b74-7aa854fd20ac.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you’ll learn how to create a clone of the mobile game Crossy Road with Three.js. The goal of this game is to move a character through an endless path of static and moving obstacles. You have to go around trees and avoid getting hit by cars.</p>
<p>There's a lot to cover in this tutorial: we will start with setting up the scene, the camera, and the lights. Then you’ll learn how to draw the player and the map with the trees and the cars. We’ll also cover how to animate the vehicles, and we’ll add event handlers to move the player through the map. Finally, we’ll add hit detection between the cars and the player.</p>
<p>This article is a shortened version of the Crossy Road tutorial from my site <a target="_blank" href="https://javascriptgametutorials.com/">JavaScriptGameTutorials.com</a>. The extended tutorial is also available as a video on <a target="_blank" href="https://www.youtube.com/watch?v=vNr3_hQ3Bws&amp;ab_channel=HunorM%C3%A1rtonBorb%C3%A9ly">YouTube</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-game">How to Set Up the Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-render-a-map">How to Render a Map</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-animate-the-cars">How to Animate the Cars</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-move-the-player">How to Move the Player</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hit-detection">Hit Detection</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ol>
<h2 id="heading-how-to-set-up-the-game">How to Set Up the Game</h2>
<p>In this chapter, we’ll set up the drawing canvas, camera, and lights and render a box representing our player.</p>
<h3 id="heading-initializing-the-project">Initializing the Project</h3>
<p>I recommend using Vite to initialize the project. To do so, go to your terminal and type <code>npm create vite</code>, which will create an initial project for you.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create app</span>
npm create vite my-crossy-road-game

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    map.add(row);
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    map.add(row);
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  setPosition(progress);

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

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

. . .

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

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

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

. . .

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

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

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

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

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

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

. . .

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

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

  movesQueue.push(direction);
}

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

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

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

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

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

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

. . .

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

  renderer.render(scene, camera);
}
</code></pre>
<h2 id="heading-next-steps">Next Steps</h2>
<p>Congratulations, you’ve reached the end of this tutorial, and we’ve covered all the main features of the game. We rendered a map, animated the vehicles, added event handling for the player, and added hit detection.</p>
<p>I hope you had great fun creating this game. This game, of course, is far from perfect, and there are various improvements you can make if you’d like to keep working on it.</p>
<p>You can find the extended tutorial with interactive demos on <a target="_blank" href="http://JavaScriptGameTutorials.com">JavaScriptGameTutorials.com</a>. There, we also cover how to add shadows and truck lanes and how to generate an infinite number of rows as the player moves forward. We also add UI elements for the controls and the score indicator, and we add a result screen with a button to reset the game.</p>
<p>Alternatively, you can find the extended tutorial on <a target="_blank" href="https://www.youtube.com/watch?v=vNr3_hQ3Bws&amp;ab_channel=HunorM%C3%A1rtonBorb%C3%A9ly">YouTube</a>.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/vNr3_hQ3Bws" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Three.js By Building Five Projects ]]>
                </title>
                <description>
                    <![CDATA[ Three.js is a powerful JavaScript library that simplifies the process of creating 3D graphics in the browser. We just released a video course on the freeCodeCamp.org YouTube channel that will teach you how to use Three.js by guiding you through build... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-threejs-by-building-five-projects/</link>
                <guid isPermaLink="false">6669a3aaf3fbc64a45129ed4</guid>
                
                    <category>
                        <![CDATA[ ThreeJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 12 Jun 2024 13:33:30 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718199206429/fa28f06e-1fe1-48cd-a1f7-59d513e4124e.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Three.js is a powerful JavaScript library that simplifies the process of creating 3D graphics in the browser.</p>
<p>We just released a video course on the freeCodeCamp.org YouTube channel that will teach you how to use Three.js by guiding you through building five projects. This course is designed to introduce you to the world of 3D web graphics using Three.js, an essential tool for creating stunning visual experiences on the web. Bobby Roe created this course.</p>
<p>Three.js abstracts the complexities of WebGL (Web Graphics Library), making it easier for developers to create and display animated 3D computer graphics on a web page. With Three.js, you can create everything from simple 3D models to complex interactive experiences.</p>
<h2 id="heading-what-youll-learn">What You’ll Learn</h2>
<p>In this course, you'll learn Three.js by building five hands-on projects. Each project is designed to teach you different aspects of the library and how to leverage its capabilities to create stunning 3D graphics.</p>
<ol>
<li><p><strong>Getting Started</strong></p>
<ul>
<li><p><strong>Overview:</strong> This project will introduce you to the basics of Three.js. You'll learn how to set up your development environment and create your first 3D scene.</p>
</li>
<li><p><strong>Topics Covered:</strong></p>
<ul>
<li><p>Setting up a Three.js project</p>
</li>
<li><p>Creating a scene, camera, and renderer</p>
</li>
<li><p>Adding basic shapes and materials</p>
</li>
<li><p>Lighting and shadows</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Create the Earth with Three.js</strong></p>
<ul>
<li><p><strong>Overview:</strong> You'll build a 3D model of the Earth, complete with textures and lighting.</p>
</li>
<li><p><strong>Topics Covered:</strong></p>
<ul>
<li><p>Loading and applying textures</p>
</li>
<li><p>Using lighting to enhance realism</p>
</li>
<li><p>Creating and animating the Earth’s rotation</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Fly through a Wireframe Wormhole</strong></p>
<ul>
<li><p><strong>Overview:</strong> This project will teach you how to create a mesmerizing wireframe wormhole effect.</p>
</li>
<li><p><strong>Topics Covered:</strong></p>
<ul>
<li><p>Working with wireframe geometries</p>
</li>
<li><p>Animating camera movements</p>
</li>
<li><p>Creating dynamic and interactive animations</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Create a Transition Effect</strong></p>
<ul>
<li><p><strong>Overview:</strong> Learn how to create smooth transition effects between different scenes or states in your 3D project.</p>
</li>
<li><p><strong>Topics Covered:</strong></p>
<ul>
<li><p>Using shaders for custom effects</p>
</li>
<li><p>Creating seamless transitions</p>
</li>
<li><p>Managing multiple scenes and objects</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Add Physics to Three.js with Rapier</strong></p>
<ul>
<li><p><strong>Overview:</strong> In this final project, you’ll integrate physics into your 3D scenes using the Rapier physics engine.</p>
</li>
<li><p><strong>Topics Covered:</strong></p>
<ul>
<li><p>Setting up Rapier with Three.js</p>
</li>
<li><p>Adding physical properties to objects</p>
</li>
<li><p>Simulating realistic interactions and collisions</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="heading-why-learn-threejs">Why Learn Three.js?</h3>
<p>Three.js opens up a world of possibilities for web developers. By learning Three.js, you can create engaging and interactive experiences that captivate users and provide new ways to visualize data and content. Whether you're a web developer looking to expand your skillset or a hobbyist interested in 3D graphics, this course will give you the knowledge and hands-on experience you need.</p>
<p>Watch the full course on <a target="_blank" href="https://www.youtube.com/watch?v=UMqNHi1GDAE">the freeCodeCamp.org YouTube channel</a> (1.5-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/UMqNHi1GDAE" 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>
