I am trying to loop through an array of gameobjects and call their update methods.
Gameobjects can have different update implementations (eg: update of enemy is different from update of friend), so I created an prototype inheritance chain. But I can't get it to work: while looping through all objects I don't seem to be able to call their update methods: the compiler says they don't exist. So my question is: is it possible in Javascript to loop trough an array of objects that share the same base class and call a method on them that can be overwritten by different sub-classes?
This is what I have so far, don't know where I went wrong...:
//base gameobject class
function GameObject(name) {
this.name = name
};
GameObject.prototype.update = function(deltaTime) {
throw new Error("can't call abstract method!")
};
//enemy inherits from gameobject
function Enemy() {
GameObject.apply(this, arguments)
};
Enemy.prototype = new GameObject();
Enemy.prototype.constructor = Enemy;
Enemy.prototype.update = function(deltaTime) {
alert("In update of Enemy " + this.name);
};
var gameobjects = new Array();
// add enemy to array
gameobjects[gameobjects.length] = new Enemy("weirdenemy");
// this doesn't work: says 'gameobject doesn't have update method'
for (gameobject in gameobjects) {
gameobject.update(1); // doesn't work!!
}
Its not a problem with your Inheritance chain, but with this construct
for(gameobject in gameobjects){
gameobject.update(1); // doesn't work!!
}
When you iterate an Array with for..in, the variable will have the index values only. So, gameobject will have 0, 1.. like that, in every iteration. It is not recommended to use for..in to iterate an Array.
You might want to use, Array.prototype.forEach, like this
gameobjects.forEach(function(gameObject) {
gameObject.update(1);
});
When you iterate through an array with for ... in, the values of the loop variable will be the keys to the array, not the values.
You really shouldn't iterate through arrays that way anyway:
for (var i = 0; i < gameobjects.length; ++i)
gameobjects[i].update(1);
Try this, it works for me: =)
gameobjects.forEach(function(gameobject){
gameobject.update(1); // doesn't work!!
});
Related
I have a 2d array, which is a board for a game. I have created some objects players, weapons etc which are also held in their own arrays.
So far this is fine. I can create the objects update their properties etc.
What I'm trying to now do create some methods and functions for the game. Eg; a player picking up the weapon and have the properties updated to show this.
I have tried these methods in a single array and it worked, as soon I made it an array or arrays I have had trouble.
class Player {
constructor(name, players, hp) {
this.name = name;
this.players = players;
this.hp = 100;
this.currentWeapon = null;
}
pickUp(weapons) {
this.currentWeapon = weapons;
weapons.hold = true;
weapons.player = this;
}
dropOff(weapon) {
this.currentWeapon = null;
weapon.hold = false;
weapon.player = null;
}
class Weapon {
constructor(name, id, damage, weapons) {
this.name = name;
this.id = id;
this.damage = damage;
this.weapons = weapons;
this.player = null;
this.hold = false;
}
}
players.pickUp(weapons);
players.dropOff(weapons);
I basically want the currentWeapon to update as well as the this.player and this.hold properties when the function is called.
I have each class stored as their own array as well.
When I run it it either says XXX is not a function or XXX is not defined.
The players and weapons are on an array of an array which is the board.
Any ideas would be appreciated!
Thank you!
My educated guess is somewhere in your code you have something like:
const players = []
players.push(new Player(...)) // in a for loop
If that's the case and if you expect an array of players to have a method pickUp, that's your issue.
Each player object has a pickup method not players (array of Player).
Possible solution:
players.forEach(player => player.pickUp(weapons));
players.forEach(player => player.dropOff(weapons));
Pretty hard to tell without seeing the full code. The error you are experiencing is because the call site is
incorrect. When calling a method using obj.method().
From what I see in your code all of your methods are unbounded, meaning their lexical context depends on their call site,
calling obj.method() will bound the this (lexical context) to obj. An invalid call site (calling obj.method()
when obj resolves to null for instance) can have several origins:
out of bound error when looking up the arrays
getting references to the unbounded method and then calling it: var method = obj.method; method() will throw if
method uses this (as it is now undefined).
I am just playing around with the idea of subclassing with Javascript. I like to pretend that extending native objects (like Array, String etc) is a bad idea. This, however true, is completely out of my understanding as to why.
Having said that, let's get on with it.
What I'm trying to do is to extend Array (now, extend may not be right term for what I'm doing)
I want to create my new class MyArray and I want to have 2 methods on it. .add and .addMultiple.
So I implemented it like this.
function MyArray(){
var arr = Object.create(Array.prototype);
return Array.apply(arr, arguments);
}
MyArray.prototype = Array.prototype;
MyArray.prototype.add = function(i){
this.push(i);
}
MyArray.prototype.addMultiple = function(a){
if(Array.isArray(a)){
for(var i=0;i<a.length;i++){
this.add(a[i]);
}
}
}
This works correctly, but if I do
console.log(Array.prototype.addMultiple );
console.log(Array.prototype.add);
I get [Function] and [Function].
So this means my code is modifying the native Array object. Something that I am trying to avoid. How do I change this code in a way that those two console.logs will give me undefined but I am still able to use native Array.prototype methods like .push?
TIA
You should setup proper prototypes chain:
function MyArray(){
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype);
Object.create just creates new object with specified prototype, so after this operation following is true:
MyArray.prototype !== Array.prototype; // true
Object.getPrototypeOf(MyArray.prototype) === Array.prototype; // true
This:
MyArray.prototype = Array.prototype;
results in MyArray.prototype pointing to the same object as Array.prototype. So everything you do to MyArray.prototype after that will also be done to Array.prototype.
A way to solve this is to instead store a shallow copy of Array's prototype in MyArray:
MyArray.prototype = clone(Array.prototype);
I copied that from here:
Copy prototype for inheritance?
use class extension
class MyArray extends Array {
add(i) {
this.push(i);
return this;
}
addMultiple(a) {
if (Array.isArray(a)) {
for (var i = 0; i < a.length; i++) {
this.add(a[i]);
}
}
return this;
}
}
var test = new MyArray();
test.addMultiple([1,2,3,4,5]).add(6).add(7);
console.log(test, test.indexOf(6));
Object.create(Array.prototype);
This just creates a new object and return the objects.
So as per your scenarios, you have just created array object and added some methods to your array object - MyArray. It will affect the native Array.
You're just modifying your cloned object.
I have this function to create an object:
function Building(owner, type, hp) {
this.owner = owner;
this.type = type;
this.hp = hp;
}
So, every time I call it, a new object is created.
var barracks = new Building(player,bBarracks,"100");
But I have an another function which can be called several times.
function build() {
if (building == 1) {
$("."+xPos+"-"+yPos).addClass("building-powerplant").addClass("taken");
hudBuildings("powerPlant"); initialize();
hudBuildings("barracks");
...
} ... }
I want to create a new object every time build() is called and give it a name of "id"+[increased number], ex. id1, id2, ..., id10.
So every time I call function, an object is created. I tried increasing a number by 1 every time it is used, but I can't figure it out how to write it in. Honestly, that was kinda dumb:
objID++;
var id+(objID) = new Building(player,bPowPlant,"100");
Any ideas how to figure this out? :)
Could you keep the objects in an array? that way the "key" automatically sort of becomes the incremementing ID.
so you have earlier on:
var objects = [];
Then in the build function you do like
objects.push(new Building(player,bPowPlant,"100"))
Then in the objects variables you will have all of the objects you have created. Accessible by objects[0], objects[1] etc.
When you want to save a string as a variable name, you'll need to save it as an object key. You could build your own object, although a common method is to save to the window object using window[variableName]. As for the id number, you can save that to a higher scoped variable and then increment inside the build() function.
var objId = 0;
function build() {
if (building == 1) {
$("."+xPos+"-"+yPos).addClass("building-powerplant").addClass("taken");
hudBuildings("powerPlant"); initialize();
hudBuildings("barracks");
objId++;
...
} ... }
Then when you create a new object:
window["id"+objId] = new Building(player,bPowPlant,"100");
If you have an array of product objects created from JSON, how would you add a prototype method to the product objects so that they all point to the same method? How would you train JavaScript to recognize all product objects in an array are instances of the same class without recreating them?
If I pull down a JSON array of Products for example, and want each product in the array to have a prototype method, how would I add the single prototype method to each copy of Product?
I first thought to have a Product constructor that takes product JSON data as a parameter and returns a new Product with prototypes, etc. which would replace the data send from the server. I would think this would be impractical because you are recreating the objects. We just want to add functions common to all objects.
Is it possible to $.extend an object's prototype properties to the JSON object so that each JSON object would refer to exactly the same functions (not a copy of)?
For example:
var Products = [];
Products[0] = {};
Products[0].ID = 7;
Products[0].prototype.GetID = function() { return this.ID; };
Products[1].ID = 8;
Products[1].prototype = Products[0].prototype; // ??
I know that looks bad, but what if you JQuery $.extend the methods to each Product object prototype: create an object loaded with prototypes then $.extend that object over the existing Product objects? How would you code that? What are the better possibilities?
For one, you're not modifying the Products[0].prototype, you're modifying Object.prototype, which will put that function on the prototype of all objects, as well as making it enumerable in every for loop that touches an Object.
Also, that isn't the proper way to modify a prototype, and ({}).prototype.something will throw a TypeError as .prototype isn't defined. You want to set it with ({}).__proto__.something.
If you want it to be a certain instance you need to create that instance, otherwise it will be an instance of Object.
You probably want something like:
var Product = function(ID) {
if (!this instanceof Product)
return new Product(ID);
this.ID = ID;
return this;
};
Product.prototype.GetID = function() {
return this.ID;
};
Then, fill the array by calling new Product(7) or whatever the ID is.
First, one problem is that prototype methods are associated when the object is created, so assigning to an object's prototype will not work:
var Products = [];
Products[0] = {};
Products[0].prototype.foo = function () { return 'hello' } // ***
Products[0].foo(); // call to undefined function
(*** Actually, the code fails here, because prototype is undefined.)
So in order to attach objects, you'll need to assign actual functions to the object:
Products[0].foo = function () { return 'hello'; };
You can create a helper function to do so:
var attachFoo = (function () { // Create a new variable scope, so foo and
// bar is not part of the global namespace
function foo() { return this.name; }
function bar() { return 'hello'; }
return function (obj) {
obj.foo = foo;
obj.bar = bar;
return obj; // This line is actually optional,
// as the function /modifies/ the current
// object rather than creating a new one
};
}());
attachFoo(Products[0]);
attachFoo(Products[1]);
// - OR -
Products.forEach(attachFoo);
By doing it this way, your obj.foos and obj.bars will all be referencing the same foo() and bar().
So, if I'm getting this all correctly, this is a more complete example of KOGI's idea:
// Create a person class
function Person( firstName, lastName ) {
var aPerson = {
firstName: firstName,
lastName: lastName
}
// Adds methods to an object to make it of type "person"
aPerson = addPersonMethods( aPerson );
return aPerson;
}
function addPersonMethods( obj ) {
obj.nameFirstLast = personNameFirstLast;
obj.nameLastFirst = personNameLastFirst;
return obj;
}
function personNameFirstLast() {
return this.firstName + ' ' + this.lastName;
}
function personNameLastFirst() {
return this.lastName + ', ' + this.firstName;
}
So, with this structure, you are defining the methods to be added in the addPersonMethods function. This way, the methods of an object are defined in a single place and you can then do something like this:
// Given a variable "json" with the person json data
var personWithNoMethods = JSON.parse( json ); // Use whatever parser you want
var person = addPersonMethods( personWithNoMethods );
You could do this...
function product( )
{
this.getId = product_getId;
// -- create a new product object
}
function product_getId( )
{
return this.id;
}
This way, although you will have several instances of the product class, they all point to the instance of the function.
Could try doing something like this (without jquery)
Basic prototypal object:
function Product(id){
this.id = id;
}
Product.prototype.getId() = function(){return this.id;};
var Products = [];
Products[0] = new Product(7);
Products[1] = new Product(8);
Products[2] = new Product(9);
alert(Products[2].getId());
IMO I found a pretty good answer right here:
Return String from Cross-domain AJAX Request
...I could serialize my
data in the service as a JSON string
and then further wrap that in JSONP
format? I guess when it comes over to
the client it would give the JSON
string to the callback function.
That's not a bad idea. I guess I would
also have the option of sending a
non-JSON string which might allow me
to just use eval in the callback
function to create new Person objects.
I'm thinking this would be a more
efficient solution in both speed and
memory usage client-side.
I need an array to store some geometrical data. I would like to simply inherit from the Array object and than extend it with a few new functions like "height" and "width" (sum of all children's heights/widths), but also with a few convenience methods like "insertAt" or "remove".
What is the best way to do it without modifying the original Array object (Array.prototype.myMethod)?
You can always mixin your changes directly into Array, but that might not be the best choice given that it's not something every array should have. So let's inherit from Array:
// create a constructor for the class
function GeometricArray() {
this.width = 0;
this.height = 0;
}
// create a new instance for the prototype so you get all functionality
// from it without adding features directly to Array.
GeometricArray.prototype = new Array();
// add our special methods to the prototype
GeometricArray.prototype.insertAt = function() {
...
};
GeometricArray.prototype.remove = function {
...
};
GeometricArray.prototype.add = function( child ) {
this.push( child );
// todo calculate child widths/heights
};
Are you (maybe) applying Java concepts to Javascript?
You don't need to inherit from classes in Javascript, you just enrich objects.
So the best way in my world (a world full of people head-butting methods into objects) is:
function GeometricArray()
{
var obj=[]
obj.height=function() {
// wibbly-wobbly heighty things
for(var i=0;i<this.length;i++) {
// ...
}
}
obj.width=function() {
// wibbly-wobbly widy things
// ...
}
// ...and on and on...
return obj
}
You could use prototyping to put those functions in Array.
To add the height function for example do this:
Array.prototype.height = function() {
//implementation of height
}