画布动画不能顺利运行

时间:2017-03-10 06:24:08

标签: javascript html5-canvas

我正在尝试在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();

一开始没有问题,但是一些子弹会开始表现得很奇怪,好像在某个点之后。

2 个答案:

答案 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;
    }
 }

新阵列将只保留对现有活动子弹的引用。

Modified Fiddle

答案 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和内存开销为零,并且比创建,销毁和阵列替换方法快得多。