Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Game Programming using JavaScript, React, Canvas2D and CSS – Part 1

4.56/5 (4 votes)
3 Nov 2018CPOL5 min read 13.7K  
How to create simple 2D games using JavaScript, React, Canvas and CSS

In this series, I want to show you how to create simple 2D games using JavaScript, React, Canvas and CSS. We will make a simple Space Invaders clone, you can check out the end result on https://phillikus.github.io/react_invaders/. To check out the full code, go to https://github.com/phillikus/react_invaders.

In this part, we will setup the project, add a TitleScreen component and a class to handle user input.

Requirements

Setup

First of all, we have to setup our project. To do this, go to your Projects folder and run the following command:
create-react-app react_invaders and open up the folder in your favourite JavaScript IDE (I like to use Sublime Text). You should see a project structure like this:

Now (or anytime you want to test our progress), we can run npm start from the command line to deploy our app locally. When you open the link from the command line in your browser, you should see a react welcome page.

Next, we will remove this welcome page so we can start adding our own content. To do this, open the App.js file in the project and replace the code from the render() method with an empty div:

JavaScript
render() { 
   return (<div> </div>);
}

The render() method is the central place for all our rendering logic. It contains pseudo-HTML code (JSX) which will then be translated to real HTML in the browser.

We can also delete the logo.svg file and remove import logo from './logo.svg'; from App.js. After reloading the application in our browser, we should now see an empty page.

Drawing the Game Canvas

Our entire application will run inside an HTML canvas. To draw an empty canvas, we have to define its width, height and aspect ratio. For now, we will define 3 constants for that:

JavaScript
const width = 800;
const height = window.innerHeight;
const ratio = window.devicePixelRatio || 1;

We will place them right above our class definition. We can now use these constants to initialize the state of our App class. To do so, we have to add a constructor to our App class and initialize the state with these variables:

JavaScript
this.state = {
screen: {
    width: width,
    height: height,
    ratio: ratio
}};

I encapsulated the mentioned properties in a new screen property. Remember that we can initialize the state of a React component like this only in the constructor!

Next, we have to adjust the render method to draw a canvas with these values:

JavaScript
render() {
    return (
       <div>    
          <canvas ref="canvas" 
             width={ this.state.screen.width * this.state.screen.ratio } 
             height={ this.state.screen.height * this.state.screen.ratio } />
      </div>
    );
  }

Finally, we have to add some CSS to the already existing App.css file to set the color and align our canvas properly:

CSS
canvas {
  display: block;
  background-color: #000000;
  margin-left: auto;
  margin-right: auto;
}

That’s it! Reload the app in your browser, and you should see a black empty canvas with the defined width and height. It should be positioned right in the middle of the screen.

Adding a TitleScreen Component

Now, it’s time to start adding some content to our freshly created canvas. To do so, we will add a new React-component to display the title-screen. To keep our React-components in one place, we will first add a new folder ReactComponents in the /src folder. Then, we can add a new TitleScreen.js file to this folder:

JavaScript
import React, { Component } from 'react';

export default class TitleScreen extends React.Component {
	render() {
		return (
			<div>
				<span className="centerScreen title">React Invaders</span>
				<span className="centerScreen pressSpace">Press Enter to start the game!</span>
			</div>
			);
	}
}

That’s all! We only need a render method that returns our Welcome messages and some CSS to format it properly. Just make sure that you are inheriting from React.Component. I used the following CSS (inside App.css), feel free to play around with it:

CSS
.centerScreen {
  text-align: center;
  display: block;
  position: absolute;
  z-index: 1;
  width: 100%;  
}
.title {
  top: 20%;
  color: green;
  font-size: 80px;
}
.pressSpace {
  top: 35%;
  font-size: 20px;
  color: #ffffff;
}

Now we can import and use this class from our App.js file. To do so, we will first add an import line for our new component:

JavaScript
import TitleScreen from './ReactComponents/TitleScreen';

Finally, we can add this new component in the render method:

JavaScript
render() {
    return (
      <div>
        <TitleScreen />
        <canvas ref="canvas"
         .....

Reloading the page, we should now see the title of our game followed by instructions on how to start playing. Since our game doesn’t respond to any user input yet, pressing the Enter key won’t have any effect.

Handling User Input

Now that we have our first component up and running, it is time to think about handling user input. In JavaScript, we can intercept user input using the keyup and keydown events. To keep the logic for user input separated from our game logic, we will add a new class InputManager.js to the /src folder. The keyup and keydown events provide a simple integer to identify the pressed key. To make more sense of these keyCodes, we will first add a constant to give some better names to these keyCodes:

JavaScript
const KEY = {
   LEFT:  37,
   RIGHT: 39,
   A: 65,
   D: 68,
   SPACE: 32,
   ENTER: 13
};

You can check out the keyCodes on http://keycode.info/.
In the constructor of our InputManager class, we will add a new property pressedKeys which we will later use to see what keys were pressed by the user:

JavaScript
constructor() {
   this.pressedKeys = { left: 0, right: 0, space: 0, enter: 0 };
}

We will use the left and right keys to move the player, space for shooting and enter to navigate between menus.
Now, we will add two functions to bind the key-events when we open the app and to unbind them afterward. They will be responsible for binding these to a new handleKeys method, which will in turn be responsible for setting the pressedKeys property mentioned above:

JavaScript
bindKeys() {
   window.addEventListener('keyup',   this.handleKeys.bind(this, false));
   window.addEventListener('keydown', this.handleKeys.bind(this, true));
}

unbindKeys() {
   window.removeEventListener('keyup', this.handleKeys);
   window.removeEventListener('keydown', this.handleKeys);
}

handleKeys(value, e){
   let keys = this.pressedKeys;
   switch (e.keyCode) {
      case KEY.LEFT:
      case KEY.A:
         keys.left  = value;
         break;
      case KEY.RIGHT:
      case KEY.D:
         keys.right  = value;
         break;
      case KEY.SPACE:
         keys.space  = value;
         break;
      case KEY.ENTER:
         keys.enter = value;
         break;
    }
    this.pressedKeys = keys;
}

For the keyup event, we call <handlekeys> with the value false and for the keydown event with true. For example, when the A key is pressed down, handleKeys will be called with value==true and e.keyCode == KEY.A, so keys.left will be set to true; As soon as the key is released, the keydown event is triggered and keys.left is set back to false.

This completes our InputManager class and we are now ready to integrate it into our App.js class. First, we have to add an import statement for it:

JavaScript
import InputManager from './InputManager';

Then, we will initialize a new instance of this class in the constructor and assign it to a new state property:

JavaScript
constructor() {
  super();
  this.state = { 
    input: new InputManager(),
      screen: {
        width: width,
        height: height,
        ratio: ratio        
      }
    };
  }

To call our bindKeys and unbindKeys methods, we will override the componentDidMount and componentWillUnmount methods provided by React. They will be called when we load and unload our app, respectively:

JavaScript
componentDidMount() {
  this.state.input.bindKeys();  
}

componentWillUnmount() {
  this.state.input.unbindKeys();
}

That is all we need! If you set a breakpoint in handleKeys in your browser developer tools, you will see that it is triggered whenever you push/release some key.

In the next part of this series, we will add a player-controlled ship class, game state management, a Game Over screen and an overlay to show the controls of the game!

Thank you for reading this article :) If you have any questions, problems or feedback, please let me know in the comments.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)