Skip to content

改造: 効果音を加えてみる

サンプルゲームである Galaxy Wars には BGM や効果音がありません。 ここでは効果音の追加を通して、JavaScript の「switch 文」と「関数呼び出し」を紹介します。

素材の準備

効果音を鳴らすには音声ファイルが必要です。 次の 2 つのファイルをダウンロード (右クリックして「リンク先を別名で保存」など) してください。

内容はどちらも同じで、次のような音声です。

ここではこれを「アイテムを取得した時の効果音」として鳴らしてみましょう。

TIP

二種類の音声ファイルは 両方 必要です。 これは環境によってサポートされている形式が違うためです。 Akashic Engine は拡張子だけが違う同名の音声ファイルをまとめて扱い、環境に応じて使い分けます。

INFO

この効果音は、サンプルゲーム『ニコニコスネーク』の効果音 SE_start.aac, .ogg を改名したものです。 ライセンス CC BY 2.1 JP に従って自由に利用できます。

Galaxy Wars のフォルダを開き、audio フォルダを作成して、その中にこの二つのファイルを置いてください。 次のようなフォルダ構成になります。

GalaxyWars/
├── audio/
│   ├── SE_item.aac
│   └── SE_item.ogg
├── game.json
├── image/
│   └── (中略)
├── script/
│   └── (中略)
└── text/
    └── (中略)

Akasshic Engine では、もう一つ準備作業が必要です。 ゲームで使う画像や音声ファイル (アセット) の情報を、設定ファイル (game.json) に記載しておく必要があるためです。

GalaxyWars フォルダの game.json を開いて、次のように "SE_item": { から対応する }, までの行を追加してください。

json
{
  "width": 512,
  "height": 384,
  "fps": 30, 
  "title": "Galaxy Wars",
  "description": "画面をドラッグ(スワイプ)すると自機が移動します。自機の弾は自動で発射され、敵を倒すと様々なアイテムを落とします。左上のゲージがゼロになるとゲームオーバーです。\n\nこ>のサンプルで使用している素材は[素材ページ](https://akashic-games.github.io/asset/material.html#GALAXYWARS)で公開・配布しています。",
  "main": "./script/main.js",
  "assets": {
    "SE_item": { 
      "type": "audio", 
      "path": "audio/SE_item", 
      "global": true, 
      "systemId": "sound", 
      "duration": 1286
    }, 
    "main": {
      "type": "script",
      "path": "script/main.js",
      "global": true
    },

TIP: アセットの半自動登録

ここでは紹介のため手動で game.json を編集していますが、通常この作業 (アセットの登録と削除) は akashic scan コマンドで半自動的に行えます。

効果音を再生する箇所

「アイテムを取得した時に効果音を再生する」には、まずアイテムの取得時に実行される箇所を探す必要があります。 今回は、前ページですでに「回復アイテムを取得した時の処理」を見つけているので、その近くを探せばよさそうだと推測できます。

script/Player.js の「回復アイテムを取得した時の処理」付近を、改めて抜粋するとこのようなコードになっています。

js
class Player {
    /**
     * 衝突イベントハンドラ
     */
    onCollision(e) {
        if (e.type === EntityType_1.EntityType.ITEM) {
            e.hp = 0;
            const effectTime = g.game.fps * 10;
            let getter;
            switch (e.itemType) { // -- (A)
                case ItemType_1.ItemType.SHIELD:
                    ...
                    break;
                case ItemType_1.ItemType.HOMING:
                    ...
                    break;
                ... 
                case ItemType_1.ItemType.RECOVER: // -- (B)
                    if (this.hp <= Player.MAX_HP - 2) 
                      this.hp = this.hp + 2;
                    else if (this.hp <= Player.MAX_HP - 1) 
                      this.hp = this.hp + 1; 
                    break;
                default:
            }
            ...
        }
        ...
    }
    ...
}

(B) の行からが「回復アイテム (RECOVER) を取得した時の処理」でした。 この処理は (A)switch (e.itemType) { ... } というブロックに囲まれています。

switch 文

switch のブロック内に case, break が現れるこの記述は、JavaScript の switch 文 と呼ばれる構文です。 switch 文は次のような形をしています。

js
switch (条件値) {
  case 値1:
    処理1;
    break;
  case 値2:
    処理2;
    break;
  ...
  default:
    デフォルト処理;
}

このように書くと、次のような意味になります。

  • もし 条件値値1 と一致するなら、 処理1 を実行する
  • そうでなく、 値2 と一致するなら、 処理2 を実行する
  • (その後の case についても同様)
  • どの case の値とも一致しなければ、 デフォルト処理 を実行する

TIP

  • default: とその後の部分は省くこともできます。その場合、 条件値 がどの case の値とも一致しなければ、何もせずに次に進みます。
  • break; を忘れないようにしてください。break; の有無で動作が変わります。意図的に省くこともありますが、稀なのでここでは気にしないでください。
  • 要は条件分岐なので、switch 文を使わず if-else 文で同じ処理を行うこともできます。 一つの 条件値 で値ごとに分岐したいことはよくあるので、専用の構文が用意されています。

Galaxy Wars の先ほどの switch 文では、 条件値 として e.itemType があり、以下のような case がありました。

  • case ItemType_1.ItemType.SHIELD:
  • case ItemType_1.ItemType.HOMING:
  • case ItemType_1.ItemType.RECOVER:

前後から察しがついていたかもしれませんが、つまりこれは「 e.itemType (e のアイテム種別) によって分岐する」コードです。 e は変数で、代入されている値はわかりませんが、文脈からすると「自機が今ぶつかった相手」でしょう。

さて、ここではアイテムの種類を問わず、何であれ同じ効果音を鳴らしたいのでした。 であれば効果音を再生する箇所は この switch 文の直前か直後 が良さそうだとわかります。 プログラムは原則上から実行されるので、どのアイテムを取得していてもこの switch 文の直前の行や直後の行は実行されるでしょう。

効果音を再生する

効果音を再生するコード自体はごく簡潔です。 script/Player.js の switch 文の直前に、以下のように 2 行追加してください。

js
    onCollision(e) {
        if (e.type === EntityType_1.EntityType.ITEM) {
            e.hp = 0;
            const effectTime = g.game.fps * 10;
            let getter;
            const seItemAsset = g.game.asset.getAudio("/audio/SE_item"); 
            g.game.audio.play(seItemAsset); 
            switch (e.itemType) {
                case ItemType_1.ItemType.SHIELD:
                    ...
                    break;

実行して動作を確認してみましょう。

sh
akashic sandbox

無音のプレイ中、アイテムを取得した時だけ効果音が再生されるややシュールなゲームになっていれば成功です。

解説: 変数定義と関数呼び出し

追加した 2 行は次のようなものでした。

js
const seItemAsset = g.game.asset.getAudio("/audio/SE_item");
g.game.audio.play(seItemAsset);

一行目の const変数定義 の構文 (の一つ) です。JavaScript では

js
const 変数名 = 値;

と書くと「変数名 という名前の変数を定義し、その値として を代入する」という意味になります。 つまりここでは、 seItemAsset という名前の変数を定義しています。

TIP

const による変数定義は、これまでに登場したある種の代入文とよく似ています。たとえば

js
Player.MAX_HP = 10;

という記述は、 Player.MAX_HP という名前で値 10 を参照できるようになるので、おおむね変数定義のようなものでした。 このような代入と const の厳密な違いはここで解説しきれませんが、次のような点で異なります。

  • const は新たに変数を定義する。 Player.MAX_HP = ... は、すでにある Player に紐づく値 (プロパティ) を設定している。
  • const で定義された変数は、それが書かれているブロックの中のコードでしか使えない。 Player.MAX_HPPlayer にアクセスできる箇所ならどこでも使える。
  • const で定義された変数には、後から = で別の値を代入することはできない。 Player.MAX_HP は、他の箇所で代入すれば書き換えることができる (定数として扱っているので実際は書き換えませんが) 。

seItemAsset に代入されている値は g.game.asset.getAudio("/audio/SE_item") です。

この名前のあとに括弧 () が続く表記は、JavaScript において 関数の呼び出し を表します。 関数 とは、処理をまとめたものです。 ここでは g.game.asset.getAudio という関数を呼び出していて、名前からこれは「音声 (オーディオ) アセットを取得する」関数だと推測できます。

関数は 引数 を取ることがあります。 たとえば今回の「音声アセットを取得する」関数には「どの音声を取得したいか」を指定する引数が必要です。 関数呼び出しの () の中に値を書くと、それは引数として関数に渡されます。

TIP

このあたりの言葉は、数学の関数 (e.g. sin(θ), log(x)) と同じです。 ただし JavaScript の関数はあくまでもプログラム=処理のまとまりなので、値を返すだけでなく、画像を表示したり音声を再生したりという "副作用" を伴うことがあります。

ここで引数として与えられているのは "/audio/SE_item" という値です。 "" で括られた文字の並びは、JavaScript において「文字列」を表します。

まとめると 1 行目のこの文は、

js
const seItemAsset = g.game.asset.getAudio("/audio/SE_item");
  • g.game.asset.getAudio (音声アセットを取得する関数) に、
  • 引数として文字列 "/audio/SE_item" を与えて呼び出し、
  • その結果を、ここで定義した変数 seItemAsset に代入する

という処理になっています。

2 行目は、単に関数呼び出しをしているだけです。

js
g.game.audio.play(seItemAsset);

g.game.audio.play は「音声アセットを再生する」関数で、引数として再生する対象を取ります。 1 行目で定義した変数を渡すことで、結果として SE_item.ogg (環境によっては SE_item.aac) にあたる音声アセットを再生する処理になっています。

TIP: JavaScript と Akashic Engine

前ページまでで紹介した「変数」や「if 文」「演算子」「関数呼び出し」などは、全て JavaScript の言語機能です。 それらは JavaScript でプログラムを書く際には常に利用できます。

一方このページで扱った g.game.asset.getAudiog.game.audio.play は、Akashic Engine が提供する関数です。 あくまで「Akashic Engine でゲームを作成する時」にしか利用できません。 非常に大雑把に言えば、Akashic Engine は「ゲームの作成に便利な JavaScript の関数詰め合わせ」のようなものです。

Akashic Engine の提供する機能は原則 g. で始まるようになっています。

TIP

ここで 2 行に分けて書いたのは分かりやすさと解説のためで、変数を介さず 1 行で書いてしまうこともできます。

js
g.game.audio.play(g.game.asset.getAudio("/audio/SE_item"));

BGM の場合

ここまでで効果音を追加することができました。 しかし効果音が一つだけなので、ゲームとしてはややシュールなものになってしまいました。 同じような改造で、他の効果音や BGM を加えることも難しくないので、よければチャレンジしてみてください。

ただし BGM は効果音と少しだけ違います。

BGM の登録

Akashic Engine での BGM と効果音の違いは、主にループ再生されるかどうかです。 BGM は効果音と同じ手順で扱えますが、game.json の登録内容が少し違います。

以下は BGM の音声アセット audio/bgm.ogg, audio/bgm.aac を置いた時の game.json の追記内容です。

json
{
  ...
  "assets": {
    "bgm": { 
      "type": "audio", 
      "path": "audio/bgm", 
      "global": true, 
      "systemId": "music", 
    }, 
    ...
  }
}

"path" の値がファイル名に対応していることと、 "systemId" の値が効果音とは違うことに注意してください。 効果音は "systemId": "sound", でしたが、BGM の場合は "systemId": "music", とする必要があります。

TIP

  • ここでは紹介していませんが、akashic scan コマンドで登録をした場合、音声は常に効果音として登録されます ("systemId""sound") 。 BGM の場合は手動で "music" に変更してください。
  • "systemId" の違いの他に、"duration" がなくなっていることに気づかれるかもしれません。 これは音声の長さをミリ秒で指定するものですが、音声ファイルの内容次第であることと、ここでは使っていないので省略しています。

再生は効果音同様、アセットを取得して再生する関数を呼び出すだけです。

js
const bgmAsset = g.game.asset.getAudio("/audio/bgm");
g.game.audio.play(bgmAsset);

BGM を再生する箇所

たとえばシューティングゲームの「ステージ開始時」に BGM 再生を始めるとします。 これまでの改造と異なり、これは自機とは関係ありません。 再生処理を書くべき場所は、 Player.js の中ではなさそうです。

改めてソースコードのファイル名を眺めてみましょう。

GalaxyWars/
├── script/
│   ├── AlphaEnemy.js
│   ├── Background.js
│   ├── BetaEnemy.js
│   ├── Bullet.js
│   ├── Enemy.js
│   ├── EnemyManager.js
│   ├── EntityType.js
│   ├── GameCore.js
│   ├── GameOverLogo.js
│   ├── GammaEnemy.js
│   ├── Global.js
│   ├── Item.js
│   ├── ItemGaugeTray.js
│   ├── ItemType.js
│   ├── Math.js
│   ├── Particle.js
│   ├── Player.js
│   ├── PlayerStatus.js
│   ├── ScreenEffector.js
│   ├── Shield.js
│   ├── bootScene.js
│   ├── emmitDamageEffect.js
│   ├── gameScene.js
│   ├── main.js
│   └── titleScene.js
└── (略)

「ステージ開始」のようなゲーム進行に関係ありそうなものとしては、 titleScene.js, gameScene.js というファイルが目につきます。 "title scene" がタイトル画面のことだと予想すると、 "game scene" はメインゲームの画面に対応しているかもしれません。 また「シューティングゲームのコア部分」という意味で名付けられていそうな GameCore.js も気になります。

結果から言えば gameScene.js, GameCore.js どちらでも目的は果たせますが、わかりやすいのは GameCore.js の方でしょう。

js
class GameCore {
    ...
    /**
     * ゲーム開始
     */
    start() {
        this.background = new Background_1.Background();
        this.entities.push(this.background);
        this.itemGaugeTray = new ItemGaugeTray_1.ItemGaugeTray();
        this.entities.push(this.itemGaugeTray);
        this.player = new Player_1.Player();
        this.entities.push(this.player);
        this.playerStatus = new PlayerStatus_1.PlayerStatus();
        this.entities.push(this.playerStatus);
        this.enemyManager = new EnemyManager_1.EnemyManager();
        this.entities.push(this.enemyManager);
    }
    ...
}

ずばり「ゲーム開始」とコメントがついている start() が見つかります。 ここに BGM 再生のコードを追加すれば良さそうだと分かります。

改造後のゲームコンテンツ

ここまでの改造、最大 HP 増加・回復アイテム強化・効果音追加を行ったコンテンツが以下です。

Playground で実行

次は?

サンプルゲーム Galaxy Wars の改造を通して、プログラミングの基本的な概念をいくつか紹介しました。 より本格的にニコ生ゲームを作成するための文書としては、以下も参照してみてください。