Home >Web Front-end >JS Tutorial >Building a Game with Three.js, React and WebGL
Core points
I am making a game called "Chameleon Charm". It is built using Three.js, React and WebGL. This article describes how these technologies work together using react-three-renderer (abbreviated as R3R).
Please check out the WebGL Getting Started Guide and the React and JSX Getting Started Guide on SitePoint for an introduction to React and WebGL. This article and the accompanying code use ES6 syntax.
The beginning of everything
A while ago, Pete Hunt made a joke in the #reactjs IRC channel, saying that he would use React to create a game:
I bet we can make a first-person shooter with React! The enemy has
A few years later, I did exactly this.
Chameleon Charm is a game that collects enhanced props that will allow you to narrow down to solve the infinite fractal maze. I've been working as a React developer for a few years and I'm curious to know if there is a way to use React to drive Three.js. At this time, R3R attracted my attention.
Why choose React?
I know what you are thinking: Why? Please let me explain. Here are some reasons to consider using React to drive 3D scenes:
<player></player>
, <wall></wall>
, <level></level>
, and more. <texture require="" src="%7B"></texture>
Let's set up a scenario to see how this all works.
React and WebGL
I created a sample GitHub repository to work with this article. Clone the repository and run the code as in the README and continue learning. It features SitePointy 3D robot as the main character!
Warning: R3R is still in the testing phase. Its API is unstable and may change in the future. Currently it only processes a subset of Three.js. I found it complete enough to build a complete game, but your results may vary.
The main benefit of using React to drive WebGL is that our view code is decoupled from the game logic. This means that the entities we render are small and easy to understand components.
R3R exposes a declarative API that encapsulates Three.js. For example, we can write:
<code><scene>></scene> <perspectivecamera> position={ new THREE.Vector3( 1, 1, 1 ) /> > </perspectivecamera></code>
Now we have an empty 3D scene with a camera. Adding a mesh to a scene is as simple as including a <mesh></mesh>
component and giving it <geometry></geometry>
is as easy as <material></material>
.
<code><scene>></scene> … <mesh>></mesh> <boxgeometry></boxgeometry> width={ 1 } height={ 1 } depth={ 1 } /> <meshbasicmaterial></meshbasicmaterial> color={ 0x00ff00 } /> > </code>
Behind the scenes, this will create a THREE.Scene and automatically add a grid with THREE.BoxGeometry. R3R handles the differences between old scenes and any changes. If you add a new mesh to the scene, the original mesh will not be recreated. Just like using normal React and DOM, 3D scenes only update the differences.
Because we work in React, we can detach the game entity into the component file. The Robot.js file in the sample repository demonstrates how to represent a major role using pure React view code. It is a "stateless function" component, which means it does not save any local state:
<code>const Robot = ({ position, rotation }) => <group></group> position={ position } rotation={ rotation } > <mesh> rotation={ localRotation }></mesh> <geometryresource></geometryresource> resourceId="robotGeometry" /> <materialresource></materialresource> resourceId="robotTexture" /> > >; </code>
Now we will include <robot></robot>
in our 3D scene!
<code><scene>></scene> … <mesh>></mesh>…> <robot></robot> position={…} rotation={…} /> > </code>
You can view more API examples on the R3R GitHub repository, or see the full sample settings in the included project.
The other half of the equation is to deal with game logic. Let's add some simple animations to our robot SitePointy.
How does traditional game loop work? They accept user input, analyze the old "world state", and return to the new world state for rendering. For convenience, let's store the "Game State" object in the component state. In a more mature project, you can move game state to Redux or Flux storage.
We will use the browser's requestAnimationFrame
API callback to drive our game loop and run the loop in GameContainer.js
. To animation the robot, let's calculate a new location based on the timestamp passed to requestAnimationFrame
and then store the new location in the state.
<code><scene>></scene> <perspectivecamera> position={ new THREE.Vector3( 1, 1, 1 ) /> > </perspectivecamera></code>
Calling setState()
will trigger the re-render of the child component and update the 3D scene. We pass state from container component to demo component: <game></game>
<code><scene>></scene> … <mesh>></mesh> <boxgeometry></boxgeometry> width={ 1 } height={ 1 } depth={ 1 } /> <meshbasicmaterial></meshbasicmaterial> color={ 0x00ff00 } /> > </code>We can apply a useful pattern to help organize this code. Updating the robot location is a simple time-based calculation. In the future, it may also consider previous robot locations from previous game states. A function that accepts some data, processes it, and returns new data is often called a reducer. We can abstract the moving code into a reducer function!
Now we can write a concise and clear game loop that contains only function calls:
<code>const Robot = ({ position, rotation }) => <group></group> position={ position } rotation={ rotation } > <mesh> rotation={ localRotation }></mesh> <geometryresource></geometryresource> resourceId="robotGeometry" /> <materialresource></materialresource> resourceId="robotTexture" /> > >; </code>To add more logic to the game loop, such as handling physics, create another reducer function and pass it to the result of the previous reducer:
<code><scene>></scene> … <mesh>></mesh>…> <robot></robot> position={…} rotation={…} /> > </code>As the growth of game engines, it becomes crucial to organize game logic into separate functions. Using reducer mode, this organization is very simple.
Resource Management
<code>// … gameLoop( time ) { this.setState({ robotPosition: new THREE.Vector3( Math.sin( time * 0.01 ), 0, 0 ) }); } </code>With this setting, if you change the image on disk, your 3D scene will be updated in real time! This is invaluable for fast iterating game design and content.
For other resources (such as 3D models), you still have to use Three.js' built-in loader (such as JSONLoader) to handle them. I've tried using a custom webpack loader to load 3D model files, but ended up with too much work and no benefit. It is easier to think of models as binary data and use a file loader to load them. This can still implement real-time overloading of model data. You can see this in the sample code.
Debug
R3R supports React developer tool extensions for Chrome and Firefox. You can check your scene like you would check a normal DOM! Hovering over elements in the inspector displays their bounding boxes in the scene. You can also hover over the texture definition to see which objects in the scene use these textures.
Performance Precautions
When building Chameleon Charm, I encountered some performance issues that are unique to this workflow.
setState()
multiple times per frame will result in double rendering and degrade performance. setState()
That's it!
Check out Chameleon Charm to learn what you can do with this setting. While this toolchain is still young, I found that using R3R's React is crucial to clearly organize my WebGL game code. You can also check out the small but growing R3R sample page to see some well-organized code examples.
This article was peer-reviewed by Mark Brown and Kev Zettler. Thanks to all the peer reviewers at SitePoint for getting SitePoint content to its best!
FAQ for Building Games with ReactJS and WebGLWhat are the prerequisites for building games with ReactJS and WebGL?
How to integrate Unity with ReactJS?
What are the different ways to create 3D applications using React?
WebGL allows you to create interactive 3D graphics directly in your browser, without plug-ins. You can use WebGL's API to create complex 3D graphics, animations, and games. However, WebGL's API is low-level and it can be complicated to use directly. Therefore, many developers prefer to use libraries like Three.js that provide a higher level of interface to WebGL.
react-unity-webgl package allows you to embed Unity WebGL builds into ReactJS applications. This means you can create complex 3D games with Unity and then easily integrate them into your ReactJS application. This is especially useful if you want to create a web-based game or interactive 3D application.
Optimizing games built with ReactJS and WebGL may involve multiple strategies. These strategies include minimizing the number of rerenders in React, using WebGL's built-in performance features such as requestAnimationFrame
to achieve smooth animations, and optimizing 3D models and textures for the web.
Yes, you can use ReactJS and WebGL to build games running in a web browser on your mobile device. However, for native mobile games, you may want to consider using game development platforms like Unity or Unreal Engine, which can be exported directly to iOS and Android.
User input can be processed in ReactJS and WebGL games using standard JavaScript event handlers. You can listen to keyboard, mouse, and touch events and then update the game status accordingly. ReactJS also provides synthesis events that can be used to process user input across different browsers in a consistent manner.
Yes, you can use other JavaScript libraries with ReactJS and WebGL. For example, you might use Three.js for 3D graphics, Howler.js for audio, or Matter.js for physical processing. The key is to make sure these libraries work seamlessly in your game.
Games built using ReactJS and WebGL can be debugged using developer tools in a web browser. These tools allow you to check HTML, CSS, and JavaScript code, view console logs, and debug the code step by step. Additionally, React Developer Tools is a browser extension that allows you to check React component hierarchy, props, and state.
The above is the detailed content of Building a Game with Three.js, React and WebGL. For more information, please follow other related articles on the PHP Chinese website!