Help us understand the problem. What is going on with this article?

ローグライクゲームを作ってみるその3 プレイヤーの移動

過去記事一覧

現在のコードについては前回の記事の最後の項を参照してください。

プログラム

今回作ったプログラムはhttp://yurinya.net/roguelike2019/3/srl.htmlで公開しています。良かったらアクセスしてみてください!

プレイヤーの移動

このゲームは基本的にキーボードを使って操作することにします。

プレイヤーの移動は矢印キーを使って行うことにします。

まず、プレイヤーは全てのマスに移動できる訳ではないのでした。

床のマスには移動できますが、壁のマスには移動できません。

つまり、マスの種類によって移動できるかどうかが決まります。

これをB_CAN_STANDという配列で下のように定義することにします。trueは移動できるマスであることを表し、falseは移動できないマスであることを表します。

var B_CAN_STAND = [];
B_CAN_STAND[B_FLOOR] = true;
B_CAN_STAND[B_WALL] = false;

そして、キー入力の処理を下のように変更します。

なお、矢印キーのkeyCodeの値はそれぞれ下のようになっています。

  • ・・・37
  • ・・・38
  • ・・・39
  • ・・・40
    c.on('keydown', function (e) {
(省略)
        if (e.keyCode >= 37 && e.keyCode <= 40) {
            var x = player.x;
            var y = player.y;
            if (e.keyCode === 37) {
                if (x === 0) {
                    return;
                }
                x--;
            }
            else if (e.keyCode === 38) {
                if (y === 0) {
                    return;
                }
                y--;
            }
            else if (e.keyCode === 39) {
                if (x === LX - 1) {
                    return;
                }
                x++;
            }
            else if (e.keyCode === 40) {
                if (y === LY - 1) {
                    return;
                }
                y++;
            }

            if (x !== player.x || y !== player.y) {
                var block = field.blocks[x][y];
                if (B_CAN_STAND[block.base]) {
                    player.x = x;
                    player.y = y;
                }
                else {
                    return;
                }
            }
            else {
                return;
            }
        }
        else {
            return;
        }

        draw(con);
    });

矢印キーが押された時の処理が追加されました。

矢印キーの種類によってプレイヤーの新しい位置が決まり、その位置のマスの種類が移動できるものである場合には移動し(プレイヤーの位置を更新し)、再描画を行います。

See the Pen roguelike-3-1 by pizyumi (@pizyumi) on CodePen.

斜め移動

ローグライクゲームはやっぱり斜め移動もできないとですよね。

斜め移動を実装しましょう。

斜め移動はShiftキーを押しながら矢印キーを押すことで行うことにします。

実装においては少なくとも以下の2点を満たす必要があるかと思います。

  • Shiftキーを押している時のみしか斜め移動できない(Shiftキーを押していないのに斜め移動できてはならない)。
  • Shiftキーを押している時には斜め移動しか出来ない(Shiftキーを押している時には上下左右の移動はできてはならない)。

このようにすることにより、誤操作が起きにくくなります。

このような斜め移動の実装方法は幾つかあると思いますが、私は下のような実装方法が好きです。

keylkeyukeyrkeydという変数を用意し、矢印キーが押されているかどうかを常に監視するようにします。

そして、Shiftキーが押されている場合にはこれらの変数の値を基に移動を行うべきかを判断します。

    var keyl = false;
    var keyu = false;
    var keyr = false;
    var keyd = false;

    var c = $('body');
    c.on('keydown', function (e) {
        if (e.keyCode === 37) {
            keyl = true;
        }
        else if (e.keyCode === 38) {
            keyu = true;
        }
        else if (e.keyCode === 39) {
            keyr = true;
        }
        else if (e.keyCode === 40) {
            keyd = true;
        }
        else {
            keyl = false;
            keyu = false;
            keyr = false;
            keyd = false;
        }
    });
    c.on('keyup', function (e) {
        if (e.keyCode === 37) {
            keyl = false;
        }
        else if (e.keyCode === 38) {
            keyu = false;
        }
        else if (e.keyCode === 39) {
            keyr = false;
        }
        else if (e.keyCode === 40) {
            keyd = false;
        }
    });
    c.on('keydown', function (e) {
(省略)
        if (e.keyCode >= 37 && e.keyCode <= 40) {
            var x = player.x;
            var y = player.y;
            if (e.shiftKey) {
                if (keyl && keyu) {
                    if (x === 0 || y === 0) {
                        return;
                    }
                    x--;
                    y--;
                }
                else if (keyr && keyu) {
                    if (x === LX - 1 || y === 0) {
                        return;
                    }
                    x++;
                    y--;
                }
                else if (keyl && keyd) {
                    if (x === 0 || y === LY - 1) {
                        return;
                    }
                    x--;
                    y++;
                }
                else if (keyr && keyd) {
                    if (x === LX - 1 || y === LY - 1) {
                        return;
                    }
                    x++;
                    y++;
                }
                else {
                    return;
                }
            }
            else {
                if (e.keyCode === 37) {
                    if (x === 0) {
                        return;
                    }
                    x--;
                }
                else if (e.keyCode === 38) {
                    if (y === 0) {
                        return;
                    }
                    y--;
                }
                else if (e.keyCode === 39) {
                    if (x === LX - 1) {
                        return;
                    }
                    x++;
                }
                else if (e.keyCode === 40) {
                    if (y === LY - 1) {
                        return;
                    }
                    y++;
                }
            }

            if (x !== player.x || y !== player.y) {
(省略)
            }
            else {
                return;
            }
        }
        else {
            return;
        }

        draw(con);
    });

斜め矢印の表示

Shiftキーを押した時に斜め移動が可能なことをユーザーに分かりやすく見せるために、Shiftキーを押した時にプレイヤーの周りに斜め矢印が表示されるようにしましょう。

この斜め矢印は、たとえば、ふし幻(不思議の幻想郷)でも採用されていますね。

01.jpg

これを行うには、Shiftキーが押されているか(というか斜め移動可能な状態か)を保持しておく必要があります。

この状態はenv.diagonalで保持することにします。env.diagonalの値がtrueである場合には斜め移動可能で、falseである場合には斜め移動不可能です。

    var env = {
        diagonal: false
    };

そして、この状態(env.diagonalの値)によってゲームの描画内容(プレイヤーの周りに斜め矢印を表示するかどうか)が変わりますので、この状態はdraw関数に渡さなければならないということになります。

そこで、全てのdraw関数の呼び出しにおいてはenvを第2引数として指定することにします(変更したコードは示しませんが、全てのdraw関数の呼び出しで第2引数としてenvを渡すように変更します)。

そうしたら、2つ目のkeydownイベントハンドラを下のように変更します。

つまり、Shiftキーが押された場合の処理を追加します。

Shiftキーが押された場合にはenv.diagonalの値をtrueにし、再描画を行います。

    c.on('keydown', function (e) {
        if (!startf) {
(省略)
        }

        if (e.keyCode === 16) {
            if (!env.diagonal) {
                env.diagonal = true;

                draw(con, env);
            }

            return;
        }

        if (e.keyCode >= 37 && e.keyCode <= 40) {
(省略)
        }
        else {
            return;
        }

        draw(con, env);
    });

更に、新たにkeyupイベントハンドラとwindowblurイベントハンドラを追加します。

keyupイベントハンドラにはShiftキーが離された場合の処理を記述します。

Shiftキーが離された場合にはenv.diagonalの値をfalseにし、再描画を行います。

blurイベントハンドラにはウィンドウからフォーカスが外れた場合の処理を記述します。

ウィンドウからフォーカスが外れた場合にもenv.diagonalの値をfalseにし、再描画を行います。

これはウィンドウからフォーカスが外れた場合にも斜め移動が有効なままであるように表示されるのを防ぐためです。

    c.on('keyup', function (e) {
        if (e.keyCode === 16) {
            if (env.diagonal) {
                env.diagonal = false;

                draw(con, env);
            }
        }
    });
    $(window).on('blur', function (e) {
        if (env.diagonal) {
            env.diagonal = false;

            draw(con, env);
        }
    });

最後にdraw関数を変更します。

env.diagonalの値がtrueである場合の描画処理を追加します。

function draw (con, env) {
(省略)

    if (env.diagonal) {
        con.save();

        con.strokeStyle = 'white';
        con.translate(player.x * PX + (PX / 2), player.y * PY + (PY / 2));
        con.rotate(Math.PI / 4);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();

        con.restore();
    }
}

これでShiftキーを押すとプレイヤーの周りに4つの斜め矢印が表示され、斜め移動できるようになりました。

今回はここまで

今回はここまでです。

game.jsは下のようになりました。

var TITLE = 'シンプルローグライク';

var TEXT_START = 'はじめる';

var SCREEN_X = 1600;
var SCREEN_Y = 800;

var LX = 25;
var LY = 25;
var PX = 32;
var PY = 32;

var B_FLOOR = 0;
var B_WALL = 1;

var B_CAN_STAND = [];
B_CAN_STAND[B_FLOOR] = true;
B_CAN_STAND[B_WALL] = false;

var startf = false;

var field = null;
var player = null;

$(function(){
    var canvas = document.getElementById('game');
    var con = canvas.getContext('2d');

    var keyl = false;
    var keyu = false;
    var keyr = false;
    var keyd = false;

    var env = {
        diagonal: false
    };

    var c = $('body');
    c.on('keydown', function (e) {
        if (e.keyCode === 37) {
            keyl = true;
        }
        else if (e.keyCode === 38) {
            keyu = true;
        }
        else if (e.keyCode === 39) {
            keyr = true;
        }
        else if (e.keyCode === 40) {
            keyd = true;
        }
        else {
            keyl = false;
            keyu = false;
            keyr = false;
            keyd = false;
        }
    });
    c.on('keyup', function (e) {
        if (e.keyCode === 37) {
            keyl = false;
        }
        else if (e.keyCode === 38) {
            keyu = false;
        }
        else if (e.keyCode === 39) {
            keyr = false;
        }
        else if (e.keyCode === 40) {
            keyd = false;
        }
    });
    c.on('keydown', function (e) {
        if (!startf) {
            if (e.keyCode === 90) {
                startf = true;

                init();

                draw(con, env);
            }

            return;
        }

        if (e.keyCode === 16) {
            if (!env.diagonal) {
                env.diagonal = true;

                draw(con, env);
            }

            return;
        }

        if (e.keyCode >= 37 && e.keyCode <= 40) {
            var x = player.x;
            var y = player.y;
            if (e.shiftKey) {
                if (keyl && keyu) {
                    if (x === 0 || y === 0) {
                        return;
                    }
                    x--;
                    y--;
                }
                else if (keyr && keyu) {
                    if (x === LX - 1 || y === 0) {
                        return;
                    }
                    x++;
                    y--;
                }
                else if (keyl && keyd) {
                    if (x === 0 || y === LY - 1) {
                        return;
                    }
                    x--;
                    y++;
                }
                else if (keyr && keyd) {
                    if (x === LX - 1 || y === LY - 1) {
                        return;
                    }
                    x++;
                    y++;
                }
                else {
                    return;
                }
            }
            else {
                if (e.keyCode === 37) {
                    if (x === 0) {
                        return;
                    }
                    x--;
                }
                else if (e.keyCode === 38) {
                    if (y === 0) {
                        return;
                    }
                    y--;
                }
                else if (e.keyCode === 39) {
                    if (x === LX - 1) {
                        return;
                    }
                    x++;
                }
                else if (e.keyCode === 40) {
                    if (y === LY - 1) {
                        return;
                    }
                    y++;
                }
            }

            if (x !== player.x || y !== player.y) {
                var block = field.blocks[x][y];
                if (B_CAN_STAND[block.base]) {
                    player.x = x;
                    player.y = y;
                }
                else {
                    return;
                }
            }
            else {
                return;
            }
        }
        else {
            return;
        }

        draw(con, env);
    });
    c.on('keyup', function (e) {
        if (e.keyCode === 16) {
            if (env.diagonal) {
                env.diagonal = false;

                draw(con, env);
            }
        }
    });
    $(window).on('blur', function (e) {
        if (env.diagonal) {
            env.diagonal = false;

            draw(con, env);
        }
    });

    draw(con, env);
});

function init () {
    field = create_field();
    player = {
        x: 12,
        y: 17
    };
}

function create_field () {
    var blocks = [];
    for (var i = 0; i < LX; i++) {
        blocks[i] = [];
        for (var j = 0; j < LY; j++) {
            if ((i === 0 || j === 0) || (i === LX -1 || j === LY - 1)) {
                blocks[i][j] = {
                    base: B_WALL
                };
            }
            else {
                blocks[i][j] = {
                    base: B_FLOOR
                };
            }
        }
    }

    return {
        blocks: blocks
    };
}

function draw (con, env) {
    con.fillStyle = 'black';
    con.fillRect(0, 0, SCREEN_X, SCREEN_Y);

    if (!startf) {
        con.textBaseline = 'alphabetic';
        con.textAlign = 'center';
        con.fillStyle = 'white';

        con.font = "48px consolas";
        con.fillText(TITLE, SCREEN_X / 2, SCREEN_Y / 4);

        con.font = "32px consolas";
        con.fillText('> ' + TEXT_START, SCREEN_X / 2, SCREEN_Y / 4 * 3);

        return;
    }

    for (var i = 0; i < LX; i++) {
        for (var j = 0; j < LY; j++) {
            var block = field.blocks[i][j];
            if (block.base === B_FLOOR) {
                con.fillStyle = 'white';
                con.beginPath();
                con.arc((i + 0.5) * PX, (j + 0.5) * PY, 1, 0, Math.PI * 2);
                con.closePath();
                con.fill();
            }
            else if (block.base === B_WALL) {
                con.strokeStyle = 'white';
                con.strokeRect(i * PX, j * PY, PX, PY);
                con.beginPath();
                con.moveTo(i * PX, j * PY);
                con.lineTo((i + 1) * PX, (j + 1) * PY);
                con.moveTo((i + 1) * PX, j * PY);
                con.lineTo(i * PX, (j + 1) * PY);
                con.closePath();
                con.stroke();
            }
        }
    }

    con.textBaseline = 'middle';
    con.textAlign = 'center';
    con.fillStyle = 'red';
    con.font = '24px consolas';
    con.fillText('🚶\uFE0E', player.x * PX + (PX / 2), player.y * PY + (PY / 2));

    if (env.diagonal) {
        con.save();

        con.strokeStyle = 'white';
        con.translate(player.x * PX + (PX / 2), player.y * PY + (PY / 2));
        con.rotate(Math.PI / 4);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();

        con.restore();
    }
}

次回はダンジョンの生成を少しだけ考えてみたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした