I am trying prototypical inheritance in order to calculate distance(). But I am getting an error which is TypeError: Cannot read property 'x' of undefined. The error line is marked at the code. I know that the way I am calculating distance() is wrong. But someone can guide me what is the correct way?
'use strict';
function Shape(x,y){
this.x = x;
this.y = y;
};
Shape.prototype.distance = function(s1,s2){
// this.distance = function(s1,s2){
const xDiff = this.s1.x - this.s2.x; //error
const yDiff = this.s1.y - this.s2.y;
return Math.sqrt(xDiff*xDiff + yDiff*yDiff);
// }
};
function Circle(x,y,radius){
Shape.call(this,x,y);
this.radius = radius;
this.area = Math.PI*this.radius*this.radius;
};
function Rectangle(x,y,w,h){
Shape.call(this,x,y);
this.width =w;
this.height =h;
this.area = this.width * this.height;
};
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
const shapes = [
new Rectangle(3,4,5,6),
new Circle(0,0,1),
];
shapes.forEach((s) => console.log(s.x, s.y, s.area));
console.log(Shape.prototype.distance(shapes[0],shapes[1]));
Typically when you put a method on the prototype you anticipate it being called on an instance of that class. So, in your example you would do something like:
someShape.distance(someOtherShape)
When you do that the this in the function refers to the shape on which you called the function. So your distance function would look like this:
Shape.prototype.distance = function(otherShape){
const xDiff = this.x - otherShape.x;
const yDiff = this.y - otherShape.y;
return Math.sqrt(xDiff*xDiff + yDiff*yDiff);
}
Then you can call it as a method on one of the objects:
function Shape(x, y) {
this.x = x;
this.y = y;
}
Shape.prototype.distance = function(otherShape) {
const xDiff = this.x - otherShape.x; //error
const yDiff = this.y - otherShape.y;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
}
let sh1 = new Shape(0, 0)
let sh2 = new Shape(3, 4)
console.log(sh1.distance(sh2))
// or the opposite
console.log(sh2.distance(sh1))
If instead you want distance to be a class method and you want to pass in two instances, don't put the function on the prototype, add it as a property of the function itself:
function Shape(x,y){
this.x = x;
this.y = y;
}
// not on the prototype
Shape.distance = function(s1, s2){
const xDiff = s1.x - s2.x;
const yDiff = s1.y - s2.y;
return Math.sqrt(xDiff*xDiff + yDiff*yDiff);
}
let sh1 = new Shape(0, 0)
let sh2 = new Shape(3, 4)
console.log(Shape.distance(sh1, sh2))
Related
How do you make a class property that recalculates each time you use it?
class myClass {
constructor(x, y) {
this.x = x
this.y = y
this.percent = x/y * 100
}
}
var test = new myClass(5, 10)
test.percent
//50
test.x = 10
test.percent
//still 50
I want test.percent to change 100 and adapt to other changes.
Can I do this without turning the variable into a function?
What you are looking for is called a getter. A getter is recomputed everytime its property is accessed:
class myClass {
constructor(x, y) {
this.x = x
this.y = y
}
get percent(){
return this.x / this.y * 100
}
}
var test = new myClass(5, 10)
console.log(test.percent) //50
test.x = 10
console.log(test.percent) //100
There are two ways you can do this
You can have this.percent as a function
class myClass {
constructor(x, y) {
this.x = x;
this.y = y
this.percent = function() {
return this.x / this.y * 100
}
}
}
var test = new myClass(5, 10)
console.log(test.percent())
test.x = 10
console.log(test.percent())
You can also use getter
class myClass {
constructor(x, y) {
this.x = x;
this.y = y;
}
get percent() {
return this.x / this.y * 100
}
}
var test = new myClass(5, 10)
console.log(test.percent)
test.x = 10
console.log(test.percent)
You can use an accessor ( getter ) to alter the data each time you are accessing it.
In your case, you can replace the percent property by a getter.
class myClass {
constructor(x, y) {
this.x = x
this.y = y
}
get percent() {
return this.x / this.y * 100;
}
}
var test = new myClass(5, 10)
console.log(test.percent);
//50
test.x = 10
console.log(test.percent);
//now 100
I have also edited your code to use this to access x and y
I am having problems with p5 js.
I am trying to add two ellipse objects that spin around the circumference of a circle.
The code below represents the constructor function of the object :
function Obj(){
this.ang = TWO_PI;
this.x = w/2 + cos(ang)*r;
this.y = h/2 + sin(ang)*r;
this.fil = 255;
this.size = 28;
this.show = function(){
noStroke();
fill(this.fil);
ellipse(this.x,this.y,this.size,this.size);
}
this.update = function(){
this.ang+=.02;
}
}
And this is the main file :
let w = innerWidth;
let h = innerHeight;
let xd = [];
let r = 200;
function setup() {
createCanvas(w, h);
for (let i = 0; i < 2; i++)
xd[i] = new Obj();
}
function draw(){
background(0,70,80);
noFill();
strokeWeight(7);
stroke(255);
ellipse(w/2, h/2, r*2, r*2);
xd[0].update();
xd[0].show();
}
The problem is that it says that ang is not defined even though i did clearly define it with this.ang = TWO_PI;. And if I declare it in the main file and in setup() I say ang = TWO_PI; the object stays in place. Can anyone help ?
Thank you.
The problem in the constructor function in this code, it should be like this :
this.x = w/2 + cos(this.ang)*r;
this.y = h/2 + sin(this.ang)*r;
Because you are using a property from the constructor function itself
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.
I have a card class:
function Card() {
this.image = new Image();
this.x = 0;
this.y = 400;
this.initialX = 0;
this.initialY = 0;
this.scaleFactor = 4;
this.setImage = function(ii){
this.image.src = ii;
};
this.getWidth = function(){
if (this.image == null){
return 0;
}
return this.image.width / this.scaleFactor;
}
this.getHeight = function(){
if (this.image == null){
return 0;
}
return this.image.height / this.scaleFactor;
}
this.pointIsInCard = function(mx, my){
if (mx >= this.x && mx <= (this.x + this.getWidth()) && my >= this.y && my <= (this.y + this.getHeight()))
{
return true;
}
else{
return false;
}
};
};
I then have a deck class:
function Deck(x, y, w, h){
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.cards = [];
}
I need to add a method in Deck class similar to pointIsInCard instead it will be called pointIsInDeck. The logic will be same i.e to check whether the passed in point falls in the boundary of the object. So seeing this duplication of code I wanted to know what is a good design practice to avoid this duplication? One option I thought of was to extract the method out and create a function for generic object with x, y, width, height but again from OOP principles I thought this method should belong to the class/object. I appreciate any help! Thanks!
A common approach for what you're doing is to attach a Rectangle or similar instance with that functionality to both of your objects, that is:
class Rectangle {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
containsPoint(x, y) {
return x >= this.x && x =< this.width &&
y >= this.y && y =< this.height;
}
}
Then just add it to Card and Deck:
function Card() {
this.rect = new Rectangle(/* Your card shape */);
// ...
}
function Deck() {
this.rect = new Rectangle(/* Your deck shape */);
// ...
}
And you can do:
card.rect.containsPoint();
deck.rect.containsPoint();
If these are classes related to drawing, they would both inherit from something like Rectangle, which they would both inherit this behaviour from.
If they are gameplay-related, I would prefer them each referencing a Rectangle (or its subclass) that they would delegate all UI-related tasks to; then reduce this to the previous paragraph's solution.
You can use Function.prototype.call() to set this at a function call
function Card() {
this.x = 1; this.y = 2;
};
function Deck() {
this.x = 10; this.y = 20;
}
function points(x, y) {
// do stuff
console.log(x, this.x, y, this.y); // `this`: `c` or `d`
}
var c = new Card();
var d = new Deck();
points.call(c, 3, 4); // `this`: `c` within `points` call
points.call(d, 100, 200); // `this`: `d` within `points` call
I would like to create a custom Raphael element, with custom properties and functions. This object must also contain predefined Raphael objects. For example, I would have a node class, that would contain a circle with text and some other elements inside it. The problem is to add this new object to a set. These demands are needed because non-Raphael objects cannot be added to sets. As a result, custom objects that can contain Raphael objects cannot be used. The code would look like this:
var Node = function (paper) {
// Coordinates & Dimensions
this.x = 0,
this.y = 0,
this.radius = 0,
this.draw = function () {
this.entireSet = paper.set();
var circle = paper.circle(this.x, this.y, this.radius);
this.circleObj = circle;
this.entireSet.push(circle);
var text = paper.text(this.x, this.y, this.text);
this.entireSet.push(text);
}
// other functions
}
var NodeList = function(paper){
this.nodes = paper.set(),
this.populateList = function(){
// in order to add a node to the set
// the object must be of type Raphael object
// otherwise the set will have no elements
this.nodes.push(// new node)
}
this.nextNode = function(){
// ...
}
this.previousNode = function(){
// ...
}
}
You can only add Raphael object (rect,circle, eclipse,text) to paper.set(), not self defined object( with Raphael.fn) . Instead use normal array definition of javascript [].
As fas as i understand nodeList is a simple list but with more options like nextnode , previous nodes.
Take a look at this demo, i changed abit José Manuel Cabrera's codes: http://jsfiddle.net/Tomen/JNPYN/1/
Raphael.fn.node = function(x, y, radius, txt) {
this.x = x;
this.y = y;
this.radius = radius;
this.txt = txt;
this.circleObj = paper.circle(this.x, this.y, radius), this.textObj = paper.text(this.x, this.y, this.txt);
this.entireSet = paper.set(this.circleObj, this.textObj);
return this
}
Raphael.fn.nodeList = function() {
this.nodes = [];
this.push = function(p) {
return this.nodes.push(p);
};
// this.nextNode = function(){
// ... manipulate this.nodes here
// }
// this.previousNode = function(){
// ...
// }
return this
}
var ca = paper.node(250, 150, 50.0, "hola");
var list = paper.nodeList();
list.push(ca);
Some examples may fall down if there is no global 'paper'
The context of Raphael.fn.yrMethod will be the instance (paper)
This example creates a raphael object which wraps a g element, which is for some reason not currently supported:
(function(R){
function g(_paper){
var _canvas = _paper.canvas,
_node = document.createElementNS("http://www.w3.org/2000/svg", "g");
_canvas.appendChild(_node);
this.add = function(rElement){
_node.appendChild(rElement.node);
}
this.remove = function(rElement){
_canvas.appendChild(rElement.node);
}
this.transform = function(tString){
_node.setAttribute('transform', tString);
}
}
R.fn.g = function(){
return new g(this);
}
})(Raphael);
this code allow you to create a node with a text (it returns a set) and you can manipulate it as a Raphael object (put the method after loading the dom):
function loadShape(){
Raphael.fn.node = function(x, y, radius, txt){
this.x = x;
this.y = y;
this.radius = radius;
this.txt = txt;
this.drawCircle = function () {
return paper.circle(this.x, this.y, radius);
};
this.drawText = function () {
return paper.text(this.x, this.y, this.txt);
};
this.draw = function(){
var group = paper.set();
var circulo = paper.circle(this.x, this.y, radius);
var texto = paper.text(this.x, this.y, this.txt);
group.push(circulo);
group.push(texto);
return group;
}
this.init = function(ox, oy, r, t){
this.x = ox;
this.y = oy;
this.radius = r;
this.txt = t;
};
// etc…
return this;
};
var paper = new Raphael(document.getElementById("wrapper"), "100%", "100%");
//var nodo = paper.node();
//nodo.init(50, 50, 2.0, "soy un nodo");
var nodo = paper.node(250, 150, 50.0, "hola");
nodo.draw();
//circ.attr({"propiedad":"hola"});
//alert(circ.attr("propiedad"));
}
Tell me if this was useful to you!