I have an object called Scene and it stores screen dimensions as well as an array of Shape objects, each of which have an x and y coordinate. I would like to scramble a Scene object and get a scrambledScene object so that I can compare the scrambledScene to the original. Here is the function I've come up with:
function scrambleScene(scene) {
var scrambledScene = {...scene};
for(var i = 0; i < scene.shapes.length; i++) {
scrambledScene.shapes[i].x = getRandInt(0, scene.width);
scrambledScene.shapes[i].y = getRandInt(0, scene.height);
}
return(scrambledScene);
}
The problem is when this function is run, it returns an object that is identical to the Scene object that was passed to it. Nothing was changed. Here are definitions of the functions I used. I am quite new to OOP so feel free to give pointers (unlike JS, why does var clone = object just return a pointer and not another object?!!?!?!!).
function getRandInt(min, max) {
return(Math.floor(Math.random() * (max - min + 1)) + 1);
}
function Shape(shapeX, shapeY, shapeSize, shapeType) {
this.x = shapeX;
this.y = shapeY;
this.size = shapeSize;
this.type = shapeType;
//circle uses arc() function, which takes the center of the circle as a reference
//circle sets the radius to this.size
//square uses rect() function, which take the upper left corner as a reference
//square sets the height and width of the square to this.size
if(this.type !== 'circle' && this.type !== 'square') {
console.error('Invalid shapeType: ', this.shapeType);
}
this.centerX = function() {
if(this.type === 'square') {
return(this.size/2 - this.x);
}
if(this.type === 'circle') {
return(this.x);
}
}
this.centerY = function() {
if(this.type === 'square') {
return(this.size/2 - this.y);
}
if(this.type === 'circle') {
return(this.y);
}
}
this.isInside = function(point_x, point_y) {
if(this.type === 'square') {
//canvas y increases as you go down
//if point_x is greater than x + size, then its far too right
//if point_x is less than x, then its far too left
//if point_y is greater than y + size, then its below
//if point_y is less than y, then its above
//if none of these are true then its inside
if(point_x <= x + size && point_x >= x && point_y <= y + size && point_y >= y) return(true);
}
if(this.type === 'circle') {
//checks distance from point to center of the circle
//if its greater than the radius of the circle then its outside
//if its less than or equal to the radius then its inside
const distance = Math.sqrt(Math.pow(point_x - x, 2) - Math.pow(point_y - y, 2));
if(this.distance >= size) return(true);
}
}
}
function Scene(numberOfShapes, sceneWidth, sceneHeight) {
this.shapes = [];
for(var i = 0; i < numberOfShapes; i++) {
var shapeType = Math.random() < 0.5 ? 'square' : 'circle';
this.shapes[i] = new Shape(getRandInt(0, sceneWidth), getRandInt(0, sceneHeight), 100, shapeType)
}
this.width = sceneWidth;
this.height = sceneHeight;
}
So the Object was being changed, it was just that the clone of that Object was also being changed to be the exact same.
In the scrambleScene function I tried to create is what's called a shallow clone of the Scene object passed. What I really needed was a deep clone. A shallow clone is like if you copied a folder with files inside on Windows, but instead of actually copying the files inside, the files were replaced with shortcuts to those files. If you edit the original file and open the shortcut, you will see the edit because its just a shortcut, not a new file. A deep clone is similar to when you copy a folder with all the files inside. If you edit an original file, its copy won't be changed because they are two different files.
If you continue the analogy, when I ran the scrambleScene function it edited the original file, and when I look at the Shapes, it was the same because it was just a shortcut, not a new file. I needed to copy the file, not create a shortcut.
You can create a completely independent object by using JSON. There are some limitations, but for my case it worked.
Related
I wasn't sure how to word this, because I don't actually know what exactly causes this bug. I'm trying to put together a simple Asteroids knockoff.
When the player shoots, a new object (Bullet) is created using array.push(...). Once this bullet goes beyond the canvas (out of bounds), it is deleted using array.splice(...);
The problem is that the bullets are moving in unpredictable ways. I don't know how to word it so here's the full code (working, including html/css): https://pastebin.com/tKiSnDzX
Hold spacebar for a few seconds (to shoot) and you'll see the issue clearly. You can also use A/D to turn and W to go forward.
Here's what I think is happening. The code runs fine as long as there is only one bullet on the screen (in the array). This means that either the incorrect element is being deleted or the values that go into the constructor of the object are messed up somewhere along the way.
Exhibit A (bullet constructor and its methods):
function Bullet(x,y,rot,vel) {
this.x = x;
this.y = y;
this.rot = rot;
this.vel = (vel+5);
this.move = function() {
this.x += this.vel*Math.cos(this.rot-Math.PI/2);
this.y += this.vel*Math.sin(this.rot-Math.PI/2);
}
this.draw = function() {
engine.circle(this.x, this.y, 4, "black");
var c = engine.canvas.getContext('2d');
c.translate(this.x, this.y);
c.rotate(this.rot);
c.beginPath();
c.strokeStyle="#00FF00";
c.strokeRect(-5, -5, 10, 10);
c.closePath();
c.stroke();
}
}
Exhibit B (function that creates/deletes the bullets):
shoot: function() {
if(engine.keyDown.sp == true) {
if(this.fire > 20) {
engine.bullets.unshift(new Bullet(this.x, this.y, this.rot, this.velocity));
this.fire = 0;
} else {
this.fire++
}
}
for(i = 0; i < engine.bullets.length; i++) {
engine.bullets[i].move();
engine.bullets[i].draw();
if(engine.bullets[i].x > engine.canvas.width+5 || engine.bullets[i].x < -5
|| engine.bullets[i].y > engine.canvas.height+5 || engine.bullets[i].y < -5) {
console.log('bullet gone, '+i);
engine.bullets.splice(i, 1);
}
}
}
the array is declared like so: bullets: []
Thank you for any answers.
How about just tag any bullets that need to die as you come across them in your loop with something like engine.bullets[i].dead = true; Then, at the end outside the loop, filter out the dead bullets with engine.bullets = engine.bullets.filter(b => !b.dead);
You're using splice inside a for loop. When you remove element index 1, then element index 2 becomes index 1, and element index 3 becomes index 2. But your i variable is already 1, so the next iteration of the loop goes to the new index 2. But the new index 2 is the original index 3, and the original index 2 is skipped. You might be better served by a linked list:
var bulletList = null;
function bullet(){
this.next = bulletList;//if there was a list, record that
if (this.next)//and set that one to point back to this
this.next.prev = this;
bulletList = this;//place new bullet at start of list
this.previous = null;
this.draw = function(){
//do stuff here
this.next && this.next.draw();//call next item in the list, if any
if (shouldIBeRemoved){//placeholder, obviously
if (this.next && this.prev){//remove from middle of list
this.prev.next = this.next;
this.next.prev = this.prev;
}
else if (this.next && !this.prev){//remove from beginning of list
bulletList = this.next;
this.next.prev = null;
}
else if (this.prev && !this.next){//remove from end of list
this.prev.next = null;
}
else if (!this.prev && !this.next){//remove only item in list
bulletList = null;
}
}
}
}
then to draw every bullet, simply call:
if (bulletList)
bulletList.draw();
The problem was that I was translating the context in which all the bullets are, each time a new bullet was created. This meant that each bullet would be moved by x and y away from the previous one. It made it seem like the bullets were being created where they weren't supposed to be. The "unpredictability" was caused by the fact that the bullet takes on the player's rotation, so whenever a new bullet was created, it's rotation was increased by however much the player rotated before the new bullet was fired, on top of the previous bullet's rotation.
Putting context.save(); before the translation/rotation of the bullet's hitbox and context.restore(); after it perfectly solved the issue:
Bullet.draw = function() {
engine.circle(this.x, this.y, 4, "black");
if(hitbox == true) {
var c = engine.canvas.getContext('2d');
c.save();
c.translate(this.x, this.y);
//c.rotate(this.rot);
c.beginPath();
c.strokeStyle="#00FF00";
c.strokeRect(-5, -5, 10, 10);
c.closePath();
c.stroke();
c.restore();
}
}
Someone else mentioned that I was using array.splice(); in a for loop. This made it so that when a bullet (i) is deleted, the bullet right after (i+1) is moved one index back (i). So that bullet was essentially skipped over.
I could notice this sometimes when looking at the bullets while one was deleted- They "jumped" ahead.
The solution was to put i -= 1 after bullets.splice(i, 1);. This makes the next iteration of the loop go one index back, solving the occassional stuttering of the bullets:
shoot: function() {
if(engine.keyDown.sp == true) {
if(this.fire > 5) {
engine.bullets.unshift(new Bullet(this.x, this.y, this.rot, this.velocity));
this.fire = 0;
}
}
if(this.fire<=5) {
this.fire++
}
for(i = 0; i < engine.bullets.length; i++) {
engine.bullets[i].move();
engine.bullets[i].draw();
if(engine.bullets[i].x > engine.canvas.width+5 || engine.bullets[i].x < -5
|| engine.bullets[i].y > engine.canvas.height+5 || engine.bullets[i].y < -5) {
engine.bullets.splice(i, 1);
i-=1;
}
}
}
Thanks for all the answers.
I am working on creating a game that has to do with objects(circles) falling from the top of the canvas. I have these circles randomly generating at x coordinates and then falling at a constant rate. I am trying to write a collision algorithm but cannot seem to access the x coordinates or width/height of the circles that are falling.
This is how I created the circles and put them in an array. var projectiles = [] was declared at the top of my code already
function spawnEnemies()
{
var g1 = new createjs.Graphics();
g1.beginStroke('white').beginFill('red').drawCircle(Math.floor(Math.random() * 650) + 50, 50, 20);
var e = new createjs.Shape(g1);
projectiles.push(e);
stage.addChild(e);
}
This is my collision algorithm where I am trying to access the x and y coordinates of the circles and also retrieve their width and height. I used console.log to check and see what values are being returned. For p.x the value 0 is returned every time and p.width returns NaN. I am confused why p.x and p.width are not working.
function checkCollision()
{
for (i = 0; i < projectiles.length; i++) {
var p = projectiles[i];
if((p.x + width) < ship.x)
{
hit = false;
}
else if(p.x > (ship.x + ship.image.width))
{
hit = false;
}
else if(p.y > (ship.y + ship.image.height))
{
hit = false;
}
else if((p.y + p.height) < ship.y)
{
hit = false;
}
else
{
hit = true;
console.log(p.x);
}
}
The x and y properties of a Shape can be used to translate it. In your code, you instead leave them as the default (0) and draw the circle at a specific location in the Shape's canvas.
If you want the x to reflect the location of the circle, consider changing spawnEnemies to always draw the circle at the Shape's origin, then setting its x to the desired location.
g1.(...).drawCircle(0, 50, 20);
var e = new createjs.Shape(g1);
e.x = Math.(...);
I'm new in JavaScript and I'm creating a kind of shooting game. I'm to make 1 object to move toward another object. So the "bullet" get the location of the "prey" and it will move toward it. I have no idea how to implement that, I can't find anything similar on the internet. So, I tried at first something simpler:
In the following code the "bullet" move to the left. How can I specify to move it toward an object?
I have 2 object. It's the enemyBullet object(not the bullet object) which should go toward another object.
PS: English is not my native language. Sorry for any confusion
thanks.
this.draw = function () {
this.context.clearRect(this.x + 2, this.y + 1.5, this.width - 4.5, this.height);
//x+ the speed make it go to the left
this.x += this.speed;
if (self === "bullet" && this.x >= this.canvasWidth) {
return true;
}
else if (self === "enemyBullet" && this.x >= 1000) {
console.log(this.x);
return true;
}
else {
if (self === "bullet") {
this.context.drawImage(imageRepository.bullet, this.x, this.y);
}
else if (self === "enemyBullet") {
this.context.drawImage(imageRepository.enemyBullet, this.x, this.y);
}
return false;
}
};
Normalised vector
You need to find the normalised vector from one object to the next. A vector is just an arrow that has a direction and a length. In this case we normalise the length, that is make it equal to 1 unit long. We do this so we can easily set a speed when using the vector.
Function to return a vector from one point to the next
// Code is in ES6
// fx, fy is from coordinate
// tx, ty is to coordinate
function getNormVec(fx, fy, tx, ty){
var x = tx - fx; // get differance
var y = ty - fy;
var dist = Math.sqrt(x * x + y * y); // get the distance.
x /= dist; // normalised difference
y /= dist;
return {x,y};
}
Once you have the vector you can move an object by adding the vector times the speed. Example of creating and moving a bullet from myObj to myTarget
var myObj = {}
var myTarget = {};
var myBullet = {}
myObj.x = 100;
myObj.y = 100;
myTarget.x = 1000
myTarget.y = 1000
var vecToTag = getNormVect(myObj.x, myObj.y, myTarget.x, myTarget.y);
myBullet.nx = vecToTag.x; // set bullets direction vector
myBullet.ny = vecToTag.y;
myBullet.x = myObj.x; // set the bullet start position.
myBullet.y = myObj.y;
myBullet.speed = 5; // set speed 5 pixels per frame
To move the bullet
myBullet.x += myBullet.nx * myBullet.speed;
myBullet.y += myBullet.ny * myBullet.speed;
I am trying to write a script to place 100 circles of varying sizes onto a stage. I've outlined the concise requirements below.
Given the following:
var stage; // contains a "width" and "height" property.
var circle; // the circle class. contains x, y, radius & a unique id property.
var circleArray; // contains 100 circle instances
requirements:
write a function to place 100 circles of varying radius onto the stage.
placements must be random but evenly distributed (no clumping).
placement must be performant - this will be executing on a mobile web browser.
circles must not intersect/overlap other circles.
circle.x >= 0 must be true.
circle.y >= 0 && circle.y <= stage.height must be true.
circles may have any of the following radius sizes (assigned at creation):
150
120
90
80
65
My current attempt is a brute-force method, which does not operate efficiently. If I attempt to insert any more than ~10 circles, the browser hangs. Below is my current implementation, which I am completely OK with throwing away in favor of a more performant / better one.
Here is a live demo (NOTE: there is no actual drawing code, just the logic, but it will still lock up the browser so be warned!!) http://jsbin.com/muhiziduxu/2/edit?js,console
function adjustForOverlap (circleArray) {
// a reference to the circle that is invoking this function.
var _this = this;
// remove this circle from the array we are iterating over.
var arr = circleArray.filter(function (circle){
return circle.id !== _this.id;
});
// while repeat == true, the circle may be overlapping something.
var repeat = true;
while(repeat) {
var hasOverlap = false;
for (var i=0; i<arr.length; i++) {
var other = arr[i];
var dx = _self.x - other.x;
var dy = _self.y - other.y;
var rr = _self.radius + other.radius;
if (dx * dx + dy * dy < rr * rr) {
// if here, then an overlap was detected.
hit = true;
break;
}
}
// if hit is false, the circle didn't overlap anything, so break.
if (hit === false) {
repeat = false;
break;
} else {
// an overlap was detected, so randomize position.
_self.x = Math.random() * (stage.width*2);
_self.y = Math.random() * stage.height;
}
}
}
There are lots of efficient collision detection algorithms. Many of them work by dividing up the space into cells and maintaining a separate data structure with efficient lookup of other objects in the cell. The basic steps are:
Identify a random spot for your new circle
Determine which cells it's in
Look in each of those cells for a collision
If there's a collision, goto 1.
Else, add the new circle to each of the cells it overlaps.
You can use a simple square grid (i.e. a 2-d array) for the cell data structure, or something else like a quadtree. You can also in some cases get a bit of extra speed by trying a cheap-but-coarse collision check first (do the bounding boxes overlap), and if that returns true try the slightly more expensive and exact check.
Update
For quadtrees, check out d3-quadtree, which ought to give you a pretty good implementation, with examples.
For a (very quick, untested) 2-d array implementation:
function Grid(radius, width, height) {
// I'm not sure offhand how to find the optimum grid size.
// Let's use a radius as a starting point
this.gridX = Math.ceil(width / radius);
this.gridY = Math.ceil(height / radius);
// Determine cell size
this.cellWidth = width / this.gridX;
this.cellHeight = height / this.gridY;
// Create the grid structure
this.grid = [];
for (var i = 0; i < gridY; i++) {
// grid row
this.grid[i] = [];
for (var j = 0; j < gridX; j++) {
// Grid cell, holds refs to all circles
this.grid[i][j] = [];
}
}
}
Grid.prototype = {
// Return all cells the circle intersects. Each cell is an array
getCells: function(circle) {
var cells = [];
var grid = this.grid;
// For simplicity, just intersect the bounding boxes
var gridX1Index = Math.floor(
(circle.x - circle.radius) / this.cellWidth
);
var gridX2Index = Math.ceil(
(circle.x + circle.radius) / this.cellWidth
);
var gridY1Index = Math.floor(
(circle.y - circle.radius) / this.cellHeight
);
var gridY2Index = Math.ceil(
(circle.y + circle.radius) / this.cellHeight
);
for (var i = gridY1Index; i < gridY2Index; i++) {
for (var j = gridX1Index; j < gridX2Index; j++) {
// Add cell to list
cells.push(grid[i][j]);
}
}
return cells;
},
add: function(circle) {
this.getCells(circle).forEach(function(cell) {
cell.push(circle);
});
},
hasCollisions: function(circle) {
return this.getCells(circle).some(function(cell) {
return cell.some(function(other) {
return this.collides(circle, other);
}, this);
}, this);
},
collides: function (circle, other) {
if (circle === other) {
return false;
}
var dx = circle.x - other.x;
var dy = circle.y - other.y;
var rr = circle.radius + other.radius;
return (dx * dx + dy * dy < rr * rr);
}
};
var g = new Grid(150, 1000, 800);
g.add({x: 100, y: 100, radius: 50});
g.hasCollisions({x: 100, y:80, radius: 100});
Here's a fully-functional example: http://jsbin.com/cojoxoxufu/1/edit?js,output
Note that this only shows 30 circles. It looks like the problem is often unsolvable with your current radii, width, and height. This is set up to look for up to 500 locations for each circle before giving up and accepting a collision.
So i have been working hard at a game using HTML5 and JavaScript. I am trying to make a space invaders styled game and as a result i have an array of enemies. I have separate functions dedicated to creating the array of enemies, drawing them to screen, moving the enemies and finally removing them.
the removal of enemies however is causing an issue. This is my logic
if an enemies health is less than or equal to 0, delete the enemy from the array and shrink the arrays length by 1. Now logic would dictate that this can be a calamity, if you start shooting and killing enemies from the start of the array, as the arrays length will be reduced and this is exactly my problem, so low and behold my code.
function hostile(x, y) {
this.speed = 1;
this.health = 100;
this.x = x;
this.y = y;
this.height = 32;
this.width = 32;
this.isDead = false;
this.direction = 0;
this.deadCount = 0;
this.firing = false;
//this.moving = true;
this.move = function () {
if (this.isDead === false && gameStart === true) {
context.clearRect(0, 0, canvas1.width, canvas1.height);
if (this.x > canvas.width - 64) {
this.y += 10;
this.direction = 0;
}
if (this.x < 0) {
this.y += 10;
}
if (this.direction === 1) {
this.x += this.speed;
} else {
this.x -= this.speed;
}
if (this.x < 0) {
this.direction = 1;
}
if (this.y > 420) {
this.x = 600;
}
}
};
this.draw = function () {
context.drawImage(sprite, 0, 480, 65, 68, this.x, this.y, 65, 65);
};
this.reset = function () {
context.clearRect(this.x, this.y, 65, 65);
this.x = 20;
this.y = 20;
this.health = 100;
};
};
var enemylist = [];
function createEnemies() {
for (var i = 0; i < 6; i++) {
enemylist.push(new hostile(75 * i, 20));
}
};
function deleteEnemy(a) {
enemylist.splice(a);
enemyBulletList.splice(a);
//enemylist.length = enemylist.length-1;
//enemyBulletList.length = enemyBulletList.length - 1;
};
createEnemies();
function moveEnemies() {
for (var i = 0; i < enemylist.length; i++) {
if (enemylist[i].isDead === false && gameStart === true) {
if (enemylist[i].x > canvas.width - 64) {
enemylist[i].y += 10;
enemylist[i].direction = 0;
}
if (enemylist[i].x < 0) {
enemylist[i].y += 10;
}
if (enemylist[i].direction === 1) {
enemylist[i].x += enemylist[i].speed;
} else {
enemylist[i].x -= enemylist[i].speed;
}
if (enemylist[i].x < 0) {
enemylist[i].direction = 1;
}
if (enemylist[i].y > 420) {
enemylist[i].x = 600;
}
}
}
};
So to explain my problem, i can shoot and kill enemies from the array, this will also remove them from the screen. However if i shoot an enemy from the start of the array i cause all the enemies to cleared off the screen and the game to crash. If you need more information, feel free to ask.
By request, i have submitted more code relating to my question. The code above includes the hostile function and other functions associated directly with it.
EDIT: Vitim.us had offered a very useful tip regarding my question, he suggested to create a flag of some sort (var dead = false/true etc.) and once the value is changed the specific instance can simply be removed from the screen away from the player.
Don't manually update the array like that. delete is for removing named properties. If you want to remove elements from an Array, use splice:
function deleteEnemy(a) { // capitalized functions are usually reserved for constructors
enemylist.splice(a);
enemyBulletList.splice(a);
}
Also, var enemylist = [6] doesn't do what you think it does. It creates an array with a single element, [6]. You should create an empty array and then enemylist.push(new hostile(...)) to add them to the array.
This will avoid you having to manually set the length (which you should not ever have to do.)
You can implement this in various ways
You can implement a method to hostile to remove itself from screen,
To this each instance should keep a reference to itself on screen, it can be a direct reference to DOM or it can be a property like hostile.entitieId that correlate to the onscreen object.
When hostile.health<=0 you flag hostile.isDead = true; and automatically call a method to remove it from screen, than notify other method to finally delete the instance from your enemyList[]
You can either check this on the game tick or you can build it in a event dispatch fashion, using getter and setter for the health property.
this.setHeath = function(value){
//set entity heath
if(health<=0){
//remove itself from screen
//notify other method to clear this instance from the enemyArray[]
}
}