Javascript Image not rendered unless global - javascript

I'm trying to render an image from a sprite sheet using JS. The curious thing is, unless the object that does the rendering is global, it doesn't work (see code and comments). The behaviour is identical in both FF and Chrome.
resetGame() is executed on page load.
var TILE_SIZE = 24;
function CharacterImage(imageSource)
{
var tile_x = 0;
var tile_y = 0;
var img = new Image();
img.src = imageSource;
this.render = function(ctx, x, y)
{
ctx.drawImage(img, tile_x, tile_y, TILE_SIZE, TILE_SIZE,
x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
}
function Hero(canvas, image)
{
var ctx = canvas.getContext("2d");
var img = image;
this.render = function()
{
var x = 1;
var y = 1;
img.render(ctx, x, y);
}
}
// If the heroImage is constructed here, instead of within the function below,
// the image is rendered as expected.
var heroImage = new CharacterImage("img/sf2-characters.png");
function resetGame()
{
var heroCanvas = document.getElementById("heroLayer");
// On the otherhand, if the object is constructed here, instead of
// globally, the rendering doesn't work.
var heroImage = new CharacterImage("img/sf2-characters.png");
var hero = new Hero(heroCanvas, heroImage);
hero.render();
}

Oh hang on I think I see what's happening. The image needs time to load, so you should somehow bind an event to the loading of the image. This could be done as for example:
var TILE_SIZE = 24;
function CharacterImage(imageSource)
{
var tile_x = 0;
var tile_y = 0;
var img = new Image();
img.src = imageSource;
this.render = function(ctx, x, y)
{
ctx.drawImage(img, tile_x, tile_y, TILE_SIZE, TILE_SIZE,
x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
// Set up a "load" event for the img
this.loaded = function(callback) {
img.addEventListener('load', callback);
}
}
function resetGame()
{
var heroCanvas = document.getElementById("heroLayer");
var heroImage = new CharacterImage("img/sf2-characters.png");
var hero;
// Initiate the "load" event
heroImage.loaded(function() {
hero = new Hero(heroCanvas, heroImage);
hero.render();
};
}
What you'll probably want though is some sort of preloader "class"/event that keeps track of everything being loaded before you actually continue with rendering. It could look something like this.
var TILE_SIZE=60;
function Sprite(imageSource)
{
this.img = new Image();
this.img.src = imageSource;
this.position = { x:0, y:0 };
}
Sprite.prototype = {
isLoaded: function() {
return this.img.complete;
},
onLoad: function(callback) {
if (typeof callback !== "function") return;
if (this.isLoaded()) {
callback();
}
else {
this.img.removeEventListener('load', callback);
this.img.addEventListener('load', callback);
}
},
moveBy: function(x, y) {
this.position.x += x;
this.position.y += y;
},
render: function(ctx) {
if (!this.isLoaded()) return;
ctx.drawImage(this.img, this.position.x * TILE_SIZE, this.position.y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
};
function SpriteList()
{
this.list = {};
}
SpriteList.prototype = {
isLoaded: function() {
for (var i in this.list) {
if (!this.list[i].isLoaded()) {
return false;
}
}
return true;
},
_onLoadFunc: null,
onLoad: function(callback) {
this._onLoadFunc = callback;
this.onImageLoaded();
},
onImageLoaded: function() {
if (this.isLoaded() && typeof this._onLoadFunc === "function") {
this._onLoadFunc();
}
},
add: function(name, sprite) {
this.list[name] = sprite;
sprite.onLoad(this.onImageLoaded.bind(this));
},
get: function(name) {
return this.list[name];
}
};
var sprites = new SpriteList();
sprites.add("player", new Sprite("http://www.fillmurray.com/200/200"));
sprites.add("enemy", new Sprite("http://www.fillmurray.com/100/100"));
sprites.add("pickup", new Sprite("http://www.fillmurray.com/60/60"));
sprites.get("pickup").moveBy(1,2);
sprites.get("enemy").moveBy(2,0);
sprites.onLoad(function() {
document.getElementById("loading").innerHTML = "Loaded!";
var c = document.getElementById("ctx");
var ctx = c.getContext("2d");
sprites.get("player").render(ctx);
sprites.get("enemy").render(ctx);
sprites.get("pickup").render(ctx);
});
<div id="loading">Loading...</div>
<canvas id="ctx" width="200" height="200">
Anyways, that's why your code isn't firing, probably.

Related

Server side onchange event for custom class

If I create a custom class in Google Apps Script and assign it to a variable, can I create a server side onchange event that will react when the values change? For example, something like:
var Polygon = function(height, width) {
this.height = height;
this.width = width;
this.save = function() { <code to draw the polygon here ...> };
}
Polygon.onchange = function() {
currentPolygon = this.value;
currentPolygon.draw();
}
var myPolygon = new Polygon(10, 12);
myPolygon.height = 20; // triggers draw
Or, must it be included in a set function? For example:
var Polygon = function(height, width) {
var myHeight = height;
var myWidth = width;
this.height = function() { return myHeight; }
this.width = function() { return myWidth; }
this.draw = function() { <code to draw the polygon here ...> };
this.changeHeight = function(value) {
myHeight = value;
this.draw();
}
this.changeWidth = function(value) {
myWidth = value;
this.draw();
}
}
var myPolygon = new Polygon(10, 12);
myPolygon.changeHeight(20);
There is no such handler. But you can use a proxy to intercept all set calls:
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
const Polygon = function(height, width) {
this.height = height;
this.width = width;
this.drawn = 0;
this.draw = function() {
this.drawn += 1;
};
};
const PolygonOnchangeHandler = {
set(target, prop, value) {
Reflect.set(target, prop, value);//calls set
Reflect.apply(target.draw, target, []);//calls draw
},
};
const myPolygon = new Proxy(new Polygon(10, 12), PolygonOnchangeHandler);
myPolygon.height = 20; // trigges draw
console.log(myPolygon.drawn);//drawn once
myPolygon.width = 5;
console.log(myPolygon.drawn);//drawn twice
<!-- https://meta.stackoverflow.com/a/375985/ --> <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

Bouncing ball code causes jitter in HTML5 canvas

I have a HTML Canvas on my website that I use to contain balls that drop into the canvas, they bounce around and have a real good time settling down at the bottom in a range of ways.
I was under the impression this was working perfectly. However, it has now been brought to my attention these balls jitter and freak out on other people's computers. I checked the browser they are using and it is the same as mine (Chrome V79). So just to clarify - I have never had it bug on my computer but it consistently has this jitter on other peoples computers, some with more powerful computers as well as lower spec computers.
This is it (this CodePen doesn't have the jitter on their computers, so you may not see it also): https://codepen.io/jason-is-my-name/pen/gObKGRg
This is a video of the bug: https://streamable.com/vtg0g
$(document).ready(function () {
$.ajaxSetup({
cache: true
});
$.when(
$.getScript("https://code.createjs.com/easeljs-0.6.0.min.js"),
$.getScript("https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.min.js"),
$.Deferred(function (deferred) {
$(deferred.resolve);
})
).done(function () {
new App();
});
});
function App() {
var self = this;
self.running = false;
self.initialized = false;
var stageClicked = false;
var stage, canvas;
var canvasWidth = 410;
var canvasHeight = 550;
var ballBounce = 0.8;
var balls = [];
var matterJsBalls = [];
var _gravityY = 1;
var _gravityX = 0;
var FPS = 60;
var infoText, detailsText;
var ballsInitalized = false;
var iOS = navigator.userAgent.match(/(iPod|iPhone|iPad)/);
var startTime = (new Date()).getTime();
var engine = Matter.Engine.create();
var world = engine.world;
var floor = Matter.Bodies.rectangle(canvasWidth / 2, canvasHeight + 50, canvasWidth, 100, {
isStatic: true,
render: {
visible: false
}
});
var leftWall = Matter.Bodies.rectangle(-50, canvasHeight / 2, 100, canvasHeight, {
isStatic: true,
render: {
visible: false
}
});
var rightWall = Matter.Bodies.rectangle(canvasWidth + 50, canvasHeight / 2, 100, canvasHeight, {
isStatic: true,
render: {
visible: false
}
});
Matter.World.add(world, [floor, leftWall, rightWall]);
self.initialize = function () {
toggleListeners(true);
self.initCanvas();
self.initGame();
};
var toggleListeners = function (enable) {
if (!enable) return;
};
self.refresh = function () {};
self.initCanvas = function () {
canvas = $("#ball-stage").get(0);
stage = new createjs.Stage(canvas);
window.addEventListener("resize", onStageResize, false);
onStageResize();
createjs.Touch.enable(stage);
createjs.Ticker.addListener(tick);
createjs.Ticker.setFPS(FPS);
self.initialized = true;
};
self.initGame = function () {
initBalls(canvasWidth, canvasHeight);
};
var onStageResize = function () {
stage.canvas.width = canvasWidth;
stage.canvas.height = canvasHeight;
};
var initBalls = function (stageX, stageY) {
var imagesArray = [
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg"
];
for (var i = 0; i < imagesArray.length; i++) {
(function (imageArray) {
setTimeout(function () {
var arrayImage = new Image();
arrayImage.onload = function () {
addBall(arrayImage, stageX / 2, 52);
};
arrayImage.src = imageArray;
}, (i * 1000) + 1000);
})(imagesArray[i]);
}
};
var addBall = function (img, x, y) {
var shape = new createjs.Shape();
shape.id = balls.length;
shape.radius = 51.25;
shape.mass = shape.radius;
shape.x = x;
shape.y = y;
shape.vx = rand(-3, 3);
shape.vy = rand(-3, 3);
shape.stuck = false;
var transform = new createjs.Matrix2D();
transform.appendTransform(-shape.radius, -shape.radius, 1, 1, 0);
shape.graphics.beginBitmapFill(img, "repeat", transform).drawCircle(0, 0, shape.radius);
stage.addChild(shape);
balls.push(shape);
var circle = Matter.Bodies.circle(x, y, shape.radius, {
isStatic: false,
restitution: ballBounce
});
Matter.World.add(world, circle);
Matter.Body.applyForce(circle, {
x: circle.position.x,
y: circle.position.y
}, {
x: shape.vx * 0.05,
y: shape.vy * 0.05
});
matterJsBalls.push(circle);
};
var tick = function () {
Matter.Engine.update(engine, 16);
for (var i = 0; i < matterJsBalls.length; i++) {
var curBall = balls[i];
var curMatterJsBall = matterJsBalls[i];
curBall.x = curMatterJsBall.position.x;
curBall.y = curMatterJsBall.position.y;
}
stage.update();
};
var rand = function (min, max) {
return Math.random() * (max - min) + min;
return Math.random() * max + min;
};
self.initialize();
return self;
}
window.log = function f() {
log.history = log.history || [];
log.history.push(arguments);
if (this.console) {
var args = arguments,
newarr;
args.callee = args.callee.caller;
newarr = [].slice.call(args);
if (typeof console.log === "object")
log.apply.call(console.log, console, newarr);
else console.log.apply(console, newarr);
}
};
(function (a) {
function b() {}
for (
var c = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(
","
),
d; !!(d = c.pop());
) {
a[d] = a[d] || b;
}
})(
(function () {
try {
console.log();
return window.console;
} catch (a) {
return (window.console = {});
}
})()
);
.tech-stack-icons-container{width:410px;height:100%}#ball-stage{width:100%;height:100%;background-color:pink;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="tech-stack-icons-container">
<canvas id="ball-stage"></canvas>
</div>
How can I find a fix to this invisible issue?

Draw an image on a Canvas from an Object property

I want to draw an image on the canvas at each iteration, an image coming from an object.
Either I get "not the format HTML..." in the console, or nothing and it blocks the loop.
Is there a way to draw an image on a canvas without putting it first on the index.html, or without loading from an URL?
I tried the two standard solutions and they didn't work.
I didn't find a similar problem using an object property containing the image, to draw it on a canvas.
function Board(width, height) {
this.width = width;
this.height = height;
this.chartBoard = [];
// Création du plateau logique
for (var i = 0; i < this.width; i++) {
const row = [];
this.chartBoard.push(row);
for (var j = 0; j < this.height; j++) {
const col = {};
row.push(col);
}
}
}
let board = new Board(10, 10);
console.log(board);
// CONTEXT OF THE CANVAS
const ctx = $('#board').get(0).getContext('2d');
Board.prototype.drawBoard = function () {
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.strokeRect(j * 64, i * 64, 64, 64);
ctx.closePath();
}
}
};
board.drawBoard();
Board.prototype.test = test;
function test() {
console.log(this);
}
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
const lava = new Obstacle("Lave", "assets/lave.png");
const lava1 = new Obstacle("Lave1", "assets/lave.png");
const lava2 = new Obstacle("Lave2", "assets/lave.png");
const lava3 = new Obstacle("Lave3", "assets/lave.png");
const lava4 = new Obstacle("Lave4", "assets/lave.png");
const lava5 = new Obstacle("Lave5", "assets/lave.png");
const lava6 = new Obstacle("Lave6", "assets/lave.png");
const lava7 = new Obstacle("Lave7", "assets/lave.png");
const lava8 = new Obstacle("Lave8", "assets/lave.png");
const lava9 = new Obstacle("Lave9", "assets/lave.png");
const lavaArray = [lava, lava1, lava2, lava3, lava4, lava5, lava6, lava7, lava8, lava9];
// FUNCTION TO DRAW
Board.prototype.setPiece = function (piece) {
let randomX = Math.floor(Math.random() * board.width);
let randomY = Math.floor(Math.random() * board.height);
let drawX = randomX * 64;
let drawY = randomY * 64;
if (randomX >= this.width || randomY >= this.height) {
throw new Error('Pièce hors limite');
}
if (piece instanceof Obstacle) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle)) {
this.chartBoard[randomY][randomX] = piece;
// CODE TO DRAW, BUG DOESN'T WORK
ctx.fillRect(drawX, drawY,64,64);
let image = Obstacle.sprite;
ctx.drawImage = (image, drawX, drawY);
}
}
} else {
throw new Error('Pièce non valide');
}
};
Board.prototype.setObstacles = function () {
for (let lava of lavaArray) {
const obstacle = board.setPiece(lava);
}
};
board.setObstacles();
Actual: No image is drawn. And if I try fillRect, it works well. So the loop works.
Expected: Be able to draw an image on a canvas from an object property.
It's not really clear what you're trying to do.
The code you have commented
ctx.drawImage = (image, drawX, drawY);
should be this
ctx.drawImage(image, drawX, drawY);
Looking a little broader you have this
let image = Obstacle.sprite;
ctx.drawImage(image, drawX, drawY); // assume this was fixed
But Obstacle is a class, not an instance of that class. You want
let image = piece.sprite;
ctx.drawImage(image, drawX, drawY);
But that leads the next issue. looking at the rest of the code piece.sprite is a string not an image. See this code.
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
const lava = new Obstacle("Lave", "assets/lave.png");
There are a several ways you can draw images to a canvas. If they come from a file you do have to wait for them to download. Otherwise you can generate them from another canvas. You can also use createImageData and putImageData as another way to make images.
Let's change the code to load a bunch of images and then start
I moved all the class code to the top and the start up code to the bottom.
There were a few places inside Board methods where the global variable board was used instead of this so I fixed those.
Here`s a function that loads an image and returns a Promise
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => { resolve(img); };
img.onerror = reject;
img.crossOrigin = 'anonymous'; // REMOVE IF SAME DOMAIN!
img.src = url;
});
}
You could use that to load one image like this
loadImage(urlOfImage).then(function(img) {
// use the loaded img
});
I used that function to write this function that takes an object of names to urls and returns an object of names to loaded images.
function loadImages(images) {
return new Promise((resolve) => {
const loadedImages = {};
const imagePromises = Object.entries(images).map((keyValue) => {
const [name, url] = keyValue;
return loadImage(url).then((img) => {
loadedImages[name] = img;
});
});
Promise.all(imagePromises).then(() => {
resolve(loadedImages);
});
});
}
I then call that and pass the object of names to loaded images to the start function. Only one image is loaded at the moment but you can add more.
const images = {
lave: 'https://i.imgur.com/enx5Xc8.png',
// player: 'foo/player.png',
// enemy: 'foo/enemny.png',
};
loadImages(images).then(start);
// CONTEXT OF THE CANVAS
const ctx = $('#board').get(0).getContext('2d');
function Board(width, height) {
this.width = width;
this.height = height;
this.chartBoard = [];
// Création du plateau logique
for (var i = 0; i < this.width; i++) {
const row = [];
this.chartBoard.push(row);
for (var j = 0; j < this.height; j++) {
const col = {};
row.push(col);
}
}
}
Board.prototype.drawBoard = function () {
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.strokeRect(j * 64, i * 64, 64, 64);
ctx.closePath();
}
}
};
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
// FUNCTION TO DRAW
Board.prototype.setPiece = function (piece) {
let randomX = Math.floor(Math.random() * this.width);
let randomY = Math.floor(Math.random() * this.height);
let drawX = randomX * 64;
let drawY = randomY * 64;
if (randomX >= this.width || randomY >= this.height) {
throw new Error('Pièce hors limite');
}
if (piece instanceof Obstacle) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle)) {
this.chartBoard[randomY][randomX] = piece;
// CODE TO DRAW, BUG DOESN'T WORK
ctx.fillRect(drawX, drawY,64,64);
let image = piece.sprite;
ctx.drawImage(image, drawX, drawY);
}
} else {
throw new Error('Pièce non valide');
}
};
Board.prototype.setObstacles = function (lavaArray) {
for (let lava of lavaArray) {
const obstacle = this.setPiece(lava);
}
};
function start(images) {
let board = new Board(10, 10);
// console.log(board);
const lava = new Obstacle("Lave", images.lave);
const lava1 = new Obstacle("Lave1", images.lave);
const lava2 = new Obstacle("Lave2", images.lave);
const lava3 = new Obstacle("Lave3", images.lave);
const lava4 = new Obstacle("Lave4", images.lave);
const lava5 = new Obstacle("Lave5", images.lave);
const lava6 = new Obstacle("Lave6", images.lave);
const lava7 = new Obstacle("Lave7", images.lave);
const lava8 = new Obstacle("Lave8", images.lave);
const lava9 = new Obstacle("Lave9", images.lave);
const lavaArray = [lava, lava1, lava2, lava3, lava4, lava5, lava6, lava7, lava8, lava9];
board.drawBoard();
board.setObstacles(lavaArray);
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => { resolve(img); };
img.onerror = reject;
img.crossOrigin = 'anonymous'; // REMOVE IF SAME DOMAIN!
img.src = url;
});
}
function loadImages(images) {
return new Promise((resolve) => {
const loadedImages = {};
// for each name/url pair in image make a promise to load the image
// by calling loadImage
const imagePromises = Object.entries(images).map((keyValue) => {
const [name, url] = keyValue;
// load the image and when it's finished loading add the name/image
// pair to loadedImages
return loadImage(url).then((img) => {
loadedImages[name] = img;
});
});
// wait for all the images to load then pass the name/image object
Promise.all(imagePromises).then(() => {
resolve(loadedImages);
});
});
}
const images = {
lave: 'https://i.imgur.com/enx5Xc8.png',
// player: 'foo/player.png',
// enemy: 'foo/enemny.png',
};
loadImages(images).then(start);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="board" width="640" height="640"></canvas>

My height is defined but is shown as undefined

So I'm making a game with canvas and my object for one of the characters aren't showing up. My console didn't show any errors so I just decided to check my asteroidList object. I checked it and saw that the first character in the object's height was undefined, but I already defined it. Can someone tell me what the problem is? Here is my code:
var c = document.getElementById("game");
var ctx = c.getContext("2d");
//variables
pX = 1;
pY = 40;
pW = 54.6;
pH = 52.6;
hw = 100;
hh = 10;
asteroidSpeed = 0.05;
//load image sprites
var player = new Image();
var aster = new Image();
var enemy = new Image();
var max = new Image();
var animatedPlayer = new Image();
player.src = "player.png";
aster.src = "aster.png";
enemy.src = "enemy.png";
max.src = "max.png";
animatedPlayer.src = "animatedPlayer.png";
//keys
document.addEventListener("keydown",function (e) {
if (e.keyCode === 83) {
moveDown();
}
else if(e.keyCode === 87) {
moveUp();
}
})
function moveDown() {
pY += 2;
}
function moveUp() {
pY -= 2;
}
//asteroid constructor
asteroidList = {};
function asteroid(id,x,y,img,width,height) {
var asteroid = {
x:x,
y:y,
width:width,
height:height,
id:id
};
asteroidList['A1'] = asteroid;
}
function updateAsteroid(asteroid) {
asteroid.x -= asteroidSpeed;
ctx.drawImage(aster, asteroid.x, asteroid.y, asteroid.width, asteroid.height);
}
function draw() {
ctx.clearRect(0,0,c.width,c.height);
//characters
asteroid('A1', 250, 40, 29.6, 29.3);
ctx.drawImage(player,pX,pY,pW,pH);
setInterval(update, 40);
function update() {
//map collision
if(pY < 0) {
pY = 0;
}
if(pY > 100) {
pY = 100;
}
//enemy loop
for(var key in asteroidList) {
updateAsteroid(asteroidList[key]);
}
}
//hp
ctx.fillStyle = 'green';
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.fillRect(199,139,hw,hh);
ctx.strokeRect(199,139,100,10);
//animation call
requestAnimationFrame(draw);
}
</script>
You have an extra param called img in your asteroid function definition:
function asteroid(id, x, y, img, width, height) {
var asteroid = {
x:x,
y:y,
width:width,
height:height,
id:id
};
asteroidList['A1'] = asteroid;
}
So when you call it like:
asteroid('A1', 250, 40, 29.6, 29.3);
// asteroid(id, x, y, img, width);
you are missing to set up the height value and thus getting undefined.

Prototypal Inheritance in Javascript Working with Basic but not Complex Program

What is confusing is how this simple script works fine:
function A() {
this.value = 0;
}
A.prototype.foo = function() {
console.log(this.value);
};
function B() {
this.value = 1;
this.foo();
}
B.prototype = Object.create(A.prototype);
B.prototype.bar = function() {
console.log(this instanceof A);
}
new B().bar();
// outputs 1, true
However, this larger script gives an error this.loadDimensions is not a function:
Basically, there is a Player class, which inherits from a MovingComponent class, which inherits from a VisibleComponent class. They all have methods attached to them.
const PX_SZ = 4, MAX_HEIGHT = 100, MIN_HEIGHT = 300;
var resources = {};
resources.sprites = {};
resources.sprites.player = new Image();
resources.sprites.player.src = "resources/sprites/player.png";
resources.sprites['default'] = new Image();
resources.sprites['default'].src = "resources/sprites/default.png";
resources.sprites.items = {};
resources.sprites.backgroundEntities = {};
var itemsTemp = ['default', 'coin0'];
for (var i=0; i<itemsTemp.length; i++) {
var item = itemsTemp[i];
resources.sprites.items[item] = new Image();
resources.sprites.items[item].src = "resources/sprites/items/" + item + ".png";
}
var backgroundEntitiesTemp = ['tree0'];
for (var i=0; i<backgroundEntitiesTemp.length; i++) {
var ent = backgroundEntitiesTemp[i];
resources.sprites.backgroundEntities[ent] = new Image();
resources.sprites.backgroundEntities[ent].src = "resources/sprites/background-entities/" + ent + ".png";
}
var canvas, ctx;
var player = new Player();
var keys = {};
var game = new Game(Math.floor(Math.random()*1000000));
var world = new World();
/** #class */
function Game(seed) {
this.seed = seed;
}
/** #class */
function World() {
this.gravity = 0.4;
this.chances = {
items: {
coin0: 0.005
},
backgroundEntities: {
tree0: 0.05
}
};
this.itemsFloating = [];
this.backgroundEntities = [];
// for spawning
this.exploredRightBound = 0;
this.exploredLeftBound = 0;
}
World.prototype.generate = function(left, right) {
if (left >= right) throw "left >= right in World#generate(left,right)";
for (x = left; x < right; x += PX_SZ) {
// world generation code here
// coin0
var level = getGroundHeightAt(x)
if (Math.random() <= this.chances.items.coin0) {
var item = new ItemFloating("coin0", x, level-20);
this.itemsFloating.push(item);
}
if (Math.random() <= this.chances.backgroundEntities.tree0) {
var ent = new BackgroundEntity("tree0", x, level-resources.sprites.backgroundEntities.tree0.height);
this.backgroundEntities.push(ent);
}
}
};
/**
* #class
* anything that has a sprite attached to it
*/
function VisibleComponent() {
this.sprite = resources.sprites['default'];
}
VisibleComponent.prototype.loadDimensions = function() {
console.log('load');
};
VisibleComponent.prototype.draw = function() {
ctx.drawImage(this.sprite, this.x, this.y, this.width, this.height);
};
/** #class */
function Item(name="default") {
VisibleComponent.call(this);
this.name = name || "default";
this.sprite = resources.sprites.items[name];
this.loadDimensions();
}
Item.prototype = Object.create(VisibleComponent.prototype);
/** #class */
function ItemFloating(name, x, y) {
Item.call(this, name);
this.name = name;
this.x = x;
this.y = y;
this.loadDimensions(); // (when ready of now)
}
ItemFloating.prototype = Object.create(Item.prototype);
/** #class */
function BackgroundEntity(name="default", x=0, y=0) {
VisibleComponent.call(this);
this.name = name;
this.x = x;
this.y = y;
this.width = 1;
this.height = 1;
this.sprite = resources.sprites.backgroundEntities[this.name];
this.loadDimensions();
}
BackgroundEntity.prototype = Object.create(VisibleComponent.prototype);
/** #class */
function MovingEntity(x=0, y=0) {
VisibleComponent.call(this);
this.x = x;
this.y = y;
this.width = 1;
this.height = 1;
}
MovingEntity.prototype = Object.create(VisibleComponent.prototype);
MovingEntity.prototype.collisionWith = function(ent) {
return ((this.x>=ent.x&&this.x<=ent.x+ent.width) || (ent.x>=this.x&&ent.x<=this.x+this.width))
&& ((this.y>=ent.y&&this.y<=ent.y+ent.height) || (ent.y>=this.y&&ent.y<=this.y+this.height));
};
/** #class */
function Player() {
MovingEntity.call(this);
this.inventory = {};
console.log(this instanceof VisibleComponent);
this.speed = 4;
this.jumpSpeed = 8;
this.vspeed = 0;
this.sprite = resources.sprites.player;
this.loadDimensions();
this.direction = "right";
}
Player.prototype = Object.create(MovingEntity.prototype);
Player.prototype.draw = function() {
ctx.save();
ctx.translate(this.x, this.y);
if (this.direction == "left") ctx.scale(-1, 1); // flip over y-axis
ctx.translate(-this.sprite.width, 0);
ctx.drawImage(this.sprite, 0, 0, this.width, this.height);
ctx.restore();
}
Player.prototype.move = function() {
if (keys['ArrowLeft']) {
this.x -= this.speed;
this.direction = "left";
var leftEdge = this.x-canvas.width/2-this.width/2;
if (leftEdge < world.exploredLeftBound) {
world.generate(leftEdge, world.exploredLeftBound);
world.exploredLeftBound = leftEdge;
}
}
if (keys['ArrowRight']) {
this.x += this.speed;
this.direction = "right";
var rightEdge = this.x+canvas.width/2+this.width/2;
if (rightEdge > world.exploredRightBound) {
world.generate(world.exploredRightBound, rightEdge);
world.exploredRightBound = rightEdge;
}
}
var level = getGroundHeightAt(this.x+this.width/2);
if (this.y + this.height < level) {
this.vspeed -= world.gravity;
} else if (this.y + this.height > level) {
this.y = level - this.height;
this.vspeed = 0;
}
if (keys[' '] && this.y+this.height == getGroundHeightAt(this.x+this.width/2)) this.vspeed += this.jumpSpeed;
this.y -= this.vspeed;
for (var i=0; i<world.itemsFloating.length; i++) {
var item = world.itemsFloating[i];
if (this.collisionWith(item)) {
if (this.inventory.hasOwnProperty(item.name)) this.inventory[item.name]++;
else this.inventory[item.name] = 1;
world.itemsFloating.splice(i, 1);
}
}
};
I'm fairly new to javascript inheritance, so I don't understand what I'm doing wrong. Also, since the first script worked, I figured there's something I'm just overlooking in my second script. Any help would be appreciated.
EDIT
In the beginning of the file, I declare player as a new Player(). resources contains Image instances that point to various image files. ctx and canvas are pretty self-explanatory globals.
Also, player isn't recognized as an instance of MovingEntity or VisibleComponent, even though Player's prototype is set to Object.create(MovingEntity.prototype), which has its prototype set to Object.create(VisibleComponent.prototype).
One other thing to mention is that in the definition of loadDimensions() in VisibleComponent, either the onload property of this.sprite is set to a function, or addEventListener() is called for 'load', depending on whether this.sprite has loaded (width != 0) or not.
In the beginning of the file, I declare player as a new Player().
That's the problem, you need to call the constructor after having set up your class. It currently doesn't throw an error about Player not being a function because the declaration is hoisted, but the prototype is not yet initialised with the value you expect so it indeed does not have a .loadDimensions() method yet.

Categories