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

More than 1 year has passed since last update.

さあ、はじめよう

はじめに

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

想定する読者

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

本連載の狙い

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

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

オンラインサンプル

本連載は全10回を予定しています。
各回にはその時点までの[ シューティングゲーム的な何か ]のサンプルが付属します。
各テキストからリンクを張っておきますので、オンラインで実際に動作確認が行えます。
サンプルプログラム一式については著作権とかライセンスとかそういったものは一切ありません。

ちなみに、最終的に完成する[ シューティングゲーム的な何か ]はこんな感じです。
マウスによる移動、クリックによるショットが可能です。ESC キーでプログラムを停止します。

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

書いてる人

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

さて、つくろう

サンプルの実行結果
sample_003.jpg

前回は簡単にですが canvas2d コンテキストの使い方について解説しました。
第一回ではマウスカーソルの座標を検知するだけでしたが、第二回でマウスカーソルの座標に円を描くことができるようになりました。第三回目となる今回は、内容的には非常に軽いです。楽な気持ちで見てみてください。

クラスっぽい実装

今回はシューティングゲームにおける自機を管理するためのクラスを考えてみます。

そもそも本連載では基本的なスタンスとして、自機キャラクターや敵キャラクターに対して固有の動作を持たせたい場合にクラスっぽい感じで実装します。どうしてそのようなコードの書き方をするのかと言えば、ゲーム特有の 一度にいろんなことをしないといけない という状況に対応するためです。

javascript を使って動的なページを作るという場合でも、大抵はユーザーの何かしらのアクションをトリガにして処理を行うとか、数十秒から数分に一度 Ajax で通信するとか、そういったものが多いと思います。

しかしゲームのプログラムというのは、基本的には常に動き続けます。それも一秒間に数十回という非常に速いスパンで回り続けます。さらに言うと、その一回の処理のなかでは自機に関すること、敵に関すること、背景やスコアの更新に関することなど、一度に大量のタスクを捌いていかないといけません。

これらを一つのプログラム(関数)に無理矢理突っ込んでしまうと非常に見通しの悪いコードが出来上がります。まあこれはゲームに限ったことではないのですが、個人的にはゲームのプログラムというのはこの傾向がかなり顕著なように思います。

そこでクラスっぽい実装を用いて、できる限り処理を分離して個別に管理・把握できるようにするわけですね。

自機キャラクター管理クラス

今回のサンプルでは、キャラクターに関するクラスの処理を記述するために新しくファイルを追加します。

index.html
<!DOCTYPE html>
<html>
    <head>
        <script src="common.js"></script>
        <script src="character.js"></script>
        <script src="main.js"></script>

        <style>canvas {border: 1px solid gray;}</style>
    </head>
    <body>
        <canvas id="screen"></canvas>
        <p id="info"></p>
    </body>
</html>

以前の index.html とどこが違っているのかわかるでしょうか。
読み込まれる javascript のファイルがひとつ増えています。character.js ですね。このファイルに自機に関するクラスを書いていきます。

それでは character.js の中身はどうなっているのでしょうか。

character.js
function Character(){
    this.position = new Point();
    this.size = 0;
}

Character.prototype.init = function(size){
    this.size = size;
};

今のところ、まだまだ簡素ですね。
コンストラクタではPointクラスを元に、自機の位置を管理するためのプロパティを用意していますね。それと、自機のサイズをコンストラクタではまず 0 に初期化します。
自機のサイズの指定はinitというメソッドで行います。引数としてサイズを受け取り、その段階でサイズが設定されるようになっているのですね。

メインプログラムを修正

さて、それではこの新しいクラスを実際に利用したプログラムを見てみます。

main.js でonloadから呼ばれる関数の中身を抜粋します。

main.jsから抜粋
window.onload = function(){

    // スクリーンの初期化
    screenCanvas = document.getElementById('screen');
    screenCanvas.width = 256;
    screenCanvas.height = 256;

    // 2dコンテキスト
    ctx = screenCanvas.getContext('2d');

    // イベントの登録
    screenCanvas.addEventListener('mousemove', mouseMove, true);
    window.addEventListener('keydown', keyDown, true);

    // その他のエレメント関連
    info = document.getElementById('info');

    // 自機初期化
    var chara = new Character();
    chara.init(10);

    // レンダリング処理を呼び出す
    (function(){
        // HTMLを更新
        info.innerHTML = mouse.x + ' : ' + mouse.y;

        // screenクリア
        ctx.clearRect(0, 0, screenCanvas.width, screenCanvas.height);

        // パスの設定を開始
        ctx.beginPath();

        // 自機の位置を設定
        chara.position.x = mouse.x;
        chara.position.y = mouse.y;

        // 自機を描くパスを設定
        ctx.arc(chara.position.x, chara.position.y, chara.size, 0, Math.PI * 2, false);

        // 自機の色を設定する
        ctx.fillStyle = CHARA_COLOR;

        // 自機を描く
        ctx.fill();

        // フラグにより再帰呼び出し
        if(run){setTimeout(arguments.callee, fps);}
    })();
};

前回から追加されている箇所はそれほど多くありません。
無名関数が呼ばれる場所の直前、自機キャラクターを初期化している部分があります。

自機キャラクターの初期化
    var chara = new Character();
    chara.init(10);

これを見るとinitメソッドによって自機キャラクターのサイズを 10 に設定しているのがわかると思います。

無名関数のなかでは、前回はマウスカーソルの座標情報をそのまま使っていましたが、今回は一度自機クラスを経由する形になっているのがわかるでしょう。

特に、canvas2d コンテキストで円形のパスを指定する部分は、自機クラスのプロパティを使って書かれているのがわかるはずです。

自機クラスを利用した設計
// 自機の位置を設定
chara.position.x = mouse.x;
chara.position.y = mouse.y;

// 自機を描くパスを設定
ctx.arc(chara.position.x, chara.position.y, chara.size, 0, Math.PI * 2, false);

// 自機の色を設定する
ctx.fillStyle = CHARA_COLOR;

また本連載では通例にならって、定数として扱う変数には大文字を使うようにします。
自機の色を設定しているCHARA_COLORは、グローバル変数群を初期化しているところで一緒に設定を行うようになっています。

定数の初期化部分抜粋
var CHARA_COLOR = 'rgba(0, 0, 255, 0.75)';

登場するキャラクターの種類が多い場合は、こういった色の情報もキャラクターを管理するクラスにプロパティとして含めてしまったほうが都合がいい場合が多いです。しかしあえて定数として扱ったほうがいい場合もあります。これはおいおい解説します。

まとめ

実は今回のサンプルは、中身こそ変わっていますが動作させた場合の見た目は前回とまったく同じです。
直接マウスカーソルから拾った座標上に円を描いているか、自機キャラクターのクラスを経由して描いているか、という違いしかありません。

今後は、このようにクラスを設けて、そのクラスを拡張するという形でゲームが完成に近づいていきます。
あまり無駄にファイルを増やすことはしないつもりですが、肝心なのは、プログラムを記述している本人が理解しやすくわかりやすい構造になっていることです。

またよく言われることではありますが、他人が見てもわかりやすいようにコードを記述することに努めましょう。
これは、自分自身が時間を経てプログラムに触れたときに、他人同然の目線でプログラムに相対することになるからです。

オンラインサンプル.03で実際に今回のサンプルの動作を確認できます。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.