2020-08-02 2020-08-03
CreateJSで夜空に打ちあがる花火を表現する
HTML5 Canvasでリッチコンテンツを表現するためのJavaScriptライブラリ群であるCreateJS。 今回はそのCreateJSを使用して、夜空に打ちあがる花火を表現するサンプルをご紹介します。
…花火といっても鮮やかな色の円を描画しただけですが。。
ライブラリの読み込みと初期設定
まずCreateJSのライブラリを読み込みます。今回はCDNを利用します。
イベントリスナーを設定し、画面の読み込みが完了したらinit関数を呼び出します。
window.addEventListener('load', init);
let stage = null;
let width = null;
let height = null;
let fireworks = null;
let fireworksArray = [];
let frame = 0;
function init() {
stage = new createjs.Stage('myCanvas');
width = stage.canvas.width;
height = stage.canvas.height;
stage.autoClear = false;
// 背景
let bg = new createjs.Shape();
bg.graphics
.beginFill('#333')
.drawRect(0, 0, width, height);
bg.alpha = 0.6;
stage.addChild(bg);
createjs.Ticker.on('tick', render); // 自動更新を有効にする
createjs.Ticker.timingMode = createjs.Ticker.RAF; // 滑らかに
}
Stage.autoClearプロパティをfalseにすると、古い描画が残ったままになり、残像を作ることができます。
背景として描画した四角形のShapeの透明度を.alphaで指定することで、残像の強さを調整しています。
createjs.Tickerクラスにイベントリスナーを設定し、tickイベントを監視します。tickイベントが発生するたびに
render関数を呼び出して画面を更新します。また、より滑らかに描画するためにcreatejs.Ticker.RAFを使うように指定しています。
これで1秒間に60回、フレームが更新されます。
花火クラスの作成
createjs.Containerクラスを継承して花火クラスを設計していきます。
class Fireworks extends createjs.Container {
constructor() {
super();
this.x = Math.random() * width;
this.y = height + Math.random() * 30;
this.xSpeed = -3 + Math.random() * 6;
this.ySpeed = -3 - Math.random() * 6;
this.baseColor = createjs.Graphics.getHSL(63, 50, 50);
this.color = createjs.Graphics.getHSL(360 * Math.random(), 50, 50);
this.circles = [];
this.round = 4; // 外に広がる円周の数
this.countCircle = 12; // 1周あたりの花火の数
this.radius = 70 + Math.random() * 250; // 花火の半径
this.size = Math.min(this.radius / 12, 15); // 花火の球一つあたりの大きさ
this.life = 1;
this.isExploded = false;
// 中心の円
let centerCircle = new createjs.Shape();
centerCircle.graphics
.beginFill(this.baseColor)
.drawCircle(0, 0, 10);
this.addChild(centerCircle);
centerCircle.compositeOperation = "lighter";
this.centerCircle = centerCircle;
// 外側に広がる円
for (let i=0; i<this.round; i++) {
let circleArray = []; // 1週分の花火の配列
// 1周あたり12個づつ花火の円を追加
for (let j=0; j<this.countCircle; j++) {
let circle = new createjs.Shape();
circle.compositeOperation = "lighter";
this.addChild(circle);
circleArray.push(circle);
}
this.circles.push(circleArray);
}
}
// 上に向かって進む処理
moveUp() {
this.x += this.xSpeed;
this.y += this.ySpeed;
this.ySpeed += 0.05;
}
// 爆発処理
explode() {
// 中心円のアニメーション
createjs.Tween.get(this.centerCircle)
.to({scaleX: 100, scaleY: 100, alpha: 0}, 1200, createjs.Ease.circOut)
;
// 外側円のアニメーション
for (let i=0; i<this.circles.length; i++) {
if (i > 0) {
this.color = this.baseColor; // 1番外の花火以外の色
}
for (let j=0; j<this.circles[i].length; j++) {
this.circles[i][j].graphics
.beginFill(this.color)
.drawCircle(0, 0, this.size);
let angle = j * (360 /this.countCircle);
let radian = angle * Math.PI / 180;
let endX = this.radius * Math.cos(radian);
let endY = this.radius * Math.sin(radian);
if (i === 0) {
// 1番外側の周
createjs.Tween.get(this.circles[i][j])
.to({x: endX, y: endY, scaleX: 1.5, scaleY: 1.5 }, 1000, createjs.Ease.circOut)
.wait(100)
.to({alpha: 0}, 300)
.call(this.changeExplodedFlag);
} else {
createjs.Tween.get(this.circles[i][j])
.wait(100)
.to({x: endX, y: endY, alpha:0}, 1000, createjs.Ease.circOut);
}
}
this.radius -= this.radius / 4;
}
}
// 爆発済フラグを立てる
changeExplodedFlag() {
this.isExploded = true;
}
}
中心円というのは打ちあがっていく円で、一定の高さまで上がると起爆するアニメーションを設定しています。今回はscaleXとscaleYに100を設定しているので、結構な勢いで爆発しますね。
外側円は起爆した際に外側に向かって移動する円のことで、1周あたり12個×4週分を描画します。一番外側の円は毎回
ランダムなカラーを設定するようにします。createjs.Graphics.getHSL関数を使うと、色相・彩度・明度それぞれを指定することが可能です。
Math.sinとMath.cos関数で、それぞれの円の移動先(X座標とY座標)を計算し、アニメーションで設定します。一番最後まで残る外側の周の円には、アニメーションの最後にchangeExplodedFlag関数を呼び出すように設定、爆発済みフラグを立ててもらいます。
画面更新で呼び出される処理
// 毎フレーム毎の処理
function render() {
stage.update();
// 花火を作成
createFireworks();
for (let i=0; i<fireworksArray.length; i++) {
// 打ち上げる
launch(fireworksArray[i]);
// 爆発済であれば配列・画面から削除
if (fireworksArray[i].isExploded) {
stage.removeChild(fireworksArray[i]);
fireworksArray.splice(i, 1);
}
}
}
// 花火インスタンスを生成する処理
function createFireworks() {
frame++;
if (frame % 50 === 0) {
// 50フレームごとに一回生成
fireworks = new Fireworks();
stage.addChild(fireworks);
fireworksArray.push(fireworks);
}
}
// 花火を打ち上げる処理
function launch(fireworks) {
fireworks.moveUp();
if (fireworks.ySpeed > -2) {
if (fireworks.life > 0) {
fireworks.explode();
fireworks.life--;
}
}
}
花火は50フレームごとに1つ作成され、用意しておいたfireworksArray配列に追加されます。そしてその配列の数だけ、打ち上げの処理が実行されます。
花火は上に向かって移動するため、Y座標は常にマイナス方向へ移動します。そこへ重力としてySpeedに0.05ずつ加算されていき、ySpeedが-2よりも大きくなれば爆発します。
その後爆発済フラグが立ち、Stage及びfireworksArray配列から取り除いておきます。