改造: 効果音を加えてみる
サンプルゲームである 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": {
から対応する },
までの行を追加してください。
{
"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
の「回復アイテムを取得した時の処理」付近を、改めて抜粋するとこのようなコードになっています。
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 文は次のような形をしています。
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 行追加してください。
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;
実行して動作を確認してみましょう。
akashic sandbox
無音のプレイ中、アイテムを取得した時だけ効果音が再生されるややシュールなゲームになっていれば成功です。
解説: 変数定義と関数呼び出し
追加した 2 行は次のようなものでした。
const seItemAsset = g.game.asset.getAudio("/audio/SE_item");
g.game.audio.play(seItemAsset);
一行目の const
は 変数定義 の構文 (の一つ) です。JavaScript では
const 変数名 = 値;
と書くと「変数名
という名前の変数を定義し、その値として 値
を代入する」という意味になります。 つまりここでは、 seItemAsset
という名前の変数を定義しています。
TIP
const
による変数定義は、これまでに登場したある種の代入文とよく似ています。たとえば
Player.MAX_HP = 10;
という記述は、 Player.MAX_HP
という名前で値 10
を参照できるようになるので、おおむね変数定義のようなものでした。 このような代入と const
の厳密な違いはここで解説しきれませんが、次のような点で異なります。
const
は新たに変数を定義する。Player.MAX_HP = ...
は、すでにあるPlayer
に紐づく値 (プロパティ) を設定している。const
で定義された変数は、それが書かれているブロックの中のコードでしか使えない。Player.MAX_HP
はPlayer
にアクセスできる箇所ならどこでも使える。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 行目のこの文は、
const seItemAsset = g.game.asset.getAudio("/audio/SE_item");
g.game.asset.getAudio
(音声アセットを取得する関数) に、- 引数として文字列
"/audio/SE_item"
を与えて呼び出し、 - その結果を、ここで定義した変数
seItemAsset
に代入する
という処理になっています。
2 行目は、単に関数呼び出しをしているだけです。
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.getAudio
と g.game.audio.play
は、Akashic Engine が提供する関数です。 あくまで「Akashic Engine でゲームを作成する時」にしか利用できません。 非常に大雑把に言えば、Akashic Engine は「ゲームの作成に便利な JavaScript の関数詰め合わせ」のようなものです。
Akashic Engine の提供する機能は原則 g.
で始まるようになっています。
TIP
ここで 2 行に分けて書いたのは分かりやすさと解説のためで、変数を介さず 1 行で書いてしまうこともできます。
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 の追記内容です。
{
...
"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"
がなくなっていることに気づかれるかもしれません。 これは音声の長さをミリ秒で指定するものですが、音声ファイルの内容次第であることと、ここでは使っていないので省略しています。
再生は効果音同様、アセットを取得して再生する関数を呼び出すだけです。
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
の方でしょう。
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 増加・回復アイテム強化・効果音追加を行ったコンテンツが以下です。
次は?
サンプルゲーム Galaxy Wars の改造を通して、プログラミングの基本的な概念をいくつか紹介しました。 より本格的にニコ生ゲームを作成するための文書としては、以下も参照してみてください。
- ニコ生ゲームを作ろう » ブロック崩しを作ろう
- 改造ではなくゼロからニコ生ゲームを作るチュートリアルです。
- MDN の JavaScript ガイド (外部サイト)
- ここで扱いきれない JavaScript の各種文法が紹介されています。