Create a Mobile HTML5 RPG for Beginners

Intro

In this tutorial, I’ll show you how to create a (very) simple RPG demo that works in all HTML5 supporting platforms, using the Open Source Javascript framework LimeJS.

This framework allows you to quickly create simple games that work regardless of the screen resolution of your device (laptop, phone, tv). You can play these games through HTML5 supporting web browsers, or you can turn them into native mobile apps using Titanium or Phonegapp, and submit them to the various “app stores” out there.

RPG Games

What is the most important aspect of an RPG game? I’m sure each one will have their own opinion on this. Some might say the characters, their sometimes erratic evolution and deceptive plots. Some others might say exploring huge bizarre worlds. Or perhaps fighting weird creatures created by some tripping mind for our enjoyment. No matter the answer to this question, I think we can all agree that the following elements are no less than important:

  • World
  • Characters
  • Monsters
  • Story

We will thus cover these items, except for the “Story” one, as it’s really up to you to come up with something exciting.

Tutorial requirements

  • Basic HTML and Javascript knowledge
  • Basic notions of Object Oriented programming
  • A text editor or IDE to create and edit code
  • Some love for RPG games!

LimeJS

We’ll be using an awesome HTML5 game dev framework called LimeJS. This framework makes it easy to get started making your own games, as it comes with good support for aspects such as rendering images, user interaction for touch and non-touch screens, and many other game dev aspects (animations, 2D physics, etc). Moreover, LimeJS has a great and open community.

In this tutorial we will not go very deeply into installing the framework or using all of it’s power. You can get the installation instructions from the official guide here.

If you want a more detailed explanation of the installation process, I have created a full chapter on how to install the framework for Windows, Mac and Ubuntu in my video-based course [HTML5 Mobile Game Development for Beginners, where you can also learn a lot more about how to create games with this framework. I’ll talk more about this course at the end of this tutorial.

Installation Overview

  • Install Python
  • Install GIT
  • Install SVN
  • Download the ZIP file from www.limejs.com or clone their Github repo
  • Rename the folder we have downloaded as “limejs”, I will be refering to it with that name in this tutorial
  • Go into the limejs folder using your terminal. Type:bin/lime.py init

(this will download a bunch of dependencies) If you run into trouble installing LimeJS, the community is another excellent place to look for help and OS-specific guides.

Creating a new project

Once LimeJS has been installed, we can create a new project by going to our limejs folder using the terminal and typing:

bin/lime.py create rpg_tutorial

This will create a folder called “rpg_tutorial” in the limejs folder. That is where all of our files will reside.

Keep in mind that if you move this folder anywhere else, the game will not run. This is because while we are developing, our project will need the libraries in our limejs folder. Once we are finished with a project, you need to “compile” it, and the resulting JS files you can put anywhere you want.

Game World

We’ll start by creating our world, which is an image made with this tileset. The tileset has been created by Sharm and released with the licence CC By 3.0, which means we are free to use this for any purpose we want as long as we keep this notice and give attribution to Sharm (thank you Sharm, whoever you are!).

The game will have a resolution of 352×256. The game map has that same resolution (we won’t do scrolling in this tutorial). You can download the game map image from this link and copy it to your “rpg_tutorial” folder.

Let’s do some coding. Open the file rpg_tutorial.js, delete all of it’s contents (which is a sample game project, it’s worth checking out to get an intro to the framework) and copy the following:

//set main namespace 
goog.provide('rpg_tutorial');   
//get requirements 
goog.require('lime.Director'); 
goog.require('lime.Scene'); 
goog.require('lime.Layer');   

    //entrypoint 
    rpg_tutorial.start = function(){          
    var director = new lime.Director(document.body,352,256);     
    director.makeMobileWebAppCapable();     
    director.setDisplayFPS(false);          
    var mapScene = new lime.Scene();              
    director.replaceScene(mapScene); 
}

What I’ve entered there is pretty much a basic skeleton for a new project. For a good introduction to all these elements there is the official guide.

Mainly, what is happening here is:

  • We are telling the rest of the project that the namespace of the objects declared here is “rpg_tutorial”. A namespace is a unique identifier which helps you distinguish the objects in this file from objects that could have the same name in other files (imagine you create a “Circle”, there is already a “Circle” object in the framework, but they would not be confused as yours would be “your_namespace.Circle”, whereas the one that comes with the framework is called “limejs.Circle”).
  • Some objects are being imported so that they can be used here.
  • We declare the “director”, which is the object that does pretty much the same roles as a movie director: define main aspects, tell the game which scene we are playing now, etc
  • Create the current scene, which is where the action happens.

So far this doesn’t do anything, so let’s show our map after the scene has been declared:

var mapScene = new lime.Scene();
var mapLayer = new lime.Layer().setPosition(0,0).setRenderer(lime.Renderer.CANVAS).setAnchorPoint(0,0);

var gameMap = new lime.Sprite().setSize(352,256).setFill('map.png').setPosition(0,0).setAnchorPoint(0,0);

mapLayer.appendChild(gameMap);
mapScene.appendChild(mapLayer);

Now open the file rpg_tutorial.html in your web browser, you should be seeing the game map image. What we are doing here is creating a new Sprite object, which can be an image or just a rectangle with some filling (colours, gradients). An anchor point of (0,0) means that we’ll use the top left corner of the image to set it’s position (coordinates go from the top left corner of things in this framework, and with working with the Canvas in general). Position (0,0) means we are setting the image’s anchor point to the top left corner of the scene. Basically, the image will cover the entire screen.

map loaded

Notice how if you resize the browser window, the image will resize as well. Cool stuff. What I usually do as well is giving the “body” tag in the HTML file the colour black using CSS, that’s just my preference here.

The “layer” that we created is an invisible container where we put the elements. A layer itself can be animated, moved, resized, etc. We are using the Canvas on this layer, as opposed to using the DOM.

Hero

What would it be of RPGs without a hero? Who will then endure the vicissitudes of a decadent world full of monsters? Well, let’s create it then. What we’ll do is create a hero and have him move to wherever we click or touch on the map, how does that sound?

We will use another Sprite object to represent the hero (I encourage you to create custom objects for each game entity). The image file we’ll use is labelled a public domain and can be downloaded here. Copy it to your rpg_tutorial folder.

The code of the rpg_tutorial.start object will now be:

rpg_tutorial.start = function(){          
    var director = new lime.Director(document.body,352,256);     
    director.makeMobileWebAppCapable();     
    director.setDisplayFPS(false);          
    var mapScene = new lime.Scene();
    var mapLayer = new lime.Layer().setPosition(0,0).setRenderer(lime.Renderer.CANVAS).setAnchorPoint(0,0);

    var gameMap = new lime.Sprite().setSize(352,256).setFill('map.png').setPosition(0,0).setAnchorPoint(0,0);
    var hero = new lime.Sprite().setSize(40,36).setFill('hero.png').setPosition(100,100);          
    mapLayer.appendChild(gameMap);     
    mapLayer.appendChild(hero);
    mapScene.appendChild(mapLayer);
    director.replaceScene(mapScene); 
}

hero

We want the hero to move to where we click or touch, for which we need to bind the “click” and “touch” event in our map, so that the hero moves wherever the user clicked (or touched). This is done in LimeJS with the following code, which we’ll add after our hero definition:

goog.events.listen(gameMap, ['mousedown','touchstart'], function(e) {         
    var movement = new lime.animation.MoveTo(e.position.x,e.position.y).setDuration(1);        
    hero.runAction(movement);     
});

Some comments:

  • By using goog.events.listen we bind a game object (in this case the map) with events, in this case pressing the mouse or touching the screen.
  • We create a new “animation” which is a movement to the target coordinates, which will take 1 second to complete.
  • The animation is applied on the hero.

Ideally, one should create separate files for each game object. We will do that in a bit, but for the sake of simplicity and length I will leave it up to you to organise the code better. The next thing we’ll do to our character is giving it some characteristics:

hero.life = 20; 
hero.money = 100; 
hero.attack = 5;

Monsters

Time for evilness to appear. Let’s create a monster and put in on the map. The image file is a public domain one and you can get it here. After hour hero definition let’s add the monster sprite and give it some properties:

var monster = new lime.Sprite().setSize(40,36).setFill('monster.png').setPosition(200,200);
monster.life = 15;
monster.money = 10;
monster.attack = 1;
    mapScene.appendChild(monster);

monster

Let’s make it so that if the user touches the monster, we’ll be taken to a “fight scene”. It’s not gonna be as elaborated as you expect, but it can be used as a ground for your own further enhancements. Let’s first create the fight scene. We need to include the LinearGradient file to fill out our sky with a linear colour transition:

goog.require('lime.fill.LinearGradient');

After the monster definition add the following code to show the fighting scene with our hero and monster (again, your homework here is to make this code modular, etc):

var fightScene = new lime.Scene().setRenderer();    
var fightLayer = new lime.Layer().setPosition(0,0).setRenderer(lime.Renderer.CANVAS).setAnchorPoint(0,0);
var sky_gradient = new lime.fill.LinearGradient().setDirection(0,0,1,1)
    .addColorStop(0,'#B2DFEE').addColorStop(1, '#0000CD');
var sky = new lime.Sprite().setSize(352,128).setPosition(0,0).setAnchorPoint(0,0).setFill(sky_gradient);
var grass = new lime.Sprite().setSize(352,128).setPosition(0,128).setAnchorPoint(0,0).setFill('rgb(0,125,0)');
fightLayer.appendChild(sky);
fightLayer.appendChild(grass);

//show the images of the monster and hero
var fighterOne = new lime.Sprite().setSize(hero.getSize()).setFill(hero.getFill()).setPosition(50,210);
var fighterTwo = new lime.Sprite().setSize(monster.getSize()).setFill(monster.getFill()).setPosition(280,210);

fightLayer.appendChild(fighterOne);
fightLayer.appendChild(fighterTwo);
fightScene.appendChild(fightLayer);

We can check how it’s looking by having the Director putting it in place using replaceScene as we did earlier with “mapScene”.

Ok! let’s add now collision detection so that if our brave hero touches the monster, the fighting scene comes into play. Include this file, which contains the Google Closure library math methods:

goog.require('goog.math');

Then add this code after the fight scene definition:

hero.inFightScene = false;

lime.scheduleManager.schedule(function(dt) {
    if(!this.inFightScene) {            
        if(goog.math.Box.intersects(this.getBoundingBox(),monster.getBoundingBox())) {
            director.replaceScene(fightScene);
            fightLayer.setDirty(255)
            hero.inFightScene = true;
        }
    }
}, hero);

There is a lot happening here guys!

  • We are using the scheduleManager, which will be executed periodically. The parameter dt contains the elapsed time between iterations.
  • If the player is not already in the fighting scene, we are using Closure Library’s “Box” object to check for collision between the boxes of both characters (see how the bounding box of each is extracted).
  • If the characters are indeed colliding, replace the scene.
  • The setDirty() method is just a workaround for a framework bug when replacing between Canvas scenes.

fight scene

All ready! We have a pretty fancy fight scene in place, but what about the fighting itself? Well, you can create your own fighting algorithms involving all sorts of player choices, character attributes, items, etc. There is a lot of material online about RPG fights that you can use as inspiration. What I’ll do now is make up a really simple fighting algorithm, which will be basically tossing a random number and taking the “attack” attribute out of the enemy’s life attribute. But before that, let’s show the characters’ attributes in our fight scene, and add some fighting options.

Let’s include this file to create simple buttons:

goog.require('lime.GlossyButton');

The fight scene definition will look like this:

var fightScene = new lime.Scene().setRenderer();    
var fightLayer = new lime.Layer().setPosition(0,0).setRenderer(lime.Renderer.CANVAS).setAnchorPoint(0,0);

var sky_gradient = new lime.fill.LinearGradient().setDirection(0,0,1,1)
    .addColorStop(0,'#B2DFEE').addColorStop(1, '#0000CD');

var sky = new lime.Sprite().setSize(352,128).setPosition(0,0).setAnchorPoint(0,0).setFill(sky_gradient);

var grass = new lime.Sprite().setSize(352,128).setPosition(0,128).setAnchorPoint(0,0).setFill('rgb(0,125,0)');

fightLayer.appendChild(sky);
fightLayer.appendChild(grass);

//show the images of the monster and hero
var fighterOne = new lime.Sprite().setSize(hero.getSize()).setFill(hero.getFill()).setPosition(50,210);
var fighterTwo = new lime.Sprite().setSize(monster.getSize()).setFill(monster.getFill()).setPosition(280,210);

//labels with characters stats
var labelFighterOneLife = new lime.Label().setText('Life:'+hero.life).setPosition(50,150);
var labelFighterOneAttack = new lime.Label().setText('Attack:'+hero.attack).setPosition(50,170);

var labelFighterTwoLife = new lime.Label().setText('Life:'+monster.life).setPosition(280,150);
var labelFighterTwoAttack = new lime.Label().setText('Attack:'+monster.attack).setPosition(280,170);

//some options
var attackButton = new lime.GlossyButton().setSize(70,20).setPosition(40,10)
    .setText('Attack').setColor('#B0171F'); 

var runButton = new lime.GlossyButton().setSize(70,20).setPosition(120,10)
    .setText('Run').setColor('#00CD00'); 

fightLayer.appendChild(fighterOne);
fightLayer.appendChild(fighterTwo);

fightLayer.appendChild(labelFighterOneLife);
fightLayer.appendChild(labelFighterOneAttack);
fightLayer.appendChild(labelFighterTwoLife);
fightLayer.appendChild(labelFighterTwoAttack);

fightLayer.appendChild(attackButton);
fightLayer.appendChild(runButton);

fightScene.appendChild(fightLayer);

fight scene2

Let’s implement the “Run” behavior by binding the button “click” or “touch” with going back to the map scene. Add the following after the fight scene definition.

//run away, coward
goog.events.listen(runButton, ['mousedown','touchstart'], function(e) {
    //go back to the map
    director.replaceScene(mapScene);
    mapLayer.setDirty(255)

    //move the hero away from the monster, or the fight scene will be triggered again!
    //this is just a quick, non-elegant way of doing this
    currentPos = hero.getPosition();
    hero.setPosition(currentPos.x-60, currentPos.y-60);

    hero.inFightScene = false;

});

Righto. We have a brave hero who can get into fights and run away from them. Sweet.

Now let’s man up a bit and get into the fighting itself with this ugly, vicious creature/monster/evil king once and for all. The simple fighting algorithm will work as follows:

  1. Generate a random number, if it’s < 0.5, the player hits, otherwise the monster hits.
  2. When a character hits the other character, the attacker’s “attack” attribute will be subtracted from the opponent’s “life” attribute.
  3. If a character’s “life” attribute reaches zero.. he is then ready to be buried.

We’ll implement this by binding the attack button to the “click” and “touch” events, and by running this simple fighting algorithm when any of those events are triggered. As I’ve mentioned earlier, you are very welcome to transform this code into something modular and scalable, and if you do so and want to share it with the world just let me know and I’ll add it to the tutorial, or you can also do it through the Github repo.

//fighting algorithm
goog.events.listen(attackButton, ['mousedown','touchstart'], function(e) {

    //generate random number
    var randomStuff = Math.random();

    //the player hits!
    if(randomStuff < 0.5) {
        monster.life -= hero.attack;

        //is he dead yet?
        if(monster.life <= 0) {
            console.log('monster dead');
            //get the monster money
            hero.money += monster.money;

            //go back to the map
            director.replaceScene(mapScene);
            mapLayer.setDirty(255);
            hero.inFightScene = false;

            //delete the monster object
            monster.setHidden(true);
            delete monster;
        }
    }
    else {
        hero.life -= monster.attack;

        //have you been killed?
        if(hero.life <= 0) {
            var labelGameOver = new lime.Label().setText('GAME OVER!!!').setPosition(160,100);
            fightLayer.appendChild(labelGameOver);
        }
    }

    //update stats
    labelFighterOneLife.setText('Life:'+hero.life);
    labelFighterTwoLife.setText('Life:'+monster.life);
});

Key points here:

  • We are listening to “click” and “touch” events on the attack button using the same technique we’ve used earlier.
  • A random number decides who “hits”. Instead of being 50% each, you could make this dependent of the character’s agility or experience.
  • If the monster is dead, we are taking their money ($$$) and killing the object.
  • It a pretty unfair fight, as we are way stronger than the monster. If you happen to die I added a “game over” text to show up. Now that you know how to work with scenes, you could go to a game over scene.

To prevent the fight scene to be loaded again from the bounding box of the monster, change the line where we checked for collision detection, for this one, so that only alive monsters trigger this:

if(monster.life >0 && goog.math.Box.intersects(this.getBoundingBox(),monster.getBoundingBox())) {

Time to Play and Download

You can download the complete game files from this link. As I mentioned earlier, running these files outside the limejs folder is not gonna work. In order to deploy a LimeJS project you have to follow this guide.

What’s Next

After doing this tutorial you are all good to get started with this awesome framework. The first thing I reckon you should look into is on making this more modular, for instance putting each game object in a separate file, and add some methods so that you are not repeating the code. Also, the fighting scene display and fight algorithm should be made generic to “any” monster instead of having one monster hard coded. Getting you into Game Dev was my goal here, not teaching OOP design patterns (there are plenty of tutorials for that out there!).

Now where should you go next? I have an idea.

HTML5 Mobile Game Development for Beginners

Banner11 1

I’ve prepared a comprehensive online course which will guide you through the creation of HTML5 crossplatform games using LimeJS game development framework. The course is 100% video based so that you can see in real time how games are created from scratch. Each lesson comes with its own zip file with all the code so that you can have a play with the examples and used them as starters for your own games.