Ready Player React: How to Build a Video Game With JavaScript — Part 2
As we move on to the next level of “Ready Player React,” it’s time to shift gears from concept to creation in Phase 2: Building.
Last time, we covered the basics from sketching a plan to deciding on a game loop and hook. In this next chapter, we’ll walk through setting up your environment and building out the initial structure for test-driven development. We’ll select tools and libraries, project structure, and game architecture.
We’ll start by introducing test-driven development (TDD) using a pragmatic approach to coding each game element. Next, we’ll build our first components while infusing them with game logic and styling. Finally, we’ll tackle one of the more challenging aspects of using React for game development — working with sound in the browser!
So, get ready as we gear up for the next round in this step-by-step guide to building a game with JavaScript!
(Before moving on, make sure you’re up to speed with Part 1 here.)
Setting up the Environment
This stage is all about choosing the right tools and setting up a project structure that allows for a streamlined workflow.
This project uses Create React App (“Happy hacking!”), Tailwind, Storybook, and Jest to get the vibes right straight from the start. Visualizing components, and mapping out state management for prop drilling is an important step in building a strong foundation for your game.
Create React App
The official React docs have stopped recommending create-react-app (CRA) as the best way to jumpstart a React project. I decided to go against this recommendation having used CRA to bootstrap many projects similar to this one in the past. You’re welcome to install React however you prefer using whatever framework works best for you — just keep in mind that all steps in this guide will assume you are using CRA or that you’re savvy enough to work around that!
To get started with CRA follow these steps:
- Run the following command in your terminal replacing "my-project-name" with your project’s name:
npx create-react-app my-project-name
- Change into your new project directory:
cd my-project-name
Tailwind CSS
Next up — installing Tailwind. This allows elements to be styled quickly and in a uniform way throughout the game:
“The simplest and fastest way to get up and running with Tailwind CSS from scratch is with the Tailwind CLI tool“— tailwindcss.com
If you’ve never installed Tailwind, I recommend following the dev team’s advice by using the CLI tool. You can learn more about it and read step-by-step instructions here: https://tailwindcss.com/docs/installation
Storybook UI
“Storybook is an interactive directory of your UI components and their stories.” — storybook.js.org
I decided to use Storybook to build out the component library for this project following a strict component-driven-development (CDD) methodology. That was great in theory, but adding this extra step to a project with such a short deadline ended up not working out. As a result, I had to abandon this approach part of the way into development.
You can see some of the earlier stories I created for my game in the project repo here: Vibe Check — Storybook Stories
You can get started with adding Storybook to your project by following their step-by-step guide here: https://storybook.js.org/docs/get-started/install
Although I had to move away from this approach for this project, I learned enough to add CDD to my workflow for future projects — and you should too!
Jest Testing
Testing is vital to adding efficiency to your development process while reducing errors and bugs.
I went with Jest for this project but there are plenty of testing options to choose from. You can get started by following my guide here: https://medium.com/@jawaragordon/jest-the-delightful-javascript-testing-suite-e129390ee65
Once you’re up and running, you can start the process of TDD.
Test-driven development was first introduced to me through the curriculum at my Flatiron bootcamp. We were tasked with getting tests to pass from the very first lessons — and let me tell you, it was frustrating!
Testing has grown on me since then, having seen first-hand why it’s so important for creating an efficient workflow, and how it saves you time when introducing new features or fixes.
TDD is like setting the rules of a puzzle before you start solving it. We start by writing the tests for our game’s functions before writing any of the code that makes them work. Sort of like creating a checklist of things for the program to do and only starting to code once you have that list. Then you can start building each part, checking items off the list one by one.
Embracing TDD is like having a safety net as you fly through the development process, ensuring every new feature performs flawlessly. It’s a disciplined way to build reliable and bug-free software that every developer should adopt.
Bonus
There are endless ways to transform your ideas into reality using React to build classic, 2D, or even 3D games!
Here are some suggestions from ReactJam.com:
DOM-based games using plain React: see multiplayer Sudoku.
2D canvas games using plain canvas: see React Pixi.
3D canvas games using: React Three Fiber.
Designing the Game Architecture
This is where we turn our abstract ideas into tangible components. Think of it as drafting the blueprint of your game’s world, where every component has its place and purpose. This includes creating a comprehensive list of components, each serving as a building block for our game’s structure.
Keeping track of each component on a Trello board is a great place to start organizing your thoughts. This is also a good place to break down the functionality for each element before adding them to Storybook or your component library.
Component List
1. Basic UI Components (Foundational Elements)
Button: For actions like starting the game, selecting options, etc.
InputField: For text inputs, including the vibe words entry.
Modal: Reusable modal component for dialogues, alerts, etc.
Slider: For the intensity selection feature.
All of this planning should start to pay off as your workflow should look something like this:
- Write a test for the next step in your development process:
import { render, screen } from '@testing-library/react';
import TestComponent from '../components/TestComponent';
test('renders learn react link', () => {
render(<TestComponent />);
const linkElement = screen.getByText(/thinking in react/i);
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', 'https://react.dev/learn/thinking-in-react');
});
- Create a component that causes the test to go from red (failing) to green (passing):
const TestComponent = () => {
return (
<div>
<a
href="https://react.dev/learn/thinking-in-react"
target="_blank"
rel="noopener noreferrer"
>
thinking in react
</a>
</div>
);
};
export default TestComponent;
- Build a story to view and update this component’s state:
import Test from '../components/TestComponent';
export default {
title: 'Test',
component: Test
};
export const Default = () => <Test />;
The tight feedback loop of CDD, TDD, and error handling will allow you to fly through your component list in no time! Remember to break down each step of your game loop into individual screens, then use your diagram to map out each component for each screen, then you can fill in the blanks for each function inside of those components. What goes inside those functions? The game logic of course!
Integrating Game logic
Mileage may vary, but I ended up with four game screens of logic to implement. They included the following:
- Main
- GameSetup
- GamePlay
- GameResults
Each of these screens acts as a parent container, overseeing the commonly shared state and components. This structure allows for different logic on each screen while keeping consistent global conditions and state management.
Let’s take a look behind the scenes.
Centralized State Management: React’s state management is key in handling global conditions like player scores, game levels, or overall game stats. This ensures that any changes in state, like player progress or game settings, are consistently updated throughout the game.
Component-Specific Logic: Each screen will have its own unique logic, controlling the behavior and interactions within that specific part of the game. For example, a home screen might handle player registrations, while a gameplay screen manages the actual game mechanics.
React Hooks for Lifecycle and State: React Hooks are crucial for managing the lifecycle and state of your components. The useState
hook allows you to add state to your components, while the useEffect
hook is perfect for handling side effects like fetching data or subscribing to a service. For game development, you might use these hooks to control animations, trigger sound effects, or update the UI based on user interactions.
Implementing the Game Loop: Incorporating the game loop within React involves setting up a process that continuously updates the game state and UI. This could be managed through a combination of useEffect
for triggering updates and useState
to reflect the current state. For example, in a turn-based game, your loop might check for player actions, update the game state, and then render the updated state to the user.
Global Conditions Handling: Global conditions like winning criteria, game over scenarios, or high scores are managed through centralized state logic. By setting up global conditions, you ensure that the game reacts appropriately to certain events, regardless of the screen or component in focus
Once you have a good understanding of how to implement your game logic, start to build out each of the components and elements from your list with pseudocode and comments for any sections that you’re unsure about. The goal is to make progress without getting stuck on one component or function for too long. You can always come back to work on challenging sections later, for now — keep moving down your checklist!
Sound and Music
We’ve reached an important and often overlooked aspect of game development — sound and music. My son recently asked me why the sound was so bad in the game he was playing, almost like it was an afterthought. And I said, “It probably was!” — which is sadly the case with game audio. Due to a combination of time and financial constraints, game audio is often brought in too late, and on too small of a budget to enhance the game in the way that it should.
Audio adds a rich layer to the gaming experience, enhancing our emotional connection to the game and its characters. Understanding how to effectively synchronize audio with game events is an important part of creating a dynamic and fun gaming experience.
However, working with audio in React presents its own set of challenges from handling audio file formats to managing playback in the browser.
Yes, there are plenty of excellent React audio players to try for a project like this. Feel free to explore those options. If you’re a purist like me, then you’ll be spending time working with the HTML5 <audio>
element.
I decided to make a game based around a hook of streaming music, then having players describe the song by typing words into an input that matches the song's “vibe” for points. Getting the input mechanics to work was tricky but nothing too crazy — the audio, on the other hand, was not.
The irony of being an audio engineer working as a web developer realizing that I didn’t have much experience working with audio in the browser was not lost on me. As I’m sure you’ve realized, there isn't much music on websites because it’s equal parts annoying and invasive. That presents a significant hurdle when using a browser for game development — its restriction on auto-playing audio. Audio can't play in modern browsers until the user interacts with the page.
This limitation means that developers need to design a user interaction (like a click or a tap) as a trigger to start audio playback. This could be a ‘Start’ button at the start of the game or any interaction within the game’s UI. While this adds an extra step for the user, it can be seamlessly integrated into the game’s flow as part of the initial game start sequence.
Another challenge is browser compatibility. Different browsers may have varied support for audio file formats, creating a situation where sounds might play correctly in one browser but not in another. This means you should only use universally supported formats like MP3 or provide multiple audio file sources in different formats.
Adding audio can turn a good game into a great one! Even if your game isn’t audio-focused, having background music and sound effects will enhance the user experience. However, integrating audio in React and managing it within the browser environment is challenging.
(I had to resubmit my game for React Jam after fixing a major bug with Safari browsers.)
With the essential groundwork laid, we’re ready to rock onto phase three of our React game development journey. It’s crucial to ensure that you’re completely invested in your game concept, game loop, and have the right project structure because there’s no going back at this point. You should have all of your game screens and main components completely built out and ready to start testing before moving on.
Be sure to subscribe to this blog and follow me on social media to be notified about the next chapter of “Ready Player React: How to Build a Video Game with JavaScript.” In the next installment, we’ll move into the crucial third phase: Testing.
Sources:
https://en.wikipedia.org/wiki/Test-driven_development