If you haven't heard of Pixi JS, it is essentially a 2D rendering engine that uses WebGL for hardware-accelerated graphics rendering. PixiJS relies on communication with it's own PixiJS API, that in turn utilizes Web Graphics Language API (WebGL) underneath the hood, WebGL allows you to create custom graphics for game design, and the former allows for seamless integration into any Javascript code. PixiJS will take an html Canvas element, and then load it's assets into it. However, when it comes to building a web-game that utilizes React and it's declarative syntax, you will need to opt for using Pixi React. This partially has something to do with the fact that React uses .jsx files, with the primary benefit being that Pixi-React adapts to Reacts component-based syntax style, and allows the code to integrate seamlessly into React's syntax.
PixiJS
When I first began looking into PixiJS I was happy to see they have some amazing documentation on how to get started with the basics of their application. Once you get your server running you would essentially add a script tag to the head of your HTML file: <script src="https://pixijs.download/release/pixi.js"></script>
then create new PIXI application instance:
<script type="module">
const app = new PIXI.Application();
await app.init({ width: 640, height: 360 });
</script>
The application instance we created is a helper class, it creates the rendering and stage for us along with a ticker for updating. We then initialize this helper class and pass in the option which will dictate the size of the stage where our images will be rendered. With the advent of version 8 for PixiJS, it will primarily be using WebGPU, which will be an asynchronous process thus we need to add await onto the initialization of the application. Earlier versions of PixiJS goes through of process of choosing whether to use WebGL vs WebGPU for rendering, which is an asynchronous process in itself, so regardless of the selected API, you will need to ensure we wait for this value to return within our code.
PixiJS in it's native form will build it's stage within an HTML canvas element. It primarily draws inside of this canvas using a stage and various sprites.
<!doctype html>
<html>
<head>
<script src="https://pixijs.download/release/pixi.min.js"></script>
</head>
<body>
<script type="module">
const app = new PIXI.Application();
await app.init({ width: 640, height: 360 })
// Canvas element added to body
document.body.appendChild(app.canvas);
// Loading of an image
await PIXI.Assets.load('sample.png');
let sprite = PIXI.Sprite.from('sample.png');
app.stage.addChild(sprite);
</script>
</body>
</html>
The Assets property of Pixi has a load method. These methods return a promise that when resolved will have the parsed value of your image. Assets.load
can automatically tell which parser to use for loading a file based on the file type if it is included in the file name, but for something ambiguous, you will need to specify an explicit loader like so:
promise = Assets.load({
src: 'https://example.com/ambiguous-file-name',
loadParser: 'loadTextures'
})
/*
The parser options can vary based on your needs
Textures: loadTextures
Web fonts: loadWebFont
Json files: loadJson
Text files: loadTxt
*/
Once you have your image loaded; you can then add a ticker which will simulate the movement of the sprite:
let elapsed = 0.0;
app.ticker.add((ticker) => {
elapsed += ticker.deltaTime;
sprite.x = 100.0 + Math.cos(elapsed/50.0) * 100.0;
});
Afterwards you should be able to see your image of choice moving back and forth across the screen; truly amazing. Check out the full tutorial on how to do something like this on pixijs site here: https://pixijs.com/8.x/guides/basics/getting-started
Pixi-React
As for Pixi-React, I personally felt that the process was much simpler, albeit providing a little bit less direct control. For Pixi-React, you will need to run npm install @pixi/react
, along my coding journey of learning Pixi, I found that having this installed prevented me from using various other Pixi installations due to dependency conflicts. However this didn't truly matter in the long run because this version of pixi essentially allowed me to do everything those other dependencies could do without needing to install them... in a slightly different way of course.
import { Container, AnimatedSprite, Stage, Sprite } from '@pixi/react';
I like to call these the four horseman of Pixi-React. I was able to build an entire map filled with animated characters who would do battle against one another using these four alone. I wanted to take advantage of tileMap for pixi, but ran into trouble with installation due to it having conflicting dependencies with the version of Pixi I was using... thankfully I understood it's underlying logic and made a tilemap object from scratch, and utilized Reacts hooks and basic logic to overcome this small obstacle. I found that the reasoning behind many of these occurrences where there were conflicting dependencies was simply because there was no need for them if you can understand the logic behind what they need to do and the various things react provides. Some examples include how I implemented sound effects while in React, I utilized the use-sound hook, and basic JavaScript logic for map controls, collision detection, and setting up tiles. Just want to point out I thoroughly enjoyed the music-making aspect the most as I have a long history of music production and I almost cried when I saw my game running for the first time with my chiptune bops playing in the background... but that is for another article! For now; I will show you how to get an animated item onto your page, this time using Pixi-React.
const MyComponent = () => {
// Set your values here for an enemy!
return(
<Stage
width={420}
height={666}
>
<Container position={[16, 16]}>
<AnimatedSprite
key='enemyPos'
images={enemyAnimation}
isPlaying
initialFrame={0}
animationSpeed={0.3}
anchor={0.5}
x={enemyX32}
y={enemyY32}
width={32}
height={32}
/>
</Container>
</Stage>
)
}
Don't blink because that is pretty much it. Pixi-React provides you with these elements. The Stage element works just like earlier, however this time we simply need to just place the element into our components return statement. Inside of the stage will be a container, which works somewhat like the canvas element mentioned earlier. The containers are given a position, and inside those containers you can place your sprites. In this example I am using an AnimatedSprite
element.
The Animated Sprite element allows you to put an "images" tag, which can be an array of images you wish to animate from. The element will cycle through these images starting at the frame number you provide the value of initialFrame, and cycling at the rate of the value you provide animationSpeed. The anchor tag sets where a hypothetical pin will be placed in your image when it comes to setting it's location; I usually default this to 0.5 for the middle. The x and y values inside the element will be numbers; that point to the coordinates of your enemy in pixels.
Lets say you ran into a similar obstacle like mine and are looking to create a map using only Pixi-React without the sweet benefits of using the tilemap. What you can do is create an object, and give it a key that has an array of numbers, along with a key that has an array of images that are already cut to precise 32 bit squares (or whatever bit size you want your map to be).
const mapData = {
tileLayout: [
[ 0, 1, 2, ]
[ 2, 1, 0, ]
[ 0, 1, 2, ]
],
tiles: {
0: 'path/to/red.png'
1: 'path/to/blue.png'
2: 'path/to/black.png'
}
}
Then pass that map and those values into a Sprite tag like so:
<Stage
width={mapData[0].length * 32}
height={mapData.length * 32}
>
<Container
position={[0,0]}
>
{mapData.tileLayout.map((row, y) =>
row.map((tile, x) => (
<Sprite
key={`${x}-${y}`}
image={mapData.tiles[tile]}
x={x * tileSize}
y={y * tileSize}
/>
))
)}
</Container>
I didn't use destructuring yet so you could see how the two previous code blocks will connect and rely on one another. With further object nesting, it is possible to have a different map load depending on the situation, this takes advantage of reacts useEffect hooks, and highlights how well Pixi-React compliments react by opting to use less space in the code block so that you can keep your files abstract and easily readable. We can also layer the map with seperate overlay tiles that are transparent. Together with destructuring, your code would look like this:
import mapData from './path-to-your-map-object'
import { React, useState } from 'react'
import { Container, AnimatedSprite, Stage, Sprite } from '@pixi/react';
const myLevel = () => {
const {
tileLayout,
tiles,
overlayTileLayout,
overlayTiles,
enemy,
enemyStartingPosition,
} = mapData;
const [ tileset, setTileset ] = useState(tiles);
const [ overlayTileset, setOverlayTileset ] = useState(tiles);
const [ layout, setLayout ] = useState(tileLayout);
const [ overlayLayout, setOverlayLayout ] = useState(overlayTileLayout);
// useState is optional here, but allows for level unique changes mid-game if needed when properly combined with useEffect.
const [enemyX32, setenemyX32] = useState(enemyStartingPosition[1] * 32);
const [enemyY32, setenemyY32] = useState(enemyStartingPosition[0] * 32);
const [enemyPos, setEnemyPos] = useState([enemyY32 / 32, enemyX32 / 32]);
const [enemyAnimation, setEnemyAnimation] = useState(enemy);
return (
<Stage width={mapData[0].length * 32} height={mapData.length * 32}>
<Container position={[0,0]}>
{mapData.tileLayout.map((row, y) =>
row.map((tile, x) => (
<Sprite
key={`${x}-${y}`}
image={mapData.tiles[tile]}
x={x * tileSize}
y={y * tileSize}
/>
))
)}
{overlayData.map((row, y) =>
row.map((tile, x) => (
<Sprite
key={`${x}-${y}`}
image={overlayTiles[tile]}
x={x * tileSize}
y={y * tileSize}
/>
))
)}
</Container>
<Container position={[16, 16]}>
<AnimatedSprite
key='enemyPos'
images={enemyAnimation}
isPlaying
initialFrame={0}
animationSpeed={0.3}
anchor={0.5}
x={enemyX32}
y={enemyY32}
width={80}
height={80}
/>
</Container>
</Stage>
)}
And that's it! Hopefully now you have a good basic understanding of the differences between PixiJS and React-Pixi, as well how to make a map using React-Pixi without utilizing the tileMap feature. I look forward to learning more about game development while using PixiJS and React-Pixi, as well as sharing some of the cool things I have learned along the way.
-Cheers
Top comments (0)