我正在尝试在JavaScript中每帧更新一连串子弹,但似乎它无法正常工作。这是我在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();
一开始没有问题,但是一些子弹会开始表现得很奇怪,好像在某个点之后。
答案 0 :(得分:1)
原因是在for循环中删除了一个项目,导致跳过一个项目符号,更准确地说,这一行:
enemy.bullets.splice(i, 1);
我建议采用另一种方法 - 构建一个仅包含活动项目符号(moving===true
)的新数组,然后在循环后用新项替换数组。
例如:
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;
}
}
新阵列将只保留对现有活动子弹的引用。
答案 1 :(得分:1)
在处理粒子系统时,创建新数组不是一个好策略。子弹有效的是什么。
每个子弹被删除,需要通过GC(垃圾收集)进行清理,每个子弹都需要创建并需要内存分配。这些开销会对游戏产生负面影响。 GC可以导致动画随机挂起。
对于一致,流畅的动画,你应该针对零分配和删除(这可能是因为asm.js和web Assembly不在正在运行的模块中分配或删除)。
在vanilla JS中,这是可能的,但代码对于这个答案来说太复杂了。下一个最佳解决方案是使用对象池。
由于子弹首先是正常创建的,但是当不再需要子弹而不是取消引用它时,将其移动到另一个阵列(称为池),下次需要子弹时,首先要检查是否有可用的子弹。使用它而不是创建新对象。
这确保GC只需清理不断变化的数组大小,而不是每个项目符号使用的数据。
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
}
在您的代码中,当不再需要子弹时,只需将活动标志设置为false。在游戏循环结束时,删除所有不活动的子弹。
当您删除子弹时,只需将其从子弹阵列移动到池
function cleanBulletsArray(){
for(var i = 0; i < bullets.length; i ++){
if(!bullets[i].active){
bulletPool.push(bullets.splice(i--,1)[0]);
}
}
}
之所以调用,是因为活动项目落在数组的底部
更好的方法是只使用一个数组。当子弹未激活时,它将保留在阵列中,但是当您迭代阵列时,您将活动子弹交换为非活动状态,活动子弹向下移动并处于非活动状态。每次迭代最多只有一次交换。您还可以计算活动子弹的数量。当你需要一个新子弹时,你只需重置子弹的属性,索引计数+ 1,然后将计数增加一。
此方法(如果您在游戏开始时预先分配所有项目符号)的GC和内存开销为零,并且比创建,销毁和阵列替换方法快得多。