© Frank Zammetti 2020
F. ZammettiPractical JAMstackhttps://doi.org/10.1007/978-1-4842-6177-4_7

7. JAMstack for Fun and… Well, Just FUN!

Frank Zammetti1 
(1)
Pottstown, PA, USA
 

In the immortal words of one Homer J. Simpson, “All work and no play makes Homer something, something.” Well, Homer, I agree! We can’t sit around making business applications all day; we gotta sneak some fun in there every so often!

In my capacity as a team lead, I’ve been asked several times by junior developers what they can do to improve their skills. My answer has always been the same: make games! It sounds weird, but the truth is that games force us to overcome challenges in ways you can’t anticipate. Games require us to use all our skills in novel ways, from data structure and algorithms to time and space complexity, from data flow to object design.

Games are a learning exercise, and they also by their nature are fun to make! After all, games are meant to be fun to play, so shouldn’t they be fun to make too? I think so!

With those thoughts in mind, the final project we’re going to tackle in this book is a game. It will give you a somewhat different perspective on Gatsby, React, and JAMstack and will introduce a few new things in the process. It’ll be a good learning experience while also being enjoyable to do.

So, let’s figure out what this game of ours is going to be and then get into actually putting it together!

A Short Time Ago, in a Galaxy Right Around the Corner…

Okay, here’s the story: you’re the lowest-ranking astronaut on a ship whose job it is to steal energy orbs from alien civilizations (because isn’t that always how it is?!). Yeah, you know the drill: you’re a disposable red shirt!

Of course, the aliens who own these energy orbs are not too happy about this, so you’d better run for your life!

There’s just one catch: you have to navigate a series of stone pathways suspended over lava (yikes) which, for some reason, seems to be a common defense strategy by all energy orb-possessing alien species. Why that may be is a discussion for another day because there are two far more pressing concerns. First, if you and the orb you’re carrying at any given moment fall into the lava, not only will you most certainly be dead (no giant eagles to save you here!) but second, you’ll also create a rupture in the spacetime continuum that will destroy the universe (and to be clear: that’s bad!).

Finish a planet, and you’ll move on to the next and do it all over again – at least until you die… or are promoted into a better position (presumably – that possibility is covered in the inevitable sequel – #SixSeasonsAndAMovie!).

To play, you use the left, right, and up arrow keys to jump left, right, and straight up, respectively. Get to the end, and you’ll get beamed up to your ship, safe and sound, ready for the next heist… err, retrieval!

It’s not a complicated game to be sure – it’s certainly not going to compete with the Halo’s and FIFA’s of the world – but it’ll get the job done for our purposes here, which is to learn and gain experience with JAMstack.

And, of course, to have some fun!

So, what does this game, which I’m calling JAMJumper, look like? Figure 7-1 provides that answer, and hopefully, it’s not much different than what you imagined reading the description.
../images/497584_1_En_7_Chapter/497584_1_En_7_Fig1_HTML.jpg
Figure 7-1

JAMJumper, in all its glory (trust me, it looks better in color)

As you can see, there’s not much to it. The main game area is on the left, a title, and instructions on the right. Your score is below that and, of course, a button to start the game with. Once you click the button, the stone walkway begins to scroll (along with the fire in a nice parallax manner), and you can jump as needed to stay on the stone pathway until you reach the end (here, the endpoint, a teleport pad, is visible). You get some points as you go automatically, plus a bonus when you reach the end. There are several levels to navigate, and if you finish them all, there’s another score bonus. The player will just try to survive to get the maximum possible score; that’s the end goal.

As Captain Kirk Says: “Fire Phaser!”

Okay, now that you know what we’re building, how are we going to accomplish it? Well, we’ll use Gatsby for this, and we’ll also be hosting it on Netlify. But that will come in the next chapter. Before we get to that, we have to talk about building HTML5 games in general and one particular library for doing so: Phaser.

While it is 100% possible to build a web-based game with plain old HTML, CSS, and JavaScript, like so many things in web development, there’s great value in choosing a library, toolbar, or framework to help. Game development is no different, and, in that arena, few things stand out above Phaser.

Phaser is a game framework that is 100% free and open source that is specifically geared to writing 2D games. It is extremely fast, allowing your games to perform exceptionally well on most devices. Honestly, it can be rather shocking how good Phaser games run!

Phaser, the website for which can be found at https://phaser.io, does all its magic using WebGL when the browser supports it (which is most of them these days) and falls back on Canvas otherwise. In other words, Phaser isn’t using the DOM (except at a basic level). That’s a big part of why it’s so fast: it’s really just pushing pixels around at a low level (relative to a web page and the DOM anyway). In fact, in many cases, maybe even most, this will all be hardware-accelerated, so the performance can be remarkably close to native. The only real bottleneck is JavaScript itself, but that too can be surprisingly fast when the DOM isn’t really involved.

Phaser provides a great deal of functionality that games require including things like sprites (including animation), a physics engine (which includes collision detection), a particle engine for real-time particle effects, tweening (for animations of all sorts), audio, input control, primitives drawing (geometric shapes and such), tiles, text, and more. It’s all cross-platform, pretty easy to use, and very well performing. Phaser is an extremely robust platform on which to create games (though note that there’s nothing that says you have to build games with it – you could make just about any app you wanted with it, though it clearly is geared toward games).

As you may have guessed, we won’t be going into excruciating details on Phaser here. Ultimately, this isn’t a book about game development, so that wouldn’t make sense. No, you’ll get a good look at the basics as we progress into the code, but there will be a lot of Phaser surface area not touched. If game development is an interest of yours, then spend some time looking at the Phaser website to see all it offers, including some examples of games built with it that I think you’ll find extremely impressive.

Before we get into the code, though, and see the basics of Phaser, we need to create a basic project to build on, so let’s do that now.

Note

I’m expecting at this point that you’ll pause here and play the game a little bit (just execute gatsby develop and then access it at localhost:8000, like always) to get a little familiarity with what it is and how it plays. That will give you context for the rest of this chapter that I think you’ll need to understand what I’m talking about throughout.

Starting from “Hello, World!”

Once again, we’ll use Gatsby for this project, but also once again, we won’t be using a comprehensive template. At the time of this writing, no Gatsby template includes Phaser, and in fact, none I could find is even geared toward making a game. So, instead, we’re going to start with the gatsby-starter-hello-world starter:
gatsby new JAMJumper https://github.com/gatsbyjs/gatsby-starter-hello-world
This is even more minimal than the starter we used in previous chapters. In fact, you’ll find (assuming the starter hasn’t changed since this writing) that there is only a lone source file, index.js, and it’s just the following:
import React from "react"
export default function Home() {
  return <div>Hello world!</div>
}
If you then look in gatsby-config.js, it’s just as minimal:
module.exports = {
  plugins: [],
}

Yep, it doesn’t get any more basic than that! There are no other Gatsby config files by default with this starter. The only bit of cleanup to do is to delete the favicon.ico in the static directory because we won’t need it, and we’ll be using something else for the same basic purpose in the next chapter. However, leave the static directory itself because that we will use – more on what that is later!

Finally, the only dependencies in package.json are gatsby, react, and react-dom, the bare necessities for a Gatsby project. This is perfect for our needs! We can begin adding on to this as needed without having to clean anything up like for past projects, and the first thing we’ll need to add is Phaser itself:
npm install --save phaser

With that, we’re ready to start building JAMJumper!

The Starting Gate: index.js

Of course, the index.js file created by the hello-world starter has to be replaced altogether. For this project, we are going to accomplish what we just barely missed accomplishing with the last project: this will be an actual SPA! Of course, in this case, that’s not saying all that much given that there’s only ever one screen that the user will see. Still, by the letter of the definition, that’s what JAMJumper is.

As usual, we begin with some imports:
import React from "react";
import * as gameCore from "../gameCore.js";
import { graphql } from "gatsby";
The only one you haven’t seen before is gameCore.js. That’s where the majority of the code for this project is and what we’ll be dissecting that right after we’re done with index.js. But, we still need to create a React component for this single page, and that’s what we do next:
export default class IndexPage extends React.Component {
That looks pretty boring by now, I hope! So, we’ll move on to the constructor for the IndexPage component:
constructor(inProps) {
  super(inProps);
  global.levelData = inProps.data.allLevelsJson.edges;
}

As always, we call super() to ensure React has everything it needs to be happy. As you’ll see a bit later, every level in the game is defined by some data that is in a JSON file, one per level. There are actually two ways we could deal with these, and I’m going to show you both. This is the first way: they will be read in by Gatsby and made a part of the data model for the site, which means we can write a query to access them. The query, since it’s a page query, will be at the end, but here is where the query results are used. As you’ll recall, Gatsby automagically includes the query results in the props passed to the constructor. In this app, unlike the previous one, that data is not used by the component itself, or any of its children. Instead, it’s going to be used by the code in that gameCore.js file I mentioned. So, we have to make the data available to that code. There are a couple of ways that can be done, but I felt that the simplest is just to add it to the global object that React provides us as a global namespace. In a small project like this, I’m not worried about global-scope pollution or any of those sorts of concerns.

And don’t worry, I haven’t forgotten: I’ll show you the second way to deal with the JSON level data files when we get to the gameCore.js code. Before that, we have to add a componentDidMount() method to this component:
componentDidMount = () => {
  gameCore.init();
};

Here’s the basic idea: if you were writing as plain HTML/JS/CSS app with Phaser, you would need to initialize some things (which you’ll see later) when the DOM is ready. If you try to do it before, it won’t work. The same is true here, but with React, you learned in the last chapter that componentDidMount() must be used for that task. That’s true here too. The init() function in gameCore.js is what kicks everything off, what gets Phaser involved, and starts the game up, but none of that can happen until the DOM is done being built by React; hence, componentDidMount() comes into play again.

After that, it’s time to render our component:
render() {
  const pStyle =
    { paddingTop : "10px", paddingBottom : "10px" };
  const hrStyle =
    { marginTop : "10px", marginBottom : "10px" };
Even before rendering the component, I know that there are a couple of styles that are going to be used several times, so I define them here. Making them objects like this means that I can write
<hr style={hrStyle} />

The hrStyle object will populate the style prop for the <hr> element exactly like you’d expect. It’s just another syntactic way of doing styles.

Next, the component’s return value is begun:
return (
  <div style={{ display : "grid",
    gridTemplateColumns : "624px 380px", gap : "10px" }}>
    <div id="gameContainer"
      style={{ border : "20px inset #c0c0c0",
        padding : "4px 0px 0px 4px" }} />

For JAMJumper, there’s nothing fancy here. There’s no Material-UI, no React components except for the one that is parent to everything else. All we’ll be returning is plain old HTML. The topmost element, a <div>, is what provides the overall structure to the page, which is a two-column layout where one column is 624 pixels wide and is where the gameplay area is, and the second is 380 pixels wide and is where our instructions, score, and one control button is.

The gameplay area, which is where Phaser will render the game content later, is the first child <div>. I give it a thick, inset border to try and sort of mimic an arcade machine look.

After that is another <div>, this one representing the second column:
<div>
  <h1>JAMJumper</h1>
  <h2>A simple, silly little JAMstack game.</h2>
  <hr style={{hrStyle}} />

As you can see, it’s just basic HTML now, and you can see the first instance of those styles defined earlier being used.

The explanation of the game begins next:
<p style={{pStyle}}> Okay, here's the story: you're the lowest-ranking astronaut on a ship whose job it is to steal energy orbs from alien civilizations (because isn't that always how it is?!)</p>
There are several more paragraphs like this, but just to spare a few trees, I’m going to refrain from listing them here. Let’s pick up after that with the divider below the description and the instructions:
<hr style={{hrStyle}} />
<p style={{pStyle}}>Use the left, right and up arrow keys to jump left, right and straight up.  Get to
the end and you'll get beamed up to your ship, safe and sound, ready for the next heist... err, retrieval!</p>
<hr style={{ ...hrStyle, marginBottom : "20px" }} />

Well, there’s something a little bit interesting! In this case, I want to use the hrStyle object , but I need to override the marginBottom value so that there is a little more space between the divider and the score. Because the style is defined as an object, I can use the spread operator to “spread” the values from the hrStyle object into the style prop here. Then, the additional marginBottom setting explicitly here overrides whatever was spread into the prop object. It’s a nice, clean way to do overrides like this on an individual element level.

After that comes the area for the score:
<h3 style={{ color:"#ff0000" }}>
  Score: <span id="score" />
</h3>

Again, nothing fancy, just a <span> that the gameCore.js code will insert the current score into.

Finally, since Material-UI isn’t used here, we have to build a faux-button (I could have used a basic HTML button, of course, but I thought this was a little more fun!):
<div onClick={gameCore.startLevel}
  style={{ cursor : "pointer", width : "100%",
    color :"#ffffff", fontWeight : "bold",
    backgroundColor : "#8080ff", textAlign : "center",
    paddingTop : "10px", paddingBottom : "10px",
    marginTop : "50px" }}>Start Game
</div>

Yep, nothing but a <div> with an onClick handler that points to a startLevel function , a function you’ll see a bit later. Then, it’s just some basic styling. There’s nothing tricky there, so I’m going to assume you got it and move on to the page query I mentioned earlier.

Querying for JSON-Level Data

As I said earlier, each level of the game will be stored in a JSON file. These will be in the src/levels directory (aside from the first one, which is in the src directory). One of the two ways we’ll get them into our app is to let Gatsby process them so that we can execute a GraphQL query to get at the data. To do this, we have to bring in a transformer that knows how to read JSON, so add this dependency to the project:
npm install --save gatsby-transformer-json
And, as you saw in the previous project, we’ll also need the gatsby-source-filesystem plugin to actually read the files:
npm install --save gatsby-source-filesystem
As you saw when we dealt with Markdown, there’s a little bit of configuration that must be added to gatsby-config.json to make use of these, and in fact, it looks a lot like the Markdown configuration. Just add two elements to the plugins array:
"gatsby-transformer-json",
{
  resolve : "gatsby-source-filesystem",
  options : {
    name : "data",
    path : `${__dirname}/src/levels`
  }
}
That’s it! Gatsby will now source the JSON files in src/levels and add them to the internal data model. Once that’s done, a page query can retrieve the data:
export const pageQuery = graphql`
  query LevelsQuery {
    allLevelsJson {
      edges {
        node {
          totalScreens
          data
        }
      }
    }
  }
`;

We’ll see the level data later, but you can already tell from this that it has two elements, totalScreens and data. The allLevelsJson element is created by Gatsby and is where the JSON files are found. We just need to dig down through the usual edges/nodes/node structure that is common to most sourced data. The results of this query will be an array where each element in the array is an object defined in each level data file, an object that we’ll look at in context later, so it makes some sense.

Note

Just a reminder that the query name, LevelsQuery, doesn’t really matter, it can be anything and has no special meaning except to you as the developer.

Before that, though, we have some other code to discuss, the beginnings of the code that constitutes the actual game!

The Heart of the Matter: gameCore.js, Part 1

The gameCore.js file is where you find the real meat of this project, the actual game code. It largely follows the basic structure of a Phaser game, which means there is a Game object, which is a class provided by Phaser, which is the primary interface into Phaser. There is also at least one Scene object, where a game can have multiple scenes (though this game only needs one). Think of a scene as the various screens that might appear in a typical game: maybe a menu screen, a character selection screen if it’s an adventure game, then a scene for the actual game (maybe more, depending on the game). This structure is up to you, and in JAMJumper, we’ll just need a single Scene.

A Phaser scene has a standard set of methods that you, as the developer, must provide, which Phaser will then call at various points in the lifecycle of the game and scene to perform specific tasks. Most importantly, those methods are preload(), create(), and update(). We’ll look at each of those in turn (although update() will be looked at in the next chapter). But, before we get to those (and the handful of “support” functions that will live outside of the Game and Scene objects), let’s start from the top.

Basic Structure

The basic structure of the gameCore.js file is pretty typical stuff, beginning with some imports:
import Phaser from "phaser";
import level_01 from "./level_01.json"

Obviously, we’ll need Phaser itself, or we won’t get far with this whole thing! After that, you can now see the second approach that I mentioned there was to working with JSON files: you can simply import them! The level_01 variable will contain an actual JavaScript object, so effectively, the JSON is run through JSON.parse() (I don’t actually know if that’s what the JavaScript engine literally does under the covers, but it’s a good bet, and even if it’s not what it does, it does the equivalent, which is what matters to us as a programmer). The first level is imported this way, and the rest are sourced by Gatsby and read in via the query you saw earlier. It doesn’t make much sense to do one this way and all the rest via query except as a way to show you that you can do both, which was my only goal here!

Note

The level_01.json file is in the src directory, whereas all the other level data files are in src/levels. This is on purpose: it avoids level_01.json being part of the query results and the player winding up having to play through the level twice.

Module-Level Variables

Following the imports, we have a series of module-level variables to declare:
const sizes = {
  tile : { width : 64, height : 64 },
  grid : { columns : 9, rows : 11 },
  astronaut : { width : 54, height : 44 },
  trackPixelsPerIteration : 2
};

First up is an object that contains information about the size of the graphics used in the game (which you’ll find in the static/img directory that we’ll be discussing shortly). The fire in the background and the stone track are created as a grid of tiles. Here, we can see that this grid is 9 columns by 11 rows. Each tile has a width of 64 pixels and a height of 64 pixels. If you do some multiplication here, you realize that the game area is 9*64=576 pixels wide by 11*64=704 pixels tall. Our astronaut has a width of 54 pixels and a height of 44 pixels, which you’ll note are even numbers. That allows for an even number of pixels around the astronaut when centered on any tile.

Next, we have to create a configuration object that Phaser will use to create our Game and Scene objects later:
const config = {
  type : Phaser.AUTO,
  parent : "gameContainer",
  width : sizes.tile.width * sizes.grid.columns,
  height : sizes.tile.height * sizes.grid.rows,
  fps : { target : 60, forceSetTimeOut : true },
  physics : { default : "arcade" },
  scene : {
    preload : preload, create : create, update : update
  }
};

The type attribute tells Phaser to choose between its WebGL and Canvas renderer as appropriate for the browser. The parent attribute is the ID of the element on the page where the game will be rendered (from index.js). The width and height are, of course, the size of the area the game will take up, and you can see the calculation done as described before. The fps attribute tells Phaser how many frames per second we want to render. We target 60 here, and as long as our code isn’t terribly inefficient, Phaser will be able to ensure that happens on any halfway decent systems.

The forceSetTimeOut option tells Phaser to use setTimeout() rather than requestAnimationFrame() when rendering each frame. Usually, you would want requestAnimationFrame() to be used because it leads to a somewhat smoother gameplay. However, for JAMJumper, it’s critical that we know exactly how long each iteration will take, something you can’t be sure of with requestAnimationFrame() because it is time-based rather than target frames per second-based. Using setTimeout() instead ensures that each frame will have exactly 1000/60=16.6 milliseconds per frame, and we can move things on the screen knowing that. It’s just a slightly easier way to code movement in graphics, and for a simple game like this, it works well enough.

The physics attribute indicates to Phaser that we want the physics engine to run and that we want it to run in arcade mode. We’ll need physics for one purpose: collision detection. As such, we’re not simulating gravity or anything, so the more straightforward arcade mode is suitable. There is also a matter mode , which allows for much more complicated physics bodies defined for game elements and much more complex activities like simulations of gravity and how it affects different types of physical objects. There is also an impact mode, and that appears to be a mode somewhere in-between the two.

Finally, the scene object is where you define one or more scenes. This can be an array or an object, and since we just have a single scene, it’s just an object here. This object references the three methods I mentioned earlier, referencing functions you’ll find in this module and that we’ll look at very soon.

Next up, we have variables that will reference the Game object:
let game = null;
And, one to reference the Scene object:
let scene = null;

Those will be created a bit later, but having them in module scope like this ensures that they are accessible to all the functions to follow.

Next, we have an object that stores most of the current state of the game at any moment in time:
const gameState = {
  currentLevelIndex : 0, currentLevel : level_01,
  gameRunning : false, score : 0, trackMoveCount : 0
};

This tells us what the current level is (as an index into the array that comes from the GraphQL query in index.js), as well as containing a reference to the actual object that houses the level data in the currentLevel attribute . We also have an attribute that tells us if the game is currently running (false before the user has clicked the Start Game button or after they have died), and the current score. The trackMoveCount attribute will be described later as it wouldn’t make much sense to you right now.

Next, we have an object that will store references to the graphics needed:
const images = {
  track : null, astronaut : null, endTile : null
};

The track attribute is going to wind up being an array of static images, as you’ll see soon. The astronaut attribute is a sprite, which is an image that has multiple frames of animation. The endTile attribute is a reference to the special track tile that is the end that the astronaut must reach to end a level.

Next up, what game is any good without sound? That’s right, JAMJumper will have audio too:
const sounds = { running : null, jump : null, death : null };

There are just three sounds: running, which is a footstep sound any time the astronaut is running (but not jumping). The jump sound is obviously when he jumps, and the death sound is when he meets his early demise. You’ll see these loaded later.

Next, each of the fire tiles needs to move in the background, and that’s going to be done with tweening, which is a way for us to tell Phaser “change property X of this object over time as I specify.” In this case, we’ll be telling it to move the vertical position of the fire tiles:
const fireTweens = [ ];
Of course, this game wouldn’t do much if we didn’t have some controls:
let keys = { left : null, right : null, up : null };

Each of these flags in this keys object will get set with the corresponding cursor keys are pressed, as you’ll see later, and our code will react accordingly to them.

Speaking of controls, jumping is the one game mechanic the player is in control of, and we have an object named jumpData with all the data about jumping:
const jumpData = {
  isJumping : false, direction : null, phase : null,
  jumpTicks : null, tickCount : null,
  DIR_UP : 0, DIR_LEFT : -2, DIR_RIGHT : 2, PHASE_UP : 0.12,
  PHASE_DOWN : -0.12
};

Obviously, we need a flag to tell the code when the player is jumping. We then need one to tell us whether they are jumping left, right, or straight up, and that’s the direction of the jump. We also need to know whether the player is going up or coming back down, which I term the phase. The jumpTicks and tickCount are values that change as the player is jumping so that the code knows when to switch phases (when they’ve jumped as high as they can and now need to come back down) and when they land again. You’ll see how these are used later. The remaining attributes are simply constants. The value of direction will be one of DIR_UP, DIR_LEFT, or DIR_RIGHT, while the value of phase will be one of PHASE_UP or PHASE_DOWN. The values are specifically chosen here to make the timing of the jump work right, as you’ll see later.

When the player dies, they tear a rupture in spacetime. To make that happen, we’ll use Phaser’s particle system. The key component of the particle system is something called an emitter. Although there’s only one, I create an object to contain a reference to it, just for consistency with the images and sound:
const emitters = { rupture : null };
To avoid doing DOM lookups over and over again when updating the score, a reference to that <span> is cached in the scoreSpan variable:
let scoreSpan = null;
Finally, we will need a flag to know when a click on the Start Game button should start a new game:
let restartGame = false;

Obviously, there’s still a lot you don’t know at this point, so most of these variables and objects don’t have full meaning to you. Don’t worry, as you see them used, I think it’ll become clear very quickly.

And, you’re about to see the first such example in the next section!

The init() Function

The init() function , if you remember back to index.js, is called from the componentDidMount() function. It’s quite simple:
export function init() {
  scoreSpan = document.getElementById("score");
  game = new Phaser.Game(config);
}

Until this function executes, there is no game. A new Phaser.Game object is created here, passing it the config object from earlier. Phaser takes over from there, building the display and taking control of (almost) everything that happens from this point on is, either directly or indirectly. In essence, React is out of the equation at this point, its job done in index.js to render the basic layout and the <div> that Phaser will render the game into.

Of course, displaying the score is something our game code will have to handle, so here is where we get a reference to the <span> as described before.

The preload() Method

The first of the three methods that are needed to satisfy Phaser is the preload() method . While you theoretically could get away without this (as well as the create() method), that would be a nonstandard way to use Phaser, and you may well run into issues. It’s better to just stick with what you’re supposed to do and provide one:
function preload() {
  scene = this;

The goal of this method is to load any audio and graphical assets that your scene requires. As such, Phaser will call this first. Remember that JAMJumper only has a single scene, so here we’re going to load everything that the entire game needs. For a more complex game, you would limit what you load here to just what a particular scene needs. However, before we load anything, a reference to the scene is stored. This will make some of the code later a lot simpler.

After that, the actual loads begin:
this.load.image("track", "img/track.png");
this.load.image("trackBlank", "img/track_blank.png");
this.load.image("trackEnd", "img/track_end.png");
this.load.image("ruptureParticle", "img/ruptureParticle.png");

The Phaser Scene object provides a variety of methods for loading various assets, but load.image() – along with load.audio(), as you’ll see – is probably the most used. As their names imply, they are for loading images and audio assets. These make Phaser aware of them and give Phaser a chance to load them. Each image is given an identifier, like track, trackBlank , trackEnd (images for drawing the stone track, or areas where there is no track, and the special end tile that is the teleporter the astronaut is trying to reach), and ruptureParticle (an image used to produce the spatial rupture, in conjunction with the Phaser particle system). These identifiers are how you’ll refer to a given asset later on when you want to put one of these images on the screen, or use them to create a sprite from.

Now, if you look at the directory structure of JAMJumper in the code bundle, you’ll quickly notice that there doesn’t appear to be an img directory under src, like you’d probably expect. So, where does img/track.png go to load the image? Well, that’s where that static directory I’ve mentioned a few times comes into the picture!

A Quick Detour: The Static Directory

Usually, when you build a site with Gatsby, and you want to use images, you’ll import them directly into your code as you would any JavaScript module. Gatsby, in conjunction with Webpack, makes this work. This usually goes for stylesheets too, as well as many other assets a website might need (audio files, web fonts, etc.). This has several benefits. First, these resources are automatically minified and bundled together for efficient loading. Second, any missing files will show up as compilation errors, not runtime errors. Third, the filenames that are used by Gatsby are dynamic (hashes), so that browser won’t cache older versions.

However, there are sometimes cases when you want or need to opt out of all of that and handle things yourself. Phaser is one such instance because it doesn’t know how to use the bundled resources that Gatsby and Webpack produce. Fortunately, Gatsby recognizes that this is sometimes needed and provides an “escape hatch,” so to speak: the static directory.

You create this directory in the root of your project, and within it, you can have any directory structure you like (or no subdirectories at all). Every file and directory you put in the static directory will be simply copied right into the public folder at build time, unaltered. You can then reference these resources in your code as you would any other resources in a plain, non-Gatsby website, because recall that the public folder is where the output from the build process goes.

So, when we tell Phaser to load /img/track.png, it will look for an actual file named track.png that starts in the static/img directory and winds up in the public/img directory after the build is completed.

It really is that simple, but it’s a powerful mechanism when you need it, as we do with Phaser. As you’ll see in the next section, audio files are in the static/snd directory and are managed the same way as the images. Remember, though, that Gatsby or Webpack won’t touch these resources, so you’ll need to minify them yourself. And, remember that if you type a path wrong, you’ll get an error at runtime, not at build time, so be careful!

Back to the Code!

Next, we have to load two spritesheets:
this.load.spritesheet("fire", "img/fire.png",
  { frameWidth : 64, frameHeight : 64, endFrame : 3 }
);
this.load.spritesheet("astronaut", "img/astronaut.png", { frameWidth : 54, frameHeight : 44, endFrame : 1 });
What’s a spritesheet, you ask? Well, take a look at Figure 7-2. That’s the spritesheet for fire on the left and the astronaut on the right.
../images/497584_1_En_7_Chapter/497584_1_En_7_Fig2_HTML.jpg
Figure 7-2

The spritesheets for the fire (left) and astronaut (right)

A spritesheet is basically an image that contains multiple frames of animation. Take the fire, for example. To give it a swirling effect, I took the base image, which is the one on the left, and then rotated in 90 degrees to create the second frame, then rotated that one 90 degrees again to get the third, and once more to get the fourth. Note that I’ve added some separation between the frames to make it a bit more obvious what you’re looking at, but there are no gaps in the actual fire.png graphic.

Each frame or animation is the same size: 64x64. So, when the load.spritesheet() function is used, you tell Phaser what identifier to use for this spritesheet, the image filename that contains the animation frames, and then you tell it the frameWidth and frameHeight of each animation frame (while there are options for frames that aren’t all the same size, it’s often the case that they are all the same size just because it’s easier to put the images together that way). In addition, you essentially tell it how many frames there are in total with the endFrame value (3 because the first image is 0, so 0–3 for all the frames, with the last frame, or “end” frame, being index 3).

The astronaut is the same, but in this case, there are just two frames of animation. Note that none of this inherently makes any animation occur. It’s simply giving Phaser the information it needs to load the image and then slice it up into individual frames. We’ll define the actual animations later and activate them. We have to do that because, in a spritesheet, there might actually be several different animation sequences, and Phaser won’t know how to use them. For example, if we wanted to have an animation sequence of the astronaut exploding, we might put, say, ten more frames after the two you see, but then frames 0–1 is one animation sequence (running, in this case) and then 2–11 will be the explosion. Having them all in one spritesheet allows for more significant compression and is more efficient, but it means we have to tell Phaser what’s going on.

Finally, we have some audio assets to load:
this.load.audio("running", "snd/running.ogg");
this.load.audio("jump", "snd/jump.ogg");
this.load.audio("death", "snd/death.ogg");
this.load.audio("beamup", "snd/beamup.ogg");

As mentioned earlier, they’re just Ogg Vorbis (.ogg) audio files and they are in the static directory in their own snd subdirectory. It’s the same deal as images and spritesheets: call the appropriate loader method on the Scene object and provide an identifier and the filename and Phaser will load them up.

After all of this, these resources are all ready for us to use throughout the rest of the code, and the next method is where we begin to use them.

The create() Method

The second method that Phaser calls is the create() method . If you didn’t have a preload() method , then create() would become the first method called. The goal here is to create the elements of the game, and do general setup required.
function create() {
  scoreSpan.innerHTML = gameState.score;

First things first: an initial score is shown. Since scoreSpan is just a reference to a plain old <div>, it’s nothing fancy: setting innerHTML does the job. The gameState.score attribute has a value of zero at this point, which makes sense when the game is starting.

From here on out, I’ll break this method down into logical chunks, starting with a chunk for creating our astronaut.

The Astronaut

The first step is to figure out where the astronaut is on the screen:
const ax = (sizes.grid.columns / 2) * sizes.tile.width;
const ay = ((sizes.grid.rows-1) * sizes.tile.height) - (sizes.astronaut.height / 2) -
  ((sizes.tile.height - sizes.astronaut.height) / 2);

Since we know that there are nine columns of tiles, figuring out the horizontal location is a simple matter of figuring out the center tile and multiplying by the width of a tile.

The vertical location is a little trickier. Here, we want to place the astronaut one row up from the bottom, so one is subtracted from the number of rows. This is multiplied by the height of a tile, which gets us onto the right row. But, then, we want to make sure that the astronaut is centered on that file, which they wouldn’t be if we stopped here. So, we now need to subtract the difference between the height of a tile and the height of the astronaut, and then divide that result by two, so that the space is evenly distributed.

The result of all of this is that ax and ay are coordinates that put the astronaut on the center tile of the second row from the bottom, centered on that tile. That is, it will, once we actually create the astronaut! Keep in mind that by default, the coordinates for an object in a Phaser game are based on the center of the object. You can change that, and you’ll see how when we deal with the track.

First, though, we’ll need an animation sequence for the running animation:
this.anims.create({ key : "run", repeat : -1, frameRate : 5,
  frames : this.anims.generateFrameNumbers(
    "astronaut", { start : 0, end : 1, first : 0 }
  )
});

Recall that the astronaut spritesheet is two frames of animation. We can create an animation sequence using the anims.create() method of the Scene object (which, remember, is referenced by the this keyword at this point). The key is just an identifier we can use later to activate a given animation. If we had ten more frames, and they represented a different animation sequence (say, the player exploding), then we could define another sequence in the same way for those, all sourced from the same spritesheet, and could then activate them as needed.

The repeat attribute being set to -1 means to repeat this sequence indefinitely. It’ll just keep looping between the two frames, which are referenced by the frames attribute. The value needed is what is returned by the anims.generateFrameNumbers() method of the Scene object. We tell it what the first frame and end frame are (taken from the astronaut spritesheet) and which frame the sequence starts on. Finally, the frameRate attribute tells Phaser how frequently to flip through the frames. Remember that the game runs at 60FPS, so the astronaut will appear to alternate between left and right legs 12 times a second, which gives the desired running effect.

Next, it’s time to actually create the astronaut, which is a sprite:
images.astronaut =
  this.physics.add.sprite(ax, ay, "astronaut");
images.astronaut.body.setSize(1, 1);

Because we need collision detection, we have to use the physics.add.sprite() method of the Scene object (you could also create a basic Image instead of a Sprite, but then collision detection can’t be used). This takes the location coordinates and a reference to the spritesheet. Here, I’m also defining the size of the physics body for this sprite. By default, the physics body will be a square completely encompassing the sprite (it’s also possible to define them as more complex bodies that follow the contour of the object – it all depends on the needs of your game). That works in many cases, but here, it would be a problem because as soon as the astronaut is even a single pixel off the edge of the stone track, it would register as a collision with the fire, and they would die. The game would be virtually impossible to play. Instead, the player need some leeway. The easiest way to do that is to make the physics body a single pixel right in the middle of the player (remember, these coordinates are by default based on the center of the object). That way, the astronaut can be off the edges of the track a little bit without registering as a fiery death!

We also need to ensure that the astronaut will be on top of the track and fire graphics (which, of course, haven’t been created yet). Similar to HTML, there is the concept of z-index for Phaser objects, called depth:
images.astronaut.setDepth(2);

The fire will have a depth of 0, the track images will be 1, the astronaut is 2, and the spatial rupture particles will be 3. As in HTML, higher-numbered objects are on top of objects with lower numbers when rendered onto the screen.

Finally, we have to deal with the run animation:
images.astronaut.anims.play("run");
images.astronaut.anims.pause();

As you can see, the anims.play() method of a Phaser object allows you to specify the animation to run. But, since the game starts out not playing until you click the button, I immediately pause the animation. When the game starts, images.astronaut.anims.resume() will be called to start the run animation (only one animation can be playing at a time for an object, but it can be paused and resumed at will).

The Fire

Now that we have an astronaut, let’s go ahead and create some fire. First, we need an animation:
this.anims.create({ key : "burn", repeat : -1, frameRate : 10,
  frames : this.anims.generateFrameNumbers(
    "fire", { start : 0, end : 3, first : 0 }
  )
});

Giving it a key of burn, I think, makes sense! This animation runs a bit faster than the player’s run animation, but otherwise, it’s defined the same.

Now, with an animation defined, it’s time to create a bunch of fire images, one per space in the grid in the play area:
for (let row = 0; row < (sizes.grid.rows + 1); row++) {
  for (
    let column = 0; column < sizes.grid.columns; column++
  ) {
    const tile = this.add.sprite(
      column * sizes.tile.width,
      (row - 1) * sizes.tile.height,
      "fire"
    );
    tile.setOrigin(0, 0);
    tile.anims.play("burn");

I’ll stop here so you can chew on that. Once more, the add.sprite() method of the Scene object is used. This is done inside two loops that will “paint” fire tiles, so to speak, onto the 9x11 grid of tile spaces from the upper left to the lower right. In this case, though, I call setOrigin(0, 0) on each sprite. This has the effect of making the x and y coordinates of the sprite relative to the upper-left corner of the sprite. This makes the math far easier when positioning each fire sprite.

One important thing to note here is that I’m actually creating one extra row of fire tiles, which are positioned outside the gameplay area, above it. In game development, this is often referred to as an “overscan.” Remember that the fire appears to scroll down the screen. This effect is sort of a cheat. The way it works is that all the tiles are moved downward, including that row of tiles above the gameplay area. The bottom row of fire tiles will move down and out of the gameplay area, while those above move into it. When all the tiles have moved 64 pixels – the height of an individual tile – the positions of all of the tiles are reset to where they begin. The effect is that of a continuous scrolling sea of fire. You’ll see that reset in the next chapter, but I think giving you the basic idea here makes sense.

Now, you know that tiles move, but how is that accomplished? The answer is this:
const t = this.tweens.add({
  targets : tile, duration : 1000, repeat : -1,
  y: { from: (row - 1) * sizes.tile.height,
    to: row * sizes.tile.height },
});
t.pause();
fireTweens.push(t);

A tween is a way to animate some property of an object over time. The term “animate” here is a little bit of a misnomer because animation doesn’t necessarily mean movement. You could animate the alpha property of an object, for example, to have it fade out over a few seconds. You can do that with a tween. The great thing about a tween is that it is, largely, a “fire and forget” sort of thing: you tell Phaser what to do, and it goes off and does it. In this case, the tweens.add() method of the Scene object creates a tween for us (a reference to which is stored in the fireTweens array, since we’ll need to pause the tween later – and just like with an animation, that’s something you can totally do with tweens!). We have to tell it the target, that is, what object to animate, and we have to tell it the duration that tween should take, 1 second here (1000 milliseconds). As with animations, repeat set to -1 means keep doing it over and over again. Then, we have to tell Phaser what property to animate. In this case, it’s the y location, the vertical position of the fire tile. We tell it what the starting value is (from) and the ending value (to). Since each subsequent row of tiles created here will simply move 64 pixels down and then reset, and since we have that overscan row to consider, the starting position is the height of a tile minus the row number minus one. So, for the first row drawn, row-1 will yield -1, so multiplied by the tile height (64) means that tile will start 64 pixels above the gameplay area, just like we want. The to value is 64 pixels more, so 0 in the case of the overscan row. With each row of tiles having the same positioning logic, we get the desired continuous scrolling effect we’re after. But, as with the fire animation, we need this tween not to be going at the start, so it too is paused for now.

The Track

Next up, we need to create the stone track:
createTrack();

Uhh, wait, that seems a bit anticlimactic, doesn’t it?! Not to worry, we’re going to look at that in the next section because it’s probably the most complicated bit of code in the whole game, so I think it more than deserves its own section. Before that, though, let’s finish up the rest of the code in create().

Sounds

While we loaded the sounds in the preload() method , that just prepares them for Phaser to use. In order to actually use them, we have to add them to the scene:
sounds.running = this.sound.add("running");
sounds.jump = this.sound.add("jump");
sounds.death = this.sound.add("death");
sounds.beamup = this.sound.add("beamup");

References so these are kept in the sounds object so that we can play these later, as you’ll see.

Keyboard Input

Obviously, JAMJumper wouldn’t be much of a game if we couldn’t control the astronaut, so let’s set up some key handling here:
keys.up = this.input.keyboard.addKey("up");
keys.left = this.input.keyboard.addKey("left");
keys.right = this.input.keyboard.addKey("right");

The way this works is simple: any time the up arrow key, for example, is pressed or released, the up attribute of the keys object will be updated. It’ll have a value of true when the key is pressed, false when it’s not. We can read this value in the main game code later (which you’ll see in the next chapter) and react as appropriate. You can hook up as many such key handlers as you wish, whatever your game requires.

The Spatial Rupture

Finally, we have that spatial rupture to deal with. This uses the Phaser particle system. If you’ve never heard this term before, you’ve undoubtedly seen it if you’ve ever played even a semimodern video game. A particle effect is one that uses numerous small images to create a more significant effect than the individual images themselves could produce on their own, and a particle system is a piece of code that produces these effects. Picture an explosion like you’ve probably seen in many games. That’s almost certainly a particle effect: tiny bits of fire and debris fly out from a central point to produce the explosion you see. Sparks on an electrical wire are another typical example. Rain can be created with a particle system. Even the flame on a torch might be produced this way. Particle systems can be extraordinarily complex and the effects they produce very involved.

Fortunately, for the spatial rupture, it’s not so complex:
const particles = this.add.particles("ruptureParticle");
particles.setDepth(3);
emitters.rupture = particles.createEmitter({
  radial : true, quantity : 1,
  speed : { min : -800, max : 800 },
  angle : { min : 0, max : 360 },
  scale : { start : 4, end : 0 },
  lifespan : 800, visible : false
});
emitters.rupture.pause();

First, we have to add an image that will be the basis for the particle effect. The add.particles() method of the Scene object here references the ruptureParticle we loaded earlier, which is just a circle with a little gradient color effect. Then, the depth of it is set so that it will be above all the other graphics of the game.

Note

An interesting aside: the term “particle system” came about thanks to 1982’s Star Trek II: The Wrath of Khan. In that movie, the Genesis video that Kirk, Spock, and McCoy watch used a system of particles to produce the effect of the Genesis “fire” progressing across the surface of a planetoid. Before this, similar results could be created by animators hand-drawing them (see Disney’s Fantasia, for example), but Star Trek was the first time this was done via computer graphics, and the first time the term “particle system” was used to describe it.

After that is where the magic happens: a particle emitter is created with the particles.createEmitter() method . An emitter is responsible for creating the particles, positioning them, and animating them through the life of the effect. Here, I’m saying that the particles should emerge from the emitter randomly (radial : true) in all directions around the center of the emitter between the angles specified by angle min and max (0–360 degrees is all directions around a circle). The quantity is how many particles are emitted at each tick of the emitter’s animation cycle. One is sufficient for the effect I was after. The speed attribute denotes the speed of emitted particles in pixels per second. Here, I gave a range that the values will be chosen from to provide some variation. The scale attribute serves a similar purpose to speed in that it gives a range from which particle sizes will be chosen. So, we’ll get some small particles and some larger ones. The lifespan attribute tells how many milliseconds each particle should live for; after which, they will fade away. Finally, setting visible to false means that none of what we just set up will show up on the screen! Obviously, this should only appear when the astronaut dies.

One last thing to consider: a particle system can be computationally expensive. Maybe not so much in this case, but even still, better to not have all that work going on if the rupture isn’t actually visible, so the emitter is paused here, just like an animation and a tween, and will be started up (and made visible) when the time is right.

The createTrack() Function

Now, let’s go back to the createTrack() function that you saw called from create() and see what it’s all about. But, that will be hard to do without one of my world-famous quick detours.

A Quick Detour: Defining Level Data

By now, you’ve seen level data loaded two ways, but to this point (unless you’ve looked at the code already!), you haven’t seen what’s in the JSON files for the levels. Well, time to remedy that!
{
  "totalScreens" : 4,
  "data" : [
    [ 1, 0, 0, 0, 0, 0, 0, 0, 1 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 1, 0, 0, 0, 0 ],
    [ 1, 0, 0, 0, 2, 0, 0, 0, 1 ],
    ...three more blocks of 11 arrays each, one per screen...
  ]
}

It’s just an object with two attributes. The totalScreens attribute tells the game code how many total screens there are defined for the level. The data attribute is the actual data that corresponds to the pattern of stone pathways on the screen.

Each screen is simply a full set of 11*9=99 codes, one per tile, which in total fills the gameplay screen. Each screen consists of 11 arrays in the data, since there’s room for 11 rows of tiles in the gameplay area, and each array has 9 values in it since there’s room for 9 columns of tiles. The values are as follows:
  • 0 – No track (identifier trackBlank, file: track_blank.png)

  • 1 – Stone track (identifier: track, file: track.png)

  • 2 – End tile (identifier: trackEnd, file: track_end.png)

The easiest way to make the connection between this and what you see on the screen is to load the game, but don’t start it. Then, load the level_01.json file and look at the last set of arrays. Compare those arrays to what you see on the screen and see how the pattern of the data matches up to what you get on the screen. The data maps directly to the virtual grid of tiles in the gameplay area. If you’re still having trouble making the connection, go into those arrays, and anywhere you see a 0, make it a space. You should quickly see that the stone pathway you see on the screen mirrors the 1s still present.

The track images are going to scroll downward, which is why we start with the last set of arrays: that’s really the first screen of tiles drawn. It’s where things start at the beginning of every level. As you play the game, the track tiles scroll down, and the next row of tiles above scrolls into view and the one on the bottom scrolls out of view. This happens over and over again until the set of arrays at the top of the level data is what’s filling the screen, which means the end of the level has been reached.

Back to the Code!

Now, it’s time to build us a stone track! The aptly named createTrack() function is responsible for that:
function createTrack() {
  if (images.track && images.track.length > 0) {
    images.track.forEach(
      item => { item.destroy(); item = null; }
    );
  }
  images.track = null;
  images.track = [ ];

This first bit accounts for the fact that this function can be called at the start of the game from create(), or when the game is restarted when the user clicks the Start Game button after dying. In the latter case, since we’re effectively resetting everything, including this track, we need to destroy any track images that exist. The Phaser Image object provides a destroy() method , which gives Phaser a chance to clean it up and get rid of it. Also, I want to ensure there are no references left anywhere that could cause a memory leak, so I set each item reference to null after that and then set the entire array to null before creating a new, empty array. All of that very much should be overkill and more than is necessary, but when I kill something, I like to make real sure it’s dead! I mean, I’ve seen more than enough sci-fi movies in my day to know you don’t play games with stuff like this!

Next, it’s time to start creating Image objects, one per tile per screen in the level data:
let y = -(sizes.tile.height * sizes.grid.rows *
  (gameState.currentLevel.totalScreens - 1));
for (let row = 0;
  row < sizes.grid.rows * gameState.currentLevel.totalScreens;
  row++
) {
  for (
    let column = 0; column < sizes.grid.columns; column++
  ) {

At this point in writing the code, I had a choice to make: do I create images for every single element in every single array in the level data, or do I instead create them on the fly, one row at a time? Essentially, do I create a big, long array of images and make the gameplay area basically a window into it, with tiles scrolling in and out of view all the time but never being created or destroyed during that time? Or, do I do a similar thing as with the fire where only enough images to fill the gameplay area are created, plus one extra row up top to scroll into view, then destroy the row that just scrolled out of view and create a new overscan row?

In thinking this through, two thoughts occurred to me. First, creating them all up-front would likely be a bit simpler to code. I like simpler! Second, I was concerned that continually creating and destroying objects during gameplay might lead to jankiness. JavaScript would be doing garbage collection, and that might impact the smoothness of the game. As an extremely broad, general statement, in game development, it’s usually better to avoid object destruction and creation during gameplay as much as possible, especially in a garbage-collected language.

Since memory isn’t a real concern with the relatively small size of these graphics, wihch would be the other concern, I opted for creating them all at once. With that in mind, the very first step is to determine the starting Y location of the first row. Since I actually want the last 11 arrays to be visible initially, that means that the first row has to start with a negative Y value so that it’s way off-screen above the gameplay area, in effect. The calculation then is just the height of a tile, times the number of rows, times the total number of screens minus one, and negating that result. That gives the proper location for the top row, and each row after that is just that value plus the height of a tile.

After that calculation is done, two loops are started so that we can create the tiles on a per-column/per-row basis.

Next, we create the appropriate kind of tile based on the level data:
const tileType = gameState.currentLevel.data[row][column];
let tile = null;
if (tileType === 0) {
  tile = scene.physics.add.image(
    column * sizes.tile.width, y, "trackBlank"
  );
  scene.physics.add.collider(
    images.astronaut, tile, () => gameOver(false)
  );
} else if (tileType === 2) {
  tile = scene.add.image(
    column * sizes.tile.width, y, "trackEnd"
  );
} else {
  tile = scene.add.image(
    column * sizes.tile.width, y, "track"
  );
}

Note that for areas in the level data with a zero, you would think there would simply be nothing there, but it winds up being a little easier to code if you do have a tile, but it just so happens to be a blank one. Much more importantly, we need that because without it, we wouldn’t have a way to register death! At first, you’d imagine, quite logically, that if the player collides with a fire image, then that’s a death. The problem, however, is that collision detection isn’t aware of depth. In other words, even though there’s a stone tile between the astronaut and the fire, a collision would still be registered because, based on X/Y coordinates, the astronaut collides with the fire.

So, instead, the collision detection is applied on the blank tiles! If the astronaut collides with a blank tile, and if they aren’t jumping, then that’s the same, effectively, as hitting fire, and we know they’re dead.

The collision detection is added to the blank tile using adding a Collider to it, which is the other half of the equation (in addition to configuring the physics engine and using the physics.add.image() method instead of the add.image() method). This informs the physics engine that we want to detect collisions with this object, but only if it collides with the astronaut sprite. When that happens, the gameOver() function is called, passing it false (the meaning of which you’ll learn in the next chapter).

For the regular stone track tiles, as well as the special end tile, since no animation is required, they’re just basic Image objects created with the Scene’s add.image() method rather than sprites. For all of them, the X/Y location is calculated based on the look values and the width of a tile.

I haven’t mentioned how the game detects that the player has reached the end tile, but clearly, it’s not collision detection since there’s no Collider attached to it. Don’t worry, that’s something else you’ll see in the next chapter.

Finally, for each tile, we have a little more work to do:
    tile.setOrigin(0, 0);
    tile.setDepth(1);
    images.track.push(tile);
    if (tileType === 2) { images.endTile = tile; }
  }
  y += sizes.tile.height;
}

As with the fire tiles, since we’re building these images and in a virtual sense inserting them into a grid, the calculations are more straightforward with the local origin of the images being the upper-left corner. And, you already know that the depth has to be set so that these images are above the fire but below the astronaut. Each image gets pushed into an array, and note that this is a plain old array, it’s not multidimensional. This makes moving the tiles easier since there’s just one loop needed to iterate through all of them (which, again, is something for the next chapter). Since there’s only ever a single end tile, and since the logic that determines when the player reaches it depends on having a reference to it, as you’ll see, that reference is captured when tileType is 2.

Finally, the y value is bumped up at the end of each row so that the next row is positioned correctly, and that’s how you build a stone track over fire for a thieving astronaut to run away from angry aliens on!

Dealing with a Build Issue

Before we can go any further, we have to address an issue that is present at this point. If you try to build the project now (gatsby build), you’ll encounter the same sort of error I discussed in the previous project: the window object is not available. Unfortunately, the code that uses the window object is Phaser itself, and its core to what Phaser does. So, whereas the code that used window in the previous project was application code, so it wasn’t a problem to refactor the code to avoid the issue, going and hacking Phaser code isn’t really an ideal solution (and, as it happens, not even possible or Phaser won’t work).

Fortunately, there’s another solution. It involves adding a gatsby-node.js file with the following content:
exports.onCreateWebpackConfig = (
  { stage, loaders, actions }
) => {
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module : {
        rules : [
          { test : /phaser/, use : loaders.null() }
        ]
      }
    })
  }
};

This code executes when Gatsby builds the Webpack configuration it will use to build the project. This allows us to alter that configuration. The goal here is to make it so that Webpack ignores Phaser entirely when doing the Server-Side Rendering since the window object isn’t available at that point. So, when Webpack reaches its build-html phase, which is when SSR happens, we define a rule that says: “hey, Webpack, when you encounter a resource named phaser, don’t load it.” The loader.null is a noop resource loader available in a Webpack configuration, which does exactly what we want: nothing! With this in place, you should be able to do a gatsby build successfully.

If you do that now using the Chapter 7 code in the code bundle, you’ll discover that the build works, but when you access the site, the screen appears, and there’s some spinning fire, but that’s it. The Start Game button does nothing; the cursor keys do nothing; the game doesn’t play. That’s expected, because if you then examine the code, you’ll find that there are three functions that are empty: update(), gameOver(), and startGame(). Those are the remaining three functions that we need to build to make the game work, with update() being the most critical. That’s what the next chapter will cover… you know, nothing too important, right?

Summary

In this chapter, we began the process of building a little game using Gatsby and JAMstack techniques. You got a look at the Phaser library and how it can be used in a Gatsby/React app. You saw how you can source JSON files in Gatsby, or just import them directly, and you got a look at avoiding a frequent problem with Gatsby apps in terms of objects not available during SSR.

In the next chapter, we’ll complete the game code to make it a fully functional and playable game, and we’ll also take the game and make it a PWA (whatever that is!) to show that Gatsby and JAMstack are totally cool with being involved in that new hotness!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.125.219