Wednesday, October 22, 2014

Creating Configurable Game Levels In Unity

This post is about creating game levels that are configurable using XML files in Unity3D.
However the concept can be easily applied anywhere.

When creating a game that contains levels, the last thing a developer would like to do is to code every single level. Instead what he wishes to do is to create independent game objects which he can put together in different combinations and levels using easily configurable XML files.

When developing LittleBirdy Android game I had to generate tens of levels because the game evolves from one level to another with different difficulties, different obstacles or surprises.

To make the task easier on myself, I conceptually designed a generic structure for all levels.
So each level will have a name and a description, a success message to be displayed when the player wins and failure message when he loses.
Furthermore the level must contain the scoring rule such as the target points to earn, the time limit, the targets to hit and the order in which they must be hit.

Then comes the board, which is the rectangular area where the gaming takes place which is different from the other displays such as the current score, the menu etc...
Inside the board we find the targets to be hit, their location, their condition of appearance, the points earned.
Then comes the obstacle settings inside the board. Each obstacle has a location and a dimension as well as a certain behavior: fixed, moving, rotating, growing/shrinking.
Outside the board element, you can find the BirdSettings that defines the number, location and velocity of the birds that will be in the level.
The BranchesSettings is used to tell how many Left and Right branches available in this level.
Finally the GiftSettings are the gift objects that appear from time to time to give the player extra bonus or eliminate certain threat.

The XML structure appears like the following:


Level
 |
 |------ Scoring
 |               |-------Target
 |               |-------Timer
 |               |-------Rules
 |               |-------RuleOrder
 |
 |
 |------ Board
 |               |------- Targets
 |               |              |----- Target (type, count, appearance times ...)
 |               |
 |               |------- Obstacles
 |               |              |----- Obstacle (type, count, dimensions, postion ...)
 |               |              |             |
 |               |              |             |--- ObstacleBehavior (rotation, translation)
 |
 |
 |------ BirdSettings
 |               |------- Birds
 |               |              |----- Bird(name, type, x, y, velocityX, velocityY)
 |
 |
 |------ BranchSettings
 |               |------- Branches
 |               |              |----- Branch(name, side, count, image ...)
 |
 |
 |------ GiftSettings
 |               |------- Gifts
 |               |              |----- Gift(type, count, startTime, endTime, duration ...)



For each node in the above structure corresponds a C# class with the same name.
Using System.Xml.Serialization of .Net it is very easy to serialize/deserialize object hierarchy into XML files.
The method DeserializeReader  shows how easy to read an XML file and create a C# object hierarchy from it.

         private Level DeserializeReader(TextReader reader){  
                XmlSerializer ser = new XmlSerializer(typeof(Level));  
                Level level = (Level)ser.Deserialize(reader);  
                return level;  
           }  


Once the Level C# object hierarchy is ready, we can proceed to create Unity GameObject from these objects and assign to their components the corresponding data reas from XML file.

 void Build(Level lvl, List<GameObject> gameObjects){  
      if(lvl.Board != null){  
           GameObject board = GameObject.FindGameObjectWithTag("Board");  
           // create Targets  
           if(lvl.Board.Targets != null){  
                foreach(Target t in lvl.Board.Targets){  
                     try{  
                          GameObject go = GameObjectFactory.CreateTarget(t);  
                     }catch(Exception ex){  
                          Debug.LogException(ex);  
                     }  
                }  
           }  
           // create obstacles  
           if(lvl.Board.Obstacles != null){  
                foreach(Obstacle o in lvl.Board.Obstacles){  
                     try{  
                          GameObject go = GameObjectFactory.CreateObstacle(o);  
                     }catch(Exception ex){  
                          Debug.LogException(ex);  
                     }  
                }  
           }  

           // build the Gift Settings  
           if(lvl.GiftSettings != null && lvl.GiftSettings.Gifts != null 
                                       && lvl.GiftSettings.Gifts.Length > 0){  
                BuildGiftSettings(lvl, lvl.GiftSettings);  
           }  
// Set the bird if(lvl.BirdSettings != null && lvl.BirdSettings.Birds != null && lvl.BirdSettings.Birds.Length > 0){ Bird birdInfo = lvl.BirdSettings.Birds[0]; CBird.Instance.BirdSettings = birdInfo; } } }



The GameObjectFactory contains method to create Unity GameObject objects and assigns to them the corresponding XML data.
For example CreateTarget checks whether it knows hos to create the given Target object, TargertPeanut in the code below, and instantiates the GameObject using Unity APIs then gets the Unity Component (CPeanut) attached to the GameObject and gives it the Target 't' in order to initialize the GameObject variables such as location, speed, movement etc...


      GameObject CreateTarget(Target t){  
           if( t is TargetPeanut){  
                go = GameObject.Instantiate(Resources.Load("prefab/peanut")) as GameObject;  
                CPeanut gPeanut = go.GetComponent();  
                gPeanut.PeanutInfo = (TargetPeanut)t;  
           }  
...
      }  


Using this approach hundreds of game levels could be created by simply duplicating XML files and apply some changes to each one.
In a team of two or more people, a developer can program each object or character of the game in an independent manner, while a level designer would easily write XML files to create combinations of scenarios and game play. With some extra efforts they can build level editor so level design will become a drag&drop gesture.



Friday, January 10, 2014

Tank Battle Field, HTML5 Game #6: First enemy unit

Despite that the Flak 88 was a fearsome German anti-tank gun, it is not its historical reputation that made it through to be the first enemy unit in Tank Battle Field game.

As a matter of fact two reasons make it appealing:
- Easiness of animation
- Cheap artificial intelligence (AI) requirement

The animation

Flak88 animation is rather simple. The canon itself is composed of the base and the gun. The base is symmetric and fixed, while the gun can rotate 360°

It is easy to see a similarity with the tank chassis and its turret.
Notice that the gun orientation is horizontal and to the right (towards the increasing x-axis). This is important in case we don't have to worry about the initial angle. Otherwise we should subtract the initial angle each time the gun rotates.
Here's an example:
Suppose the sprite of the gun is upwards like in this picture.

Since the angular system is like the trigonometric one, this gun makes 90° with the x-axis. We are speaking visually wise. However the code does not know that! What it knows is that everything starts at 0°. So if you tell the gun to rotate 90°, the graphic engine will rotate the above sprite by 90° which makes it look like this.
Sadly enough, this is not our intention because visually speaking it is equivalent to 180°. To solve this we subtract 90° from the rotation angle to obtain the desired visual effect.
In order to avoid all this fuss, we draw (from the start) the gun horizontally and oriented to the right.

It is important to differentiate what your eyes see and what data is supplied to the code.

The artificial intelligence (AI)

Probably Flak88 has the easiest AI to implement in any war game!
Even in real life, the Flak88 mission is to track a tank until it has a good shot, then fires.
Same thing happens in the game. Flak88 calculates the angle that separates its gun from the center of the tank. Then starts closing this gap by rotating the gun until the gap reaches 2° or less. At this moment it fires.




There are times when the center of the tank is hidden behind a building. In such cases the Flak88 searches of any of the tank border points to which it has a clear line of fire, if it finds ones it closes in on it and fires.

Time now for some maths calculations:
The angle between the Flak88 and the tank is given by the calculating the arc-tangent of the slope of the line passing by the tank and the Flak88. The arc-tangent returns an angle within the range [-180, 180]. But on the other hand the angle of the gun is expressed in the range [- 360, 360].
So to be able to compute the difference between the two angles, we convert the gun angle to the range  [-180, 180]. This is done like the following:

if gunAngle > 180  then gunAngle2 = gunAngle  - 360 (ex: if gunAngle =   270° => gunAngle2 = -90°)
if gunAngle < -180 then gunAngle2 = gunAngle + 360 (ex: if gunAngle = -270° => gunAngle2 =   90°)

Now we calculate the deltaAngle which is the difference between the angle of the tank and the Flak88 and the Flak88 gun.

if tankFlakAngle and  gunAngle2 have the same signs (both positive or negative) then
deltaAngle = tankFlakAngle - gunAngle2

However if tankFlakAngle and  gunAngle2 have opposing signs (one is positive and the other is negative) then we have to be careful how to compute deltaAngle .
Suppose tankFlakAngle  is 178° and gunAngle2 is -178°

Computing deltaAngle as tankFlakAngle - gunAngle2 will result in an angle of 356° which is not logical since as you can see on the picture above the real difference is 4°.
We can reach this result by doing the following:
if gunAngle2 < 0  then the gun is moving clockwise so deltaAngle must be negative:
deltaAngle = - ( (180 - tankFlakAngle ) + (180 + gunAngle2 ) )

if gunAngle2 > 0 then the gun is moving anti clockwise so the delta must be positive
deltaAngle =  ( (tankFlakAngle + 180 ) + (180 - gunAngle2 ) )

Knowing the deltaAngle, now we can tell the gun to move to close the gap formed by this deltaAngle.

The demo


That's it for today, as always you can check the latest demo by going to http://tankbattlefield.eu.pn

Sunday, January 5, 2014

Tank Battle Field, HTML5 Game #5: Collision detection

Collision detection is an important feature in every game. Because of it you know where you can go, if you hit the enemy, etc... There are plenty of theories regarding this subject (you can check this blog for a good introduction) but they are beyond the scope of this article. What interests us in this post is how the collision detection is applied in the Tank Battle Field game.

In Tank Battle Field game, collision detection mostly applies in two cases:
- The primary tank (player tank) runs into a building, enemy or any other object on the ground
- Shells hitting the primary tank, the enemy units, building or other objects.

Main tank collision detection

As of this version of the game, the only moving vehicle on the field is the primary tank. Since it can move forward and backward, turns left and right, it is essential to be sure that any of these movements do not hit a solid object that denies the tank from changing position.
(it won't be realistic enough, nor interesting, to see the tank driving through buildings for instance).

So to be able to verify whether the tank hit something or not, we keep an eye on eight predefined points of the tank, and we check whether in the new position of the tank any of these points hit a solid object.
These points (called border points) are defined relatively to the center of the tank. For example the upper left corner has the coordinates x = - TankWidth/2 and y = TankLength/2 and so on for the other points.
Each time the tank moves or turns (rotates) the border points are computed in absolute coordinates taking into consideration the new position (including the rotation angle) that the tank it trying to move to.


Lets take an example. Consider that Xc, Yc are the coordinates of the center of the tank and Xul, Yul those of the upper left corner relatively to the tank center. When the tank moves the coordinates of the upper left corner in the absolute system will be:
AbsXul = Xc + (Xul * cosθ - Yul * sinθ)
AbsYul = Yc - (Xul * sinθ + Yul * cosθ)
where θ is the rotation angle of the tank

The above formula is a combination of a translation and a rotation transformations. (You can read more about transformations in the following wikipedia article "transformation matrix" )

The above has determined the border points of the tank. What remains is to check whether any of these points collided into a building. In other words, if any of these points came into intersection with building (or any other solid object) borders then we consider that a collision took place.

This suggests that we know the borders of these solid objects. In case of sprites, we know that they are always contained in a rectangle. However the rectangle might not accurately model the content of the sprite which might be of any shape. So the containing rectangle is not useful for a large number of cases which does not make it appealing. You can easily verify in the image below that the containing rectangle can quickly make the game unplayable.


In the image above, the tank can't move inside the containing rectangle which make the game unrealistic.
To solve this problem, we use polygons to model non-rectangular shapes such as this castle.
However this comes at a price of CPU power in order to try finding if any border point of the tank has crossed the polygon.
You may like to refer to this wikipedia article to know more about detection of a point inside a polygon.



The good news, is that not every thing requires a polygon, but there are cases where several rectangles might do the job. Telling that a point is inside a rectangle is by far faster than telling if it is inside a polygon. So, it is perfectly reasonable to model some objects using a series of rectangle as depicted in the image below.


In the above image, we modeled the house with two rectangles leaving the outdoors free for the tank to drive through.

Shells hit point

What makes tank games appealing is the ability to fire shells on targets. Determining how and where a shell hit a target is also a collision detection problem.
Mathematically this problem can be reduced to finding the intersection point between two lines.
The scenario where the tank fires on the castle,
can be reduced to the following intersection of two lines.




Here we consider that there are two lines defined as
Line A: y = a*x + b
Line B: y = c*x + d
Finding the hit point comes to find the intersection point of Line A with Line B. We define this intersection point by xi, yi. The solution becomes
xi = (b - d) / (c - a)
It follows that yi = a*xi + b

Certainly enough if the slopes represented by a and c are equal, this means the two lines are parallel and won't be intersecting thus the xi won't be determined since the denominator will be zero.

Mathematically this is valid solution. However this is not necessarily true in the game, because in maths the lines are infinite, so in case they are not parallel they will eventually intersect at some point. But in the game the shell has a limited range and the target's borders are also constrained in length. To be valid the intersection point must necessarily be with the valid range of each line.
Back to our previous example, if we say that the Line A stretches between point A1 and point A2 and Line B goes from point B1 to point B2. So in order for the intersection point given by (xi, yi) to be valid, the following relations should be true.
xi >= A1.x && xi <= A2.x assuming that A1.x <= A2.x
and
xi >= B1.x && xi <= B2.x assuming that B1.x <= B2.x

if the above relations are not true we might be in situation like in the image below, where the intersections happens outside the valid ranges.



Special case

We might think that we are done with the hit point. But still this not true yet!
Actually the equations handling the shell trajectory is based on time and velocity.
They have the following form:
x = Vx * t + x0
y = Vy * t + y0
where Vx and Vy are the components of the shell velocity along the x-axis and the y-axis while the x0, y0 is the point of origin (in other terms the tank).

The coordinates of the shell location are updated on each loop turn. This represents a problem since the update occurs every laps of time. So the graphical representation of the shell is discrete and not continuous.
The image below shows in grey the theoretical (continuous) trajectory of the shell, and in red dots the reel  location of the shell over the time.



As you can see there is no way to verify that the shell hit the house by following the red dots alone. Actually the red dot never happen to appear inside the house borders.
To solve this issue, after each update of the shell position we check for intersection between the line formed by the starting point of the shell (position of the tank when it fired) and the latest shell position and the borders of the house.
It appears that for the first two shell positions there is no intersection. But on the third time we notice two intersections represented by the red crosses (X).
Naturally we chose the intersection that is closer to the tank as the hit point or the point of impact then we trigger the explosion sequence. Eventually we ignore the second intersection point and we don't show the third shell position on the screen.

Performance issue

So far the game does not contain too many objects nor requires too many computations that might affect performance. However, to do the things "right", we do take performance topic into consideration.
When detecting collision, we have to check the tank or shell position against every other solid object on the map. So in order to avoid using costly line intersections or polygons checks we first start by simply finding out what containing  rectangles (surrounding objects) intersect with the rectangle formed by the tank position as first corner and the shell position as diagonally opposed corner (see image below). This way using cheap technique we can gather all solid objects that potentially lie in the way of the shell. Then we start testing using the more costly line intersection method on this reduced set of objects.



On the other hand, when seeking to find if the tank is about to collide with other solid objects, we start by checking for candidate objects by verifying if the tank crossed their containing rectangle, then we run a more accurate test using the point-in-polygon technique to find out if the tank really collided with one of them.


The demo

That's it for today, as always you can check the latest demo by going to http://tankbattlefield.eu.pn

D3GGQP7C7BF3

Friday, December 27, 2013

Tank Battle Field HTML5 Game #4: The Object Model

Today's post is about the structure of Tank Battle Field game. Its object model.
Object model defines how objects are related to each other and determines the interactions with each other.

Tank Battle Field object model has two aspects. The containment model and the hierarchical one.

The containment model
As you can see in the picture below the objects are contained in a logical manner.



At the top there is the "Game" object which constitutes a global container which includes all other objects that are intrinsic to the game. It also constitutes a single entry point.
The "Game" object contains the following important objects:


  1.  "Model" object: defines the game logic and abstracts away logic from graphical representation. In turn it cotains
    1. "Field" object, that represents the terrain or the theater in which the game takes place. The aim of this "Field" is to set the dimensions, background and relief.
    2. "PrimaryTank" is the main tank of the game. The one controlled by the player.
    3. "Buildings" is a object containing a collection of buildings. It has also the role to render them and make necessary calculations related to their positioning on the screen.
    4. "Enemies" is the collection that contains the enemy units. This collection plays a role in coordinating AI enemy units movements.
  2. "Graphics" object contains all what is necessary for rendering the game on the screen. Among others it contains:
    1. "context" object which is a direct reference to the graphical device context provided by HTML5.
    2. "layers" array that contains the layers which will be displayed on the screen using the z-index order.  
    3. "layer_ground" object has the lowest z-index order and thus it is displayed the first. It contains the background, and the field details as well as borders.
    4. "layer_objects" object is the layer that contains static objects such as buildings. It is displayed second, over the "layer_ground".
    5. "layer_units" object is responsible of rendering ground military units including friend and foes.
    6. "layer_projectiles" object will contain transient objects such as shells, missiles, rockets as well as explosions. These objects are added to this layer at creation time then are removed when they expire.
    7. "layer_high" object will contain all flying machines like airplanes.
    8. "layer_widgets" object has the highest z-index order and is drawn last. It is responsible of displaying widgets on the screen such as a mini-map, primary tank status, notification messages or chats etc..
  3. "Settings" object is where the game is configured, in terms of control keys, navigation mode etc. For the moment it contains "Navigation" and "Keys" objects for setting the navigation mode and the control keys.
  4. "Res" objects contains resources needed by the game. Such as images, sounds etc...

The hierarchical model
Although that Javascript is not Object Oriented language, the Tank Battle Field game uses commonly known tricks to simulate class hierarchy and inheritance. The inheritance logic is hand crafted and does not use any library in order to keep it simple and minimal.
The concept of class does not exist in Javascript, everything is object but since hierarchy of classes is simulated in this section, the terms "class" and "object" refer to the same entity.



All objects in the game inherit from one super class which is ModelObjectClass. This gives the ability to get and set data and have an ID.

Objects that have dimensions, locations and rotation settings are of type ModelLocatableObjectClass. Note that the setX()/getX(), setY() /getY() coordinates functions represent the center of the object while the getTop(), getLeft() represent the top left corner of the object. It is also important to note that dimensions terms are not exactly the same as one might expect. Actually the width is the horizontal distance following the x-axis, the length is the vertical distance following the y-axis (which normally used to be called height), and height is the elevation from the ground following the z-axis. It is true that this is a 2D game, however the height (z-axis) will play a role.

Moving objects inherit from ModelMoveableObjectClass and have linear and angular speed, as well as acceleration.

VehicleClass contains commands to move forward, backward and to turn left and rights. It has also an import set of functions that determines how the vehicle will behave and how it is going to be rendered on the screen.

TankClass inherits from the VehicleClass and contains a turret object.
Other objects such as BuildingClass and Shell make use of this hierarchy of classes.

Initialization and data
Objects in the class hierarchy are instantiated using the new keyword, however the initialization does not take place inside the constructor, but by explicitly calling init() method and passing to it an arbitrary data object that each class might be expecting.
The reason for this approach is purely technical. Actually when instantiating an object using new keyword, the whole hierarchy of objects is instantiated and linked together via the __proto__ property.
So after the instantiation and the linking are done then it is possible to initialize using the init() method.

The data object given as parameter in the call to init() method, is used as momento design pattern, in the sens that it is used to save the state of the object so it can be saved or transferred via network in a multiplayer game.

Game loop: updates and rendering
As it is known by now, the game loop is a never ending cycle of tasks that is executed sequentially.
The loop reads the input of the user, calls updateModel() function on every object in the graphic layers. This method updates the internal state of the object, such as location, direction, health, etc...then the loop calls renderGraphics() on each object to draw itself on the screen, and finally updateModelAfterRendering() is called to update the state after the rendering phase. One example of the usefulness of the updateModelAfterRendering() method, is to reset the "fire" flag of the tank which was set by the updateModel() method and drawn by the renderGraphics() method.

Demo
That's it for now, as usual you can check the demo by going to http://tankbattlefield.eu.pn


Tuesday, December 24, 2013

HTML5 Game #3: Tank Battle Field, new approach

Welcome to this new episode of HTML5 Tank Battle Field game. In this article a drastic restructuring of the game has been put in place.

In order to make the game more fit to new features and scenarios, some major concepts have been implemented:

1- World concept. The world concept is a simulation of how the tank battle occur in the real world. This is called the Model in the code.
The importance of the Model is that it abstracts away the real stuff from the graphic technicalities. So in theory, once the Model is ready, porting the game to different devices and/or different screen resolutions is a matter of adapting the graphics only.
It also ensures that the game play can vary by only touching the graphics without the need of rewriting the whole game.
A third important reason why the Model is crucial, is that the game could be transformed into multi-player one. So it will present a same world (Model) for all the players without caring about their graphic settings.

2- The Graphics, is what appears on the screen in front of the user. The graphic elements drawn on the screen will reflect what happens in the Model. However that does not mean that the mapping is one-to-one. Some transformations are required in order to render those elements appropriately.
Ideally the graphics coding structure will evolve separately from the Model, and the two can be linked by the "bridge design pattern". This will add great amount of flexibility.

3- Navigation view mode: As said in the first and second article, the game is about "first person vehicle" (as an analogy to the "first person shooter"). So despite the fact that this is a plain top view 2D game, the graphics are rendered to give the impression that the user is inside the tank. Which means that the tank is always fixed at the center of the screen and the objects move around it as it (the tank) moves.

However to keep things open for future navigation modes, this mode is used as part of the settings. In future releases, the user might be able to switch between navigation modes.

4- The game loop of course. It was not present in the previous article for simplicity purpose. However since the game is evolving, the game loop existence is extremely important. It has the role to keep the game together: It reads user input, updates the Model and renders the graphics and sounds.





The Model
As said the Model represents a simulation of the real world of the game which abstracts it from the graphics technical issues and constitutes a unique reference of what is happening in side the game for all players in a multi-player game.

As for this version of the game, the coordinates system of the Model is similar to the screen one, i.e. the origin (0,0) is at the top left of the screen, and the positive x-axis goes to the right, while the positive y-axis goes down.
The dimensions of the battle field, known as Field in the code, is 5000m x 5000m.
The angular system, is similar to the classic trigonometric one. So positive x-axis goes to the right, positive y-axis goes up and a positive angle is the one that goes from right to left.

There is no particular reason I have chosen this configuration, you can choose the one you want as long as you make the appropriate adaptation in your code.



The Graphics
The Graphics is to represent the objects of the Model on screen, by taking into consideration the screen resolution, the navigation settings, etc.
It is composed among other things of five layers:
The ground layer: To draw the background
The objects layer: Usually used to draw static objects such as buildings
The units layer: to draw the vehicles and big mobile objects
The projectiles layer: to draw the shells, rockets, missiles fired by the units
The flying layer: this is to draw any future flying object such as planes etc..
The widgets layer: this layer is for displaying extra features in the game such as mini-map, text messages etc..
As you can notice the existence of the layers determines the depth or z-index of the graphic elemnts. The rendering starts with the ground layer up to the widgets layer.

It is important to note that every object of the Model must be in one of these layers in order to appear on the screen.



The Navigation Mode
As already stated, the navigation mode in this version is about first person vehicle, or FixedCenter as referred to in the code. So the tank is fixed at the center of the screen, only its turret can turn. When trying to turn the chassis, the objects around the tank turns in the opposite direction to give the user the feeling that tank has turned.

So if the tank turns  towards a building on its left, what the user sees on the screen is that the building is turning to the right to come in front of the tank. When the tank moves forward, or backwards, the building approaches or moves away.

It is important to note that all of this is only graphical, in the Model only the tank moves foward and backwards or turns left or right. But when representing those movements on the screen, the impression is given that the tank is fixed and the buildings are moving.

Technically, when the tank moves towards a building the distance between the tank and the building is measured and on the screen the building is drawn closer to the center.

When the tank turns, the building appears to be turning in the opposite direction, in order to come in front of the tank. However there is a little catch in here. Because our tank is always positioned upwards, in practice  it is making 90° with the x-axis (according to our angular system). So when the tank moves left to position 120°, the building should rotate by 30° (120° - 90°) to the right (around the center of course).





Collision detection
For simplicity reason in this version the collision of the tank with any building is reduced to detecting whether the center point of the tank crosses the boundary of any building. Thus you will easily see half of the tank mounting the building.

Note that collision is detected in the Model and not graphically. So once the Model detects that the tank collided it cancel all movements towards the collision point, and this way the graphics on the screen keeps drawing the tank at the same position, giving the impression that it has stopped moving.



Demo
For the demo please go to the following http://tankbattlefield.eu.pn/tank6/index.html





Saturday, December 14, 2013

HTML5 Game #2 : The Tank Battle


In my previous article I have shown how to use a vehicle as "1st person navigator" in 2D.
After a second thought I found that there are more things to tell regarding HTML5game dev. So I decided to continue the series hoping that at the end of which I can come up with a minimum playable game. It would be a good practice for me and others.
During this series if you have remarks, or suggestions feel free to drop a line in the comments.

First I will redefine the game story.
Starting this article the game will be about a tank, navigating and and shooting  on a map. The enemies are not very specific for the time being, they can be computer animated ones or other user(s) in a multiplayer game. Future articles will help determining them, since it is all about what can be achieved in a reasonable amount of time and efforts.

So in this article I will do the following:
1- add a tank with a moving turret
2- control the tank via keyboard instead of push buttons for better playability. 
3- add some quick objects to the map
4- some technical stuff

As a matter of fact this article is more about polishing than anything else. It doesn't require any head scratching.
However before I start, I would like to point that the code has suffered a bit and needs some refactoring. I hope to be able to do this in the next articles.

1. The tank
In order to simulate a tank I have used a  sprite PNG format with a transparent background. See image
The chassis and the turret are separate since the turret should be able to turn 360°.
Placing the chassis and the turret on the screen is straight forward. First you have to load the image then you have to draw each part alone.




It is worth noting that the tank is positioned upwards which is opposed to the relative axis system initial direction. This means that the turret along with its gun as the trajectory of the shell evolve in the negative values of the y-axis.

As I said earlier the chassis is fixed, but the turret can rotate. The good news is that since we are in a 2D and Top view, all objects are symmetric thus we don't need to draw every shape that the turret takes as it rotates. So delegating the job to the graphic engine is sufficient.

Tank forward, reverse, rotation speed as well as the rotation speed of the turret are controlled by the following variables in the code.
var ctDefaultForward = 5;
var ctDefaultBackward = 3;
var ctDefautltTurning = 2;
var ctDefautltTurretTurning = 2;

Firing is also constrained by the loading time. Meaning that after every shot there is a certain amount of time to wait until the next shell is loaded. This time is controlled by the variable.
var ctDefaultLoadTime = 20;

Explosion of the shell is a sequence of sprites that display one after the other.
Each of these sprites is contained in a  64x64 rectangle, so it suffice to know the number of the sequence in order to deduce the sprite position on the sprite sheet.
The variable explosionSeq keeps track of the explosion sequence. A value of -1 means there is no explosion.

Upon firing the gun recoils and this is reflected by move back of the turret. The variable recoil tells if there has been a recoiling and recoilDist controls the amount.

2. Tank control

Unlike in the previous article, the tank is now controlled via keyboard instead of push buttons. This is will allow a greater maneuverability.
The logic of the keyboard controls lies in the existence of a keyDown array. This array will take as index the key code and as value true in case the key is down and false in case the key is up. This keyDown array is essential when several keys are pressed down.
The variables that set the key controls are listed in here:
var ctKeyMoveUp = 69; /*E*/
var ctKeyMoveDown = 68; /*D*/
var ctKeyTurnLeft = 83; /*S*/
var ctKeyTurnRight = 70; /*F*/
var ctKeyTurretLeft = 37; /*left arrow*/
var ctKeyTurretRight = 39; /*right arrow*/
var ctKeyFire = 16; /*shift*/

In order to avoid the differences between QWERTY and AZERTY keyboards, I have set the control keys to match both layouts, whereas E = Forward, D = Backwards, S = TurnLeft, F = TurnRight, Left Arrow = Turret Left, Right Arrow = Turret Right, Shift = Fire.

3. Buildings

For the time being other objects on the map, particularly buildings are no more than rectangles of different dimensions and colors spread randomly.
As of this article, there are no collisions detection implemented in the source code.


4. Technical stuff

The most important technical issues to mentions are:
1- The game loop that maintain the game running. It is called every 100ms and consists of function readInput() for reading and analyzing the key strokes, the update() function that make some calculations regarding the positioning of objects, the drawAll() function that draws all the objects on the screen and finally the postUpdate() method that perform computations for the after drawing phase (useful in case of shell firing).
2- Another important issue, the rotating turret. The turret rotates within a relative axis system. This is the easiest way to do it, since the graphics api is able to rotate the turret sprite without deteriorating its quality.





5. The demo

Here is a live demo of this new version. Give it a try!




Your browser does not support the HTML5 canvas tag.





Keys: E = Forward, D = Backward, S = Left, F = Right, Left Arrow = Turret Left, Right Arrow = Right Turret, Shift = Fire


Please find below the source code of this version:
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas2" width="600" height="480" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<br/>
<span id="loadingInfo"></span><br/>
<span id="info">Keys: E = Forward, D = Backward, S = Left, F = Right, Left Arrow = Turret Left, Right Arrow = Right Turret, Up Arrow = Fire</span><br/>

<script>
var timeIntervalID = -1;
var canvas=document.getElementById("myCanvas2");
var ctx=canvas.getContext("2d");
var info=document.getElementById("info");
var loadingInfo=document.getElementById("loadingInfo");
var imgSprites = new Image();
imgSprites.src="file:///C:/projects/tank/t2_1.png";
var imgExplo = new Image();
imgExplo.src="file:///C:/projects/tank/explo2.png";
var keyDown = [];
var ctDefaultLoadTime = 20;
var ctDefaultForward = 5;
var ctDefaultBackward = 3;
var ctDefautltTurning = 2;
var ctDefautltTurretTurning = 2;
var ctTotalExplosionSeq = 5;
var ctShellMaxRange = 300;

var ctKeyMoveUp = 69; /*E*/
var ctKeyMoveDown = 68; /*D*/
var ctKeyTurnLeft = 83; /*S*/
var ctKeyTurnRight = 70; /*F*/
var ctKeyTurretLeft = 37; /*left arrow*/
var ctKeyTurretRight = 39; /*right arrow*/
var ctKeyFire = 16; /*shift*/

// coordinates of the center of the map
// they are also those of the vehicle since it does not physically move on the map.
var canvasWidth = parseInt(canvas.width);
var canvasHeight = parseInt(canvas.height);
var centerX = canvasWidth/2;
var centerY = canvasHeight/2;

// the rotation angle by which the relative system will rotate
var rotAngle = 0;

// the rotation angle of the turret
var rotTurret = 0;

// movements
var deltaAngle = 0;
var deltaMove = 0;
var deltaTurret = 0;
var explosionSeq = -1;

// tank dimensions
var tankWidth = 40;
var tankHeight = 86;
var tankHalfWidth = 20;
var tankHalfHeight = 43;
var realTankPoints = [];



// this is the PI/2 which is 90 in radians
var PIover2 = Math.PI/2;

// coordinates of the objects in the relative system
var buildings = createBuildings();

var shellY = 0;
var fire = false;
var recoil = false;
var recoilDist = 2;
var loadingTime = 0;

// draw everything at start with no translation nor rotation
//drawAll(0, 0);
imgSprites.addEventListener("load", function(){
drawAll(0,0, 0);
});

window.addEventListener("keydown", function(ev){

if(ev.keyCode == ctKeyMoveUp || ev.keyCode == ctKeyTurnLeft|| ev.keyCode == ctKeyMoveDown|| ev.keyCode == ctKeyTurnRight || ev.keyCode == ctKeyTurretLeft || ev.keyCode == ctKeyTurretRight
|| ev.keyCode == ctKeyFire){
keyDown[ev.keyCode] = true;
}
});

window.addEventListener("keyup", function(ev){
if(ev.keyCode == ctKeyMoveUp || ev.keyCode == ctKeyTurnLeft || ev.keyCode == ctKeyMoveDown || ev.keyCode == ctKeyTurnRight || ev.keyCode == ctKeyTurretLeft || ev.keyCode == ctKeyTurretRight){
keyDown[ev.keyCode] = false;
}
});



function readInput(){
deltaAngle = 0;
deltaMove = 0;
deltaTurret = 0;

if(keyDown[ctKeyMoveUp]){
deltaMove = -ctDefaultForward;
}
if(keyDown[ctKeyMoveDown]){
deltaMove = ctDefaultBackward;
}
if(keyDown[ctKeyTurnLeft]){
deltaAngle = ctDefautltTurning;
}
if(keyDown[ctKeyTurnRight]){
deltaAngle = -ctDefautltTurning;
}
if(keyDown[ctKeyTurretLeft]){
deltaTurret = -ctDefautltTurretTurning;
}
if(keyDown[ctKeyTurretRight]){
deltaTurret = ctDefautltTurretTurning;
}

if(loadingTime > 0){
keyDown[ctKeyFire] = false;
}
if(keyDown[ctKeyFire]){
fire = true;
recoil = true;
keyDown[ctKeyFire] = false;
loadingTime = ctDefaultLoadTime;
}
}
function update(){

rotAngle = (rotAngle + deltaAngle) % 360;
}

function postUpdate(){

// loading time
if(loadingTime > 0){
loadingTime --;
loadingTime = Math.max(0, loadingTime);
loadingInfo.innerHTML= "Reloading: " + loadingTime;
}else{
loadingInfo.innerHTML = "Loaded";
}
}

// the drawAll function draws all the map and its objects
// parameters:

function drawAll(){
// compute the total rotation angle of the vehicle in degrees and radians
var rad = rotAngle*Math.PI/180;

// compute the forward vector coordinates in the relative system
var rotdy = Math.round(deltaMove * Math.sin(PIover2-rad));
var rotdx = Math.round(deltaMove * Math.cos(PIover2 - rad));

// clear the map
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);

// save the context in order to start the transformation
ctx.save();

// move the coordinates system to the center (this is the relative system)
ctx.translate(centerX, centerY);

// apply the rotation to the relative system
ctx.rotate(rad);


for(var i = 0; i < buildings.length; i++){
// make the necessary translation in the relative system
buildings[i].x += -rotdx;
buildings[i].y += -rotdy;

// draw the objects on the map in the relative system
ctx.fillStyle = buildings[i].c;
ctx.fillRect(buildings[i].x, buildings[i].y ,buildings[i].w, buildings[i].h);
if(buildings[i].hilit){
ctx.strokeStyle="#000000";
ctx.lineWidth=4;
ctx.strokeRect(buildings[i].x, buildings[i].y ,buildings[i].w, buildings[i].h);
}
}

// restore the previous context (return to the fixed system)
ctx.restore();

// draw the vehicle at the center of the map using the fixed system
ctx.drawImage(imgSprites, 1, 0, tankWidth, tankHeight, centerX - tankHalfWidth, centerY - tankHalfHeight, tankWidth, tankHeight);


// draw turrent
rotTurret = (rotTurret + deltaTurret) % 360;
var radDeltaTurret = rotTurret*Math.PI/180;
ctx.save();
ctx.translate(centerX, centerY);

// apply the rotation to the relative system
ctx.rotate(radDeltaTurret);
ctx.drawImage(imgSprites, 75, 0, 27, 99, - 15, - 80 + (recoil ? recoilDist : 0), 28, 99);

//fire
var fired = fire || false;
if(fired || shellY != 0){
if(shellY == 0){
shellY = -107;
}
recoil = false;
ctx.beginPath();
ctx.fillStyle = '#ff0000';
ctx.strokeStyle = '#ff0000';
ctx.arc(0,shellY,2,0,2*Math.PI);
ctx.fill();
ctx.stroke();

shellY += -50;

if(Math.abs(shellY) > ctShellMaxRange){
fire = false;
explosionSeq = 0;
explosionY = shellY;
shellY = 0;
}
}

// explosion
if(explosionSeq > -1){
var eX = (explosionSeq * 64) % 320;
var eY = 0;//(explosionSeq * 64) % 320;
ctx.drawImage(imgExplo, eX, eY, 64, 64, - 32, explosionY - 32, 64, 64);
explosionSeq++;
if(explosionSeq >= ctTotalExplosionSeq){
explosionSeq = -1;
}
}
ctx.restore();
}

timeIntervalID = setInterval(function(){gameLoop();}, 100);
function gameLoop(){
readInput();
update();
drawAll();
postUpdate();
}

function stopGame(){
clearInterval(timeIntervalID);
timeIntervalID = -1;
}

function createBuildings(){
var temp = [];
for(var i = 0; i < 20; i++){
var tx = getRandomInt(-200, 200);
var ty = getRandomInt(-200, 200);
var tw = getRandomInt(50, 150);
var th = getRandomInt(50, 150);
temp[i] = {
x: tx,
y:ty,
w:tw,
h:th,
c:'#'+Math.floor(Math.random()*16777215).toString(16),
}
}
return temp;
}

function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

</script>

</body>
</html>

Friday, December 6, 2013

HTML5 Game #1: First person vehicle in 2D


I am by no mean a game developer, however recently I have been tinkering with HTML5 2D graphics.I encountered a challenging problem when writing a piece of code for simulating a vehicle navigating through out a map.

The main idea is that the vehicle is fixed at the center of the map and the map objects move and rotate around it as the vehicle moves around.
Actually the concept is quite simple. When a person drives the vehicle he has the impression that objects (trees, houses, etc..) are moving.

Logically speaking the problem is quite easy, however the implementation is rather tricky. The issue stems from the fact that raw 2D engines APIs are not very flexible.PS the concept is the same disregarding the 2D technology you are using (JavaScript, c#, Java...)

So let's now put some hypothesis before we start.We have a fixed vehicle positioned at the center of the map having only one single direction which is upward. The vehicle can move forward, backward, and it can also rotate. But since the vehicle is always fixed in position and direction, it is the objects around it that move and rotate.

To solve this problem we will consider two axis systems.
1- the first one we call "fixed system", and is defined by the origin at the upper left corner with x-axis goes to the left, and the y-axis goes down.
2- the second axis system, which we call "relative system", has its origin at the center of the map and the axis can rotate in any direction

The fixed system is used to draw the fixed vehicle, while the relative system is used to draw the moving objects around the vehicle.
When the vehicle turns what actually happens is that the relative axis system that turns.
As you can see in the image below, the vehicle is still fixed but the circle that was on the left side came into the vehicle vision field.
Now when the vehicle move backward or forward the circle gets closer or farther.

So now what is done practically is that you tell the graphics engine to draw the vehicle at the center of the map (or canvas), then you translate the axis system to the center, rotate them then draw the circle. All of this is done via API calls. No tough calculations are done on your side.

Here is the 1st example:



Your browser does not support the HTML5 canvas tag.


In this example you can press the buttons forward and backward to move and the button left and right to rotate. You might be quite happy with that, but there is a glitch!
Try rotate the vehicle 90degrees then go forward!
Surprise! You notice that the vehicle seems to go horizontally rather than vertically. For sure you don't want your vehicle to go on the left or right when you meant to go forward.

So what happened behind the scenes?
It all lies in the relative axis system that has been rotated. The picture below depicts the concept.
Moving forward as seen in the picture happens in the relative axis system, which has been rotated and as you can see its axis directions have changed. So a simple translation of objects won't  give the correct result because the translation occur in the relative system and not the fixed one.
In other words, if you rotate the relative axis 90 degrees the x-axis becomes vertical and the y-axis becomes horizontal,  so moving along the y-axis (which is the forward direction) does not go vertically but horizontally which gives the impression that the vehicle is going to its side!

To solve this problem we need to understand that moving vertically in the fixed system means that we need to adjust the vertical vector to the relative system. This can be done by taking the rotation that occurred to the system into consideration.
Moving dy pixels in the fixed system means that in the relative system we have moved as follows
rdx = dy * cos(Π/2 - Θ); along the relative x-axis
rdy = dy * sin(Π/2 - Θ); along the relative y-axis
where Θ is the rotation angle of the relative system.
Now having the coordinates in the relative system we can translate any object in the that system via a simple operation:
Xobj += - rdx;
Yobj += - dy;

Lets see how this is concertized in reality


Your browser does not support the HTML5 canvas tag.




Hope this was useful to anyone who is facing a similar problem.
The finish, I have included the code below.


<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas2" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<br/>
<input type="button" id="btnForward2" value="forward" onclick="drawAll(0, -5)"/>
<input type="button" id="btnBack2" value="backward" onclick="drawAll(0, 5)"/>
<br/>
<input type="button" id="btnLeft2" value="left" onclick="drawAll(5, 0)"/>
<input type="button" id="btnRight2" value="right" onclick="drawAll(-5, 0)"/>

<br/>

<script>
var canvas=document.getElementById("myCanvas2");
var ctx=canvas.getContext("2d");

// the rotation angle by which the relative system will rotate
var rotAngle = 0;

// coordinates of the center of the map
// they are also those of the vehicle since it does not physically move on the map.
var centerX = 150;
var centerY = 75;

// this is the PI/2 which is 90 in radians
var PIover2 = Math.PI/2;

// coordinates of the objects in the relative system
var obj1X = 100 - centerX ;
var obj1Y = 5 - centerY ;
var obj2X = 135 - centerX ;
var obj2Y = 60- centerY ;

// draw everything at start with no translation nor rotation
drawAll(0, 0);

// the drawAll function draws all the map and its objects
// parameters:
// deltaAngle = is the angle by which the vehicle has turned compared to the previous time
// deltaForward = is the number of pixels the vehicle has moved forward since the last time
function drawAll(deltaAngle,deltaForward){
// compute the total rotation angle of the vehicle in degrees and radians
rotAngle = (rotAngle + deltaAngle) % 360;
var rad = rotAngle*Math.PI/180;

// compute the forward vector coordinates in the relative system
var rotdy = Math.round(deltaForward * Math.sin(PIover2-rad));
var rotdx = Math.round(deltaForward * Math.cos(PIover2 - rad));

// clear the map
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, 300, 150);


// save the context in order to start the transformation
ctx.save();

// move the coordinates system to the center (this is the relative system)
ctx.translate(centerX, centerY);

// apply the rotation to the relative system
ctx.rotate(rad);

// make the necessary translation in the relative system
obj1X += -rotdx;
obj1Y += -rotdy;
obj2X += -rotdx;
obj2Y += -rotdy;

// draw the objects on the map in the relative system
ctx.fillStyle = '#0000ff';
ctx.fillRect(obj1X, obj1Y ,100,10);
ctx.fillRect(obj2X , obj2Y ,30,30);

// restore the previous context (return to the fixed system)
ctx.restore();

// draw the vehicle at the center of the map using the fixed system
ctx.fillStyle = '#ff0000';
ctx.fillRect(centerX - 5, centerY - 5,10, 10);
ctx.fillRect(centerX - 2, centerY - 10,4, 10);

}

</script>

</body>
</html>