[Hooks]A game with React? Moving an element on a grid.

[Hooks]A game with React? Moving an element on a grid.

I do not have a clue where this is going.

Hello there!

Today, I wanna share a little experiment I made with React, started as just a test and might end up with a really cool outcome. Don't know yet. Still on it.

So anyways, after working a little bit on functional components, I wanted to see how I could use them to actually, let's say, create a simple 2D game like, I don't know, some kind of snake or block breaker or Tetris Clone or something like that. Nothing fancy for a first try, but this would already be very cool :)

Never done that before, so it took me some time to figure out where to start, but then I realized, hey, first things first, I need to be able to move an element around the screen. In all those games, you get moving blocks, sometimes you move them around with clicks or keypresses, sometimes they move by themselves (Shady business if you ask me).

So I went on and started a new react App. I figured I would use Redux Later on so I used this command but standard create-react-app with added Redux later on is fine as well.

# Redux + Plain JS template
npx create-react-app my-app --template redux

I also added sass support(even if we're not gonna use it for now)

npm install node-sass --save

and, figuring I would need a grid to move things on, created simple styling for it in a base.scss file:

.grid {
    height: 100vh;
    width: 100vh;
}

#root {
    height:100%;
    display:flex;
    flex-direction:row;
    justify-content:space-around;
}

Then I also created the styling for our "moving Object"

.fancy-square {
    height: 1vh;
    width: 1vh;
    background-color:black;
    position: relative;
}

So our Square is going to be positionned on a grid which is a square with size equal to 100% of the viewport height.

The square is going to be relatively positioned on that grid, but you can see that for now, it just have a size (1vh, which is 1% of the size of the grid)

Because, as you might already guessed, the positioning is going to be made inside the component.

So let us create a component in the src/component folder, and call it MovingObject.jsx. First, we create a base structure for it:

const MovingItem = (props) => {
    /*Some very clever and well written code*/
    return (
        /*Some JSX content that will render on screen*/
    )
}
export default MovingItem

You see i've given the component props. This is because I'm actually planning on giving the component initial values for X and Y axis positioning of the Moving Object. Let us do that:

const MovingItem = (props) => {
    /*Some very clever and well written code*/
    return (
        /*Some JSX content that will render on screen*/
        <div className = "fancy-square" style = {{top:props.yPosition + "vh", left: props.xPosition + "vh"}}></div>
    )
}
export default MovingItem

Now, all we have to do is to call the component from the app.js file:

function App() {
  return(
    <div className = "grid" >
      <MovingItem xPosition = {50} yPosition = {50}/>
    </div>
  )
}

export default App;

And now, we have a black square positioned in the middle of the screen. Great success. Now, let's move this square around a bit.

How we're gonna do it

Well, we need to tweak the MovingObject component a bit:

  • First, we need to create hooks so the component can re-render whenever coordinates change. Hooks will be created using useState, and their value initialized by the one we got in props. This way we have an object capable of moving, and a customizable start position.

  • Second, we need to add an event on keyDown to capture user input and define an action for each keyboard arrow. Each action will be to update the relative hook value (For arrow left/right we update xPosition, and for arrow down/up, we update yPosition.

  • Third, we need to give focus to our element so that whenever it is rendered, the object is given the focus so it can catch the next keyDown event. This will be made using the useEffect hook and we'll use refs to target the object.

Creating the hooks

So we have to import useState first, then define local state values for the coordinates of our object on the grid.

import { useState } from 'react'

const MovingItem = (props) => {
    /*Some very clever and well written code*/
    //Creating hooks for xPosition and yPosition: this will allow the component to re-render whenever coordinates change.
    const [xPosition, setXPosition] = useState(props.xPosition);
    const [yPosition, setYPosition] = useState(props.yPosition);
    return (
        /*Some JSX content that will render on screen*/
        <div className = "fancy-square" style = {{top:yPosition + "vh", left: xPosition + "vh"}}></div>
    )
}
export default MovingItem

See what we did there? Not much :)

The component will render exactly the same as before. HOWEVER, instead of using the props directly when rendering, we use the hooks, initialized with the props. Now, thanks to the hook, whenever the value of a hook changes (or as we might say, whenever the state changed, the component will update).

Now, let us define a onKeydown event for the object. This event will be triggered any time a key is pressed, whenever the object has the focus on the page. This event will call a function whose purpose is to check the key that has been pressed, and alter one of the coordinates by 1.

This is how it is done:

import '../styles/fancySquare.scss'
import { useState } from 'react'
import { useRef, useEffect } from 'react';

const MovingItem = (props) => {

    //Creating hooks for xPosition and yPosition: this will allow the component to re-render whenever coordinates change.
    const [xPosition, setXPosition] = useState(props.xPosition);
    const [yPosition, setYPosition] = useState(props.yPosition);

    //handling position changing scenarios in a function

    const moveAround = (key) => {
        if (key === "ArrowDown") {
            setYPosition(yPosition+1)
        } else if ( key === "ArrowUp") {
            setYPosition(yPosition-1)
        } else if ( key === "ArrowLeft") {
            setXPosition(xPosition-1)
        } else if ( key === "ArrowRight") {
            setXPosition(xPosition+1)
        }
    }

    //Returning the square element   
    return <div tabIndex = '0' className = "fancy-square" style = {{top:yPosition + "vh", left: xPosition + "vh"}} onKeyDown = { e => {
            e.preventDefault();  
            moveAround(e.key)
    }} ref={inputRef}></div>
}
export default MovingItem

Now, what we do here is two things:

  • Create the keydown event on the "Fancy square" and then make it call a function named moveAround that takes on parameter, the key that was pressed.

  • Create the moveAround function, and make it change coordinates according to the direction the user selected.

And now we've got a working... nothing

Yeah there is one more thing we need to do remember, it is to give focus to the object, because until we do that, the event listener won't be working its magic.

And to do that, we have to rely on ref. When we create an element in the react dom like our fancy-square

, we can give it a reference, and this reference can be used to target the element, which is what we need as we want to give it focus.

Once we define this reference, we can actually give focus to the component everytime it renders, ensuring it can listen to the user input. To that effect we use useEffect to define what happens just after the component renders.

import '../styles/fancySquare.scss'
import { useState } from 'react'
import { useRef, useEffect } from 'react';

const MovingItem = (props) => {

    //Creating the reference we'll assign to the square.
    const inputRef = useRef();

    //Adding behavior on render: give focus to the square.
    useEffect(() => {
        inputRef.current.focus();
      })

    //Creating hooks for xPosition and yPosition: this will allow the component to re-render whenever coordinates change.
    const [xPosition, setXPosition] = useState(props.xPosition);
    const [yPosition, setYPosition] = useState(props.yPosition);

    //handling position changing scenarios in a function

    const moveAround = (key) => {
        if (key === "ArrowDown") {
            if (yPosition <= 98) {
            setYPosition(yPosition+1)
            }
        } else if ( key === "ArrowUp") {
            if (yPosition >= 1) {
                setYPosition(yPosition-1)
                }
        } else if ( key === "ArrowLeft") {
            if (xPosition >= 1) {
                setXPosition(xPosition-1)
            }
        } else if ( key === "ArrowRight") {
            if (xPosition <= 98) {
                setXPosition(xPosition+1)
            }
        }
    }

    //Returning the square element   
    return <div tabIndex = '0' className = "fancy-square" style = {{top:yPosition + "vh", left: xPosition + "vh"}} onKeyDown = { e => {
            e.preventDefault();  
            moveAround(e.key)
    }} ref={inputRef}></div>
}
export default MovingItem

And NOW we do have a moving square, it can go left, right, up, down, it is pretty cool, we also have boundaries for coordinates so that our square does not escape the grid. You're welcome to try it but believe me: there is no way out the grid.

You can try it live here: clementbenezech.github.io/grid-experiment

Pretty much the same, except the square is a giant Borat head moving through space.

So anyways, that will be all for now!

However this ain't over and I'll be sure to come back with more features for this, now that we have an element we can move across the grid, maybe we add some other items, maybe some of them could block the way, maybe some of them could hurt the player when he touches them, this mean probably maintaining some kind of grid state using Redux to handle to be able to tell when objects collide at the same coordinates, so yeah, pretty of cool stuff to do here with hooks and Redux :)

Hope you enjoyed!