fpsは、frames per second(フレーム毎秒)の略です。1秒間に、どれだけの回数描画されるかという単になります。
PCゲームで難しいのは、マシンの性能によって1秒間に描画できる回数が違うことです。
どのスペックのマシンを切るか、あるいは、どこまで対応するか。また、低スペックのマシンで描画方式を切り替えるかなど、考えるべきことは多いです。
ファイル構成は、以下の通りです。index.htmlを開くと、4:3の比率の描画領域がブラウザ内に現れます。複数の四角を回転描画します。fpsが表示されます。クリックするごとに、停止/再開します。
ChromeやFirefoxといったモダンブラウザ用のサンプルコードです。IEには対応していません。
index.html css/main.css js-game/game.anim.js 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.anim.js"></script> <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>
/** * @license com.crocro.game v4.0.0 * (c) 2013-2018 Masakazu Yanai https://crocro.com/ * License: MIT / Date: 2018-05-06 */ 'use strict'; // ライブラリ用のオブジェクトの作成 ((o, p) => p.split('.').forEach(k => o = !o[k] ? o[k]={} : o[k])) (window, 'com.crocro.game.anim'); //------------------------------------------------------------ (function() { // ショートカットの作成 const game = window.com.crocro.game; const _t = game.anim; //------------------------------------------------------------ // 変数の初期化 _t.lowCpuMode = false; // 低CPUモード _t.frmRt = 30; // 低CPUモード時のフレームレート //------------------------------------------------------------ // アニメーション実行/停止用関数 _t.rqstAnmFrm = function(cb) { // 低CPUモード if (_t.lowCpuMode) { const id = window.setTimeout(cb, 1000 / _t.frmRt); return id; } // 通常モード const id = ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(cb) { return window.setTimeout(cb, 1000 / 60); })(cb); return id; }; _t.cnclAnmFrm = function(id) { // 低CPUモード if (_t.lowCpuMode) { window.clearTimeout(id); return; } // 通常モード (window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || window.clearTimeout)(id) }; //------------------------------------------------------------ // アニメーション用変数 _t.anmId = null; _t.updtArr = {default: [], static: []}; // defaultは名前無しで追加削除可能。staticは削除しないアニメ用の配列。 // 各配列の要素は {nm: 名前, fnc: 関数} _t.tm = {sum: 0, old: null, now: 0, dif: 0}; _t.flgStop = false; // 停止フラグ //------------------------------------------------------------ // アニメーションの開始 _t.strt = function() { // 変数の初期化 _t.flgStop = false; _t.tm.old = new Date(); // アニメーション ループ const anmFnc = function() { _t.updt(); if (_t.flgStop) {return} _t.anmId = _t.rqstAnmFrm(anmFnc); }; anmFnc(); }; //------------------------------------------------------------ // アニメーションの停止 _t.stop = function() { if (_t.anmId === null) {return} _t.cnclAnmFrm(_t.anmId); _t.flgStop = true; }; //------------------------------------------------------------ // アニメーションの更新 _t.updt = function() { // 差分時間と経過時間を計算 _t.tm.now = new Date(); _t.tm.dif = _t.tm.old == null ? 0 : (_t.tm.now - _t.tm.old); _t.tm.sum += _t.tm.dif; _t.tm.old = _t.tm.now; // 更新配列を全て実行 for (let key in _t.updtArr) { _t.updtArr[key].forEach(x => {x.fnc(_t.tm)}); } }; //------------------------------------------------------------ // アニメーションの追加 _t.add = function(nm, fnc, trgt) { const arr = _t.updtArr[trgt === undefined ? 'default' : trgt]; if (arr === undefined) {arr[trgt] = []} // 配列の作成 arr.push({nm: nm, fnc: fnc}); }; // アニメーションの削除 _t.rmv = function(nm, trgt) { const arr = _t.updtArr[trgt === undefined ? 'default' : trgt]; if (arr === undefined) {return} for (let i = arr.length - 1; i >= 0; i --) { if (nm == arr[i].nm) {arr.splice(i, 1)} } }; // アニメーションの全削除 _t.rmvAll = function(trgt) { const arr = _t.updtArr[trgt === undefined ? 'default' : trgt]; if (arr === undefined) {return} arr.splice(0, arr.length); }; })();
//------------------------------------------------------------ // Xorshift _t.Xors = function(n) { let x, y, z, w; // シード this.seed = function(n) { x = 123456789; y = 362436069; z = 521288629; w = 88675123; if (typeof n === "number") {w = n;} } // ランダム this.rnd = function() { const t = x ^ (x << 11); x = y; y = z; z = w; return w = (w^(w>>19))^(t^(t>>8)); } // 初回実行 this.seed(n); };
/** * @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 layerFps = 2; const layerMax = 3; // キャンバス情報とキャンバス配列の初期化 const cnvsInf = new game.canvas.CnvsInf({n: layerMax, bg: '#ffc'}); const cnvsArr = game.canvas.initCnvsArr(cnvsInf); // 画面の初期化 game.canvas.clearCnvsArr(cnvsArr); // キャンバスのクリア game.view.init(cnvsInf); // 表示の初期化 // タップ位置の描画 game.view.addTap('tapAndDraw', null, (x, y) => { // アニメーション停止/再開 game.anim.flgStop = ! game.anim.flgStop; if (! game.anim.flgStop) {game.anim.strt()} }); //------------------------------------------------------------ // アニメーション const xors = new game.core.Xors(); const w = cnvsInf.w; const h = cnvsInf.h; const fntSz = 12; // アニメーションの登録 描画領域のクリア game.anim.add('clearCnvs', tm => { const cntx = cnvsArr[layerDraw].cntx; cntx.clearRect(0, 0, w, h); }); // アニメーションの登録 fpsの描画 let dtOld = +new Date(); let fpsArrIndx = 0; const fpsArr = []; const fpsArrMax = 20; game.anim.add('drawFps', tm => { // 変数の初期化 const dtNow = +new Date(); const dtDiff = dtNow - dtOld; const fps = 1000 / dtDiff | 0; fpsArr[fpsArrIndx] = fps; const cntx = cnvsArr[layerFps].cntx; cntx.font = `${fntSz}px sans-serif`; cntx.textBaseline = 'middle'; cntx.strokeStyle = 'white'; cntx.lineWidth = 4; cntx.clearRect(0, 0, w, h); // fpsの描画 for (let i = 0; i < fpsArr.length; i ++) { // 変数の初期化 const txt = 'fps : ' + fpsArr[i]; const x = fntSz / 2; const y = fntSz * (i + 1) * 1.5; cntx.fillStyle = (i === fpsArrIndx) ? 'red' : 'black'; // 描画 cntx.strokeText(txt, x, y); cntx.fillText(txt, x, y); } // 値の更新 dtOld = dtNow; fpsArrIndx = (fpsArrIndx + 1) % fpsArrMax; }); // アニメーションの生成 const genAnmObj = function() { // 変数の初期化 const anmId = 'obj' + (+new Date()); const strt = game.anim.tm.sum; let endFlg = false; // 最後に1回描画するため const anmLen = 1000; const x = xors.rnd() % w; const y = xors.rnd() % h; const sqLen = 100; const colorArr = ['faa', 'afa', 'aaf', 'ff8', 'f8f', '8ff']; const color = colorArr[xors.rnd() % colorArr.length]; const cntx = cnvsArr[layerDraw].cntx; // アニメーションの登録 game.anim.add(anmId, tm => { // 終了管理 if (endFlg) { game.anim.rmv(anmId); // アニメ除去 return; } if (tm.sum >= strt + anmLen) {endFlg = true} // 変数の初期化 const rate = game.core.minMax(0, (tm.sum - strt) / anmLen, 1); // 描画処理 cntx.save(); cntx.fillStyle = `#${color}`; cntx.globalAlpha = Math.sin(Math.PI * rate); cntx.translate(x, y); cntx.rotate(2 * Math.PI * rate); cntx.fillRect(- sqLen / 2, - sqLen / 2, sqLen, sqLen); cntx.restore(); }); }; // アニメーションの登録 (function() { // 変数の初期化 const strt = game.anim.tm.sum; let last = strt; const intrvl = 50; game.anim.add('mngObj', tm => { const now = tm.sum; if (((now - strt) / intrvl | 0) > ((last - strt) / intrvl | 0)) { genAnmObj(); last = now; } }); })(); // アニメーション開始 game.anim.strt(); });