I'm trying to create a way to store and load maps for an HTML5 canvas game. In order to do so, I must create new instances of objects (functions) referenced by a variable. This is my code.
function Map(w, h) {
if (typeof w == "undefined")
this.width = 640;
else
this.width = w;
if (typeof h == "undefined")
this.height = 480;
else
this.height = h;
this.obj = new Array();
this.x = new Array();
this.y = new Array();
this.backgroundColor = "#C0C0C0";
}
Map.prototype.addObject = function(cl, xx, yy) {
this.obj.push(cl);
this.x.push(xx);
this.y.push(yy);
}
function newInstance(o, x, y) {
this.tmp = new o(); //This is what doesn't work.
tmp.x = x;
tmp.y = y;
objects.push(tmp);
tmp.create();
}
Map.prototype.load = function() {
for (var i = 0; i < this.obj.length; i++) {
newInstance(this.obj[i], this.x[i], this.y[i]);
}
}
I want it to be used as such:
//When I create the map.
var mMain = new Map();
//This cannot be new Player() and new Block() because I don't want to load the map yet. I'm just creating the map.
mMain.addObject(Player, 320, 240);
mMain.addObject(Block, 0, 256);
mMain.addObject(Block, 32, 256);
//etc...
//When I load the map.
mMain.load();
Just an example of how the Player and Block objects work.
function Player() {
this.x = 0;
this.y = 0;
//Other variables irrelevant to this problem.
}
//Block object code
I figured out the problem. I had forgotten to check all of my code before posting, so I gave false information. Instead of "function Player()", I had "var Player = ObjectResource;" because I wanted it to extend the ObjectResource function. I just had to change it to what I had posted.
Related
I'm a Ruby developer who finally decided to learn JavaScript seriously. So I purchased some books and I started to dive in, but I got stuck quickly when I tried to understand prototypal inheritance...
One of the examples of the book is the following.
Given a Shape which prototype has a draw method, and two child shapes: a Triangle and a Rectangle which prototype inherit from Shape;
when I call the draw function on Triangle and Rectangle instances the method will draw them properly.
when I add a second method to show their name, every instance will log it properly.
Everything was understandable perfectly until I added a third method to fill the shapes... And only the last one get filled. no matter which one I call. Why? Is there something special in canvas?
Here is the code of the exercise:
function Point(x, y) {
this.x = x;
this.y = y;
}
function Shape() {
this.points = [];
this.init();
}
Shape.prototype = {
constructor: Shape,
init: function() {
if (this.context === undefined) {
Shape.prototype.context = document.getElementById('canvas').getContext('2d');
};
if (this.name === undefined) {
Shape.prototype.name = 'generic shape'
}
},
draw: function() {
var i, ctx = this.context;
ctx.strokeStyle = 'rgb(0,0,255)';
ctx.beginPath();
ctx.moveTo(this.points[0].x, this.points[0].y);
for (i = 1; i < this.points.length; i++) {
ctx.lineTo(this.points[i].x, this.points[i].y);
}
ctx.closePath();
ctx.stroke();
},
fill: function(color) {
var ctx = this.context;
ctx.fillStyle = color;
ctx.fill();
},
say_name: function() {
console.log('Hello my name is ' + this.name)
}
};
function Triangle(a, b, c) {
this.points = [a, b, c];
this.name = 'Triangle'
this.context = document.getElementById('canvas').getContext('2d');
}
function Rectangle(side_a, side_b) {
var p = new Point(200, 200);
this.points = [
p,
new Point(p.x + side_a, p.y), // top right
new Point(p.x + side_a, p.y + side_b), // bottom right
new Point(p.x, p.y + side_b) // bottom left
];
this.name = 'Rectangle'
this.context = document.getElementById('canvas').getContext('2d');
}
(function() {
var s = new Shape();
Triangle.prototype = s;
Rectangle.prototype = s;
})();
function testTriangle() {
var p1 = new Point(100, 100);
var p2 = new Point(300, 100);
var p3 = new Point(200, 0);
return new Triangle(p1, p2, p3);
}
function testRectangle() {
return new Rectangle(100, 100);
}
function make_me_crazy() {
var t = testTriangle();
var r = testRectangle();
t.draw();
r.draw();
t.say_name();
r.say_name();
t.fill('red');
}
make_me_crazy();
<canvas height='600' width='800' id='canvas' />
Thank you!
More details:
Why the function say_name is working exactly I expect saying: 'I am a triangle' or 'I am a rectangle' and never 'I am a generic shape', but the fill function fills the rectangle despite I'm calling it on a triangle instance? As people rightly answered to flip the two draw functions calls, I would specify better the following. The problem is not about the color of a shape, but the context pointer. why only the last shape is filled? If I add more shapes before calling fill only the last one get filled. This means I'm doing something wrong referring to the canvas. I supposed it was "the place where I draw shapes" but it seems more like "the last active shape"
How can I fix that code to make it working correctly filling the shape I want whenever I want? I mean. what if I want to have a function which receive an instance of a particular shape and fills it?
Is there any way to access a the draws contained into a canvas?
The core of the problem is the context - your shapes are sharing the single context of the canvas, and therefore it is not straight-forward to flip back and forth between objects. Instead, think of your order-of-operations as handling a single shape at a time and only moving on to the next one when you are done with the former.
Note the order of calls in the make_me_crazy function:
function Point(x, y) {
this.x = x;
this.y = y;
}
function Shape() {
this.points = [];
this.init();
}
Shape.prototype = {
constructor: Shape,
init: function(){
if (this.context === undefined) {
Shape.prototype.context = document.getElementById('canvas').getContext('2d');
};
if(this.name === undefined){
Shape.prototype.name = 'generic shape'
}
},
draw: function(){
var i, ctx = this.context;
ctx.strokeStyle = 'rgb(0,0,255)';
ctx.beginPath();
ctx.moveTo(this.points[0].x, this.points[0].y);
for (i = 1; i<this.points.length; i++) {
ctx.lineTo(this.points[i].x, this.points[i].y);
}
ctx.closePath();
ctx.stroke();
},
fill: function(color){
var ctx = this.context;
ctx.fillStyle = color;
ctx.fill();
},
say_name: function(){console.log('Hello my name is '+ this.name)}
};
function Triangle(a,b,c){
this.points = [a, b, c];
this.name = 'Triangle'
this.context = document.getElementById('canvas').getContext('2d');
}
function Rectangle(side_a, side_b){
var p = new Point(200, 200);
this.points = [
p,
new Point(p.x + side_a, p.y),// top right
new Point(p.x + side_a, p.y + side_b), // bottom right
new Point(p.x, p.y + side_b)// bottom left
];
this.name = 'Rectangle'
this.context = document.getElementById('canvas').getContext('2d');
}
(function(){
var s = new Shape();
Triangle.prototype = s;
Rectangle.prototype = s;
})();
function testTriangle(){
var p1 = new Point(100, 100);
var p2 = new Point(300, 100);
var p3 = new Point(200, 0);
return new Triangle(p1, p2, p3);
}
function testRectangle(){
return new Rectangle(100, 100);
}
function make_me_crazy(){
var t = testTriangle();
t.say_name();
t.draw();
t.fill('red');
var r = testRectangle();
r.draw();
r.say_name();
}
make_me_crazy();
<canvas height='600' width='800' id='canvas'></canvas>
About the points of your question.
For the first one: the key is this line of code
if(this.name === undefined){
Shape.prototype.name = 'generic shape'
}
When you instantiate Rectangle and Triangle, both of them set name.
In the other hand, the render method is only available in the Shape prototype.
About the second point (and the third one):
Maybe are you painting the Rectangle over the Triangle. Try to switch the order of the draw calls to check it.
So I'm making my "Sprite" class, and right now it works properly when it's laid out like this (alot of this is unnecessary, but might help you understand):
function Entity(tname)
{
if (typeof (tname) === 'undefined') tname = "Entity";
this.tname = tname;
}
Entity.prototype.confirmType = function(tname)
{
if (this.tname === tname) return true;
else return false;
}
Entity.prototype.constructor = Entity;
function Sprite(tname, x, y, src)
{
this.parent.constructor.call(this, tname);
this.x = x;
this.y = y;
this.img = new Image();
this.img.src = src;
this.render = function()
{
ctx.drawImage(this.img, this.x, this.y);
}
}
Sprite.prototype = Object.create(Entity.prototype);
Sprite.prototype.constructor = Sprite;
Sprite.prototype.parent = Entity.prototype;
var sprite = new Sprite("Lucario", 400, 400, "img/slot.png");
var update = function()
{
sprite.render();
}
But what I want to do is make Sprite's render function just like Entity's confirmType function, outside the constructor.
What I want to do is this:
function Sprite(tname, x, y, src)
{
...
}
Sprite.prototype.render = function()
{
ctx.drawImage(this.img, this.x, this.y);
}
Not:
function Sprite(tname, x, y, src)
{
...
this.render = function()
{
ctx.drawImage(this.img, this.x, this.y);
}
}
Basically, I want to add functions to subclasses, not just override preexisting ones. Can someone help me?
If I understand your issue, it may be purely an issue of the order of your Javascript statements. You don't show the whole sequence of code, but when you do this:
Sprite.prototype = Object.create(Entity.prototype);
That replaces the entire prototype on the Sprite object so if you had previously put any methods on the prototype, they would be wiped out by this assignment. If you then want to add more methods to the Sprite prototype, just add them after you do that (not before):
Sprite.prototype = Object.create(Entity.prototype);
Sprite.prototype.render = function() {
ctx.drawImage(this.img, this.x, this.y);
}
If you did them in the other order, it would not work:
Sprite.prototype.render = function() {
ctx.drawImage(this.img, this.x, this.y);
}
// replaces the entire prototype object, wiping out any methods that were on it
Sprite.prototype = Object.create(Entity.prototype);
I've spent about 12 hours looking through this code, and fiddling with it, trying to find out where there's a recursion problem because I'm getting the, "maximum call stack size exceeded," error, and haven't found it. Someone smarter than me please help me!
so far, all I found was that when I make the object, spot, a circle, object, the problem disappears, but when I make it a, 'pip', I get this stack overflow error. I've gone over the pip class with a friggin' microscope, and still have no idea why this is happening!
var canvas = document.getElementById('myCanvas');
//-------------------------------------------------------------------------------------
// Classes
//-------------------------------------------------------------------------------------
//=====================================================================================
//CLASS - point
function point(x,y){
this.x = x;
this.y = y;
}
//=====================================================================================
// CLASS - drawableItem
function drawableItem() {
var size = 0;
this.center = new point(0,0);
this.lineWidth = 1;
this.dependentDrawableItems = new Array();
}
//returns the size
drawableItem.prototype.getSize = function getSize(){
return this.size;
}
// changes the size of this item and the relative size of all dependents
drawableItem.prototype.changeSize = function(newSize){
var relativeItemSizes = new Array;
relativeItemSizes.length = this.dependentDrawableItems.length;
// get the relative size of all dependent items
for (var i = 0; i < this.dependentDrawableItems.length; i++){
relativeItemSizes[i] = this.dependentDrawableItems[i].getSize() / this.size;
}
// change the size
this.size = newSize;
// apply the ratio of change back to all dependent items
for (var i = 0; i < relativeItemSizes.length; i++){
this.dependentDrawableItems[i].changeSize(relativeItemSizes[i] * newSize);
}
}
//moves all the vertices and every dependent to an absolute point based on center
drawableItem.prototype.moveTo = function(moveX,moveY){
//record relative coordinates
var relativeItems = new Array;
relativeItems.length = this.dependentDrawableItems.length;
for (var i = 0; i < relativeItems.length; i++){
relativeItems[i] = new point;
relativeItems[i].x = this.dependentDrawableItems[i].center.x - this.center.x;
relativeItems[i].y = this.dependentDrawableItems[i].center.y - this.center.y;
}
//move the center
this.center.x = moveX;
this.center.y = moveY;
//move all the items relative to the center
for (var i = 0; i < relativeItems.length; i++){
this.dependentDrawableItems[i].moveItemTo(this.center.x + relativeItems[i].x,
this.center.y + relativeItems[i].y);
}
}
// draws every object in dependentDrawableItems
drawableItem.prototype.draw = function(ctx){
for (var i = 0; i < this.dependentDrawableItems.length; i++) {
this.dependentDrawableItems[i].draw(ctx);
}
}
//=====================================================================================
//CLASS - circle
function circle(isFilledCircle){
drawableItem.call(this);
this.isFilled = isFilledCircle
}
circle.prototype = new drawableItem();
circle.prototype.parent = drawableItem.prototype;
circle.prototype.constructor = circle;
circle.prototype.draw = function(ctx){
ctx.moveTo(this.center.x,this.center.y);
ctx.beginPath();
ctx.arc(this.center.x, this.center.y, this.size, 0, 2*Math.PI);
ctx.closePath();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.outlineColor;
if (this.isFilled === true){
ctx.fill();
}else {
ctx.stroke();
}
this.parent.draw.call(this,ctx);
}
//=====================================================================================
//CLASS - pip
function pip(size){
circle.call(this,true);
}
pip.prototype = new circle(false);
pip.prototype.parent = circle.prototype;
pip.prototype.constructor = pip;
//----------------------------------------------------------------------
// Objects/variables - top layer is last (except drawable area is first)
//----------------------------------------------------------------------
var drawableArea = new drawableItem();
var spot = new pip();
spot.changeSize(20);
drawableArea.dependentDrawableItems[drawableArea.dependentDrawableItems.length] = spot;
//------------------------------------------
// Draw loop
//------------------------------------------
function drawScreen() {
var context = canvas.getContext('2d');
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
spot.moveTo(context.canvas.width/2, context.canvas.height/2);
drawableArea.draw(context);
}
window.addEventListener('resize', drawScreen);
Here's the demo: http://jsfiddle.net/DSU8w/
this.parent.draw.call(this,ctx);
is your problem. On a pip object, the parent will be circle.prototype. So when you now call spot.draw(), it will call spot.parent.draw.call(spot), where this.parent is still the circle.prototype…
You will need to explicitly invoke drawableItem.prototype.draw.call(this) from circle.prototype.draw. Btw, you should not use new for the prototype chain.
Why would you write code like that? It's so difficult to understand and debug. When I'm creating lots of classes I usually use augment to structure my code. This is how I would rewrite your code:
var Point = Object.augment(function () {
this.constructor = function (x, y) {
this.x = x;
this.y = y;
};
});
Using augment you can create classes cleanly. For example your drawableItem class could be restructured as follows:
var DrawableItem = Object.augment(function () {
this.constructor = function () {
this.size = 0;
this.lineWidth = 1;
this.dependencies = [];
this.center = new Point(0, 0);
};
this.changeSize = function (toSize) {
var fromSize = this.size;
var ratio = toSize / fromSize;
this.size = toSize;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
dependency.changeSize(dependency.size * ratio);
}
};
this.moveTo = function (x, y) {
var center = this.center;
var dx = x - center.x;
var dy = y - center.y;
center.x = x;
center.y = y;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
var center = dependency.center;
dependency.moveTo(center.x + dx, center.y + dy);
}
};
this.draw = function (context) {
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) dependencies[index++].draw(context);
};
});
Inheritance is also very simple. For example you can restructure your circle and pip classes as follows:
var Circle = DrawableItem.augment(function (base) {
this.constructor = function (filled) {
base.constructor.call(this);
this.filled = filled;
};
this.draw = function (context) {
var center = this.center;
var x = center.x;
var y = center.y;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, this.size, 0, 2 * Math.PI);
context.closePath();
context.lineWidth = this.lineWidth;
context[this.filled ? "fill" : "stroke"]();
base.draw.call(this, context);
};
});
var Pip = Circle.augment(function (base) {
this.constructor = function () {
base.constructor.call(this, true);
};
});
Now that you've created all your classes you can finally get down to the drawing:
window.addEventListener("DOMContentLoaded", function () {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var drawableArea = new DrawableItem;
var spot = new Pip;
spot.changeSize(20);
drawableArea.dependencies.push(spot);
window.addEventListener("resize", drawScreen, false);
drawScreen();
function drawScreen() {
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
spot.moveTo(width / 2, height / 2);
drawableArea.draw(context);
}
}, false);
We're done. See the demo for yourself: http://jsfiddle.net/b5vNk/
Not only have we made your code more readable, understandable and maintainable but we have also solved your recursion problem.
As Bergi mentioned the problem was with the statement this.parent.draw.call(this,ctx) in the circle.prototype.draw function. Since spot.parent is circle.prototype the this.parent.draw.call(this,ctx) statement is equivalent to circle.prototype.draw.call(this,ctx). As you can see the circle.prototype.draw function now calls itself recursively until it exceeds the maximum recursion depth and throws an error.
The augment library solves this problem elegantly. Instead of having to create a parent property on every prototype when you augment a class augment provides you the prototype of that class as a argument (we call it base):
var DerivedClass = BaseClass.augment(function (base) {
console.log(base === BaseClass.prototype); // true
});
The base argument should be treated as a constant. Because it's a constant base.draw.call(this, context) in the Circle class above will always be equivalent to DrawableItem.prototype.draw.call(this, context). Hence you will never have unwanted recursion. Unlike this.parent the base argument will alway point to the correct prototype.
Bergi's answer is correct, if you don't want to hard code the parent name multiple times you could use a helper function to set up inheritance:
function inherits(Child,Parent){
Child.prototype=Object.create(Parent.prototype);
Child.parent=Parent.prototype;
Child.prototype.constructor=Child;
};
function DrawableItem() {
this.name="DrawableItem";
}
DrawableItem.prototype.changeSize = function(newSize){
console.log("changeSize from DrawableItem");
console.log("invoking object is:",this.name);
}
function Circle(isFilledCircle){
Circle.parent.constructor.call(this);
this.name="Circle";//override name
}
inherits(Circle,DrawableItem);
Circle.prototype.changeSize = function(newSize){
Circle.parent.changeSize.call(this);
console.log("and some more from circle");
};
function Pip(size){
Pip.parent.constructor.call(this,true);
this.name="Pip";
}
inherits(Pip,Circle);
var spot = new Pip();
spot.changeSize();
For a polyfill on Object.create look here.
I want to create a few instance of this class
var fruit = {
texture: new Image(),
speed: 5,
x: 0,
y: 0,
};
function fruits(speed, x, y)
{
fruit.speed = speed;
fruit.x = x;
fruit.y = y;
return fruit;
};
but when i create new object the all value was overridet by last created object. How can i repair this?
My loop:
var apples = [];
for(var i = 0; i < 10; i++)
{
apples[i] = new fruits(5, Math.floor((Math.random()*775)+1), 0);
apples[i].texture.src = "_img/apple.png";
}
The other answers which are appearing here are just bizarre. Here's the solution:
function fruits(speed, x, y)
{
this.texture = new Image( );
this.speed = speed;
this.x = x;
this.y = y;
};
Notice that the keyword this is used to set attributes. That means that when you call
var apple = new fruits( blah blah );
then apple will be set to a new object which has texture, speed, x and y attributes. There is no need to reference some global object to store these; they are stored in the newly created object itself.
Also I would rename it; the convention is to use singular names and a capital first letter for objects, so Fruit would make more sense (allowing new Fruit(...))
function Fruit( speed, x, y ){
var fruit = {}; // or use some base object instead of {}
fruit.texture = new Image();
fruit.speed = speed || 5;
fruit.x = x || 0;
fruit.y = y || 0;
return fruit;
};
var apples = [];
for( var i=0; i<10; i++ ){
apples[i] = Fruit( 5, Math.floor((Math.random()*775)+1), 0 );
apples[i].texture.src = "_img/apple.png";
}
Douglas Crockford - Power Constructor, 'new', 'this' and more
You got an object here:
var fruit = {
texture: new Image(),
speed: 5,
x: 0,
y: 0, // Note the superflous comma, which might break the code in some IE versions
};
And a function here:
function fruits(speed, x, y) {
fruit.speed = speed;
fruit.x = x;
fruit.y = y;
return fruit;
};
The function modifies above object whenever it is called and returns it.
Now, what you want is a constructor, but you don't have one here.
This, would be a constructor for a new Fruit:
function Fruit(speed, x, y) {
this.texture = new Image();
this.speed = speed || 5; // Note: Using logical OR to emulate default values for the argument
this.x = x || 0;
this.y = y || 0;
// Note: There is no return here!
}
var a = new Fruit(2, 1, 10);
var b = new Fruit(4, 10, 20);
a === b; // Returns false, you got two instances :)
new may have the functionality of being able to create instances of a Function, but you can still override this behavior by returning manually from within the constructor Function.
Also, even if you left out the return fruit in your original code, you would get back an empty instance of fruits since you don't assign any properties to the newly created instance.
In my Fruit example I reference the instance object via the this keyword, so I can assign speed, image, x and y to each instance created.
You might also want to read:
http://bonsaiden.github.io/JavaScript-Garden/#function.constructors
http://bonsaiden.github.io/JavaScript-Garden/#function.this
function fruits(speed, x, y) {
return {
texture: new Image(),
speed: speed,
x: x,
y: x,
}
};
Try such constructor:
function Fruit(speed, x, y) {
return {
speed: speed,
x: x,
y: y
}
}
alert(new Fruit("mySpeed", 1, 2).speed);
I am using the following code to try out the canvas element
Shape = function(x,y){
this.x = x;
this.y = y;
};
shapes = new array();
shapes.push(new Shape(50,50,10,10));
shapes.push(new Shape(100,100,10,10));
shapes.push(new Shape(150,150,10,10));
function animate(){
context.clearRect(0,0, canvas.width(),canvas.height());
var shapesLength = shapes.length;
for(var i = 0; i < shapesLength; i++){
var tmpShape = shapes[i];
tmpShape.x++;
context.fillRect(tmpShape.x,thmpShape.y,10,10);
}
context.fillStyle = 'yellow';
context.fill();
if(playAnimation){
setTimeout(animate, 1)
}
}
However, when I run this in the browser I get the following error -
ReferenceError: array is not defined
shapes = new array();
I have tried making it a global and local variable I just cant see where I am going wrong?
array should be capitalized, as that is the name of the constructor:
shapes = new Array();
In addition it's better to use the square bracket notation to create an array. Like this:
shapes = [];