アフィリエイト広告を利用しています

2024年07月12日

音を鳴らす方法

以下の3パターンで音を鳴らします。
1)黒い領域をクリックした時にBGMを鳴らします。
それと同時に、3つのエフェクトアニメーションを順番に表示します。
2)1つ目と2つ目のエフェクト表示時は効果音をBGMに重ねて鳴らします。
3)3つ目のエフェクト表示時はBGMを中断して効果音を鳴らします。

※エフェクトの画像は「ぴぽや」様(https://pipoya.net/)よりお借りしています。

※BGMは「watson」様(http://musmus.main.jp/)よりお借りしています。

※効果音は「小森 平」様(https://taira-komori.jpn.org/)よりお借りしています。

フォルダー構成

以下の構成でフォルダーを作成し、その下にファイルを置きます。

    sample04
    • sample04.html

    • css
      • sample04.css

    • js
      • Sprite.js ※以前の頁の「画像を表示する方法」で作成したものと同じ

      • Anime.js ※前頁の「アニメーションを表示する方法」で作成したものと同じ

      • Canvas.js ※前頁の「アニメーションを表示する方法」で作成したものと同じ

      • Bgm.js

      • Loop.js ※以前の頁の「メッセージウィンドウの作り方」で作成したものと同じ

      • sample04.js

    • img
    • sound

HTML

sample04.html

<!DOCTYPE HTML>
<html lang='ja'>
<head>
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width" />
    <title>音を鳴らす方法</title>
    <link rel="stylesheet" href="css/sample04.css" type="text/css" />
    <script src="js/Sprite.js"></script>
    <script src="js/Anime.js"></script>
    <script src="js/Canvas.js"></script>
    <script src="js/Bgm.js"></script>
    <script src="js/Loop.js"></script>
    <script src="js/sample04.js"></script>
</head>
<body>
    <div class="sample04">
        <div class="メイン画面">
            <canvas class="レイヤー1" width="640" height="640"></canvas>
        </div>
        <div class="素材">
            <div class="画像">
                <img src="img/pipo-btleffect001.png" alt="0" />
                <img src="img/pipo-btleffect040.png" alt="1" />
                <img src="img/pipo-btleffect111d.png" alt="2" />
            </div>
            <div class="BGM">
                <audio src="sound/MusMus-BGM-078.mp3"/>
            </div>
            <div class="効果音">
                <audio src="sound/swing1.mp3" />
                <audio src="sound/attack3.mp3" />
                <audio src="sound/strange_wave.mp3" />
            </div>
        </div>
    </div>
</body>
</html>

22〜24行目で3つの画像ファイルを読み込んでいます。

27行目でBGMファイルを読み込んでいます。

30〜32行目で3つの効果音ファイルを読み込んでいます。

CSS

Sample04.css

body,div,p,td,th {
    margin: 0;
    padding: 0;
}

.メイン画面 {
    position: relative;
    width: 640px;
    height: 640px;
    background-color: rgba(0, 0, 0);
    margin: 20px auto;
}

canvas {
    position: absolute;
}

.素材 {
    display: none;
}

19行目で、読み込んだimg要素の画像ファイルを非表示にしています。

JavaScript

Bgm.js

class Bgm{
    static 前回鳴動BGM = null;

    static 切替えて鳴らす(bgm, loop = true) {
        if (Bgm.前回鳴動BGM == bgm) {
            Bgm.前回鳴動BGMを鳴らす();
            return;
        }

        // 指定したBGMとは別のBGMが鳴っている場合は
        // そのBGMを停止してから指定したBGMを鳴らす
        Bgm.停止する();
        Bgm.前回鳴動BGM = bgm;
        Bgm.前回鳴動BGM.loop = loop;
        Bgm.前回鳴動BGM.play();
    }

    static 中断して鳴らす(bgm) {
        if (Bgm.前回鳴動BGM == bgm) {
            Bgm.前回鳴動BGMを鳴らす();
            return;
        }

        // 指定したBGMとは別のBGMが鳴っている場合は
        // そのBGMを一時停止してから指定したBGMを鳴らす
        // BGMが終了したら元のBGMを再生する
        return new Promise(resolve => {
            Bgm.停止する();

            bgm.onended = (evt) => {
                evt.target.onended = null;
                Bgm.前回鳴動BGM.play();
                resolve();
                return;
            };

            bgm.play();
        });
    }

    static 重ねて鳴らす(bgm) {
        if (Bgm.前回鳴動BGM == bgm) {
            Bgm.前回鳴動BGMを鳴らす();
            return;
        }

        // 指定したBGMとは別のBGMが鳴っている場合は
        // そのBGMを鳴らしたまま指定したBGMを鳴らす
        return new Promise(resolve => {
            bgm.onended = (evt) => {
                evt.target.onended = null;
                resolve();
                return;
            };

            bgm.play();
        });
    }

    static 停止する() {
        if (Bgm.前回鳴動BGM) {
            if (Bgm.前回鳴動BGM.paused) return;
            Bgm.前回鳴動BGM.pause(); // 一時停止する
            Bgm.前回鳴動BGM.currentTime = 0; // 最初の位置に巻き戻す
        }
    }

    static 前回鳴動BGMを鳴らす() {
        // 前回鳴動BGMが既に鳴っている場合は何もしない
        if (Bgm.前回鳴動BGM.paused == false) return;

        // 鳴っていない場合は鳴らす
        Bgm.前回鳴動BGM.play();
    }
}

sample04.js

class Sample04 {
    static async main() {
        Sample04.画像リスト = document.querySelectorAll('.素材 .画像 img');
        Sample04.BGMリスト = document.querySelectorAll('.素材 .BGM audio');
        Sample04.効果音リスト = document.querySelectorAll('.素材 .効果音 audio');
        Sample04.レイヤー1 = new Canvas(".sample04 .レイヤー1", 1000 / 12);
        Sample04.レイヤー1.クリック時コールバック関数 = Sample04.アニメーションを開始する;
        Sample04.エフェクト画像 = new アニメスプライト(Sample04.レイヤー1);
        ループ.開始する();
        ループ.更新関数を追加する("レイヤー1", Sample04.レイヤー1.更新する.bind(Sample04.レイヤー1));
    }

    static async アニメーションを開始する(evt) {
        Bgm.切替えて鳴らす(Sample04.BGMリスト[0]);

        let ar = [];
        ar.push(Sample04.エフェクト画像.アニメーションを開始する('エフェクト', Sample04.画像リスト[0], 240, 240, 0, 0, 640, 640, true));
        ar.push(Bgm.重ねて鳴らす(Sample04.効果音リスト[0]));
        await Promise.all(ar);

        ar = [];
        ar.push(Sample04.エフェクト画像.アニメーションを開始する('エフェクト', Sample04.画像リスト[1], 240, 240, 0, 0, 640, 640, true));
        ar.push(Bgm.重ねて鳴らす(Sample04.効果音リスト[1]));
        await Promise.all(ar);

        ar = [];
        ar.push(Sample04.エフェクト画像.アニメーションを開始する('エフェクト', Sample04.画像リスト[2], 120, 120, 0, 0, 640, 640, true));
        ar.push(Bgm.中断して鳴らす(Sample04.効果音リスト[2]));
        await Promise.all(ar);
    }
}

addEventListener('load', Sample04.main);

33行目でhtmlとリンク先の画像や音声ファイルが読み込み終わったタイミングで、「Sample04.main」関数をコールするように設定しています。
つまり、本プログラムは「Sample04.main」関数から始まります。

2行目の「Sample04.main」関数について説明します。
この関数は、初期化処理等プログラム内で最初の1回だけ行う処理を実行します。

3行目は、使用するエフェクト画像を指定したimg要素の配列を取得し、それを「Sample04.画像リスト」変数に保持しています。
これにより、以後は「Sample04.画像リスト」変数にインデックス番号を指定するだけで使いたいエフェクト画像のimg要素にアクセスできるようになります。

4行目は、上述のエフェクト画像と同様に、音声ファイルを指定したaudio要素の配列を取得し、「Sample04.BGMリスト」変数に保持しています。
5行目も同様に、「Sample04.効果音リスト」変数に保持しています。

6行目はCanvasクラスのオブジェクトを作成して「Sample04.レイヤー1」変数に保持しています。
「Sample04.レイヤー1」変数のオブジェクトはクラスセレクタが「.レイヤー1」のcanvas要素に対する処理を行います。
このcanvas要素に表示する画像の更新間隔(fps)は1秒間に12回に設定しています。

7行目は「Sample04.レイヤー1」変数のオブジェクトが持つcanvas要素をクリックした時に、「Sample04.アニメーションを開始する」関数をコールするように設定しています。

8行目はアニメスプライトクラスのオブジェクトを「Sample04.エフェクト画像」変数に保持しています。
「Sample04.エフェクト画像」変数のオブジェクトは、エフェクト画像を「Sample04.レイヤー1」変数のオブジェクトのcanvas要素に表示するように設定しています。
但し、この時点ではどんなエフェクト画像を表示するかは指定していません。
エフェクト画像を指定するのは、後の関数でエフェクト画像を表示したいタイミングで行います。

9行目は、ループ処理を開始しています。
アニメーションの処理は画像を更新する関数を繰り返して(ループして)コールする必要があります。
ループ処理はそのための処理です。
但し、この時点ではコールする関数を何も追加していないので何もコールしません。

10行目は、繰り返しコールするための更新関数を追加しています。
これにより「Sample04.レイヤー1.更新する」関数がループ処理から繰り返しコールされるようになります。
「Sample04.レイヤー1.更新する」関数は、「Sample04.レイヤー1」変数のオブジェクトのcanvas要素の表示内容をクリアし、そのオブジェクトの更新関数リストの更新関数をコールします。
但し、この時点では更新関数リストは何もないので、クリアだけが行われます。

13行目の「Sample04.アニメーションを開始する」関数について説明します。
この関数は「Sample04.レイヤー1」変数のオブジェクトのcanvas要素をクリックするとコールされます。

14行目はBGMを鳴らします。
「Sample04.BGMリスト」配列変数の0番目に保持しているaudio要素に指定された音声ファイルを鳴らします。
「Bgm.切り替えて鳴らす」関数は、既に音声ファイルが鳴っている場合は、その音声を停止して、指定された音声ファイルを鳴らします。

17行目は斬撃のアニメーションを表示します。
但し、この時点では関数を実行しません、配列変数のarに関数を追加するだけです。
理由は19行目で配列変数arに追加した複数の関数を同時に実行するためです。

18行目は斬撃の効果音を鳴らします。
但し、この関数もこの時点では実行しません、配列変数のarに関数を追加するだけです。

19行目で配列変数のarに追加している2つの関数を同時に実行します。
これにより、斬撃のアニメーション表示と同時に、斬撃の効果音を鳴らします。
そして、2つの関数がどちらも終了するまで次の処理を行わないように待ちます。

22行目〜24行目は、雷のアニメーション表示と雷の効果音を同時に鳴らします。
そして2つの関数が終わると、次の処理を行います。

27行目〜29行目は、緑の光のアニメーション表示と緑の光の効果音を同時に鳴らします。
但し、効果音はBGMを停止してから鳴らします。
そして2つの関数が終わると、次の処理を行います。
このタイミングで停止していたBGMを再び鳴らします。

この記事へのコメント
コメントを書く

お名前:

メールアドレス:


ホームページアドレス:

コメント:

この記事へのトラックバックURL
https://fanblogs.jp/tb/12601820
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック
カテゴリーアーカイブ