画面の作り方として、どういう画面を選ぶかという話をします。
画面サイズは、毎回頭を悩まされる問題です。PCとスマホ両対応にしようとすると、かなり難易度が高いです。
自分の中で決定版はなく、どちらかを切り捨てるといきなり楽になります。
初心者のうちは、開発画面と同じ、PC向けのゲームを作るとよいのではないかと思います。なんなら、画面サイズを変えられなくするのも、一つの手だと思います。
HTML5が登場して、ピクセル描画が行えるCanvasと、音声を複数制御して鳴らせるSoundが加わりました。そのおかげで、HTMLとJavaScriptを使って、ゲームを一通り作れる環境が整いました。
それ以降、私はこれらの技術を利用して、色々とゲームを開発しています。簡単なゲームなら、これだけの技術で完成させることができます。
Canvasのリファレンスとしては、以下のサイトが非常によくまとまっています。
サンプルも豊富なので、一通り読めばCanvasの2D描画はマスターできます。
ファイル構成は、以下の通りです。index.htmlを開くと、4:3の比率の描画領域がブラウザ内に現れます。クリックするごとに、前のクリック位置から線を引くことができます。
ChromeやFirefoxといったモダンブラウザ用のサンプルコードです。IEには対応していません。
index.html css/main.css js-game/game.canvas.js js-game/game.core.js js-game/game.view.js js-lib/jquery-3.3.1.min.js js-main/main.js
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=0" />
<title></title>
<link rel="stylesheet" href="css/main.css">
<!-- /* jQuery, other... */ -->
<script>
(function() {
// Electron or Browser
const pJQ = './js-lib/jquery-3.3.1.min.js';
if (window.require) {return window.jQuery = window.$ = require(pJQ)}
document.writeln(`<script src="${pJQ}"></${``}script>`);
})();
</script>
<!-- /* Lib. Game */ -->
<script src="js-game/game.canvas.js"></script>
<script src="js-game/game.core.js"></script>
<script src="js-game/game.view.js"></script>
<!-- /* Lib. Main */ -->
<!-- /* Start Main */ -->
<script src="js-main/main.js"></script>
</head>
<body>
<div id="app" class="app"></div>
</body>
</html>
html, body, .app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
position: absolute;
}
body {
background: #000;
}
#app canvas {
image-rendering: pixelated;
position: absolute;
}
* {
-webkit-tap-highlight-color:rgba(0,0,0,0);
}
/**
* @license com.crocro.game v4.0.0
* (c) 2013-2018 Masakazu Yanai https://crocro.com/
* License: MIT / Date: 2018-03-15
*/
'use strict';
// ライブラリ用のオブジェクトの作成
((o, p) => p.split('.').forEach(k => o = !o[k] ? o[k]={} : o[k]))
(window, 'com.crocro.game.canvas');
//------------------------------------------------------------
(function() {
// ショートカットの作成
const game = window.com.crocro.game;
const _t = game.canvas;
//------------------------------------------------------------
// キャンバスの生成
_t.genCnvs = function(w, h) {
const $cnvs = $(`<canvas width="${w}" height="${h}">`);
const cnvs = $cnvs[0];
const cntx = cnvs.getContext('2d');
return {$cnvs: $cnvs, cnvs: cnvs, cntx: cntx, w: w, h: h};
};
//------------------------------------------------------------
// キャンバス情報
_t.CnvsInf = function(o) {
// デフォルト値
this.w = 512; // 16*8*4
this.h = 384; // 16*8*3
this.n = 2; // レイヤー枚数
this.bg = '#000'; // 背景色
// 引数の反映
for (let k in o) {this[k] = o[k]}
};
//------------------------------------------------------------
// キャンバス配列の初期化
// arguments
// cnvsInf - _t.CnvsInf
// return
// cnvsArr = [{$cnvs, cnvs, cntx, w, h, bg}, ...]
_t.initCnvsArr = function(cnvsInf) {
// 変数の初期化
const cnvsArr = [];
const $app = $('#app');
// レイヤー枚数のキャンバスを初期化
for (let i = 0; i < cnvsInf.n; i ++) {
const c = _t.genCnvs(cnvsInf.w, cnvsInf.h);
c.bg = cnvsInf.bg; // 背景色を追加
c.cntx.imageSmoothingEnabled = false; // ドット絵用
$app.append(c.cnvs); // DOMに追加
cnvsArr.push(c); // 配列に追加
}
return cnvsArr;
};
//------------------------------------------------------------
// キャンバス配列のクリア
// 一番下は塗り潰し、それ以外は削除。
_t.clearCnvsArr = function(cnvsArr) {
cnvsArr.forEach((c, i) => {
if (i == 0) {
c.cntx.fillStyle = c.bg;
c.cntx.fillRect(0, 0, c.w, c.h);
} else {
c.cntx.clearRect(0, 0, c.w, c.h);
}
});
};
})();
/**
* @license com.crocro.game v4.0.0
* (c) 2013-2018 Masakazu Yanai https://crocro.com/
* License: MIT / Date: 2018-03-15
*/
'use strict';
// ライブラリ用のオブジェクトの作成
((o, p) => p.split('.').forEach(k => o = !o[k] ? o[k]={} : o[k]))
(window, 'com.crocro.game.core');
//------------------------------------------------------------
(function() {
// ショートカットの作成
const game = window.com.crocro.game;
const _t = game.core;
//------------------------------------------------------------
// 変数の初期化
_t.ua = {};
_t.ua.pc = ! window.navigator.userAgent.match(
/iphone|ipod|ipad|android|windows Phone/i);
//------------------------------------------------------------
// 範囲内か判定
// cX - check X
// cY - check Y
// x, y, w, h - 矩形領域
_t.inRng = function(cX, cY, x, y, w, h) {
if (cX < x || x + w <= cX) {return false}
if (cY < y || y + h <= cY) {return false}
return true;
};
// 範囲内か判定 - 矩形
_t.inRngRct = function(cX, cY, rct) {
if (cX < rct.x || rct.x + rct.w <= cX) {return false}
if (cY < rct.y || rct.y + rct.h <= cY) {return false}
return true;
};
})();
/**
* @license com.crocro.game v4.0.0
* (c) 2013-2018 Masakazu Yanai https://crocro.com/
* License: MIT / Date: 2018-03-15
*/
'use strict';
// ライブラリ用のオブジェクトの作成
((o, p) => p.split('.').forEach(k => o = !o[k] ? o[k]={} : o[k]))
(window, 'com.crocro.game.view');
//------------------------------------------------------------
(function() {
// ショートカットの作成
const game = window.com.crocro.game;
const _t = game.view;
// 変数の初期化
_t.cnvsInf = null; // キャンバス情報
_t.$idApp = null; // $('#app')
_t.$clsApp = null; // $('.app')
_t.appRct = {}; // アプリ矩形
_t.tapArr = []; // タップ配列 [{id, fnc, rct: {x, y, w, h}}, ...]
//------------------------------------------------------------
// ビューの初期化
_t.init = function(cnvsInf) {
// 変数の初期化
_t.cnvsInf = cnvsInf;
_t.$idApp = $('#app');
_t.$clsApp = $('.app');
// 実行初期化
_t.initTap(); // タップの初期化
_t.autoResize(); // 画面サイズの自動変更
};
//------------------------------------------------------------
// アプリ矩形の計算
_t.calcAppRct = function() {
// Windowサイズの取得
const winW = window.innerWidth;
const winH = window.innerHeight;
const cW = _t.cnvsInf.w;
const cH = _t.cnvsInf.h;
// App矩形の計算
_t.appRct.w = Math.min(winW, winH * cW / cH) | 0;
_t.appRct.h = Math.min(winH, winW * cH / cW) | 0;
_t.appRct.x = (winW - _t.appRct.w) / 2 | 0;
_t.appRct.y = (winH - _t.appRct.h) / 2 | 0;
};
//------------------------------------------------------------
// アプリを画面にフィット
_t.fitApp = function() {
// キャンバスのサイズを調整
_t.$idApp.find('canvas')
.width(_t.appRct.w).height(_t.appRct.h);
// .appのサイズと位置を調整
_t.$clsApp
.css({left: _t.appRct.x, top: _t.appRct.y})
.width(_t.appRct.w).height(_t.appRct.h);
};
//------------------------------------------------------------
// 画面サイズの自動変更
_t.autoResize = function() {
// 変数の初期化
let tmrId = null;
const fncResize = () => {
_t.calcAppRct(); // アプリ矩形の計算
_t.fitApp(); // アプリを画面にフィット
};
// 遅延付き実行(短時間の連続実行を避ける)
$(window).resize(() => {
if (tmrId !== null) {clearTimeout(tmrId)}
tmrId = setTimeout(fncResize, 50);
});
// 初回実行
fncResize();
};
//------------------------------------------------------------
//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//------------------------------------------------------------
// タップの初期化
// _t.tapArr に対する処理をタップ イベントに登録する。
_t.initTap = function() {
// タップ イベントの登録
_t.onTap((x, y) => {
// タップ配列への処理
for (let i = _t.tapArr.length - 1; i >= 0; i --) {
// 変数の初期化
const o = _t.tapArr[i];
// 矩形が存在して範囲外なら飛ばす
if (o.rct !== null && ! game.core.inRngRct(x, y, o.rct)) {continue}
// 実行と継続確認
const res = o.fnc(x, y, o); // 実行
if (res !== true) {break} // 明示的にtrueでないなら終了
}
});
};
//------------------------------------------------------------
// タップ イベントの登録
_t.onTap = function(cb) {
_t.$idApp.mousedown(e => {
// 変数の初期化
const eX = e.clientX;
const eY = e.clientY;
// 画面範囲内か確認
if (! game.core.inRngRct(eX, eY, _t.appRct)) {return}
// 変数の初期化
const cW = _t.cnvsInf.w;
const cH = _t.cnvsInf.h;
// イベント位置の計算
let eCX = ((eX - _t.appRct.x) * cW / _t.appRct.w) | 0;
let eCY = ((eY - _t.appRct.y) * cH / _t.appRct.h) | 0;
// コールバック
cb(eCX, eCY);
});
};
//------------------------------------------------------------
//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//------------------------------------------------------------
// タップの追加
// fnc - (x, y, {id, rct, fnc})
// rct が null の場合は全面
_t.addTap = function(id, rct, fnc) {
_t.tapArr.push({id: id, rct: rct, fnc: fnc});
};
// タップの削除(前方一致)
_t.delTap = function(id) {
_t.tapArr = _t.tapArr.filter(o => o.id.indexOf(id) === -1);
};
// タップの全削除
_t.delAllTap = function() {
_t.tapArr = [];
};
})();
/**
* @license com.crocro.game Sample
* (c) 2018 Masakazu Yanai https://crocro.com/
* License: MIT / Date: 2018-03-15
*/
'use strict';
// 開始
$(function() {
// ショートカットの作成
const game = window.com.crocro.game;
//------------------------------------------------------------
// レイヤー情報の初期化
const layerBg = 0;
const layerDraw = 1;
const layerMax = 2;
// キャンバス情報とキャンバス配列の初期化
const cnvsInf = new game.canvas.CnvsInf({n: layerMax, bg: '#ffc'});
const cnvsArr = game.canvas.initCnvsArr(cnvsInf);
// 画面の初期化
game.canvas.clearCnvsArr(cnvsArr); // キャンバスのクリア
game.view.init(cnvsInf); // 表示の初期化
// タップ位置の描画
let oldX, oldY;
game.view.addTap('tapAndDraw', null, (x, y) => {
// 描画の設定
const cntx = cnvsArr[layerDraw].cntx;
cntx.fillStyle = 'red';
cntx.strokeStyle = 'red';
cntx.lineWidth = 2;
// 描画
if (oldX === undefined) {
// 初回時処理 - 初期位置描画
cntx.fillRect(x - 5, y - 5, 10, 10);
} else {
// 2回目以降 - 線を引く
cntx.beginPath();
cntx.moveTo(oldX, oldY);
cntx.lineTo(x, y);
cntx.stroke();
}
// タップ位置の記録
oldX = x;
oldY = y;
});
});