Type Error - Referencing Javascript Classes - javascript

Firstly a little elaboration of the project I'm working on. I have started building a 'map maker' for a 2d game I am working on. The project is just for fun and has proven so far to be a great way to learn new things.
I recently showed my working map maker code to a friend who suggested it would be much more re-usable if I restructured the project to be more OOR, which I am now attempting.
The problem I have is when I add a 'Guild' instance to my map, the first one works fine, but the second causes a type error that is giving me a headache!
I will post all of the relevant code from the different files below, but the overall structure is as follows:
Map.js = Map class file, container for setting the map overall size and iterating over (and placing) map objects.
MapObject.js = Class file for simple map objects such as walls, contains the position and icon properties.
Guild.js = Class file, extends MapObject.js, this is where my problem seems to be, adds an additional 'MapIcon' and will have other features such as levels and names etc.
map-maker.js = Main file for generating the map-maker page, utilises the above class files to create the map.
Below is the code used to create an instance of 'Guild' on my map:
map-maker.js (creating the map / map object / guild instances)
// Initialise a new instance of map class from Map.js using the user
provided values for #mapX and #mapY.
var currentMap = new Map(XY,40);
// Get the user box volume * map boxsize from Map.js
currentMap.calcDimensions();
// Create a Map Object and push it to the currentMap with its position.
function createMapObject(x,y,floor){
currentMap.objects.push(new MapObject(x,y,floor));
}
// Create a Guild Object (extension of Map Object) and push it to the currentMap with its position.
function createGuildObject(x,y,floor){
currentMap.objects.push(new Guild(x,y,floor));
}
....
case 13: // Enter Key (Submit)
unhighlightTools();
currentMap.drawMap();
if(currentFloor != null){
currentFloor.hasFloor = true;
if(currentFloor.tileName == "Guild"){
createGuildObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}else {
createMapObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}
console.log("Map object created at - X:"+currentFloor.position.x+" Y:"+currentFloor.position.y);
}
currentFloor = [];
highlightTools();
break;
}
Guild.js (constructor and assigning map icon)
class Guild extends MapObject {
constructor(x,y,floor) {
super(x,y,floor);
this.levels = [];
}
mapIcon() {
this.mapIcon = new Image();
this.mapIcon.src = "../images/mapSprites/obj-1.png"
return this.mapIcon;
}
}
MapObject.js (position setup and constructor)
class MapObject {
constructor(x,y,floor) {
this.position = {x, y};
this.icon = this.wallFloorIcons(floor);
}
wallFloorIcons(floor) {
this.img = new Image();
this.name = "";
this.name += (floor.wallNorth) ? 'n' : '';
this.name += (floor.wallEast) ? 'e' : '';
this.name += (floor.wallSouth) ? 's' : '';
this.name += (floor.wallWest) ? 'w' : '';
this.name = 'wall-'+this.name+'.png';
if(this.name == 'wall-.png'){
this.img.src = "../images/mapSprites/floor.png";
}else {
this.img.src = "../images/mapSprites/"+this.name;
}
return this.img;
}
getIcon() {
return this.img;
}
}
Map.js (processing the objects at a given location and drawing the canvas)
class Map {
// Map Width / Height and number of boxes. Used to generate map and zoom level.
constructor(wh, boxSize) {
this.size = wh;
this.width = wh[0];
this.height = wh[1];
this.boxSize = boxSize;
this.objects = [];
this.boxes = wh[0];
}
// Calculates the width and height * boxSize for rendering the canvas.
calcDimensions(){
this.realX = Math.floor(this.width * this.boxSize);
this.realY = Math.floor(this.height * this.boxSize);
this.realX = parseInt(this.realX,10);
this.realY = parseInt(this.realY,10);
this.realXY = [
this.realX,
this.realY
];
return this.realXY;
}
// Draws the canvas, grid and adds the objects that belong to the map.
drawMap(){
var self = this;
self.canvas = document.getElementById("canvas");
self.c = self.canvas.getContext("2d");
self.background = new Image();
self.background.src = "../images/mapSprites/oldPaperTexture.jpg";
// Make sure the image is loaded first otherwise nothing will draw.
self.background.onload = function(){
self.c.drawImage(self.background,0,0);
self.fillMap();
}
}
fillMap(){
var self = this;
self.c.lineWidth = 1;
self.c.strokeStyle = 'black';
self.c.fillStyle = "rgba(255, 255, 255, 0.2)";
for (var row = 0; row < self.boxes; row++) {
for (var column = 0; column < self.boxes; column++) {
var x = column * self.boxSize;
var y = row * self.boxSize;
self.c.beginPath();
self.c.rect(x, y, self.boxSize, self.boxSize);
self.c.stroke();
self.c.closePath();
for (var i=0; i<self.objects.length; i++) {
var floor = self.objects[i];
if (floor.position.x == column && floor.position.y == row) {
if (self.objectsAtPosition({x:floor.position.x, y:floor.position.y}) != null) {
var mapObjects = self.objectsAtPosition({x:floor.position.x, y:floor.position.y})
for (var mapObject of mapObjects) {
this.c.drawImage(mapObject.getIcon(), x, y, self.boxSize, self.boxSize);
console.log(mapObject);
if(mapObject instanceof Guild){
console.log(mapObject);
this.c.drawImage(mapObject.mapIcon(), x, y, self.boxSize, self.boxSize);
}
}
}
}
}
}
}
}
deleteObject(pos){
this.objectsAtPosition(pos);
for( var i = 0; i < this.objects.length; i++){
if(this.objects[i] == this.objs){
delete this.objects[i];
this.objects.splice(i,1);
}
}
}
objectsAtPosition(position) {
var objs = [];
for (var o of this.objects) {
if (o.position.x == position.x && o.position.y == position.y) {
objs.push(o);
}
}
return objs;
}
}
When I run the code, this is my error:
Uncaught TypeError: mapObject.mapIcon is not a function
at Map.fillMap (Map.js:70)
at Image.self.background.onload (Map.js:39)
The error comes after I add 1 guild then try to add any other map object. Guild or otherwise.
Sorry if this question is a little vague, I'm still learning (as you can see :p).
Thanks for your time!
Earl Lemongrab

Got a solution from a friend in the end.
The issue was that I was reassigning this.mapIcon = new Image() so it exploded when it was called a second time.
Feel pretty silly for not spotting it.
Thanks for the help everyone.

Related

how to prevent overlapping of 2 components

how to prevent overlapping of 2 components, please help me to make them follow Mouse but not overlap. i am not expert in coding, please explain in simple language.
function component(x,y,r) {
var randomcolor = ["violet","indigo","blue","green","yellow","orange","red"];
this.pos=createVector(x,y);
this.r=r;
this.color=randomcolor[Math.floor(Math.random()*randomcolor.length)];
this.show=function() {
fill(this.color);
stroke(241,241,241);
ellipse(this.pos.x,this.pos.y,this.r*2,this.r*2);
}
this.crash = function(other) {
var d = p5.Vector.dist(this.pos,other.pos);
if (d<this.r+other.r) {
this.r+=other.r/20;
return true;}
}
this.move=function(){
this.pos.x=lerp(this.pos.x,mouseX,0.1);
this.pos.y=lerp(this.pos.y,mouseY,0.1);
this.pos.x = constrain(this.pos.x,this.r,width-this.r)
this.pos.y = constrain(this.pos.y,this.r,height-this.r)
}
}
To make multiple objects move without running into each other you will need to
keep track of the current location of all objects
be able to identify each object so that the collision method does not detect a collision of an object with itself
check to make sure there will not be a collision before attempting to move an object
For your example code this is one possibility for making multiple components move towards the mouse without running into each other. I rewrote your crash function and added some global variables. This is not elegant but I think it answers your question in a way that you can understand how this kind of problem can be approached.
var ids = 0;
var allComponents = [];
function setup(){
createCanvas(600,600);
new component(10,10,10);
new component(590,10,10);
}
function draw(){
background(255);
for (let i = 0; i < allComponents.length; i++){
allComponents[i].show();
allComponents[i].move();
}
}
function component(x,y,r) {
var randomcolor = ["violet","indigo","blue","green","yellow","orange","red"];
this.pos=createVector(x,y);
this.r=r;
this.id = ids++;
allComponents[allComponents.length] = this;
this.color=randomcolor[Math.floor(Math.random()*randomcolor.length)];
this.show=function() {
fill(this.color);
stroke(241,241,241);
ellipse(this.pos.x,this.pos.y,this.r*2,this.r*2);
}
this.crash = function(other) {
var d = p5.Vector.dist(this.pos,other.pos);
if (d< this.r + other.r) {
return true;
}
return false;
}
this.move=function(){
let originalX = this.pos.x;
let originalY = this.pos.y;
this.pos.x=lerp(this.pos.x,mouseX,0.1);
this.pos.y=lerp(this.pos.y,mouseY,0.1);
this.pos.x = constrain(this.pos.x,this.r,width-this.r);
this.pos.y = constrain(this.pos.y,this.r,height-this.r);
for (let i = 0; i < allComponents.length; i++){
let other = allComponents[i];
if (this.id !== other.id && this.crash(other)){
this.pos.x = originalX;
this.pos.y = originalY;
break;
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

Object instance is stripped of its methods [duplicate]

This question already has answers here:
socket.io - emited objects from server lose their functions and name once received by client
(1 answer)
Socket.io passing javascript object
(2 answers)
Closed 5 years ago.
For an online board game I am making, I have defined a Tile class, whose instances I use to create a "map". The Tile class has several properties, and a method called show which displays the instance on the canvas.
All the scripts are passed on to the clients as static files via script tags in the index.html which is served by node.js. The instantiation of the Tile class worked properly before I introduced the server. Now it produces some very weird and interesting results.
The HexMap class, creates upon instantiation a 2d array of Tiles as follows:
function HexMap (width, height, image) {
this.width = width;
this.height = height;
this.contents = [];
for (let i = 0; i < this.width; i++) {
this.contents[i] = [];
for (let j = 0; j < this.height; j++)
this.contents[i].push(new Tile(i, j, size, image, "SEA"));
}
}
The code for the Tile class is this:
var Tile = function (row, column, side, image, type) {
Vector.call(this, row, column);
this.unit = null;
this.canvas = null;
this.side = side;
this.type = type;
this.startingPoint = new Vector(this.x*Math.sqrt(3)*this.side + (this.y& 1) * this.side*Math.sqrt(3)/2, this.side*1.5*this.y);
this.middlePoint = new Vector(this.startingPoint.x + Math.sqrt(3)*this.side/2, this.startingPoint.y + this.side/2);
this.getOffset = function () {
return {
"SEA" : 0,
"SHORELINE" : 60,
"PLAINS" : 120,
"FOREST_LIGHT" : 180,
"FOREST_HEAVY" : 240,
"MOUNTAINS" : 300,
"SWAMP" : 360,
"MARSH" : 420
}[this.type];
};
this.getVector = function () {
return new Vector(this.x, this.y);
};
this.show = function (context) {
if(!this.canvas){
this.canvas = makeTemplate(side, this.type, image);
}
context.drawImage(this.canvas, this.startingPoint.x - this.getOffset(), this.startingPoint.y);
};
this.getPixelX = function () {
return this.x*Math.sqrt(3)*this.side + (this.y & 1) * this.side;
};
this.getPixelY = function () {
return this.side/2 + this.side*2*this.y;
};
this.setType = function (type){
this.type = type;
};
};
Printing a Tile object in the console would normally display something like this:
Tile {x: 0, y: 0, unit: null, canvas: canvas, side: 15, …}
I tried the same thing using the server this time, and the result is this:
{x: 0, y: 1, unit: null, canvas: null, side: 15, …}
Interestingly enough, the result is indeed an object, but not a Tile object. It has all the properties a Tile object has, but has none of its methods.
The error I end up getting is this:
tile.show is not a function
The map object is created and transferred to the server via socket.io sockets. The following piece of code runs on the client (the guest).
function selectGame(id) {
var hexmap = new HexMap(rowspan, colspan, spritesheet);
hexmap.generateIsland();
socket.emit('game-select', {id: id, hexmap: hexmap});
}
The server then receives the map:
socket.on('game-select', function (data) {
//The 2 sockets corresponding to the players join the same room
io.sockets.connected[data.id].join(games[data.id].identifier);
io.sockets.connected[socket.id].join(games[socket.id].identifier);
//The start-game event is emitted for just the sockets of the same room
io.to(games[socket.id].identifier).emit('start-game', {
map: data.hexmap
});
});
Then both the clients receive it again:
socket.on('start-game', function (data) {
let el = document.getElementById("wrapper");
el.parentNode.removeChild(el);
hexmap = data.map;
contexts = setupCanvas();
displayMap(hexmap, contexts.mapContext);
});
What displayMap does is it iterates through the contents of the map, and displays each Tile using its show method.
I cannot pin down the problem no matter how hard i try... Any suggestion on how to solve this?

How to add a 16x16 grid of pseudo-random tiles as a background in canvas (or other web language)

I have recently been working on a web based project using canvas on HTML5. The program consists of a 16x16 grid of tiles that have been pseudo-randomly generated. I am relatively new to canvas, but have built this program in several other environments, none of which however compile successfully to a web based language. this is the main code section that is giving me bother:
var A = 8765432352450986;
var B = 8765432352450986;
var M = 2576436549074795;
var X = 1;
var rx = 0;
var ry = 0;
this.image = new Image();
var i = 0;
var ii = 0;
while(i < 16)
{
while(ii < 16)
{
this.image = new Image();
this.image.src = "textures/grass.png";
x = (((A*X)+B)%M)%M;
if((x/2)%1 == 0)
{
this.image.src = "textures/grass.png";
}
if((x/8)%1 == 0)
{
this.image.src = "textures/hill.png";
}
if((x/21)%1 == 0)
{
this.image.src = "textures/trees.png";
}
if((x/24)%1 == 0)
{
this.image.src = "textures/sea.png";
}
if((x/55)%1 == 0)
{
this.image.src = "textures/mountain.png";
}
if((x/78)%1 == 0)
{
this.image.src = "textures/lake.png";
}
if((x/521)%1 == 0)
{
this.image.src = "textures/volcano.png";
}
if((x/1700)%1 == 0)
{
this.image.src = "textures/shrine.png";
}
if((x/1890)%1 == 0)
{
this.image.src = "textures/outpost.png";
}
if((x/1999)%1 == 0)
{
this.image.src = "textures/civ.png";
}
ctx = myGameArea.context;
ctx.drawImage(this.image,rx, ry, 20, 20);
ii ++;
rx += 20;
}
i ++;
rx = 0;
ry += 16;
}
I would like canvas to draw along the lines of this code above, effectively generating a grid like this
pre generated grid image
(please try and ignore the obvious bad tile drawings, I planned on either finding an artist or trying slightly harder on them when I get the game fully working.)
The black square is a separate movable object. I haven't got as far as implementing it in this version, but if you have any suggestions for it please tell me
in the full html file I have now, the canvas renders but none of the background (using the w3schools tutorials, I can make objects render however)
In short: how do I render a background consisting of a 16x16 grid of pseudo-random tiles on an event triggered or on page loaded, using canvas or if that does not work another web based technology
Thank you for your time.
A few problems but the main one is that you need to give an image some time to load before you can draw it to the canvas.
var image = new Image();
image.src = "image.png";
// at this line the image may or may not have loaded.
// If not loaded you can not draw it
To ensure an image has loaded you can add a onload event handler to the image
var image = new Image();
image.src = "image.png";
image.onload = function(){ ctx.drawImage(image,0,0); }
The onload function will be called after all the current code has run.
To load many images you want to know when all have loaded. One way to do this is to count the number of images you are loading, and then use the onload to count the number of images that have loaded. When the loaded count is the same as the loading count you know all have loaded and can then call a function to draw what you want with the images.
// Array of image names
const imageNames = "grass,hill,trees,sea,mountain,lake,volcano,shrine,outpost,civ".split(",");
const images = []; // array of images
const namedImages = {}; // object with named images
// counts of loaded and waiting toload images
var loadedCount = 0;
var imageCount = 0;
// tile sizes
const tileWidth = 20;
const tileHeight = 20;
// NOT SURE WHERE YOU GOT THIS FROM so have left it as you had in your code
// Would normally be from a canvas element via canvasElement.getContext("2d")
var ctx = myGameArea.context;
// seeded random function encapsulated in a singleton
// You can set the seed by passing it as an argument rand(seed) or
// just get the next random by not passing the argument. rand()
const rand = (function(){
const A = 8765432352450986;
const B = 8765432352450986; // This value should not be the same as A?? left as is so you get the same values
const M = 2576436549074795;
var seed = 1;
return (x = seed) => seed = ((A * x) + B) % M;
}());
// function loads an image with name
function addImage(name){
const image = new Image;
image.src = "textures/" + name + ".png";
image.onload = () => {
loadedCount += 1;
if(loadedCount === imageCount){
if(typeof allImagesLoaded === "function"){
allImagesLoaded();
}
}
}
imageCount += 1;
images.push(image);
namedImages[name] = image;
}
imageNames.forEach(addImage); // start loading all the images
// This function draws the tiles
function allImagesLoaded(){ /// function that is called when all the images have been loaded
var i, x, y, image;
for(i = 0; i < 256; i += 1){ // loop 16 by 16 times
ctx.drawImage(
images[Math.floor(rand()) % images.length]; //random function does not guarantee an integer so must floor
(i % 16) * tileWidth, // x position
Math.floor(i / 16) * tileHeight, // y position
tileWidth, tileHeight // width and height
);
}
}

Create viewport for map loaded from tiled Javascript

I have created a loader for tiled in the JSON format for my JavaScript game. The game is based on a 2048 x 1280 map and my canvas size is 704 x 608.
So my predicament is how would i create a viewport/camera when the player approaches the edge of the canvas ,so that the map will move?
In my situation, it is a little more difficult, because im not using a standard 2D array like most of the tutorials, so it is confusing me very much.
The code for loading my map is like so:
function Level (){
var data;
this.tileset = null;
var returned;
var t = this;
this.load = function(name){
$.ajax({
type:"POST",
url: "maps/" + name + ".json",
dataType: 'JSON',
async: false,
success: function(success){
returned = loadTileset(success);
}
});
return returned;
};
function loadTileset(json){
data = json;
Level.tileset = $("<img />", { src: json.tilesets[0].image })[0]
Level.tileset.onload = done = true;
if(done){
return data;
}
else
return 0;
}
this.draw_layer = function(layer){
if (layer.type !== "tilelayer" || !layer.opacity) { return; }
// if (layer.properties && layer.properties.collision) {
// return;
// }
var size = data.tilewidth;
layer.data.forEach(function(tile_idx, i) {
if (!tile_idx) { return; }
var img_x, img_y, s_x, s_y,
tile = data.tilesets[0];
tile_idx--;
img_x = (tile_idx % (tile.imagewidth / size)) * size;
img_y = ~~(tile_idx / (tile.imagewidth / size)) * size;
s_x = (i % layer.width) * size;
s_y = ~~(i / layer.width) * size;
ctx.drawImage(Level.tileset, img_x, img_y, size, size,
s_x, s_y, size, size);
});
};
this.collisionLayer = function(layer){
var row = [];
t.solids = [];
layer.data.forEach(function(idx, i) {
var s_x,s_y,
size = 32;
s_x = (i % layer.width) * size;
s_y = ~~(i / layer.width) * size;
if (i % layer.width === 0 && i) {
t.solids.push(row);
row = [];
}
row.push([idx,s_x,s_y]);
});
t.solids.push(row);
};
return t.solids;
}
And this Level class is called from my main javascript file like this:
layer = scene.load("level_"+level);
layer = $.isArray(layer) ? layer : layer.layers;
layer.forEach(function(layer){
if(layer.name == "v.bottom" && layer.type == "tilelayer"){
b_layer = layer;
}
else if(layer.type == "tilelayer" && !layer.properties){
t_layer.push(layer);
}
else if (layer.type == "objectgroup"){
$.each(layer.objects, function(i, value){
coll_layer.push([value.x,value.y,value.width,value.height,"dialogue"]);
});
}
});
And then scene.draw_layer() is called from my update function lower in my main javascript function.
My map holds different layers and you can see in the code above there is and if statement determining where the layer is the bottom layer, top layer or and object layer and storing it in the specific array. This is just so the player is shown between layers.
So to clarify, for my game, how would i create a view port/camera according to my map loader?
Also do i move the map or the player?
And should i keep the player centered? if the player is centered then what happens when the edge of the map is visible?
Here is a live version of the game so you can see how it works etc:
LIVE DEMO
Use guest as the username and password
Thankyou very much
UPDATE
I have made a small progression i believe but with big consequences. I have tried implementing a viewport from a JSFIDDLE on one of the questions on here, its designed to work with a 2D array, but thought id give it a go.
and the result can be found here:
Updated link
If anyone could have a look to see where im going wrong it would be much appreciated. Thanks

Javascript memory leak deleting Raphael path objects

I have a memory leak in my code and I cannot figure out what is causing it. I am receiving target data from a WebSocket which contains basically an ID and an X,Y coordinate. The data is passed to a HandleData function which creates a circle for each target and a line (which is updated) to show where the target has moved from/to.
If a target no longer appears in the WebSocket stream it is removed. On testing I have found the webpage quickly amasses hundreds of mb of data in spite of me removing these items. Upon using Chrome's memory profiler it seems Raphael (or something) is holding onto all the path information in spite of me deleting it.
If anyone can help me in anyway I would be very grateful. It does seem that Raphael is holding onto the data but there is every chance I have made a mistake somewhere in my code :)
var arrayColours = ["#f33", "#3f3", "#33f", "#f66", "#6f6", "#66f"];
var targetStructArray = [];
function HandleTargetData(data) {
//Go through all our existing targets and mark them as not updated
for (var i = 0; i < targetStructArray.length; i++) {
targetStructArray[i].updated = false;
}
for (var i = 0; i < data.targets.length; i++) {
var targetData = data.targets[i];
var targetStruct = undefined;
//find our targetStruct
for (var j = 0; j < targetStructArray.length; j++) {
if (targetStructArray[j].id == targetData.id) {
targetStruct = targetStructArray[j];
break;
}
}
//If it doesnt exist, create it
if (!targetStruct) {
var path = paper.path();
path.attr({ "stroke-width": "2", "stroke": "#888" });
path.addPart(['M', targetData.x, targetData.y]);
var circle = paper.circle(targetData.x, targetData.y, 15, 15).attr({
stroke: "none",
fill: arrayColours[Math.floor(Math.random() * arrayColours.length)] //random colour
});
//Create our struct
targetStruct = {
circle: circle,
path: path,
id: targetData.id,
updated: false
};
targetStructArray.push(targetStruct);
}
else {
targetStruct.circle.attr({ cx: targetData.x, cy: targetData.y });
targetStruct.path.addPart(['L', targetData.x, targetData.y]);
}
//ensure we are set as updated
targetStruct.updated = true;
}
//Go through all our existing targets and delete any that werent updated
for (var i = targetStructArray.length - 1; i >= 0; i--) {
if (!targetStructArray[i].updated) {
targetStructArray[i].circle.remove();
targetStructArray[i].path.remove();
targetStructArray[i].circle.removeData();
targetStructArray[i].path.removeData();
targetStructArray[i].circle = null;
targetStructArray[i].path = null;
targetStructArray[i] = null;
targetStructArray.remove(i);
}
}
}
I use two functions not listed here which are John Resig's Array.Remove and a Raphael helper function from a different question

Categories

Resources