Please click here if you are not redirected within a few seconds.
GMP Neko Tutorial › shanebow.com
GMP Neko Tutorial
Click anywhere to move the mouse, then watch Neko chase it!

This tutorial will take your understanding of Trevor Cowley's GMP game engine to the next level after you have completed the basic Hello World Tutorial on the GMP site.

You will implement the well known Neko game in javascript using some more advanced features of the GMP engine, specifically:

as well as some other miscellaneous game development tips and techniques along the way.

Prerequisites

This tutorial assumes that you have completed the Hello World Tutorial on the GMP site. If you understand that application, this page will be a natural progression for you.

About Neko

Neko was a small Macintosh animation/game written and drawn by Kenji Gotoh in 1989. "Neko" is Japanese for "cat," and the animation is of a small kitten that chases the mouse pointer around the screen, sleeps, scratches, and generally acts cute. The Neko program has since been ported to just about every possible platform, as well as rewritten as a popular screensaver.

For this example, you'll implement the game using the GMP Engine by Trevor Cowley using the original Neko graphics.

You need to set up an HTML file to display the game as shown. The important things are to:

  1. include the gmp-engine javascrpt file ahead of your code, and
  2. create an empty <div id="neko-game"> to hold the game.

Notice that I have commented out the links to the game's external stylesheet and javascript files.

While coding, I find it easier to just edit everything in one place (i.e. the html file), because the browser stubbornly caches external assets.1

As you follow along, put all your javascript between the <script></script> tags and your styles inside <style></style> tags. Then you can simply refresh the browser (press F5 in Windows) to see your changes.


<!DOCTYPE HTML>
 <html>
 <head>
    <!-- link rel="stylesheet" href="neko.css" -->
    <style>
       // during development, put your styles here
    </style>
 </head>
 <body>
  <div id="neko-game"></div>
    <script src="gmp-engine.1.7.4.js"></script>
    <!-- script src="neko.js"></script -->
    <script type="text/javascript">
       // during development, javascript goes here
    </script>
 </body>
 </html>

When the program development is finished, simply copy the contents of the <style> tag to neko.css, and the <script> tag to neko.js and uncomment the links.


1You can read about this caching problem on stackoverflow amoung other places. The www.quirksmode.org solution — enter the URL of the .js file in the location bar, load it, hit Reload — works; but do you really want to go through all that while you're developing?

We'll start with a basic shell that we can build on as we go. All of this should look familiar if you've gone through the Hello World Tutorial on the GMP site, so the explaination will be brief. I'll just point out the differences specific to Neko.

Relative Positioning

The first point has to do with positioning the game on the page. In the GMP tutorial, the "hello world" app was absolutely positioned at the upper left corner of the page with this viewport definition:


  G.makeGob('viewport', G)
   .setVar({x:50, y:50, w:200, h:325});

In order to relatively position the game on the page set nextStyle:{position:'relative'} on the viewport Gob, after specifying its parent element (i.e. the game's div) in the makeGob call as shown.

The Game Sprites

For this game there will be two primary game objects — the cat, Neko, and the mouse he is chasing. In conventional game terminology these are the sprites, but in GMP they are just "game objects" or Gobs.

To avoid confusion I've named the mouse Gob mickey since we will also be using the computer's mouse for input. From now on in this tutorial, mouse will refer to the computer's mouse, and mickey will refer to the mouse that Neko is chasing.

We need to create a main AI method, and an AI method for each of these sprites. At this point they just draw themselves — we'll implement the game logic below.

A Word About Debuging

I'm a bit old school when it comes to debugging — I like to dump variables to the screen from within the program. This lets the game loop run. So I've added a debug panel that can be deleted when the app is completed.

Right now the mainAI method displays the locations of Neko and Mickey (which were very useful to me in developing this game). When we get to the mouse handling, you might find it useful to dump the mouse location (as I did) — Or not, it's up to you!


G.F.loadMain = function () {
  this.AI = G.F.mainAI;

  var vp = G.makeGob('viewport',G,'div',
     document.getElementById('neko-game'))
   .setVar({w:300, h:350,
     nextStyle:{position:'relative'}})
   .setStyle({backgroundColor:'#ddd'})
   .turnOn();
     
  G.makeGob('neko', G.O.viewport)
   .setVar({x:(vp.w-32)/2, y:vp.h-150, z: 101,
            w:32, h:32, AI:G.F.nekoAI})
   .setStyle({backgroundColor:'#09f'})
   .turnOn();

  G.makeGob('mickey',G.O.viewport)
   .setVar({x:16,y:16, z:100,
            w:16,h:16,AI:G.F.mickeyAI})
   .setStyle({backgroundColor:'#f00'})
   .setState({moves:0})
   .turnOn();

  G.makeGob('debug',G.O.viewport)
   .setVar({x:0, y:vp.h-50, w:vp.w, h:50})
   .setStyle({backgroundColor:'#09f'})
   .turnOn();
  }

G.F.mainAI = function () {
  G.O.mickey.AI();
  G.O.neko.AI();
  // debugging
  var n = G.O.neko, m = G.O.mickey;
  var msg = "Neko: " + n.x + ", " + n.y +
     "
Mickey: " + m.x + ", " + m.y; G.O.debug.setSrc(msg).draw(); return this; }; G.F.nekoAI = function (){ this.draw(); return this; }; G.F.mickeyAI = function () { this.draw(); return this; }; G.makeBlock('main', G.F.loadMain).loadBlock('main');

We need to detect mouse clicks in order to place mickey in a new location. GMP offers two basic approaches that are exposed through the Global Mouse Input Manager Object, G.M. The first way is to use a tradional mouse handler to hook mouse events instaneously as as they occur. The other approach is much more convinient: GMP catches the mouse events for us and sets a flag which can be polled during the subsequent game loop iteration.

We'll use the polling approach because it leads to much cleaner code that adheres more closely to accepted game development practices2. To detect a mouse press we simply check whether G.M.wasPressed is true. If so, then G.M.up.x and G.M.up.y cache the location of the press (for the duration of the current game loop iteration). What could be easier?

Well, there is one slight caveat — The mouse press could have occured any place on the current page, not necessarily on our app. Because of this, the G.M.up coordinates are measured from the upper-left corner of the web page, rather than the game.

For our purposes we are only interested in clicks on the game's viewport. Once we have such a click, we need to translate it's location into "viewport" coordinates (since these are what we use to specify Gob locations in GMP). At first, this seems terribly inconvinient, but it's simple to do and gives us quite a bit more flexibility as developers.

The key is that each Gob exposes the following method and properties:

  • tagContainsXY(someX,someY), and
  • docx and docy which give the origin of the Gob (actually its associated DOM element) in absolute "page" coordinates.

Let's apply them to our game. In the highlighted code below, the mainAI only calls the mickeyAI after ensuring that a valid mouse click occurred. Otherwise we don't need to redraw Mickey.

Then the mickyAI sets its x and y location to the viewport coordinates of the click (by subtracting the absolute viewport location from the absolute mouse location) and calls draw().


G.F.mainAI = function () {
  if (G.M.wasPressed
  && G.O.viewport.tagContainsXY(G.M.up.x,G.M.up.y))
     G.O.mickey.AI();

  G.O.neko.AI();
  return this;
  }; 

G.F.mickeyAI = function () {
  if (this.on) {
     var vp=G.O.viewport;
     this.setVar({x:G.M.up.x-vp.docx,
            y:G.M.up.y-vp.docy}).draw();
     }
  return this;
  };

2Although there are similarities between dynamic web design and game development as pointed out by GMP author Trever Cowley, input handling is not one of them. In game development it is generally accepted that input processing is done as a distint process in the game loop to keep things orderly. See Onslaught Case Study for a nice explanation.

Neko Tiled Image

So far the game is pretty ugly — We can move a square (Mickey) anyplace on the screen and the other block (Neko) just sits there. Let's add a couple of images to make it prettier.

In game programming it is common to place all of the images for a sprite or even the entire game into one large image like the one here. ☛ This keeps all the artwork in one place and is more efficient since only one file needs to be downloaded and opened by the program.

This large image is a tile of images: There are four columns and nine rows, 36 images all together. Of these, 32 are the animation of the cat, three are unused, and one is the mouse. Each little image is 32px by 32px (but the mouse only uses a 16 x 16 corner of his image — more on this later).

GMP has built in support for using these types of graphics, so you be pleasantly surprized at how easy this step is! Once you set up the "sprite Gobs" for tiled images, you can easily specify which one you want to display in its AI method.

Initialize the Neko Sprite

We'll start with the Neko sprite — er, Gob — Anyway, the comments are pretty much self explanatory...


  G.makeGob('neko', G.O.viewport,'IMG') // use an <IMG> tag
   .setVar({x:G.S.w-32, y:G.S.h-100, z: 101,
            w:32, h:32,     // drawing size on screen
            th:288, tw:128, // size of the large image
            cw:32, ch:32,   // size of individual tiles
            tx:-16, ty:-16, // drawing offset
            AI:G.F.nekoAI,
            nextSrc:G.mediaURL+'neko.png'}) // image filespec
   .turnOn();

The tx and ty properties are optional. If you omit them, the image will be drawn with its upper-left corner at the current x and y settings. By specifying -16, we are telling GMP to draw starting at x-16 and y-16 which effectively centers the sprite at the current location.

The G.mediaURL is used as a path prefix for your image files. At the top of your G.F.loadMain add the line:
G.mediaURL='/path/to/image/files/';. If you don't specify this variable, GMP assumes the files are in the same folder as the .html file.

Draw a Neko in the AI Method

Now to draw any tile we just specify its zero-based row and column index via the cx and cy properties. For instance, setVar({x:43, y:75, cx:0, cy:1}).draw(); will draw the first image (cx:0) in the second row (cy:1), at location [43,75]. But for now, leave the nekoAI method alone and the first tile will be drawn at the starting location. In the next step we will make Neko move around.

Initialize and Draw the Mickey Sprite

To draw Mickey we use almost the exact same logic as for Neko, but we use a little trick: Since the Mickey sprite is only 16x16, we tell GMP to treat the large image as if it were 18 rows and 8 columns of 16x16 squares. With this reckoning, Mickey's tile is cx:6, cy:16

Since we always draw the same tile for Mickey (he's not animated), we can specify cx and cy properties in the makeGob and we don't need to modify the mickeyAI method at all. Here are the completed changes for Mickey:


  G.makeGob('mickey', G.O.viewport,'IMG') // use an <IMG> tag
   .setVar({x:G.S.w-32, y:G.S.h-100, z: 101,
            w:16, h:16,     // drawing size on screen
            th:288, tw:128, // size of the large image
            cw:16, ch:16,   // size of individual tiles
            tx:-8, ty:-8,   // drawing offset
            cx:6, cy:16,    // which tile to draw
            AI:G.F.mickeyAI,
            nextSrc:G.mediaURL+'neko.png'}) // image filespec
   .turnOn();

We're finally ready to animate Neko! He needs to chase Mickey in any direction, "sensing" the edges of the window. Once Mickey is caught, Neko will stop, yawn, scratch his ear, and sleep — Until Mickey appears again, then Neko wakes up and the fun starts all over..

To accomplish this programatically, we track Neko's state — he may be asleep, moving left, moving up, yawning, etc. — There are 17 possible states in this version of the game.

Moving Left Images

Each state has two or more images which are "cycled through" to create the illusion of movement. For instance, these two tiles ☛ are repeatedly swapped to make it look like Neko is running when in the moving left state.

Of course if we just cycle these images, it would look like Neko is running in place. So, for any states related to the chase, we also need to move Neko's location (i.e. his x and y coordinate) on the screen in the direction of Mickey.

And it's even more complicated, because some states need to automatically transition to others (e.g. sleep after cleaning for a while), and the rate of movement is not constant!

I've put this code into a separate file neko-brain.js for a couple of reasons:

  1. Explaining this code line by line is beyond the scope of this tutorial. But with the above background, you should be able to read the code which is actually a port of the original — Japanese state names and all! (Through the years I've ported it to C, C++, Java, and now JavaScript)
  2. I use this code as a benchmark of sorts: Although I love GMP, I may want to try another gaming platform. When evaluating a new engine, I like to see how easily Neko can be implemented — it exercises several important features of any game engine. So I've kept this piece as "agnostic" as possible.
  3. It demonstrates a simple way to "modularize" your JavaScript — to be honest, it's a quick and dirty hack job in this regard — I'm hoping to see some comments about better ways to do this.

Bottom line, on every game loop iteration, the neko-brain logic calculates three values: his new x and y coordinates and the index of an image tile. Using this information, the sprite is drawn exactly as described in the previous section. Here's the final nekoAImethod:


G.F.nekoAI = function (){
  G.F.nekoBrain.think(this, G.O.mickey).draw();
  return this;
  };

Step 6 — Finishing Touches

We're basically finished, but there are couple of loose ends.

First of all the game runs too fast. I've settled on the following setting to slow it down: G.interval = 80;
— This goes right at the beginning of the loadMain function.

Secondly, if you drag your mouse in the viewport you will notice that you "select" Neko. To prevent this add G.M.deselectGob = G.makeGob('viewport' ... — Also in the loadMain function.

Don't forget to remove your debugging Gob and any debugging calls elsewhere in the program.

Once you have done these things, it's time to move your code into a separate ".js" file and minify it for distribution.

Some Ideas for Further Exploration

  • Add a keyboard interface to allow the user to move Mickey with the arrow keys.
  • Expand the mouse handling to support drag and drop.
  • Keep a timer and track how long you can keep Mickey away from Neko.
  • You can speed up Neko after a user has gotten proficient at avoiding the cat.
    — hint: Expose and use the kSpeed variable in neko-brain.js.
    This could be the beginning of game levels.
  • Add a nice background or three. Neko could be a "Cat on a hot tin roof."
  • Animate Mickey by adding another sprite.
    — even if your not an artist you ought to be able to make Mickey wag his tail.
  • If you are an artist, change it into a fox chasing a rabbit,
    a dog chasing a cat, or a boy chasing a girl — oh, the possibilities.
  • Make an explosion when Neko gets the mouse — that would be cool!
  • Add sound effects — This would be ambitious since GMP does not have built-in sound support.

In any case, try to "make it your own." Change it, or apply the ideas to a totally different app.

Well that's it — Thanks for sharing.

Comments