I am working on a game in javascript that spawns and terminates shapes. Therefore, I need full control over these shapes that are being spawned. I want to do the following, but I am not sure on how to do it:
I want to define a constructor Shape.
Then, I want to make a function so that when it is called, it applies to ALL of the Shape in existence and being able to change each ones attribute. (For example, I call function moveAll() and all of the shapes' this.y increments by one.)
I also want to be able to terminate a specific Shape with a function, or add a Shape with a function.
My main problem is this:
var x = new Shape();
var y = new Shape();
...
I don't want to make a million variables. I need a way to make tons of shapes at once and still be able to control each one individually. And if I made an array, how would I control everything individually?
Anything helps, I just need a basis to understand the concept of constructors. Thanks in advance!
You could make an array of shapes:
let shapes = []
// this will create 6 shapes
for(let i of [0,1,2,3,4,5]) {
shapes.push(new Shape())
}
If a shape has an id you can terminate it:
// incrementing id counter
let id = 0
// x and y are optional starting positions
function Shape(x, y) {
this.id = id++
this.y = y || 0
this.x = x || 0
}
// remove the shape from the array
function terminate(id) {
shapes = shapes.filter(shape => shape.id !== id)
}
// or add a shape
function spawn() {
shapes.push(new Shape())
}
Now you have your shapes.
The simplest way to move all shapes would be something like this:
function moveAll() {
shapes.forEach(shape => shape.y++)
}
Runtime for this will increase as the number of shapes increase.
updated moveAll, per request
function moveAll() {
shapes.forEach(shape => {
if(shape.type === true) {
shape.y++
} else {
shape.y--
}
})
}
Related
I am building an project for which I need a ball which moves up and down based on the frequency of microphone input. I am using p5.js library for it.
Ball Movement: I want to take average of frequencies for each second and change the y-axis placement of the ball based on that.
Here is the code that I have written as of now.
var mic;
var fft;
const average = arr => arr.reduce((a,b) => a + b, 0) / arr.length;
function setup() {
createCanvas(700, 700);
mic = new p5.AudioIn();
buttonStart = createButton('start')
buttonStop = createButton('stop')
buttonStart.mousePressed(() => {
mic.start();
})
fft = new p5.FFT(0, 32);
fft.setInput(mic);
buttonStop.mousePressed(() => {
mic.stop();
})
}
function timeout(freqs) {
var avgFreq = average(freqs);
console.log(avgFreq);
fill(0);
ellipse(50, avgFreq, 30, 30)
}
function draw() {
background(220);
let temp = [];
let freqs = fft.analyze();
temp.push(average(freqs));
setInterval(timeout(temp),1000);
console.log(temp);
}
This doesn't seem to work very well, each time the draw function is called then its calling the timeout function as well without waiting for 1 second which is specified in setInterval()
You have a basic JavaScript mistake: when you pass a function to another function for it to call later (i.e. the first argument of setInterval) you either need to pass it by name: setInterval(timeout, 1000), or use an arrow expression (a.k.a. lambda function): setInterval(() => timeout(temp), 1000).
Otherwise you are just calling timeout immediately and passing the result, which is undefined, to setInterval().
That said, there are definitely several other issues and it isn't clear what exactly you are trying to accomplish.
You are going to have lots of delayed calls to this timeout function (~60 per second) but these will basically just amount to delayed rendering
The temp array is always going to contain a single value because you declare it and add a single item to it in each call to draw()
the FFT.analyze functions is going to return a list of buckets representing different frequency ranges and the value for each bucket is going to be the amplitude of that bucket. So the average of those values does not change with frequency but with volume.
I have a big data to handle.
They need to be classified into 4 colors, and render to the SVG.
My function is:(parameter A B C are used to do something....)
function mapRender(dataArray,A,B,color1,color2,color3,color4,C){
//......do somthing.......
var colorPlusAry = $.grep(dataArray, function(item,key){
var vote_percentage = parseInt(item.vote_percentage);
var result = vote_percentage>=0 && vote_percentage <50;
return result;
});
//......do somthing.......
}
I use grep to generate the new array which the item has same color, and render to SVG.
function colorDistrict(colorArray,color){
var village = '';
var fillColor = 'fill:'+color;
for(var item in colorArray) {
village = colorArray[item].village;
$('svg').find('path').each(function(){
var id = $(this).attr('id');
if(village) {
if(id.substring(6) === village){
$(this).attr('style',fillColor);
}
}
});
}
}
colorDistrict(colorPlusAry,color1); //Render 4 array
It works successfully, but the data is too large and make render slowly, when I trigger the function, it takes several seconds to react...
How can I optimize this function to render the color?
Optimization is a difficult business without the real data, and without knowing the precise DOM structure. I can only give some hints from what I see:
The costly process is interacting with the DOM. Looking at the colorDistrict() function, the two loops seem to be independent. It would then make sense to run the .each() loop only once, and the loop over the colorArray as the nested one. The latter only contains precomputed values and should run much faster.
Looking at what these loops really do, you can write them much more semantic. You compare the collection of all paths and the colorArray for intersection, and then assign a style to a filtered list of paths.
function colorDistrict (colorArray, color){
var fillColor = 'fill:' + color;
// filter the paths to find those mentioned in colorArray
var relevantPaths = $('svg').find('path').filter(function () {
var village = $(this).attr('id').substring(6);
// boolean to indicate the village is part of the colorArray
var included = colorArray.some(function (item) {
return item.village === village;
});
return included;
});
relevantPaths.attr('style', fillColor);
}
If I understand correctly what you are doing, the colorDistrict() function is executed multiple times, once for every color you assign. Do the <path> elements change between rendering the different colors? If not, you should execute $('svg').find('path') only once and cache the found paths for reuse inside that function.
var paths = $('svg').find('path');
function colorDistrict (colorArray, color){
var fillColor = 'fill:' + color;
var relevantPaths = paths.filter(function () {
...
});
}
I made a code with the intention of having a square appear where the mouse is pressed down, stay in that spot despite mouse movement and not disappear when the mouse it released.
THIS IS P5.JS ! https://p5js.org/reference/
Instead, the square follows the mouse until it is released then it disappears!
I believe that my code keeps declaring a new constant and deleting the old one every time the shoot() function is run.
var clocker = 0;// NOT YET USED
var player = {
x:400,
y:400,
};
function shoot(x1, y1, x2, y2, speed, range, power, playerDirection){
var bulletAlive = true;
var bulletDamage = power;
const startX = x1;
const startY = y1;
const destX = x2;
const destY = y2;
var bulletX = startX;
var bulletY = startY;
if(bulletAlive){
fill(0,100,200);
rect(destX-12.5,destY-12.5,25,25);
};
};
function setup() {
createCanvas(1000,650);
}
function draw() {
background(204,204,204);
if(mouseIsPressed){
shoot(player.x,player.y,mouseX,mouseY,2,100,0,"right");
}
}
Perhaps I am using const wrong. If so how should I use it? How can I make it so that destX and destY don't change? (Don't follow mouse or disappear)
PS: sorry for the miscellaneous information, this is supposed to build up to simple bullet physics.
It sounds like there is some confusion about scoping, and there is probably a better way to think about this problem.
First let's look at what is going wrong, and talk through a few details to explain why.
Just like variables (let, var), constants are always declared in a specific scope.
Scopes are like containers for constants and variables. Scopes are private, that is they cannot be accessed from the outside. Scopes can be created and destroyed.
When you declare a constant directly inside a function, the scope is the function itself (like startX inside shoot). (Note that if you declare a constant inside an if statement or other block, the scope is the block. That's not the case here, though.)
Function scopes are created each time the function is called, and destroyed when the function is finished executing.
Each time a function is called and its scope is created, all constants (and variables) are reinitialized with new values.
A constant appearing in your code may have different values during different function calls. It is only constant during its lifetime, which in your case is a single given execution of the function.
This is why your constants aren't staying constant. You are calling shoot() repeatedly while the mouse is down, and so the constants are repeatedly being recreated and assigned new values.
With this information, hopefully you can see the problems with the current approach. As for a solution, let's think about what exactly is happening. shoot() is an action that should be triggered when the user issues a "shoot" command, such as a mouse click. The draw() function is a continuous event triggered to say "hey, update the screen". Putting the shoot action inside the draw event is kind of a mis-match of intentions and is the root of struggles like this.
Instead, let's introduce the idea of a bullet object. A bullet has an x and a y value. A bullet is created when the user shoots, and is given a specific x and y value at the moment of creation. None of this happens inside draw, it happens in another event listener such as "click".
The job of draw is to check to see if there is an active bullet, and if there is one, draw it at the specified x and y coordinate. If there is no bullet, do nothing. (Of course you might need to draw other things as well, but that's unrelated to drawing the bullet).
Keeping object creation and object drawing separate makes it easier to have the kind of control you're looking for.
Edit: Adding some code examples
Here's what the code would look like to do exactly what you asked, using the bullet object idea above. The inline comments should explain each step.
// This is where we'll store an active bullet object.
// The `shoot()` function is responsible for setting this.
// `draw()` is responsible for rendering the bullet.
// Initially we'll set the value to `null` to explicitly
// indicate that there is no bullet.
let activeBullet = null;
// The purpose of `shoot()` is to create a bullet
// and make it available to be rendered.
function shoot(x, y) {
// Create the bullet object.
const newBullet = {
x: x,
y: y,
size: 25
};
// Set the active bullet to the new bullet. This will
// cause any previously active bullet to disappear.
activeBullet = newBullet;
}
// P5 functions
// ------------
function setup() {
createCanvas(1000, 650);
}
// Shoot when the player clicks.
function mousePressed() {
shoot(mouseX, mouseY);
}
function draw() {
// Always start with a blank canvas.
clear();
// If there is an active bullet, draw it!
// (`null` is "falsy", objects are "truthy", so the
// `if` statement will only run after the `activeBullet`
// variable is assigned a bullet object.)
if (activeBullet) {
fill(0, 100, 200);
rect(
activeBullet.x - activeBullet.size / 2,
activeBullet.y - activeBullet.size / 2,
activeBullet.size,
activeBullet.size
);
}
}
You also mentioned you wanted to build up to simple bullet physics. Just to show how the bullet object idea works nicely, here's a demo where you can click to shoot multiple bullets, they all move independently, and collide with a wall at which point they are removed. There's a lot more involved in building games, but hopefully it's an inspiring starting point :)
// Store canvas dimensions globally so we have easy access.
const canvasWidth = 1000;
const canvasHeight = 650;
// We'll add a "wall" object so we have something the bullets can
// collide with. This value is the X position of the wall.
const wallX = canvasWidth - 200;
// Instead of a single bullet, using an array can accommodate
// multiple bullets. It's empty to start, which means no bullets.
// We can also use `const` for this, because we won't ever assign
// a new value, we'll only modify the contents of the array.
const activeBullets = [];
function shoot(x, y) {
// Create the bullet object.
const newBullet = {
x: x,
y: y,
size: 25,
speed: 4
};
// Instead of overwriting a single bullet variable, we'll push
// the new bullet onto an array of bullets so multiple can exist.
activeBullets.push(newBullet);
}
// P5 functions
// ------------
function setup() {
createCanvas(canvasWidth, canvasHeight);
}
// Shoot when the player clicks.
function mousePressed() {
shoot(mouseX, mouseY);
}
function draw() {
// Always start with a blank canvas.
clear();
// Draw our "wall".
fill(50);
rect(wallX, 0, 60, canvasHeight);
// Set the fill color once, to use for all bullets. This doesn't
// need to be set for each bullet.
fill(0, 100, 200);
// Loop through the array of bullets and draw each one, while also
// checking for collisions with the wall so we can remove them. By
// looping backwards, we can safely remove bullets from the array
// without changing the index of the next bullet in line.
for (let i=activeBullets.length-1; i>=0; i--) {
// Grab the current bullet we're operating on.
const bullet = activeBullets[i];
// Move the bullet horizontally.
bullet.x += bullet.speed;
// Check if the bullet has visually gone past the wall. This
// means a collision.
if (bullet.x + bullet.size / 2 > wallX) {
// If the bullet has collided, remove it and don't draw it.
activeBullets.splice(i, 1);
} else {
// If the bullet hasn't collided, draw it.
rect(
bullet.x - bullet.size / 2,
bullet.y - bullet.size / 2,
bullet.size,
bullet.size
);
}
}
}
The const declaration exists only within the scope of shoot. So once the shoot function is finished executing, startX startY destX destY, being const, are deleted.
Possible fix:
var didShootAlready = false;
var startX, startY, destX, destY;
function shoot(/*params*/){
if(!didShootAlready){
didShootAlready = true;
startX = x1;
startY = y1;
destX = x2;
destY = y2;
}
//do the rest
}
I have an svg path which I can draw. With d3js I calculate a convex hull around the path with d3.geom.hull(...). Now I have some svg objects like nodes (svg:circle) and I want to find out whether the node is in the hull or not. How can I realize that? Here is a picture from my svg view:
EDIT:
My goal is to the node elements in the hull (which are within the path), not only at the edge of the path.
Here's an easy way to do that:
Calculate your hull geometry, get back the coordinates array that d3.geom.hull gives you.
Add your new point to your original data array and calculate d3.geom.hull again on this array.
Compare the array of points returned from step 1 with the array of points returned from step 2 to see if the calculated hull has changed. If it has, then the point is outside the convex hull. If there is no change, then it's inside the convex hull.
This might be performance-intensive if you have a really large dataset.
Here's some simple code to demonstrate:
// Some Random points
var coords = d3.range(50).map(function() {return [Math.random(),Math.random()]})
yourHull = d3.geom.hull(coords)
// Random new point
newCoord = [Math.random(),Math.random()]
coords.push(newCoord)
newHull = d3.geom.hull(coords)
//The easy case to spot
if (newHull.length != yourHull.length) {
console.log("Outside")
}
//If the array lengths are the same, the point values may have changed
else {
var outside = false;
for (var x = 0; x < yourHull.length;x++) {
for (var y = 0; y < 2;y++) {
if (yourHull[x][y] != newHull[x][y]) {
outside = true;
break;
}
}
}
if (outside) {
console.log("outside")
}
else {
console.log("on the hull")
}
}
The fastest way of doing this is to have the browser do all the actual work. In particular, use the method document.getElementFromPoint() to have the rendering engine determine the overlap.
The idea is simple -- add the point you're interested in behind the hull, then check whether the above method gives you the point or the hull. The code looks like this.
function isInside(point) {
var c = svg.insert("circle", "path.hull")
.attr("r", 1)
.attr("cx", point[0])
.attr("cy", point[1]);
var bounds = c.node().getBoundingClientRect();
var atPoint = document.elementFromPoint(bounds.left, bounds.top);
var inside = atPoint == c.node() ? false : true;
c.remove();
return inside;
}
The only slightly tricky bit is to convert the relative coordinates of the point to absolute coordinates -- the above code assumes that the SVG is a top-level element on the page itself or not translated by the containing elements. If this is not the case, adjust the code as appropriate.
The big advantage over the other answer is that the runtime does not depend on the size of the hull (as in the number of points defining it). It only depends on the number of points you want to check.
Complete demo here.
I am currently working on a private side/fun-project which is a small browser-game based on javascript and jQuery.
for this game i am saving data to an array of objects:
this.map.push(
{
"id" : id,
"x" : pos_x,
"y" : pos_y
}
);
now i need to find and/or update data in this array quite frequently (in "real time").
for example when i need to find the array-index at a position of my coords-system, i am using jQuery's $.each() function:
this.getIndexAtPos = function(x, y)
{
var index = false;
$.each(this.map, function(key, obj){
if(obj.x == x && obj.y == y)
{
index = key;
return false;
}
});
return index;
}
and another example: when i need to update data for an object with a specific id, i am using jQuery's $.map() function:
this.updateCoordsById = function(id, x, y)
{
this.map = $.map(this.map, function(obj, i)
{
if(obj.id == id)
{
//only update x, y!
obj.x = x;
obj.y = y;
return obj;
}
else
{
return obj;
}
});
}
it's all working fine for me so far, but i am on a rather fast computer and as the game is extending there will be more and more of these actions. like let's say a couple of hundred $.each and $.map calls per second.
that's why i am worried about performance issues, specially on slower computers.
MY QUESTIONS:
i know that $.each and $.map are (partly or fully) iterating my array on each call. that's why i used return false; inside my $.each function as soon as the entry is found to speed up things a little.
1. can i improve the performance by using other jQuery functions or changing the way i use them?
2. are there better ways (performance wise) to solve this by using native javascript?
3. should i use other datatypes/structures to boost performance?
note: the procedures i need to implement always are quite similar: find objects in the array by coords(x, y); find them by id; update objects by coords(x, y); update objects by id, and so on.
i would appreciate any help, opinions and suggestions!
OK as this is a grid it makes sense to store it as a grid in memory.
A 2-dimensional array will give you much faster access than a one-dimensional one, and also lets you access object directly by their coordinates instead of checking the coordinates of each object.
You can also have an container object which contains all your objects, with their ID as the property, which gives you fast lookup by ID.
You can put these together by storing the ID of the object in the grid, then looking up the object in the container by the ID.
I've created a fiddle at http://jsfiddle.net/d75zkvnb/1/ which shows a simple example of this. The structure of the Map object is below:
var Map = {
init: function(){
this.Width = 10;
this.Height = 10;
// grid stores the coordinates and the IDs of the object at that coordinate
// note this is stored by y then x.
this.grid = [];
// stored the actual objects, indexed by id
this.objects = {};
// set up the grid
for(var y = 0; y < this.Height; ++y)
{
this.grid.push(new Array(this.Width));
}
},
// gets the object at (x, y)
GetAtCoord: function(x, y)
{
// find the id
var id = this.grid[y][x];
// directly access it from the array
return this.objects[id];
},
// gets the object with the ID
GetById: function(objId)
{
// direct access to the object
return this.objects[objId];
},
// add an object at its stored coordinates
AddObject: function(obj){
this.grid[obj.y][obj.x] = obj.id;
this.objects[obj.id] = obj;
},
// Move an object in the grid
MoveObject: function(objId, newX, newY)
{
// get the objct to move
var theObj = this.objects[objId];
// reove it from the grid
this.grid[theObj.y][theObj.x] = null;
// updates its stored position
theObj.x = newX;
theObj.y = newY;
// add it back to the grid
this.grid[newY][newX] = objId;
}
};