忍者ブログ

JavaScriptゲーム

JavaScriptでゲームを作る!

移動しながらフェードイン/アウトする方法

前回までに画像を移動する方法とフェードイン、フェードアウトする方法を記したので、今度はそれらを合わせて、移動しながらフェードイン、フェードアウトする方法を記します。

01 ファイル構成

ファイルとそれを入れるフォルダーを以下のような構成します。
前回の「フェードイン/アウトする方法」と同じです。

02 ファイル全体の流れ

今回のコードは前回の「フェードイン/アウトする方法」に、移動しながらフェードインする処理と、移動しながらフェードアウトする処理を追加しています。

03 各部分の詳細解説

変更を行った「sample.js」と「sprite.js」のファイルについて解説していきます。

a. 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 };
  • 状態フラグ:スプライトが「移動中」か「フェード中」かを判定します。
    • 0:処理完了
    • 1:移動中(移動フラグ)
    • 2:フェード中(フェードフラグ)
    • 3:移動中+フェード中
  • 移動処理が完了すると状態フラグの移動フラグ(1)をオフにします。
  • 透明度変更処理が完了すると状態フラグのフェードフラグ(2)をオフにします。
  • 移動処理と透明度変更処理の両方が完了すると状態フラグは処理完了(0)になります。

b. sample.js - サンプルクラス

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);

このコードの全体の流れ

  1. 画像を読み込む:
    • 必要な画像を事前にメモリへロードします(画像管理クラスを使用)。
    • ロード後に各画像をスプライトクラスとして設定し、描画を準備します。
  2. アニメーションの管理:
    • ループを使って、画面内の描画やアニメーション(フェードイン/アウト)を繰り返します。
  3. 移動しながらフェード処理を実行:
    • 人物 や モンスター 画像を移動しながらフェードインまたはフェードアウトさせ、アニメーションを構成します。
  4. 一時停止(待機):
    • 指定時間だけ処理を止めることで、画面切り替えのタイミングを調整します。

主要なポイントの解説

  1. 移動しながらフェードイン処理:
    人物01.移動してフェードインする(-10, 0, 人物01正位置.x, 0, 1);
    
    • 移動速度:上記の例では、1フレームごとに10ピクセル左に移動します。
    • フェードイン:上記の例では、1秒以内に透明度が上がり(0⇒1)、完全に表示されます。
  2. 移動しながらフェードアウト処理:
    人物01.移動してフェードアウトする(5, 0, 640, 0, 0.5);
    
    • 移動速度:上記の例では、1フレームごとに5ピクセル右に移動します。
    • フェードイン:上記の例では、0.5秒以内に透明度が下がり(1⇒0)、完全に非表示になります。

拍手[0回]

PR

コメント

P R

プロフィール

HN:
No Name Ninja
性別:
非公開