Shortcuts ⌨️

  • SPACE to play / pause
  • ARROW RIGHT or L to go forward
  • ARROW LEFT or J to go backward
  • ARROW UP to increase volume
  • ARROW DOWN to decrease volume
  • F to toggle fullscreen
  • M to toggle mute
  • 0 to 9 to go to the corresponding part of the video
  • SHIFT + , to decrease playback speed
  • SHIFT + . or ; to increase playback speed

⚠️ Update

Use version 1.3:

npm install @react-three/rapier@1.3

⚠️ Update

In the latest versions of @react-three/rapier, the debug mode is activated using the debug attribute (not <Debug>):

<Physics debug>

⚠️ Update

In the latest version of @react-three/rapier and rapier, a RigidBody falls asleep after a few seconds of inaction.

This would result in the sphere not moving even though the player presses the arrow keys.

To fix it, set the canSleep attribute to false on the <RigidBody>:

<RigidBody canSleep={ false } colliders="ball" restitution={ 0.2 } friction={ 1 } position={ [ 0, 1, 0 ] }>
    {/* ... */}

⚠️ Update

In the latest versions of @react-three/rapier, useRapier returns the actual Rapier world.

You don’t need to use world.raw() and you can use world directly.

⚠️ Update

In the latest versions of @react-three/rapier, the debug mode is activated and deactivated using the debug attribute (not <Debug>):

<Physics debug={ false }>

⚠️ Update

Since we are now using Vite.js, the index.html is now located in the src/ folder.

⚠️ Update

Use version 4.5:

npm install zustand@4.5

⚠️ Update

In the latest versions of zustand you need to import create like this:

import { create } from 'zustand'

⚠️ Update

In the latest versions of @react-three/drei, the default size of <Text> is bigger.

Set the scale to 0.5 instead of 4:

<Text scale={ 0.5 }></Text>

⚠️ Update

In the latest versions of @react-three/drei, the default size of <Text> is bigger.

Set the scale to 1 instead of 8:

<Text scale={ 1 }></Text>

Unlock content 🔓

To get access to 93 hours of video, a members-only Discord server, subtitles, lesson resources, future updates and much more join us for only $95!

Want to learn more? 🖖


That's the end of the free part 😔

To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!


Create a game

Difficulty Hard

Introduction 00:00

In this lesson, we are going to learn how to create a mini-game.

We play a marble and we have to go through a level filled with various moving obstacles in order to reach the destination.

Here are some of the mechanics:

  • The player can move the marble and make it jump with the keyboard.
  • As soon as the marble is moving, a timer starts running and it indicates to the player how long it took to finish the obstacle course.
  • At the end of the race, a “restart” button appears and by clicking on it, it will reset the marble to the initial position, reset the timer and create a new set of obstacles so that the level is never the same.

This lesson is a good opportunity to put into practice the knowledge we gathered like physics, interface & components but also new concepts like a global state or keyboard controls.

And obviously, we get to create and play a game!

Setup 00:49

In the starter, we have the classic orange sphere, purple cube, and green floor.

Both the directional light source and an ambient light source are in a <Lights> component because we are going to make some tweaks related to lights a bit later and we want to keep things organised.

Shadows are already enabled and the directional light is set to cast the shadows within a pretty large area:

    position={ [ 4, 4, 1 ] }
    intensity={ 1.5 }
    shadow-mapSize={ [ 1024, 1024 ] }
    shadow-camera-near={ 1 }
    shadow-camera-far={ 10 }
    shadow-camera-top={ 10 }
    shadow-camera-right={ 10 }
    shadow-camera-bottom={ - 10 }
    shadow-camera-left={ - 10 }

The @react-three/drei dependency is already installed within the project.

We are using the OrbitControls helper to be able to move the camera around, but we are going to remove it later so that we can have the camera follow the marble.

We haven’t added <Perf /> from r3f-perf but you should definitely use it if you create your own game. Having a good frame rate is very important and monitoring performance will help you.

No debug UI has been added because all the various values and colors have already been carefully chosen, but you should definitely add one (like Leva) if you were to create your own game or if you want to improve this game once you are done with the lesson.

Level 01:53

When creating a game, it’s good to have things on screen as quickly as possible. Even if the mechanics don’t work yet, it helps to get an idea of what’s coming up and it’s much more interesting.

That’s why we are going to start by creating the level and its various traps.

Our level will be composed of what we are going to call blocks.

The first block will be composed of a simple floor without anything else. It’s where the player will start.

The last block will be the finish line.

In between, there will be a bunch of trap blocks with moving obstacles.

We are going to create 3 different types of traps and then populate the blocks between the start and the end with a random set composed of those trap blocks.


In /src/, create a Level.jsx file, export a Level function component and put the 3 meshes from Experience in it:

export default function Level()
    return <>
        <mesh castShadow position-x={ - 2 }>
            <sphereGeometry />
            <meshStandardMaterial color="orange" />

        <mesh castShadow position-x={ 2 } scale={ 1.5 }>
            <boxGeometry />
            <meshStandardMaterial color="mediumpurple" />

        <mesh receiveShadow position-y={ - 1 } rotation-x={ - Math.PI * 0.5 } scale={ 10 }>
            <planeGeometry />
            <meshStandardMaterial color="greenyellow" />

In Experience, import Level:

import Level from './Level.jsx'

Instantiate it after the <Lights>:

export default function Experience()
    return <>

        <OrbitControls makeDefault />

        <Lights />
        <Level />


We have our little scene back, but now it’s set in the <Level>.

Add Physics

The game will rely heavily on physics and we are going to use Rapier with @react-three/rapier as seen in the previous lesson.

Want to learn more?

That's the end of the free part 😔

To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!

That was the last lesson!

Care to join?

How to use it 🤔

  • Download the Starter pack or Final project
  • Unzip it
  • Open your terminal and go to the unzip folder
  • Run npm install to install dependencies
    (if your terminal warns you about vulnerabilities, ignore it)
  • Run npm run dev to launch the local server
    (project should open on your default browser automatically)
  • Start coding
  • The JS is located in src/script.js
  • The HTML is located in src/index.html
  • The CSS is located in src/style.css

If you get stuck and need help, join the members-only Discord server: