I am attempting a game of snake, although the shape seems to not be moving. I don't know why it is doing this and it is not a typographical error. In order to see my problem, see snippet below ↓.
I have added both a CSS and HTML file as well, so you know I did that right. My method of making this is to get the direction of the snake (represented by the variables 'vx' and 'vy') and then use the direction to calculate the future position of the 'snakePiece()' object in the Array of 'snake'.
window.onload = function main() {
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
function snakePiece() {
this.color = "#9999ff";
this.dimension = 25;
this.x = 0;
this.y = 0;
this.vx = 0;
this.vy = 0;
}
var snake = new Array(new snakePiece());
function setPosition() {
for (i = 0; i < snake.length; i++) {
switch (snake[i].vx) {
case 1:
snake[i].x += snake[i].dimension;
snake[i + 1].x = snake[i].x - snake[i].dimension;
snake[i + 1].vx = 1;
break;
case -1:
snake[i].x -= snake[i].dimension;
snake[i + 1].x = snake[i].x + snake[i].dimension;
snake[i + 1].vx = -1;
break;
}
switch (snake[i].vy) {
case 1:
snake[i].y -= snake[i].dimension;
snake[i + 1].y = snake[i].y + snake[i].dimension;
snake[i + 1].vy = 1;
break;
case -1:
snake[i].y += snake[i].dimension;
snake[i + 1].y = snake[i].y - snake[i].dimension;
snake[i + 1].vy = -1;
break;
}
}
}
setInterval(function draw() {
setPosition();
ctx.clearRect(0, 0, c.width, c.height);
for (j = 0; j < snake.length; j++) {
ctx.fillStyle = snake[j].color;
ctx.fillRect(snake[j].x, snake[j].y, snake[j].dimension, snake[j].dimension);
}
}, 500);
document.onkeydown = function(event) {
var keyPress = String.fromCharCode(event.keyCode);
switch (keyPress) {
case 40: //DOWN
snake[0].vy = 1;
snake[0].vx = 0;
break;
case 39: //RIGHT
snake[0].vx = 1;
snake[0].vy = 0;
break;
case 38: //UP
snake[0].vy = -1;
snake[0].vx = 0;
break;
case 37: //LEFT
snake[0].vx = -1;
snake[0].vy = 0;
break;
}
}
}
body {
padding: 15px;
}
#canvas {
border: 3px solid black;
background-color: #FFB6C1;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Simple Snake</title>
<script src="snake-java.js" type="text/javascript" rel="javascript"></script>
<link href="snake-css.css" type="text/css" rel="stylesheet" />
</head>
<body>
<canvas height="900" width="900" id="canvas"></canvas>
</body>
</html>
Here's my take on it. I added comments in the code where I modified something to exlain what was wrong.
window.onload = function main() {
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
function snakePiece() {
this.color = "#9999ff";
this.dimension = 25;
this.x = 100;
this.y = 100;
this.vx = 0;
this.vy = 0;
}
var snake = new Array(new snakePiece());
function setPosition() {
for (i = 0; i < snake.length; i++) {
switch (snake[i].vx) {
case 1:
snake[i].x += snake[i].dimension;
// You don't need all the commented lines in this function.
// The for loop is already taking care of your snake cells.
// Moreover snake[i + 1] is undefined if your snake is length 1 (hence a JS error).
//snake[i + 1].x = snake[i].x - snake[i].dimension;
//snake[i + 1].vx = 1;
break;
case -1:
snake[i].x -= snake[i].dimension;
//snake[i + 1].x = snake[i].x + snake[i].dimension;
//snake[i + 1].vx = -1;
break;
}
switch (snake[i].vy) {
case 1:
snake[i].y -= snake[i].dimension;
//snake[i + 1].y = snake[i].y + snake[i].dimension;
//snake[i + 1].vy = 1;
break;
case -1:
snake[i].y += snake[i].dimension;
//snake[i + 1].y = snake[i].y - snake[i].dimension;
//snake[i + 1].vy = -1;
break;
}
}
}
setInterval(function draw() {
setPosition();
ctx.clearRect(0, 0, c.width, c.height);
for (j = 0; j < snake.length; j++) {
ctx.fillStyle = snake[j].color;
ctx.fillRect(snake[j].x, snake[j].y, snake[j].dimension, snake[j].dimension);
}
}, 500);
document.onkeydown = function(event) {
//var keyPress = String.fromCharCode(event.keyCode); <- this is wrong -> verify with : console.log(keyPress);
switch (event.keyCode) { // switch on event.keyCode instead
case 40: //DOWN
snake[0].vy = -1; // -1 instead of 1
snake[0].vx = 0;
break;
case 39: //RIGHT
snake[0].vx = 1;
snake[0].vy = 0;
break;
case 38: //UP
snake[0].vy = 1; // 1 instead of -1
snake[0].vx = 0;
break;
case 37: //LEFT
snake[0].vx = -1;
snake[0].vy = 0;
break;
}
// add preventDefault(); to disable window scrolling
event.preventDefault();
}
}
body {
padding: 15px;
}
#canvas {
border: 3px solid black;
background-color: #FFB6C1;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Simple Snake</title>
<script src="snake-java.js" type="text/javascript" rel="javascript"></script>
<link href="snake-css.css" type="text/css" rel="stylesheet" />
</head>
<body>
<canvas height="900" width="900" id="canvas"></canvas>
</body>
</html>
Related
2 questions: I want to disable the snake being able to go in opposite directions (so when it is moving left it cannot go right, if going up cannot go down etc.). How do I do it? Newbie here. Please, as descriptive as possible.
What are all those variables? px, py, gs, tc, ax, ay, yv
Here is the full code:
<canvas id="gc" width="400" height ="400"></canvas>
<script>
window.onload=function(){
canv=document.getElementById("gc");
ctx=canv.getContext("2d");
document.addEventListener("keydown", keyPush);
setInterval(game,1250/15);
}
px=py=10
gs=tc=20;
ax=ay=15;
xv=yv=0;
trail=[];
tail=5;
function game(){
px+=xv;
py+=yv;
if(px<0) {
px = tc-1;
}
if (px>tc-1){
px = 0;
}
if (py<0) {
py=tc-1;
}
if (py>tc-1) {
py=0;
}
ctx.fillStyle="black";
ctx.fillRect(0,0,canv.width,canv.height);
ctx.fillStyle="lime";
for(var i =0; i<trail.length; i++) {
ctx.fillRect(trail[i].x*gs, trail[i].y*gs, gs-2, gs-2);
if (trail[i].x==px && trail[i].y ==py) {
tail =5;
}
}
trail.push({x:px,y:py});
while(trail.length>tail){
trail.shift();
}
if (ax==px && ay ==py) {
tail++;
ax=Math.floor(Math.random()*tc);
ay=Math.floor(Math.random()*tc);
}
ctx.fillStyle="red";
ctx.fillRect(ax*gs, ay*gs, gs-2, gs-2);
}
function keyPush(evt){
switch(evt.keyCode){
case 37:
xv=-1;yv=0;
break;
case 38:
xv=0;yv=-1;
break;
case 39:
xv=1;yv=0;
break;
case 40:
xv=0;yv=1;
break;
}
}
</script>
px, py, gs, tc, ax, ay, yv are the inital x and y coordinate of snake and the first bullet and the size of the two elements
to disallow move in oposite direction, you can store the previous move inside a variable and allow to move the snake in another direction only if previous move was not the oposite one
window.onload = function() {
canv = document.getElementById("gc");
ctx = canv.getContext("2d");
document.addEventListener("keydown", keyPush);
setInterval(game, 1250 / 15);
}
var previousMove;
px = py = 10
gs = tc = 20;
ax = ay = 15;
xv = yv = 0;
trail = [];
tail = 5;
function game() {
px += xv;
py += yv;
if (px < 0) {
px = tc - 1;
}
if (px > tc - 1) {
px = 0;
}
if (py < 0) {
py = tc - 1;
}
if (py > tc - 1) {
py = 0;
}
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canv.width, canv.height);
ctx.fillStyle = "lime";
for (var i = 0; i < trail.length; i++) {
ctx.fillRect(trail[i].x * gs, trail[i].y * gs, gs - 2, gs - 2);
if (trail[i].x == px && trail[i].y == py) {
tail = 5;
}
}
trail.push({
x: px,
y: py
});
while (trail.length > tail) {
trail.shift();
}
if (ax == px && ay == py) {
tail++;
ax = Math.floor(Math.random() * tc);
ay = Math.floor(Math.random() * tc);
}
ctx.fillStyle = "red";
ctx.fillRect(ax * gs, ay * gs, gs - 2, gs - 2);
}
function keyPush(evt) {
switch (evt.keyCode) {
case 37:
if (previousMove !== 39) {
xv = -1;
yv = 0;
previousMove = 37;
}
break;
case 38:
if (previousMove !== 40) {
xv = 0;
yv = -1;
previousMove = 38;
}
break;
case 39:
if (previousMove !== 37) {
xv = 1;
yv = 0;
previousMove = 39;
}
break;
case 40:
if (previousMove !== 38) {
xv = 0;
yv = 1;
previousMove = 40;
}
break;
}
}
<canvas id="gc" width="400" height="400"></canvas>
to do it i add a global variable
var previousMove;
and change the keyPush function to became the following
function keyPush(evt) {
switch (evt.keyCode) {
case 37:
if (previousMove !== 39) {
xv = -1;
yv = 0;
previousMove = 37;
}
break;
case 38:
if (previousMove !== 40) {
xv = 0;
yv = -1;
previousMove = 38;
}
break;
case 39:
if (previousMove !== 37) {
xv = 1;
yv = 0;
previousMove = 39;
}
break;
case 40:
if (previousMove !== 38) {
xv = 0;
yv = 1;
previousMove = 40;
}
break;
}
I made a simple snake game by javascript, I try to add a feature that when the user holds the shift key, the snake will speed up. But right now when I hold shift for a short time it works fine, but if I hold it for a long time the game will pause (snake stop moving). Here is my code, please help me fix it.
var grid_size = 20;
var grid_num = 20;
var player_x = Math.floor(Math.random() * grid_num);
var player_y =Math.floor(Math.random() * grid_num);
var apple_x = Math.floor(Math.random() * grid_num);
var apple_y = Math.floor(Math.random() * grid_num);
var x_direction = 0;
var y_direction = 0;
var trail = [];
var tail = 5;
var normal_speed = 1000 / 10
var speed = 1000 / 10;
var game_status = 0;
var my_game;
// 1. Create the button
var button = document.createElement("button");
button.innerHTML = "Do Something";
// 2. Append somewhere
var body = document.getElementsByTagName("body")[0];
body.appendChild(button);
// 3. Add event handler
button.addEventListener ("click", function() {
clearInterval(my_game);
my_game = setInterval(game, speed);
});
window.onload = function() {
canv = document.getElementById("gc");
context = canv.getContext("2d");
document.addEventListener("keydown", keyPush);
document.addEventListener("keyup", keyRelease);
}
function game() {
player_x += x_direction;
player_y += y_direction;
if (player_x < 0) {
player_x = grid_num - 1;
}
if (player_x > grid_num - 1) {
player_x = 0;
}
if (player_y < 0) {
player_y = grid_num - 1;
}
if (player_y > grid_num - 1) {
player_y = 0;
}
context.fillStyle = "black";
context.fillRect(0, 0, canv.width, canv.height);
context.fillStyle = "lime";
for(var i = 0; i < trail.length; i++) {
context.fillRect(trail[i].x * grid_size, trail[i].y * grid_size, grid_size - 2, grid_size - 2);
if(trail[i].x == player_x && trail[i].y == player_y) {
if (game_status == 0) {
tail = 5;
} else {
endGame();
return;
}
}
}
trail.push({x:player_x, y:player_y});
while(trail.length > tail) {
trail.shift();
}
if (apple_x == player_x && apple_y == player_y) {
tail++;
apple_x = Math.floor(Math.random() * grid_num);
apple_y = Math.floor(Math.random() * grid_num);
}
context.fillStyle = "red";
context.fillRect(apple_x * grid_size, apple_y * grid_size, grid_size - 2, grid_size - 2);
}
function keyPush(keyEvent) {
switch(keyEvent.keyCode) {
case 37:
if (x_direction == 1) {
break;
}
game_status++;
x_direction = -1;
y_direction = 0;
break;
case 38:
if (y_direction == 1) {
break;
}
game_status++;
x_direction = 0;
y_direction = -1;
break;
case 39:
if (x_direction == -1) {
break;
}
game_status++;
x_direction = 1;
y_direction = 0;
break;
case 40:
if (y_direction == -1) {
break;
}
game_status++;
x_direction = 0;
y_direction = 1;
break;
case 65:
if (x_direction == 1) {
break;
}
game_status++;
x_direction = -1;
y_direction = 0;
break;
case 87:
if (y_direction == 1) {
break;
}
game_status++;
x_direction = 0;
y_direction = -1;
break;
case 68:
if (x_direction == -1) {
break;
}
game_status++;
x_direction = 1;
y_direction = 0;
break;
case 83:
if (y_direction == -1) {
break;
}
game_status++;
x_direction = 0;
y_direction = 1;
break;
case 16:
clearInterval(my_game);
speed = normal_speed * 0.5;
my_game = setInterval(game, speed);
break;
}
}
function keyRelease(keyEvent) {
switch(keyEvent.keyCode) {
case 16:
clearInterval(my_game);
speed = normal_speed;
my_game = setInterval(game, speed);
break;
}
}
function endGame() {
clearInterval(my_game);
context.fillStyle = "yellow";
context.font="20px Georgia";
context.fillText("Game Over!", 10, 50);
x_direction = 0;
y_direction = 0;
trail = [];
tail = 5;
normal_speed = 1000 / 10
speed = 1000 / 10;
game_status = 0;
}
body {
background-color: lightblue;
}
h1 {
color: white;
text-align: center;
}
button {
margin-top: 20px;
line-height: 60px;
font-weight: bold;
padding: 0 40px;
background: salmon;
border: none;
}
button:hover {
background: lightsalmon;
}
canvas {
padding: 0;
margin: auto;
display: block;
width: 400px;
height: 400px;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="web_style.css">
<canvas id = "gc" width = "400" height = "400"></canvas>
<script type="text/javascript" src="snake_script.js"></script>
</head>
<body>
<h1>Snake</h1>
</body>
</html>
I'm coding Snake Game, but I have an issue.
var ctx = document.getElementById('ctx').getContext('2d');
var canvas = document.getElementById('ctx');
var y = [240, 230, 220];
var x = [240, 240, 240];
var xSpeed = 0;
var ySpeed = 0;
function load() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (p = 0; p < x.length; p++) {
ctx.fillStyle = "black";
ctx.fillRect(x[p], y[p], 10, 10);
}
}
function keyDown() {
var key = event.keyCode; /*getting keyCode of pressed key*/
ctx.fillStyle = "black"; /*color of rectangle*/
switch (key) {
case 39: //RIGHT
xSpeed = 10;
ySpeed = 0;
break;
case 37: //LEFT
xSpeed = -10;
ySpeed = 0;
break;
case 38: //UP
xSpeed = 0;
ySpeed = -10;
break;
case 40: //DOWN
xSpeed = 0;
ySpeed = 10;
break;
}
}
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (x[0] >= 490) {
xSpeed = 0;
} else if (y[0] >= 490) {
ySpeed = 0;
}
//console.clear();
y[0] += ySpeed;
x[0] += xSpeed;
for (i = x.lenght; i >= 0; i--) {
y[i] = y[i - 1];
x[i] = x[i - 1];
console.log(i);
}
for (i = 0; i < x.length; i++) {
ctx.fillStyle = "black";
ctx.fillRect(x[i], y[i], 10, 10);
//console.log("y= " + y[i]);
//console.log("x= " + x[i]);
}
//console.log(xSpeed);
//console.log(ySpeed);
}
setInterval(update, 500);
<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="#">
<title>The Snake Game</title>
</head>
<style>
#ctx {
position: absolute;
/*it can be fixed too*/
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
/*this to solve "the content will not be cut when the window is smaller than the content":*/
max-width: 100%;
max-height: 100%;
overflow: auto;
}
</style>
<body onkeydown="keyDown()" onload="load()">
<center><canvas id="ctx" width="500" height="500" style="border:1px solid black"></canvas></center>
</body>
<script src="script.js"></script>
</html>
The for loop inside of update function is not loggin, so it looks like it's not working.
for (i = x.lenght; i >= 0; i--) {
y[i] = y[i - 1];
x[i] = x[i - 1];
console.log(i);
}
I don't know what I've done wrong, but I think it's just another one stupid mistake. Please don't criticize me so much, I'm just nooby 14 y.o. programmer.
Thanks in advice,
Tomas ;-)
On a first sight it's just a typing mistake because you wrote x.lenght instead of x.length.
var ctx = document.getElementById('ctx').getContext('2d');
var canvas = document.getElementById('ctx');
var y = [240, 230, 220];
var x = [240, 240, 240];
var xSpeed = 0;
var ySpeed = 0;
function load() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (p = 0; p < x.length; p++) {
ctx.fillStyle = "black";
ctx.fillRect(x[p], y[p], 10, 10);
}
}
function keyDown() {
var key = event.keyCode; /*getting keyCode of pressed key*/
ctx.fillStyle = "black"; /*color of rectangle*/
// Prevent the snake from going in the reverse direction.
switch (key) {
case 39: //RIGHT
if(xSpeed != -10) {
xSpeed = 10;
ySpeed = 0;
}
break;
case 37: //LEFT
if(xSpeed != 10) {
xSpeed = -10;
ySpeed = 0;
}
break;
case 38: //UP
if(ySpeed != 10) {
xSpeed = 0;
ySpeed = -10;
}
break;
case 40: //DOWN
if(ySpeed != -10) {
xSpeed = 0;
ySpeed = 10;
}
break;
}
}
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (x[0] >= 490) {
xSpeed = 0;
} else if (y[0] >= 490) {
ySpeed = 0;
}
if((xSpeed + ySpeed) != 0) {
for(var idx = x.length - 1; idx >= 1; idx--) {
y[idx] = y[idx - 1];
x[idx] = x[idx - 1];
}
}
y[0] += ySpeed;
x[0] += xSpeed;
for (i = 0; i < x.length; i++) {
ctx.fillStyle = "black";
ctx.fillRect(x[i], y[i], 10, 10);
}
}
setInterval(update, 500);
<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="#">
<title>The Snake Game</title>
</head>
<style>
#ctx {
position: absolute;
/*it can be fixed too*/
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
/*this to solve "the content will not be cut when the window is smaller than the content":*/
max-width: 100%;
max-height: 100%;
overflow: auto;
}
</style>
<body onkeydown="keyDown()" onload="load()">
<center><canvas id="ctx" width="500" height="500" style="border:1px solid black"></canvas></center>
</body>
<script src="script.js"></script>
</html>
I really liked your code, especially for how simple it is! So I have added a slightly improved version here. These are the issues I noticed:
Spoilers
In the keyDown function, by adding the condition to check previous speed you can prevent the snake from reversing its direction.
In update function you should update the location of x[0] and y[0] only after updating the "tail" elements. Otherwise, you would not have the new location for the x[1] and y[1] element.
Also, you might need to put an if condition for (xSpeed + ySpeed) != 0 around the loop that updates the location of the tail elements. This would help ensure that the snake doesn't jumble up into single element before the user interacts with it.
I'm trying to update a barrage of bullets each frame in JavaScript, but it seems it's not working properly. Here's the code I put on JSFiddle.
https://jsfiddle.net/Reverseblade/co2mpLnv/5/
var ctx;
var hero;
var enemy;
var beams = [];
var beam_hitting = false;
var continuous_hit = false;
var count = 0;
var canvas_w = 500, canvas_y= 700;
var keycode = NaN;
var laser_degree = 200;
function init(){
createCanvas();
createMainHero();
createEnemy();
draw();
mainRoutine();
}
function mainRoutine(){
count++;
ctx.clearRect(0, 0, canvas_w, canvas_y);
// laserTest();
hero.move(keycode);
enemyUpdate();
// hero.setBullets();
// hero.moveBullets();
draw();
window.setTimeout(mainRoutine, 7);
}
function createCanvas(){
var canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.width = canvas_w;
canvas.height = canvas_y;
document.body.appendChild(canvas);
}
function createMainHero(){
hero = new Hero();
}
function createEnemy(){
enemy = new Enemy;
}
function Hero(){
this.w = this.h = 25,
this.x = canvas_w/2 - this.w/2,
this.y = canvas_y - this.h,
this.dx = this.dy = 2.5;
this.bullets = [];
this.move = function(key){
switch(key){
case 37: if (hero.x > 0) {hero.x -= this.dx;} break;
case 38: if (hero.y > 0) {hero.y -= this.dy;} break;
case 39: if (hero.x < canvas_w - hero.w) {hero.x += this.dx;} break;
case 40: if (hero.y < canvas_y - hero.h) {hero.y += this.dy;} break;
}
};
this.setBullets = function(){
if (count % 20 === 0) {
var w = h = 8;
var dx = dy = 5;
var x = hero.x + hero.w/2 - w/2;
var y = hero.y;
hero.bullets.push({x: x, y: y, w: w, h: h, dx: dx, dy: dy, moving:true});
}
};
this.moveBullets = function(){
for (var i = 0; i < this.bullets.length; i++) {
if (this.bullets[i].y < 0) {
this.bullets[i].moving = false;
continue;
}
if (this.bullets[i].moving === false) {
continue;
}
this.bullets[i].y -= this.bullets[i].dy;
if (this.bullets[i].y < -100) {this.bullets.splice(i, 1)}
}
}
}
function Enemy(){
this.w = this.h = 25;
this.x = canvas_w/2 - this.w/2;
this.y = 50;
this.bullets = [];
this.moving = false;
this.move_to = 0;
this.bullets_count = 0;
this.bullets_angle = 0;
this.current_skill = 1;
this.barrage_count = 0;
this.skill = function(){
enemySkill();
};
}
function enemyUpdate(){
function move(){
function changeDirection(){
var options = ["left", "right", "up", "down"];
var id;
if (enemy.x <= 50) {id = options.indexOf("left"); options.splice(id, 1);}
if (enemy.x >= 450) {id = options.indexOf("right");options.splice(id, 1);}
if (enemy.y <= 50) {id = options.indexOf("up");options.splice(id, 1);}
if (enemy.y >= 200) {id = options.indexOf("down");options.splice(id, 1);}
var rand = Math.floor(Math.random() * options.length);
enemy.moving = options[rand];
switch(enemy.moving){
case "left": enemy.move_to = enemy.x - 150 ; break;
case "right": enemy.move_to = enemy.x + 150 ; break;
case "up": enemy.move_to = enemy.y - 150 ; break;
case "down": enemy.move_to = enemy.y + 150 ; break;
}
} /* end changeDirection() */
if (count % 800 === 0) {changeDirection(); console.log("changing");}
switch(enemy.moving){
case "left": if (enemy.x > 50 && enemy.x > enemy.move_to) {enemy.x -= 0.5;} break;
case "right": if (enemy.x < 450 && enemy.x < enemy.move_to) {enemy.x += 0.5;} break;
case "up": if (enemy.y > 50 && enemy.y > enemy.move_to) {enemy.y -= 0.5; } break;
case "down": if (enemy.y < 200 && enemy.y < enemy.move_to) {enemy.y += 0.5; } break;
}
} /* end move()*/
move();
enemy.skill();
} /* end enemyUpdate() */
function enemySkill(){
// console.log("enemy skill");
function setBullets(){
var prev_status = enemy.bullets_count === 0 ? 500 : enemy.bullets[enemy.bullets.length - 1]["radius"];
if (prev_status >25) {
// console.log("bullets set");
var center_x = enemy.x + enemy.w/2;
var center_y = enemy.y + enemy.h/2;
var radius = 20;
var ceil = enemy.bullets.length === 0 ? 0 : enemy.bullets.length -1;
for (var angle = enemy.bullets_angle, i= ceil; angle < enemy.bullets_angle + 360; angle += 40, i++ ) {
// console.log("i: " + i);
var radian = angle * Math.PI / 180;
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log("angle: " + /angle + "set_x: " + set_x + "set_y: " + set_y);
enemy.bullets.push({"x": set_x, "y": set_y, "moving": true, "radius": radius, "center_x": center_x, "center_y": center_y, "w": 25, "h": 25, "radian": radian});
if (enemy.bullets_count === 0) {enemy.bullets_count++;}
// console.log(enemy.bullets[0][i]["x"]);
}
enemy.bullets_angle += 10;
enemy.barrage_count ++;
if (enemy.barrage_count % 100 === 0) {
enemy.current_skill = 0;
}
}
} /* end setBullets */
function moveBullets(){
if (count % 4 ===0) {
for (var i = 0; i < enemy.bullets.length; i++) {
if (enemy.bullets[i]["moving"] === true) {
var radian = enemy.bullets[i]["radian"];
var center_x = enemy.bullets[i]["center_x"];
var center_y = enemy.bullets[i]["center_y"];
enemy.bullets[i]["radius"] += 5;
var radius = enemy.bullets[i]["radius"];
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log(set_y);
enemy.bullets[i]["x"] = set_x;
enemy.bullets[i]["y"] = set_y;
if (enemy.bullets[i]["x"] < -100 || enemy.bullets[i]["x"] > canvas_w + 100 || enemy.bullets[i]["y"] < -100 || enemy.bullets[i]["y"] > canvas_y + 100 ) {
// enemy.bullets[i]["moving"] = false;
enemy.bullets.splice(i, 1);
}
}
}
}
}
if (enemy.current_skill === 1) {
setBullets();
}
moveBullets();
}
function draw(){
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
//hero
//ctx.fillStyle = "blue";
//ctx.fillRect(hero.x, hero.y ,hero.w, hero.h);
//enemy
//ctx.fillStyle = "red";
//ctx.fillRect(enemy.x, enemy.y ,enemy.w, enemy.h);
//heroの弾
ctx.fillStyle = "blue";
for (var i = 0; i < hero.bullets.length; i++) {
if (hero.bullets[i].moving === false) {
continue;
}
ctx.fillRect(hero.bullets[i].x, hero.bullets[i].y ,hero.bullets[i].w, hero.bullets[i].h);
}
//敵の弾
ctx.fillStyle = "red";
for (var i = 0; i < enemy.bullets.length; i++) {
ctx.fillStyle = "green";
if (enemy.bullets[i]["moving"] === false) {
continue;
}
ctx.beginPath();
ctx.arc(enemy.bullets[i]["x"], enemy.bullets[i]["y"], 15, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
}
window.addEventListener("keydown", function(e){
switch(e.keyCode){
case 37: keycode = 37; break;
case 38: keycode = 38; break;
case 39: keycode = 39; break;
case 40: keycode = 40; break;
}
}, false);
window.addEventListener("keyup", function(e){
switch(e.keyCode){
case 37: keycode = NaN; break;
case 38: keycode = NaN; break;
case 39: keycode = NaN; break;
case 40: keycode = NaN; break;
}
}, false);
init();
*{
margin:0;
padding:0;
}
#canvas{
background:url("../img/seamles_space.jpg");
animation: mainBack 5s linear infinite;
animation-play-state:paused;
background: black;
display: block;
position:relative;
margin:50px auto;
}
<body>
<script src="js/main.js"></script>
</body>
var ctx;
var hero;
var enemy;
var beams = [];
var beam_hitting = false;
var continuous_hit = false;
var count = 0;
var canvas_w = 500, canvas_y= 700;
var keycode = NaN;
var laser_degree = 200;
function init(){
createCanvas();
createMainHero();
createEnemy();
draw();
mainRoutine();
}
function mainRoutine(){
count++;
ctx.clearRect(0, 0, canvas_w, canvas_y);
// laserTest();
hero.move(keycode);
enemyUpdate();
// hero.setBullets();
// hero.moveBullets();
draw();
window.setTimeout(mainRoutine, 7);
}
function createCanvas(){
var canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.width = canvas_w;
canvas.height = canvas_y;
document.body.appendChild(canvas);
}
function createMainHero(){
hero = new Hero();
}
function createEnemy(){
enemy = new Enemy;
}
function Hero(){
this.w = this.h = 25,
this.x = canvas_w/2 - this.w/2,
this.y = canvas_y - this.h,
this.dx = this.dy = 2.5;
this.bullets = [];
this.move = function(key){
switch(key){
case 37: if (hero.x > 0) {hero.x -= this.dx;} break;
case 38: if (hero.y > 0) {hero.y -= this.dy;} break;
case 39: if (hero.x < canvas_w - hero.w) {hero.x += this.dx;} break;
case 40: if (hero.y < canvas_y - hero.h) {hero.y += this.dy;} break;
}
};
this.setBullets = function(){
if (count % 20 === 0) {
var w = h = 8;
var dx = dy = 5;
var x = hero.x + hero.w/2 - w/2;
var y = hero.y;
hero.bullets.push({x: x, y: y, w: w, h: h, dx: dx, dy: dy, moving:true});
}
};
this.moveBullets = function(){
for (var i = 0; i < this.bullets.length; i++) {
if (this.bullets[i].y < 0) {
this.bullets[i].moving = false;
continue;
}
if (this.bullets[i].moving === false) {
continue;
}
this.bullets[i].y -= this.bullets[i].dy;
if (this.bullets[i].y < -100) {this.bullets.splice(i, 1)}
}
}
}
function Enemy(){
this.w = this.h = 25;
this.x = canvas_w/2 - this.w/2;
this.y = 50;
this.bullets = [];
this.moving = false;
this.move_to = 0;
this.bullets_count = 0;
this.bullets_angle = 0;
this.current_skill = 1;
this.barrage_count = 0;
this.skill = function(){
enemySkill();
};
}
function enemyUpdate(){
function move(){
function changeDirection(){
var options = ["left", "right", "up", "down"];
var id;
if (enemy.x <= 50) {id = options.indexOf("left"); options.splice(id, 1);}
if (enemy.x >= 450) {id = options.indexOf("right");options.splice(id, 1);}
if (enemy.y <= 50) {id = options.indexOf("up");options.splice(id, 1);}
if (enemy.y >= 200) {id = options.indexOf("down");options.splice(id, 1);}
var rand = Math.floor(Math.random() * options.length);
enemy.moving = options[rand];
switch(enemy.moving){
case "left": enemy.move_to = enemy.x - 150 ; break;
case "right": enemy.move_to = enemy.x + 150 ; break;
case "up": enemy.move_to = enemy.y - 150 ; break;
case "down": enemy.move_to = enemy.y + 150 ; break;
}
} /* end changeDirection() */
if (count % 800 === 0) {changeDirection(); console.log("changing");}
switch(enemy.moving){
case "left": if (enemy.x > 50 && enemy.x > enemy.move_to) {enemy.x -= 0.5;} break;
case "right": if (enemy.x < 450 && enemy.x < enemy.move_to) {enemy.x += 0.5;} break;
case "up": if (enemy.y > 50 && enemy.y > enemy.move_to) {enemy.y -= 0.5; } break;
case "down": if (enemy.y < 200 && enemy.y < enemy.move_to) {enemy.y += 0.5; } break;
}
} /* end move()*/
move();
enemy.skill();
} /* end enemyUpdate() */
function enemySkill(){
// console.log("enemy skill");
function setBullets(){
var prev_status = enemy.bullets_count === 0 ? 500 : enemy.bullets[enemy.bullets.length - 1]["radius"];
if (prev_status >25) {
// console.log("bullets set");
var center_x = enemy.x + enemy.w/2;
var center_y = enemy.y + enemy.h/2;
var radius = 20;
var ceil = enemy.bullets.length === 0 ? 0 : enemy.bullets.length -1;
for (var angle = enemy.bullets_angle, i= ceil; angle < enemy.bullets_angle + 360; angle += 40, i++ ) {
// console.log("i: " + i);
var radian = angle * Math.PI / 180;
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log("angle: " + /angle + "set_x: " + set_x + "set_y: " + set_y);
enemy.bullets.push({"x": set_x, "y": set_y, "moving": true, "radius": radius, "center_x": center_x, "center_y": center_y, "w": 25, "h": 25, "radian": radian});
if (enemy.bullets_count === 0) {enemy.bullets_count++;}
// console.log(enemy.bullets[0][i]["x"]);
}
enemy.bullets_angle += 10;
enemy.barrage_count ++;
if (enemy.barrage_count % 100 === 0) {
enemy.current_skill = 0;
}
}
} /* end setBullets */
function moveBullets(){
if (count % 4 ===0) {
for (var i = 0; i < enemy.bullets.length; i++) {
if (enemy.bullets[i]["moving"] === true) {
var radian = enemy.bullets[i]["radian"];
var center_x = enemy.bullets[i]["center_x"];
var center_y = enemy.bullets[i]["center_y"];
enemy.bullets[i]["radius"] += 5;
var radius = enemy.bullets[i]["radius"];
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log(set_y);
enemy.bullets[i]["x"] = set_x;
enemy.bullets[i]["y"] = set_y;
if (enemy.bullets[i]["x"] < -100 || enemy.bullets[i]["x"] > canvas_w + 100 || enemy.bullets[i]["y"] < -100 || enemy.bullets[i]["y"] > canvas_y + 100 ) {
// enemy.bullets[i]["moving"] = false;
enemy.bullets.splice(i, 1);
}
}
}
}
}
if (enemy.current_skill === 1) {
setBullets();
}
moveBullets();
}
function draw(){
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
//hero
//ctx.fillStyle = "blue";
//ctx.fillRect(hero.x, hero.y ,hero.w, hero.h);
//enemy
//ctx.fillStyle = "red";
//ctx.fillRect(enemy.x, enemy.y ,enemy.w, enemy.h);
//heroの弾
ctx.fillStyle = "blue";
for (var i = 0; i < hero.bullets.length; i++) {
if (hero.bullets[i].moving === false) {
continue;
}
ctx.fillRect(hero.bullets[i].x, hero.bullets[i].y ,hero.bullets[i].w, hero.bullets[i].h);
}
//敵の弾
ctx.fillStyle = "red";
for (var i = 0; i < enemy.bullets.length; i++) {
ctx.fillStyle = "green";
if (enemy.bullets[i]["moving"] === false) {
continue;
}
ctx.beginPath();
ctx.arc(enemy.bullets[i]["x"], enemy.bullets[i]["y"], 15, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
}
window.addEventListener("keydown", function(e){
switch(e.keyCode){
case 37: keycode = 37; break;
case 38: keycode = 38; break;
case 39: keycode = 39; break;
case 40: keycode = 40; break;
}
}, false);
window.addEventListener("keyup", function(e){
switch(e.keyCode){
case 37: keycode = NaN; break;
case 38: keycode = NaN; break;
case 39: keycode = NaN; break;
case 40: keycode = NaN; break;
}
}, false);
init();
There's no problem at the beginning, but then some of the bullets would start acting weirdly as if after some point.
The cause is that an item is removed while in a for-loop, causing one bullet to be skipped, more precisely, this line:
enemy.bullets.splice(i, 1);
I would suggest a different approach - build a new array only consisting of the active bullets (moving===true), then after the loop replace the array with the new.
For example:
function moveBullets(){
if (count % 4 ===0) {
// will hold active bullets in current pass
var newBullets = [];
for (var i = 0; i < enemy.bullets.length; i++) {
// cut code for clarity
if (!(enemy.bullets[i].x < -100 || enemy.bullets[i].x > canvas_w + 100 ||
enemy.bullets[i].y < -100 || enemy.bullets[i].y > canvas_y + 100 )) {
newBullets.push(enemy.bullets[i]);
}
}
// replace array with only active bullets
enemy.bullets = newBullets;
}
}
The new array will simply hold references to existing active bullets.
Modified Fiddle
Particles and object pools.
Creating a new array is not a good strategy when dealing with particle systems. Which is what bullets are in effect.
Each bullet deleted, will need to be cleaned up by GC (Garbage Collection), each bullet added needs to be created and requires memory allocation. These overhead can have a negative effect on a game. GC can cause an animation to hang at random times.
For consistent, smooth animation you should aim for zero allocation and deletion (which is possible as asm.js and web Assembly do not allocate or delete inside a running modula).
Object pool
In vanilla JS it is possible but the code is too complex for this answer. The next best solution is to use a object pool.
As bullets are first created as normal, but when a bullet is no longer needed rather than dereference it you move it to another array (called the pool), next time a bullet is needed you first check if any are available on the pool and use that rather than create a new object.
This ensures that GC only has to clean up the changing array sizes and not the data used by each bullet.
var bullets = [];
var bulletPool = [];
function createBullet(x,y,dir,whatNot){
var newBullet;
if(bulletPool.length > 0){
newBullet = bulletPool.pop(); // reuse old bullet memory
newBullet.x = x;
newBullet.y = y;
newBullet.dir = dir;
newBullet.whatNot = whatNot;
newBullet.active = true;
}else{
newBullet = {x,y,dir,whatNot,active:true}; // create only if needed
}
return newBullet;
}
function fire(){
bullets.push(createBullet(10,10,0,0)); /// add a bullet
}
In your code when a bullet is no longer needed just set the active flag to false. At the end of the game loop remove all the inactive bullets.
When you delete a bullet just move it from the bullet array to the pool
function cleanBulletsArray(){
for(var i = 0; i < bullets.length; i ++){
if(!bullets[i].active){
bulletPool.push(bullets.splice(i--,1)[0]);
}
}
}
Precipitation array
So called because the active items fall to the bottom of the array
An even better method is to use just one array. When a bullet is not active it stays in the array, but as you iterate the array you swap active bullets for inactive, with active bullets moving down and inactive up. There is at most one swap per iteration. You also keep a count of the number of active bullets. When you need a new bullet you just reset the properties of the bullet at index count + 1 and then increase count by one.
This method (if you pre allocate all the bullets at the start of the game) has zero GC and Memory overhead and is significantly quicker than the creation, destruction, and array replacement methods.
This is my first year in programming and I'm having problems clearing my gEnemies canvas.
$(document).ready(function() {
initStars(600);
});
var FPS = 60;
width = 300;
height = 400;
var gBackground = document.getElementById("canvas_background").getContext("2d");
var gPlayer = document.getElementById("canvas_player").getContext("2d");
var gEnemies = document.getElementById("canvas_enemies").getContext("2d");
var GUI = document.getElementById("canvas_ui").getContext("2d");
var bullets = [];
var enemies = [];
var shootTimer = 0;
var maxShootTimer = 15;
var Key = {
up: false,
down: false,
left: false,
right: false
};
var player = {
width: 16,
height: 16,
x: (width / 2) - 8,
speed: 3,
y: height - 20,
canShoot: true,
render: function() {
gPlayer.fillStyle="aqua";
gPlayer.fillRect(this.x,this.y,this.width,this.height);
},
tick: function() {
if(Key.left && this.x > 0) this.x -= this.speed;
if(Key.right && this.x < width - 20) this.x += this.speed;
if(Key.space && this.canShoot) {
this.canShoot = false;
bullets.push(new Bullet(this.x,this.y - 4));
bullets.push(new Bullet(this.x + this.width,this.y - 4));
shootTimer = maxShootTimer;
}
}
};
stars = [];
addEventListener("keydown", function(e) {
var keyCode = (e.keyCode) ? e.keyCode : e.which;
switch(keyCode) {
case 38: // up
Key.up = true;
break;
case 40: // down
Key.down = true;
break;
case 37: // left
Key.left = true;
break;
case 39: // right
Key.right = true;
break;
case 32: //spacebar
Key.space = true;
break;
}
}, false);
addEventListener("keyup", function(e) {
var keyCode = (e.keyCode) ? e.keyCode : e.which;
switch(keyCode) {
case 38: // up
Key.up = false;
break;
case 40: // down
Key.down = false;
break;
case 37: // left
Key.left = false;
break;
case 39: // right
Key.right = false;
break;
case 32: //spacebar
Key.space = false;
break;
}
}, false);
function collision(obj1,obj2) {
return (
obj1.x < obj2.x+obj2.width &&
obj1.x + obj1.width > obj2.x &&
obj1.y < obj2.y+obj2.height &&
obj1.y + obj1.height > obj2.y
);
}
function Star(x,y) {
this.x = x;
this.y = y;
this.size = Math.random() * 2.5;
this.render = function() {
gBackground.fillStyle = "white";
gBackground.fillRect(this.x,this.y,this.size,this.size)
};
this.tick = function() {
this.y++;
}
}
function createStars(amount) {
for(i=0;i<amount;i ++) {
stars.push(new Star(Math.random() * width, -5));
}
}
function initStars(amount) {
for(i=0;i<amount;i++) {
stars.push(new Star(Math.random()*width,Math.random()*height));
}
}
function Bullet(x,y) {
this.x = x;
this.y = y;
this.width = 2;
this.height = 12;
this.speed = 5;
this.render = function() {
gPlayer.fillStyle = "red";
gPlayer.fillRect(this.x,this.y,this.width,this.height);
};
this.tick = function() {
if(this.y < -this.height) {
var index = bullets.indexOf(this);
bullets.splice(index,1);
}
this.y-=this.speed;
};
}
function Enemy(x,y) {
this.x = x;
this.y = y;
this.width = 16;
this.height = 16;
this.speed = 0.5;
this.render = function() {
gEnemies.fillStyle = "red";
gEnemies.fillRect(this.x,this.y,this.width,this.height);
};
this.tick = function() {
this.y += this.speed;
}
}
function render() {
gBackground.clearRect(0,0,width,height);
gPlayer.clearRect(0,0,width,height);
gEnemies.clearRect(0,0,this.width,this.height);
player.render();
for(x=0;x<8;x++) {
for(y=0;y<8;y++) {
enemies.push(new Enemy(x,y));
}
}
for(i in enemies) enemies[i].render();
for(i in stars) {
stars[i].render();
}
for(i in bullets) bullets[i].render();
}
function tick() {
createStars(1);
player.tick();
for(i in enemies) enemies[i].tick();
for(i in stars) stars[i].tick();
for(i in bullets) bullets[i].tick();
if(shootTimer <= 0) player.canShoot = true;
shootTimer--;
}
setInterval(function() {
render();
tick();
}, 1000/FPS );
canvas {
position: absolute;
top: 0;
left: 0;
}
#canvas_background {
background: black;
}
<!DOCTYPE html>
<html>
<head>
<title> Game </title>
</head>
<body>
<canvas id='canvas_background' width='300' height='400'></canvas>
<canvas id='canvas_player' width='300' height='400'></canvas>
<canvas id='canvas_enemies' width='300' height='400'></canvas>
<canvas id='canvas_ui' width='300' height='400'></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
</body>
</html>
Everything is working properly. I can move the square and shoot bullets. However, the enemies canvas is not clearing properly.
To be clear, this is how I'm trying to clear the Enemies Canvas:
gEnemies.clearRect(0,0,width,height);
Why is the Enemies Canvas not clearing?
It is clearing. The problem is you keep creating 64 new enemies every time you render:
for(x=0;x<8;x++) {
for(y=0;y<8;y++) {
enemies.push(new Enemy(x,y));
}
}
Add this line to your render function and you'll see what I mean:
console.log('enemies='+enemies.length);