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>

No comments:

Post a Comment