コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

canvasで少しずつ作るブロック崩し(3/5)

前回はボールを1個だけ出現させてバーで跳ね返せる機能を作りました。複数のボールを出現する機能とクリックでバーを上下させボールを打ち返せる機能をつけてみます。

機能追加版

左クリック: バーを下げる
右クリック: ボール出現


ソースコード

<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript">
(function() {

  var canvas;
  var ctx;
  var width;
  var height;
  var mouseX;
  var mouseY;
  var barTimerID;
  var pushTimerID;
  var popTimerID;
  var ball_id = 0;
  var ball = [];
  
  var BAR = {
    'HEIGHT' : 10,
    'WIDTH'  : 50,
    'UNDER'  : 25,
    'X'      : 0,
    'Y'      : 0,
    'PUSH'   : 10,
    'Vx'     : 0,
    'Vx0'    : 0,
    'Vy'     : 0,
    'Vy0'    : 100,
    'M'      : 10,
    'E'      : 0.7,
    'E0'     : 10,
  };
  
  var BALL = {
    'ALIVE'  : 0,
    'X'      : 0,   
    'Y'      : 0,   
    'Vx'     : 0,   // ボール速度(x成分)
    'Vx0'    : 100, // ボールの初速
    'Vy'     : 0,   // ボール速度(y成分)
    'Vy0'    : 600, // ボールの初速(y成分の最低速度)
    'RADIUS' : 5,
    'M'      : 1,
  };
  
  var FIELD = {
    'GRAVITY' : 588, // 重力加速度(0.9 * FPS)
    'FPS'     :  60, // frame per second
    'E'       : 0.7, 
    'E0'      : 10,
  };
  
  
  var Ball = function(id) {
    this.id = id;
    this.Alive = 0;
    this.X = 0;
    this.Y = 0;
    this.Vy = 0;
    this.Vx = 100;
    this.Vx0 = 0;
    this.Vy0 = 200;
    this.RADIUS = 5;
    this.M = 1;
    this.timerID;
  };
  
  // 初期化処理
  function initialize() {
    canvas = document.getElementById('canvas');
    if(!canvas && !canvas.getContext) {
      return false;
    }
    
    ctx = canvas.getContext('2d');
    width = ctx.canvas.width ;
    height = ctx.canvas.height;
    
    // バーの初期位置は中心
    mouseX = width/2;
    BAR.X = mouseX;
    BAR.Y = height-BAR.UNDER;
    
    
    canvas.addEventListener('mousemove', getMouseCoordinate, false);
    canvas.addEventListener('mousedown', pushBar, false);
    canvas.addEventListener('mouseup', popBar, false);
    canvas.addEventListener('contextmenu', putBall, false);
    
    setInterval(calcBallP, 1000/FIELD.FPS);
    setInterval(drawField, 1000/FIELD.FPS);
  };
  
  
  // マウス座標の更新
  function getMouseCoordinate(e) {
    var rect = e.target.getBoundingClientRect();
    mouseX = Math.floor(e.clientX - rect.left);
    mouseY = Math.floor(e.clientY - rect.top);
  };
  
  // ボールの生成
  function putBall(e) {
    e.preventDefault();
    
    // ボールの初期位置は中心
    ball[ball_id] = new Ball(ball_id);
    ball[ball_id].Alive = 1;
    ball[ball_id].X = BAR.X;
    ball[ball_id].Y = height-BAR.UNDER;
    ball[ball_id].Vy = ball[ball_id].Vy0 * (-1);
    
    ball_id++;
    
    console.log(ball.length);
  };
  
  // バーの収縮
  function pushBar(e) {
    if (!e.pageX) {
      e = event.touches[0];
    }
    
    if (e.button == 0) {
      //BAR.Y += BAR.PUSH;
      clearInterval(popTimerID);
      clearInterval(pushTimerID);
      pushTimerID = setInterval(pushBarEvent, 1000/FIELD.FPS);
    }
  };
  
  // バーの反発
  function popBar(e) {
    if (!e.pageX) {
      e = event.touches[0];
    }
    
    if (e.button == 0) {
      //BAR.Y = height-BAR.UNDER;
      clearInterval(popTimerID);
      clearInterval(pushTimerID);
      pushTimerID = setInterval(popBarEvent, 1);
    }
  };
  
  
  // バーの収縮処理
  function pushBarEvent() {
    if (BAR.Y < height - BAR.UNDER + BAR.PUSH) {
      BAR.Vy = BAR.Vy0;
      BAR.Y += BAR.Vy * (FIELD.FPS/1000);
    } else {
      BAR.Vy = 0;
      BAR.Y = height - BAR.UNDER + BAR.PUSH;
    }
  };
  
  // バーの反発処理
  function popBarEvent() {
    if (BAR.Y > height-BAR.UNDER) {
      BAR.Vy = BAR.Vy0 * (-1);
      BAR.Y  += BAR.Vy * (FIELD.FPS/1000);
    } else {
      BAR.Vy = 0;
      BAR.Y = height-BAR.UNDER;
    }
  };
  
  // ボール位置計算
  function calcBallP() {
    
    for (var i = 0; i < ball.length; i++) {
      
      // 生存しているボールのみ計算
      if (ball[i].Alive) {
        // 床・天井接触
        if (ball[i].Y <= 0 || height <= ball[i].Y) {
          if (ball[i].Y <= 0) {
            
            // 天井
            ball[i].Y = FIELD.E0;
            ball[i].Vy = ball[i].Vy * FIELD.E * (-1)
          } else {
            // 床
            // 床に接触したボールは死亡
            ball[i].Alive = 0;
            ball[i].Vx = 0;
            ball[i].Vy = 0;
          }
        }
        
        // 壁接触
        if (ball[i].X <= 0 || width <= ball[i].X) {
          if (ball[i].X <= 0) {
            ball[i].X = FIELD.E0;
          } else {
            ball[i].X = width - FIELD.E0;
          }
          
          ball[i].Vx = ball[i].Vx * (-1);
        }
        
        // バー接触
        if (BAR.X <= ball[i].X && ball[i].X <= BAR.X + BAR.WIDTH) {
          if (Math.abs(ball[i].Y - BAR.Y) <= BAR.HEIGHT) {
            
            // バーとボールの境界でバタつきを防ぐための処置
            ball[i].Y = BAR.Y - BAR.E0;
            
            // バー接触後の速度計算
            ball[i].Vy = (ball[i].M * ball[i].Vy - BAR.M * BAR.Vy) / ball[i].M;
            
            // Vyが初速度より減速した場合は、初速度に戻す
            ball[i].Vy = Math.abs(ball[i].Vy) > ball[i].Vy0 ? (ball[i].Vy * BAR.E * (-1)) : (ball[i].Vy0 * (-1));
          }
        }
        
        // 縦計算
        ball[i].Vy += FIELD.GRAVITY * (1/FIELD.FPS);
        ball[i].Y  += ball[i].Vy * (1/FIELD.FPS);
        
        // 横計算
        ball[i].X += ball[i].Vx * (1/FIELD.FPS);
      }
    } 
  };
  
  // 画面の描画
  function drawField() {
    
    drawBack();
    drawBall();
    drawBar();
    
  };
  
  function drawBack() {
    ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
    ctx.fillRect(0, 0, width, height);
  };
  
  var hue = 0.5;
  
  function drawBall() {
    
    ctx.save();
    
    // 生存しているボールの数だけ描画
    for(var i = 0; i < ball.length; i++) {
      if (ball[i].Alive) {
        // 円の描画設定
        ctx.beginPath();
        ctx.arc(ball[i].X, ball[i].Y, ball[i].RADIUS, 0, 2*Math.PI, true);
        ctx.closePath();
        
        // 色設定
        hue += 0.5;
        ctx.strokeStyle = 'hsl(' + hue + ', 50%, 50%)';
        ctx.fillStyle = 'hsl(' + hue + ', 50%, 50%)';
        ctx.shadowColor = 'hsl(' + hue + ', 50%, 50%)';
        ctx.shadowBlur = 10;
      }
      
      // 描画実行
      ctx.stroke();
      ctx.fill();
    
    }
    
    ctx.restore();
  };
  
  function drawBar() {
    var delay = 1;
    
    BAR.X = (mouseX + delay * BAR.X) / (delay+1);
    
    // 色設定
    ctx.fillStyle = 'rgb(255,255,255)';
    
    // 円の描画設定
    ctx.fillRect(BAR.X, BAR.Y, BAR.WIDTH, BAR.HEIGHT);
    
  };
  
  // 初期化イベント
  window.addEventListener('load', initialize, false);
  
} ) ();
</script>
</head>
<body>
<canvas id='canvas' width=500 height=300></canvas>
</body>
</html>



次回はブロックを作ってゲームっぽくしていきます。 あと、バーで球を打つ時の判定は改善しないとダメそう。

関連ページ

canvasで少しずつ作るブロック崩し(1/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(2/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(その4) - Segmentation Fault
canvasで少しずつ作るブロック崩し(5/5) - Segmentation Fault