HTML5のCanvasを試す
今更ですが、HTML5のCanvasを試してみたいと思います。
次期HTMLの規格であるHTML5では、Canvasというものを使って直接図形が描画できるようになりました。
描画速度は、というとそれほど速いわけではないですが、描画の多いものでなければ十分な速度で動くゲームも作れたりします。
(HTML5関連の詳しい資料はHTML5.JP - 次世代HTML標準 HTML5情報サイトへ。)
Canvasのテストとして何を作ろうかと思っているときに、iPhoneアプリのQuick Graphを見つけました。
これは式を入力すると、下画像のようなグラフが簡単に描画できるというアプリです。
このアプリには、Libraryとして幾つか図形を描画できる式が入っているのですが、これが結構面白い形で感心したので、これらをCanvasで描画してみます。
(実行ボタンを押すと動作を開始します。表示されない場合、一度ブラウザを更新してみてください。)
そもそもCanvasに対応していないブラウザもあるようなので、環境によっては上のサンプルは動作しないかもしれません。とりあえず、FirefoxとIEでは動作を確認しました。
(動作が重過ぎる場合、パスの描画をOFFにするとマシになると思います。)
本来ならば(当然と言うべきか)IEはCanvasには対応していません。
では、なぜこのサンプルがIEで動作するかというと、Google謹製のJavaScriptライブラリ「ExplorerCanvas」を使用しているためです。
このExplorerCanvasはCanvasの描画をエミュレートするもので、これを使用すればCanvasに対応していないIEでもCanvasで描画させることができるのです。素晴らしい!さすがGoogle大先生!
しかし、IEはJavaScriptの動作自体が遅いので、エミュレートさせたCanvasの描画はとても遅くなっています。
(追記:コメント欄でご指摘いただきました。JavaScriptの動作が遅いせいと言う以上に、描画にVMLを用いているから速度がでないのだそうです。)
上のサンプルでも5fps前後しかでないようです。
(また、今のところCanvasの動作を完全にエミュレートできるわけではなく、一部正しく動作しないコードもあるようです。)
まだちらっとしか触ってないCanvasですが、使いやすい感じですね。
JavaScriptでのゲーム製作がよりやりやすくなったんじゃないかと思います。
一応、今回のサンプルのコードを以下においておきます。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Language" content="ja" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <title>Quick Graph Test</title> <!--[if IE]><script type="text/javascript" src="excanvas.compiled.js"></script><![endif]--> <script type="text/javascript"> // 画面サイズ var CWIDTH = 400; var CHEIGHT = 400; function Movable() { this.initialize.apply(this, arguments); } Movable.prototype = { initialize: function(x,y) { this.x = x; this.y = y; this.theta = 0.0; }, move: function() { var pos = this.calcPos(this.theta); this.x = pos.x; this.y = pos.y; if((this.theta += 0.015) > Math.PI * 2) this.theta = 0; }, calcPos: function(theta) { return { x:0, y:0 }; }, draw: function(ctx) { ctx.beginPath(); ctx.fillStyle = 'rgb(255, 0, 0)'; ctx.arc(this.x, this.y, Movable.RADIUS, 0, Math.PI*2, false); ctx.fill(); }, drawPath: function(ctx) { var pos = this.calcPos(0); ctx.moveTo(pos.x, pos.y); for(var i = 0; i < Math.PI * 2; i += 0.01){ pos = this.calcPos(i); ctx.lineTo(pos.x, pos.y); } ctx.stroke(); } }; Movable.RADIUS = 10; Movable.BASE_X = (CWIDTH * 0.5); Movable.BASE_Y = (CHEIGHT * 0.5); function Action1() {}; Action1.prototype = new Movable(); Action1.prototype.getFunction = function() { return "r = 3 - 6・sin(3・θ)"; } Action1.prototype.calcPos = function(theta) { var r = 3 - 6 * Math.sin(3 * theta); return { x:Movable.BASE_X + Math.cos(theta) *(r * 20), y:Movable.BASE_Y + Math.sin(theta) * (r * 20) }; } function Action2() {}; Action2.prototype = new Movable(); Action2.prototype.getFunction = function() { return "r^2 = sin(3・θ)^2+cos(0.8-r)"; } Action2.prototype.calcPos = function(theta) { var r = Math.pow(Math.sin(3 * theta),2) + Math.cos(0.8-50); return { x:Movable.BASE_X + Math.cos(theta) * (r * 50), y:Movable.BASE_Y + Math.sin(theta) * (r * 50) }; } function Action3() {}; Action3.prototype = new Movable(); Action3.prototype.getFunction = function() { return "r = (1-cos(8・θ))・(1+sin(θ))"; } Action3.prototype.calcPos = function(theta) { var r = (1 - Math.cos(8 * theta) * (1 + Math.sin(theta))); return { x:Movable.BASE_X + Math.cos(theta) *(r * 50), y:Movable.BASE_Y + Math.sin(theta) * (r * 50) }; } function Action4() {}; Action4.prototype = new Movable(); Action4.prototype.getFunction = function() { return "r = 4-4・sin(θ)"; } Action4.prototype.calcPos = function(theta) { var r = 4 - 4 * Math.sin(theta); return { x:Movable.BASE_X + Math.cos(theta) *(r * 20), y:Movable.BASE_Y + Math.sin(theta) * (r * 20) }; } var Canvas = null; var CanvasContext = null; var fpsCount = 0; var fps = 0; var movable = null; var drawPath = true; onload = function() { // canvas要素のノードオブジェクト var Canvas = document.getElementById('CanvasTest'); // canvas要素の存在チェックとCanvas未対応ブラウザの対処 if ( ! Canvas || ! Canvas.getContext ) { alert("お使いのブラウザはCanvasに対応していません。"); return false; } // 2Dコンテキストの取得 CanvasContext = Canvas.getContext('2d'); // ループスタート setInterval("mainLoop()", 1000/30); // FPS setInterval(function(){ document.getElementById("fpsText").innerHTML = fpsCount + " fps"; fpsCount = 0; }, 1000); // Movable生成 onChange(document.Form.selectBox); }; function clear(ctx) { ctx.beginPath(); ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, 0, CWIDTH, CHEIGHT); } function drawGraphBG(ctx) { ctx.moveTo(CWIDTH * 0.5, 0); ctx.lineTo(CWIDTH * 0.5, CHEIGHT); ctx.moveTo(0, CHEIGHT * 0.5); ctx.lineTo(CWIDTH, CHEIGHT * 0.5); ctx.stroke(); ctx.beginPath(); ctx.strokeRect(0, 0, CWIDTH, CHEIGHT); } function draw(ctx) { if(drawPath) movable.drawPath(ctx); movable.draw(ctx); } function move() { movable.move(); } function mainLoop() { clear(CanvasContext); move(); drawGraphBG(CanvasContext); draw(CanvasContext); fpsCount++; } // チェックボックスクリック function onCheck(checkBox) { drawPath = checkBox.checked; } // セレクトボックス変更 function onChange(selectBox) { movable = eval("new " + selectBox.options[selectBox.selectedIndex].value + "(0, 0)"); } </script> </head> <body> <canvas id="CanvasTest" width="400" height="400"></canvas> <div id = "fpsText" style="position:absolute;top:15px;left:15px;"></div> <form action="#" name="Form" style="position:absolute;top:360px;left:15px;"> <select name="selectBox" onChange="onChange(this)"> <option value="Action1">r = 3 - 6・sin(3・θ)</option> <option value="Action2">r^2 = sin(3・θ)^2+cos(0.8-r)</option> <option value="Action3">r = (1-cos(8・θ))・(1+sin(θ))</option> <option value="Action4">r = 4-4・sin(θ)</option> </select><br> <input type="checkBox" name="c1" onClick="onCheck(this)" checked>パスの描画 </form> </body> </html>