Comments
Description
Transcript
Chapter 07 キャンバスに描画する
7 Chapter キャンバスに描画する [Canvas] この章ではCanvasの使い方について説明します。また、 Canvasを使ってリアルタイムゲームと簡単なノベルシステム を作成します。 なお、Canvasのピクセル単位での処理に関し ては別途、11章のWeb Workersで説明しています。 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 Canvas とは ……………………………………………… 170 四角形の描画 …………………………………………… 174 パスの作成と描画 ……………………………………… 177 画像の描画 ……………………………………………… 182 回転、 移動など ………………………………………… 188 文字の表示 ……………………………………………… 194 Canvas を使ったリアルタイムゲームの作成 …… 200 Canvas を使ったノベルシステムの作成 ………… 208 [編集部註]本章では、カラー表現に関する記述が含まれています。本文では モノクロ画像で掲載しておりますが、下記 URL のサポートサイトにカラー画像は 掲載しておりますので、詳しくはこちらをご覧下さい。 http://www.ric.co.jp/book/contents/pdfs/897_support.pdf 一目でわかる「Canvas」のしくみ ❶ getContext(contextID) コンテキストを取得 contextID Canvas 2d clearRect(x,y,width,height) 四角形の範囲を消去 webgl fillRect(x,y,width,height) 塗り潰された四角形を描画 rect(x,y,width,height) いずれか 1 つを指定 四角形のパスを作成 moveTo(x,y) lineTo(x,y) strokeRect(x,y,width,height) 指定座標にペンを移動 ペン位置から指定座標まで直線のパスを作成 枠だけの四角形を描画 (x,y) (x,y) (x,y) width height arc(x,y,r,startA,endA,anticlockwise) beginPath() closePath() isPointInPath(x,y) 正円/円弧のパスを作成 パスの新規作成 パスを閉じる 座標点がパス内にあるか調べる anticlockwise (x,y) (x,y) r startA endA arcTo(x1,y1,x2,y2,r) bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y) quadraticCurveTo(cpx,cpy,x,y) 円弧のパスを作成 3 次ベジエ曲線のパスを作成 2 次ベジエ曲線のパスを作成 (cp1x,cpy1) (cp2x,cpy2) (cpx,cpy) r (x,y) (x,y) (x2,y2) 166 (x1,y1) stroke() strokeStyle fill() fillStyle clip() パスの枠を描画 線のスタイル パスを塗り潰す 塗りのスタイル パスをクリップ領域にする lineWidth 線幅を指定(単位はピクセル [px]) lineCap lineJoin miterLimit 線端形状を指定 連結方法を指定 角のとがり具合を指定 butt round square bevel round miter shadowBlur shadowColor shadowOffsetX shadowOffsetY createPattern(image,repetition) 影のぼかしを指定 影の色を指定 影のずれ具合(オフセット)を指定 パターンを作成 no-repeat shadowOffsetX repeat-x repeat-y repeat shadowOffsetY createLinearGradient(x0,y0,x1,y1) createRadialGradient(x0,y0,r0,x1,y1,r1) addColorStop(offset,color) 直線的なグラデーションを作成 円形グラデーションを作成 中間点の位置と色を指定 (x0,y0) (x0,y0) offset r0 r1 color 一目でわかる 「Canvas」のしくみ ① color (x1,y1) (x1,y1) translate(x,y) rotate(rad) scale(x,y) 移動 回転 拡大縮小/スケーリング rad (x,y) + 167 Chapter 7 globalAlpha 不透明度を指定 キャンバスに描画する [Canvas] globalCompositeOperation 描画モードを指定 一目でわかる「Canvas」のしくみ ❷ fillText(text,x,y,maxWidth) strokeText(text,x,y,maxWidth) mtx = measureText(text) mtx.width 指定座標に通常の文字を描画 指定座標に袋文字を描画 文字幅を持つオブジェクトを返す maxWidth width maxWidth Sample (x,y) Sample (x,y) font textAlign textBaseline 文字のフォントを CSS 形式で指定 行揃えを指定 ベースラインを指定 Sample italic bold 24px Times Sample Sample Sample Sample Sample Abcdfg 描画 Abcdfg 描画 Abcdfg 描画 Abcdfg 描画 Abcdfg 描画 Abcdfg 描画 left center right start end transform(m11,m12,m21,m22,dx,dy) drawImage(image, dx, dy) drawImage(image, dx, dy, dw, dh) drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 変形行列を追加 画像を描画 setTransform(m11,m12,m21,m22,dx,dy) 変形行列を新規に設定 m11 m21 dx m12 m22 dy 0 0 1 top hanging middle alphabetic ideographic bottom image (sx,sy) (dx,dy) sh dh sw dw save() restore() Canvas 情報を保存 Canvas 情報を復元 Canvas as strokeStyle,fillStyle,globalAlpha, lineWidth,lineCap,lineJoin,miter Limit,shadowOffsetX,shadowO ffsetY,shadowBlur,shadowColor, globalCompositeOperation,font ,textAlign,textBaseline Canvas as 保存情報 保存情報 保存情報 strokeStyle,fillStyle,globalAlpha, lineWidth,lineCap,lineJoin,miter Limit,shadowOffsetX,shadowO ffsetY,shadowBlur,shadowColor, globalCompositeOperation,font ,textAlign,textBaseline スタック 薄い文字は省略可能なパラメータ 168 (x,y) image (sx,sy) (x,y) h h w w createImageData(width,height) toDataURL(type) 空のピクセルデータを作成 Canvas 内容を URL 形式 (data:∼) に変換 image width Canvas as data:image/png;base64,ZM/PRAHS... height toDataURL(type) getContext(contextId) lineTo(x,y) lineWidth measureText(text) miterLimit moveTo(x,y) putImageData(image,x,y,sx,sy,w,h) quadraticCurveTo(cpx,cpy,x,y) rect(x,y,width,height) restore() rotate(rad) save() scale(x,y) setTransform(m11,m12,m21,m22,dx,dy) shadowBlur shadowColor shadowOffsetX shadowOffsetY stroke() strokeRect(x,y,width,height) strokeStyle strokeText(text,x,y,maxWidth) textAlign textBaseline transform(m11,m12,m21,m22,dx,dy) translate(x,y) 一目でわかる 「Canvas」のしくみ ② addColorStop(offset,color) arc(x,y,r,startA,endA,anticlockwise) arcTo(x1,y1,x2,y2,r) beginPath() bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y) clearRect(x,y,width,height) clip() closePath() createImageData(width,height) createLinearGradient(x0,y0,x1,y1) createPattern(image,repetition) createRadialGradient(x0,y0,r0,x1,y1,r1) drawImage(image, dx, dy, dw, dh) drawImage(image, dx, dy) drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) fill() fillRect(x,y,width,height) fillStyle fillText(text,x,y,maxWidth) font getImageData(x,y,w,h) globalAlpha globalCompositeOperation isPointInPath(x,y) lineCap lineJoin 薄い文字は省略可能なパラメータ 169 Chapter 7 putImageData(image,x,y,sx,sy,w,h) ピクセルデータを描画 キャンバスに描画する [Canvas] getImageData(x,y,w,h) ピクセルデータを読み出し 7.1 Canvas とは Section この章ではCanvasを使って隕石を破壊するリアルタイムゲームと、画像と文字を表示するノベルシス テムを作成します。 隕石を破壊するゲームは上から落下してくる隕石をタップしてレーザーで破壊するものです。隕石を 破壊すると爆風が広がります。ノベルゲームはあらかじめ用意されたシナリオファイルを読み込みシーン に合わせて画像と文字を表示します。 作成するリアルタイムゲームとノベルシステムの画面は図 7.1.1 のようになります。 実際にこれらのプログラムを作成する前にCanvasについて説明します。 Canvasは2D(平面)に自由にグラフィックを描くことができるものと、WebGLを使って3D(立体)を 描画するものがあります。Android 標準ブラウザではWeb GL(3D)はサポートされていないので、こ こでは2D(平面、二次元描画) のグラフィックスを使ってゲームを作成します(*1)。 2D 描画に関してはW3C のページに仕様が公開されています。 ● HTML Canvas 2D Context ● http://www.w3.org/TR/2dcontext/ Android 2.x ∼ 4で使えるメソッドとプロパティを表 7.1.1 に示します。また、パソコン版 Google ChromeとAndroid 2.x ∼ 4 および Firefoxでの Canvas 関連のメソッド/プロパティ対応に関しては 「付録 4 canvas 要素のメソッド/プロパティ比較表」 を参照してください。 *1 Firefox、Google ChromeではWebGLによる3D 描画が可能です。 170 図 7.1.1 本章で作成するリアルタイムゲームとノベルシステム スコア表示 隕石。上から回転しながら落下。 隕石を破壊 した時の爆風 砲台から発射されたレーザー タップした位置まで描画される ■ノベルシステム senario.txt #img images/waterfall.jpg ♪♬♪ 音楽 シーンに応じて 対応する画像を 表示 #bgm bgm/star.mp3 「ねじだる」の次に現れるのが「霧ヶ滝」です。 エメラルドグリーンの滝壺は非常にきれいで 濁りがありません。 #wait 7.1 Canvasとは シーンに応じて 対応する文字を 表示 (26 文字 ×5 行 ) 文字表示用に 黒の半透明の 四角形を表示 シナリオファイル 171 Chapter 7 キャンバスに描画する [Canvas] ■隕石破壊ゲーム 表 7.1.1 Android の標準ブラウザで使えるメソッドとプロパティ arc 円を描く。 arcTo 円弧を描く。 beginPath パスの作成 bezierCurveTo 3 次ベジエ曲線 clearRect 四角形の範囲を消去 clearShadow 影を消去 clip パスをクリッピング範囲にする。 closePath パスを閉じる。 createImageData ピクセルデータ領域を作成 createLinearGradient 直線的なグラデーションを作成 createPattern パターンを作成 createRadialGradient 円形グラデーションを作成 drawImage 画像を描画 fill パスを塗り潰し fillRect 四角形の範囲を塗り潰し fillStyle 塗り潰しスタイルを設定 fillText 塗り潰した文字を描画 font 文字のサイズやフォントを一括指定(CSS の font と同じ) getImageData Canvas 内のピクセルデータの取得 globalAlpha 不透明度(0 が完全な透明で 1 が完全な不透明) globalCompositeOperation 合成モード(指定可能なモードについては表 7.1.2 を参照) isPointInPath 指定した点がパス内にあるか調べる。 lineCap 線端形状 lineJoin 線端結合方法 lineTo 現在の座標から指定した座標まで直線のパスを作成 lineWidth パスの線幅 measureText 文字の横幅をピクセル数で返す。 miterLimit 線端結合時の限界値 moveTo 指定座標に移動 putImageData ピクセルデータを描画 quadraticCurveTo 2 次ベジエ曲線 rect 四角形のパスを作成 restore Canvas の状態を戻す。 rotate 回転 save Canvas の状態を保存 scale 拡大縮小/スケール設定 setTransform 新規に変形マトリクスを設定 172 shadowColor 影の色 shadowOffsetX 影の横のずれ具合(オフセット) shadowOffsetY 影の縦のずれ具合(オフセット) stroke パスを線で描画 strokeRect 線だけの四角形を描画 strokeStyle 線のスタイル strokeText 線だけの文字を描画(アウトライン文字) textAlign 行揃え textBaseline 文字のベースライン transform 現在の変形マトリクスに新たな変形を追加 translate 移動 表 7.1.2 合成モード 合成モード source-atop destination-out source-in* destination-over source-out* lighter source-over copy* destination-atop* xor destination-in* *仕様とは異なる描画結果になる (Android 2.x /標準ブラウザの場合) 本章で作成するリアルタイムゲームはCanvas の以下の機能を使って処理しています。また、ノベル システムでは以下のうち (1) (4) (7) を使用しています。 (1)画面の消去(clearRect) 7.1 Canvasとは (2)直線の描画(beginPath,strokeStyle,moveTo, lineTo) (3)円の描画(arc) (4)画像の描画(drawImage) (5)回転機能(rotate, translate) (6)文字描画(font, fillText) (7)半透明処理(globalAlpha) 以後のセクションで、これらの機能について説明していきます。 173 Chapter 7 影のぼかし具合 キャンバスに描画する [Canvas] shadowBlur 7.2 四角形の描画 Section このセクションではCanvasと描画機能について説明します。なお、Canvas 機能の説明に関する以 後のセクションではjQuery Mobileは使用していません。純粋にJavaScriptだけを使用しています。 まず、四角形を描く方法について説明します。CanvasはHTML 要素だけでは何も描画することが できません。同じグラフィック機能を扱うSVG(Scalable Vector Graphics)は要素を記述すれば描く ことができますが、CanvasではJavaScript が必須となります。 ● canvas 要素を用意する 描画するまでには手順が必要になります。最初にHTMLでcanvas 要素を用意します。widthで横 幅、heightで縦幅を指定します。CSSで指定することもできますが表示結果が異なる場合があるので HTML の width、height 属性で指定する方が確実です。また、以下の例ではJavaScript からアク セスしやすくするためにID 名 myCanvasを指定しています。 <canvas id="myCanvas" width="300" height="400"></canvas> ● Java Script 側の処理 HTML の準備はこれで終わりです(*1)。次にJavaScript 側の処理です。まず、canvas 要素にアク セスしやすくするために変数にcanvas への参照を入れておきます。 var canvasObj = document.getElementById("myCanvas"); *1 Androidでは、どの機種であってもCanvasを利用できますが、もしIE8などパソコンの古いブラウザを対象にする場合には以下 のようにフォールバック機構を利用してメッセージ等を表示することができます。 <canvas id="myCanvas" width="300" height="400"> お使いのブラウザは対象外です。 </canvas> 174 次に、使用するCanvas の 2D 機能のコンテキスト(オブジェクト)を指定します。これは以下のように ここまで来たら、ようやくCanvasに描画できるようになります。赤色の四角形を描いてみましょう。 fillStyleプロパティに塗り潰す色を指定します。色指定はCSS3と同じものを利用できます。 context.fillStyle = "red"; 塗り潰された四角形の場合は以下のようにfillRect()を使います。 context.fillRect(20, 30, 200, 250); パラメータはX 座標、Y 座標、横幅、縦幅の順番になります。四角形を描くメソッドは表 7.2.1 に示 すものがあります。いずれもパラメータは同じです。実際のプログラムはサンプル 7.2.1 になります。 表 7.2.1 四角形を描くメソッド 図 7.2.1 clearRect(x, y, 横幅 , 縦幅) fillRect(x, y, 横幅 , 縦幅) strokeRect(x, y, 横幅 , 縦幅) rect(x, y, 横幅 , 縦幅) は、実行しても何も描画されず「パス」だけが生成されます。Canvasには「直接描画する」 ものと 「パス を作成した後で描画する」タイプの 2 つがあります。前者は四角形といったシンプルな図形だけが対応 しています。後者は複雑な図形を描画するのに利用されます。 次のセクションでは「パスを作成した後で描画する」方法について説明します。 175 7.2 四角形の描画 四角形を描くメソッドはrect 以外はそのままcanvasに描画 / 消去の処理が行われます。rect の場合 Chapter 7 var context = canvasObj.getContext("2d"); キャンバスに描画する [Canvas] getContext("2d")とすることでCanvas の機能にアクセスできるオブジェクトが変数に入ります。 ●サンプル 7.2.1 ● <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1"> <title>Canvas サンプル </title> </head> <body> <canvas id="myCanvas" width="300" height="400"></canvas> <script src="js/draw.js"></script> </body> </html> ●サンプル 7.2.1 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.fillStyle = "red"; // 赤色 context.fillRect(20, 30, 200, 250); // 塗り潰した四角形 176 パスの作成と描画 Section このセクションではパスの作成と描画について説明します。パスという用語はディレクトリパスのようにも 使われることがありますが、Canvasでのパスは「複数の座標点で構成された図形」を示します。複数 の座標点で構築することができるため、より複雑な図形を描くことができます。パスを構築するメソッドに は表 7.3.1 のものがあります。 表 7.3.1 パスを構築するメソッド arc(x, y, 半径 , 開始角度 , 終了角度 , 描画方向) arcTo(x1, y1, x2, y2, 半径) bezierCurveTo(制御点座標 1x, 制御点座標 1y, 制御点座標 2x, 制御点座標 2y, x, y) lineTo(x, y) moveTo(x, y) quadraticCurveTo(制御点座標 x, 制御点座標 y, x, y) rect(x, y, 横幅 , 縦幅) パスを使う場合にはあらかじめ beginPath()を呼び出します。このメソッドにより以前のパスは消去さ れ新たなパスが生成されます。beginPath()を実行した時点では座標点は何もありません。 簡単なところで直線を2 本描画してみます。直線を描画するには開始点と終了点の 2 つが必要にな 実際のプログラムはサンプル 7.3.1 になります。山形になった赤い直線が描画されます。なお、線の 色はstrokeStyleプロパティに設定します。CSS3 のカラー指定と同じものを使うことができます。 177 7.3 stroke()メソッドを使います。 パスの作成と描画 ります。まず、開始点はmoveTo()で指定し、終了点はlineTo()で指定します。線を描画するには Chapter 7 キャンバスに描画する [Canvas] 7.3 図 7.3.1 山形の直線が描画される。 ●サンプル 7.3.1 ● <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1"> <title>Canvas サンプル </title> </head> <body> <canvas id="myCanvas" width="400" height="400"></canvas> <script src="js/draw.js"></script> </body> </html> ●サンプル 7.3.1 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.strokeStyle = "red"; // 赤色 context.beginPath(); context.moveTo(10, 350); context.lineTo(150, 10); context.lineTo(290, 350); context.stroke(); 178 ●オープンパスとクローズパス いるものです。サンプル7.3.1は始点と終点がつながっていないのでオープンパスになります。 図形をクローズパスにするにはclosePath()を使います。このメソッドを実行すると始点と終点が自動 的に結ばれパスが閉じられます。サンプル7.3.1を修正してクローズパスにしたものがサンプル 7.3.2 で す。クローズパスにしたので三角形が描かれています。 また、線が太くなっています。線の太さはlineWidthで指定することができます。4を入れると4ピク セル幅の太さになります。なお、CSSとは異なり単位の指定はできません。 図 7.3.2 クローズパスにしたので三角形が描かれている。 HTML 文は、サンプル7.3.1と同じコードなので省略します。 ●サンプル 7.3.2 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.strokeStyle = "red"; // 赤色 context.lineWidth = 4; context.beginPath(); context.moveTo(10, 350); context.lineTo(150, 10); 次頁へ続く 179 7.3 パスの作成と描画 ●サンプル 7.3.2 ● Chapter 7 と終点がつながっていないものです。もう1 つがクローズパスと呼ばれるもので始点と終点がつながって キャンバスに描画する [Canvas] beginPath()によって作成されたパスには2 種類あります。1 つはオープンパスと呼ばれるもので始点 context.lineTo(290, 350); context.closePath(); context.stroke(); ●より複雑な図形を作成するには 他のメソッドと組み合わせることで、より複雑な図形も作成することができます。サンプル 7.3.3 は円弧 と直線を組み合わせて、ソフトクリームのような形をした図形を描画しています。 図 7.3.3 円弧と直線を組み合わせた図形 ●サンプル 7.3.3 ● HTML 文は、サンプル7.3.1と同じコードなので省略します。 ●サンプル 7.3.3 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.strokeStyle = "red"; // 赤色 context.lineWidth = 20; context.beginPath(); context.arc(120, 200, 80, 0, Math.PI, true); context.lineTo(120, 300); context.lineTo(200, 200); context.closePath(); context.stroke(); 180 ●重なった部分をくり抜くには サンプル 7.3.4 ではサンプル7.3.2で描画した三角形を、もう1 つ描いています。ただし、パスの作成 方向は逆にしてあります。このため、重なった部分はくり抜かれて表示されます。 図 7.3.4 重なった部分はくり抜かれる (くり抜かれた部分は透明になっている)。 ●サンプル 7.3.4 ● HTML 文は、サンプル7.3.1と同じコードなので省略します。 ●サンプル 7.3.4 draw.js ● Android 1.6 ∼ 4まではarc() のバグでドーナツ型を表示できません。このためドーナツ型にするには、自前で円を描くプログラム を作成する必要があります。 181 7.3 *1 パスの作成と描画 var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.fillStyle = "red"; // 赤色 context.beginPath(); context.moveTo(10, 350); // 上向きの三角のパスを作成 context.lineTo(150, 10); // 時計回りにパスを作成 context.lineTo(290, 350); context.closePath(); // パスを閉じる(重要) context.moveTo(70, 350); // 位置を少し右にして上向きの三角のパスを作成 context.lineTo(350, 350); // 反時計回りにパスを作成 context.lineTo(210, 10); context.closePath(); context.fill(); Chapter 7 れを利用するとドーナツのような形も簡単にできます(*1)。 キャンバスに描画する [Canvas] Canvasではパスの作成した向き(ベクトル方向)が異なる場合、重なった部分はくり抜かれます。こ 7.4 画像の描画 Section このセクションでは画像の描画について説明します。 Canvasに画像を描画するにはdrawImage()を使います。この drawImage()はパラメータの数によっ て、描画結果が変わります。描画パターンは表 7.4.1 に示すように3 つあります。 表 7.4.1 drawImage のパラメータ (1)drawImage(image, dx, dy) 指定座標に元画像のサイズで描画 (2)drawImage(image, dx, dy, dw, dh) 指定座標に指定サイズで描画 (3)drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 元画像の一部を指定した座標に指定したサイズで描画 image : 画像オブジェクト sx : 描画元の X 座標 dx : 描画先の X 座標 sy : 描画元の Y 座標 dy : 描画先の Y 座標 sw : 描画元の横幅 dw : 描画先の横幅 sh : 描画元の縦幅 dh : 描画先の縦幅 ●指定座標に元画像のサイズで描画 まず、表 7.4.1(1)drawImage(image, dx, dy) の場合から説明します。Canvasに画像を描画す るにはdrawImage()を使うのですが、その際描画する画像データは読み込みが完了している必要が あります。画像データが読み込まれていない場合には何も描画されません。このため、Imageオブジェ クトが読み込まれたら発生するloadイベントを捕捉します。手軽なのはonloadプロパティにイベントハン ドラを設定しておくことです。 イベントハンドラ内でdrawImage()を使ってCanvasに描画を行います。実際のプログラムはサン プル 7.4.1 になります。読み込んだ画像を座標 (0, 0)に元画像と同じサイズで描画しています。なお、 context.drawImage(imageObj, 0, 0);とある部 分 はcontext.drawImage(this, 0, 0);としても同じ です。 182 図 7.4.1 drawImage() による描画 元画像 (image) Canvas (dx, dy) (2)drawImage(image, dx, dy, dw, dh) 元画像 (image) Canvas (dx, dy) dh dw (3)drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 元画像 (image) Canvas sh dh sw dw 183 7.4 画像の描画 (dx, dy) (sx, sy) Chapter 7 キャンバスに描画する [Canvas] (1)drawImage(image, dx, dy) ●サンプル 7.4.1 ● <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1"> <title>Canvas サンプル </title> </head> <body> <canvas id="myCanvas" width="300" height="400"></canvas> <script src="js/draw.js"></script> </body> </html> ●サンプル 7.4.1 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = new Image(); imageObj.src = "images/waterfall.jpg"; // 画像の URL imageObj.onload = function(){ context.drawImage(imageObj, 0, 0); // (0,0) に描画 } もしかしたらサンプル7.4.1 のように処理するのは面倒だと 感じる人がいるかもしれません。もし、HTMLファイル内に Canvasに描画する画像データを用意しておくことができるので あれば別の方法もあります。それはdrawImage() の最初のパ ラメータにimg 要素を指定するという方法です。 実際のプログラムはサンプル 7.4.2 のようになります。ページ のレイアウトや状況によりけりですが、この方がシンプルでわか りやすい上に、画像を変更する場合にスクリプトを操作しなくて もよいというメリットもあります。 184 図 7.4.2 HTML に img 要素を指定してあれば、 その要素を指定して描画することができる。 ●サンプル 7.4.2 ● ●サンプル 7.4.2 draw.js ● window.onload = function(){ var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = document.getElementById("photo"); context.drawImage(imageObj, 0, 0); // (0,0) に描画 } ●指定座標に指定サイズで描画 次に表 7.4.1(2)drawImage(image, dx, dy, dw, dh) の 図 7.4.3 画像が指定した幅で描画されている (元サイズの半分)。 場合ですが、パラメータに横幅と縦幅を指定することができま す。サンプル 7.4.3 では座標(0, 0)から横幅 150ピクセル、縦 幅 200ピクセルで画像を描画します。 7.4 画像の描画 185 Chapter 7 キャンバスに描画する [Canvas] <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1"> <title>Canvas サンプル </title> </head> <body> <canvas id="myCanvas" width="300" height="400"></canvas> <img src="images/waterfall.jpg" width="1" height="1" id="photo"> <script src="js/draw.js"></script> </body> </html> ●サンプル 7.4.3 ● HTML 文は、サンプル7.4.1と同じコードなので省略します。 ●サンプル 7.4.3 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = new Image(); imageObj.src = "images/waterfall.jpg"; // 画像の URL context.drawImage(imageObj, 0, 0, 150, 200); } ●元画像の一部を指定した座標に指定したサイズで描画 最後の表 7.4.1(3)drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)の場合は、やや複雑 でパラメータがたくさんあります。元画像の転送先座標とサイズ、そしてCanvas の描画先の座標とサ イズを一括して指定します。サンプル 7.4.4 の場合は、元画像の(70, 260)から横幅 40ピクセル、縦幅 60ピクセルの範囲をCanvas の座標(50, 70) に横幅 160ピクセル、縦幅 240ピクセルを描画します。 サンプル7.4.4では元画像の一部を拡大して描画していることになります。 図 7.4.4 元画像の一部が拡大されて描画される。 186 ●サンプル 7.4.4 ● ●サンプル 6.4.4 JavaScript(storage.js) ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = new Image(); imageObj.src = "images/waterfall.jpg"; // 画像の URL imageObj.onload = function(){ context.drawImage(imageObj, 70, 260, 40, 60, 50, 70, 160, 240); } 7.4 画像の描画 187 Chapter 7 キャンバスに描画する [Canvas] HTML 文は、サンプル7.4.1と同じコードなので省略します。 7.5 回転、移動など Section このセクションではCanvas の回転と移動、拡大縮小について説明します。 Canvasでは表 7.5.1 に示す変形機能が用意されています。transform() および setTransform()は rotate()、translate()、scale()による変形処理を一括して指定できます。 Canvasでは特定の図形や画像を回転させる機能はなく、表 7.5.1に示すメソッドを使うと以後に描画 される全ての図形や文字に対して変形が適用されます。 表 7.5.1 Canvas の変形機能 rotate(角度) 回転 translate(横の移動量 , 縦の移動量) 移動 scale(横の倍率 , 楯の倍率) 拡大縮小 setTransfrom(m11, m12, m21, m22, 横の移動量 , 縦の移動量) 変形マトリックスを設定(以前の設定は消去される) transfrom(m11, m12, m21, m22, 横の移動量 , 縦の移動量) 現在の変形マトリクスに新たな変形を追加 • m11 ∼ m22は以下の変形マトリクス m11 m21 dx m12 m22 dy 0 0 1 http://himaxoff.blog111.fc2.com/blog-entry-86.html http://www.w3.org/TR/2dcontext/#dom-context-2d-transform ●画像を回転する まず、画像を回転させてみます。回転はrotate()でパラメータには回転角度をラジアンで指定します。 ラジアンなのでMath.PI*2とするとちょうど一回転することになります。角度からラジアンに変換したい場 合には以下の計算式を使います。 角度 × π ÷ 180 rotate() のパラメータが正数の場合は時計回りに、負数の場合は反時計回りに回転します。サンプ ル 7.5.1 では時計回りに20 度画像を回転させています。 188 図 7.5.1 画像が Canvas の左上を基準にして時計回りに 20 度回転した。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1"> <title>Canvas サンプル </title> </head> <body> <canvas id="myCanvas" width="300" height="400" style="border:1px solid black;"></canvas> <script src="js/draw.js"></script> </body> </html> var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = new Image(); imageObj.src = "images/waterfall.jpg"; // 画像の URL imageObj.onload = function(){ context.rotate(20 * Math.PI / 180); context.drawImage(imageObj, 0, 0); } 189 7.5 回転、移動など ●サンプル 7.5.1 draw.js ● Chapter 7 キャンバスに描画する [Canvas] ●サンプル 7.5.1 ● サンプル7.5.1を実行すると画像は回転して表示されますが、回転の中心が Canvas の左上になって います(原点)。回転の中心の位置を変更したい場合にはtranslate()を使います。translate()は横と 縦の移動量を指定します。これにより回転の中心が指定した座標になります。 に原点を移動させた後、rotate()を使って回転させて サンプル 7.5.2 ではCanvas の座標(150, 200) います。注意しないといけないのは、移動と回転の中心の順番が変われば結果が異なるという点です。 図 7.5.2 Canvas の中心 (150, 200)を基準にして画像が回転する。 ●サンプル 7.5.2 ● HTML 文は、サンプル7.5.1と同じコードなので省略します。 ●サンプル 7.5.2 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = new Image(); imageObj.src = "images/waterfall.jpg"; // 画像の URL imageObj.onload = function(){ context.translate(150, 200); context.rotate(20 * Math.PI / 180); context.drawImage(imageObj, 0, 0); } 190 ● Canvas の状態を保存、 復元するメソッド です。原点の移動量や回転、拡大縮小率などを操作していくと、元に戻す場合には逆の処理を行わ なければいけません。しかし、変形処理が複雑になった場合には簡単に元の状態に戻すのは難しいで しょう。 そこで、便利なのが以下の表 7.5.2 に示す Canvas の状態を保存、復元するメソッドです。 表 7.5.2 Canvas の状態を処理するメソッド save() Canvas の状態を保存 restore() Canvas の状態を復元 save()はCanvas の状態を保存すると書きましたが、これはCanvasで描画される表 7.5.3 に示すプ ロパティ値と変形マトリクスを保存するものです。Canvasに描かれているピクセルデータを保存するわ けではありません(*1)。また、パスなどはリセットされず残ったままになります。 表 7.5.3 保存されるプロパティ strokeStyle fillStyle globalAlpha lineWidth lineCap lineJoin miterLimit shadowOffsetX shadowOffsetY shadowColor globalCompositeOperation font textAlign textBaseline *1 Android 4 以降ではCanvas の内容をtoDataURL()メソッドを使って読み出すことができます。これによりdataURL 形式として ピクセルデータを取得できます。dataURL 形式はテキストなのでCanvas 内容をLocal Storageに保存することもできます。 191 7.5 回転、移動など shadowBlur Chapter 7 に戻さないと以後に描画される図形や画像、文字の描画結果が意図しないものになる場合があるため キャンバスに描画する [Canvas] translate()によって原点を移動させ回転させた後には、元の状態に戻しておく必要があります。元 save()により保存された内容を元に戻すにはrestore()を使います。なお、save()は入れ子(ネスト) に して使うことができます。Canvasを使ったライブラリなどを開発する際には必須の機能です。 サンプル 7.5.3 が save()、restore()を使ったプログラムです。基本的には変形前にsave()で状態保 存し、処理が終わったらrestore()で戻します。save()、restore() がなければ 2 番目に描画される画像 は回転して描画されてしまいます。 図 7.5.3 1 枚目の画像は回転して描画されるが、 2 枚目は元の状態になっているため回転せずに描画されている。 ●サンプル 7.5.3 ● HTML 文は、サンプル7.5.1と同じコードなので省略します。 ●サンプル 7.5.3 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = new Image(); imageObj.src = "images/waterfall.jpg"; // 画像の URL imageObj.onload = function(){ context.save(); context.translate(75, 100); context.rotate(45 * Math.PI / 180); context.drawImage(imageObj, -75, -100); context.restore(); context.drawImage(imageObj, 150, 200); } 192 ●画像を拡大・縮小する サンプル 7.5.4 では図形を回転させた後に横を200%、縦を50% の縮尺にしてから画像を描画してい ます。 図 7.5.4 画像が回転、変形して描画される。 ●サンプル 7.5.4 ● HTML 文は、サンプル7.5.1と同じコードなので省略します。 ●サンプル 7.5.4 draw.js ● 193 7.5 回転、移動など var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); var imageObj = new Image(); imageObj.src = "images/waterfall.jpg"; // 画像の URL imageObj.onload = function(){ context.save(); context.translate(75, 100); context.rotate(45 * Math.PI / 180); context.scale(2, 0.5); context.drawImage(imageObj, -75, -100); context.restore(); } Chapter 7 します。1.0を指定すると100%となります。0.5なら50%、2.0なら200%になります。 キャンバスに描画する [Canvas] 最後に拡大縮小です。拡大縮小はscale()メソッドを使います。パラメータには横と縦の倍率を指定 7.6 文字の表示 Section このセクションでは文字の描画について説明します。 Canvasでは以下の表 7.6.1 に示す文字描画に関する機能が用意されています。なお、Canvasに 描画される文字の方向はcanvas 要素に指定されているスタイルシートの directionとunicode-bidiに 依存します。 表 7.6.1 Canvas の文字描画機能 font フォントを設定 fillText(表示文字 , x, y) 塗り潰された文字を描画 strokeText(表示文字 , x, y) アウトライン文字を描画 measureText(表示文字) 文字の横幅を求める textAlign 行揃え textBaseline ベースライン ●文字描画の基本 描画する文字のフォントやサイズなどはfontプロパティに一括して設定します。fontプロパティには CSS の fontプロパティと同じ内容を指定することができます。例えば「italic bold 64px Times」と設定 すれば Timesフォントで64ピクセルの太字の斜体で文字が描画されます。 また、描画する文字の色はfillText() の場合はfillStyleプロパティに設定されたカラーになります。 strokeText() の場合はstrokeStyleプロパティに設定されたカラーになります。 fillText()、strokeText()とも最初のパラメータに描画する文字列を指定します。なお、画面からは み出しても自動的に改行はされません。また、描画位置を示す座標は文字のベースラインが基準になり ます。 2 番目と3 番目には文字を描画する座標を指定します。行揃えはtextAlignで「start, end, left, right, center」のいずれかを指定します。 サンプル 7.6.1 が塗り潰された文字を描画、サンプル 7.6.2 がアウトライン文字を描画するプログラム になります。 194 図 7.6.1 赤色で文字が描画される。 図 7.6.2 アウトライン文字が描画される。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1"> <title>Canvas サンプル </title> </head> <body> <canvas id="myCanvas" width="300" height="400"></canvas> <script src="js/draw.js"></script> </body> </html> var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.fillStyle = "red"; context.font = "italic bold 64px Times" context.fillText("Android", 20, 100); 195 7.6 文字の表示 ●サンプル 7.6.1 draw.js ● Chapter 7 キャンバスに描画する [Canvas] ●サンプル 7.6.1 ● ●サンプル 7.6.2 ● HTML 文は、サンプル7.6.1と同じコードなので省略します。 ●サンプル 7.6.2 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.strokeStyle = "red"; context.font = "italic bold 64px Times" context.strokeText("Android", 20, 100); ●文字にアウトラインを付ける 塗り潰された文字にアウトラインを描画する場合にはサンプル 7.6.3 のように別々に描画します。 図 7.6.3 黄色い文字に赤いアウトラインが描画される。 ●サンプル 7.6.3 ● HTML 文は、サンプル7.6.1と同じコードなので省略します。 ●サンプル 7.6.3 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.fillStyle = "yellow"; context.strokeStyle = "red"; context.font = "italic bold 64px Times" context.fillText("Android", 20, 100); context.strokeText("Android", 20, 100); 196 ●影を付ける は正常な動作になっています。 影は以下の表 7.6.2 に示す機能があります。clearShadow()はCanvasに描画されている影を消す のではなく、影を描画しないようにするメソッドです。 表 7.6.2 影に関する機能 shadowBlur 影のぼかし具合 shadowColor 影の色 shadowOffsetX 影の横方向のずれ(オフセット) shadowOffsetY 影の縦方向のずれ(オフセット) clearShadow() 影を消去 実際に文字に影を描画するプログラムがサンプル 7.6.4 です。塗り潰された黄色い文字に青い影が 描画されます。その後、clearShadow()を使って影の描画を行わないようにしているため、アウトライン 文字には影は描画されていません。 図 7.6.4 青い影が描画される。 7.6 文字の表示 197 Chapter 7 3ではshadowOffsetYプロパティに描画方向が逆になるという不具合があります。Android 4 以降で キャンバスに描画する [Canvas] Canvasでは描画する図形や文字に影(シャドウ)を付けることができます。ただし、Android 2.x ∼ ●サンプル 7.6.4 ● HTML 文は、サンプル7.6.1と同じコードなので省略します。 ●サンプル 7.6.4 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.shadowBlur = 5; context.shadowColor = "blue"; context.fillStyle = "yellow"; context.strokeStyle = "red"; context.font = "italic bold 64px Times" context.fillText("Android", 20, 100); context.clearShadow(); context.strokeText("Android", 20, 100); ●不透明度を指定する 文字の不透明度を指定して描画することもできます。Canvasでは不透明度を設定するには2 つの 方法があります。1 つはglobalAlphaプロパティに不透明度 0.0 ∼ 1.0を設定する方法です。これは全 ての描画機能に対して不透明度が設定されます。 もう1 つの方法が fillStyle や strokeStyleプロパティにCSS の rgba() 形式で不透明度含めたカラー 値を設定するものです。この場合、カラーまたは枠の色に関してのみ不透明度が反映され、他には影 響を与えません。 サンプル 7.6.5、サンプル 7.6.6とも文字を半透明にして描画しますが、結果は同じになります。 図 7.6.5 黄色い文字の不透明度を 70% にしているため、 重なった部分は半透明になっている。 198 ●サンプル 7.6.5 ● ●サンプル 7.6.5 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.font = "italic bold 64px Times" context.fillStyle = "red"; context.fillText("Android", 20, 100); context.globalAlpha = 0.7; context.fillStyle = "yellow"; context.fillText("Android", 17, 97); ●サンプル 7.6.6 ● HTML 文は、サンプル7.6.1と同じコードなので省略します。 ●サンプル 7.6.6 draw.js ● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.font = "italic bold 64px Times" context.fillStyle = "red"; context.fillText("Android", 20, 100); context.fillStyle = "rgba(255, 255, 0, 0.7)"; context.fillText("Android", 17, 97); 7.6 文字の表示 199 Chapter 7 キャンバスに描画する [Canvas] HTML 文は、サンプル7.6.1と同じコードなので省略します。 7.7 Canvas を使った リアルタイムゲームの作成 Section このセクションではCanvasを使ったリアルタイムゲームの作成について説明します。 ●ゲームの概要 章の始めにも少し説明しましたが、ここで作成するゲームは上空から回転しながら落下してくる隕石を タップして破壊するものです。タップすると中央の砲台からタップした位置までレーザービームが瞬時に 発射されます。隕石を破壊すると半透明の爆風が広がっていき、やがて消えます。隕石をタップする 際に画面下であれば、より高得点になります。隕石が完全に落下するとゲームオーバーになります。 まとめると以下のようになります。 (1)隕石は上から回転しつつ落下 (2)隕石が画面下まで落下するとゲームオーバー (3)画面をタップすると中央の砲台からレーザービームが出る。 (4)隕石をタップしたら爆風を出す。その後、再度隕石を上から落下させる。 図 7.7.1 実際のゲーム画面 200 ●プログラムの構成と処理の流れ (1)初期設定 (2)ページ読み込み後のゲーム開始処理 (3)ビームの発射処理 (4)隕石の移動や爆風などの処理 これらの処理の説明の前にHTMLとCSS 部分について説明しておきます。この Canvas の章では Canvasに画像を描画する時にはdrawImage()を使うと説明しました。今回のゲームでも図 7.7.1 を見 ればわかるように背景を描画しています。 普通に考えると背景もCanvasに描画することになります。一般的には、そのようになりますが、ここ ではプログラムの負荷を下げて処理速度を向上させるため、Canvas の背景はスタイルシートを使って 表示しています。HTMLファイルの以下の部分が Canvasに背景を設定している部分です。 canvas { position: absolute; top:10px; left:10px; border:1px solid black; background-image: url(images/bg.png); } Canvas自体は何も描画しなければ「透明」ですので、このようにすると背景が表示されることになりま す。この CSS 部分の処理はブラウザが行いますので、下手にJavaScriptを使って自前で処理するより も高速ですし、そもそも背景を描画するプログラムが不要になります。HTML5でゲーム等を作成する のであれば、このような背景との合成方法、利用方法も覚えておくとよいでしょう。 7.7 Canvasを使ったリアルタイムゲームの作成 ●サンプル 7.7.1 ● <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1, user-scalable=no"> <title> 隕石落下防止ゲーム </title> <link rel="stylesheet" href="css/jquery.mobile.css"> <style> canvas { position: absolute; top:10px; left:10px; border:1px solid black; 次頁へ続く 201 Chapter 7 このプログラムは大きく分けると以下の 4 つになっています。 キャンバスに描画する [Canvas] 今回は最初にゲームのプログラムを見てもらいましょう。以下が実際のゲームのプログラムになります。 background-image: url(images/bg.png); } #main { height: 350px; } </style> <script src="js/jquery.js"></script> <script src="js/shoot.js"></script> <script src="js/jquery.mobile.js"></script> </head> <body> <div data-role="page" id="content"> <div data-role="content" id="main"> <canvas id="myCanvas" width="300" height="350"></canvas> </div> </div> </body> </html> ●サンプル 7.7.1 shoot.js ● var g = { // ゲーム関係の情報を入れるグローバルオブジェクト canvasW : 0, // Canvas の横幅 canvasH : 0, // Canvas の縦幅 stoneImg : new Image(), // 隕石の画像オブジェクト stoneSize : 32, // 隕石の幅 context : null, // Canvas の 2D コンテキスト timerID : null, // タイマー変数 flag : false, // タップしたかどうかのフラグ。true= タップした tapx : 0, // タップした X 座標 tapy : 0, // タップした Y 座標 ix : 0, // 隕石の X 座標 iy : 0, // 隕石の Y 座標 dy : 0, // 隕石の移動量(落下量) bx : 0, // 爆風の X 座標 by : 0, // 爆風の Y 座標 bCounter : 1000, // 爆風のカウンタ変数 score : 0, // スコア rad : 0 // 隕石の回転角度(ラジアン) }; $(function(){ // Canvas 関係の読み込みとイベント、タイマー設定 var canvasObj = document.getElementById("myCanvas"); g.canvasW = canvasObj.width; g.canvasH = canvasObj.height; g.context = canvasObj.getContext("2d"); g.stoneImg.src = "images/stone.png"; g.stoneImg.onload = function(){ // 隕石の画像データを読み込み完了後にゲーム開始 g.ix = Math.random() * 200 + 50; // 隕石の出現範囲 g.iy = -g.stoneSize; 202 7.7 Canvasを使ったリアルタイムゲームの作成 }); // タップしたらビームを発射 function startBeam(evt){ // タップされた座標を求める g.tapx = evt.touches[0].pageX; g.tapy = evt.touches[0].pageY; g.flag = true; // 判定。2 点間の距離で判定する var absx = Math.abs(g.tapx - g.ix); var absy = Math.abs(g.tapy - g.iy); var d = Math.sqrt(Math.pow(absx, 2) + Math.pow(absy, 2)); if (d > 50){ return; } // 50px より大きい場合は何もしない g.score = g.score + Math.floor(g.iy); g.bx = g.tapx; // 爆風の座標を設定 g.by = g.tapy; g.bCounter = 0; g.ix = Math.random() * 200+50; g.iy = -g.stoneSize; g.dy = Math.random() * 5 + 2 + g.score/1000; // 落下速度を計算 } // 隕石移動&描画 function moveStone(){ g.context.clearRect(0,0, g.canvasW, g.canvasH); // Canvas 内容を消去 g.iy = g.iy + g.dy; // 隕石を移動(落下) g.context.save(); // コンテキストを保存しておく var centerX = g.ix + g.stoneSize/2; var centerY = g.iy + g.stoneSize/2; g.context.translate(centerX, centerY); // 隕石の中心に移動 g.context.rotate(g.rad); // 回転させる g.context.translate(-centerX, -centerY); // 元に戻す g.rad = g.rad + Math.PI / 9; g.context.drawImage(g.stoneImg, g.ix, g.iy); g.context.restore(); // 保存したコンテキストを元に戻す // 隕石が完全に落下したか調べる if (g.iy > g.canvasH){ clearInterval(g.timerID); alert("Game Over"); // 隕石が落下したのでゲームオーバー } // タップされた場合は一度だけレーザービームを描画する if (g.flag){ g.context.lineWidth = 3; g.context.strokeStyle = "yellow"; g.context.beginPath(); g.context.moveTo(150, 327); 次頁へ続く 203 Chapter 7 キャンバスに描画する [Canvas] g.dy = Math.random() * 5 + 2; g.timerID = setInterval("moveStone()", 100); } canvasObj.addEventListener("touchstart", startBeam, false); g.context.lineTo(g.tapx, g.tapy); g.context.stroke(); g.flag = false; } // 爆風があるなら処理する if (g.bCounter < 10){ g.context.beginPath(); g.context.fillStyle = "yellow"; g.context.globalAlpha = 0.7; // 不透明度を 70% にする g.context.arc(g.bx, g.by, g.bCounter*10, 0, Math.PI*2, false); // 円を描く g.context.fill(); g.context.globalAlpha = 1.0; g.bCounter = g.bCounter + 1; } // スコアを表示する g.context.font = "normal bold 22px Times"; g.context.fillStyle = "red"; g.context.fillText("SCORE "+g.score, 10, 20); } 7.7.1 初期設定とゲーム開始処理 ●初期設定 まず、 (1) の初期設定部分です。これはグローバル変数(オブジェクト)gを用意し、その中にゲーム で使われる情報を格納するようにしています。どのプロパティが何を示しているかはプログラム内のコメン トに記載してあります。 ●ゲーム開始処理 次に(2)のページが読み込まれた後、ゲームを開始する部分です。$(function(){ ∼で示される部分 が該当します。ここではCanvas の横幅や縦幅、コンテキストを読み出してします。その後、隕石の画 像を読み込みます。隕石の画像が完全に読み込まれたら隕石の落下開始位置を設定します。設定が 終わったらインターバルタイマーを使って隕石の移動処理を行うmoveStone() 関数を0.1 秒ごとに呼び出 します。 Android 端末では処理速度にばらつきがあるため、インターバルタイマーの間隔を短くしても効果が ないことがあります(*1)。 最後にCanvasにタッチされた場合に呼び出す関数(イベントリスナー)を設定します。タッチされた場 合にはstartBeam() 関数が呼び出されます。 *1 作成するゲームによりけりですが別解としてsetRequestAnimationFrame()を使う方法もあります。 204 7.7.2 レーザービームの処理 央の砲台からタップした位置までレーザービームを描画します。レーザービームは直線ですのでlineTo() を使って描画することになります。が、実際にはstartBeam() 関数内にはレーザービームを描画する処 理がありません。 レーザービームの描画処理はmoveStone() 関数内で行うようにしています。なぜ、このようになって いるのでしょうか? それはAndroid 2.xとAndroid 3 以降では画面の書き換えタイミングが異なってい るためです。Android 2.xであれば startBeam() 関数内でlineTo()を使って描画しても問題なく画面 に表示されます。 ところが、Android 3 以降では画面の描画タイミングが異なるため、startBeam() 関数内で処理して も表示される場合もあれば、されない場合もあります。そこで、レーザービームが発射された事だけを 示すフラグ (g.flag) をtrueにしています。 次にタップした場所が隕石と同じ場所かどうか調べます。正確にタップするというのはAndroidでは 難しいため、隕石から50ピクセル以内であればヒットしたことにしています。これが以下の部分になりま す。2 点間の距離で判別しています。より正確に判定するのでれば、隕石の中心座標から行うとよいで しょう。 var absx = Math.abs(g.tapx - g.ix); var absy = Math.abs(g.tapy - g.iy); var d = Math.sqrt(Math.pow(absx, 2) + Math.pow(absy, 2)); if (d > 50){ return; } // 50px より大きい場合は何もしない 隕石がタップされたらスコアを加算します。スコアは隕石を下でタップすれば高得点になるのでY 座標 を使って整数値にしています。 g.score = g.score + Math.floor(g.iy); 最後に隕石をタップした場合に広がる爆風の処理です。レーザービームと同様にstartBeam() 関数 内では処理せず、以下のように爆風のサイズを示すカウンタを設定するようにしています。 g.bCounter = 0; 205 7.7 Canvasを使ったリアルタイムゲームの作成 を加算するスコアにします。なお、このゲームではY 座標は小数値になることがあるのでMath.floor() Chapter 7 次に(3)のレーザービームの処理です。レーザービームはタップした位置の座標を取得し、画面下中 キャンバスに描画する [Canvas] ●ビームの発射処理 7.7.3 隕石の移動と描画処理 ●隕石の移動・爆風ほかの処理 最後に (4)の隕石の移動と爆風の処理です。まず、Canvas の内容を消去します。これは、以前の セクションでも解説したclearRect()メソッドを使います。Canvas の内容は消去されますが、CSSで設 定した背景画像はそのままです。もし、CSSで背景画像を設定していない場合は、この後背景画像の 描画処理を行うことになります。リアルタイムゲームでは、 そのような処理を追加するだけで速度が遅くなっ てしまいますので、そのような方法は避けるのがよいでしょう。 g.context.clearRect(0,0, g.canvasW, g.canvasH); // Canvas 内容を消去 隕石の落下処理は簡単で隕石の Y 座標に移動量を加算するだけです。 g.iy = g.iy + g.dy; // 隕石を移動(落下) この隕石は単純に落下するのではなく回転しながら落下してきます。回転は以前のセクションで説 明したようにrotate()メソッドを使います。translate()を使って隕石の中心を回転するようにします。 Canvas の状態を保存、復元するsave()、restore()を忘れないようにしておきます。忘れると描画され る画像などがおかしくなってしまいます。 g.context.save(); // コンテキストを保存しておく var centerX = g.ix + g.stoneSize/2; var centerY = g.iy + g.stoneSize/2; g.context.translate(centerX, centerY); // 隕石の中心に移動 g.context.rotate(g.rad); // 回転させる g.context.translate(-centerX, -centerY); // 元に戻す g.rad = g.rad + Math.PI / 9; g.context.drawImage(g.stoneImg, g.ix, g.iy); g.context.restore(); // 保存したコンテキストを元に戻す 隕石が完全に下まで落下したかどうかはCanvas の高さを超えたかどうかを調べます。落下していた らインターバルタイマーを止めた後にゲームオーバーのメッセージを出します。今回のゲームではアラート ダイアログを使っていますが、Canvas 内にGame Over の文字を描画してもよいでしょう。 if (g.iy > g.canvasH){ clearInterval(g.timerID); alert("Game Over"); // 隕石が落下したのでゲームオーバー } 206 ここまで来れば残っている処理はレーザービームの描画、爆風の描画、スコアの表示だけです。こ なお、Android 2.x 以降では爆風は半透明になりますが、Android 1.6 端末では爆風は半透明にな りません。 if (g.bCounter < 10){ g.context.beginPath(); g.context.fillStyle = "yellow"; g.context.globalAlpha = 0.7; // 不透明度を 70% にする g.context.arc(g.bx, g.by, g.bCounter*10, 0, Math.PI*2, false); // 円を描く g.context.fill(); g.context.globalAlpha = 1.0; g.bCounter = g.bCounter + 1; } 隕石の数を増やしてみる、爆風を複数表示するように改良してみるのもよいでしょう。 7.7 Canvasを使ったリアルタイムゲームの作成 207 Chapter 7 ウンタを1 つずつ増やしていき10 未満の場合のみ arc()を使って黄色い円を描画しています。 キャンバスに描画する [Canvas] れまでのセクションでの説明を理解していれば難しい部分はありません。爆風が広がる処理ですが、カ 7.8 Canvas を使った ノベルシステムの作成 Section このセクションではCanvasを使ったノベルシステムの作成について説明します。ノベルシステムは Canvasを2 枚重ねて表示します。一枚が画像を表示するCanvasで、もう1 つが文字を表示する Canvasです。Canvasを2 枚重ねることで、文字を書き換える際に背景の画像の書き換えを考慮しなく てもよいというメリットがあります。 ●ノベルシステムとシナリオファイル ノベルシステムは、あらかじめ用意されたシナリオファイルに従って画像と文字を表示していくものです。 ノベルゲームであれば条件分岐なども必要になりますが、ここで作成するシステムはそのような機能はあ りません。単純に画像とテキストを表示しBGMを演奏するだけのものです。 シナリオファイルはテキストで構成されており、何もしなければ文字がそのままCanvas 上に描画されま す。ただし、Canvas 上に表示される文字は26 文字×5 行です。この文字数を超えても自動的にスクロー ルして表示する機能はありません。このため、一度に表示される文字は26 文字×5 行に収める必要が あります。 一定数文字を表示したらタップされるのを待つ、画像を切り換える、といった処理はシナリオファイル 内に専用のコマンドを埋め込むことで実現します。ここで作成するノベルシステムでは表 7.8.1 に示すコ マンドを用意しています。今回のシステムでは行頭に# があるものをコマンドとして処理します。 表 7.8.1 使用できるコマンド img URL URL にある画像を表示。ネットワーク上にあるものであれば何でも使用できる。なお、画像は合成で きるので PNG 形式で透明情報が含まれていれば背景に人物を重ねて表示することもできる。 wait 画面がタップされるのを待つ。 bgm URL URL にある音楽を演奏する。Android 2.x ∼ 3 では多重演奏できるが Android 4 では単独演奏のみ。 end 処理を終了。最後の画面が表示されたままになる。 以下の例ではimagesフォルダにあるschool.jpgを表示しbgmフォルダにあるopening.mp3を演奏し ます。その後「ようこそ!」 という文字を表示して画面がタップされるのを待ちます。 208 #wait ノベルシステムでは、このようなシナリオデータを読み込み処理します。実際のプログラムがサンプル 7.8.1 になります。Canvas 部分に関しては、これまでの説明で十分理解できるでしょう。また、BGM 演奏 もAudioオブジェクトを生成した後、play()メソッドで再生しているだけです。 図 7.8.1 ノベルシステムの画面 ●シナリオファイルの読み込みと解析処理 ここではシナリオファイルを読み込み処理する部分について説明します。 まず、シナリオファイルは以下のようにして非同期通信を利用して読み込ます。シナリオファイル名は senario.txtとしています。シナリオデータはsplit()を使って一行単位で分けて配列変数 storyに入れて 想定しています(String.fromCharCode(10) の部分)。 シナリオデータを読み込んだら解析開始の行番号を0にします(pointer = 0 の部分)。その後、実際 に解析を行うメソッドを呼び出します(nsFunc.main() の部分)。 $.get("./senario.txt", null, function(data){ story = data.split(String.fromCharCode(10)); pointer = 0; nsFunc.main(); }); 209 7.8 Canvasを使ったノベルシステムの作成 おきます。これは解析処理を簡単にするためです。なお、改行コードはLF(アスキーコード10 番)を Chapter 7 ようこそ! キャンバスに描画する [Canvas] #img images/school.jpg #bgm bgm/opening.mp3 解析を行う部分は以下のブロックです。 while(true){ var text = story[pointer]; if (text.indexOf("#end") > -1){ endFlag = true; return; } pointer++; if (text.charCodeAt(0) < 32){ continue; } if (text.indexOf("#bgm") > -1){ nsFunc.bgm(text); continue; } if (text.indexOf("#wait") > -1){ return; } if (text.indexOf("#img") > -1){ nsFunc.image(text); continue; } contextText.fillStyle = "white"; contextText.font = "11px bold"; contextText.fillText(text, 10, linePointer, 300); linePointer = linePointer + 18; // 行間隔は 18 ピクセル } 最初にendコマンドを処理します。endコマンドであれば何もする必要がないため returnで関数から 抜けます。 endコマンド以外であれば解析する行を示すポインタ(pointer++)を進めます。もし、空行であれば 特に処理せずにポインタを進めます。 次にbgmコマンドであればコマンドの後に続くURL の曲を演奏します。解析処理は以後に説明する 画像表示と同じなので省略します。 次にタップを待つ waitコマンドです。この場合は何もせず関数から抜けます。タップしたら次に進ま せるには以下のようにCanvasでtapイベントが発生したら解析処理を呼び出すようにします。 $("#textCanvas").bind("tap", nsFunc.main); 次にimgコマンドです。imgコマンドの場合は半角空白で区切られた後に続く文字をURLとして処 理する必要があります。これは一行のデータをsplit(" ")とし空白単位で切り分けます。配列の 2 番目に 格納されるのが URLになるので、これを画像オブジェクトの srcに入れます。後はCanvasに描画すれ ばできあがりです。 image : function(text){ var data = text.split(" "); // 空白区切り var imageObj = new Image(); imageObj.src = data[1]; // 2 番目が画像の URL imageObj.onload = function(){ contextBG.drawImage(this, 0, 0); } }, 210 最後に文字表示です。いずれのコマンドでもない場合はCanvasに文字を表示します。そして、い #evalコマンドを追加し、以後に続く文字列をJavaScriptコードとして実行させるという手法もあります。 この方法だとシステムに変更を加えることなく複雑な処理も実現できます。 図 7.8.2 最初の画面 図 7.8.3 図 7.8.4 画面をタップすると画像と文字、または文字だけが切り替わる (①∼④)。 ① ② 7.8 Canvasを使ったノベルシステムの作成 211 Chapter 7 他にコマンドを追加して条件分岐やローカルストレージへの保存を行うのも面白いでしょう。ちなみに キャンバスに描画する [Canvas] ずれかのコマンドが見つかるまで解析処理を行います。 図 7.8.5 図 7.8.6 ③ ④ 図 7.8.7 bgmコマンドにより音楽の演奏が開始される。 図 7.8.8 タップすることで次々と画像と文字が切り替わる (①∼④)。 ① 212 図 7.8.9 図 7.8.12 endコマンドを処理すると終了する。 なお、 End の文字は透明の PNG 画像になっており、 描画済みの背景画像に重ねて表示している。 ④ 7.8 Canvasを使ったノベルシステムの作成 213 Chapter 7 図 7.8.11 ③ キャンバスに描画する [Canvas] ② 図 7.8.10 ●サンプル 7.8.1 ● <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content= "initial-scale=1, user-scalable=no"> <title> ノベルシステム </title> <link rel="stylesheet" href="css/jquery.mobile.css"> <style> canvas { position: absolute; top:50px; left:10px; } #main { height: 400px; } </style> <script src="js/jquery.js"></script> <script src="js/novel.js"></script> <script src="js/jquery.mobile.js"></script> </head> <body> <div data-role="page" id="content"> <div data-role="header"> <h1> ノベルシステム </h1> </div> <div data-role="content" id="main"> <canvas id="bgCanvas" width="300" height="400"></canvas> <canvas id="textCanvas" width="300" height="400"></canvas> </div> </div> </body> </html> ●サンプル 7.8.1 novel.js ● var story, contextBG, contextText, canvasW, canvasH, pointer; var endFlag = false; $(function(){ // シナリオデータを読み込む $.get("./senario.txt", null, function(data){ story = data.split(String.fromCharCode(10)); pointer = 0; nsFunc.main(); }); // Canvas を初期化し塗り潰す var canvasBgObj = document.getElementById("bgCanvas"); canvasW = canvasBgObj.width; canvasH = canvasBgObj.height; contextBG = canvasBgObj.getContext("2d"); var canvasTextObj = document.getElementById("textCanvas"); contextText = canvasTextObj.getContext("2d"); 214 7.8 Canvasを使ったノベルシステムの作成 }); var nsFunc = { // 解析する main : function(){ if (endFlag == true){ return; } var linePointer = 320; // 文字を表示する開始 Y 座標 nsFunc.clearText(); while(true){ var text = story[pointer]; if (text.indexOf("#end") > -1){ endFlag = true; return; } pointer++; if (text.charCodeAt(0) < 32){ continue; } if (text.indexOf("#bgm") > -1){ nsFunc.bgm(text); continue; } if (text.indexOf("#wait") > -1){ return; } if (text.indexOf("#img") > -1){ nsFunc.image(text); continue; } contextText.fillStyle = "white"; contextText.font = "11px bold"; contextText.fillText(text, 10, linePointer, 300); linePointer = linePointer + 18; // 行間隔は 18 ピクセル } }, // 文字表示用の Canvas をクリア clearText : function(){ contextText.save(); contextText.clearRect(0,0, canvasW, canvasH); contextText.fillStyle = "black"; contextText.globalAlpha = 0.65; contextText.fillRect(0, 300, canvasW, 100); contextText.restore(); }, // 指定された画像を表示 image : function(text){ var data = text.split(" "); // 空白区切り var imageObj = new Image(); imageObj.src = data[1]; // 2 番目が画像の URL imageObj.onload = function(){ contextBG.drawImage(this, 0, 0); } }, // 指定された曲を演奏 bgm : function(text){ var data = text.split(" "); // 空白区切り var audioObj = new Audio(data[1]); // 2 番目が BGM の URL audioObj.play(); } } 215 Chapter 7 キャンバスに描画する [Canvas] nsFunc.clearText(); $("#textCanvas").bind("tap", nsFunc.main); ●サンプル 7.8.1 senario.txt ● #img images/title.jpg ここは木曽谷。 木曽の山奥には、いろいろな滝があります。 今回は南木曽町にある柿其渓谷の滝を紹介しましょう。 #wait 【………… 省 略 …………】 #img images/waterfall.jpg #bgm bgm/star.mp3 「ねじだる」の次に現れるのが「霧ヶ滝」です。 エメラルドグリーンの滝壺は非常にきれいで濁りが ありません。 #wait 【………… 省 略 …………】 #img images/end.png 以上で終わりです。 #end 216 Column Canvas 関係のバグ このバグは Android 4 では修正されています。 ●コード● var canvasObj = document.getElementById("myCanvas"); context = canvasObj.getContext("2d"); context.fillStyle = "red"; context.shadowBlur = 5; context.shadowOffsetX = 4; context.shadowOffsetY = 8; context.shadowColor = "orange"; context.font = "32px Times"; context.fillText("Android", 10, 60); 図 7.8.13 Android 2.x では影が上方向に描画がされている。 図 7.8.14 Android 4 では影が正しく下方向に描画がされている。 本来は下方向に描画されなければならない。 7.8 Canvasを使ったノベルシステムの作成 次頁へ続く 217 Chapter 7 shadowOffsetY プロパティは Android 2.x, 3 では、通常とは逆方向に影が描画されてしまうバ グがあります。正数を指定すると下方向に影が描画されるはずが、上方向に描画されてしまいます。 キャンバスに描画する [Canvas] 【バグ】shadowOffsetY プロパティ 【バグ】arc メソッド Canvas では描画する方向(ベクトル)が異なっている場合、重なったパスの内部はくり抜かれます。 このため、arc() メソッドの最後のパラメータで時計回りに描画するか、反時計回りに描画するかを指 定するフラグがあります。しかし、Android 1.6 ∼ 4.0 の全てにおいて最後のパラメータが正しく反 映されません。 このため、ドーナツ型の図形を描こうとしても、ただの塗り潰された丸になってしまいます。通常の パスを構築した場合は正常に処理されるので arc() メソッドのバグでしょう。 図 7.8.15 Android 4 (Galaxy Nexus) での実行結果。 図 7.8.16 iOS での実行結果。これが正しい結果。 重なった部分がくり抜かれない。 パソコンのブラウザでも同様の結果になる。 ●コード● var canvasObj = document.getElementById("myCanvas"); var context = canvasObj.getContext("2d"); context.fillStyle = "red"; // 赤色 context.beginPath(); context.arc(120, 200, 60, 0, Math.PI*2, true); context.closePath(); context.arc(180, 200, 60, 0, Math.PI*2, false); context.closePath(); context.fill(); 218