HTML5 を使ったシンプルな 2 D ゲームの作り方(スプライトを使った画像の切り替え)


前回に引き続き HTML5 を使用したシンプルな 2D ゲームの作り方の第 9  回めをお送りします。

どんなゲームを作るのかは 1 回目の記事の中に実際に動作するゲームが埋め込んであるのでぜひ遊んでみてください。なお、開発に必要な画像データは 2 回目の記事からダウンロードできますので、実際にゲーム開発を体験したい方はそちらから入手してください。

  1. HTML5 を使ったシンプルな 2 D ゲームの作り方(序)
  2. HTML5 を使ったシンプルな 2 D ゲームの作り方(準備編)
  3. HTML5 を使ったシンプルな 2 D ゲームの作り方 (画像のロード)
  4. HTML5 を使ったシンプルな 2 D ゲームの作り方 (アニメーションの実装)
  5. HTML5 を使ったシンプルな 2 D ゲームの作り方 (矢印キーとタッチによる制御の実装)
  6. HTML5 を使ったシンプルな 2 D ゲームの作り方 (当たり判定の実装)
  7. HTML5 を使ったシンプルな 2 D ゲームの作り方 (複数のSpriteの生成)
  8. HTML5 を使ったシンプルな 2 D ゲームの作り方 (ランダムな動作と FPS の制御)

前回まで記事で、1. 入力装置からのプレイヤーの操作2. 複数のターゲットの生成とランダムな動作3.当たり判定、いうアクションゲームの基本的な機能が実装されました。ここに「雪の結晶を拾ったら点数を加算し、3 回逃がしたらゲームオーバー」もしくは「降ってくる雪の結晶をよけて、3 回当たったらゲームオーバー」というようなルールを (※) 実装すれば簡単なゲームのできあがりです。(※なんであれ「ゲーム」には「ルール」が必要なのです。)

しかしながら今の状態では、動作中や当たり判定時の表現がいまひとつなので、今回は状態の変化をユーザーより分かり易くするためにキャラクターの画像を変更する機能を実装します。

この機能を今回は スプライト という方法を使用します。今回の「スプライト」は第 7 回の記事で紹介したゲーム内のキャラクターを表す「Sprite」ではなく、画像処理における重ね合わせの「スプライト」でもなく、一枚の画像から該当の絵が書かれている箇所を抜き出して使用する方法のスプライトです。

image

スプライトのメリット

スプライトを使用すると、使用する複数の画像が 1 つのファイルにまとまっているため Web サーバーとの通信を減らすことが出来ます。

HTTP のリクエストやレスポンスには、ブラザー内の表示されるデータの他に、HTTP 通信のためのさまざまな付加情報が含まれています。この情報はリクエスト毎にやり取りされるので、使用するファイルの数に比例して増えていきます。しかも、それらの情報は同一の場所に配置されているファイルであればほとんど同じものです。使用する画像をまとめることにより、これらの情報やコネクション数なども減らすことができるので、通信にかかるコストも減りパフォーマンスも向上します。またキャッシュも効きやすくなり、今回のようなゲームの使用する場合は、画像の読み込み完了のチェックが減るため管理が楽になります。

 

Canvas でのスプライトの実装

Canvas では、読み込んだ画像を表示する際に context.drawImage メソッドを使用していますが、この context.drawImage メソッドの引数を適切に指定することにより元画像から任意の位置の任意のサイズの領域を切り出すことができます。具体的な書式は以下のとおりです。

[書式]
context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);

[引数]
image : 元のイメージ
sx : 切り出す画像の左上の x 座
sy : 切り出す画像の左上の y 座標
sw : 切り出す画像の幅
sh : 切り出す画像の高さ
dx : Canvas 上で描画する画像の左上の x 座標
dy : Canvas 上で描画する画像の左上の y 座標
dw : Canvas 上で描画する画像の幅
dh : Canvas 上で描画する画像の高さ

これを使用してスプライトを実装することができます。

しかし、この書式のまま使用するとなると、毎回切り出す座標の位置や領域のサイズを数値で指定しなければならないため面倒です。

そこで、drawImage メソッドを以前定義した Sprite クラスの中で使用し、外部にはもっと引数が少なく使いやすい drow メソッドを作って公開するようにします。

このシリーズの第 2 回めの記事から入手できる画像のセットには、雪の結晶の 3 つの状態をまとめた以下のような画像ファイル sp_snow.png が含まれています。

image

これを以下のようにインデックスで指定できるようにすると便利です。

image

そしてこのインデックスの値に以下のように名前を付けるとより分かり易くなります。

const SNOW_BLUE    = 0;
const SNOW_WHITE = 1;
const SNOW_CLASH = 2;

 

たとえば、これから画像スプライト必要な機能を組み込んだ後の Sprite クラスを使用して 「Canvas 上端」、「左端から 100 px」 の場所に sp_snow.png の真ん中の画像(インデックス 1 )を表示するには以下のような記述となります。

//Sprite インスタンスを格納するための変数
var sprite_snow = null;
//image のインスタンスを生成
var image_snow = new Image();
//画像ファイルをロード
image_snow.src = ‘/img/snowSP.png’;
//画像がロード完了時のイベント
image_snow.onLoad = function (){ 
    //Sprite のインスタンスを生成
    sprite_snow = new Sprite(this, 32, 32);
    //表示する画像のインデックスを指定 
    sprite_snow.imageIndex = SNOW_WHITE ;
    //Canvas 左端からの距離 
    sprite_snow.x = 100;
    //Canvas 上端からの距離
    sprite_snow.y = 0;
    //描画する
    sprite_snow.drow();
}

 

上記のコードで生成された sprite_snow インスタンスでは、以降 imageIndex プロパティに指定したい画像のインデックスを指定することでリアルタイムに画像を変更することが出来るようになります。

しかしそのように実装を行うには一工夫必要です。それは imageIndex プロパティの実装です。

 

setter と getter

既存の Sprite クラスのプロパティは、以下のように単に外部からアクセス可能な変数を宣言しているにすぎません。

var Sprite = function(){
    this.x = 0;   //表示位置 x 
    this.y = 0;  //表示位置 y 
     //略
}

これらはの変数は、値を設定したり参照したりはできますが、値を設定する際、もしくは値を取り出す際に何らかの処理を行うことはできません。

たとえば、画像を切り替えるための imageIndex プロパティをこの方式で実装したとしても、外部から指定された数値は保持されるものの、画像ファイルの指定位置を指定するという処理を行うことはできません。解決策としては、プロパティではなくメソッドとして、例えば setImageIndex 関数として実装するという方法も考えられますが、imageIndex というプロパティがありながら、それに値を設定する際はわざわざメソッドを使用する必要があるのは実装方法として美しくありません。

プロパティに値を設定する際、あるいは値を参照する際になんらかの処理を行いたい場合は、setter メソッドと getter メソッドを使用します。これらのメソッドはプロパティの値の設定/参照の際の処理を実装することができます。

JavaScript における settergetter の指定はいくつか方法がありますが、ここでは 以下のように Object.defineProperty メソッドを使用します。

Object.defineProperty(this, "imageIndex", {
           //プロパティ値を返す
           get: function () {
               return プロパティ値;
           },
          //プロパティ値を設定する
           set: function (val) {
               プロパティ値を保持する変数 = val;
           }
});

 

上記を踏まえ Sprite 関数 (クラス) を以下のように書き換えます。コンストラクターに画像のサイズを指定する引数 width と  height が追加されていることに注意してください。これら引数の値は元画像ファイルから必要箇所を切り出すのに使用されます。なお、太字の部分が追加されたコードです。

//Sprite クラスの定義
var Sprite = function (img, width, height)  {
    this.image = img; //image オブジェクト
    this.height = img.height;
    this.width = img.width;
    this.x = 0;   //表示位置 x
    this.y = 0;  //表示位置 y
    this.dx = 0; //移動量 x
    this.dy = 0; //移動量 y
    var _offset_x_pos = 0;
    var that = this;

    //使用するインデックスを設定するための Setter/Getter
    var _imageIndex = 0;
    Object.defineProperty(this, "imageIndex", {
        get: function () {
            return _imageIndex;
        },
        set: function (val) {
            _imageIndex = val;
            _offset_x_pos = width * _imageIndex;
        }
    });

    //Sprite を描画するメソッド
    this.draw = function () {
        ctx.drawImage(img, _offset_x_pos, 0, width, height, that.x, that.y, width, height);
    };
}

 

これに合わせ、雪だるまと雪の結晶の画像サイズを以下のように const か、もしくは var でソース前部の変数等を宣言している適当な箇所に記述します。

//雪の結晶の画像サイズ
const SNOW_PIC_HEIGHT = 32;
const SNOW_PIC_WIDTH = 32;

//雪ダルマの画像サイズ
const SNOW_MAN_PIC_HEIGHT = 80; const SNOW_MAN_PIC_WIDTH = 80;

 

画像を切り替える処理の実装

Sprite クラスのコンストラクタの引数の変更に合わせ loadAssets 関数内で Sprite クラスのインスタンスを生成している箇所を各々以下のように書き換えます。画像のファイルが snow.png から sp_snow.png に変更されているので注意してください。

//image オブジェクトに画像をロード 
img_snow.src = ‘/img/sp_snow.png‘;

/*画像読み込み完了のイベントハンドラーに Canvas に
           画像を表示するメソッドを記述 */ 
img_snow.onload = function () {
    for (var i = 0; i < SNOWS_COUNT; i++) {
        var sprite_snow = new Sprite(img_snow, SNOW_PIC_WIDTH, SNOW_PIC_HEIGHT);
        sprite_snow.dy = 1;
        sprite_snow.dx = NEIGHBOR_DISTANCE;
        sprite_snow.x = i * sprite_snow.dx;
        sprite_snow.y = getRandomPosition(SNOWS_COUNT, SNOW_START_COEFFICIENT);
        snow_sprites.push(sprite_snow);
        sprite_snow = null;
    }
};
//雪だるま画像のロード
img_snow_man = new Image();
img_snow_man.src = ‘/img/snow_man.png’;
img_snow_man.onload = function () {
    sprite_snow_man = new Sprite(img_snow_man, SNOW_MAN_PIC_WIDTH,
                                                 SNOW_MAN_PIC_HEIGHT);

    sprite_snow_man.x = getCenterPostion(canvas.clientWidth, img_snow_man.width);
    sprite_snow_man.y = canvas.clientHeight – img_snow_man.height;
    sprite_snow_man.limit_rightPosition = getRightLimitPosition(canvas.clientWidth,
                                                                            img_snow_man.width);
};

 

次に、雪の結晶が降ってくる際の動きを出すための処理を記述します。具体的には、一定時間ごとに雪の結晶の画像を切り替えて、チラチラと点滅するようにします。

現在このゲームのソースでは、前回の記事の設定により 1 秒間に 48 回のフレーム処理が行われるようになっています。よって 1 秒間の長さを測るには処理の回数が 48 回行われたかをカウントすればよく、0.5 秒ごとに画像を切り替えるのであれば、その半分の 24 回処理が行われたかどうかを判断し、画像を切り替えてやればいいことになります。

そのためには処理数をカウントする必要があるので、カウントに使用する変数 loopCounter  と、ついでに画像の切り替えのタイミングである 0.5 秒を表す 24 も SWITCH_PICTURE_COUNT という名前付き定数として以下のようにソースコードの前部で宣言します。

//画面の書き換え数をカウントする
var loopCounter = 0;
//雪の結晶画像を切り替える閾値 
const SWITCH_PICTURE_COUNT = 24;

 

次にフレームの処理を行っている renderFrame 関数内にこれらを使用して 1 秒間の処理数をカウントを行う記述を行います。

renderFrame 関数は if 文内の timeKeeper.nextFrameJob 関数によって、フレーム処理を行うか否かの判断がされているので、フレーム処理を行っている分岐側の一番最後の処理である window.requestAnimationFrame(renderFrame); の前の行に以下の構文を追加します。

//処理数のカウント
if (loopCounter == SWITCH_PICTURE_COUNT) { loopCounter = 0; }
loopCounter++;

 

そして、同関数内の snow_sprite の y 値(縦位置) が canvas からはみ出たら先頭に戻す処理に以下のように追記します。なお、追記部分は太字になっています。

/ //snow_sprite の y 値(縦位置) が canvas からはみ出たら先頭に戻す
if (snow_sprite.y > canvas.clientHeight) {
    snow_sprite.y = getRandomPosition(SNOWS_COUNT, SNOW_START_COEFFICIENT);
    snow_sprite.imageIndex = SNOW_BLUE;
}else { 
   if (loopCounter == SWITCH_PICTURE_COUNT 
                                      && snow_sprite.imageIndex != SNOW_CLASH) {
        snow_sprite.imageIndex = (snow_sprite.imageIndex == SNOW_BLUE) 
                                                       ? SNOW_WHITE : SNOW_BLUE;
     }
};

 

次に同関数内で画像を描画するための 2 つのの ctx.drawImage 関数を以下のように Sprite  クラスに実装した draw 関数に書き換えます。

雪の結晶
現在) ctx.drawImage(snow_sprite.image, snow_sprite.x, snow_sprite.y);

                           ↓

修正後)snow_sprite.draw();

雪だるま
現在) ctx.drawImage(sprite_snow_man.image, sprite_snow_man.x, sprite_snow_man.y);

                           ↓

修正後)sprite_snow_man.draw();

上記を含めた renderFrame 関数全体のコードは以下のとおりです。太字の部分が追記、もしくは変更したものです。

function renderFrame() {
    if (timeKeeper.nextFrameJob()) {
       //canvas をクリア
       ctx.clearRect(0, 0, canvas.width, canvas.height);
        //sprite_snow_man の x 値が動作範囲内かどうか
        if ((sprite_snow_man.x < sprite_snow_man.limit_rightPosition && key_value > 0)
             || (sprite_snow_man.x >= 3 && key_value < 0)) {
                //img_snow_man の x 値を増分
                sprite_snow_man.x += key_value;
        }

        var length = snow_sprites.length;
        for (var i = 0; i < length; i++) {
            var snow_sprite = snow_sprites[i];
            //snow_sprite の y 値(縦位置) が canvas からはみ出たら先頭に戻す
            if (snow_sprite.y > canvas.clientHeight) {
                snow_sprite.y = getRandomPosition(SNOWS_COUNT, SNOW_START_COEFFICIENT);
                snow_sprite.imageIndex = SNOW_BLUE;
            }else {
                if (loopCounter == SWITCH_PICTURE_COUNT
                                           && snow_sprite.imageIndex != SNOW_CLASH) {
                    snow_sprite.imageIndex = (snow_sprite.imageIndex == SNOW_BLUE)
                                           ? SNOW_WHITE : SNOW_BLUE;
                } 
            };
            //snow_sprite の y 値を増分
            snow_sprite.y += snow_sprite.dy;
            //画像を描画
            snow_sprite.draw();
            //当たり判定
            isHit(snow_sprite, sprite_snow_man);
            snow_sprite = null;
        }
        //画像を描画
        sprite_snow_man.draw();
        //処理数のカウント
       if (loopCounter == SWITCH_PICTURE_COUNT) { loopCounter = 0; }
            loopCounter++;

            window.requestAnimationFrame(renderFrame);
    } else {
        window.requestAnimationFrame(renderFrame);
    }
}

 

ここまでの作業で、ゲーム内で降る雪が 0.5 秒ごとに画像を瞬くように変えながら降ってくるようになりました。

次は当たり判定時に、雪の結晶の画像を切り替える処理を実装します。

 

当たり判定時の画像切り替え処理

雪だるまと雪の結晶が衝突した際に、粉砕を表す左から 3 番目 (インデックスは 2 ) の画像に切り替える処理を実装します。

image

この作業は isHit 関数に以下のように一行追加するだけです。

//当たり判定
function isHit(targetA, targetB) {
    if ((targetA.x <= targetB.x && targetA.width + targetA.x >= targetB.x)
         || (targetA.x >= targetB.x && targetB.x + targetB.width >= targetA.x)) {
         if ((targetA.y <= targetB.y && targetA.height + targetA.y >= targetB.y)
                || (targetA.y >= targetB.y && targetB.y + targetB.height >= targetA.y)) {
             ctx.font = "bold 20px ‘MS ゴシック’";
             ctx.fillStyle = "red";
             ctx.fillText("ヒットしました", getCenterPostion(canvas.clientWidth, 140), 160);
             targetA.imageIndex = SNOW_CLASH;
        }
    }
}

 

ここまでの処理で雪の結晶が一定時間で表示する画像を切り替えながら降り、雪だるまと衝突すると粉砕した画像に切り替わるようになりました。

以下の黒いボックスをクリックして、実際の動作を確認してください。(矢印キー[←][→]で雪だるまが左右に移動します)

 

今回の変更を加えた main.js の完全なソースコードは以下になります。

(function () {
    //矢印キーのコード
    var LEFT_KEY_CODE = 37;
    var RIGHT_KEY_CODE = 39;
    var key_value = 0;

    //全体で使用する変数
    var canvas = null;
    var ctx = null;
    var img_snow = null;
    var img_snow_man = null;

    //画面の書き換え数をカウントする
    var loopCounter = 0;

    //雪の結晶画像を切り替える閾値
    const SWITCH_PICTURE_COUNT = 24;

    //1 秒間に実行されるフレーム数
    const GAME_FPS = 48;

    //表示する雪の結晶の数
    const SNOWS_COUNT = 6;

    //移動開始位置を得るための係数
    const SNOW_START_COEFFICIENT = -50;

    //隣り合う 雪の結晶画像の x 位置の差分
    const NEIGHBOR_DISTANCE = 58;

    //雪だるまの Sprite のインスタンスを格納する配列 
    var sprite_snow_man = null;

    //雪の Sprite のインスタンスを格納する配列 
    var snow_sprites = [];

    //スプライト画像のインデックス
    const SNOW_BLUE = 0;
    const SNOW_WHITE = 1;
    const SNOW_CLASH = 2;

    //雪の結晶の画像サイズ
    const SNOW_PIC_HEIGHT = 32;
    const SNOW_PIC_WIDTH = 32;

    //雪ダルマの画像サイズ
    const SNOW_MAN_PIC_HEIGHT = 80;
    const SNOW_MAN_PIC_WIDTH = 80;

    //Sprite クラスの定義
    var Sprite = function (img, width, height) {
        this.image = img; //image オブジェクト
        this.height = img.height;
        this.width = img.width;
        this.x = 0;   //表示位置 x
        this.y = 0;  //表示位置 y
        this.dx = 0; //移動量 x
        this.dy = 0; //移動量 y
        var _offset_x_pos = 0;
        var that = this;

        //使用するインデックスを設定するための Setter/Getter
        var _imageIndex = 0;
        Object.defineProperty(this, "imageIndex", {
            get: function () {
                return _imageIndex;
            },
            set: function (val) {
                _imageIndex = val;
                _offset_x_pos = width * _imageIndex;
            }
        });

        //Sprite を描画するメソッド
        this.draw = function () {
            ctx.drawImage(img, _offset_x_pos, 0, width, height, that.x, that.y, width, height);
        };
    }

    //FPS をコントロールするための timeKeeper クラス
    function TimeKeeper(frameCount) {
        var bofore_animation_time = 0;
        var frameInterval = (600 / frameCount);
        //window.performance オブジェクトに対応していないブラウザへの対応
        var getNow = (window.performance.now) ?
            function () { return window.performance.now(); }
            : function () { return (new Date()).getTime(); }
        //FPS として指定したフレームごとの時間が経過したら true を返す
        this.nextFrameJob = function () {
            var now_the_time = getNow();
            var renderFlag = !(((now_the_time – bofore_animation_time) < frameInterval)
                && bofore_animation_time);
            if (renderFlag) bofore_animation_time = now_the_time;
            return renderFlag;
        };
    }

    //TimeKeeper クラスのインスタンスを格納
    var timeKeeper = new TimeKeeper(GAME_FPS);

    //DOM のロードが完了したら実行
    document.addEventListener("DOMContentLoaded", function () {
        loadAssets();
        setHandlers();
    });

    function setHandlers() {
        //キーイベントの取得 (キーダウン)
        document.addEventListener("keydown", function (evnt) {
            if (evnt.which == LEFT_KEY_CODE) {
                key_value = -3;
            } else if (evnt.which == RIGHT_KEY_CODE) {
                key_value = 3;
            }
        });

        //雪だるまが進みっぱなしにならないように、 キーが上がったら 0 に
        document.addEventListener("keyup", function () {
            key_value = 0;
        });

        //Canvas へのタッチイベント設定
        canvas.addEventListener("touchstart", function (evnt) {
            if ((canvas.clientWidth / 2) > evnt.touches[0].clientX) {
                key_value = -3;
            } else {
                key_value = 3;
            }
        });

        //雪だるまが進みっぱなしにならないように、 タッチが完了したら 0 に
        canvas.addEventListener("touchend", function (evnt) {
            key_value = 0;
        });
    }

    function loadAssets() {
        //HTML ファイル上の canvas エレメントのインスタンスを取得 
        canvas = document.getElementById(‘bg’);
        //アニメーションの開始
        canvas.addEventListener("click", loadCheck);
        //2D コンテキストを取得
        ctx = canvas.getContext(‘2d’);
        //image オブジェクトのインスタンスを生成
        img_snow = new Image();
        //image オブジェクトに画像をロード
        img_snow.src = ‘/img/sp_snow.png’;

        /*画像読み込み完了のイベントハンドラーに Canvas に
           画像を表示するメソッドを記述 */
        img_snow.onload = function () {
            for (var i = 0; i < SNOWS_COUNT; i++) {
                var sprite_snow = new Sprite(img_snow, SNOW_PIC_WIDTH, SNOW_PIC_HEIGHT);
                sprite_snow.dy = 1;
                sprite_snow.dx = NEIGHBOR_DISTANCE;
                sprite_snow.x = i * sprite_snow.dx;
                sprite_snow.y = getRandomPosition(SNOWS_COUNT, SNOW_START_COEFFICIENT);
                snow_sprites.push(sprite_snow);
                sprite_snow = null;
            }
        };
        //雪だるま画像のロード
        img_snow_man = new Image();
        img_snow_man.src = ‘/img/snow_man.png’;
        img_snow_man.onload = function () {
            sprite_snow_man = new Sprite(img_snow_man, SNOW_MAN_PIC_WIDTH, SNOW_MAN_PIC_HEIGHT);
            sprite_snow_man.x = getCenterPostion(canvas.clientWidth, img_snow_man.width);
            sprite_snow_man.y = canvas.clientHeight – img_snow_man.height;
            sprite_snow_man.limit_rightPosition = getRightLimitPosition(canvas.clientWidth, img_snow_man.width);
        };
    };

    //ゲームで使用する Splite オブジェクトが準備されたかどうかを判断
    function loadCheck() {
        if (snow_sprites.length && sprite_snow_man) {
            //準備ができたらアニメーションを開始
            window.requestAnimationFrame(renderFrame);
        } else {
            //まだの場合はループして待機
            window.requestAnimationFrame(loadCheck);
        }
    }

    function renderFrame() {
        if (timeKeeper.nextFrameJob()) {
            //canvas をクリア
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            //sprite_snow_man の x 値が動作範囲内かどうか
            if ((sprite_snow_man.x < sprite_snow_man.limit_rightPosition && key_value > 0)
             || (sprite_snow_man.x >= 3 && key_value < 0)) {
                //img_snow_man の x 値を増分
                sprite_snow_man.x += key_value;
            }

            var length = snow_sprites.length;
            for (var i = 0; i < length; i++) {
                var snow_sprite = snow_sprites[i];
                //snow_sprite の y 値(縦位置) が canvas からはみ出たら先頭に戻す
                if (snow_sprite.y > canvas.clientHeight) {
                    snow_sprite.y = getRandomPosition(SNOWS_COUNT, SNOW_START_COEFFICIENT);
                    snow_sprite.imageIndex = SNOW_BLUE;
                }else {
                    if (loopCounter == SWITCH_PICTURE_COUNT
                                   && snow_sprite.imageIndex != SNOW_CLASH) {
                        snow_sprite.imageIndex = (snow_sprite.imageIndex == SNOW_BLUE)
                                    ? SNOW_WHITE : SNOW_BLUE;
                    }
                };
                //snow_sprite の y 値を増分
                snow_sprite.y += snow_sprite.dy;
                //画像を描画
                snow_sprite.draw();

                //当たり判定
                isHit(snow_sprite, sprite_snow_man);
                snow_sprite = null;
            }
            //画像を描画
            sprite_snow_man.draw();

            //処理数のカウント
            if (loopCounter == SWITCH_PICTURE_COUNT) { loopCounter = 0; }
            loopCounter++;

            window.requestAnimationFrame(renderFrame);
        } else {
            window.requestAnimationFrame(renderFrame);
        }
    }

    //中央に配置する画像の X 座標を求める関数
    function getCenterPostion(containerWidth, itemWidth) {
        return (containerWidth / 2) – (itemWidth / 2);
    };

    //Player (雪だるまを動かせる右の限界位置)
    function getRightLimitPosition(containerWidth, itemWidth) {
        return containerWidth – itemWidth;
    }

    //雪の結晶の縦位置の初期値をランダムに設定する
    function getRandomPosition(colCount, delayPos) {
        return Math.floor(Math.random() * colCount) * delayPos;
    };

    //雪と雪だるまがヒットした際の処理
    function hitJob(snow_sprite) {
        ctx.font = "bold 50px";
        ctx.fillStyle = "red";
        ctx.fillText("ヒットしました", 100, 160);
        snow_sprite.imageIndex = SNOW_CLASH;
        if (!snow_sprite.soundPlayed) {
            snow_sprite.sound.play();
            snow_sprite.soundPlayed = true;
        }
    }

    //当たり判定
    function isHit(targetA, targetB) {
        if ((targetA.x <= targetB.x && targetA.width + targetA.x >= targetB.x)
                || (targetA.x >= targetB.x && targetB.x + targetB.width >= targetA.x)) {

            if ((targetA.y <= targetB.y && targetA.height + targetA.y >= targetB.y)
                || (targetA.y >= targetB.y && targetB.y + targetB.height >= targetA.y)) {
                ctx.font = "bold 20px ‘MS ゴシック’";
                ctx.fillStyle = "red";
                ctx.fillText("ヒットしました", getCenterPostion(canvas.clientWidth, 140), 160);
                targetA.imageIndex = SNOW_CLASH;
            }
        }
    }
})();

 

まとめ

今回はスプライトという方法で、1 つの画像ファイルから任意の範囲にある絵を取り出してゲーム内のキャラクターの画像を変更する処理を実装しました。

このスプライトを使用してキャラクターの画像を適宜切り替えることにより、疑似的なアニメーション効果を得ることができます。

キャラクターの歩行や、爆発、状態の変化など、さまざまに応用できますので、ぜひ覚えてください。

次回は効果音を鳴らす実装を行います。

Real Time Analytics

Clicky

Comments (0)

Skip to main content