日経ソフトウェア2014年11月号「HTML5でゲームを作ろう(第12回)」に掲載された「横スクロールジャンプアクションゲーム」です。
・・・連載は最終回。
右の画像をクリックするとゲームページを表示します。
ゲームは、プレイヤーをジャンプさせて、障害物(落とし穴、壁)を避けて、できるだけ走り続けるだけです。
ただし、飛んで来る鳥にぶつかってもゲームオーバーとなるので注意して下さい。
ジャンプ操作には、PCなら「↑」キーを押すか、マウスをクリックします。タブレット端末ならタッチします。
いずれかにぶつかるとゲームオーバーとなり、スコアが表示されます。
再プレイは、Enterキーを押すか、「OK」ボタンをクリックします。
・・・横スクロールのスピードが速くて難易度が高いかも知れません。
ゲームで使用している画像は以下の10種類です。・・・例によって紙面を参考に、GIMPで適当に制作したので、絵心のある方は作り直して下さい。
ソースはこちら。
<!DOCTYPE html>
<html manifest="jump-action.appcache">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=800">
<title>Jump action</title>
<script type="text/javascript" src="jump-action.js">
</script>
<style>
* {
margin: 0;
padding: 0;
}
.layer {
position: absolute;
background-color: transparent;
left: 0; top: 0;
}
#msg {
background-color: rgba(255,255,255,0.7);
color: black;
font-size: 11px; padding: 5px;
width: 80px; height: 20px;
left: 10px; top: 10px;
}
#foot {
font-size: 11px; padding: 8px;
}
</style>
</head>
<body>
<div id="msg" class="layer">SCORE:0</div>
<canvas id="stageCV" class="layer" width="600" height="400"></canvas>
<canvas id="spriteCV" class="layer" width="80" height="80"></canvas>
<div style="clear:both; height:410px"></div>
<p id="foot">カーソル[↑]または画面クリックでジャンプ。</p>
</body>
</html>
// 定数の宣言
var SCR_W = 640; // 画面の幅
var SCR_H = 400; // 画面の高さ
var CHR_W = 80; // キャラクターの大きさ
var FPS_INTERVAL = 1000 / 10; // FPS
// ステージのパターン(7種類)
var stagePattern = [
[ // 平らな土地
[2,2,2,2,3],
[5,2,2,2,3],
[6,2,2,2,3],
[7,2,2,2,3],
[2,2,2,2,3]
],[ // 穴あり
[2,2,2,2,3],
[5,2,2,2,3],
[6,2,2,2,3],
[7,2,2,2,2],
[2,2,2,2,3]
],[ // 凸あり
[2,2,2,2,3],
[2,2,2,2,3],
[2,2,2,3,4],
[2,2,2,2,3],
[2,2,2,2,3]
],[ // 穴あり(2)
[2,2,2,2,3],
[2,2,2,2,3],
[2,5,2,2,3],
[2,6,2,2,2],
[2,7,2,2,3]
],[ // 凸あり(2)
[2,2,2,2,3],
[2,2,2,2,3],
[5,2,2,2,3],
[7,2,2,3,4]
],[ // 鳥あり。8=鳥
[2,2,2,2,3],
[5,8,2,2,3],
[7,2,2,2,3],
[2,2,2,2,3]
],[ // 雲だけ
[2,5,2,2,3],
[2,7,2,2,3]
]
];
// 各キャラクターが障害物かどうか判定するための配列
var IS_OBSTACLE = [ true, true, false, true, true, false, false, false, true];
// グローバル変数の宣言
var xStage, xSprite, xOff; // 描画用コンテキスト
var offCanvas; // オフスクリーン用
var images = []; // 読み込んだ画像の一覧
// フレーム描画用
var frame = { time:0, index:0, id:0 };
var stage = []; // ステージデータ
var plx = 1, ply = 2; // プレイヤーのX、Y座標
var score = 0; // スコア
var isGameOver = false; // ゲームオーバーかどうか
var isJump = false; // ジャンプ中かどうか
// HTMLの初期化処理
window.onload = function () {
// レイヤーの重ね合わせを考慮(z-index)
$("stageCV").style.zIndex = 10;
$("spriteCV").style.zIndex = 11;
$("msg").style.zIndex = 12;
// オフスクリーン用のキャンバスを作成
offCanvas = document.createElement("canvas");
offCanvas.width = SCR_W;
offCanvas.height = SCR_H;
// 描画コンテキストの取得
xStage = $("stageCV").getContext("2d");
xSprite = $("spriteCV").getContext("2d");
xOff = offCanvas.getContext("2d");
// イベントハンドラの設定
$("stageCV").onmousedown = touchHandler;
$("stageCV").ontouchstart = touchHandler;
window.onkeydown = function (e) {
if (e.keyCode == 38) isJump = true;
}
// 画像の読み込み
loadImages(initGame);
};
// 画面をタッチまたはクリックしたときに呼ばれる
function touchHandler(e) {
e.preventDefault();
isJump = true;
}
// 連番画像の読み込み
function loadImages(callback) {
showMessage("画像の読込");
var num = 0, imCount = 10;;
for (var i = 0; i <imCount; i++) {
var im = new Image();
im.src = "img/" + i + ".png";
im.onload = function () {
num++;
if (num >= imCount) callback();
};
images[i] = im;
}
}
// メッセージ表示用
function showMessage(s) { $("msg").innerHTML = s; }
// メッセージの初期化処理
function initGame() {
showMessage("SCORE:0");
ply = 2; plx = 1;
score = 0;
isGameOver = false;
stage = [];
createStage();
frame.index = 0;
if (frame.id == 0) redraw();
}
// ステージを生成する
function createStage() {
// 平らな土地を3個並べる
var block = stagePattern[0];
for (var i = 0; i < 3; i++) {
for (var j = 0; j < block.length; j++) {
stage.push(block[j]);
}
}
// ランダムにパターンを100個配置
for (var i = 0; i < 100; i++) {
var block = randomA(stagePattern);
for (var j = 0; j < block.length; j++) {
stage.push(block[j]);
}
}
}
// 再描画関数
function redraw() {
var t = Date.now();
if (t - frame.time >= FPS_INTERVAL) {
frame.time = t;
if (onFrame(frame.index)) frame.index++;
}
frame.id = requestAnimationFrame(redraw);
}
// 毎フレーム実行される関数
function onFrame(index) {
if (isGameOver) {
alert("Game Over!!\nSCORE:" + score);
initGame();
return false;
}
movePlayer(index);
drawChar(index);
drawStage(index);
showMessage("SCORE:" + (++score));
return true;
}
// プレイヤーの移動に関する処理
function movePlayer(index) {
// プレイヤーの直下の座標を確認
var isObs = IS_OBSTACLE[stage[index+plx][ply+1]];
// ジャンプする?
if (isJump) {
isJump = false;
if (isObs && ply >= 2) {
ply -= 2;
}
}
// 下に落ちる?
if (!isObs) {
ply++;
if (ply > 4) ply =4;
}
// ぶつかった?
var ch = stage[index + plx][ply];
if (IS_OBSTACLE[ch]) {
isGameOver = true;
}
// 最後まで走ったら新たにステージを追加する
if (index >= stage.length - 8) {
createStage();
}
}
// ステージの描画処理
function drawStage(index) {
// 新たにステージを描画する
var drawStage = function (x, y) {
var i = stage[index + x][y];
var yy = y * CHR_W;
var xx = x * CHR_W;
xOff.drawImage(images[i], xx, yy);
};
if (index == 0) {
// オフスクリーンにステージを描く
for (var y = 0; y < 5; y++) {
for (var x = 0; x < 8; x++) {
drawStage(x, y);
}
}
} else {
// 再利用する領域をコピー
xOff.drawImage(offCanvas, CHR_W, 0, SCR_W - CHR_W, SCR_H, 0, 0, SCR_W - CHR_W, SCR_H);
//スクロール後の新しい領域を描画
for (var y = 0; y < 5; y++) {
drawStage(7, y);
}
}
// オフスクリーンを画面に反映させる
xStage.drawImage(offCanvas, 0, 0);
}
// キャラクターを描画する
function drawChar(index) {
$("spriteCV").style.top = (ply * CHR_W) + "px";
$("spriteCV").style.left = (plx * CHR_W) + "px";
var char_anime = index % 2;
if (isGameOver) char_anime = 9;
xSprite.clearRect(0,0,CHR_W,CHR_W);
xSprite.drawImage(images[char_anime], 0, 0);
}
function random(v) {
return Math.floor(Math.random() * v); }
function randomA(ary) {
return ary[random(ary.length)]; }
function $(id) {
return document.getElementById(id); }