JavaScriptでゲームを作る!
前回までに画像を移動する方法とフェードイン、フェードアウトする方法を記したので、今度はそれらを合わせて、移動しながらフェードイン、フェードアウトする方法を記します。
ファイルとそれを入れるフォルダーを以下のような構成します。
前回の「フェードイン/アウトする方法」と同じです。
今回のコードは前回の「フェードイン/アウトする方法」に、移動しながらフェードインする処理と、移動しながらフェードアウトする処理を追加しています。
変更を行った「sample.js」と「sprite.js」のファイルについて解説していきます。
export class スプライトクラス {
constructor(レイヤー, 画像要素, x1, y1, x2, y2) {
this.レイヤー = レイヤー;
this.ctx = レイヤー.ctx;
this.画像要素 = 画像要素;
this.座標を設定する(x1, y1, x2, y2);
}
座標を設定する(x1, y1, x2, y2) {
const 画像要素 = this.画像要素;
const 表示幅 = x2 - x1;
const 表示高さ = y2 - y1;
const 画像縦横比 = 画像要素.width / 画像要素.height;
const 表示縦横比 = 表示幅 / 表示高さ;
let 描画幅, 描画高さ, 位置調整x, 位置調整y;
if (画像縦横比 > 表示縦横比) {
描画幅 = 表示幅;
描画高さ = 表示幅 / 画像縦横比;
位置調整x = 0;
位置調整y = (表示高さ - 描画高さ) / 2; // 垂直中央揃え
} else {
描画高さ = 表示高さ;
描画幅 = 表示高さ * 画像縦横比;
位置調整x = (表示幅 - 描画幅) / 2; // 水平中央揃え
位置調整y = 0;
}
this.x = x1 + 位置調整x;
this.y = y1 + 位置調整y;
this.幅 = 描画幅;
this.高さ = 描画高さ;
}
描画する() {
this.ctx.drawImage(this.画像要素, this.x, this.y, this.幅, this.高さ); // 画像を描画
}
移動して描画する(移動幅x, 移動幅y, 停止位置x, 停止位置y) {
const 制御情報 = { 状態フラグ: 1 }; // 状態フラグ = 1(移動フラグオン)
return new Promise(resolve => {
this.レイヤー.処理を追加する(this, () => {
this.移動する(制御情報, 移動幅x, 移動幅y, 停止位置x, 停止位置y);
this.描画する();
if (制御情報.状態フラグ) return;
// 停止位置に来たら描画だけの処理に切替える
this.レイヤー.処理を追加する(this, this.描画する.bind(this));
resolve();
});
})
}
フェードインする(フェード時間 = 1) {
const 増減値 = 1 / (60 * フェード時間); // フェード時間は秒単位とする
const 制御情報 = { 状態フラグ: 2, 不透明度: 0.0 }; // 状態フラグ = 2(フェードフラグオン)
return new Promise(resolve => {
this.レイヤー.処理を追加する(this, () => {
this.描画してフェードする(制御情報, 増減値);
if (制御情報.状態フラグ) return;
// 不透明度が100%になったら描画だけの処理に切替える
this.レイヤー.処理を追加する(this, this.描画する.bind(this));
resolve();
});
})
}
フェードアウトする(フェード時間 = 1) {
const 増減値 = -1 / (60 * フェード時間); // フェード時間は秒単位とする
const 制御情報 = { 状態フラグ: 2, 不透明度: 1.0 }; // 状態フラグ = 2(フェードフラグオン)
return new Promise(resolve => {
this.レイヤー.処理を追加する(this, () => {
this.描画してフェードする(制御情報, 増減値);
if (制御情報.状態フラグ) return;
// 不透明度が0%になったら描画処理を削除する
this.レイヤー.処理を削除する(this);
resolve();
});
})
}
移動してフェードインする(移動幅x, 移動幅y, 停止位置x, 停止位置y, フェード時間 = 1) {
const 増減値 = 1 / (60 * フェード時間); // フェード時間は秒単位とする
const 制御情報 = { 状態フラグ: 3, 不透明度: 0.0 }; // 状態フラグ = 1(移動フラグオン) + 2(フェードフラグオン)
return new Promise(resolve => {
this.レイヤー.処理を追加する(this, () => {
this.移動する(制御情報, 移動幅x, 移動幅y, 停止位置x, 停止位置y);
this.描画してフェードする(制御情報, 増減値);
if (制御情報.状態フラグ) return;
// 停止位置に来て、尚かつ不透明度が100%なったら描画だけの処理に切替える
this.レイヤー.処理を追加する(this, this.描画する.bind(this));
resolve();
});
})
}
移動してフェードアウトする(移動幅x, 移動幅y, 停止位置x, 停止位置y, フェード時間 = 1) {
const 増減値 = -1 / (60 * フェード時間); // フェード時間は秒単位とする
const 制御情報 = { 状態フラグ: 3, 不透明度: 1.0 }; // 状態フラグ = 1(移動フラグオン) + 2(フェードフラグオン)
return new Promise(resolve => {
this.レイヤー.処理を追加する(this, () => {
this.移動する(制御情報, 移動幅x, 移動幅y, 停止位置x, 停止位置y);
this.描画してフェードする(制御情報, 増減値);
if (制御情報.状態フラグ) return;
// 停止位置に来て、尚かつ不透明度が0%なったら描画処理を削除する
this.レイヤー.処理を削除する(this);
resolve();
});
})
}
移動する(制御情報, 移動幅x, 移動幅y, 停止位置x, 停止位置y) {
if ((制御情報.状態フラグ & 1) ^ 1) return; // 移動フラグがオフなら移動しない
if (移動幅x) {
this.x += 移動幅x;
if (移動幅x > 0) {
if (this.x >= 停止位置x) 制御情報.状態フラグ &= ~1; // 移動フラグをオフにする
} else {
if (this.x <= 停止位置x) 制御情報.状態フラグ &= ~1; // 移動フラグをオフにする
}
}
if (移動幅y) {
this.y += 移動幅y;
if (移動幅y > 0) {
if (this.y >= 停止位置y) 制御情報.状態フラグ &= ~1; // 移動フラグをオフにする
} else {
if (this.y <= 停止位置y) 制御情報.状態フラグ &= ~1; // 移動フラグをオフにする
}
}
}
描画してフェードする(制御情報, 増減値) {
this.ctx.save(); // 現在の描画状態を保存
this.ctx.globalAlpha = 制御情報.不透明度;
this.描画する();
this.ctx.restore(); // 元の描画状態を復元
if ((制御情報.状態フラグ & 2) ^ 2) return; // フェードフラグがオフならフェードしない
制御情報.不透明度 += 増減値;
if (制御情報.不透明度 > 0 && 制御情報.不透明度 < 1) return;
// 不透明度が下限値または上限値を超えたら、フェードフラグをオフにする
制御情報.状態フラグ &= ~2;
// 不透明度が下限値と上限値を超えないようにする
制御情報.不透明度 = Math.max(0, Math.min(1, 制御情報.不透明度));
}
}
移動してフェードインする(移動幅x, 移動幅y, 停止位置x, 停止位置y, フェード時間 = 1) {
const 増減値 = 1 / (60 * フェード時間); // フェード時間は秒単位とする
const 制御情報 = { 状態フラグ: 3, 不透明度: 0.0 }; // 状態フラグ = 1(移動フラグオン) + 2(フェードフラグオン)
return new Promise(resolve => {
this.レイヤー.処理を追加する(this, () => {
this.移動する(制御情報, 移動幅x, 移動幅y, 停止位置x, 停止位置y);
this.描画してフェードする(制御情報, 増減値);
if (制御情報.状態フラグ) return;
// 停止位置に来て、尚かつ不透明度が100%なったら描画だけの処理に切替える
this.レイヤー.処理を追加する(this, this.描画する.bind(this));
resolve();
});
})
}
移動してフェードアウトする(移動幅x, 移動幅y, 停止位置x, 停止位置y, フェード時間 = 1) {
const 増減値 = -1 / (60 * フェード時間); // フェード時間は秒単位とする
const 制御情報 = { 状態フラグ: 3, 不透明度: 1.0 }; // 状態フラグ = 1(移動フラグオン) + 2(フェードフラグオン)
return new Promise(resolve => {
this.レイヤー.処理を追加する(this, () => {
this.移動する(制御情報, 移動幅x, 移動幅y, 停止位置x, 停止位置y);
this.描画してフェードする(制御情報, 増減値);
if (制御情報.状態フラグ) return;
// 停止位置に来て、尚かつ不透明度が0%なったら描画処理を削除する
this.レイヤー.処理を削除する(this);
resolve();
});
})
}
このコードでは「状態フラグ」を使ってスプライトの状態を管理しています。
const 制御情報 = { 状態フラグ: 3, 不透明度: 0.0 };
import { 画像管理クラス } from './imageManager.js'; // クラスをインポート
import { スプライトクラス } from './sprite.js'; // クラスをインポート
import { レイヤークラス } from './layer.js'; // クラスをインポート
import { ループ } from './loop.js'; // クラスをインポート
class サンプル {
static async main() {
const レイヤー = new レイヤークラス('canvas.レイヤー1');
const 画像管理 = new 画像管理クラス();
ループ.初期化する(1000/60);
ループ.開始する();
// 事前に読み込む画像の配列
const 画像情報リスト = [
{ id: '草原', src: 'img/bg01.jpg' },
{ id: '人物01', src: 'img/chara01.png' },
{ id: '炎竜', src: 'img/mon01.png' },
{ id: 'デビル', src: 'img/mon02.png' }
];
// ページ初期化時に画像をプリロード
if (!await 画像管理.画像を読み込む(画像情報リスト)) return;
// 画像の描画位置を設定
const 背景01 = new スプライトクラス(レイヤー, 画像管理.取得する('草原'), 0, 0, 640, 640);
const 人物01 = new スプライトクラス(レイヤー, 画像管理.取得する('人物01'), 0, 100, 640, 640);
const 人物01正位置 = { x: 人物01.x, y: 人物01.y };
人物01.座標を設定する(640, 100, 1280, 640); //初期表示位置を設定する
const モンスター01 = new スプライトクラス(レイヤー, 画像管理.取得する('炎竜'), 0, 0, 640, 540);
const モンスター01正位置 = { x: モンスター01.x, y: モンスター01.y };
モンスター01.座標を設定する(-640, 0, 0, 540); //初期表示位置を設定する
const モンスター02 = new スプライトクラス(レイヤー, 画像管理.取得する('デビル'), 0, 320, 320, 640);
const モンスター02正位置 = { x: モンスター02.x, y: モンスター02.y };
モンスター02.座標を設定する(640, -640, 960, 320); //初期表示位置を設定する
const モンスター03 = new スプライトクラス(レイヤー, 画像管理.取得する('デビル'), 320, 320, 640, 640);
ループ.処理を追加する(レイヤー, レイヤー.描画する.bind(レイヤー));
背景01.レイヤー.処理を追加する(背景01, 背景01.描画する.bind(背景01));
let プロミスリスト;
while (1) {
await 人物01.移動してフェードインする(-10, 0, 人物01正位置.x, 0, 1);
await サンプル.一時停止(2000);
プロミスリスト = [];
プロミスリスト.push(人物01.移動してフェードアウトする(5, 0, 640, 0, 0.5));
プロミスリスト.push(モンスター01.移動して描画する(5, 0, モンスター01正位置.x, 0));
プロミスリスト.push(モンスター02.移動してフェードインする(-10, 10, モンスター02正位置.x, モンスター02正位置.y, 1.5));
プロミスリスト.push(モンスター03.フェードインする(0.5));
await Promise.all(プロミスリスト);
await サンプル.一時停止(2000);
プロミスリスト = [];
プロミスリスト.push(モンスター01.移動して描画する(0, 10, 0, 640));
プロミスリスト.push(モンスター02.移動してフェードアウトする(0, 5, 0, 640,0.5));
プロミスリスト.push(モンスター03.フェードアウトする(3));
await Promise.all(プロミスリスト);
await サンプル.一時停止(2000);
モンスター01.座標を設定する(-640, 0, 0, 540); //初期表示位置を設定する
モンスター02.座標を設定する(640, -640, 960, 320); //初期表示位置を設定する
}
}
static 一時停止(時間) {
return new Promise(resolve => setTimeout(resolve, 時間));
}
}
addEventListener('load', サンプル.main);
人物01.移動してフェードインする(-10, 0, 人物01正位置.x, 0, 1);
人物01.移動してフェードアウトする(5, 0, 640, 0, 0.5);