Saturday, March 15, 2014

Html5 Game Programming Tutorial for Beginners Part 2 - Keyboard and Bullets

In this part, we will load an image object, control its movement using keyboard, and then we will arm our hero with a loaded gun.

So, time to bring our hero in picture. I will load the image saved in my local folder.

Load Image



[code language="javascript"]
//create init function to initialize image loading
var img = new Image();
var imgLoaded = false;
img.onload = function() {
imgLoaded=true;
}
img.src = 'images/hero.png';
[/code]

After loading the image, we need to draw it on canvas

[code language="javascript"]context.drawImage(img,x,y);[/code]

Create Hero Object


To keep code sane, we will create hero object that will hold all properties and methods of hero. If you are new to Objects in javascript refer Working with objects

[code language="javascript"]
var hero = {
x: 50,
y: 50,
width:img.width,
height:img.height,
draw: function() {
if(imgLoaded){
context.drawImage(img,this.x,this.y);
}
}
};

[/code]



To draw hero, we will call its draw function like hero.draw().

The structure of our program is gonna be same, as discussed in  part1. We will code same main loop

[code language="javascript"]
//main loop
function loop(){
requestAnimFrame(loop); //refresh screen for animation
update(); //update coordinates for each new draw
clear(); // clear canvas to give animation effect
render(); //draw the object
}
[/code]

We will modify our update and render functions accordingly.

Handling Keyboard Input 


In update function, we will handle keyboard inputs. I will use simple keyboard input handler defined here input.js.

To check for LEFT key event we will simply write

[code language="javascript"]if(input.isDown('LEFT')){}[/code]

Based on input key, update the position of hero object

[code language="javascript"]
function handleInput(){

if(input.isDown('LEFT')) {
hero.x -= 3;
}
if(input.isDown('RIGHT')) {
hero.x += 3;
}
if(input.isDown('UP')) {
hero.y -= 3;
}
if(input.isDown('DOWN')) {
hero.y += 3;
}

}
[/code]

Updating position with constants is not recommended because its dependent on frame rate.

Lets say frame rate 17ms, then in each frame object will move 3 pixels, according to our code. But, what if frame rate is 30ms(just assume!) then also our object will move only 3 pixels. So, its advised change object position based on time elapsed between call to update function, independent of frame rate.

x += 3 * (time elapsed in sec)

Now, object moves at rate of 3 pixels/sec.

Read more about it here.

okay, our hero is waiting to go on field. Lets bring him in

Full code



[code language="javascript"]<!DOCTYPE HTML>
<html>
<head>
<script src="scripts/input.js"></script>
<style>
body {
margin: 0px;
padding: 0px;
}
canvas {
border:1px solid #ccc;
}

</style>
</head>
<body>
<script>
//global variables
//create canvas
var canvas = document.createElement('canvas');
canvas.id = 'myCanvas';
canvas.width ='500';
canvas.height ='300';
document.body.appendChild(canvas);

//get 2d context
var context = canvas.getContext("2d");

//create init function to initialize image loading
var img = new Image();
var imgLoaded = false;
img.onload = function() {
imgLoaded=true;
}
img.src = 'images/hero.png';

//create hero object
var hero = {
x: 50,
y: 50,
width:img.width,
height:img.height,
draw: function() {
if(imgLoaded){
context.drawImage(img,this.x,this.y);
}
}
};

//main loop
function loop(){
requestAnimFrame(loop); //refresh screen for animation
update(); //update coordinates for each new draw
clear(); // clear canvas to give animation effect
render(); //draw the object
}

function update(){
handleInput();

}

function handleInput(){

if(input.isDown('LEFT')) {
hero.x -= 3;
}
if(input.isDown('RIGHT')) {
hero.x += 3;
}
if(input.isDown('UP')) {
hero.y -= 3;
}
if(input.isDown('DOWN')) {
hero.y += 3;
}

}

//draw function
function render() {
hero.draw();
}

// http://paulirish.com/2011/requestanimationframe-for-smart-animating
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();

function clear(){
context.clearRect(0,0,context.canvas.width,context.canvas.height);
}

//call onload
loop();

// ]]></script>

[/code]

[caption id="attachment_741" align="aligncenter" width="420"]Screen Shot 2014-03-15 at 9.10.38 am Output of program[/caption]

Create Bullets


I know our hero does not looks like a hero. Well, give him a gun and he will.

When user presses space bar key, we want hero to shoot. Each bullet is an entity, similar to hero object, with its own properties(current position on canvas, colour, status) and methods(draw and update methods).

Create a bullet with initial coordinates same as hero's position.
Update the coordinates of bullet on each refresh.
Draw all bullets on canvas on each refresh.

[code language="javascript"]
//keeps number of fireshots in check
var lastFire = Date.now();

var Bullets =[]

//set properties of bullet object, argument - Bullet object
function Bullet(B) {
B.vx = B.speed;
B.vy = 0;
B.radius = 5;
B.color = "#0fa";
//draw circle
B.draw = function() {
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
context.fillStyle=this.color;
context.fill();
};
B.update = function() {
B.x += B.vx;
B.y += B.vy;
}
return B;

}
[/code]

We can create bullet objects using

[code language="javascript"]
var bullet = Bullet({speed:1,x:hero.x ,y:hero.y});
[/code]

When user presses space bar key, create Bullet objects and add it to Bullets array.

[code language="javascript"] if(input.isDown('SPACE') && Date.now() - lastFire > 250){
Bullets.push(Bullet({speed:1,x:(hero.x+hero.width/2) ,
y:(hero.y+hero.height/2)}));
lastFire = Date.now();
}[/code]

In render function, draw each bullet on canvas with

[code language="javascript"]Bullets.forEach(function(Bullet){Bullet.draw();});[/code]

Final Code

[code language="javascript"]<!DOCTYPE HTML>
<html>
<head>
<script src="scripts/input.js"></script>
<style>
body {
margin: 0px;
padding: 0px;
}
canvas {
border:1px solid #ccc;
}

</style>
</head>
<body>
<script>
//global variables

//create canvas
var canvas = document.createElement('canvas');
canvas.id = 'myCanvas';
canvas.width ='500';
canvas.height ='300';
document.body.appendChild(canvas);

//get 2d context
var context = canvas.getContext("2d");
console.log(context.canvas.width);
console.log(context.canvas.height);

//create init function to initialize image loading
var img = new Image();
var imgLoaded = false;
img.onload = function() {
imgLoaded=true;
}
img.src = 'images/hero.png';

console.log(img.width);
console.log(img.height);

//create hero object
var hero = {
x: 50,
y: 50,
width:img.width,
height:img.height,
draw: function() {
if(imgLoaded){
context.drawImage(img,this.x,this.y);
}
}
};

//keeps number of fireshots in check
var lastFire = Date.now();

var Bullets =[]

//set properties of bullet object, argument - Bullet object
function Bullet(B) {
B.vx = B.speed;
B.vy = 0;
B.radius = 5;
B.color = "#0fa";
//draw circle
B.draw = function() {
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
context.fillStyle=this.color;
context.fill();
};
B.update = function() {
B.x += B.vx;
B.y += B.vy;
}
return B;

}

//main loop
function loop(){
requestAnimFrame(loop); //refresh screen for animation
update(); //update coordinates for each new draw
clear(); // clear canvas to give animation effect
render(); //draw the object
}

function update(){
handleInput();
Bullets.forEach(function(Bullet){Bullet.update();});
}

function handleInput(){

if(input.isDown('LEFT')) {
hero.x -= 3;
//clamp function will make sure that image moves within canvas area
hero.x= hero.x.clamp(0,context.canvas.width);
}
if(input.isDown('RIGHT')) {
hero.x += 3;
console.log(hero.x);
hero.x= hero.x.clamp(0,context.canvas.width-img.width);
}
if(input.isDown('UP')) {
hero.y -= 3;
hero.y= hero.y.clamp(0,context.canvas.height);
}
if(input.isDown('DOWN')) {
hero.y += 3;
hero.y= hero.y.clamp(0,context.canvas.height-img.height);
}
if(input.isDown('SPACE') && Date.now() - lastFire > 250){
//fire bullets from center of image
Bullets.push(Bullet({speed:1,x:(hero.x+hero.width/2) ,
y:(hero.y+hero.height/2)}));
lastFire = Date.now();
}

}

//draw function
function render() {
hero.draw();
Bullets.forEach(function(Bullet){Bullet.draw();});
}

// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();

function clear(){
context.clearRect(0,0,context.canvas.width,context.canvas.height);
}

Number.prototype.clamp = function(min, max) {
return Math.min(Math.max(this, min), max);
};

//call onload
loop();

</script>
</body>
</html>[/code]

References
http://blog.sklambert.com/html5-canvas-game-panning-a-background/
http://www.html5rocks.com/en/tutorials/canvas/notearsgame/

No comments:

Post a Comment