ゲームエンジンやライブラリを使わずに、素のHTMLとJavaScriptだけでゲームを作ります。
以下のような人のためにあります。
ゲームを作ってみたいけど、何をどうしていいか分からない。
単純なゲームを作りたいだけなのに、UnityとかUnreal Engineは面倒くさい。
ある程度プログラミングの知識がある人を対象にしています。
先に読んでおく記事
倉庫番の基本ロジックの作成は前編で説明しています。先に読んでおくことをお勧めします。
この記事で作るもの
前編の四角と丸の表示からドット絵に変えたものです。これだけでも随分、雰囲気が良くなりました。
オリジナルにない要素として「ドア」があります。荷物をすべて所定の場所に置いたらドアが開きます。
ちょっと、本家にそっくりすぎたので、動くバージョンは公開しません。
記事を読んで完成版は自分で作ってみてください。
ソースコード
前編からの主な差分のみを掲載します。
const canvas = document.getElementById('gameCanvas');
const context = canvas.getContext('2d');
context.imageSmoothingEnabled = false;
const SCREEN_W = 20
const SCREEN_H = 15
const BLOCK_SIZE = 32
const Const = {
NONE: 0,
WALL: 1,
BOX: 2,
POINT: 3,
PLAYER: 4,
DOOR: 5,
GOAL: 6,
}
const PlayerDir = {
LEFT: 0,
RIGHT: 1,
UP: 2,
DOWN: 3,
}
class Obj {
constructor(x,y) {
this.x = x;
this.y = y;
}
}
const stages = [
// 省略
];
let stage_i = 0;
let map;
let player = new Obj(0,0);
let player_dir = PlayerDir.LEFT;
let box_list = [];
// 入力ハンドラー
document.addEventListener('keydown', keyDownHandler, false);
function keyDownHandler(event) {
if (event.key === 'i') { // Up
movePlayer(0, -1);
player_dir = PlayerDir.UP;
}
if (event.key === 'm') { // Down
movePlayer(0, 1);
player_dir = PlayerDir.DOWN;
}
if (event.key === 'j') { // Left
movePlayer(-1, 0);
player_dir = PlayerDir.LEFT;
}
if (event.key == 'l') { // Right
movePlayer(1, 0);
player_dir = PlayerDir.RIGHT;
}
if (event.key == 'r') { // Right
init();
}
}
// ・・・・省略
function drawSprite(sprite_no, x, y) {
context.drawImage(spriteSheet, sprite_no*8, 0, 8, 8, x*BLOCK_SIZE, y*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
function drawWall(x, y) {
drawSprite(1, x, y);
}
function drawDoor(x, y) {
drawSprite(3, x, y);
}
function drawPoint(x, y) {
drawSprite(2, x, y);
}
function drawPlayer() {
drawSprite(4 + player_dir, player.x, player.y);
}
function drawBox() {
for (const item of box_list) {
drawSprite(0, item.x, item.y);
}
}
function drawScreen() {
for (j = 0; j < SCREEN_H; j++) {
for (i = 0; i < SCREEN_W; i++) {
obj = map[ i + j*SCREEN_W ];
switch(obj) {
case Const.WALL:
drawWall(i, j); break;
case Const.POINT:
drawPoint(i, j); break;
case Const.DOOR:
drawDoor(i, j); break;
default:
break;
}
}
}
}
function isGameClear() {
for (const item of box_list) {
if (map[item.x + item.y * SCREEN_W] != Const.POINT) {
return false;
}
}
return true;
}
function init() {
map = Array.from(stages[stage_i]);
player_dir = PlayerDir.LEFT;
box_list = [];
for (y = 0; y < SCREEN_H; y++) {
for (x = 0; x < SCREEN_W; x++) {
obj = map[x + y*SCREEN_W];
switch (obj) {
case Const.PLAYER:
player.x = x;
player.y = y;
break;
case Const.BOX:
box_list.push(new Obj(x,y));
break;
}
}
}
}
function openTheDoor() {
for (i = 0; i < SCREEN_W*SCREEN_H; i++) {
if (map[i] == Const.DOOR)
map[i] = Const.NONE;
}
}
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);
drawScreen();
drawBox();
drawPlayer();
if (isGameClear()) {
openTheDoor();
}
if (hitObj(player.x, player.y, Const.GOAL)) {
stage_i = (stage_i+1) % 2;
init();
}
}
// スプライトシートのロード
const spriteSheet = new Image();
spriteSheet.src = "./spritesheet.png";
init();
setInterval(update, 10);
ドット絵を表示する方法
まずは、ドット絵のpngファイルを作成します。ピクセル編集できるグラフィックスエディターが色々でていますが、筆者はFirealpacaを使っています。
用意したのは以下の画像データです。実際の画像サイズはかなり小さいので、ブログ用に拡大表示しています。背景のグレーの部分は、実際は透明色指定です。
ひとつの8×8ピクセルの画像をスプライトと呼びます。プログラムの中では、このスプライトの番号を指定して描画するように関数を作ります。
この画像を spritesheet.png と言う名前で、index.js と同じフォルダーに保存します。
そして、JavaScriptからは png ファイルをロードします。
// スプライトシートのロード
const spriteSheet = new Image();
spriteSheet.src = "./spritesheet.png";
これにより、spriteSheetという名前のImageオブジェクトのインスタンスに画像データが保存されます。
イメージデータの描画は context.drawImage() 関数を使います。仕様は下記の通りです。
context.drawImage(
image, // 画像オブジェクト
sx, // 画像内で切り取るX座標
sy, // 画像内で切り取るY座標
sWidth, // 切り取る幅
sHeight, // 切り取る高さ
tx, // Canvas上のX座標
ty, // Canvas上のY座標
tWidth, // 描画する幅
tHeight // 描画する高さ
);
実際のゲームの中での処理は下記の関数です。
function drawSprite(sprite_no, x, y) {
context.drawImage(spriteSheet, sprite_no*8, 0, 8, 8, x*BLOCK_SIZE, y*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
やっていることは、spritesheet.png の画像データの中の特定の領域を切り取って、canvasに拡大描画しています。図示すると下記になります。
ゲーム作りの楽しいところ、面倒くさいところ
ゲームを作るにあたって、楽しい部分と面倒に感じる部分があります。どの作業が楽しくて、どの作業がつまらないかは人それれぞれだと思います。
筆者はゲームプログラミングは無性に楽しいのですが、それ以外の作業がとても面倒に感じます。グラフィックスを作る部分もそうです。正直、ひとつのキャラクタが8 x 8ピクセルの絵を描くのが忍耐力の限界です。それ以上は作る気が起きません。
とはいえ、しょぼいピクセル画であっても、あるのとないとでは天地の差があります。前回の四角と丸の世界からすると、だいぶゲームの雰囲気が出ています。
8 x 8ピクセルの世界でも悪くないな、と今回改めて思いました。
作った後の楽しみ方
さて、これまで倉庫番の作り方を見てきました。とてもシンプルなゲームなので作ること自体は難しくないと思います。
ただし、このゲームが面白いのは「面データ」です。ゲームシステムはパズルのルールですが、パズルそのものは「面データ」です。
自分で作って気にいったら、本家本元を買って遊ぶのが王道かと思います。
「倉庫番」が驚異的なのは、1980年代の発売から40年以上経っているのに、いまだに現役で売っているということです。このゲームは極めてシンプルですが、ゲーム史に残る名作と言えます。
著者は以前、このゲームの影響を受けたパズルゲームを2つ紹介しました。この2つも名作です。倉庫番を気に入った人は、こちらもお勧めです。
コメント