Shoot 3 bullets on set times from each enemy - javascript

I'm trying to create a simple game with kineticJS on my canvas(Just some practice) and managed to get my player to shoot bullets. The same goes for the enemies that are spawned. They shoot a bullet each time the last bullet leaves the stage.
However: I'd like all the enemies(Variable number) to shoot 3 bullets at a 2 seconds interval. But i'm stuck completely and can't figure a way how to get it done.
Could anyone please look at my fiddle and see what's up?
http://jsfiddle.net/eRQ3P/6/
Note: line 573 is the function that loops(And draws the bullets and such every 30FPS)
Here's the code where i create a new bullet object:(Line 406 in fiddle)
function Enemybullet(destinationX, destinationY, enemySprite) {
this.id = 'bullet';
this.x = enemySprite.getX()+(enemySprite.getWidth()/2);
this.y = enemySprite.getY()+(enemySprite.getHeight()/2);
var targetX = destinationX - this.x,
targetY = destinationY - this.y,
distance = Math.sqrt(targetX * targetX + targetY * targetY);
this.velX = (targetX / distance) * 5;
this.velY = (targetY / distance) * 5;
this.finished = false;
this.sprite = new Kinetic.Circle({
x: this.x,
y: this.y,
radius: 3,
fill: 'black',
name: 'enemyProjectile'
});
this.draw = function(index) {
var mayDelete = false;
this.x += this.velX;
this.y += this.velY;
this.sprite.setAbsolutePosition(this.x, this.y);
//console.log(this.sprite.getX());
/*
if(enemyCollision(this) == true) {
mayDelete = true;
}*/
if (bulletLeftField(this.sprite) == true) {
mayDelete = true;
}
if (mayDelete == true) {
this.sprite.remove();
enemies[index].bullets.splice(0, 1);
createEnemyBullet(enemies[index]);
}
ammoLayer.draw();
}
}
And the function providing a new bullet: (line 247 in fiddle)
function createEnemyBullet(enemy) {
var blt = new Enemybullet(player.sprite.getX(), player.sprite.getY(), enemy.sprite);
ammoLayer.add(blt.sprite);
enemy.bullets.push(blt);
}

Probably the hardest part of this problem is figuring out when to draw each bullet to make three be fired in every 2-second interval. To make the bullets fire evenly, you want to divide the number of frames in the interval by the number of bullets to fire in that interval.
Because you're running the game at 30 frames per second, 2 seconds equals 60 frames.
60 frames / 3 bullets = 20 frames/bullet
So, we're going to create a new bullet for each enemy every 20 frames, or every 20th time refreshLoop() is called, and inside refreshLoop(), you now have to loop through all of the bullets each enemy has in its bullets array, because there can now be more than just one.
The fact that there can be more than one bullet in the bullets array introduces a new problem to the way bullets are removed from the array. Previously, you relied on the fact that one bullet at a time means it will always be the first one in the array, thus your code called bullets.splice(0, 1);. However, when the player is moving around and the enemies fire at the different locations, it is completely possible to have a bullet leave the screen and be removed sooner than one that was fired before it. This would cause the correct bullet sprite to be removed, but the first bullet in the array would be removed from bullets, so it wouldn't be updated anymore in refreshLoop(), and it would just sit on the screen doing nothing.
In order to avoid this, it is necessary to pass to the enemy bullets' draw() function the index in bullets at which the bullet being drawn is located. Since you need to loop through the array anyway, the index is already at hand in refreshLoop(), so just pass this to draw(). Now, every time a bullet needs to be removed, you can just call bullets.splice(bulletIndex, 1);
I hope you don't mind; I forked your fiddle to update it with the changes listed below.
EDIT: A new fiddle for burst-fire instead of sustained fire.
// Inside your Enemybullet definition
// One simple change to draw(), pass in the index of the bullet in the array
this.draw = function(indexEnemy, indexBullet) {
var mayDelete = false;
...
if (bulletLeftField(this.sprite) == true) {
mayDelete = true;
}
if (mayDelete == true) {
this.sprite.remove();
// Since you now have multiple bullets, you'll have to make
// sure you're removing the correct one from the array
enemies[indexEnemy].bullets.splice(indexBullet, 1);
}
ammoLayer.draw();
}
...
// Inside your refreshLoop function
// If there are enemies they should be checked
if (enemies.length > 0) {
for (var i = 0; i < enemies.length; i++) {
enemies[i].draw();
// At 30 frames per second, 3 bullets in 2 seconds would be
// one bullet for every 20 frames. So, every 20 frames,
// create a new bullet for each enemy
if ((enemyShootTimer % 20) == 0) {
createEnemyBullet(enemies[i]);
}
// The same way you draw all of the player's bullets,
// loop through the array of bullets for this enemy,
// and draw each one, passing in the new parameters
if (enemies[i].bullets.length > 0) {
for (var j = 0; j < enemies[i].bullets.length; j++) {
enemies[i].bullets[j].draw(i, j);
}
}
}
}
// Update loop for burst-fire instead of sustained fire
var burstTime = 10; // 10 frames between bullets, 3 per second
var needToShoot = ((enemyShootTimer % burstTime) == 0);
if (enemies.length > 0) {
for (var i = 0; i < enemies.length; i++) {
enemies[i].draw();
// if the enemies still have bullets to shoot this burst
// and if 10 frames have passed since the last shot
// ( enemyBurstCounter is declared outside refreshLoop() )
if (enemyBurstCounter < 3 && needToShoot) {
createEnemyBullet(enemies[i]);
}
if (enemies[i].bullets.length > 0) {
for (var j = 0; j < enemies[i].bullets.length; j++) {
enemies[i].bullets[j].draw(i, j);
}
}
}
if ((enemyShootTimer % 60) == 0) {
enemyBurstCounter = 0; // if 2 seconds have passed, reset burst counter
} else if (needToShoot) {
enemyBurstCounter++; // if the enemies shot, update burst counter
}
}

Related

Executing function once in a game loop

I have a game loop that refreshes ~20 times per second with the following condition, inside function growPlayerBlockCounterWinner():
if (obstacleArray[i][2] > canvas.height) {player.size += 5;}
That means: if black object goes outside the canvas area, player size will increase by 5 pixels.
The function normally executes once, but due to putting it in a game loop, it executes each time the game is refreshed, so player is growing continuously.
Previously i had it with the sign '=' instead od '>', but that was working well when black blocks were moving pixel by pixel and i want them to move faster.
You can inspect the problem and full code on the remote server: https://stacho163.000webhostapp.com/firstLevel.html
Below I paste only my obstacle functions:
// js game script //
let obstacle = {
size: 0,
posX: 0,
posY: 0,
speed: 0
}
let obstacleArray = new Array(100);
function generateObstacle() {
for (i = 0; i < obstacleArray.length; i++) {
obstacle.size = Math.round((Math.random() * 100) + 50);
obstacle.posX = Math.round((Math.random() * (canvas.width -
obstacle.size)));
obstacle.posY = -450 - (i * 100);
obstacle.speed = 5;
obstacleArray[i] = [obstacle.size, obstacle.posX, obstacle.posY,
obstacle.speed];
}
}
function drawObstacle() {
for (i = 0; i < obstacleArray.length; i++) {
ctx.fillStyle = "#000";
ctx.fillRect(obstacleArray[i][1], obstacleArray[i][2],
obstacleArray[i][0], obstacleArray[i][0]);
obstacleArray[i][2] += obstacleArray[i][3];
}
}
function growPlayerBlockCounterWinner() {
for (i = 0; i < obstacleArray.length; i++) {
// grow player
if (obstacleArray[i][2] > canvas.height) {
player.size += 5;
}
}
}
generateObstacle();
function game() {
drawObstacle();
growPlayerBlockCounterWinner();
requestAnimationFrame(game);
}
requestAnimationFrame(game);
I am looking for opinions, maybe my logic about that statement is incorrect or i should place that statement in other place.
Thanks for your tips :)
As you have a defined number of black boxes, you might be able to store, for which black box the player already increased size like this:
let obstacle = {...};
var affectedBlackBoxes = {};
//...//
function growPlayerBlockCounterWinner() {
for (i = 0; i < obstacleArray.length; i++) {
// grow player
if (obstacleArray[i][2] > canvas.height && !affectedBlackBoxes[i]) {
player.size += 5;
affectedBlackBoxes[i] = true;
}
}
}

Svg object modified with setAttributeNS not updating until after while loop

I'm creating a small game in javascript and I'm using svg for the graphics. Right now I'm having a problem with updating the game in the middle of a game tick. If I exit my loop directly after I update the fill attribute with "setAttributeNS", it's redrawn, but if I don't do that, it isn't updated until after "game_tick" is over. Even worse, if I call "game_tick" multiple times in a row, the svg objects aren't updated until after I've run all of the "game_tick"s instead of being updated after each one.
function game_tick(){
num_grid_copy = num_grid.slice();
for (var x = 0; x < num_squares_x; x += 1) {
for (var y = 0; y < num_squares_x; y += 1) {
var n = get_neighbors(x,y);
var isAliveInNextGen = next_gen(n, num_grid[x*num_squares_x+y]);
num_grid_copy[x*num_squares_x+y] = isAliveInNextGen;
if (isAliveInNextGen == 1){
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#0099ff');
}
else {
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#fff');
}
}
}
num_grid = num_grid_copy;
}
Thanks to valuable input from Robert I realized that javascript execution and page rendering are done in the same thread. I changed the function to the following:
function start() {
var inc = 0,
max = 25;
delay = 100; // 100 milliseconds
var repeat = setInterval(function() {
game_tick();
if (++inc >= max)
clearInterval(repeat);
},
delay);
}
This works fine. I can set the delay and the number of times it repeats.

JavaScript game starts fast and slows down over time

My simple JavaScript game is a Space Invaders clone.
I am using the p5.js client-side library.
I have tried many things to attempt at speeding up the game.
It start off fast, and then over time it get slower, and slower, it isn't as enjoyable.
I do not mean to show every bit of code I have. I am not showing every class, but I will show the main class where everything is happening.
Could someone eyeball this and tell me if you see anything major?
I am new to JS and new to making games, I know there is something called update()
that people use in scripting but I am not familiar with it.
Thank you.
var ship;
var flowers = []; // flowers === aliens
var drops = [];
var drops2 = [];
function setup() {
createCanvas(600, 600);
ship = new Ship();
for (var i = 0; i < 6; i ++) {
flowers[i] = new Flower(i * 80 + 80, 60);
}
flower = new Flower();
}
function draw() {
background(51);
ship.show();
ship.move();
shipDrops();
alienDrops();
dropsAndAliens();
dropDelete();
drop2Delete();
}
// if 0 drops, show and move none, if 5, etc..
function shipDrops() {
for (var i = 0; i < drops.length; i ++) {
drops[i].show();
drops[i].move();
for (var j = 0; j < flowers.length; j++) {
if(drops[i].hits(flowers[j]) ) {
flowers[j].shrink();
if (flowers[j].r === 0) {
flowers[j].destroy();
}
// get rid of drops after it encounters ship
drops[i].evaporate();
}
if(flowers[j].toDelete) {
// if this drop remove, use splice function to splice out of array
flowers.splice(j, 1); // splice out i, at 1
}
}
}
}
function alienDrops() {
// below is for alien/flower fire drops 2
for (var i = 0; i < drops2.length; i ++) {
drops2[i].show();
drops2[i].move();
if(drops2[i].hits(ship) ) {
ship.shrink();
drops2[i].evaporate(); // must evap after shrink
ship.destroy();
if (ship.toDelete) {
delete ship.x;
delete ship.y;
} // above is in progress, deletes after ten hits?
}
}
}
function dropsAndAliens() {
var randomNumber; // for aliens to shoot
var edge = false;
// loop to show multiple flowers
for (var i = 0; i < flowers.length; i ++) {
flowers[i].show();
flowers[i].move();
// ******************************************
randomNumber = Math.floor(Math.random() * (100) );
if(randomNumber === 5) {
var drop2 = new Drop2(flowers[i].x, flowers[i].y, flowers[i].r);
drops2.push(drop2);
}
//**************** above aliens shooting
// below could be method, this will ensure the flowers dont
//go offscreen and they move
//makes whtever flower hits this space become the farther most
//right flower,
if (flowers[i].x > width || flowers[i]. x < 0 ) {
edge = true;
}
}
// so if right is true, loop thru them all again and reset x
if (edge) {
for (var i = 0; i < flowers.length; i ++) {
// if any flower hits edge, all will shift down
// and start moving to the left
flowers[i].shiftDown();
}
}
}
function dropDelete() {
for (var i = drops.length - 1; i >= 0; i--) {
if(drops[i].toDelete) {
// if this drop remove, use splice function to splice out of array
drops.splice(i, 1); // splice out i, at 1
}
}
}
function drop2Delete() {
for (var i = drops2.length - 1; i >= 0; i--) {
if(drops2[i].toDelete) {
// if this drop remove, use splice function to splice out of array
drops2.splice(i, 1); // splice out i, at 1
}
}
}
function keyReleased() {
if (key != ' ') {
ship.setDir(0); // when i lift the key, stop moving
}
}
function keyPressed() {
// event triggered when user presses key, check keycode
if(key === ' ') {
var drop = new Drop(ship.x, height); // start ship x and bottom of screen
drops.push(drop); // when user hits space, add this event to array
}
if (keyCode === RIGHT_ARROW) {
// +1 move right
ship.setDir(1);
} else if (keyCode === LEFT_ARROW) {
// -1 move left
ship.setDir(-1);
} // setir only when pressing key, want continuous movement
}
Please post a MCVE instead of a disconnected snippet that we can't run. Note that this should not be your entire project. It should be a small example sketch that just shows the problem without any extra code.
But to figure out what's going on, you need to debug your program. You need to find out stuff like this:
What is the length of every array? Are they continuously growing over time?
What is the actual framerate? Is the framerate dropping, or does it just appear to be slower?
At what point does it become slower? Try hard-coding different values to see what's going on.
Please note that I'm not asking you to tell me the answers to these questions. These are the questions you need to be asking yourself. (In fact, you should have all of these answers before you post a question on Stack Overflow!)
If you still can't figure it out, then please post a MCVE in a new question post and we'll go from there. Good luck.

Three.js performance very slow using onMouseMove with RayCaster

I'm building an application in three.js, however I'm having real problems with performance. This part of the application is based upon the Voxel Painter example. In my version, the user clicks on a cell to begin placement, drags the cursor to where they wish to end placement, and clicks to end.
function onDocumentMouseMove(event) {
//set up mouse and raycaster
event.preventDefault();
mouse.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
switch (buildMode) {
case buildModes.CORRIDOR:
scene.add(rollOverFloor);
var intersects = raycaster.intersectObjects(gridObject);
if (intersects.length > 0) {
var intersect = intersects[0];
if (beginPlace == true) {
//store the intersection position
var endPlace = new THREE.Vector3(0, 0, 0);
endPlace.copy(intersect.point).add(intersect.face.normal);
endPlace.divideScalar(step).floor().multiplyScalar(step).addScalar(step / step);
endPlace.set(endPlace.x, 0, endPlace.z);
corridorDrag(endPlace);
}
//if user hasn't begun to place the wall
else {
//show temporary wall on grid
rollOverFloor.position.copy(intersect.point).add(intersect.face.normal);
rollOverFloor.position.divideScalar(step).floor().multiplyScalar(step).addScalar(step / step);
rollOverFloor.position.set(rollOverFloor.position.x, 0, rollOverFloor.position.z);
}
}
break;
}
render();
}
The code above is called when the user moves the mouse (there are many buildmodes in the main application, but I have not included them here). This function simply gets a start and end point, the corridorDrag() function fills in the cells between the start and end points:
function corridorDrag(endPlace) {
deleteFromScene(stateType.CORRIDOR_DRAG);
var startPoint = startPlace;
var endPoint = endPlace;
var zIntersect = new THREE.Vector3(startPoint.x, 0, endPoint.z);
var xIntersect = new THREE.Vector3(endPoint.x, 0, startPoint.z);
var differenceZ = Math.abs(startPlace.z - zIntersect.z);
var differenceX = Math.abs(startPlace.x - xIntersect.x);
var mergedGeometry = new THREE.Geometry();
for (var i = 0; i <= (differenceZ / step); i++) {
for (var j = 0; j <= (differenceX / step); j++) {
var x = startPlace.x;
var y = startPlace.y;
var z = startPlace.z;
if (endPoint.x <= (startPlace.x )) {
if (endPoint.z <= (startPlace.z)) {
x = x - (step * j);
z = z - (step * i);
}
else if (endPoint.z >= (startPlace.z)) {
x = x - (step * j);
z = z + (step * i);
}
} else if (endPoint.x >= (startPlace.x)) {
if (endPoint.z <= (startPlace.z)) {
x = x + (step * j);
z = z - (step * i);
}
else if (endPoint.z >= (startPlace.z)) {
x = x + (step * j);
z = z + (step * i);
}
}
floorGeometry.translate(x, y, z);
mergedGeometry.merge(floorGeometry);
floorGeometry.translate(-x, -y, -z);
}
}
var voxel = new THREE.Mesh(mergedGeometry, tempMaterial);
voxel.state = stateType.CORRIDOR_DRAG;
scene.add(voxel);
tempObjects.push(voxel);
}
Firstly, the deleteFromScene() function removes all current highlighted cells from the scene (see below). The code then (I believe), should create a number of meshes, depending on the start and end points, and add them to the scene.
function deleteFromScene(state) {
tempObjects = [];
var i = scene.children.length;
while (i--) {
if (scene.children[i].state != undefined)
if (scene.children[i].state == state)
scene.children.splice(i, 1);
}
}
As I said, it is very, very slow. It also appears to be adding an obscene amount of vertices to the renderer, as seen in the WebGLRenderer stats window. I have no idea why it's adding so many vertices, but I'm assuming that's why it's rendering so slowly.
The application can be viewed here - the problem can be seen by clicking on one cell, dragging the cursor to the other end of the grid, and observing the time taken to fill in the cells.
Thank you in advance, this really is a last resort.
A few years ago Twitter put out an update. In this update they had just introduced infinite scrolling and on the day of its release the update was crashing users browsers. Twitter engineers did some investigating and found that the crashes were the result of the scroll event firing hundreds of times a second.
Mouse events can fire many MANY times a second and can cause your code to execute too often, which slows down the browser and (in many cases) crashes it. The solution for Twitter (and hopefully you) was simple: Poll your event.
Inside your mousemove event handler check that it has been some number of milliseconds since the last move event.
var lastMove = Date.now();
function onDocumentMouseMove(event) {
if (Date.now() - lastMove < 31) { // 32 frames a second
return;
} else {
lastMove = Date.now();
}
// your code here
}
I hope that helps!

getImageData memory leak?

Background: Over the last week I've been working on a game that is essentially multi-directional Tron, using Canvas and JavaScript. I opted not to clear the Canvas every frame so that my little line segments leave a trail. For collision detection, I use this function:
// 8 sensors for collision testing, positioned evenly around the brush point
var detectionRadius = this.width / 2 + 1; //points just outside the circumference
var counter = 0;
var pixelData;
for (var i = 0; i < 16; i += 2) {
//collisionPixels[] is an array of 8 (x, y) offsets, spaced evenly around the center of the circle
var x = this.x + collisionPixels[i] * detectionRadius;
var y = this.y + collisionPixels[i + 1] * detectionRadius;
pixelData = context.getImageData(x,y,1,1).data; //pixel data at each point
if (pixelData[3] != 0) {
counter++;
}
}
if (counter > 4) {
this.collision();
}
The purpose here is to get the alpha values of 8 pixels around the brushpoint's surface; alpha values of 0 are just on the background. If the number of colliding pixels, out of the total 8, is greater than 4 (this is including the trail behind the player) then I call the collision() method. This function actually works really well (and this IS inside a function, so these declarations are local).
The problem is that context.getImageData() skyrockets my memory usage, and after 3 or 4 games tanks the framerate. Cutting just that line out and assigning pixelData some other value makes everything run very smoothly, even while doing the other computations.
How do I fix this memory leak? And, if there's a less convoluted way to do collision detection of this type, what is it?
EDIT: at request, here is my loop:
function loop() {
now = Date.now();
delta = now - lastUpdate;
lastUpdate = now;
if (!paused) {
for (var i = 0; i < numPlayers; i++) {
if (players[i].alive) {
players[i].update(delta);
players[i].render();
}
}
}
requestAnimationFrame(loop);
}
EDIT 2: So I tried Patrick's UInt8ClampedArrays idea:
//8 sensors for collision testing, positioned evenly around the brush point
var detectionRadius = this.width / 2 + 1;
var counter = 0;
for (var i = 0; i < 16; i += 2) {
var x = this.x + collisionPixels[i] * detectionRadius;
var y = this.y + collisionPixels[i + 1] * detectionRadius;
//translate into UInt8ClampedArray for data
var index = (y * canvas.width + x) * 4 + 3; //+3 so we're at the alpha index
if (canvasArray[index] != 0) {
counter++;
}
}
And, at the top of my loop I added a new global variable, updated once per frame:
var canvasArray = context.getImageData(0,0,canvas.width,canvas.height).data;
Hope I did that right. It works, but the memory and framerate still get worse each round you play. Going to upload some heap snapshots.
EDIT 3:
Snapshot 1: https://drive.google.com/open?id=0B-8p3yyYzRjeY2pEa2Z5QlgxRUk&authuser=0
Snapshot 2: https://drive.google.com/open?id=0B-8p3yyYzRjeV2pJb1NyazY3OWc&authuser=0
Snapshot 1 is after the first game, 2 is after the second.
EDIT 4: Tried capping the framerate:
function loop() {
requestAnimationFrame(loop);
now = Date.now();
delta = now - lastUpdate;
//lastUpdate = now;
if (delta > interval) {
lastUpdate = now;
if (!paused) {
for (var i = 0; i < numPlayers; i++) {
if (players[i].alive) {
players[i].update(delta);
players[i].render();
}
}
}
}
}
Where
interval = 1000 / fps;
It delays the eventual performance hit, but memory is still climbing with this option.
EDIT 5: While I'm sure there must be a better way, I found a solution that works reasonably well. Capping the framerate around 30 actually worked in terms of long-term performance, but I hated the way the game looked at 30 FPS.. so I built a loop that had an uncapped framerate for all updating and rendering EXCEPT for collision handling, which I updated at 30 FPS.
function loop() {
requestAnimationFrame(loop);
now = Date.now();
delta = now - lastUpdate;
lastUpdate = now;
if (!paused) {
for (var i = 0; i < numPlayers; i++) {
if (players[i].alive) {
players[i].update(delta);
players[i].render();
}
}
if (now - lastCollisionUpdate > collisionInterval) {
canvasData = context.getImageData(0, 0, context.canvas.width, context.canvas.height).data;
for (var i = 0; i < numPlayers; i++) {
if (players[i].alive) {
if (players[i].detectCollisions()) {
players[i].collision();
}
}
}
lastCollisionUpdate = now;
}
canvasData = null;
}
}
Thanks for the answers.. a lot of your ideas found their way into the final(?) product, and I appreciate that. Closing this thread.
Is there some point at which you could call context.getImageData(0, 0, context.canvas.width, context.canvas.height).data so that you can use that single UInt8ClampedArray instead of however many you're using? Also when you're done with the image data (the ImageData that is, not the TypedArray inside it), you could try calling delete on it, though I'm not certain if that will deallocate the memory.
While I'm sure there must be a better way, I found a solution that works reasonably well. Capping the framerate around 30 actually worked in terms of long-term performance, but I hated the way the game looked at 30 FPS.. so I built a loop that had an uncapped framerate for all updating and rendering EXCEPT for collision handling, which I updated at 30 FPS.
//separate update cycle for collision detection
var collisionFPS = 30;
var lastCollisionUpdate;
var collisionInterval = 1000 / collisionFPS;
var canvasData;
function loop() {
requestAnimationFrame(loop);
now = Date.now();
delta = now - lastUpdate;
lastUpdate = now;
if (!paused) {
for (var i = 0; i < numPlayers; i++) {
if (players[i].alive) {
players[i].update(delta);
players[i].render();
}
}
if (now - lastCollisionUpdate > collisionInterval) {
canvasData = context.getImageData(0, 0, context.canvas.width, context.canvas.height).data;
for (var i = 0; i < numPlayers; i++) {
if (players[i].alive) {
if (players[i].detectCollisions()) {
players[i].collision();
}
}
}
lastCollisionUpdate = now;
}
canvasData = null;
}
}
Might not be the best solution, but it's consistent.

Categories

Resources