画面サイズは、毎回頭を悩まされる問題です。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; }); });