Surface Game Canvas: JavaScript Source Code

Free HTML5 Game Tutorial for Beginners: Page 29

Landscape Resize Event Handler Portrait Resize Event Handler Draw to the Canvas

HTML5 Mobile Web Programming

Free Online Game Development Course: Design with HTML5

This Web page includes the entire source code which draws to the canvas for the Surface Game. JavaScript file surface-game-canvas.js responds to window resize events. surface-game-canvas.js saves coordinates from window resize events. surface-game-canvas.js reacts to touch and mouse movement on the canvas. surface-game-canvas.js draws digits as the user moves them across the canvas. surface-game-canvas.js snaps the digits into place within the answer box.

//HTML canvas element.
var cvs = null; 

//canvas context.
var context = null; 

//Bounding box of canvas.
var bb = null;

//Width and height of one image.
var iDimDigit = new Number(10);

//Half the dimensions of one image.
var iDimHalf = new Number(5);

//Vertical space between rows.
var iSpacer  = new Number(5); 

//Y coordinate to highlight 
//a digit when selected.
var iHighlightY = new Number(0);

//Array of images of 
//digits or symbols.
var aImgDigits = null; 

//Answer box rectangle:x,y,h,w.
var shapeAnswerBox = null; 

//Top box of draggable 
//digits, bottom Y coordinate.
var iDragBoxT = null; 

//Bottom box of draggable 
//digits, top Y coordinate.
var iDragBoxB = null;

// Offsets of the 
// bounding box of the canvas:
var iOffsetX = new Number(0);
var iOffsetY = new Number(0);

//Array of coordinates 
// in top row of images:
var aCoords  = null; 

//Array of coordinates 
// in bottom row of images:
var aCoordsB  = null;

//One coordinate;
var coord = null;

//Array of coordinates 
//representing answers. 
//Each coord contains an 
// index into the digit array.
var aAnswers = null;

// Index into aAnswers:
var iAnswer  = new Number(0);

// Max images to display [0..9].
const IMG_COUNT = new Number(10); 

// Max images per row to display [0..4].
const ROW_COUNT = new Number(5);

// Dimensions of one digit image. 
const I_DIM_DIGIT  = new Number(32); 

/**
 * function loadDisplay() is called
 * to initialize fields for the 
 * code in this file.
 * This file modifies the canvas.
 */
function loadDisplay(){
 
 cvs = document.getElementById('cv');

 context  = cvs.getContext('2d'); 

 aAnswers = new Array(ROW_COUNT);

 loadDigits(); 

 if (window.innerWidth < window.innerHeight){

  resizeCanvasMobile();

 }
 else {  

   resizeCanvasDesktop();

 }
 
 cvs.addEventListener('touchmove', function (e) {
 
 e.preventDefault(); 
  
 if (e.targetTouches.length >=  1) {
   
  var t1 = e.targetTouches[0];
   
  drawTapGraphic(t1.pageX,t1.pageY);
   
 }
  
 },false);

}

/**
 * function loadDigits()
 * 1. create a local array of String.
 * Each String is the path to the source
 * for one image.
 * 2. Iterate over the array of String.
 * For each String create an instance of a 
 * new Image and assign the path to the source.
 * 3. Finally wait until the last image has loaded,
 * then call the function drawDigits().
 */
function loadDigits(){
 
 iDimDigit = Number(window.innerWidth * 0.09);
 
 aImgDigits = new Array(IMG_COUNT);
 
var aSDigits=[
 "assets/digit0.gif",
 "assets/digit1.gif",
 "assets/digit2.gif",
 "assets/digit3.gif",
 "assets/digit4.gif",
 "assets/digit5.gif",
 "assets/digit6.gif",
 "assets/digit7.gif",
 "assets/digit8.gif",
 "assets/digit9.gif"
];
 
 for (var i= 0; i < IMG_COUNT; i++){
 
  aImgDigits[i] = new Image();

  aImgDigits[i].src = aSDigits[i];

 }

 aImgDigits[IMG_COUNT-1].onload = function() { 
 
  drawDigits();

 };
}

Generic Resize Event Handler

The Surface Game's body tag assigns function resizeCanvas() to execute whenever a resize event triggers, as follows.

<body 
onload="loadGame()" 
onresize="resizeCanvas()" 
onunload="stopGame()"
>

Function resizeCanvas() simply tests the window's width by it's height. If the width is greater than the height, then resize for landscape orientation, with function resizeCanvasDesktop(). Otherwise resize for portrait orientation with function resizeCanvasMobile().

function resizeCanvas(){
if(window.innerWidth > window.innerHeight){
 resizeCanvasDesktop();
}
else {
 resizeCanvasMobile();
}
}

Landscape Resize

/**
* Resize the canvas
* for horizontal views.
* Canvas covers 1/2 the
* window width.
 */
function resizeCanvasDesktop(){ 
 
 var iW = window.innerWidth * 0.4; 
 
 iW = new Number(iW); 
 
 resizeCoords(iW); 
 
 iSpacer = iDimDigit/4;
 
 iDragBoxT = iDimDigit+iSpacer;
 
 iDragBoxB = iDimDigit * 2.75;

 iH = new Number(iDimDigit * 3.75); 
 
 resizeData(iW,iH);
 
 iOffsetX = bb.left;
 
 iOffsetY = bb.top; 
 
}

Portrait Resize

/**
* resizeCanvasMobile()
* assumes vertical
* orientation.
* canvas width equals
* 90% of the window width.
 */
function resizeCanvasMobile(){
 
 var iW = window.innerWidth * 0.90;

 resizeCoords(iW);

 iSpacer = iDimDigit/2;

 iDragBoxT = iDimDigit+iSpacer;

 iDragBoxB = iDimDigit * 3.5;

 iH = new Number(iDimDigit * 4.5);

 resizeData(iW,iH);  

}

/**
 * function resizeData() is called
 * by both the desktop and the mobile
 * onresize event handlers.
 * 1. Set the canvas dimensions.
 * 3. draw the various graphics 
 * onto the canvas.
 * @param {Number} iW: Canvas width.
 * @param {Number} iH Canvas height.
 */
function resizeData(iW,iH){
 
 if (context != null){
 
  context.canvas.width = iW;

  context.canvas.height = iH; 

  bb  = cvs.getBoundingClientRect();

  drawAnswerBox();

  drawAnswers();

  drawDigits(); 

 } 
}

/**
 * function resizeCoords()
 * creates two arrays, 
 * representing two rows
 * of coord. 
 * Each coordinate represents 
 * the X coordinates of one digit
 * on the canvas display.
 *
 * @param {Number} iW:
 * width of top or bottom
 * draggable boxes of digits.
 */
function resizeCoords(iW){
 
 // function coordsLoad()
 // is defined in coords.js.
 aCoords = coordsLoad(
  iW,
  aCoords,
  0
 );

 aCoordsB = coordsLoad(
  iW,
  aCoordsB,
  ROW_COUNT
 );

 iDimDigit = aCoords[0].max;

 iDimHalf = iDimDigit/2; 
}

Draw to the Canvas

/**
 * function redraw() clears then redraws the area
 * where we dragged. Computations are minimized
 * in redraw() because it's called often.  
 */
function redraw(){  
 
context.fillRect(
 0,
 0,
 bb.width,
 bb.height
);

context.fillStyle="#ffffff";

context.fillRect(
 shapeAnswerBox.sx,
 shapeAnswerBox.sy,
 shapeAnswerBox.sw,
 shapeAnswerBox.sh
);
 
context.fillStyle="#000099";

var j = 0;

var i = 0;

for (i = 0; i < ROW_COUNT; i++){ 
 
 context.drawImage(
  aImgDigits[i],
  0,
  0,
  I_DIM_DIGIT,
  I_DIM_DIGIT,
  aCoords[i].min,
  iSpacer, 
  iDimDigit,
  iDimDigit
 );

}

for (i = ROW_COUNT; i < IMG_COUNT; i++){
 
  context.drawImage(
   aImgDigits[i],
   0,
   0,
   I_DIM_DIGIT,
   I_DIM_DIGIT,
   aCoords[j].min,
   iDragBoxB, 
   iDimDigit,
   iDimDigit
  );

 j++;

} 

for (i = 0; i < aAnswers.length; i++){
 
 var c = aAnswers[i];


 if (c != null && c.idx != null){
 
  context.drawImage(
   aImgDigits[c.idx],
    0,
    0,
    I_DIM_DIGIT,
    I_DIM_DIGIT,
    c.min,
    shapeAnswerBox.sy, 
    iDimDigit, 
    iDimDigit
   );

  }

 }

 context.strokeRect(
  shapeAnswerBox.sx,
  shapeAnswerBox.sy,
  shapeAnswerBox.sw,
  shapeAnswerBox.sh
 );

}

/**
 * function drawDigits() draws only the digits, 
 * after verifying each source image has loaded.
 */
function drawDigits(){   
 
  var i = 0; 
  var j = 0;
  try {   
   for (i = 0; i < ROW_COUNT; i++){
 
    if(aImgDigits[i].complete == true) {
 
     context.drawImage(
      aImgDigits[i],
      0,
      0,
      I_DIM_DIGIT,
      I_DIM_DIGIT,
      aCoords[i].min,
      iSpacer, 
      iDimDigit,
      iDimDigit
     );

    }

   }

   for (i = ROW_COUNT; i < IMG_COUNT; i++){
 
    if(aImgDigits[i].complete == true) {
 
     context.drawImage(
      aImgDigits[i],
      0,
      0,
      I_DIM_DIGIT,
      I_DIM_DIGIT,
      aCoords[j].min,
      iDragBoxB, 
      iDimDigit,
      iDimDigit
     ); 
   
    }

   j++;
  }

 }

 catch (err){
 
  viewDebug("drawDigits():"+err.toString()); 

 } 
}

/**
 * function drawAnswers() is called in response
 * to window resizing. Therefore each answer's
 * X position has to be recalculated.
 */
function drawAnswers(){

for (var i = 0; i < aAnswers.length; i++){
  
 var c = aAnswers[i];
  
 if (c != null && c.idx != null){
   
  c.min = i * iDimDigit;
   
  context.drawImage(
   aImgDigits[
    c.idx],
    0,
    0,
    I_DIM_DIGIT,
    I_DIM_DIGIT,
    c.min,
    shapeAnswerBox.sy, 
    iDimDigit, 
    iDimDigit
   );

  }
  
 }
 
}

/**
 * function drawAnswerBox()
 * Creates a rectangle for the answer box.
 * draws the box where answers are placed. 
 */
function drawAnswerBox(){
 
 shapeAnswerBox = new Shape4Sides(
  0,
  iDimDigit+iSpacer*2,
  iDimDigit*ROW_COUNT,
  iDimDigit
 );
 
 context.strokeStyle = "#ff0000";
 
 context.fillStyle = "#ffffff";
 
 context.beginPath();  
 
 context.fillRect(
  shapeAnswerBox.sx,
  shapeAnswerBox.sy,
  shapeAnswerBox.sw,
  shapeAnswerBox.sh
 );
 
 context.strokeRect(
  shapeAnswerBox.sx,
  shapeAnswerBox.sy,
  shapeAnswerBox.sw,
  shapeAnswerBox.sh
 );

 // Medium-dark blue
 context.fillStyle = "#000099";

}

/**
 * function drawTapGraphic()
 * is called eventually by both mobile
 * and desktop versions of the game.
 *
 * @param {Number} iX: The tapped X coordinate.
 * @param {Number} iY: The tapped Y coordinate.
 */
function drawTapGraphic(iX,iY){
 
 try{ 
  
  if (iX < 0) iX = iOffsetX;
  
  if (iY < 0) iY = iOffsetY; 
  
  if (iY <= iDragBoxT){
   
   coord = coordFind(iX,aCoords);
   
   iHighlightY = iSpacer;
   
  }
  
  else if (iY >= iDragBoxB){
   
   coord = coordFind(iX,aCoordsB); 
   
   iHighlightY = iDragBoxB; 
   
  }
  
  if (coord != null){
   
  // Draw graphic inside the answer box:
  if (isInsideShape(iX,iY,shapeAnswerBox)== true){
    
    drawAnswerDigit(coord.idx);
    
   }
   
   // Draw graphic tapped:
   else if (isInsideShape(iX,iY,shapeAnswerBox) == false){

    redraw();

    drawHighlight();

   }
   
  }
  
 }
 
  catch (err){
  
  viewDebug("drawTapGraphic(): "+err.toString());
 
 }
 
}

function drawHighlight(){
 
// Highlight the digit the user tapped.
context.strokeRect(
 coord.min,
 iHighlightY,
 iDimDigit,
 iDimDigit
);

 context.globalAlpha = 0.5;

 context.fillStyle = "#0000ff";

  context.fillRect(
  coord.min,
  iHighlightY,
  iDimDigit,
  iDimDigit
 );

 context.globalAlpha = 1.0;

 context.fillStyle = "#000099";

}

/***
* @param {Number} index:
* index into array of images.
**/
function drawAnswerDigit(index){
 
 try {

  // Clear the answers out
  // for another set of answers to display.
  if (iAnswer >= ROW_COUNT){
   answersSet();
  }
  redraw();

  var im = aImgDigits[index];

  var c = new Coord(
   (iAnswer * iDimDigit),
   0,
   index
  );

  //Index in the array.
  aAnswers[iAnswer] = c;

  context.drawImage(
   im,
   0,
   0,
   I_DIM_DIGIT,
   I_DIM_DIGIT,
   c.min,
   shapeAnswerBox.sy, 
   iDimDigit, 
   iDimDigit
  );

  context.strokeRect(
   shapeAnswerBox.sx,
   shapeAnswerBox.sy,
   shapeAnswerBox.sw,
   shapeAnswerBox.sh
  );

  context.strokeRect(
   c.min,
   shapeAnswerBox.sy,
   iDimDigit,
   iDimDigit
  );

  iAnswer++;

 }

 catch (err) {
 
  viewDebug("drawAnswerDigit(): "+err.toString());
 
 }

 coord = null;

}
/**
 * function answersGet() is called
 * by the controller.
 * 
 * @returns {Array} aAnswers: Coord
 * representing every digit the player
 * dragged to the answer box.
 */
function answersGet(){
 
 return aAnswers;
 
}

/**
 * function answerSet() is called
 * by the controller to
 * create default answer data.
 * aAnswers ia an Array of Coord.
 * iAnswer is an index into aAnswers.
 */
function answersSet(){
 
 iAnswer = new Number(0);

 aAnswers = new Array(ROW_COUNT);

}

/**
 * function mouseMoveCanvas()
 * @param {MouseEvent} ev:
 * 
 * The mouse move event input clientX 
 * and clientY are the coordinates
 * of the entire browser screen, 
 * not the coordinates of the canvas.
 * Therefore translate the coordinates, 
 * and then call our drawTapGraphic() function.
 */
function mouseMoveCanvas(ev){
 
 ev.preventDefault();

 var a = translateToCanvasCoords(
  ev.clientX, 
  ev.clientY
 );

 drawTapGraphic(a[0],a[1]);

}

/**
 * function takes mouse movement from a 
 * desktop browser window,
 * then offsets the X and Y values, to represent
 * coordinates within the canvas itself.
 
 * @param {Number} x: Player's current
 * X coordinate.
 * @param {Number} y: Player's current
 * Y coordinate.
 * @return {Array} X and Y canvas
 * coordinates.
 */
function translateToCanvasCoords(x,y){
 
 var actualX = x - iOffsetX;

 var actualY = y - iOffsetY;

 return [actualX,actualY];

}

For the next section, tap the image icon containing text which says Next Lesson, or the right pointing arrow at the bottom of the page.

Page 29

Game Tutorial for Beginners was prepared a few years ago. HTML5, CSS3, and JavaScript have improved over time, yet this free course continues to include useful features and long standing software development techniques. Enjoy more projects with free Web & game development tutorials.

Tags

free tutorials, learn web design, learn web programming, read books online free, free ebooks, free books online,free books to read, online books, learn web development, STEM, CSS 3, HTML 5, Web developer, learn to code, learn programming, interactive web design, interactive website design,

Copyright © 2015 Seven Thunder Software. All Rights Reserved.