/* * * Author: Janne Siera * Author Website: http://www.bbgamedesign.com * * Permission: * Anyone can use or alter this script for any purpose. However you cannot sell it. If you found this script usefull or want to contribute to * improve it and give back to the community please visit http://www.bbgamedesign.com to share your experiences. Also, visit http://www.pbbgsnippets.com to * find more resources and to give something back to the community. * * Content: * This is a very basic javascript script based on the canvas element to display a game map in a browser. I share it with the PBBG community as an * example to the absolute beginner and / or anyone else interested in the script. This is not a very optimised script and there is a lot of room * for improvement. However, at time of writing (or better, typing) I am planning to write (or again better, type) an article on creating two * dimensional game maps for PBBG's including different techniques to implement this sort of map. * * The map doesn't work? * This map is tested in Google Chrome on windows. It is not tested in any other browser / on any other platform. Yeah... bite me. (and fuck IE too btw) * */ /* * Javascript engine for the canvas game map. * This script can easily be addapted to run with a programming language on the server side by making use of AJAX calls. */ // Load the game map on page load (the code is in a javascript namespace) window.onload = init; function init() { MAP.load(); } // The game map namespace var MAP = function() { // Static game data /* * The map data is a multi-dimensional array, since we will have different 'layers' for our map * The map is a grid with rows and columns forming 'cels' * The first layer of the map contains out of tiles, every cel must have an id as reference to a tile image * The second layer is optional (set by the 'exists' parameter) and contains an id as reference to an image if it exists * If you know how the map works you will see that it's very easy to add an extra layer, don't add too much since this is a very unoptimised script */ var map_data = { 0 : { 0 : { 'tile_img' : 1, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 5 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 2, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 3, 'item' : { 'exists' : false, 'img' : '', }, }, }, 1 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 0, 'item' : { 'exists' : true, 'img' : 17, }, }, 3 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 5 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 2 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 0, 'item' : { 'exists' : true, 'img' : 17, }, }, 5 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 3 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 14, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 12, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 5 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 4 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 0, 'item' : { 'exists' : true, 'img' : 18, }, }, 5 : { 'tile_img' : 10, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 10, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 10, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 0, 'item' : { 'exists' : true, 'img' : 17, }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 5 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 0, 'item' : { 'exists' : true, 'img' : 18, }, }, 2 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 5 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 6 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 5 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 16, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 14, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 12, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 7 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 9, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 0, 'item' : { 'exists' : true, 'img' : 18, }, }, 5 : { 'tile_img' : 10, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 10, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 12, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 8 : { 0 : { 'tile_img' : 8, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 0, 'item' : { 'exists' : true, 'img' : 17, }, }, 2 : { 'tile_img' : 10, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 10, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 12, 'item' : { 'exists' : false, 'img' : '', }, }, 5 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 0, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 15, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 4, 'item' : { 'exists' : false, 'img' : '', }, }, }, 9 : { 0 : { 'tile_img' : 7, 'item' : { 'exists' : false, 'img' : '', }, }, 1 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 2 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 3 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 4 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 5 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 6 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 7 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 8 : { 'tile_img' : 6, 'item' : { 'exists' : false, 'img' : '', }, }, 9 : { 'tile_img' : 5, 'item' : { 'exists' : false, 'img' : '', }, }, }, }; // Set these variables manually var tile_width = 50; var tile_height = 50; var map_width = 10 * tile_width; // cols * tile_width var map_height = 10 * tile_height; // rows * tile_height var fps = 25; // Frames per second var speed = 1; // Pixels to move the map per frame // Where are your images located? (I host this tileset on my blog, but you can also download them there for free) var path = "http://www.gamemap.bbgamedesign.com/img/"; // The images id's refer to this array // You can download this tileset for free from http://www.bbgamedesign.com var SRC = ['base_tile.jpg', '0_edge_tile.jpg', '1_edge_tile.jpg', '2_edge_tile.jpg', '3_edge_tile.jpg', '4_edge_tile.jpg', '5_edge_tile.jpg', '6_edge_tile.jpg', '7_edge_tile.jpg', '0_road_tile.jpg', '1_road_tile.jpg', '2_road_tile.jpg', '3_road_tile.jpg', '4_road_tile.jpg', '5_road_tile.jpg', '0_nature_tile.jpg', '1_nature_tile.jpg', 'city.png', 'waypoint.png']; // Preload the images var images = []; for(var i = 0; i < SRC.length; i++) { images[i] = new Image; // create new Image object and add to array images[i].src = path+SRC[i]; // assign the .src property of the Image object } // Map coordinates on page load // These are the coordinates on the "whole" map var x_start = 0; var y_start = 0; // Create page element references function create_element_reference() { canvas = document.getElementById('map'); // Reference to the canvas element canvas_height = canvas.height; // Get the canvas height canvas_width = canvas.width; // Get the canvas width }; /* Draw the map to the screen */ function draw() { ctx = canvas.getContext('2d'); // Get the context /* Prepare the vars */ // Canvas coordinates // These are the coordinates on the canvas element x = 0; y = 0; /* Start and End col */ // Since our canvas element will be smaller than our total map we will only draw the cels that we are currently viewing (from x_start and y_start) // I make sure that, if we are not on the edge, we will draw an extra column and an extra row // That way items can be bigger than tiles (but only 3x tile size and height, centered in the midle of it's cel!) // Start rendering from tile: start_col = Math.floor(x_start / tile_width); // The column the left-up corner cel is in start_row = Math.floor(y_start / tile_height); // The row the left-up corner cel is in // Calculate offset // When the map starts, for example, in the middle of a tile we will start drawing that tile off the screen a little bit offset_x = start_col * tile_width - x_start; offset_y = start_row * tile_height - y_start; // Number of tiles to draw // Can differ if we are on the beginning or end of the map num_tiles_x = canvas_width / tile_width; num_tiles_y = canvas_height / tile_height; // Render extra rows / cols (?) // I already mentioned we render an extra row and column so items can be bigger than tiles, we don't do this on the first and last row / col of the map if(num_tiles_x + start_col + 1 < map_width / tile_width) { num_tiles_x++; } if(num_tiles_y + start_row + 1 < map_height / tile_height) { num_tiles_y++; } // Set while parameters end_col = num_tiles_x + start_col; end_row = num_tiles_y + start_row; // Render extra rows / cols (?) // The order in which we do the checks is important if(start_col > 0) { start_col -= 1; offset_x -= tile_width; } if(start_row > 0) { start_row -= 1; offset_y -= tile_height; } /* Draw everything to the canvas */ draw_tiles(); // First layer draw_items(); // Second layer } // Render the tiles (base layer) // We loop through the cels and draw each image to the canvas element, row by row function draw_tiles() { y = offset_y; row = start_row; while(row <= end_row) { x = offset_x; col = start_col; while(col <= end_col) { var img = images[map_data[row][col]['tile_img']]; // tile image var x_pos = x; var y_pos = y; ctx.drawImage(img,x_pos,y_pos); // draw to the context x += tile_width; // Increment x coord col++; // Increment col } y += tile_height; // Increment y coord row++; // Increment row } } // Render the items (second layer) // We loop through the cels and if an item exists we draw it to the canvas, centered in the middle of it's cel // For each item we add some data to an 'EVENTS' array to set up an event listener for the mouse so that item will be clickable // If you want to, you can also make every tile clickable, but this is the technique I use for clickable areas var EVENTS = []; // Prepare the global var function draw_items() { EVENTS = []; // Reset when the map is drawn again (movement) y = offset_y; row = start_row; while(row <= end_row) { x = offset_x; col = start_col; while(col <= end_col) { if(map_data[row][col]['item']['exists'] == true) { var img = images[map_data[row][col]['item']['img']]; // tile image var width = img.width; // Get the object width var height = img.height; // Get the object height var x_pos = x + (tile_width / 2) - (width / 2); // Center the object on the tile (on x axis) var y_pos = y + (tile_height / 2) - (height / 2); // Center the object on the tile (on y axis) ctx.drawImage(img,x_pos,y_pos); // Draw to the context var event_data = {'img':img, 'x':x_pos, 'y':y_pos, 'col':col, 'row':row}; EVENTS.push(event_data); // Add the object id to the list to draw } x += tile_width; // Increment x coord col++; // Increment col } y += tile_height; // Increment y coord row++; // Increment row } create_item_events(); // Create the mouse event listeners } // Create the event listener for the mouse function create_item_events() { // Change the cursor style when mouseover an object canvas.addEventListener('mousemove', func1 = function(e) { var x_mouse = e.clientX-canvas.offsetLeft; // Offset on the canvas element var y_mouse = e.clientY-canvas.offsetTop; // Offset on the canvas element this.style.cursor='default'; // Change it back to normal if we are not on an item for(id in EVENTS) // Check for each item if we are on it { var x = EVENTS[id]['x']; var y = EVENTS[id]['y']; var img = EVENTS[id]['img']; var width = img.width; var height = img.height; if(x <= x_mouse && x_mouse <= x + width && y <= y_mouse && y_mouse <= y + height) { this.style.cursor='pointer'; } } }, false); // Catch onclick event on an item canvas.addEventListener('click', func2 = function(e) { var x_mouse = e.clientX-canvas.offsetLeft; // Offset on the canvas element var y_mouse = e.clientY-canvas.offsetTop; // Offset on the canvas element for(id in EVENTS) // Check for each item if we are on it { var col = EVENTS[id]['col']; var row = EVENTS[id]['row']; var x = EVENTS[id]['x']; var y = EVENTS[id]['y']; var img = EVENTS[id]['img']; var width = img.width; var height = img.height; if(x <= x_mouse && x_mouse <= x + width && y <= y_mouse && y_mouse <= y + height) { click_item(col,row); // When clicked on an item, do this function and pas whatever data you want to pas } } }, false); } // Remove the clickable area event listeners (re-created in create_object_events()) function destroy_item_events() { canvas.removeEventListener('mousemove', func1, false); canvas.removeEventListener('click', func2, false); } // Function to be called when an item is 'clicked' function click_item(col,row) { document.getElementById('disp').innerHTML = 'You clicked on an object. This object is on row '+row+' and col '+col+', which is a reference to specifc information about this house.'; } /* Key input */ // Set up an event listener to catch key input (we are using the arrow keys to move the map) // Initialise the keycode vars var keycode_left = 37; var keycode_up = 38; var keycode_right = 39; var keycode_down = 40; // Initialise the (in)active key vars var right = false; var left = false; var up = false; var down = false; // Create event listeners to catch user keyboard input function create_key_input() { // When a key is released window.addEventListener('keyup', function(e) { switch(e.keyCode) { case keycode_right: right = false; break; case keycode_left: left = false; break; case keycode_up: up = false; break; case keycode_down: down = false; break; } }, false); // End keyup // When a key is pressed window.addEventListener('keydown', function(e) { switch(e.keyCode) { case keycode_right: right = true; break; case keycode_left: left = true; break; case keycode_up: up = true; break; case keycode_down: down = true; break; } }, false); // End keydown } // End create_key_input() // Update key input function update_key_input() { if(right == true && map_width - x_start - canvas_width > speed) { // If the key is pressed and if not exceeding map boundaries x_start += +speed; // Change the coords } if(left == true && x_start >= speed) { // If the key is pressed and if not exceeding map boundaries x_start += -speed; // Change the coords } if(up == true && y_start >= speed) { // If the key is pressed and if not exceeding map boundaries y_start += -speed; // Change the coords } if(down == true && map_height - y_start - canvas_height > speed) { // If the key is pressed and if not exceeding map boundaries y_start += +speed; // Change the coords } } /* The game loop */ // We are going to move the game map by using the arrow keys // Since this is animation we need a 'game loop' // In this game map I will use a setInterval that checks the game 25 times per second (25 FPS) // A more advanced explanation of game loops you can find at http://dev.koonsolo.com/7/dewitters-gameloop/ // Start the game loop function start_gameloop() { gameloop_interval = setInterval(function() { gameloop(); },fps/1000); // frames per second / 1000 milliseconds (=1sec) } // Stuff to do every frame function gameloop() { update_key_input(); // Checks if a key is pressed and updates x_start & y_start if(right == true || left == true || up == true || down == true) { // If a key is pressed destroy_item_events(); // Remove the old mouse event listeners to make room for the new draw(); // Draw the map again with the new coords } } // Return from the namespace return { // Load the game load : function() { create_element_reference(); // Create references for the page elements (needs to be called first!) create_key_input(); // Create the event listeners for key input draw(); // Draw the map once start_gameloop(); // Start the game loop }, }; }();