3-1 高度な描画を行う

ゲーム機というハードには、複数のグラフィック画面を重ねて表示する機能が備わっています。 複数のグラフィックを重ねる仕組みをレイヤーといいます。 パソコンのペイントソフトのレイヤー機能では、複数の絵を重ね合わせ、表示の順番を入れ替えることができます。 それと同じことをHTML5のキャンバスで実現できます。 本項では、高度な画像表示とレイヤーを実現するテクニックを解説します。
例えばHTML5のゲーム制作で、キャラクターを手前のレイヤー、背景を奥のレイヤーに表示し、 背景は必要な時にだけ描き変えれば、無駄のない高速な描画処理が可能となります。


(1)画像と文字の回転表示

画像と文字を回転して表示するサンプルです。まずは動作をご覧下さい。
example311.html ← 動作の確認
ソースコードは次のようになります。
01<!DOCTYPE html>
02<html lang="ja">
03<head>
04<meta charset="utf-8">
05<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0 user-scalable=no">
06<title>JavaScriptのテストプログラム</title>
07</head>
08<body style="background-color:#000;">
09<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto;" id="bg0"></canvas>
10<script>
11
12//キャンバスの準備
13var winW = window.innerWidth;
14var winH = window.innerHeight;
15var CWIDTH = 800;
16var CHEIGHT = 800;
17if( winW < winH )
18 winH = winW;
19else
20 winW = winH;
21var SCALE = winW / CWIDTH;
22
23var canvas = document.getElementById("bg0");
24canvas.width = winW;
25canvas.height = winH;
26var cnt = canvas.getContext("2d");
27cnt.scale( SCALE, SCALE );
28cnt.textAlign = "center";
29cnt.textBaseline = "middle";
30
31//画像ファイルの処理
32var img = [];
33var imgPre = [];
34
35function loadImg( n ) {//画像を読み込む
36 imgPre[n] = false;
37 img[n] = new Image();
38 img[n].src = "example311_" + n + ".png";
39 img[n].onload = function() { imgPre[n] = true; }
40}
41
42function drawImg( ino, cx, cy ) {//画像の表示
43 if( imgPre[ino] != true ) return;
44 cnt.drawImage( img[ino], cx, cy );
45}
46
47function drawImgR( ino, x, y, ang ) {//画像の回転表示
48 if( imgPre[ino] != true ) return;
49 var w = img[ino].width;
50 var h = img[ino].height;
51 cnt.save();
52 cnt.translate( x, y );
53 cnt.rotate( Math.PI*ang/180 );
54 cnt.drawImage( img[ino], -w/2, -h/2 );
55 cnt.restore();
56}
57
58function clrCanvas() {//キャンバスをクリアする
59 cnt.clearRect( 0, 0, CWIDTH, CHEIGHT );
60}
61
62function fText( str, x, y, siz, col ) {//文字の表示
63 cnt.font = siz + "px monospace";
64 cnt.fillStyle = col;
65 cnt.fillText( str, x, y );
66}
67
68function fTextR( str, x, y, siz, col, ang ) {//文字の回転表示
69 cnt.save();
70 cnt.translate( x, y );
71 cnt.rotate( Math.PI*ang/180 );
72 cnt.font = siz + "px monospace";
73 cnt.fillStyle = col;
74 cnt.fillText( str, 0, 0 );
75 cnt.restore();
76}
77
78var tmr = 0;
79loadImg(0);
80window.onload = mainProc();
81function mainProc() {
82 tmr ++;
83 clrCanvas();
84 drawImgR( 0, CWIDTH/2, CHEIGHT/2, tmr );
85 fText( "画像の回転角度 "+tmr, CWIDTH/2, CHEIGHT-50, 32, "#0f0" );
86 fTextR( "低速回転", 100, 100, 32, "#f00", tmr/2 );
87 fTextR( "高速回転", CWIDTH-100, CHEIGHT-100, 32, "#0ff", tmr*8 );
88
89 setTimeout( mainProc, 100 );
90}
91</script>
92</body>
93</html>

画像の通常表示と回転表示、文字の通常表示と回転表示、それぞれの関数を用意しましたので、見比べてみましょう。 回転表示は次のような流れで行います。

save()でキャンバスの状態を保存
 ↓
translate( x, y )で原点を(x,y)に移動
 ↓
rotate(角度)で回転 = 描画する向きが変わる
 ↓
画像もしくは文字を描く
 ↓
restore()でキャンバスの状態を復旧

save() と restore() は 2-3 でも用いましたが、 context.save() は canvas の context に設定した状態を保存する命令、 context.restore() は 保存しておいた context の状態を復元する命令です。
rotate()の角度は 1-7(1) の円の描画と同様にラジアンで指定します。

今回用意した画像を回転表示する関数、文字を回転表示する関数とも、画像及び文字を表示する中心座標を引数で指定します。 画像は大きさ(幅wと高さh)を調べ cnt.drawImage( img[ino], -w/2, -h/2 ); として表示することで中心になるようにしています。

それから今回、次のようなキャンバスを透明色でクリアする関数を用意しました。

function clrCanvas() {
cnt.clearRect( 0, 0, CWIDTH, CHEIGHT );
}

clearRect命令はキャンバスの指定した範囲が何も表示されていない状態になります。


(2)レイヤー表示を実現する

キャンバスを複数配置し、スタイルでキャンバスの位置を重ねることで、ブラウザ画面でレイヤー表示を行うことができます。
example312.html ← 動作の確認
ソースコードは次のようになります。
01<!DOCTYPE html>
02<html lang="ja">
03<head>
04<meta charset="utf-8">
05<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0 user-scalable=no">
06<title>JavaScriptのテストプログラム</title>
07</head>
08<body style="background-image:url('example312_back.png');">
09<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto; z-index:3;" id="bg0"></canvas>
10<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto; z-index:2;" id="bg1"></canvas>
11<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto; z-index:1;" id="bg2"></canvas>
12<script>
13
14//キャンバスの準備
15var winW = window.innerWidth;
16var winH = window.innerHeight;
17var CWIDTH = 640;
18var CHEIGHT = 640;
19if( winW < winH )
20 winH = winW;
21else
22 winW = winH;
23var SCALE = winW / CWIDTH;
24
25var canvas = [];
26var cnt = [];
27for( var i = 0; i < 3; i ++ ) {
28 canvas[i] = document.getElementById("bg"+i);
29 cnt[i] = canvas[i].getContext("2d");
30 canvas[i].width = winW;
31 canvas[i].height = winH;
32 cnt[i].scale( SCALE, SCALE );
33 cnt[i].textAlign = "center";
34 cnt[i].textBaseline = "middle";
35}
36
37//画像ファイルの処理
38var img = [];
39var imgPre = [];
40
41function loadImg( n ) {//画像を読み込む
42 imgPre[n] = false;
43 img[n] = new Image();
44 img[n].src = "example312_" + n + ".png";
45 img[n].onload = function() { imgPre[n] = true; }
46}
47
48function drawImg( cn, ino, x, y ) {//画像の表示
49 if( imgPre[ino] != true ) return false;
50 cnt[cn].drawImage( img[ino], x, y );
51 cnt[cn].restore();
52 return true;
53}
54
55function drawImgR( cn, ino, x, y, sc, ang ) {//画像の回転表示
56 if( imgPre[ino] != true ) return false;
57 var w = img[ino].width;
58 var h = img[ino].height;
59 cnt[cn].save();
60 cnt[cn].translate( x, y );
61 cnt[cn].rotate( Math.PI*ang/180 );
62 cnt[cn].scale( sc, sc );
63 cnt[cn].drawImage( img[ino], -w/2, -h/2 );
64 cnt[cn].restore();
65 return true;
66}
67
68function clrCanvas( cn ) {//キャンバスをクリアする
69 cnt[cn].clearRect( 0, 0, CWIDTH, CHEIGHT );
70}
71
72function fTextR( cn, str, x, y, siz, col, ang ) {//文字の回転表示
73 cnt[cn].save();
74 cnt[cn].translate( x, y );
75 cnt[cn].rotate( Math.PI*ang/180 );
76 cnt[cn].font = siz + "px monospace";
77 cnt[cn].fillStyle = col;
78 cnt[cn].fillText( str, 0, 0 );
79 cnt[cn].restore();
80}
81
82function _sin( ang ) {//三角関数 sin
83 return Math.sin( Math.PI*ang/180 );
84}
85
86function _cos( ang ) {//三角関数 cos
87 return Math.cos( Math.PI*ang/180 );
88}
89
90//日時の取得
91var year, month, date, day, hour, min, sec;
92function getDate() {
93 var D = new Date();
94 year = D.getFullYear();//西暦
95 month = D.getMonth();//月 1月は0
96 date = D.getDate();//日
97 day = D.getDay();//曜日 日曜は0
98 hour = D.getHours();//時間
99 min = D.getMinutes();//分
100 sec = D.getSeconds();//秒
101}
102
103var idx = 0;
104var tmr = 0;
105
106window.onload = mainProc();
107function mainProc() {
108 var i;
109 tmr ++;
110 switch( idx ) {
111 case 0://初期化(画像読み込み)
112 loadImg(0);
113 loadImg(1);
114 idx = 1;
115 break;
116
117 case 1://背景画像と、円状に並んだ時分秒の数字を、一度だけ描く
118 if( drawImg( 2, 0, 0, 0 ) == true ) {
119  for( i = 0; i < 60; i ++ ) fTextR( 1, i, 320+280*_cos(6*i-90), 320+280*_sin(6*i-90), 24, "#4f8", 6*i-90 );//秒
120  for( i = 0; i < 60; i ++ ) fTextR( 1, i, 320+240*_cos(6*i-90), 320+240*_sin(6*i-90), 24, "#ee4", 6*i-90 );//分
121  for( i = 0; i < 24; i ++ ) fTextR( 1, i, 320+180*_cos(15*i-90), 320+180*_sin(15*i-90), 32, "#fa0", 15*i-90 );//時
122  idx = 2;
123 }
124 break;
125
126 case 2://日時の表示
127 getDate();
128 clrCanvas(0);
129 drawImgR( 0, 1, 320, 320, 6, tmr );
130 fTextR( 0, sec, 320+280*_cos(6*sec-90), 320+280*_sin(6*sec-90), 28, "#fff", 6*sec-90 );//秒
131 fTextR( 0, min, 320+240*_cos(6*min-90), 320+240*_sin(6*min-90), 28, "#fff", 6*min-90 );//分
132 fTextR( 0, hour, 320+180*_cos(15*hour-90), 320+180*_sin(15*hour-90), 40, "#fff", 15*hour-90 );//時
133 fTextR( 0, year+":"+(month+1)+":"+date, 320, 320, 32, "#000", tmr );//日付
134 break;
135 }
136
137 setTimeout( mainProc, 100 );
138}
139</script>
140</body>
141</html>

HTMLのキャンバス要素の記述を抜粋します。

<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto; z-index:3;" id="bg0"></canvas>

<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto; z-index:2;" id="bg1"></canvas>

<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto; z-index:1;" id="bg2"></canvas>

z-index は 重ね合わせの順番で、値が大きいほど手前に表示されます。
今回のサンプルでは
1.一番奥(z-index:1)のキャンバスに宇宙の背景画像を表示
2.一つ奥(z-index:2)のキャンバスに円状に時分秒の数字を表示
3.手前(z-index:3)のキャンバスに回転する光の画像、現在時間の白い数字、日付の黒い文字を表示しています。
常に描き変えているのは3のキャンバスのみです。

キャンバスとコンテキスト用の変数は配列で用意しています。

var canvas = [];
var cnt = [];

画像や文字を表示する関数は、どのキャンバスに表示するかを引数で指定できるようにしています。

時分秒の数字を円状に表示するのに三角関数( sin, cos )を用いています。続いて三角関数について説明します。


(3)三角関数について

高校数学で学ぶ三角関数はプログラミングの世界でも用いられます。 ゲーム制作では、画面の表示位置やエフェクト表示などで使います。

数学では三角関数を次のように表します。

正弦 sinθ = y / r
余弦 cosθ = x / r
正接 tanθ = y / x



プログラミングでは三角関数に角度(θ)を与え、sin、cos、tanの値を求めます。 例えばθが30°の時、JavaScriptでは次のように記述します。

var s = Math.sin(Math.PI*30/180);
var c = Math.cos(Math.PI*30/180);
var t = Math.tan(Math.PI*30/180);


また上図で角度θ、半径rの時の x, y の値は

x = r * Math.cos(Math.PI*θ/180);
y = r * Math.sin(Math.PI*θ/180);

となります。 (2)のソースコードではこの計算式で、秒、分、時の表示座標を決めています。

JavaScriptの三角関数、角度、座標の補足
三角関数に与える角度はラジアンであること、 角度は 1-7(1) で説明しましたように、 中心から右水平ラインが0で、そこから時計回りとなることにご注意下さい。 数学は反時計回りです。またコンピューターと数学ではY座標の向きが逆となります。



前のページへ / 次のページへ

お気軽にお問い合わせ下さい →