-
Notifications
You must be signed in to change notification settings - Fork 0
Getting started
The following steps should help you get started with the HCJE. The HCJE has been designed to facilitate incorporation into a project as a Git submodule and contains build tools that run in a Node.js environment. No additional packages need to be added to the Node environment.
Note
In the code samples, function comments are minimal to keep the examples as short as possible. Also in some cases the code is deliberately not as efficient as possible in order to highlight certain features of the HCJE.
This guide assumes that your current project structure looks something like this:
myProject
package.json
+ source
index.html
+ other source files and required folders
Tip
Although you can name the source folder to anything you like, src for example, keeping the name as above will make this guide easier to follow and reduce any necessary editing of the configuration files used by the project.
If you don't have a package.json file yet, you can run npm init from within your myProject folder.
To install the HCJE as a submodule in the project structure above, you can simple run the following command from within the myProject folder:
git submodule add https://github.com/henspace/html-css-js-engine source
The result should be:
myProject
+ source
index.html
+ other source files and required folders
+ html-css-js-engine
+ all the hcje files
Warning
Make sure the HCJE submodule is installed in the same folder as index.html and the rest of the source, and that its name is not changed from its default of html‑css‑js‑engine as this name and location are required for the build script to operate correctly.
The HCJE should now be installed ready for use.
First ensure that package.json includes the following lines and that the main property has been removed. We are only using package.json as a build tool and not for publishing Node packages.
"type": "module",
"private": true,To utilise some of the tools in the HCJE, you must modify your package.json file to facilitate running the scripts. Typically your scripts property should include the following:
"scripts": {
"prebuild": "npm run test",
"build": "node ./source/html-css-js-engine/tools/build/build.js ./build-config.json",
"test": "node ../source/html-css-js-engine/tools/testing/runner.js test-config.json",
"serve-build": "node ./source/html-css-js-engine/tools/server/server.js 8080 ./build",
"serve-source": "node ./source/html-css-js-engine/tools/server/server.js 8080 ./source"
},The build and test commands require JSON configuration files, build-config.json and test-config.json respectively. There are sample configuration files in the HCJE submodule in the folder resources_for_submodule_users. You can copy these into the root folder of your project alongside package.json.
Note
The default build configuration will not create a zip file of the resulting build. If you require a zipped version of the build, you will need to edit the build‑config.json file and set the zippedOutputDir property; if using Windows, you will probably need to alter the zipOptions.win32.cmd property as well.
Your index.html page will need to load the HCJE style sheet, your style sheet, the HCJE scripts, and finally your own game scripts. The following code shows the way to do this.
<html>
<head>
…
<link rel='stylesheet' href='html-css-js-engine/source/hcje/styles/style.css'>
<link rel='stylesheet' href='styles/style.css'>
<head>
…
<body>
<p>Loading. Please wait.</p>
</body>
<script type = 'module' src='html-css-js-engine/source/hcje/scripts/hcje-lib.js'></script>
<script type = 'module' src='scripts/index.js'></script>
</html>Warning
The build tools automatically flatten the HCJE folder and place the HCJE's script files in a subdirectory named _hcje and modify the link and script lines above by replacing html-css-js-engine/source/hcje with _hcje. As such it is important to write the references to the HCJE style and script files as shown above.
The result of the build process is summarised below.
outputFolder
+ index.html
+ other source files and required folders
+ _hcje
<link rel='stylesheet' href='_hcje/styles/style.css'>
...
<script type = 'module' src='_hcje/scripts/hcje-lib.js'></script>
When we start writing our game code we will remove the content of the <body>. This means that index.html can contain any information we like to indicate to the user that we are loading.
We now need to use code in our index.js script to clear the loading information from the body and create the game area. Add the following code to your script. You can change the GAME_WIDTH_ and GAME_HEIGHT constants to your own preferred values.
/**
* Load assets and set up the environment.
*/
function loadAssets() {
const assets = {};
return Promise.resolve(assets);
}
/**
* Start the game.
*/
function startGame(assets) {
const GAME_WIDTH = 672;
const GAME_HEIGHT = 420;
document.body.replaceChildren();
const gameArea = new hcjeLib.domTools.GameArea({
width: GAME_WIDTH,
height: GAME_HEIGHT,
});
const title = new hcjeLib.domTools.createChild(gameArea, 'p', 'game-title');
title.innerText = 'My game';
}
// execute the game
loadAssets()
.then((assets) => startGame(assets));At the very bottom of the example code we call the loadAssets function. This function currently does nothing and just returns a Promise that fufils to an empty object. Later on it may be used to load assets that take time but which we want to ensure have completed before starting the game. The startGame function is called once loadAssets has completed. The startGame function clears the existing body, creates the
GameArea and adds a title. If you run it, you should see a rather boring grey rectangle appear.
Note
Information about the createChild and createGameArea methods can be found in the hcje/domTools module documentation.
Most games have music, but music can only be started in HTML games in response to a user action. So, in most games you will want to start with a button being clicked. In this code modification, we'll make the following changes:
- Create a
playGamefunction. This will hold our main game code when we get round to writing it. - Modify
startGameto create a welcome dialog and then call theplayGamefunction.
/**
* Load assets and set up the environment.
*/
function loadAssets() {
const assets = {};
return Promise.resolve(assets);
}
/**
* The main game loop.
*/
function playGame(gameArea) {
}
/**
* Start the game.
*/
function startGame(assets) {
const GAME_WIDTH = 672;
const GAME_HEIGHT = 420;
document.body.replaceChildren();
const gameArea = new hcjeLib.domTools.GameArea({
width: GAME_WIDTH,
height: GAME_HEIGHT,
});
const title = new hcjeLib.domTools.createChild(gameArea, 'p', 'game-title');
title.innerText = 'My game';
hcjeLib.domTools.createDialog({
title: "Welcome",
markdown: "Click the *PLAY* button to get started.",
children: [],
buttonDefns: [
{id: 'PLAY', label: 'Play'},
]
})
.then((id) => playGame(gameArea, assets));
}
// execute the game
startGame();If you run this code, you should finish up with a welcome dialog appearing that closes when you click the play button.
Note
The loadAssets fulfils to an empty object at the moment, but this will be used later to return objects that are loaded in the function. This object is passed to the startGame function and then on to the playGame.
Note
information about the createDialog method can be found in the hcje/domTools module documentation.
To make things a little more interesting, let's add a simple text Sprite to our game. We're using a text sprite so we don't have to worry about loading images.
Modify the playGame function as shown below:
/**
* The main game loop.
*/
function playGame(gameArea) {
const GAME_DIMENSIONS = gameArea.designDims;
const mySprite = hcjeLib.sprites.createTextSprite(gameArea, "HELLO *WORLD*",
{ markdown: true,
dimensions: {width: 64, height: 64}
});
mySprite.position = {x: GAME_DIMENSIONS.width / 2, y: GAME_DIMENSIONS.height / 2, angle: Math.PI};
}This code creates a simple text sprite and then positions it near the middle of the game area and rotated by 180°(π radians). It's not actually centred as the position property is the sprite's top-left corner and not its centre.
This is where things start to get a bit more interesting. First let's modify the loadAssets function to load a sprite sheet. In this example, we assume you have a folder named assets/textures and this contains the image sprite-sheet.png and associated data file sprite-sheet.json. In this guide the image contains three frames as shown below:
| player_eating0.png | player_eating1.png | player_eating2.png |
These represent a sprite with a basename of player.png, a state of eating, and three numbered frames: 0, 1 and 2.
Note
You can find sample files for this guide in the HCJE submodule in the folder resources_for_submodule_users\assets\textures. You can copy these into your source/assets/textures folder. Refer to the hcje/sprites module for details on how to create your own sprite sheets.
function loadAssets() {
const assets = {};
return hcjeLib.sprites.loadSpriteSheet(
"assets/textures/sprite-sheet.json",
"assets/textures/sprite-sheet.png",
new hcjeLib.domTools.TimeLimitedBusyIndicator()
)
.then((texture) => {
assets.textureManager = textureManager;
return assets;
});
}The loadSpriteSheet method creates a TextureManager instance which is added to the assets object once it has been loaded.
Note
Information about the loadSpriteSheet method can be found in the hcje/sprites module.
Now the texture is loaded, let's replace our text sprite with an image loaded from the texture. To do this we need to modify the playGame function as follows:
- Remove the call to the
hcjeLib.sprites.createTextSpriteas we no longer want the text sprite. - Set the factory used by the
TextureManagerto create sprites. - Create a sprite using the texture manager's
createSpritemethod.
function playGame(gameArea, assets) {
const GAME_DIMENSIONS = gameArea.designDims;
assets.textureManager.spriteFactory = new hcjeLib.sprites.DomImageSpriteFactory(gameArea);
const mySprite = assets.textureManager.createSprite("player.png", [
{name:"eating", interval:100, cycleType: hcjeLib.sprites.CycleType.OSCILLATE}
]);
mySprite.position = {x: GAME_DIMENSIONS.width / 2, y: GAME_DIMENSIONS.height / 2, angle: Math.PI};
}Note
The spriteFactory property of the TextureManager is set to a DOMImageSpriteFactory. The TextureManager will use this factory to create sprites from the images contained in the texture.
Tip
ALthough you can use any naming convention you want for the images in the sprite sheet, if you follow the default convention you will not need to pass your own frameNameGenerator function to the createSprite method. For a base name of STEM.EXT, a state of RUNNING and a frame number of five, the name of the entry in the sprite sheet is expected to be STEM_RUNNING5.EXT. The createSprite method always starts looking for frame 0, then 1 and so on until it ceases to find a frame.
You should finish up with a sprite towards the middle of the screen, but it won't be animated yet. To animate the sprite, we need to add an Animator to the game and update it. Add the following lines to the bottom of your playGame function.
const animator = new hcjeLib.sprites.Animator();
animator.addTarget(mySprite);
animator.active = true;These lines simply create an Animator, add the sprite to the list of targets that need to be regularly updated, and finally turn the animator on.
If you run the code, the sprite should now be animated.
First, we'll make the sprite respond to the spacebar being pressed. To do this, add the following function:
/**
* Make sprite jump.
*/
function jump() {
const position = sprite.position;
position.y -= 2;
sprite.position = position;
}Then add the following code to the bottom of the playGame function to create a Keyboard instance to respond to the spacebar:
const keyboard = new hcjeLib.device.Keyboard();
keyboard.addDownListener(" ", {
callback: () => jump(mySprite),
noRepeat: false
});If you run it now, you should see the sprite move up when you tap the spacebar. OK! It's not really a jump yet, but we'll look at that later.
Our users might not have a keyboard, so let's make the game respond to the GameArea being tapped. Add the following line below where we added the down listener.
gameArea.addEventListener("pointerdown", () => hcjeLib.device.Keyboard.simulateKeydown(" "));This makes tapping or clicking on the game area respond as though the spacebar has been pressed.
Note
The pointerdown event does not repeat. If you want to use a keyboard repeat and simulate it with the pointerdown event, this will not work. However, the HCJE does provide a Button class which can be used. Have a look at Making a fullscreen repeating button to see how this is done.
Now let's address the fact that the sprite is not really jumping.
To make our sprite more dynamic we need to add a Dynamics object. This will make the sprite respond to its velocity and acceleration if it is being updated by an Animator.
Modify the playGame function so that just after mySprite is created, you add the following line:
mySprite.dynamics = new hcjeLib.sprites.Dynamics();The sprite will now have velocity and acceleration. Now change the jump function so that instead of changing the position of the sprite, it gives it a vertical velocity.
function jump(sprite) {
sprite.dynamics.vy = -200;
}Now when you tap the spacebar, the sprite will move up. Unfortunately, it will keep rising. Let's add some gravity to pull it back down. Modify the jump function:
function jump(sprite) {
sprite.dynamics.vy = -200;
sprite.dynamics.ay = 300;
}OK! It's better, but now it will fall off the bottom of the game. When we created the Dynamics we could have passed a DynamicLimiter to the constructor. Let's do that now.
Above the line in playGame where we set the mySprites.dynamics property, create a DynamicLimiter and then pass it into the constructor of the Dynamics object.
const limiter = {
limit: (target, dynamics) => {
let position = target.position;
const bounds = target.bounds;
if (position.y + bounds.height > GAME_DIMENSIONS.height) {
position.y = GAME_DIMENSIONS.height - bounds.height;
dynamics.vy = 0;
dynamics.ay = 0;
}
}
};
mySprite.dynamics = new hcjeLib.sprites.Dynamics(limiter);The sprite should now stop falling when it hits the bottom of the game area. In a real game you might want to add a bit of bounce of course.
Note
In the above code, we used a Dynamics to limit the motion of the sprite. The Sprite also supports use of a BaseSpriteAdjuster. This could do the same thing but is generally reserved for more complex interactions.
That concludes the getting started tutorial. There are a lot more pages covering other topics that you might like to take a look at.