I'm relatively new to Javascript and I am trying to create a very simple physics engine for a game type project I am working on. In order to do this, I created what I understand to be the JS equivalent of a class that I can create new copies of for each object I want. The problem is that I want to be able to update a value such as the x position and have this also update things such as the x Middle position (x center of object on screen). I know this is possible by using an object literal and the getter, however I want to be able to create new objects at realtime based on what's on the screen and I couldn't figure out how to use get to make this work. Here's the general idea of what I am trying to do:
var object = function (xPos, yPos, width, height) {
this.xPos = xPos;
this.yPos = yPos;
function getXMid (xP) { return xP + width/2; }
this.xMid = getXMid (this.xPos);
function getYMid (yP) { return yP + height/2; }
this.yMid = getYMid (this.yPos);
}
var ball = new object (10, 20, 50, 50);
ball.xPos = 50;
console.log (ball.xMid); // want this to output 75 instead of 45
You're changing one property, and expecting other properties to update, unfortunately it doesn't work that way when the properties hold primitive values.
You could use setters and getters and a function to update the other properties when you set a value
var object = function(xPos, yPos, width, height) {
this._xPos = xPos;
this._yPos = yPos;
this.recalc = function() {
this.xMid = getXMid(this.xPos);
this.yMid = getYMid(this.yPos);
}
Object.defineProperty(this, 'xPos', {
get: function() {
return this._xPos;
},
set: function(v) {
this._xPos = v;
this.recalc();
}
});
Object.defineProperty(this, 'yPos', {
get: function() {
return this._yPos;
},
set: function(v) {
this._yPos = v;
this.recalc();
}
});
function getXMid(xP) { return xP + width / 2; }
function getYMid(yP) { return yP + height / 2; }
this.recalc();
}
var ball = new object(10, 20, 50, 50);
ball.xPos = 50;
console.log (ball.xMid); // want this to output 75 instead of 45
Related
I am struggling with the error of which throw me once I am trying to recursively change a colour. The error is: “Uncaught TypeError Cannot read property ‘map’ of undefined (sketch: line 18)” and reference to this piece of code: this.color.levels.map(x => x * 0.9) .
I supposed that’s because of a recursive and problem with “this” context. The “right” function which I’ve created executing just one time until above error is thrown.
Any ideas how to make this work or how to recursively change the colour referencing to the same object which I’ve been created?
My code: https://editor.p5js.org/grzegorz.kuguar#gmail.com/sketches/Syc1qQmnQ
<code> class Branch {
constructor(begin, end, strokeW, color, angle) {
this.begin = begin;
this.end = end;
this.angle = angle;
this.strokeW = strokeW;
this.color = color;
}
display() {
stroke(this.color);
strokeWeight(this.strokeW);
line(this.begin.x, this.begin.y, this.end.x, this.end.y);
}
right(angle) {
let direction = p5.Vector.sub(this.end, this.begin);
direction.rotate(angle);
let nextPoint = p5.Vector.add(direction, this.end);
let right = new Branch(this.end, nextPoint, this.strokeW*0.7, this.color.levels.map(x => x * 0.9)); //this line of code throw an error once I am trying to manipulate on the array
return right;
}
}
let tree = [];
let trunk;
let something; //just for check how looks like a p5.color object
function setup() {
createCanvas(400, 400);
background(20);
something = color(100, 230, 100);
console.log(something);
let x = createVector(width/2, height);
let y = createVector(width/2, height-100);
trunk = new Branch(x,y, 7, color(255,100, 100));
tree[0] = trunk;
tree.push(trunk);
}
function draw() {
for(let i = 0; i < tree.length; i++) {
tree[i].display();
}
}
function mousePressed() {
for(let i = tree.length-1; i >= 0; i--) {
tree.push(tree[i].right(Math.PI/4, 0.66));
}
}
The problem is in your use of map. You are doing:
this.col.levels.map(x => x * 0.9)
which returns an array, not a p5.Color object.
To create the new color object, instead use:
color.apply(this, this.col.levels.map(x => x * 0.9))
You can see the full sketch here
I am initializing classes like so (Character is a class in my program):
character = new Character();
I want two instances of this class/object, so I tried this:
character2 = new Character();
Yet character2 simply replaces character; therefore, there is only one object. Is it possible to create another instance, or would I need to make another Character class (a lot of code duplication!).
I tried adding a second draw function (named draw2, for the second object), but that didn't help.
You need to move the definition of the Character.prototype.draw method out of the constructor function. Otherwise, each time you create a new Character(), you also override the Character.prototype.draw method.
You also need to replace references to local constructor function variables within the Character.prototype.draw method such as x, y or size with object properties such as this.X, this.Y and this.Size.
Also, you need to make the img a (possibly static) property of your Character.
Object creation in modern JS
Modern JS includes some shortcuts when defining objects. There is no real need to use prototype unless you are creating many instances of the Object. Accessing the prototype adds some overhead when using objects
You can create the object inside the creating function that allows you to define private properties via closure.
Closure creates private properties
function Character() {
var x = 0;
var y = 0;
var size = 25;
var vx = 4;
var vy = 4;
var width = 45;
var height = 45;
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
get x() { return x },
get y() { return y },
get vx() { return vx },
get vy() { return vy },
get size() { return size },
get width() { return width },
get height() { return height },
set x(v) { x = v },
set y(v) { y = v },
set vx(v) { vx = v },
set vy(v) { vy = v },
set size(v) { size = v },
set width(v) { width = v },
set height(v) { height = v },
draw(ctx) {
ctx.save();
ctx.translate(x, y);
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
usage
var char = Character();
var char1 = Character();
// or
var char = new Character();
var char1 = new Character();
Performance considerations
If the object is required in performance code you may want to create more performant setters and getters, or avoid the getter setter overhead and include the properties in the object itself.
function Character() {
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
x : 0,
y : 0,
size : 25,
vx : 4,
vy : 4,
width : 45,
height : 45,
draw(ctx) {
const size = API.size;
ctx.save();
ctx.translate(API.x, API.y); // Note that I use API.x rather than this.x
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
Notes.
I noticed you have way too many comments.
eg
//public property for VX
Object.defineProperty(this, 'width',
and
//function public draw method
Character.prototype.draw = function (ctx) {
//save the ctx
ctx.save();
//set x and y
ctx.translate(x, y);
//set the line width
ctx.lineWidth = 2;
You are stating the obvious in a comment, no machine will read it, no human needs to read it, so why is it there. Comments add noise, and source code noise is dangerous, avoid all unnecessary noise in your code.
You create the pattern each time the draw function is called, this is unneeded overhead. Create it only once.
There is a time when calling the draw function may not work as the image will not have loaded yet. Maybe you should manage images outside the object where you can ensure that media objects are loaded and ready to be used befor you try to use them.
Each instance of Character will load the image, create 100 of them and there will be 100 copies of the same image. This will affect performance and memory negatively.
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 want to create an instance of a Point with and without the new operator like:
Point(5, 10); // returns { x: 5, y: 10 }
// or
new Point(5, 10); // also returns { x: 5, y: 10 }
I got it working so far with the help of StackOverflow.
function Point() {
if (!(this instanceof Point)) {
var args = Array.prototype.slice.call(arguments);
// bring in the context, needed for apply
args.unshift(null);
return new (Point.bind.apply(Point, args));
}
// determine X and Y values
var pos = XY(Array.prototype.slice.call(arguments));
this.x = pos.x;
this.y = pos.y;
}
But that looks horrible, I am even unshifting null into the array so I can use apply. That just doesn't feel right.
I found a lot of solutions how to achieve it with new constructors and constructor wrappers but I want to keep it as simple as possible (it's just a plain, simple Point).
Is there an easier way to achieve this behaviour?
If you don't mind using ECMAScript 5 functions, Object.create() could help:
function Point()
{ var args = Array.prototype.slice.call(arguments);
if (this instanceof Point) return Point.apply(null, args);
var pos = XY(args);
var result = Object.create(Point.prototype);
result.x = pos.x;
result.y = pos.y;
return result;
}
If you need ECMAScript 3 compatibility, this crazy, convoluted solution is yet another one (note that it's just a wrapper for an internal equivalent of new Point):
function Point()
{ var pos = XY(Array.prototype.slice.call(arguments));
function internalPoint()
{ this.x = pos.x;
this.y = pos.y;
}
internalPoint.prototype = Point.prototype;
return new internalPoint;
}
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};