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);
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.
I was really struggling coming up with a title but basically I'm working on a game in the html5 canvas and have a class called player with a subclass aiPlayer for when playing against ai. The code for updating the players looks like this:
var entitiesCount = this.entities.length;
for (var i = 0; i < entitiesCount; i++) {
var entity = this.entities[i];
entity.update();
if (entity instanceof special && entity.x > WIDTH || entity.x + 200 < 0) {
this.entities.splice(i, 1);
entitiesCount--;
}
}
However, the aiPlayer never updates with the aiPlayer update function. I've printed out the constructor of each entity and there is one Player and one aiPlayer. However, when printing out the method they are calling, both of them are calling the Player update. Does anyone know why it would do this?
Also, if it helps, the aiPlayer update looks like:
aiPlayer.prototype.update = function() {
if((this.game.timer.gameTime % this.moveTime) > (this.moveTime * 0.9)) {
this.chooseMove();
}
Player.prototype.update.call(this);
};
And ai constructor looks like:
function aiPlayer (game, character, x, y, health) {
Player.call(this, game, character, x, y, health, PLAYER2_CONTROLS, "left");
aiPlayer.prototype = new Player(this.game, this.character, this.x, this.y,
this.health, this.control, this.facing);
aiPlayer.prototype.constructor = aiPlayer;
this.controls = PLAYER2_CONTROLS;
this.attackLength = 50;
this.fleeLength = 70;
this.moveTime = 1;
this.prevControl = "idle";
}
function aiPlayer (game, character, x, y, health) {
Player.call(this, game, character, x, y, health, PLAYER2_CONTROLS, "left");
aiPlayer.prototype = new Player(this.game, this.character, this.x, this.y,this.health, this.control, this.facing);
aiPlayer.prototype.constructor = aiPlayer;
this.controls = PLAYER2_CONTROLS;
this.attackLength = 50;
this.fleeLength = 70;
this.moveTime = 1;
this.prevControl = "idle";
}
These lines here
aiPlayer.prototype = new Player(this.game, this.character,
this.x, this.y,this.health,
this.control, this.facing);
aiPlayer.prototype.constructor = aiPlayer;
are wrong. They are wrong because
you are setting the prototype to an Instance of Player
you are resetting the prototype and the constructor of the prototype of aiPlayer every time you create a new instance of aiPlayer. You should move all modifications to the prototype outside of the constructor function, like this:
-
function aiPlayer (game, character, x, y, health) {
Player.call(this, game, character, x, y, health, PLAYER2_CONTROLS, "left");
this.controls = PLAYER2_CONTROLS;
this.attackLength = 50;
this.fleeLength = 70;
this.moveTime = 1;
this.prevControl = "idle";
}
aiPlayer.prototype.someMethod = function someMethod() {
....
}
A correct way to set the prototype of the subclass is like this
aiPlayer.prototype = Object.create(Player.prototype, {
constructor : {
value : aiPlayer
}
};
This will set as the aiPlayer prototype a new object that inherits from Player.prototype (i.e. has Player.prototype as its prototype) and has aiPlayer registered as its constructor function
Also, the .update of Player is called from aiPlayer because you explicitly calling it here
aiPlayer.prototype.update = function() {
if((this.game.timer.gameTime % this.moveTime) > (this.moveTime * 0.9)) {
this.chooseMove();
}
Player.prototype.update.call(this); //you call the Player.update()
};
Considering the above, this is how you should register the aiPlayer.update
aiPlayer.prototype = Object.create(Player.prototype, {
constructor : {
value : aiPlayer
}
};
aiPlayer.prototype.update = function update() {
//your code here
}
Now, when you create a new aiPlayer object instance, the inheritance chain will go like this
aiPlayerInstance --> aiPlayer.prototype --> Player.prototype
and when you call aiPlayerInstance.update() it will first look to aiPlayer.prototype and since aiPlayer.prototype does have a method called update it will execute it, and it will not look any further down the inheritance chain (i.e. in Player.prototype)
I'm a newbie to javascript and I need some help.
I was trying to sum radius by function, but got an undefined error:(
function sumWithFunction(func, number) {
return func() + number;
}
function Circle(X, Y, R) {
this.x = X;
this.y = Y;
this.r = R;
}
Circle.prototype.getRadius = function () {
return this.r;
}
Circle.prototype.increaseRadiusBy = function(number) {
this.r = sumWithFunction(this.getRadius, number);
}
function addFivetoIt(func) {
func(5);
}
var MyCircle = new Circle(0, 0, 10);
addFivetoIt(MyCircle.increaseRadiusBy);
The problem is that you're passing a function a reference to another function, and the passed function is therefore losing scope! Here's the offending line:
Circle.prototype.increaseRadiusBy = function(number) {
this.r = sumWithFunction(this.getRadius, number);
}
JavaScript objects are in some ways simpler than they appear. When you added the getRadius method to the Circle prototype, you were not defining a class method like you would in classical OO. You were simply defining a named property of the prototype, and assigning a function to the value of that property. When you pass this.getRadius as an argument to a static function, like sumWithFunction, the context of this is lost. It executes with the this keyword bound to window, and since window has no r property, the browser throws an undefined error.
Put another way, the statement this.getRadius() is actually saying "execute the function assigned to the getRadius property of this, and execute it in the context of this. Without calling the function explicitly through that statement, the context is not assigned.
A common solution to this is to add an expected argument to any function which receives another function, for context.
function sumWithFunction(func, context, number) {
return func.apply(context) + number;
}
function Circle(X, Y, R) {
this.x = X;
this.y = Y;
this.r = R;
}
Circle.prototype.getRadius = function () {
return this.r;
}
Circle.prototype.increaseRadiusBy = function(number) {
this.r = sumWithFunction(this.getRadius, this, number);
}
function addFivetoIt(func, context) {
func.apply(context,[5]);
}
var MyCircle = new Circle(0, 0, 10);
addFivetoIt(MyCircle.increaseRadiusBy, myCircle);
A simpler, but less robust solution would be to declare a function inline that can access a context reference in the local closure.
function sumWithFunction(func, number) {
return func() + number;
}
function Circle(X, Y, R) {
this.x = X;
this.y = Y;
this.r = R;
}
Circle.prototype.getRadius = function () {
return this.r;
}
Circle.prototype.increaseRadiusBy = function(number) {
var me = this;
this.r = sumWithFunction(function() {
return me.getRadius()
}, number);
}
function addFivetoIt(func) {
func(5);
}
var MyCircle = new Circle(0, 0, 10);
addFivetoIt(function(number) {
return MyCircle.increaseRadiusBy(number);
});
But by far the simplest solution is to use a newer feature of ECMAScript, a function method called bind. It is explained well here, including the fact that it is not supported by all browsers. That's why a lot of libraries, like jQuery, Prototype, etc., have cross-browser function-binding utility methods like $.proxy.
function sumWithFunction(func, number) {
return func() + number;
}
function Circle(X, Y, R) {
this.x = X;
this.y = Y;
this.r = R;
}
Circle.prototype.getRadius = function () {
return this.r;
}
Circle.prototype.increaseRadiusBy = function(number) {
this.r = sumWithFunction(this.getRadius.bind(this), number); // or $.proxy(this.getRadius,this)
}
function addFivetoIt(func) {
func(5);
}
var MyCircle = new Circle(0, 0, 10);
addFivetoIt(MyCircle.increaseRadiusBy.bind(MyCircle)); // or $.proxy(MyCircle.increaseRadiusBy,MyCircle)
The tricky thing with this in JavaScript is that it contains the object the function was a property of when it was called by default. So when you pass MyCircle.increaseRadiusBy as a parameter, you then call it as func(), so the function isn't a property of any object. The easiest way to set this is to use the call() function:
function addFivetoIt(func, context) {
// The first parameter to `call()` is the value of `this` in the function
func.call(context, 5);
}
var MyCircle = new Circle(0, 0, 10);
addFivetoIt(MyCircle.increaseRadiusBy, MyCircle);
The below approach, setting func as a property before calling it, also works. You would never do this in practice because it adds an unnecessary property to context, but it's a good didactic example to show how this works.
function addFivetoIt(func, context) {
context.func = func;
context.func(5);
}
var MyCircle = new Circle(0, 0, 10);
addFivetoIt(MyCircle.increaseRadiusBy, MyCircle);
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.
I have a piece of js software that is structured like so:
obj = new object[id]();
function wrapperFunction (e) {
var pos = findPos(this);
e._x = e.pageX - pos.x;
e._y = e.pageY - pos.y;
var func = obj[e.type];
if (func) {
func(e);
}
}
__
obj.line = function () {
this.started = false;
this.mousedown = function (e) {
}
this.mousemove = function (e) {
if (this.started) {
}
}
this.mouseup = function (e) {
if (this.started) {
}
}
}
The above code block is duplicated for multiple shapes so there is also a obj.square obj.circle etc...
I also have a shape object that is as follows.
function Shape (type, color, height, width, radius, x, y) {
this.type = type;
this.color = color;
this.h = height;
this.w = width;
this.r = radius;
this.points = ["x","y"];
this.points["x"] = [x];
this.points["y"] = [y];
};
I would like to initiate the shape object on a mousedown for each obj.* and populate the shape object with the propper info.
Now for the issue.
The radius is calcuated on every mousemove as well as height and width but when I add shapes = new Shape(circle, black, 10, 10, null, e._x, e._y) to the mousemove so it looks like...
this.mousemove = function (e) {
if (this.started) {
shapes = new Shape(circle, black, 10, 10, null, e._x, e._y);
}
}
The shape object does not create.
If I create the shape object inside the wrapper function instead of the mousemove then the object initiates but I cannot use radius or height/width.
How can I create an object inside another object inside a wrapper function so I can use calculated terms inside the created object? Is there an alternate route to take besides what I am doing?
Aside from wonkiness in the obj = new object[this.id](); line, I think you're just missing a this keyword:
this.mousemove = function (e) {
if (this.started) {
this.shapes = new Shape(circle, black, 10, 10, null, e._x, e._y);
}
}
Edit just noticed more wonkiness in your code (yes, that's a technical term :). I think you want to change these lines in the constructor:
this.points = ["x","y"]; // creates an array, which is indexed by numbers
this.points["x"] = [x]; // tacks on some ad-hoc properties to the array, which
this.points["y"] = [y]; // doesn't really make sense
to this:
this.points = {x: x, // I think this is what you actually mean to do.
y: y};