...

Chapter 07 キャンバスに描画する

by user

on
Category: Documents
11

views

Report

Comments

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
Fly UP