ホーム < ゲームつくろー! < IKD備忘録

HTML5
HTML5で最低限のゲーム作成環境を作ってみる


 IKD備忘録「サーバ」の所でLAMP構成によるゲームサーバを作る為の基礎実験のような事をしました。この記事を書いている段階でそこもまだ不完全ではあるのですが、ゲームプレイヤーが目にするクライアント側も固めておかないとクライアントサーバタイプのゲームの流れが掴めないと感じました。そこで「オンラインゲームクライアントあれこれ」という章でゲームクライアントについて考えを展開しました。その結果ゲームクライアントの選択肢として出てきたのが「さくっと作るならHTML5、こってり作るならUnity」でした。

 HTML5については、今の段階で僕は何も知りません(^-^;。なので「さくっと作るなら」と言ってはいますが、さくっと作れるかどうかもわかっていません。でも、C++で作るよりは便利に作れるのはきっと間違いないだろうと思います。では、具体的にどうやって作るのか?この節で実践していきたいと思います。



@ 潜水艦ゲームを作ってみる事にします

 今回は具体的なゲームを作りながらHTML5に慣れて行こうかなと目論んでおります。で、どんなゲーム作ろうかなぁと思い、小学生の頃に僕らの中で流行っていた「潜水艦ゲーム」にする事にしました。

 潜水艦ゲームは1対1の対戦ゲームです。適当な大きさのマス目(8×8とか)の中に自分の潜水艦を潜ませます。お互いにマスは見せないようにして、交互にマスの一つをコールしていきます。もしそのコールしたマスに相手の潜水艦がいたら撃沈!勝ちとなります。自分も相手もいくつかの武器を持っています。例えばボムを使うと広範囲に攻撃ができます。他にもソナーがあって、それを使うとある範囲について相手の潜水艦などの位置を探る事ができます。運も大きくからむゲームですが、シンプルなルールでとても楽しく遊んだのを覚えています。これを作ってしまおうというわけです。…で、できるのかな(^-^;;;

 サーバとのやり取りを勉強するためのゲームなので3Dバリバリっという事は端から考えて無くて、シンプルに2Dなゲームで十分。なのでUnityは使わずHTML5でと。さて…何をどうすればいいか調べます。



A canvas要素

 HTML5での「Hello World!」を作るのが最初の目的になりそうです。とはいえ、<p>Hello World!</p>という事では無くて、ちゃんとゲームの基盤の上でのHello Worldを目指します。そのためには、HTML5上で2Dを描画する仕組みを調べる必要があります。キーワードはWikipediaにも載っていた「canvas要素」です(^-^)

 Google先生に「HTML5 canvas」で検索するとい〜〜っぱいヒットします。資料沢山って嬉しい(^-^)。色々あるなかで、こちらのサイトを参考にしてみる事にしました:

JSライブラリの併用でIE対応もOK!
HTML5先取り!CanvasならFlash不要で絵が描ける
http://ascii.jp/elem/000/000/418/418912/

 まずHTMLなので、HTMLの骨格部分だけは作ってしまいます。こんな感じです:

OXSubmarine.html
<html>
    <head>
        <meta charset="UTF-8">
        <title>まるぺけ潜水艦ゲーム</title>
    </head>

    <body>
    </body>
</html>

HTML5ではmeta要素はずいぶん簡素にできるそうです。文字コードだけはしっかり定義しておきます(^-^)。

 canvas要素というのはHTML5で新しく追加されたタグで「図形を描画する領域」を定義できます。クイックリファレンスはこちらのサイトが参考になるかな(HTMLクイックリファレンス Canvas:http://www.htmq.com/canvas/index.shtml)。見よう見まねでcanvasタグを打ち込んでみます:

OXSubmarine.html
<html>
    <head>
        <meta charset="UTF-8">
        <title>まるぺけ潜水艦ゲーム</title>
    </head>

    <body>
        <canvas id="view" width=320px height=240px></canvas>
    </body>
</html>

でChromeで見てみると…:

お、いる!何かいます(笑)。続けましょう。

 このcanvasに図形を描くにはHTML内にJavaScriptなスクリプトを記述すると良いようです。読み込み時に早速描くのであれば、例えば、

OXSubmarine.html
<html>
    <head>
        <meta charset="UTF-8">
        <title>まるぺけ潜水艦ゲーム</title>

        <script>
            window.onload = function() {
                var canvas = document.getElementById("view");
                if ( canvas.getContext ) {
                    var context = canvas.getContext("2d");
                    context.fillRect(20, 20, 100, 80);
                }
            }
        </script>
    </head>

    <body>
        <canvas id="view" width=320px height=240px></canvas>
    </body>
</html>

というスクリプトを追加します。window.onloadと言うのはページの読み込みが行われた後に呼び出される関数で、その関数を上書きしています。関数の中身でdocument.getElementById関数はページ内のタグの中で<*** id="+++">とid名が付いているタグを探してくれる関数です。見つかればcanvas変数にそのタグのオブジェクトが入ります。上の場合だと"view"というIDがcanvas要素についているので、canvas変数にはCanvasオブジェクトが格納されるわけです。if文はそのCanvasオブジェクトがgetContextと言う関数を持っているかをチェックしています。JavaScriptはこういう関数保持のチェックも簡単なのが良いですね。getContextはCanvasクラスが持っている関数で「コンテキスト」というオブジェクトを返してくれるようです。コンテキストと言うとWindows APIなプログラムでお馴染み「デバイスコンテキスト」と似たようなもので、描画領域に対する描画メソッドを提供してくれるオブジェクトです。上の例ではその一つfillRect関数を使ってCanvasの中に塗りつぶされた長方形を描画しようとしています。で、このHTML5をChromeで読み込むと:

おいえ〜い!出たぜい(^-^)v。小さな一歩、でも偉大な一歩!こんな感じで簡単に図形が描画出来てしまいました。…んあ!しまった!「Hello World!」を出すつもりが図形を描画してしまった(^-^;;。ま、まぁ、フォントはこの後直ぐに調べてみよう。



B Hello Worldをフォント文字で

 図形についてはあっけない程簡単に描画出来てしまいました。線なども描けるとあるので、グラフなどはもうこれで描けます。では文字表示について調べてみます。…<調査中>…。あ、超簡単でした。こちらのサイトに答えがあります(DegiWiki:HTML5 canvas上に文字を書く)。コンテキストのメソッドにfillTextメソッドが用意されていました:

OXSubmarine.html
<html>
    <head>
        <meta charset="UTF-8">
        <title>まるぺけ潜水艦ゲーム</title>

        <script>
            window.onload = function() {
                var canvas = document.getElementById("view");
                if ( canvas.getContext ) {
                    var context = canvas.getContext("2d");

                    // Hello World !描画
                    context.font = "48px Arial";
                    context.fillText("Hello World !", 20, 50);

                }
            }
        </script>
    </head>

    <body>
        <canvas id="view" width=320px height=240px></canvas>
    </body>
</html>

素直に描画できました〜。Context.fontはフォントプロパティなんでしょうね。そこに文字の大きさとフォント名を指定すれば、そのフォントがあればそれを描画してくれるようです。Context.fillTextメソッドは第1引数に文字列、第2、3引数にXY座標を指定するとそこを左下基点としてフォントを描画してくれるようです。Hello World、できました(^-^)



C 絵を描画するにはちゃんと待つ事

 ゲームを作るには図形描画はあまり使わなくて、BMPやPNGなどの絵ファイルを多用する事になります。Canvasで絵を表示するにはどうするか?…<調査中>…。なるほど〜、こちらのサイトがとても参考になりました(HTML5.jp:画像を組み込む)。

 画像を読み込む事自体はとても簡単のようで、Context.drawImageメソッドを次のように利用します:

Context.drawImageメソッド
drawImage( image, x, y );

imageはImageオブジェクトを渡します。x,yはXY座標です。Imageオブジェクトはその名前の通り、画像を格納して管理しているオブジェクトです。なのでイメージオブジェクトを作れればOKというわけです。そのイメージオブジェクトは、

Imageメソッド
var image = new Image();
image.src = "resources/test.png"; // ←このHTMLからの相対パスです

という感じであっさりと作れてしまうんですが…、実はimage.srcにパスを指定すると、その時に初めて画像ファイルを読みに行きます。そのため、srcを指定した直後にdrawImageメソッドを呼び出すと、読み込みが間に合わずに画像が描画されずに終わってしまいます。そのため、画像が確実に読み終わっている必要があります。この「待ち」を実現するために、実際はこのように描画する事になります:

OXSubmarine.html
<html>
    <head>
        <meta charset="UTF-8">
        <title>まるぺけ潜水艦ゲーム</title>

        <script>
            window.onload = function() {
                var canvas = document.getElementById("view");
                if ( canvas.getContext ) {
                    var context = canvas.getContext("2d");

                    // 画像を読み込んでから描画
                    var image = new Image();
                    image.src = "resources/test.png";

                    image.onload = function() {
                        context.drawImage( image, 10, 10 );
                    }
                }
            }
        </script>
    </head>

    <body>
        <canvas id="view" width=320px height=240px></canvas>
    </body>
</html>

image.srcで画像ファイルを指定した後にimage.onloadメソッドを作ります。このメソッドは画像が読み終わった後に呼ばれるため、この中でdrawImageメソッドを呼べば確実に画像を描画できます。こんな感じです:

よ〜し、画像の読み込みや表示も最低限ですが出来ました。ただ、今の段階ではまだ「HTMLが呼び出された時」という1フレームの動作しか出来ていません。ゲームにするにはこれを毎フレーム描画する仕組みがいります。



D 毎フレームほにゃらら

 ゲームですから毎フレーム何らかの計算や描画が走ります。それを実現するには毎フレーム誰かにどこかの関数を読んでもらわなければなりません。このいわゆる「メインループ」の仕組みについてはこちらのスライドがとても参考になりました(HTML5 Canvasでシューティングゲーム 27p)。本当に資料が多くて助かります(T-T)。これによると、どうやらタイマーを使って呼び出ししてもらうようです。なるほど。

 手順はメインループとなる関数を一つ作り、その中にやりた事を書き込むというスタイルのようです。例えば先程覚えたてほやほやのフォント描画で、フレームカウント数を描画してみましょうか。きっとこんな感じです:

OXSubmarine.html
<html>
    <head>
        <meta charset="UTF-8">
        <title>まるぺけ潜水艦ゲーム</title>

        <script>
            var context;
            var timer;        // タイマー
            var counter = 0;  // カウンター

            window.onload = function() {
                var canvas = document.getElementById("view");
                if ( canvas.getContext ) {
                    context = canvas.getContext("2d");

                    // 16ミリ秒ごとにmainLoop関数を呼んでもらうようにする
                    var timer = setInterval( mainLoop, 16 );
                }
            }

            // メインループ関数
            function mainLoop() {
                context.clearRect( 0, 0, 320, 240 );    // Canvasをクリア
                context.font = "50px Arial";
                context.fillText( counter, 20, 60 );
                counter++;
            }
        </script>

    </head>

    <body>
        <canvas id="view" width=320px height=240px></canvas>
    </body>
</html>

一番最初にwindow.onloadメソッドが呼ばれた時にCanvasからコンテキストを取得し保持しておきます。同時にsetInterval関数を使って16ミリ秒毎にmainLoop関数を読んでもらうようにしています。こうすると、後はmainLoop関数が勝手に16ミリ秒ごとに呼ばれます。実際に動かしてみたのがこちら:

あ、HTML5だからリンク先で確かめてもらってもいいわけだ(^-^;。こちらで実行できます(OXSubmarineTest0001.html)。HTML5に対応したブラウザで見られるはずです。

 さて、後残すは「プレイヤーからのインタラクティブ」です。ただ画面をぼ〜っと眺めていてもゲームになりません。プレイヤーがコントロールしたり場所を指示し、それに対するレスポンスを返して初めてゲームとして成立します。



E プレイヤーインタラクティブ

 プレイヤーからの指示を検知し、それに対してレスポンスするという、ゲームで大切な要素の一つをどう実現するか…んー…調べてみます(^-^;。…<調査中>…。先程の「メインループ」の仕組みでお世話になったスライド内に答えがありました(HTML5 Canvasでシューティングゲーム 31p)。これによりますと、キーの情報はdocument.onkeypressメソッドに飛んでくるようです。このメソッドを上書きして乗っ取り、引数に渡されているキーコードを判別して好きなようにレスポンスさせる…というのが基本のようです。実際にキーイベントをキャッチしてみます。

 押されたキーのコードを先程のフォント描画を使って出力してみます。onkeypressメソッドを使うとこうなります:

OXSubmarineTest_KeyTest.html
<script>
    var context;
    var timer;
    var curKeyCode = 0;

    window.onload = function() {
        var canvas = document.getElementById("view");
        if ( canvas.getContext ) {
            context = canvas.getContext("2d");
            var timer = setInterval( mainLoop, 16 );

            // キー押し下げハンドラを登録
            document.onkeypress = onKeyPress;
        }
    }

    function onKeyPress( e ) {
        curKeyCode = e;
    }

    function mainLoop() {
        context.clearRect( 0, 0, 320, 240 );
        context.font = "50px Arial";
        context.fillText( "Code = " + curKeyCode.keyCode, 20, 60 );
    }
</script>

 さ、Chromeで実行してみましょう(OXSubmarineTest_KeyPress.html)…ん?ん!アルファベットはちゃんとキーコードが出るのですが(ASCIIですね)、上下左右キーを押し下げても値が変わらない…。じゃ、じゃぁFireFoxでは…?おっと、こちらは逆だ!上下左右キーは反応するけど、アルファベットキーは軒並み全部ゼロ…。そうかぁ、onkeypressはちょっとブラウザによって挙動が違うみたいです。じゃぁ似たようなハンドラであるonkeydownはどうか?先のコードのonkeypressの部分をonkeydownに変更するだけです(OXSubmarineTest_KeyDown.html)。…お!こちらは全部のキー情報がちゃんと取れるし、ChromeでもFireFoxでも同じ挙動です。という事で、onkeypressは使わずonkeydown(とonkeyup)で対処する事にしましょう。

 ちなみに、上下左右のキーコードはそれぞれ以下の通りでした:

Key Code
37
38
39
40

続いて、マウスポインタの情報を取得する必要もありますね。これは…えーと、あ〜こちらの記事にありました(JavaScript Maniax:マウス位置判定)。なるほど、マウスが移動した時にはdocument.onmousemouveにイベントが飛んでくるようです。で、気をつけなくてはいけないのは、これはクライアント領域の位置情報なので、Canvas内位置情報に変換する必要があります。Canvasの位置がずれるのには「ブラウザのスクロールバーを動かした時」と「Canvas自体の位置がクライアントの左上でない時」です。スクロールバーによる位置はdocument.body.scrollLeft及びscrollTopで、canvasの位置はCanvas.offsetLeftとoffsetTopでそれぞれ取得できます。これらの情報からクライアント座標をCanvas座標に変換する関数を作る事ができます:

クライアント座標をCanvas座標に変換
function clientToCanvas(canvas, clientX, clientY) {
    var cx = clientX - canvas.offsetLeft + document.body.scrollLeft;
    var cy = clientY - canvas.offsetTop + document.body.scrollTop;
    var ret = {
        x: cx,
        y: cy
    };
    return ret;
}

第1引数がCanvasオブジェクト、第2と第3引数はクライアント領域のXY座標です。マウスポインタのクライアント位置座標を得た後にこの変換関数を通せばキャンバス上の座標となります。

 ついでにマウスのクリック情報も必要ですよね。クリック情報は…んと〜、document.onmouseupとonmousedownのようです。引数のbuttonにLRボタンの値が数字で入ってくるそうです。そこで、現在のCanvas座標及びbuttonの数値を表示するサンプルコードを作ってテストしてみます:

OXSubmarineTest_MouseMove.html
var canvas;
var context;
var timer;
var curPosX = 0;
var curPosY = 0;
var mouseState = -1;

window.onload = function() {
    canvas = document.getElementById("view");
    if ( canvas.getContext ) {
        context = canvas.getContext("2d");
        var timer = setInterval( mainLoop, 16 );

        document.onmousemove = onMouseMove;   // マウス移動ハンドラ
        document.onmouseup = onMouseUp;       // マウスアップハンドラ
        document.onmousedown = onMouseDown;   // マウスダウンハンドラ
    }
}

function onMouseMove( e ) {
    curPosX = e.clientX;
    curPosY = e.clientY;
    var pos = clientToCanvas( canvas, curPosX, curPosY );
    curPosX = pos.x;
    curPosY = pos.y;
}

function onMouseDown( e ) {
    mouseState = e.button;
}

function onMouseUp( e ) {
    mouseState = -1;
}

function clientToCanvas(canvas, clientX, clientY) {
    var cx = clientX - canvas.offsetLeft + document.body.scrollLeft;
    var cy = clientY - canvas.offsetTop + document.body.scrollTop;
    var ret = {
        x: cx,
        y: cy
    };
    return ret;
}

function mainLoop() {
    context.clearRect( 0, 0, 320, 240 );
    context.font = "48px Arial";
    context.fillText( curPosX + ", " + curPosY + "(" + mouseState + ")", 20, 60 );
}

ハンドラが増えたのと座標変換関数が入った分ちょっとだけ長くなりましたが、これでマウス検知ができているみたいです(OXSubmarineTest_MouseMove.html)。これによると、マウスの左クリックは0、中クリックは1、右クリックは2という値がe.buttonに入って来るようです:


右クリックした場合

これで「毎フレーム更新」「画像読み込みと描画」「フォント描画」「キー押し下げ情報の取得」「マウスカーソル位置の取得」「マウス押し下げ情報の取得」と、ゲーム制作に必要そうな所がずらっと揃いました。もちろん、細かく他に色々と必要になるとは思いますが、今の所これでも十分にゲーム制作を進める事ができます。潜水艦ゲーム、この基盤を元にちょっとずつ進めていきましょう。