JavaScript

[連載] javascriptで作るシューティングゲーム的な何か(最終回)

More than 1 year has passed since last update.

さあ、はじめよう

はじめに

本稿は掲題の通り javascript を用いて[ シューティングゲーム的な何か ]を作ろうという試みについて解説するテキストの第十回です。

想定する読者

  • 割と暇である
  • プログラミングに興味がある
  • ゲーム作りに興味がある
  • javascriptの基本をマスターしたけど特に作るものがない
  • javascriptを使った動きのある処理を実装してみたい
  • canvas でなんか作ってみたい

本連載の狙い

本連載はどちらかというと初心者向けです。
このページに検索からやってきた「ゲーム作りてえええええ」と日に三十回くらい叫んでいる小中学生諸君は、まずjavascriptの基本をお勉強してから本連載を読みましょう。
また、最終的に出来上がる[ シューティングゲーム的な何か ]は、そんなに大層なものではありません。シューティングゲームがどのような感じで作られていくのか、その過程を眺めていろいろ考えていただくキッカケを作ることが本連載の狙いです。

また、本連載では伝わりやすさ優先でテキストを書きます。たとえば javascript には厳密にはクラスはありませんが、連載内でクラスという言葉を使って解説します。このあたりは理解しやすさや、雰囲気を伝えることを優先して書きます。

javascriptで作るシューティングゲーム的な何か(1)
javascriptで作るシューティングゲーム的な何か(2)
javascriptで作るシューティングゲーム的な何か(3)
javascriptで作るシューティングゲーム的な何か(4)
javascriptで作るシューティングゲーム的な何か(5)
javascriptで作るシューティングゲーム的な何か(6)
javascriptで作るシューティングゲーム的な何か(7)
javascriptで作るシューティングゲーム的な何か(8)
javascriptで作るシューティングゲーム的な何か(9)
javascriptで作るシューティングゲーム的な何か(最終回)

書いてる人

書いてる人はdoxasという人です。
こんな企画もやってますので、少しでも javascript でシューティングゲームを作成することに興味がわいたら、ぜひ参加してください。待ってますよ!!

さて、つくろう

前回までで、当初予定していた[ シューティングゲーム的な何か ]は、一応完成しました。

今回は、シューティングゲームを作るのに参考になりそうな tips をいくつか解説しつつ、さらに改良したシューティングのサンプルを最後に紹介したいと思います。

処理の高速化

前回までの連載を順番に読んできた方ならイメージできると思いますが、シューティングゲームでは実にたくさんの処理を同時に行う必要がありました。

特に、配列で扱うことになる大量の敵キャラクターや、撃ちだしたショットなどの管理はfor文によるループ処理を大量に利用することになりますし、当然のことながら同時に出現できる敵の数やショットの数を増やせば増やすほど、負荷はどんどん高くなっていきます。

シューティングゲームに限ったことではありませんが、プログラムはできる限りスムーズに、そして高速に動作することに越したことはありません。高速化について、よく知られているいくつかの方法を紹介します。

演算子による違い

シューティングゲームなどでよく使われている小技として、掛け算の変わりにビットシフトを利用する方法があります。

ビットシフトについてはここでは詳しく解説はしませんので、各自調べてみましょう。
簡単に言うと、2 の倍数を掛ける処理に対して利用できるテクニックで、通常の四則演算の掛け算を用いるよりも高速です。

javascript では他の言語と比較してそれほど恩恵がないような気もしますが、実際に計測してみました。

ビットシフトのテスト
    var i, j;
    j = 1;

    // 1億回ループ
    for(i = 0; i < 100000000; i++){
        j << 1; // これがビットシフト
        j *= 2; // こちらは通常の掛け算
    }

    alert(new Date().getTime() - t);
};

実際に動作させるときには、ループのなかの二行のうちの一方をコメントアウトして計測します。

実行環境によって当然結果はまちまちになるかと思いますが、こちらの環境でやってみたところ、通常の四則演算では平均して 140 ミリ秒掛かった処理が、ビットシフトなら 90 ミリ秒程度しか掛かりませんでした。処理時間としては約三分の二程度にまで高速化していることになりますね。

このように、ビットシフトは高速化に非常に効果的ですが注意すべき点もあります。

ビットシフトを行う場合には、値が強制的に整数(32ビット整数)に変換されますので、たとえば小数点以下の数値を含むような可能性のある処理では、安易に使うと思わぬ結果になってしまう可能性があります。この点には十分に注意しましょう。

演算子周りで言うと、有名なところでもうひとつ、高速化につながるものがあります。一般によく言われることですが掛け算や割り算というのは、足し算や引き算と比較して余分に時間が掛かります。
ケースバイケースになりますし、恩恵的にもそれほど大きなわけではありませんが、たとえば以下のようなコードでは足し算を使った場合のほうが若干高速です。

足し算と引き算
(1) j *= 2; // 1億回ループで約 140 ミリ秒
(2) j += j; // 1億回ループで約 110 ミリ秒

ただ単に二倍にするだけの処理などでは、上記のようにちょっとした記述方法の変化で高速化できる場合があるわけです。

事前に計算しておく

この方法も、ゲームでは割とよく使われる考え方です。
今回のサンプルゲームで言うと、ラジアンやサイン、コサインなどを利用している場面で利用できるでしょう。

たとえば前回実装した、ボスの周りを周回するビットの動きを計算している以下のメソッドを見てみます。

ボスビットの動きを管理するメソッド
Bit.prototype.move = function(){
    var i, x, y;

    // パラメータをインクリメント
    this.param++;

    // パラメータからラジアンを求める
    i = (this.param % 360) * Math.PI / 180;

    // ラジアンから横移動量を算出
    x = Math.cos(i) * (this.parent.size + this.size);
    y = Math.sin(i) * (this.parent.size + this.size);
    this.position.x = this.parent.position.x + x;
    this.position.y = this.parent.position.y + y;
};

これを見ると、このメソッドが呼び出されるたびにラジアンを計算して、さらにそのラジアンを使ってサインやコサインを計算しているのがわかります。

これがもし仮に、あらかじめルックアップテーブルのように配列に格納されていたらどうなるでしょうか。

配列を使った場合の例
var rad = new Array();
var sin = new Array();
var cos = new Array();
for(i = 0; i < 360; i++){
    rad[i] = i * Math.PI / 180;
    sin[i] = Math.sin(rad[i]);
    cos[i] = Math.cos(rad[i]);
}

上記のように事前に全ての計算結果を配列に格納しておくようにすれば、処理を行うたびに同じ計算結果を無駄に何度も算出する必要はなくなります。配列の要素を単純に読み出すだけなので、当然動作も高速です。

ただし、この方法はメモリを大量に使用することになる場合があり、一概にいいことばかりでもないのでその点には十分に注意を払いましょう。また、今回紹介したようにサインやコサインを利用する場合でも、上記のようにしてしまうと 0 ~ 359 度(度数法)を整数でしか扱えません。精度が整数による度数指定で事足りる場合は問題ありませんが、あまり精度の高い処理ができないというデメリットもありますので注意しましょう。

スコープ関連

本連載では、わかりやすさ重視という意味でグローバルスコープの変数を使ってきました。
しかし一般に、グローバルな変数は使わないようにしたほうがいいと言われています。これには理由が 本当にたくさんあります が、高速化という面で考えてもグローバルな変数は使わないようにしたほうがいい場合があります。

代表的なところでは DOM へのアクセスがあります。どうしても実装上必要な場合でも、以下のようにして一度ローカル変数に代入してやると処理が高速化される場合があります。

ローカル変数に代入
function test(){
    var w = window;
    var d = w.document;
    var b = d.body;

    // 以下コードを記述
}

これと同じような理屈で、自前でクラスを定義する場合にも、多段継承を行っているケースなどではドット演算子で地道に指定していくよりも、一度ローカル変数に必要なところでキャッシュするようにしたほうが高速化が見込めるでしょう。

その他、javascript 全般に活用できそうな高速化の tips を解説しているサイトを紹介しておきます。

まとめ

さて高速化などについて見てきましたが、参考になりそうなものは見つかったでしょうか。
一通りゲームのプログラムが組めるようになったら、いずれは今回紹介したようなテクニックを使いながらさらにレベルの高いゲーム作成にチャレンジしてみてください。

シューティングゲームは、個人的にはゲームプログラミングを始めるのには最適なテーマだと思っています。数学的な知識もそこそこ必要ですし、ゲーム全体の設計としても必要なものはほぼそろっています。

前回の、一応完成したシューティングゲーム。少しだけ、改造したものを最後に公開しておきます。従来のようにテキストに起こすことはしませんが、サンプルのコード内にはコメントは入れてあります。ここまでの連載を通しで読んできた方であれば、サンプルコードとコメントだけで把握できると思います。

敵のタイプが 4 種類に増えている点や、ボス本体がショットを撃つようになっている点などが変わっています。
オンラインサンプル.10には、改造したシューティングゲームのサンプルがありますので、気になる方はやってみてください。

全10回の長い連載になりました。

最後までお読みいただき、ありがとうございした。

※おまけを書いたので興味を持っていただけた方だけご覧ください
 ↓ ↓ ↓
 謎技術を用いたおまけテキスト