ゲーム用HTML5 Canvasの力を解き放つ方法





HTML5互換ブラウザとWindows 8 MetroのHTML5プラットフォームは、現在、最新のゲーム開発の重要な候補です。



キャンバスのおかげで、ゲームのコンテンツを表示できるハードウェアアクセラレーションサーフェスにアクセスでき、いくつかのトリックを使用して、最大60フレーム/秒の優れたレンダリングパフォーマンスを実現できます。 この連続性はゲームでは非常に重要です。ゲーム(アニメーション)が滑らかになるほど、プレイヤーの感覚が良くなるからです。



この記事の目的は、HTML5 Canvasを最大限に活用するためのヒントを提供することです。 この記事は2つの主要な部分で構成されています[最初に読む]。 David Roussetが間もなく2番目のパートを公開します。



記事では、同じ例で重要なアイデアを示します。これは、フランスのTechDays 2012でCoding4Funセッション用に作成した2Dトンネルの効果です。





私は、コモドAMIGAのコードによってこの効果を書くことに触発されました。これは、私が80年代にデモシーンの若い作家だったときに書いたものです:)。 現在、オリジナルのコードは68000アセンブラーのみに基づいていましたが、 キャンバスJavascriptのみを使用しています





JSFiddleの例 (テクスチャがコードに縫い付けられていることに注意してください)。



完全なコードは、 http//www.catuhe.com/msdn/canvas/tunnel.zipY. Narodにコピー)からダウンロードできます。



この記事の目的は、トンネルのプログラミング方法ではなく、既存のコードを取得して最適化してリアルタイムパフォーマンスを向上させる方法を説明することです。



非表示のキャンバスを使用して画像データを読み取る



最初に話したいのは、キャンバスを使用して画像データの読み取りを最適化する方法です。 実際、ほぼすべてのゲームで、スプライトまたは背景画像のグラフィックスが必要です。 Canvasには、画像を描画するための非常に便利なメソッドdrawImageがあります。 この関数は、レンダリングのソース領域と宛先領域を指定できるため、キャンバス要素にスプライトを出力するために使用できます。



しかし、時にはこれで十分ではありません。 たとえば、元の画像にいくつかの効果を適用したい場合。 または、元の画像が単なる画像ではなく、ゲームのより複雑なリソース(たとえば、データを読み取る必要があるマップ)である場合。



このような場合、内部画像データにアクセスする必要があります。 ただし、 イメージタグはコンテンツを読み取る方法を提供しません。 そして、ここでキャンバスが助けになります!



本質的に、画像の内容を読む必要があるたびに、非表示の(表示されていない)キャンバスを使用できます。 重要なアイデアは、画像をアップロードすることです。画像をアップロードしたら、DOMに挿入されていないキャンバス要素に画像を表示するだけです。 これで、対応するキャンバスピクセル(非常に簡単です)を介して、ソースイメージの[コピー]ピクセルにアクセスできます。



この手法のコードは次のとおりです(トンネルのテクスチャを読み取るために2Dトンネル効果で使用されます)。



var loadTexture = function (name, then) { var texture = new Image(); var textureData; var textureWidth; var textureHeight; var result = {}; // on load texture.addEventListener('load', function () { var textureCanvas = document.createElement('canvas'); // off-screen canvas // Setting the canvas to right size textureCanvas.width = this.width; // <-- "this" is the image textureCanvas.height = this.height; result.width = this.width; result.height = this.height; var textureContext = textureCanvas.getContext('2d'); textureContext.drawImage(this, 0, 0); result.data = textureContext.getImageData(0, 0, this.width, this.height).data; then(); }, false); // Loading texture.src = name; return result; };
      
      







このコードを使用する場合、テクスチャの読み込みは非同期で行われることに注意してください。コードの操作を続行するには、 thenパラメーターに関数を渡す必要があります。



 // Texture var texture = loadTexture("soft.png", function () { // Launching the render QueueNewFrame(); });
      
      







ハードウェアスケーリングの使用



最新のブラウザとWindows 8は、キャンバスハードウェアアクセラレーションをサポートしています。 これは、特に、 GPUを使用してキャンバス内のコンテンツをスケーリングできることを意味します。



2Dトンネル効果の場合、アルゴリズムはキャンバスの各ピクセルを処理する必要があります。 たとえば、サイズが1024x768のキャンバスの場合、786432ピクセルを処理する必要があります。 ディスプレイの連続性を確保するには、これを毎秒60回実行する必要があります。これは、毎秒47185920ピクセルの処理に相当します。



明らかに、ピクセル数の削減に役立つソリューションは、顕著なパフォーマンスの向上につながります。



繰り返しますが、canvasはそのようなツールを提供します! 次のコードは、ハードウェアアクセラレーションを使用して、内部キャンバスバッファーをDOMオブジェクトの外部サイズにスケーリングする方法を示しています。



 // Setting hardware scaling canvas.width = 300; canvas.style.width = window.innerWidth + 'px'; canvas.height = 200; canvas.style.height = window.innerHeight + 'px';
      
      







DOMオブジェクトのサイズ( canvas.style.widthcanvas.style.height )とキャンバスキャンバスバッファーのサイズ( canvas.widthcanvas.height )の違いに注意してください。



2つのサイズに違いがある場合、ハードウェア機能を使用して作業バッファーをスケーリングします-この場合、これは素晴らしい機能です:低解像度で作業し、GPUに結果をスケーリングさせてDOMオブジェクトを埋めることができます(素晴らしいフリーブラーフィルターを使用して結果を滑らかにします) )



この例では、レンダリングは300x200の領域で行われ、GPUはウィンドウのサイズに合わせてスケーリングされます。



この機能は、最新のすべてのブラウザーで広くサポートされているため、信頼できます。



レンダリングサイクルの最適化



ゲームを開発するとき、おそらくゲームのすべてのコンポーネント(背景、スプライト、ポイントなど)を描画するレンダリングサイクルが必要です。 このサイクルはコードの狭い部分であり、ゲームを高速かつスムーズに実行できるように、できる限り最適化する必要があります。



RequestAnimationFrame



HTML5に付属した興味深い機能の1つは、 window.requestAnimationFrame関数です。 window.setIntervalを使用して(1000/16)ミリ秒ごとに描画サイクルを呼び出すタイマーを作成する代わりに(大切な60fpsに到達するため)、 requestAnimationFrameを使用してこの責任をブラウザーに委任できます 。 このメソッドの呼び出しは、グラフィカル表現を更新するために、ブラウザーができるだけ早くコードを呼び出すようにすることを示しています。



ブラウザは、レンダリングスケジュール内にリクエストを含め、レンダリングとアニメーションコード(CSS、トランジションなど)と同期します。 このソリューションは、ウィンドウが表示されていない場合(最小化、完全にブロックされているなど)にはコードが呼び出されないという事実からも興味深いものです。



これにより、ブラウザは並列レンダリングを最適化できるため(たとえば、レンダリングサイクルが遅すぎる場合)、スムーズなアニメーションを生成できます。



コードは非常に明白です(ブラウザのプレフィックスの使用に注意してください):



 var intervalID = -1; var QueueNewFrame = function () { if (window.requestAnimationFrame) window.requestAnimationFrame(renderingLoop); else if (window.msRequestAnimationFrame) window.msRequestAnimationFrame(renderingLoop); else if (window.webkitRequestAnimationFrame) window.webkitRequestAnimationFrame(renderingLoop); else if (window.mozRequestAnimationFrame) window.mozRequestAnimationFrame(renderingLoop); else if (window.oRequestAnimationFrame) window.oRequestAnimationFrame(renderingLoop); else { QueueNewFrame = function () { }; intervalID = window.setInterval(renderingLoop, 16.7); } };
      
      





この関数を使用するには、レンダリングサイクルの終わりに次のフレームをサブスクライブするように呼び出すだけです。



 var renderingLoop = function () { ... QueueNewFrame(); };
      
      







DOM(ドキュメントオブジェクトモデル)へのアクセス



レンダリングサイクルを最適化するには、少なくとも1つのゴールデンルールを使用する必要があります。DOMに戻らないでください。 この場所では最新のブラウザが特に最適化されていますが、DOMオブジェクトのプロパティの読み取りは、レンダリングサイクルを高速化するには遅すぎます。



たとえば、私のコードでは、Internet Explorer 10プロファイラー( F12開発者ツールで利用可能 )を使用しましたが、結果は明らかです。



パフォーマンスDOM



ご覧のとおり、レンダリングループでは、キャンバスの幅と高さにアクセスするのに時間がかかります。



元のコードは次のようになりました。

 var renderingLoop = function () { for (var y = -canvas.height / 2; y < canvas.height / 2; y++) { for (var x = -canvas.width / 2; x < canvas.width / 2; x++) { ... } } };
      
      







canvas.widthおよびcanvas.heightのプロパティを、事前定義されたデフォルト値を持つ2つの変数に置き換えることができます。



 var renderingLoop = function () { var index = 0; for (var y = -<b>canvasHeight</b> / 2; y < <b>canvasHeight</b> / 2; y++) { for (var x = -<b>canvasWidth</b> / 2; x < <b>canvasWidth</b> / 2; x++) { ... } } };
      
      







シンプルでしょ? 理解するのは簡単ではないかもしれませんが、私を信じて、試してみる価値はあります!



予備計算



プロファイラーによると、 Math.atan2関数はやや遅いです。 実際、この操作はCPU内に配線されていないため、JavaScriptランタイムは結果を計算するためにいくつかの操作を行う必要があります。



[翻訳:技術的な観点から、特定のJSランタイムの実装はfpatanハードウェア命令に依存していると想定できますが、これを事前に保証することはできません。 数学関数のECMAScript5仕様は、それらが(明らかに)近似であることを示唆しており、Sun Microsystems数学ライブラリアルゴリズム(http://www.netlib.org/fdlibm)の使用を推奨しています。 いずれにせよ、atan2関数の実装が何であれ、これにより基本的かつ高速になりません。]



パフォーマンスatan2



一般に、長時間実行されるコードの予備計算を手配できる場合、それは常に良い考えです。 ここで、レンダリングサイクルを開始する前に、 Math.atan2の値を計算します。



 // precompute arctangent var atans = []; var index = 0; for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) { for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) { atans[index++] = Math.atan2(y, x) / Math.PI; } }
      
      







その後、 atans配列レンダリングループ内で使用して、パフォーマンスを明示的に改善できます。



Math.round、Math.floor、parseIntの使用を避ける



parseIntの使用に関するポイントは、この場合に意味があります。

パフォーマンスparseInt



キャンバスを使用する場合、整数座標(xおよびy)を使用してピクセルを指す必要があります。 ただし、すべての計算は浮動小数点数を使用して行われるため、遅かれ早かれ整数に変換する必要があります。



JavaScriptは、数値を整数に変換する関数Math.roundMath.floor、さらにはparseIntを提供します。 ただし、これらの関数は追加の作業を行います(特に、範囲の確認または値が実際に数値であることの確認。通常、parseIntはパラメーターを最初に文字列に変換します!)。 そのため、レンダリングサイクルの中で、数値を変換するより高速な方法が必要です。



古いアセンブラコードを思い出して、ちょっとしたトリックを適用することにしました。parseIntを使用する代わりに、数値を0だけ右にシフトします 。ランタイムは浮動小数点数を対応するレジスタから整数に移動し、ハードウェア変換を適用します。 数値を0だけ右にシフトすると、数値は変更されずに整数値が返されます。



ソースコードは次のようなものでした:

 u = parseInt((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u);
      
      







新しいコードは次のようになります。



 u = ((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u) >> 0;
      
      







もちろん、このソリューションでは、送信された番号の正確さを確認する必要があります:)



最終結果



説明されているすべての最適化を適用すると、次のレポートが生成されます。

最終パフォーマンス



ご覧のとおり、コードは主要な機能のみを使用して最適化されています。



この元の最適化されていないトンネルから始めました。





JSFiddleの例



そして、最適化後にこの結果に到達しました。





JSFiddleの例



[transl。:これらのスクリーンショットは、fpsが切望されている60 fpsに到達しない翻訳者のコンピューターで撮影されたものですが、IE 10では約50 fpsに近い速度で回転します。



コンピューターのフレームレートを示す次の図を使用して、各最適化の貢献度を評価できます。



パフォーマンスを比較する



進む



これらの重要な点を念頭に置いて、最新のブラウザーとWindows 8用の高速でスムーズなゲームを開発する準備ができました。



All Articles