Console error when drawing to canvas - javascript

I have a main javascript file which is trying to draw a tilemap to a canvas for a game, and i have an included file which actually draws the maps etc, but im getting this error in firebug:
TypeError: Value could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElement.
[Break On This Error]
ctx.drawImage(mapTiles[map[x][y]],x*tilesize, y*tilesize);
the main js which calls my level.js file looks like this:
$(document).ready(function(){
var canvas = document.getElementById("TBG");
var context = canvas.getContext("2d");
var ui = new Gui();
var level = new Level();
//----------Login/Register Gui --------------
$('#TBG').hide();
$('#load-new').hide();
$('#reg').hide();
$('#login').hide();
//if login_but is clicked do ui.login function
$('#login_but').click(ui.login);
//if reg_but is clicked do ui.register function
$('#reg_but').click(ui.register);
$('#new_but').click(function(){
game_settings("new");
});
$('#load_but').click(function(){
game_settings("load");
});
//if login_sumbit button is clicked do ui.login_ajax function
$("#login_submit").click(ui.login_ajax);
$("#reg_submit").click(ui.register_ajax);
$("#welcome").on("click", "#logout_but", ui.logout);
//____________________________________________
//Initialisation of game
function game_settings(state){
if(state == "load"){
ui.load_game();
//do ajax call to load user last save
//level.level_init(0,1);
draw();
}
else{
//set beginning params
//Change screens
ui.new_game();
alert("new game");
}
}
function draw () {
context.clearRect(0,0,canvas.width,canvas.height);
level.draw(context);
setTimeout(draw, 30);
}
// End Loop
});
and the file where this line of code (level.js) looks like this:
function Level(){
var tilesize = 32;
var map = 0;
var mapTiles = [];
// var saved_level = level;
//var saved_location = location;
map = [
[1,1,1,1,1,1,1,1,1,1],
[2,2,2,2,2,2,2,2,2,2]
];
for (x = 1; x <= 256; x++) {
var imageObj = new Image(); // new instance for each image
imageObj.src = "images/prototype/"+x+".jpg";
mapTiles.push(imageObj);
}
this.draw = function(ctx) {
for (var x = 0; x <= 31; x++){
for ( var y = 0; y <= 31; y++){
ctx.drawImage(mapTiles[map[x][y]],x*tilesize, y*tilesize);
}
}
};
}
Have any of you ever encountered this error?
and how would i correct the issue, thanks tom

Forget about canvas and everything else and look at the map array and the code that uses it. Let's rip out the canvas code and examine the array accesses with some console.log() calls:
map = [
[1,1,1,1,1,1,1,1,1,1],
[2,2,2,2,2,2,2,2,2,2]
];
console.log( map.length );
console.log( map[0].length );
for (var x = 0; x <= 31; x++){
for ( var y = 0; y <= 31; y++){
console.log( x, y, map[x], map[x] && map[x][y] );
}
}
Do you see the problem? You may be able to spot it by thinking about what those console.log() calls will print. If not, paste that code into the Chrome developer console and see what it does. (BTW, where the last argument has map[x] && map[x][y], that's essentially the same as map[x][y] but it protects the code from crashing when map[x] is undefined as you will see happen in the log.)
Now that we now that map[x][y] is undefined some of the time, go back to the drawImage() call, which uses this as the image argument:
mapTiles[ map[x][y] ]
When map[x][y] is undefined, what will that expression evaluate to?

Related

JS Game - cannot set property of undefined typeerror

I know there are similar questions as mine but they did not work for my case and I can not waste more time.
I am learning JS so I am trying to code an easy pacman game. In this case, every time pacman eats a powerup, all the active ghosts have to turn into weak ghosts.
The problems comes when while I am playing and I grab a powerup, the game crashes saying:
Uncaught TypeError: Cannot set property 'isWeak' of undefined.
I did not post all the code in this question. Only the main part where the error comes from(I think so).
My code in main.js:
var activeGhosts = [];
var powerups = [];
for (var i = 0; i < powerups.length; i++) {
if (pacman.collision(powerups[i])) {
makeWeak();
powerups.splice(i,1);
}
}
function makeWeak() {
for (var i = 0; i < activeGhosts.length; i++) activeGhosts[i].isWeak = true;
}
My code in ghost.js:
function Ghost(x,y,img){
this.x = x;
this.y = y;
this.img = img;
this.direction = 0;
this.radius = 16; // half of 32 px because every image is 32x32 px
this.crash = false;
this.isWeak = false;
this.show = function () {
if (this.isWeak) {
image(weakghostimg, this.x, this.y);
} else {
// img can be the all the different ghosts
image(img, this.x, this.y);
}
};

How to add a 16x16 grid of pseudo-random tiles as a background in canvas (or other web language)

I have recently been working on a web based project using canvas on HTML5. The program consists of a 16x16 grid of tiles that have been pseudo-randomly generated. I am relatively new to canvas, but have built this program in several other environments, none of which however compile successfully to a web based language. this is the main code section that is giving me bother:
var A = 8765432352450986;
var B = 8765432352450986;
var M = 2576436549074795;
var X = 1;
var rx = 0;
var ry = 0;
this.image = new Image();
var i = 0;
var ii = 0;
while(i < 16)
{
while(ii < 16)
{
this.image = new Image();
this.image.src = "textures/grass.png";
x = (((A*X)+B)%M)%M;
if((x/2)%1 == 0)
{
this.image.src = "textures/grass.png";
}
if((x/8)%1 == 0)
{
this.image.src = "textures/hill.png";
}
if((x/21)%1 == 0)
{
this.image.src = "textures/trees.png";
}
if((x/24)%1 == 0)
{
this.image.src = "textures/sea.png";
}
if((x/55)%1 == 0)
{
this.image.src = "textures/mountain.png";
}
if((x/78)%1 == 0)
{
this.image.src = "textures/lake.png";
}
if((x/521)%1 == 0)
{
this.image.src = "textures/volcano.png";
}
if((x/1700)%1 == 0)
{
this.image.src = "textures/shrine.png";
}
if((x/1890)%1 == 0)
{
this.image.src = "textures/outpost.png";
}
if((x/1999)%1 == 0)
{
this.image.src = "textures/civ.png";
}
ctx = myGameArea.context;
ctx.drawImage(this.image,rx, ry, 20, 20);
ii ++;
rx += 20;
}
i ++;
rx = 0;
ry += 16;
}
I would like canvas to draw along the lines of this code above, effectively generating a grid like this
pre generated grid image
(please try and ignore the obvious bad tile drawings, I planned on either finding an artist or trying slightly harder on them when I get the game fully working.)
The black square is a separate movable object. I haven't got as far as implementing it in this version, but if you have any suggestions for it please tell me
in the full html file I have now, the canvas renders but none of the background (using the w3schools tutorials, I can make objects render however)
In short: how do I render a background consisting of a 16x16 grid of pseudo-random tiles on an event triggered or on page loaded, using canvas or if that does not work another web based technology
Thank you for your time.
A few problems but the main one is that you need to give an image some time to load before you can draw it to the canvas.
var image = new Image();
image.src = "image.png";
// at this line the image may or may not have loaded.
// If not loaded you can not draw it
To ensure an image has loaded you can add a onload event handler to the image
var image = new Image();
image.src = "image.png";
image.onload = function(){ ctx.drawImage(image,0,0); }
The onload function will be called after all the current code has run.
To load many images you want to know when all have loaded. One way to do this is to count the number of images you are loading, and then use the onload to count the number of images that have loaded. When the loaded count is the same as the loading count you know all have loaded and can then call a function to draw what you want with the images.
// Array of image names
const imageNames = "grass,hill,trees,sea,mountain,lake,volcano,shrine,outpost,civ".split(",");
const images = []; // array of images
const namedImages = {}; // object with named images
// counts of loaded and waiting toload images
var loadedCount = 0;
var imageCount = 0;
// tile sizes
const tileWidth = 20;
const tileHeight = 20;
// NOT SURE WHERE YOU GOT THIS FROM so have left it as you had in your code
// Would normally be from a canvas element via canvasElement.getContext("2d")
var ctx = myGameArea.context;
// seeded random function encapsulated in a singleton
// You can set the seed by passing it as an argument rand(seed) or
// just get the next random by not passing the argument. rand()
const rand = (function(){
const A = 8765432352450986;
const B = 8765432352450986; // This value should not be the same as A?? left as is so you get the same values
const M = 2576436549074795;
var seed = 1;
return (x = seed) => seed = ((A * x) + B) % M;
}());
// function loads an image with name
function addImage(name){
const image = new Image;
image.src = "textures/" + name + ".png";
image.onload = () => {
loadedCount += 1;
if(loadedCount === imageCount){
if(typeof allImagesLoaded === "function"){
allImagesLoaded();
}
}
}
imageCount += 1;
images.push(image);
namedImages[name] = image;
}
imageNames.forEach(addImage); // start loading all the images
// This function draws the tiles
function allImagesLoaded(){ /// function that is called when all the images have been loaded
var i, x, y, image;
for(i = 0; i < 256; i += 1){ // loop 16 by 16 times
ctx.drawImage(
images[Math.floor(rand()) % images.length]; //random function does not guarantee an integer so must floor
(i % 16) * tileWidth, // x position
Math.floor(i / 16) * tileHeight, // y position
tileWidth, tileHeight // width and height
);
}
}

My javascript canvas map script and poor performance

Basically below is my script for a prototype which uses 128x128 tiles to draw a map on a canvas which user can drag to move around.
Script does work. However I have a few problems to be solved:
1. Poor performance and I can't figure out why.
2. I am missing a method to buffer the tiles before the actual drawing.
3. If you notice any other issues also that could help me to make things run more smoothly it would be fantastic.
Some explanations for the script:
variables
coordinates - Defines the actual images to be displayed. Image file names are type of '0_1.jpg', where 0 is Y and 1 is X.
mouse_position - As name says, is keeping record of mouse position.
position - This is a poorly named variable. It defines the position of the context drawn on canvas. This changes when user drags the view.
Any assistance would be appreciated greatly. Thank you.
var coordinates = [0, 0];
var mouse_position = [0, 0];
var position = [-128, -128];
var canvas = document.getElementById('map_canvas');
var context = canvas.getContext('2d');
var buffer = [];
var buffer_x = Math.floor(window.innerWidth/128)+4;
var buffer_y = Math.floor(window.innerHeight/128)+4;
var animation_frame_request = function() {
var a = window.requestAnimationFrame;
var b = window.webkitRequestAnimationFrame;
var c = window.mozRequestAnimationFrame;
var d = function(callback) {
window.setTimeout(callback, 1000/60);
}
return a || b || c || d;
}
var resizeCanvas = function() {
window.canvas.width = window.innerWidth;
window.canvas.height = window.innerHeight;
window.buffer_x = Math.floor(window.innerWidth/128)+4;
window.buffer_y = Math.floor(window.innerHeight/128)+4;
window.buffer = [];
for (row = 0; row < window.buffer_y; row++) {
x = [];
for (col = 0; col < window.buffer_x; col++) {
x.push(new Image());
}
window.buffer.push(x);
}
}
var render = function() {
animation_frame_request(render);
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
cy = window.coordinates[1]+row;
cx = window.coordinates[0]+col;
window.buffer[row][col].src = 'map/'+cy+'_'+cx+'.jpg';
}
}
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
window.context.drawImage(window.buffer[row][col],
window.position[0]+col*128,
window.position[1]+row*128, 128, 128);
}
}
}
var events = function() {
window.canvas.onmousemove = function(e) {
if (e['buttons'] == 1) {
window.position[0] += (e.clientX-window.mouse_position[0]);
window.position[1] += (e.clientY-window.mouse_position[1]);
if (window.position[0] >= 0) {
window.position[0] = -128;
window.coordinates[0] -= 1;
} else if (window.position[0] < -128) {
window.position[0] = 0;
window.coordinates[0] += 1;
}
if (window.position[1] >= 0) {
window.position[1] = -128;
window.coordinates[1] -= 1;
} else if (window.position[1] < -128) {
window.position[1] = 0;
window.coordinates[1] += 1;
}
render();
}
window.mouse_position[0] = e.clientX;
window.mouse_position[1] = e.clientY;
}
}
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('load', resizeCanvas, false);
window.addEventListener('mousemove', events, false);
resizeCanvas();
To get better performance you should avoid changing the src of img nodes and move them around instead.
A simple way to minimize the number of img nodes handled and modified (except for screen positioning) is to use an LRU (Least Recently Used) cache.
Basically you keep a cache of last say 100 image nodes (they must be enough to cover at least one screen) by using a dictionary mapping the src url to a node object and also keeping them all in a doubly-linked list.
When a tile is required you first check in the cache, and if it's already there just move it to the front of LRU list and move the img coordinates, otherwise create a new node and set the source or, if you already hit the cache limit, reuse the last node in the doubly-linked list instead. In code:
function setTile(x, y, src) {
var t = cache[src];
if (!t) {
if (cache_count == MAXCACHE) {
t = lru_last;
t.prev.next = null;
lru_last = t.prev;
t.prev = t.next = null;
delete cache[t.src]
t.src = src;
t.img.src = src;
cache[t.src] = t;
} else {
t = { prev: null,
next: null,
img: document.createElement("img") };
t.src = src;
t.img.src = src;
t.img.className = "tile";
scr.appendChild(t.img);
cache[t.src] = t;
cache_count += 1;
}
} else {
if (t.prev) t.prev.next = t.next; else lru_first = t.next;
if (t.next) t.next.prev = t.prev; else lru_last = t.prev;
}
t.prev = null; t.next = lru_first;
if (t.next) t.next.prev = t; else lru_last = t;
lru_first = t;
t.img.style.left = x + "px";
t.img.style.top = y + "px";
scr.appendChild(t.img);
}
I'm also always appending the requested tile to the container so that it goes in front of all other existing tiles; this way I don't need to remove old tiles and they're simply left behind.
To update the screen I just iterate over all the tiles I need and request them:
function setView(x0, y0) {
var w = scr.offsetWidth;
var h = scr.offsetHeight;
var iy0 = y0 >> 7;
var ix0 = x0 >> 7;
for (var y=iy0; y*128 < y0+h; y++) {
for (var x=ix0; x*128 < x0+w; x++) {
setTile(x*128-x0, y*128-y0, "tile_" + y + "_" + x + ".jpg");
}
}
}
most of the time the setTile request will just update the x and y coordinates of an existing img tag, without changing anything else. At the same time no more than MAXCACHE image nodes will be present on the screen.
You can see a full working example in
http://raksy.dyndns.org/tiles/tiles.html

draw image on canvas after load into array

I tried to create an array of Image to be displayed on a canvas, after each image is loaded. No errors, no draw...
var x=...
var y=...
var canvas = document.getElementById(sCanvasName);
var context = canvas.getContext('2d');
var imageCardObj = [];
//vCards contains the images file names
for (var k=0;k<vCards.length;k++){
imageCardObj[k] = new Image();
var func = function(){
var c = arguments[3];
try{
c.drawImage(arguments[0], arguments[1], arguments[2]);
}catch(e){
alert(e.message)
}
}
imageCardObj[k].onload = func(imageCardObj[k], x, y, context);
imageCardObj[k].src = "res/img/"+vCards[k].trim()+".png";
x+=40;
}
You are calling the func() handler and gives the result to it to the image's onload handler. That won't work so well.. and you cannot pass arguments that way to a handler function.
Try this:
var func = function(){
// "this" will be the current image in here
var c = arguments[3];
try{
c.drawImage(this, x, y); // you need to reference x and y
}catch(e){
alert(e.message)
}
}
imageCardObj[k].onload = func; // only a reference here
If you need different x and y's then you need to maintain those on the side, either in an additional array or use objects to embed the image, its intended x and y and use the url to identify the image in question inside the func() callback.
Also note that load order may vary as the last image loaded could finish before the first one so when you draw the image they may not appear in the same order.
You may want to do this instead:
var files = [url1, url2, url, ...],
images = [],
numOfFiles = files.length,
count = numOfFiles;
// function to load all images in one go
function loadImages() {
// go through array of file names
for(var i = 0; i < numOfFiles; i++) {
// create an image element
var img = document.createElement('img');
// use common loader as we need to count files
img.onload = imageLoaded;
//img.onerror = ... handle errors too ...
//img.onabort = ... handle errors too ...
img.src = files[i];
// push image onto array in the same order as file names
images.push(img);
}
}
function imageLoaded(e) {
// for each successful load we count down
count--;
if (count === 0) draw(); //start when all images are loaded
}
Then you can start the drawing after the images has loaded - the images are now in the same order as the original array:
function draw() {
for(var i = 0, img; img = images[i++];)
ctx.drawImage(img, x, y); // or get x and y from an array
}
Hope this helps!
This is the final (working) version
var x=...
var y=...
var canvas = document.getElementById(sCanvasName);
var context = canvas.getContext('2d');
var imageCardObj = [];
for (var k=0;k<vCards.length;k++){
imageCardObj[k] = new Image();
imageCardObj[k].xxx=x;
var func = function(){
try{
context.drawImage(this, this.xxx, yStreet);
}catch(e){
alert(e.message)
}
}
imageCardObj[k].onload = func;
imageCardObj[k].src = 'res/img/'+vCards[k].trim()+".png";
x +=40;

drawImage is not working

var canvas = null;
var context = null;
var img = null;
var frames = [];
var assets = [];
var imgLoadCount = 0;
setup = function (fileArray) {
assets = fileArray;
//Loading an image
for(var i = 0; i < assets.length; i++) {
console.log(i);
frames.push(new Image()); // declare image object
frames[i].onload = onImageLoad(assets.length); //declare onload method
frames[i].src = URL.createObjectURL(assets[i]); //set url
}
};
onImageLoad = function (len) {
console.log("IMAGE!!!");
canvas = document.getElementById("my_canvas"); //creates a canvas element
context = canvas.getContext('2d');
canvas.width = window.innerWidth; //for full screen
canvas.height = window.innerHeight;
var x, y, numTilesRow;
numTilesRow = 10;
imgLoadCount++;
console.log("imgLoadCount = " + frames.length + ", length = " + len);
if(imgLoadCount != len) {
return;
}
for(var index = 0; index < len; index++) {
x = Math.floor((index % numTilesRow));
y = Math.floor(index / numTilesRow);
worldX = x * numTilesRow;
worldY = y * numTilesRow;
context.drawImage(frames[index], worldX, worldY);
}
};
I cant tell why drawImage has suddenly stopped working after inserting the code
if(imgLoadCount != len) {return;} that makes sure that all images are properly loaded. Would some one please help me find a solution to this problem.
You'll have to understand the difference between a function reference and a function call. The .onload property expects to be assigned with a function reference, but you assign it with the return value of the immediate(!) call to the function .onImageLoad. The difference is the parentheses ().
If you want to call a function with parameters as a callback to .onload, then you'd have to include it into an anonymous function (which itself is a function reference)
frames[i].onload = function() {onImageLoad(assets.length);};
With this, of course, you create a closure. This means that at the point of execution the onImageLoad() method will have access to the current value of assets.length (and not to the value at the point of assignment!). But in your case this doesn't make any difference, because assets.length never changes.

Categories

Resources