Canvasでﾃｨｳﾝﾃｨｳﾝﾃｨｳﾝ...

# はじめに
Canvasを利用して、ライフが無くなった時のゲームオーバーエフェクトを表現してみました。SE（音）は著作権的にアヤシイ気がしたので付けていません。エフェクトはシリーズによってもずいぶん違うかと思いますが、何となく雰囲気が伝われば幸いです。

> [ティウンティウンとは (ニコニコ大百科)](http://dic.nicovideo.jp/a/%E3%83%86%E3%82%A3%E3%82%A6%E3%83%B3%E3%83%86%E3%82%A3%E3%82%A6%E3%83%B3)


**[動作イメージ]**
※エフェクト以外はDOMで表現しています
![thiwn_thiwn.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/3b205522-f516-c525-37cb-774cd02ded0b.gif)



> なお実際の動作は以下で確認することができます
> http://codepen.io/nekoneko-wanwan/pen/JYmaJx





# 特長・仕様

- Canvasサイズに依存しない
- 粒の数・大きさ・色は柔軟に変更可能
- ※少しラクをしたので、調整には直接関数内の修正が必要ですがご容赦ください





# ソースコード

```index.html
<!-- 動作イメージに出てくる○キャラは省略 -->
<canvas id="myCanvas" width="500" height="300"></canvas>
```

```canvas.js
var cs       = document.getElementById('myCanvas');
var ctx      = cs.getContext('2d');
var csWidth  = cs.width;
var csHeight = cs.height;

var fps = 60;
var frameTime_ms  = 1000 / fps; // 1フレーム辺りのミリ秒
var frameTime_sec = frameTime_ms / 1000; // 秒数に変換

var startTime = new Date().getTime();

/* 経過時間を返す */
var getTimer = function() {
  return (new Date().getTime() - startTime);
};


/* ﾃｨｳﾝﾃｨｳﾝﾃｨｳﾝ...コンストラクタ */
var ThiwnThiwn = function() {
  this.initialize();
};

ThiwnThiwn.prototype = {
  initialize: function() {
    this.x  = 0;
    this.y  = 0;
    this.r  = 20;
    this.vx = 0;
    this.vy = 0;
    this.scale = 0;
    this.alpha = 1;
    this.isBorder = true;
    this.time = getTimer();
  },
  update: function() {
    /* 実行時の経過時間を定義 */
    var nowTime = getTimer();

    /* X座標とY座標を更新 */
    this.x += this.vx * frameTime_sec;
    this.y += this.vy * frameTime_sec;

    /* 透明度を下げていく */
    this.alpha -= 0.4 * frameTime_sec;
    if (this.alpha < 0) {
      this.alpha = 0;
    }

    /* 経過時間によってスタイルを変更 */
    if (nowTime - this.time > 0) {
      this.scale += 3 * frameTime_sec;
      this.isBorder = false;
    }
    if (nowTime - this.time > 300) {
      this.isBorder = true;
      this.scale = 0;
      this.time = nowTime;
    }
  },
  draw: function() {
    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.globalAlpha = this.alpha;

    /* 枠線のみ描画 */
    if (this.isBorder) {
      ctx.beginPath();
      ctx.arc(0, 0, this.r + 7, 0, Math.PI * 2, false);
      ctx.closePath();
      ctx.lineWidth = 3;
      ctx.strokeStyle = '#fff';
      ctx.stroke();
      ctx.closePath();
    }

    /* 中心部を描画 */
    ctx.scale(this.scale, this.scale);
    var grad = ctx.createRadialGradient(0, 0, 0, 0, 0, this.r);
    ctx.beginPath();
    grad.addColorStop(0.8, '#fff ');
    grad.addColorStop(1, 'rgba(255,213,0,0.8)');
    ctx.fillStyle = grad;
    ctx.arc(0, 0, this.r, 0, Math.PI * 2, false);
    ctx.fill();

    ctx.restore();
  }
};

/**
 * コンストラクタから複数のインスタンスを作成して配列を返す
 * @param {num} x: エフェクトを発生させる中心のX座標
 * @param {num} y: エフェクトを発生させる中心のY座標
 * @param {num} deg: インスタンスをいくつ作成するかに使用
 *   45を指定すると、360/45 = 8個生成される
 */
var createCircle = function(x, y, deg) {
    var arr  = [];  // 複数のインスタンスを格納
    var sp   = 200; // アニメーション速度に影響
    var _deg = 0;
    var obj; // インスタンスを格納
    for (; _deg <= 360 * 2; _deg += deg) {
        obj = new ThiwnThiwn();

        /* 2週目以降はスピードを落とすことで、二重の輪を表現 */
        if (_deg > 360) {
            sp = 100;
        }

        obj.x  = x;
        obj.y  = y;
        obj.vx = sp * Math.cos(_deg * Math.PI / 180);
        obj.vy = sp * Math.sin(_deg * Math.PI / 180);

        arr[arr.length] = obj;
    }
    return arr;
};

/**
 * レンダリング
 * @param {arr} arr: createCircleにより複数インスタンスを格納した配列を渡す
 */
var render = function(arr) {
    var obj;
    ctx.clearRect(0, 0, csWidth, csHeight);
    for (var i = 0, l = arr.length; i < l; i++) {
        obj = arr[i];
        obj.update();
        obj.draw();
    }
    requestAnimationFrame(function() {
        render(arr);
    });
};

/* 実行 */
var circles = createCircle(csWidth/2, csHeight/2, 45);
setTimeout(function() {
    render(circles);
}, 100);
```





# 作り方・考え方

## コンストラクタの用意
- 丸オブジェクトを生成するコンストラクタを用意します
- インスタンスには更新時に必要な値を持たせます（x, y座標、スケールなど）
- インスタンスの共通メソッドとして、以下を用意します
    - どのように値を更新していくかの`update()`
    - どのようにCanvasに描画していくかの`draw()`
- `update()`内では経過時間を見ることで、動作の細かい制御を可能にしています
- `draw()`内では、`translate()`と`scale()`によりアニメーションを制御しています
- 描画の度に座標軸のズレを初期化するため、`save()`, `restore()`で元に戻してます

## インスタンスを生成し、インスタンスを実行するメソッドを用意
- `createCircle()`メソッドにより、コンストラクタから複数のインスタンスを生成します
- `render()`メソッドにより、一定期間ごとに各インスタンスの`update()`, `draw()`を一度に実行し続けます

> なおrequestAnimationFrameを止める処理は考慮していません


## 残り
- 最後に`createCircle()`と`render()`を実行する処理を書いてお終いです
- サンプルの動作イメージでは、自キャラと敵キャラの位置を監視し、重なった（衝突と見なした）ら上記メソッドを実行するようにしています




# 終わりに

作り方は自分で考えてみたので、書き方が微妙だったり非効率な可能性があります。もっと良い方法があればご教授くださいませ。





**今まで作成した謎なシリーズ**
> [Canvasでビックリマンなシールを書いてみた（光るゾ！）](http://qiita.com/nekoneko-wanwan/items/a6db5995029328f03720)
> [Canvasで斬！っと画面が斬れるようなエフェクトを書いてみた](http://qiita.com/nekoneko-wanwan/items/bdbd0d3cf294fc54f6dd)
> [Canvasで漫画にあるような吹き出しを書いてみた](http://qiita.com/nekoneko-wanwan/items/df5d8eaf33fa477fe7c9)
> [Canvasで漫画にあるような "ざわ・・・" を書いてみた](http://qiita.com/nekoneko-wanwan/items/4b196365183580831758)
> [Canvasで漫画にあるような集中線を書いてみた](http://qiita.com/nekoneko-wanwan/items/0911a59bf835d5b9e35a)