Writing a 2d Game using Canvas

The vision

C1 Droid shooting at hero

C1 Droid shooting at hero

A 2d shooter similar to the classic DOS game Cyberdogs that I used to play with a friend. It should have robot controlled zombies and smart bad guys that will run away if they are already hurt. It should have the ability to create multiple levels and the levels should be created using a map maker.

Check out the game here

Writing the core engine

Organizing the game code

JavaScript can be chaotic at the best of time, and with writing something as complex as a game it can quickly become complicated and messy. To counter the problem one must try and think about the objects, modules, level structure upfront.

I have chosen to use pseudo classic JavaScript inheritance. This basically means that the prototype of a derived object points to an instance of the base object. In addition to that the base object’s constructor is called using the apply method with the derived object sent in as the context. This means that the base objects constructor gets run against the derived object.

Creating graphics

I am using Fireworks CS5 to create all of the sprites. The whole image is exported as a single png file and each type (game object) within the game knows where its image is withing the main “sprites.png” image.

Game Types

rp.GameObject

Base for all game objects. Contains methods/properties that many of the derived objects will use.

rp.GameObject = function () {
	rp.BaseObject.apply(this, arguments);
	this._name = "GameObject";
	this._X = 0; 
	this._Y = 0;
	this._directionRadians = rp.Math.RadiansPerCircle();
	this._turnRate = 0.0003;
	this._directionDepth = 0;
	this._speedX = 0;  //which way am i moving
	this._speedY = 0;
	this._collisionRadius = 10;//how wide am I across
	this._collisionCircle = true;	//circle or a square	
	this._collisionOffsetX =0;
	this._collisionOffsetY =0;
	this._maxX = 0;
	this._maxY = 0;
	this._shouldBeRemoved = false;
	this._decorators = 0;
	this._LOSDistance = 500;
};
rp.GameObject.prototype = rp.TypeHelper.Extend(new rp.BaseObject, {
        //To be overridden in derived objects
	Init:function(){...},
	Update: function (timeSinceLastLoop, levelTime) { }, 
	Draw: function (ctx) { },

        //Common to all
	CheckCollision: function(obj){...},
	CheckCollisionSimple:function(x,y){...},
	Collide: function(obj){...},
	BinMe: function(){...},
	GetTileX:function(){...},
	GetTileY:function(){...},
	CanSeeHero:function(){...},
	CanSeeUnit:function(unit){...},
	HeroCanSeeMe:function(){...},
	Stop:function(){...},
	IsMoving:function(){...},
	TurnToDirectionAtCorrectSpeed :function(direction,timeSinceLastLoop){...}
});

rp.Hero

Where would a game be without a hero. This object represents the hero. Its main difference from other objects is that it accepts user input.

rp.Hero = function () {
	//Call the base constructor giving this as the context
	rp.GameObject.apply(this, arguments);
	...
	rp.Decorators.Add(this,rp.Decorators.Collidee);
	rp.Decorators.Add(this,rp.Decorators.GoodGuy);
};
rp.Hero.prototype = rp.TypeHelper.Extend(new rp.GameObject(), {
	Draw: function (ctx) {...},
	Update: function (timeSinceLastLoop, levelTime) {...},
	Collide: function(obj){...}
});

rp.BadGuyBase

This is the base object for all bad guys. Much of the collision logic as the AI execution logic is handled in here.

rp.BadGuyBase = function(){
	rp.GameObject.apply(this, arguments);
	...
	this._name = "Bad Guy";
	...
	this._isExploding = false;
	this._command = null;
	this._actionStack = new rp.ActionStack();
	this._lifeBarWidth = 40;
	this._lifeBarTop = -30;
	this._lifeBarLeft = -30;
         ...
	rp.Decorators.Add(this,rp.Decorators.Collidee);
	rp.Decorators.Add(this,rp.Decorators.Collider);
	rp.Decorators.Add(this,rp.Decorators.BadGuy);
};
rp.BadGuyBase.prototype = rp.TypeHelper.Extend(new rp.GameObject(), {
	BaseDraw: function (ctx) {...},
	BaseUpdate: function (timeSinceLastLoop, levelTime) {...},
	Collide:function(obj){...},
	MoveToOuterPointOfCollision:function(iObj, xDiff, yDiff, timeSinceLastLoop){...},
	IsThisCollider:function(obj,xDiff,yDiff){...}
	DrawLife:function(ctx){...}
});

rp.StaticObjects

Some of the units leave carcasses when they die. Having that image rendered by the object that was destroyed would mean that it would have to be kept in memory and be updated in the game loop. In the interest of performance I have instead introduced an object that will always be in the game loop and would be responsible for rendering such images. It can also be used for rendering any other static images that are required.

rp.StaticObjects = function(){
	this._hasCreatedSprites = false;
	this._staticObjects = [];
};
rp.StaticObjects.prototype = rp.TypeHelper.Extend(new rp.GameObject(), {
	Update:function(){
	},
	Draw: function (ctx) {
		for(var i =0 ;i < this._staticObjects.length;i++){
			var obj = this._staticObjects[i];
			obj.Sprite.DrawImage(ctx, obj.X, obj.Y, obj.Rotation)
		}	
	},
	AddStaticObject:function(x,y,sprite, rotation){
		this._staticObjects.push({X:x, Y:y, Sprite:sprite, Rotation:rotation});
	}
});

rp.Decorators

JavaScript has no concept of interfaces or decorators. I do however need something like a decorator pattern to see if an object is a Collider, BadGuy, Projectile etc.. . So I have added a number field to the rp.BaseObject (base type of all objects) called ‘_decorators’. I will use the bit-wise operators (below) to add and subtract the various decorators to an object. This should be lightening quick and saves me having to add loads of fields to my object called things like ‘_isProjectile’. I have written a very simple module that does these operations and I use this throughout the objects in the game.

/*My very fast decorator pattern*/
rp.Decorators = {
	Projectile:1,
	Collidee:2,
	Collider:4,
	BadGuy:8,
	GoodGuy:16,
	CollisionAvoider:32,
	Tile:64,
	Is:function(obj,typeAsNum){
		return (obj._decorators & typeAsNum) === typeAsNum;
	},
	Add:function(obj,typeAsNum){
		obj._decorators = (obj._decorators | typeAsNum);
	},
	Remove:function(obj,typeAsNum){
		obj._decorators = obj._decorators ^ typeAsNum;
	}
}

You can think of Collidee as a kind of loose interface. If something is a Collidee then then it has a Collide method that will be called when it collides with something. The Collidee is responsible for what it should do inside the collide method. For Example, If zombie unit gets hit by a projectile then it should cause damage and eventually die. This logic is contained within the collide method.


rp.Tile

This object represents a single tile. Tiles are decorated with the Collider decorator if they are either not passable or are collideable.

  • _isPassable = Unit can walk through it
  • _isCollidable = Projectiles will collide with it

A pond would not be collidable and also not passable, A wall would be collidable and not passable… you get the idea.

rp.Tile = function(sprite, isPassable,collidable,x ,y , tileWidth, tileHeight){
	rp.GameObject.apply(this, arguments);
	...
	this._name = "tile";
	if(!this._isPassable || this._isCollidable){
		rp.Decorators.Add(this,rp.Decorators.Collider);
	}
	rp.Decorators.Add(this,rp.Decorators.Tile);
}
rp.Tile.prototype = rp.TypeHelper.Extend(new rp.GameObject(),{
	Draw: function (ctx) { 
		this._sprite.QuickDraw(ctx, this._X, this._Y, 0);
	}
});

Tiles are not handled exactly like other objects. There is no point in calling the Update method on a tile, because they have not update logic. Also, you would not want to iterate though all of the tiles in the game when drawing because most of them wont be in view.

The tiles are stored in a multi-dimensional array in the same way as they would be displayed in the map. This means that finding which tiles are currently in view is fairly simple. 1. Find out where the view port is in the real map 2.Translate that onto the multi-dimensional array (smaller map) and read out all of the tiles that would be in view (by looping though only those in view). This logic is handled in the GetVisibleTiles Method of the rp.MapManager.

Game Loop

The game loop should run as fast as it can, I will measure the time taken between loop iterations and move the game objects the according distance. For Example.. If the game loop was running slower then an object moving forward would move further on each iteration.

GameLoop: function () {
...
this.UpdateGameObjects(timeSinceLastLoop,thisLoopStartTime);
this.QueueNewFrame(rp.GameManager.DoLoop);
....
}

Each loop the UpdateGameObjects method of the gets called. It is responsible for iterating each of the objects that need to be updated and calling update on them. It will pass in the time since the last loop so that the object can move or update its self accordingly. It will also pass in the time since the start of the game so that objects can make decisions based on how far though the level (in time) the current time is.

QueueNewFrame will use the best method available to register the GameLoop method ready for the next iteration. Using the requestAnimationFrame is the best possible method available because you are basically asking to be included in the browsers rending loop. If this is not available in any form then window.setTimout() is used.

QueueNewFrame: function (renderingLoop) {
		if (window.requestAnimationFrame)
			window.requestAnimationFrame(renderingLoop);
		else if (window.msRequestAnimationFrame)
			window.msRequestAnimationFrame(renderingLoop);
		else if (window.webkitRequestAnimationFrame)
			window.webkitRequestAnimationFrame(renderingLoop);
		else if (window.mozRequestAnimationFrame)
			window.mozRequestAnimationFrame(renderingLoop);
		else if (window.oRequestAnimationFrame)
			window.oRequestAnimationFrame(renderingLoop);
		else {
			QueueNewFrame = function () {
			};
			intervalID = window.setTimeout(renderingLoop, 16.7);
		}

rp.ActionStack

The ActionStack is the container that each unit uses for managing the its actions. Each object has a list of actions that is to be executed in order. For example, A unit may have a move to destination action followed by a shoot action.

rp.ActionStack = function(){...};
rp.ActionStack.prototype = rp.TypeHelper.Extend(new rp.UnitAction(), {
	AddAction:function(action){...},
	HasActionOfType:function(action){...},
	HasActionByNum:function(n){..},
	GetActionByNum:function(n){...},
	Update:function(obj, timeSinceLastLoop, levelTime){...},
	Clean:function(gameTime){...},
	CancelAll:function(){...}
})

AI Command and Actions

I have created a fairly simple(ish) concept for controlling of units by the AI. It works will using 2 types.

Commands

A command can be though of as a command that a general might give to a a solder. “Soldier I want you to stay here and guard this position”. To the soldier that means 1. Dont move 2. Shoot at enemies that approach

Actions

An action is what a soldier might to to fulfill a command. For Example, Shoot at enemy, Move to Location.

From the descriptions above we can conclude that a command is more high level than an action. The AI for the game will just deal with the commands. The individual unit will deal with the actions.

A command that is running may add new actions to the action stack. But the unit may also add actions. For Example, If the unit takes a lot of damage it may decide to move to a safe location. This action may trump other actions or may cancel them completely. Once this action has been completed, the units current command may add actions to make the unit re-engage the enemy.

Path-finding

After some research I decided to use the A* algorithm. I wrote my own JavaScript implementation but it wasn’t really fast enough because the structure that I was storing the nodes didn’t lend it self the the task. I instead settled for using the http://github.com/bgrins/javascript-astar. It uses a binary heap as the storage for the nodes which is very fast when accessing the nodes the way that the A* algorithm does.

Collision detection

Collisions are detected on each frame. All of the colliders are checked against all of the Collidees. Every object marked as a collidee/collider must set is _collisionRadius and _collisionsCircle. After that the collision detection will be handed by the rp.LevelEngine. If an object is a weird shape (not a circle or square) then it will have still use the rp.LevelEngines collision detection, but will then run further detection inside its Collide method and return if thinks that no collision occurred.

The basic rp.GameObject collision detection is as follows:

CheckCollision: function(obj){
		if(this._shouldBeRemoved){
			return;
		}
		var xDiff = ((this._X<< 0) +this._collisionOffsetX) - ((obj._X<< 0) +obj._collisionOffsetX);
		var yDiff = ((this._Y<< 0) +this._collisionOffsetY) - ((obj._Y<< 0) +obj._collisionOffsetY);

		if(xDiff < 0){xDiff = xDiff*-1;}
		if(yDiff < 0){yDiff = yDiff*-1;}

		var overallCollisionRadius = this._collisionRadius + obj._collisionRadius;
		//square collision
		if(xDiff < overallCollisionRadius && yDiff < overallCollisionRadius){
				if(!this._collisionCircle){
					this.Collide(obj);
					return;
				}

				//circle collision
				if(Math.sqrt((xDiff*xDiff) + (yDiff*yDiff)) < overallCollisionRadius){
					this.Collide(obj);
				}

		}
	}

First it checks to see if the squares overlap, Then if the unit has a circle collisions radius, it checks to see if the circles overlap. If it detects that they overlap then there is a collision

Collidees

Objects that have Collide method. This will be called when something hits them

Colliders

Objects that are that are checked against the collidees to see if they overlap.Some object are both collides and collidees.

Sprites / animated and non-animated

There are two types of sprites

  • rp.Sprite

    This is responsible for rendering an image to the screen, it will rotate and position the image depending on the viewport rotation and the images location withing the map/viewport.

    When it is constructed a single image is specified

  • rp.AnimatedSprite

    When a rp.AnimatedSprite is constructed and array of objects is used and each object specifies an image and a display period. These are the frames of the animation and the rp.AnimatedSprite is responsible for showing the correct image on the correct frame.

    Some animations only need to play once (explosions for example) and others need to repeat this is implemented in this class also.