Tutorial #1: GMP Game Basics - Hello World

Welcome!

This tutorial shows you how to work with the GMP javascript game engine.

We're going to build the Hello World game, shown to the right.

Take a moment to try it out. Use the F key to fire, and the LEFT and RIGHT arrow keys to move the ship.

Before starting this tutorial, you should read the introduction, and survey the API documention. The API documentation introduces many functions and objects that we will be using.

You should also have some javascript programming experience, and know how to create and edit HTML and CSS files.

You don't need to know anything about game design and development, but it will certainly help.

These are the steps we will take to create the game:

  1. Set up your game files
  2. Configure your HTML file
  3. Create an empty game
  4. Initialize your game objects
  5. Write your game AI
  6. Polish your game

Let's get started.

Step One: Set Up Your Game Files

The first thing we are going to do is set up the Hello World game files.

Create a directory called hello-world somewhere on your hard drive. This is our game directory, and it will contain all the game files. Hello World doesn't take up much space, so anywhere on your hard drive will do.

Now, lets look at the 4 files we'll be putting in this directory:

  • hello-world.html
  • hello-world.js
  • hello-world.css
  • gmp-engine.1.7.4.js

Go ahead and create the first 3 files, the ones that start with hello-world. They'll be empty for now, but we'll fill them soon.

The last file is the GMP game engine file, and you should download it to your new game directory. Don't worry if the copy you download has a different number in the name. It will work just fine for this tutorial.

Notice that we've named our game files 'hello-world' for clarity. You can call your files whatever you want.

Let's take a look at each of our 4 files.

hello-world.html

This is the web page that displays our game, and is a regular HTML document.

It has been structured to load our game files in a specific order, and to give the web browser some hints for rendering the game HTML.

We'll be configuring this file first.

hello-world.js

This is our game code file, and it will contain all of the javascript in our game.

We'll spend most of this tutorial editing this file.

hello-world.css

This file is for relatively 'static' CSS styling of game UI elements.

It isn't technically required, but it makes configuring and fine-tuning game UI really simple.

We'll show you how to use this file toward the end of the tutorial.

gmp-engine.1.7.4.js

This small file contains the GMP game engine code.

The game engine is pre-configured and self-starting -- you simply load it in your HTML file, and it works.

The API documentation explains the contents of this file in great detail.

Let's move on to editing our HTML file.

Step Two: Configure Your HTML File

Open your copy of hello-world.html, and save it with the following contents: <!DOCTYPE HTML> <html> <head> <link rel="stylesheet" href="hello-world.css" type="text/css"> </head> <body> <script type="text/javascript" src="gmp-engine.1.7.4.js"></script> <script type="text/javascript" src="hello-world.js"></script> </body> </html> There should be no blank lines at the beginning or end of the file.

Let's take a look at what's important in this file, and then we can move on.

The DOCTYPE Line

The first line of the file is the DOCTYPE. Amongst other things, this line tells your web browser whether to render HTML using quirks or strict mode.

GMP-based games look best with strict mode, and the easiest way to get that in every major web browser is with the experimental HTML5 DOCTYPE: <!DOCTYPE HTML>

(Henri Sivonen has a detailed comparison of each of the available DOCTYPEs.)

The Game CSS File Line

<link rel="stylesheet" href="hello-world.css" type="text/css">

This line loads the game CSS file, and is always inside the <HEAD> tag.

The GMP Game Engine File Line

<script type="text/javascript" src="gmp-engine.1.7.4.js"></script> This line loads the GMP game engine file, and should be at the end of your HTML page, just before the closing </BODY> tag. However, it must appear before your game code file.

The Game Code File Line

<script type="text/javascript" src="hello-world.js"></script> This line loads our javascript game file, and should be at the very end of your HTML page, just before your closing </BODY> tag.

We put all of our javascript game files at the end of the HTML body, because we want the browser to load most of the page before it starts executing any game code.

OK. We're done with our HTML file. Go ahead and close it.

Next, we'll start editing hello-world.js.

Step Three: Create An Empty Game

We're going to start our javascript file by creating an 'empty' game.

Open your copy of hello-world.js and add the following code: G.F.loadMain = function () { this.AI = G.F.mainAI; }; G.F.mainAI = function () {}; G.F.shipAI = function () {}; G.F.bulletAI = function () {}; G.F.explosionAI = function (cmd) {}; G.makeBlock('main', G.F.loadMain).loadBlock('main');

Let's look at what this code does.

The Function Definitions

The first five lines are the functions that will contain our game code. Notice that they are all members of the G.F object, which is a container for storing our custom functions.

We don't have to use G.F, but it acts like a namespace -- collecting our game functions together, and making them easy to recognise and reference.

G.F.loadMain will contain our game configuration code.

G.F.mainAI is our 'major' AI function, and it will contain the game play logic for Hello World.

G.F.shipAI, G.F.bulletAI and G.F.explosionAI perform sub-routines, and are called from within G.F.mainAI.

Creating And Loading The Main Block

The last line of our new code creates and loads a new Block named 'main'.

A Block is a container object that integrates our game code into the game engine. Our game is simple, so it only needs one Block.

Our new Block is stored automatically in the 'G.B' object, and we can reference it using G.B.main.

G.B.main has two major functions: G.B.main.load and G.B.main.AI. By default these functions are empty, but our code overwrites them with our custom functions, G.F.loadMain and G.F.mainAI.

G.B.main.load is overwritten when we create the Block. We just pass the desired function as an argument to G.makeBlock. This is just a convenience, we could also assign it manually.

Our other Block function, G.B.main.AI, is overwritten manually. We do this inside G.F.loadMain. We could have done this elsewhere, but it is convenient to assign our new AI function when a Block is loaded.

This is what happens when G.B.main is loaded:

  • G.B.main.load is called exactly once
  • G.B.main.AI is added to the global game loop.

Let's take a minute to talk about the game loop.

The Game Loop And The GMP Game Architecture

The game engine's primary job is to call a sequence of game code functions, over and over again, on a regular interval. We call this a game loop, because the game engine is looping over the same set of functions every time.

The game loop operates at high-speed, and it requires game code to be structured in certain way. We call this structure the game architecture.

Fortunately, GMP builds upon the architecture used by your web browser to build and manage dynamic HTML pages. Many game coding tasks are quite similar to making a dynamic web page.

The GMP API also provides a wrapper around many web page architecture features so that they will work with the game loop. This includes the way events and UI rendering are handled.

We will be demonstrating many game architecture features in this tutorial, but will be leaving many explanations of those features to other sections of the manual.

OK. Let's review chaining, and then get back to our code.

About Chaining

The last line of our new code contains our first use of chaining. Chaining is a javascript feature that lets us use the return object of a function directly, without having to assign it to an intermediate variable.

Chaining looks a lot like the '.' (dot) notation used with objects and their members, e.g., 'G.B.main'.

You can tell the difference by looking for the closing bracket of a function call just before the dot, e.g., 'G.makeBlock().loadBlock()'.

We can chain these functions because makeBlock returns the object that calls loadBlock, namely, the global object G.

Chaining will be used frequently in the rest of this tutorial.

Nothing To See!

Go ahead and save your javascript file, and then open the hello-world.html page in your browser. OK, there's nothing to SEE, but you are, in fact, running a game.

Let's move on and create some stuff we can see.

Step Four: Initialize Your Game Objects

The next step is to initialize the game objects, controls and state that we will need in our game.

Edit the G.F.loadMain function so that it looks like this:

G.F.loadMain = function () { this.AI = G.F.mainAI; G.KB.addKeys('LEFT','RIGHT','F'); G.setState({ targetCount: 10, targetLetters: ['H','E','L','L','O','W','O','R','L','D'] }); G.makeGob('viewport', G) .setVar({x:50, y:50, w:200, h:325}) .setStyle({backgroundColor:'#000000'}) .turnOn(); G.makeGob('ship', G.O.viewport) .setVar({x:87, y:220, z: 100, w:26, h:30, AI:G.F.shipAI}) .setStyle({backgroundColor:'#339999'}) .turnOn(); G.makeGob('help',G.O.viewport) .setVar({x:0, y:265, w:200, h:60}) .setSrc('Press <strong>F</strong> to fire<br /><strong>ARROWS</strong> to move') .setStyle({color:'#000000', backgroundColor:'#339999'}) .turnOn(); G.makeGob('bullet',G.O.viewport) .setVar({x:-100, y:-100, w:4, h:12, AI:G.F.bulletAI}) .setStyle({backgroundColor:'yellow'}) .setState({firing:0}) .turnOn(); G.makeGob('explosion',G.O.viewport) .setState({frame:0}) .setVar({x:-100, y:-100, w:4, h:12, AI:G.F.explosionAI}) .setStyle({border:'3px solid red'}) .turnOn(); for (var i = 0; i < 10; i++) { G.makeGob('target'+i, G.O.viewport) .setVar({x:30+(i%5)*28, y:25+Math.floor(i/5)*40, w:25, h:28}) .setSrc(G.S.targetLetters[i]) .setStyle({color:'#ffffff'}) .turnOn(); } };

Lets look at this new code.

The First Line: Assign Block AI

We've already seen the first line -- it assigns a new AI function to G.B.main.AI.

The Second Line: Assign Game Controls

The second line tells GMP to capture input from three keyboard keys: LEFT arrow, RIGHT arrow, and 'F'.

We'll be using these keys as the player input controls for our game.

We'll finish our controls over the next few sections of this tutorial.

The Third Line: Initialize Game State

The third line stores some custom game variables in 'G.S', the global state object.

Every game stores custom properties, or 'state', that is necessary for the play logic to work. By collecting our custom properties together, they are easier to identify, and they won't get mixed up with the built-in properties of our game objects.

Each of our major object types has an 'S' object for storing custom state. For example: 'G.S', 'G.B.main.S' or 'G.O.bullet.S'.

There are no rules for where to store game state. Just put it where it is most useful.

The Rest Of G.F.loadMain: Configure The Gobs

Most of our new code creates and configures Game objects, or Gobs. These are the 'physical' things in our game: the ship, bullet, help message, targets and explosion.

It may be helpful to think of Gobs as your game sprites. Keep in mind that they can be used for more than that, such as making a viewport.

Save your javascript file, and open the hello-world.html page in your browser. Your page should now have visible Gobs, and they should look like what you see to the right.

Our Gobs don't look 'nice' yet; for instance, our text is ugly, and our ship is just a blue box. Don't worry, we'll fix them up later.

Take a look at the code for creating each Gob. Each definition is a little different, but the code for each Gob does the same 3 things:

  • Make the Gob.
  • Set content, position, size, color & state
  • Turn it on.

Let's look at a few of these things in detail.

Referring to Gobs

New Gobs are stored automatically in the 'G.O' object, and we can reference them using G.O.viewport, G.O.ship, etc.

Setting Properties

The main function for setting built-in Gob properties is setVar, but there are many shortcut functions that can set a single property. We've used a few of them above to illustrate this. You can find all the shortcut functions in the API documentation.

You can also assign Gob properties directly, but setVar and its shortcuts allow for function chaining and setting properties in a batch.

Note also that CSS properties are named in javascript by removing any '-' (dashes) and capitalizing the next letter. For example 'background-color' is written 'backgroundColor' in javascript.

There is also a special function for setting Gob state, setState.

Some of our Gobs have custom state, and we'll be showing how we use it a little later.

G.O.viewport - A Special Purpose Gob

We made G.O.viewport first, because it is the parent of all the other Gobs.

Parent Gobs have two important properties.

First, child Gobs are positioned relative to their parent. This allows us to move the entire game just by moving the parent, G.O.viewport.

Second, by default child Gobs are only visible within their parent's width and height boundaries. This means that G.O.viewport acts like a 'window' onto its child Gobs. You can see them if they are in the middle of G.O.viewport, but they are hidden when they move past the edges.

This is a useful property. It 'crops' our game display to the dimensions of the viewport, and also allows us to move Gobs 'off screen' where they can't be seen.

We can see this right now -- look at the positions of our various Gobs. G.O.bullet and G.O.explosion are outside the width and height of G.O.viewport, and so are hidden. The other Gobs are inside the viewport dimensions, and are visible.

Defining Gob AI

Just like Block objects, every Gob has a built-in AI function. By default it does nothing, and is only useful if it is overwritten.

Take a look at the code, and you'll see that we overwrite (using setVar) the AI for 3 Gobs: the ship, the bullet and the explosion.

It can be very useful to move a sub-routine from the main Block AI into a custom Gob AI function. For instance, we will be putting the logic for moving our ship into G.F.shipAI.

Our code will look cleaner, and will almost certainly be easier to read.

Lets start writing our game AI, and take a better look at this.

Step Five: Write Your Game AI

Our next step is to write code for the game play logic. For convenience, we will refer to functions that contain play logic as AI.

The Hello World AI has five tasks it must perform:

  1. Move the ship
  2. Fire and move the bullet
  3. Animate explosions
  4. Trigger the explosion when the bullet hits a target.
  5. Reset targets after all have been exploded.

The first three AI tasks have their code written inside Gob AI functions, and the last two have their code written directly in our Block AI function, G.F.mainAI.

The first three tasks don't have to go into Gob AI functions -- we could just as easily code it directly inside G.F.mainAI. However, by putting these sub-routines in separate functions, we make the G.F.mainAI code a little tidier.

Regardless of where the code is stored, it is all executed inside G.F.mainAI.

We will write the code for the Gob AI sub-routines shortly. But first, let's look at the code in G.F.mainAI.

The Main AI Function: G.F.mainAI

Edit the G.F.mainAI function so that it looks like this: G.F.mainAI = function () { var i, target; // move ship G.O.ship.AI(); // move/fire bullet G.O.bullet.AI(); // animate explosion G.O.explosion.AI(); // trigger explosion if the bullet hits a target. for (i=0; i < 10; i++) { target=G.O['target' + i]; if (G.O.bullet.checkIntersection(target) ) { G.O.bullet.setState({firing:0}).setVar({x:-100,y:-100}).draw(); target.turnOff(); G.O.explosion.setVar({x: target.x + 6, y:target.y + 8}).AI('reset').turnOn(); G.S.targetCount--; } } // reset targets after all have been exploded if (G.S.targetCount < 1 && !G.O.explosion.on) { for (i=0; i < 10; i++) { G.O['target' + i].turnOn(); } G.S.targetCount=10; } };

The first few lines of code execute our Gob AI functions. We'll discuss the logic in those functions a little later on.

For now, let's look at the next chunk of code, which triggers explosions.

Triggering Explosions

Let's take a closer look at the code that triggers explosions (AI task #4).

The easiest way to explain this code is to present it again with detailed comments. The original code is in light blue. // Trigger explosion if the bullet hits a target. // Loop over all ten targets. We're going to be testing each one // for intersection with the bullet. for (i=0; i < 10; i++) { // Assign the current target to a 'shorter' variable name, // because we refer to it a lot in the next few lines. // Don't worry about the bracket notation for accessing a Gob: // G.O.x is the same as G.O['x']. We need to use the bracket notation because // we are dynamically constructing the target name with an expression. target=G.O['target' + i]; // Check if the bullet intersects the current target. // Returns true if both Gobs intersect and if target is turned on. if (G.O.bullet.checkIntersection(target) ) { // We detected an intersection, so we are going to 'trigger' an explosion. // First, move the bullet off screen (where we can't see it) and // tell it to stop firing. // It will appear as if it has been destroyed in the explosion. // We could turn it off, but it will get fired again ASAP, // and then we'll have to turn it back on. G.O.bullet.setState({firing:0}).setVar({x:-100,y:-100}).draw(); // Second, we turn off the target, because it has been destroyed. // We DO turn this Gob off instead of moving it off screen. // This is because targets are stationary and we don't want to // have to reposition it when we need it again. target.turnOff(); // Third, position the explosion Gob over the center of the target, // allowing for the border width of the explosion. // We also call the explosion AI with the 'reset' parameter, // which will resize the explosion to its starting size. // Finally, we turn the explosion on. If it was already on, then this just // redraws the explosion at it's new position. G.O.explosion.setVar({x: target.x + 6, y:target.y + 8}).AI('reset').turnOn(); // Fourth, we manually decrement our target counter. // We use this state variable in the code for our fifth // AI task (reseting the targets) G.S.targetCount--; } }

Now let's look at the next chunk of code in G.F.mainAI. This is the code for AI task #5: resetting the targets after they have all exploded.

Reset The Targets

Again, the easiest way to explain this code is with comments. The original code is in light blue. // reset targets after all have been exploded // We trigger the reset if two conditions are met: // 1) all targets have been exploded ( our counter has decremented to 0) // 2) The last explosion has finished animating. (this is just a nice effect) if (G.S.targetCount < 1 && !G.O.explosion.on) { // We've triggered a reset // Loop over all targets and turn them on. // Notice that we don't have to reposition them with that nasty code calculation // from the target Gobs definitions. This is why we turned them off // instead of moving them off screen! for (i=0; i < 10; i++) { G.O['target' + i].turnOn(); } // Reset our target counter to the correct value. G.S.targetCount=10; }

That's it for G.F.mainAI.

Let's move on to the G.F.shipAI function, and add the ability to control the movement of our ship.

Adding Movement To Our Ship

Go ahead and edit the G.F.shipAI function so that it looks like this: G.F.shipAI = function () { var t=this; // move the ship left if (G.KB.keys.LEFT.isPressed) { if (t.x > 7) { t.setVar({x:t.x-5}).draw(); } } // move the ship right else if (G.KB.keys.RIGHT.isPressed) { if (t.x < 167) { t.setVar({x:t.x+5}).draw(); } } return t; };

If you save your javascript file, and then open the hello-world.html page in your browser, you should be able to move your ship to the left and to the right. (That's all this function does).

Let's look at this code again, but this time with lots of comments. The original code is in light blue. G.F.shipAI = function () { // Assign the ship object to the variable 't' as a shortcut. // Notice that we are executing this function in the scope of // the G.O.ship object. This gives our movement code convenient access // to the ship object. var t=this; // Move the ship left // We test for 'isPressed' because we want a continuous movement for // while the LEFT arrow key is pressed. // Change this to 'wasPressed' and then try to move the ship. Very // different effect! if (G.KB.keys.LEFT.isPressed) { // The LEFT arrow key was pressed // Only move the ship leftward if it is still at least 7px from // the edge of the viewport. This just keeps the ship on-screen. // Set this number to a smaller or negative number and you will // be able to move the ship off-screen. if (t.x > 7) { // The ship is still on screen // Move the ship 5px leftward. Change this to a bigger value // to move the ship faster, smaller to move slower. t.setVar({x:t.x-5}).draw(); } } // This whole section is the same as above, but the movement is // to the right else if (G.KB.keys.RIGHT.isPressed) { if (t.x < 167) { t.setVar({x:t.x+5}).draw(); } } // Return the parent object so that it can be chained to another function. // This is a standard convention -- if your function doesn't need to // return a value, you can return the parent object so that the // function can be chained. return t; };

We're on a roll here, so lets add our bullet AI.

Adding Firing & Movement To The Bullet

Go ahead and edit the G.F.bulletAI function so that it looks like this: G.F.bulletAI = function () { var t=this; // start firing from nose of ship if (G.KB.keys.F.isPressed && !t.S.firing ) { t.S.firing=1; t.setVar({x:G.O.ship.x + 11,y: G.O.ship.y+10}).draw(); } // move the bullet up the screen if (t.S.firing) { if (t.y > 5) { t.setVar({y:t.y-18}).draw(); } else { t.setState({firing:0}).setVar({x:-100,y:-100}).draw(); } } return t; };

If you save your javascript file, and then open the hello-world.html page in your browser, you should be able to fire bullets by pressing the F key.

Let's look at this code again, but this time with lots of comments. The original code is in light blue. G.F.bulletAI = function () { // Assign the bullet object to the variable 't' as a shortcut. var t=this; // Start firing // We only fire if the F key is pressed AND the bullet is not // already firing. // We test for 'isPressed' because want continuous firing. e // Change this to 'wasPressed' and then try to move the ship. Very // different effect! // We are also using the state variable G.O.bullet.S.firing to track // the status of the bullet (firing or not). // This 'flag' is set to true as soon as the bullet is fired, and // stays true until the bullet is finished moving. if (G.KB.keys.F.isPressed && !t.S.firing ) { // Start firing the bullet // Set our firing state variable to 'true' t.S.firing=1; // Position our bullet in the center of the nose of our ship. t.setVar({x:G.O.ship.x + 11,y: G.O.ship.y+10}).draw(); } // Move the bullet up the screen // If our bullet is firing, then it must be moved upwards on the screen. if (t.S.firing) { // Move the bullet // We only move the bullet until it is reaches the top of the // screen. After that it will be hidden by the viewport. if (t.y > 5) { // Move the bullet upwards 18px. Decrease this value to slow // the bullet down, increase it to make it faster. // It's not recommended to move objects faster (a bigger // distance) than the smallest object they can collide with. // The reason is simple: they will 'jump' right through them. // We have to write some fancy code if we want really fast // objects (and that's another tutorial!) t.setVar({y:t.y-18}).draw(); } // The bullet has moved above the top edge of the viewport, so // we disable it. else { // Disable the bullet // We disable the bullet by setting t.S firing to false, and // then move the bullet off-screen. t.setState({firing:0}).setVar({x:-100,y:-100}).draw(); } } // Return the parent object so that it can be chained to another function. // This is a standard convention -- if your function doesn't need to // return a value, you can return the parent object so that the // function can be chained. return t; };

We're almost finished our game AI. Lets add the explosion animation logic and then we're done.

Animating The Explosion

Go ahead and edit the G.F.explosionAI function so that it looks like this: G.F.explosionAI = function (cmd) { var t=this, F; if (cmd == 'reset') { t.setState({frame:0}).setVar({ tx:0, ty:0, tw:0, th:0 }).draw(); } else { if (!t.on) { return t; } F=t.S.frame; if (F < 8) { t.setVar({ tx:-(F*F+1), ty:-(F*F+1), tw:F*F*2+2, th:F*F*2+2 }).draw(); } else { t.turnOff(); } t.S.frame++; } return t; };

If you save your javascript file, and then open the hello-world.html page in your browser, you should be able to see a red explosion everytime the bullet hits a target.

Notice that this AI function accepts a parameter, cmd. We are using this argument like a switch, so that the AI function will do different tasks depending on what the value of cmd. We didn't give this parameter to the other Gob AI functions, because they only do one thing.

Let's look at this code again, but this time with lots of comments. The original code is in light blue. G.F.explosionAI = function (cmd) { // Assign the explosion object to the variable 't' as a shortcut. // Locally scoped shortcut variables are common in functions, because they // allows us to write smaller code, and they are faster to write. var t=this, F; // Reset the explosion, if requested. // Take at look at the explosion trigger code in G.F.mainAI, and you // will see the explosion AI call that uses this code. if (cmd == 'reset') { // This line does 2 things: // 1) Reset the animation 'frame' in our animation sequence. // 2) Resize the Gob's HTML tag to 0 with (tw,th), and move its Gob // offset to (0,0) with (tx,ty). See the Gob API documentation // for more details on these parameters. t.setState({frame:0}).setVar({ tx:0, ty:0, tw:0, th:0 }).draw(); } // Else: Draw the explosion else { // If the explosion is turned off, just return. // We don't want to waste resources drawing it if it is turned off. // Notice that this code could be moved to G.F.mainAI: // Eg,: 'if (G.O.explosion.on) G.O.explosion.AI();' // (Try it and see). if (!t.on) { return t; } // Assign the explosion's state variable 'frame' to local shortcut variable 'F'. F=t.S.frame; // Resize the explostion if we are still on one of the first 8 animation 'frames' if (F < 8) { // This line does two things: // 1) Make the explosion's HTML tag a little bigger. // 2) Move the explosion's HTML tag so that it stays centered // on the Gob's position. // See the Gob API documentation for more details on // the tx,ty, tw & th parameters. t.setVar({ tx:-(F*F+1), ty:-(F*F+1), tw:F*F*2+2, th:F*F*2+2 }).draw(); } // Else we are finished animating the explosion. else { // Turn off the explosion. // We turn it off because we use the Gob's built-in on/off state // variable (G.O.explosion.on) in the above code to determine // if we should draw the explosion. t.turnOff(); } // Increment our animation frame t.S.frame++; } // Return the parent object so that it can be chained to another function. // This is a standard convention -- if your function doesn't need to // return a value, you can return the parent object so that the // function can be chained. return t; };

That's it! We've finished our game AI. Our game now does everything it's supposed to do.

The last thing we need to do is polish the appearance of Hello World.

Step Six: Polish Your Game

We're going to clean up the look of Hello World. We'll do this with CSS, and a few changes to our Gob definitions.

Edit the G.F.loadMain function, and make the changes indicated in the following code. Remove the red lines, and add the blue ones. G.F.loadMain = function () { this.AI = G.F.mainAI; G.KB.addKeys('LEFT','RIGHT','F'); G.setState({ targetCount: 10, targetLetters: ['H','E','L','L','O','W','O','R','L','D'] }); G.makeGob('viewport', G) .setVar({x:50, y:50, w:200, h:325}) .setStyle({backgroundColor:'#000000'}) .turnOn(); G.makeGob('ship', G.O.viewport) .setVar({x:87, y:220, z: 100, w:26, h:30, AI:G.F.shipAI}) .setStyle({backgroundColor:'#339999'}) .setVar({nextSrc:'<div id="shipNose"></div><div id="shipWings"></div><div id="shipBody"></div>'}) .turnOn(); G.makeGob('help',G.O.viewport) .setVar({x:0, y:265, w:200, h:60}) .setSrc('Press <strong>F</strong> to fire<br /><strong>ARROWS</strong> to move') .setStyle({color:'#000000', backgroundColor:'#339999'}) .turnOn(); G.makeGob('bullet',G.O.viewport) .setVar({x:-100, y:-100, w:4, h:12, AI:G.F.bulletAI}) .setStyle({backgroundColor:'yellow'}) .setState({firing:0}) .turnOn(); G.makeGob('explosion',G.O.viewport) .setState({frame:0}) .setVar({x:-100, y:-100, w:4, h:12, AI:G.F.explosionAI}) .setStyle({border:'3px solid red'}) .turnOn(); for (var i = 0; i < 10; i++) { G.makeGob('target'+i, G.O.viewport) .setVar({x:30+(i%5)*28, y:25+Math.floor(i/5)*40, w:25, h:28}) .setSrc(G.S.targetLetters[i]) .setStyle({color:'#ffffff'}) .addClass('target') .turnOn(); } };

Most of these changes involve removing our CSS styling from the javascript code. When assigned in the javascript game code, these styles are applied directly to a Gob's HTML tag.

We are going to move these styles into our hello-world.css file, where we have more flexibility.

Every Gob has an HTML tag, and this tag has an ID with the same value as the Gob's. For example, G.O.ship's default tag is '<div id="ship"></ship>'. This makes it easy for us to apply styles in our CSS file using the ID selector.

Notice also that we added two new lines of code.

The first line adds new HTML inside G.O.ship's tag. Each DIV inside the new HTML has an ID attribute, so that we can apply separate styles to each.

The second line adds of a class to each of our 'target' Gobs, so that we can apply a common style to each.

Let's look at our CSS file.

Edit The CSS File

Edit the hello-world.css file so that it looks like this: #viewport { background-color: #000; } #bullet { background-color:yellow; } #explosion { background-color: none; border: 3px dashed red; } #help { background-color: #399; color: #000; font-weight:bold; text-align:center; font:16px 'Comic Sans MS',Verdana,Arial,sans-serif; line-height: 28px; } #help strong { color: white; } .target { color:#ddd; border: 0px solid red; font: 24px 'Comic Sans MS',Verdana,Arial,sans-serif; text-align:center; } #ship div { position:absolute; } #shipNose { background-color: #fff; left: 10px; width: 6px; height: 30px; z-index: 10; } #shipBody { background-color: #666; left: 7px; top: 12px; width: 12px; height: 18px; z-index: 20; } #shipWings { background-color: #444; top: 20px; width: 26px; height: 11px; z-index: 30; }

Save your CSS file, and refresh your HTML page. Your game should now look like the game at the top of this page.

The CSS in our file is all standard stuff. However, let's review a few of the notable things about using a CSS file to style a game.

Using ID And Class Selectors

We can style each Gob using its name as an ID selector, and use standard CSS styling techniques. If you want to use class selectors, just add them to your Gobs using the appropriate Gob functions.

Beware The Box Model

Be very careful when applying border, margin and padding attributes.

Two words explain this pain: Box Model. Go ahead and google it if you don't know what it is.

By default, all Gobs have border, margin and padding values of 0 (zero). You won't even notice, because Gobs are absolutely positioned. But you may want to change these values when formatting a Gob with complex inner HTML, such as a help menu.

Just be aware that these properties affect the rendered size of your Gobs. You'll need to compensate for added padding, margins and borders by reducing the width and height of the Gob tag by an equivalent amount. Each browser, on each platform, can combine these properties differently, so you'll want to test these Gobs in multiple browsers.

Using the HTML5 DOCTYPE in our HTML will make this rendering much more consistent, but it doesn't fix the problem. Use these properties carefully!

Set Inner HTML Attributes Explicitly

You'll notice that we only set position and size attributes on the inner DIVs of the ship. For most Gobs, these properties are managed in the javascript, because they are frequently dynamic.

However, our Gob functions do not manage the inner HTML of a Gob. We must do this explicitly.

Since our ship's shape is static, we can just apply static CSS styles to the inner HTML. (Which is what we did).

Conclusion

Congratulations! You finished Tutorial #1, and have made a game with the GMP javascript game engine!

Continue hacking Hello World. Try different things to see what your changes do. Here's some specific things you might want to try:

Easy

  • Add more targets to the game, and position them around the viewport.
  • Change the design of the ship.
  • Change the 'play instructions' formatting to make it look nicer.
  • Change which keyboard keys are used for game controls.
  • Rearrange the chunks of logic in G.F.mainAI. How does it changes the way the game behaves?

Medium

  • Make the targets 'jiggle' (animate the position of Gobs and/or their tags).
  • Convert the targets to animated IMG sprites. (the API docs for Gobs will help).
  • Move code logic from Gob AI functions into the main Block AI function, without changing the way the game plays.
  • Rotate the game 90ยบ. This means the ship will be on its side and move up and down, and bullets will move horizonally.

Hard

  • Add 'rapid' firing with multiple bullets & explosions. Look at the code for galaxoid for clues.
  • Move the play instructions to a help menu. Look at the code for gogopop for clues.

There are a lot of other resources to check out from here. You can learn a lot from reading the gmp-engine.js file directly, as well as the API documentation.

Also, check out the templates and games on this site. They are full of techniques for implementing common game structures.

And finally, if you read this tutorial and found all the programming to be totally overwhelming, don't worry. Check out some of the javascript programming links found on my links page for more help.

Making games is a very rewarding hobby. Stick with it, and have fun!