| index | 画面 | 内容 |
|---|---|---|
| 0 | タイトル | 画面をタップしたらゲーム開始 |
| 1 | ゲーム | タップした位置に猫を移動させる ネズミに触れたら点数を増やし、一定数捕まえたらステージクリア 持ち時間を減らしていき、時間0でゲームオーバー 犬に接触したら持ち時間を多く減らす |
| 2 | ステージクリア | 一定時間文字を表示する ステージ数が10の場合はエンディングへ そうでなければステージ数を+1し再びゲーム開始 |
| 3 | エンディング | 「おめでとう!」の文字を表示し、タイトルに戻る |
| 4 | ゲームオーバー | 一定時間文字を表示したらタイトルに戻る |
function fOval( x, y, r, sc, col ) {
cnt.fillStyle = col;
cnt.save();
cnt.translate( x, y );
cnt.scale( sc, 1 );
cnt.beginPath();
cnt.arc( 0, 0, r, 0, Math.PI*2, false );
cnt.fill();
cnt.restore();
}
中心座標、半径、縦横比、色を指定し、楕円を描く関数です。
context.save() は canvas の context に設定した状態(色やスケールなど)を保存する命令です。
translateで(x,y)に描画の原点を移動、scaleで縦横の伸縮を設定し、arcで円を描いています。
最後にcontext.restore()で保存しておいたcontextの状態を復元します。
saveとrestoreを行わないと、以後の描画がここでtranslateやscaleした値で行われてしまいます。
次が3種類の動物を表示する関数です。
fRectは矩形を描く関数、fTriは三角を描く関数で、fOvalと同じように定義しています(ソースコードは(2)をご覧下さい)。
効率の良いソースコードを書くには、このように何度も使う処理を関数として定義します。
・ネズミを描く 引数は座標と顔の色
function drawRat( x, y, col ) {
fOval( x-24, y-30, 20, 1.0, col );//左耳
fOval( x-22, y-28, 12, 1.0, "#fac" );
fOval( x+24, y-30, 20, 1.0, col );//右耳
fOval( x+22, y-28, 12, 1.0, "#fac" );
fOval( x, y+ 6, 36, 1.0, col );//顔
fOval( x-18, y- 6, 6, 1.0, "#000" );//左目
fOval( x+18, y- 6, 6, 1.0, "#000" );//右目
fOval( x, y+12, 10, 1.0, "#444" );//鼻
}
・猫を描く 引数は座標、顔の色、目の色
function drawCat( x, y, colFace, colEye ) {
fOval( x, y, 40, 1.5, colFace );//顔
fTri( x, y, x-30, y-60, x-60, y, colFace );//左耳
fTri( x, y, x+30, y-60, x+60, y, colFace );//右耳
fOval( x-24, y, 8, 2.0, colEye ); fOval( x-24, y, 8, 0.5, "#000" );//左目
fOval( x+24, y, 8, 2.0, colEye ); fOval( x+24, y, 8, 0.5, "#000" );//右目
}
・犬を描く 引数は座標と顔の色
function drawDog( x, y, col ) {
var i, tx, ty;
var chin = 20*(tmr%2);
fRect( x-60, y-30, 120, 50, col );//頬
fRect( x-40, y+40+chin, 80, 30, col );//下あご
fTri( x-60, y-30, x-30, y-80, x, y-30, col );//左耳
fTri( x, y-30, x+30, y-80, x+60, y-30, col );//右耳
for( i = 0; i <= 3; i ++ ) {//上の歯
tx = x-60 + 30*i;
ty = y+20;
fTri( tx, ty, tx+15, ty+20, tx+30, ty, "#fff" );
}
for( i = 0; i <= 2; i ++ ) {//下の歯
tx = x-45 + 30*i;
ty = y+40+chin;
fTri( tx, ty, tx+15, ty-20, tx+30, ty, "#fff" );
}
fTri( x-50, y-20, x-40, y+5, x-25, y-5, "#f00" );//左目
fTri( x+50, y-20, x+40, y+5, x+25, y-5, "#f00" );//右目
fOval( x, y+8, 16, 1.5, "#422" );//鼻
}
chin = 20*(tmr%2);はあごの動き用の変数です。
| 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;" id="mybody"> |
| 09 | <canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto;" id="bg"></canvas> |
| 10 | <script> |
| 11 | var winW = window.innerWidth; |
| 12 | var winH = window.innerHeight; |
| 13 | var SCALE; |
| 14 | var canvas = document.getElementById("bg"); |
| 15 | if( winW < winH ) { |
| 16 | canvas.width = winW; |
| 17 | canvas.height = winW; |
| 18 | SCALE = winW / 800; |
| 19 | } |
| 20 | else { |
| 21 | canvas.width = winH; |
| 22 | canvas.height = winH; |
| 23 | SCALE = winH / 800; |
| 24 | } |
| 25 | var cnt = canvas.getContext("2d"); |
| 26 | cnt.textAlign = "center"; |
| 27 | cnt.textBaseline = "middle"; |
| 28 | cnt.scale( SCALE, SCALE ); |
| 29 | |
| 30 | //マウスとタップの判定 |
| 31 | var tapX = 0, tapY = 0, tapC = 0; |
| 32 | |
| 33 | canvas.addEventListener( "touchstart", touchStart ); |
| 34 | canvas.addEventListener( "touchend", touchEnd ); |
| 35 | function touchStart(event) { |
| 36 | event.preventDefault(); |
| 37 | var rect = event.target.getBoundingClientRect(); |
| 38 | tapX = event.touches[0].clientX-rect.left; |
| 39 | tapY = event.touches[0].clientY-rect.top; |
| 40 | transformXY(); |
| 41 | tapC = 1; |
| 42 | } |
| 43 | function touchEnd(event) { |
| 44 | event.preventDefault(); |
| 45 | tapC = 0; |
| 46 | } |
| 47 | |
| 48 | canvas.addEventListener( "mousedown", mouseDown ); |
| 49 | canvas.addEventListener( "mouseup", mouseUp ); |
| 50 | function mouseDown(event) { |
| 51 | var rect = event.target.getBoundingClientRect(); |
| 52 | tapX = event.clientX-rect.left; |
| 53 | tapY = event.clientY-rect.top; |
| 54 | transformXY(); |
| 55 | tapC = 1; |
| 56 | } |
| 57 | function mouseUp(event) { |
| 58 | tapC = 0; |
| 59 | } |
| 60 | |
| 61 | function transformXY() {//実座標→仮想座標への変換 |
| 62 | tapX = toInt(tapX/SCALE); |
| 63 | tapY = toInt(tapY/SCALE); |
| 64 | } |
| 65 | |
| 66 | function toInt( val ) {//整数を返す関数 |
| 67 | return parseInt(val); |
| 68 | } |
| 69 | |
| 70 | function rnd( max ) {//乱数を返す関数 |
| 71 | return toInt( Math.random()*max ); |
| 72 | } |
| 73 | |
| 74 | //描画用の関数 |
| 75 | function fontSize( siz ) {//フォントの大きさをセット |
| 76 | cnt.font = siz + "px monospace"; |
| 77 | } |
| 78 | |
| 79 | function fText( str, x, y, col ) {//文字表示 |
| 80 | cnt.fillStyle = "#000"; cnt.fillText( str, x, y+2 );//文字に影を付ける |
| 81 | cnt.fillStyle = col; cnt.fillText( str, x, y ); |
| 82 | } |
| 83 | |
| 84 | function fRect( x, y, w, h, col ) {//矩形 |
| 85 | cnt.fillStyle = col; |
| 86 | cnt.fillRect( x, y, w, h ); |
| 87 | } |
| 88 | |
| 89 | function fTri( x1, y1, x2, y2, x3, y3, col ) {//三角 |
| 90 | cnt.fillStyle = col; |
| 91 | cnt.beginPath(); |
| 92 | cnt.moveTo(x1,y1); |
| 93 | cnt.lineTo(x2,y2); |
| 94 | cnt.lineTo(x3,y3); |
| 95 | cnt.closePath(); |
| 96 | cnt.fill(); |
| 97 | } |
| 98 | |
| 99 | function fOval( x, y, r, sc, col ) {//楕円 |
| 100 | cnt.fillStyle = col; |
| 101 | cnt.save(); |
| 102 | cnt.translate( x, y ); |
| 103 | cnt.scale( sc, 1 ); |
| 104 | cnt.beginPath(); |
| 105 | cnt.arc( 0, 0, r, 0, Math.PI*2, false ); |
| 106 | cnt.fill(); |
| 107 | cnt.restore(); |
| 108 | } |
| 109 | |
| 110 | function drawRat( x, y, col ) {//ネズミ |
| 111 | fOval( x-24, y-30, 20, 1.0, col );//左耳 |
| 112 | fOval( x-22, y-28, 12, 1.0, "#fac" ); |
| 113 | fOval( x+24, y-30, 20, 1.0, col );//右耳 |
| 114 | fOval( x+22, y-28, 12, 1.0, "#fac" ); |
| 115 | fOval( x, y+ 6, 36, 1.0, col );//顔 |
| 116 | fOval( x-18, y- 6, 6, 1.0, "#000" );//左目 |
| 117 | fOval( x+18, y- 6, 6, 1.0, "#000" );//右目 |
| 118 | fOval( x, y+12, 10, 1.0, "#444" );//鼻 |
| 119 | } |
| 120 | |
| 121 | function drawTap( x, y, col ) {//肉球(タップした位置) |
| 122 | fOval( x, y, 16, 1.0, col ); |
| 123 | fOval( x-20, y-12, 6, 1.0, col ); |
| 124 | fOval( x- 8, y-22, 6, 1.0, col ); |
| 125 | fOval( x+ 8, y-22, 6, 1.0, col ); |
| 126 | fOval( x+20, y-12, 6, 1.0, col ); |
| 127 | } |
| 128 | |
| 129 | function drawCat( x, y, colFace, colEye ) {//猫 |
| 130 | fOval( x, y, 40, 1.5, colFace );//顔 |
| 131 | fTri( x, y, x-30, y-60, x-60, y, colFace );//左耳 |
| 132 | fTri( x, y, x+30, y-60, x+60, y, colFace );//右耳 |
| 133 | fOval( x-24, y, 8, 2.0, colEye ); fOval( x-24, y, 8, 0.5, "#000" );//左目 |
| 134 | fOval( x+24, y, 8, 2.0, colEye ); fOval( x+24, y, 8, 0.5, "#000" );//右目 |
| 135 | } |
| 136 | |
| 137 | function drawDog( x, y, col ) {//犬 |
| 138 | var i, tx, ty; |
| 139 | var chin = 20*(tmr%2); |
| 140 | fRect( x-60, y-30, 120, 50, col );//頬 |
| 141 | fRect( x-40, y+40+chin, 80, 30, col );//下あご |
| 142 | fTri( x-60, y-30, x-30, y-80, x, y-30, col );//左耳 |
| 143 | fTri( x, y-30, x+30, y-80, x+60, y-30, col );//右耳 |
| 144 | for( i = 0; i <= 3; i ++ ) {//上の歯 |
| 145 | tx = x-60 + 30*i; |
| 146 | ty = y+20; |
| 147 | fTri( tx, ty, tx+15, ty+20, tx+30, ty, "#fff" ); |
| 148 | } |
| 149 | for( i = 0; i <= 2; i ++ ) {//下の歯 |
| 150 | tx = x-45 + 30*i; |
| 151 | ty = y+40+chin; |
| 152 | fTri( tx, ty, tx+15, ty-20, tx+30, ty, "#fff" ); |
| 153 | } |
| 154 | fTri( x-50, y-20, x-40, y+5, x-25, y-5, "#f00" );//左目 |
| 155 | fTri( x+50, y-20, x+40, y+5, x+25, y-5, "#f00" );//右目 |
| 156 | fOval( x, y+8, 16, 1.5, "#422" );//鼻 |
| 157 | } |
| 158 | |
| 159 | //ヒットチェックを行う関数 |
| 160 | function hitCheck( x1, y1, x2, y2, r ) { |
| 161 | var dis = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) ); |
| 162 | if( dis < r ) return true; |
| 163 | return false; |
| 164 | } |
| 165 | |
| 166 | //変数の宣言 |
| 167 | var idx = 0; |
| 168 | var tmr = 0; |
| 169 | var score = 0; |
| 170 | var stage = 0; |
| 171 | var gtime = 0; |
| 172 | var norma = 0; |
| 173 | var catCol = "#000";//猫の色 |
| 174 | var catX = 400, catY = 400; |
| 175 | var ratX = 200, ratY = 400, ratXP = 0; |
| 176 | var dogX = 600, dogY = 400, dogXP = 0, dogYP = 0; |
| 177 | |
| 178 | //各ステージの背景の色 |
| 179 | var BG_COLOR = [ "#000", "#C00", "#B40", "#A60", "#880", "#4A0", "#0A8", "#08C", "#06F", "#62F", "#A0A", ]; |
| 180 | |
| 181 | function gameStart() {//ゲーム開始時に初期化する変数 |
| 182 | gtime = 600;//持ち時間 |
| 183 | norma = stage*2;//何匹捕まえればクリアか |
| 184 | catCol = "#000"; |
| 185 | catX = 400; catY = 400;//猫の座標 |
| 186 | ratX = 200; ratY = 40+rnd(720); ratXP = -8-stage;//ネズミ |
| 187 | dogX = 600; dogY = 40+rnd(720); dogXP = 12+stage; dogYP = 8+stage;//犬 |
| 188 | tapX = 0; tapY = 0; tapC = 0;//タップ値をクリア |
| 189 | document.getElementById("mybody").style.backgroundColor = BG_COLOR[stage]; |
| 190 | idx = 1;//ゲームの処理へ移行 |
| 191 | tmr = 0; |
| 192 | } |
| 193 | |
| 194 | window.onload = indexProc(); |
| 195 | function indexProc() { |
| 196 | var i, x, y; |
| 197 | tmr ++; |
| 198 | |
| 199 | //バックを描く |
| 200 | fRect( 0, 0, 800, 800, BG_COLOR[stage] ); |
| 201 | cnt.globalAlpha = 0.2; |
| 202 | for( i = 0; i < 10; i ++ ) {//チェック柄 |
| 203 | x = 80*i+20; fRect( x, 0, 40, 800, "#fff" );//縦のライン |
| 204 | y = 80*i+20; fRect( 0, y, 800, 40, "#ccc" );//横のライン |
| 205 | } |
| 206 | cnt.globalAlpha = 1.0; |
| 207 | |
| 208 | drawRat( ratX, ratY, "#888" ); |
| 209 | drawCat( catX, catY, catCol, "#cf0" ); |
| 210 | drawDog( dogX, dogY, "#840" ); |
| 211 | |
| 212 | fontSize(40); |
| 213 | fText( "STAGE:"+stage, 150, 40, "#4ef" ); |
| 214 | fText( "SCORE:"+score, 400, 40, "#0f8" ); |
| 215 | fText( "TIME:"+gtime, 650, 40, "#ff4" ); |
| 216 | |
| 217 | switch( idx ) { |
| 218 | case 0: |
| 219 | fontSize(80); fText( "ねこアクション(仮)", 400, 200, "#fff" ); |
| 220 | fontSize(40); fText( "画面をタップしてスタート", 400, 600, "#dbf" ); |
| 221 | if( tapC == 1 ) { |
| 222 | score = 0; |
| 223 | stage = 1; |
| 224 | gameStart(); |
| 225 | } |
| 226 | break; |
| 227 | |
| 228 | case 1: |
| 229 | fText( "あと " + norma + "匹捕まえればクリア", 400, 720, "#fff" ); |
| 230 | drawTap( tapX, tapY, "#fff" ); |
| 231 | //猫の動き |
| 232 | catCol = "#000"; |
| 233 | if( tapX != 0 && tapY != 0 ) { |
| 234 | if( tapX > catX ) catX += 20; |
| 235 | if( tapX < catX ) catX -= 20; |
| 236 | if( tapY > catY ) catY += 20; |
| 237 | if( tapY < catY ) catY -= 20; |
| 238 | } |
| 239 | //ネズミの動き |
| 240 | if( ratX+ratXP < 40 || ratX+ratXP > 760 ) ratXP = -ratXP; |
| 241 | ratX += ratXP; |
| 242 | if( hitCheck( catX, catY, ratX, ratY, 90 ) == true ) {//捕まえたか? |
| 243 | catCol = "#fff"; |
| 244 | score += toInt(gtime/10); |
| 245 | norma --; |
| 246 | if( norma == 0 ) { idx = 2; tmr = 0; break; } |
| 247 | ratX = 50+rnd(700); |
| 248 | ratY = 50+rnd(700); |
| 249 | } |
| 250 | //犬の動き |
| 251 | if( dogX+dogXP < 40 || dogX+dogXP > 760 ) dogXP = -dogXP; |
| 252 | if( dogY+dogYP < 40 || dogY+dogYP > 760 ) dogYP = -dogYP; |
| 253 | dogX += dogXP; |
| 254 | dogY += dogYP; |
| 255 | if( hitCheck( catX, catY, dogX, dogY, 120 ) == true ) {//犬と接触したか? |
| 256 | if( tmr%2 == 0 ) catCol = "#f00"; |
| 257 | gtime -= 10; |
| 258 | } |
| 259 | gtime --; |
| 260 | if( gtime < 0 ) { gtime = 0; idx = 4; tmr = 0; } |
| 261 | break; |
| 262 | |
| 263 | case 2: |
| 264 | y = 400; if( tmr < 6 ) y = 10*tmr*tmr; |
| 265 | fontSize(60); |
| 266 | fText( "ステージクリア!", 400, y, "#48f" ); |
| 267 | if( tmr == 30 ) { |
| 268 | if( stage == 10 ) { |
| 269 | idx = 3; |
| 270 | tmr = 0; |
| 271 | } |
| 272 | else { |
| 273 | stage ++; |
| 274 | gameStart(); |
| 275 | } |
| 276 | } |
| 277 | break; |
| 278 | |
| 279 | case 3: |
| 280 | y = 400; if( tmr < 20 ) y = tmr*tmr; |
| 281 | fontSize(60); |
| 282 | fText( "おめでとう!", 400, y-100, "#fc0" ); |
| 283 | fText( "全ステージクリアです!", 400, y, "#0f0" ); |
| 284 | if( tmr == 100 ) idx = 0; |
| 285 | break; |
| 286 | |
| 287 | case 4: |
| 288 | y = 400; if( tmr < 6 ) y = 10*tmr*tmr; |
| 289 | fontSize(60); |
| 290 | fText( "ゲームオーバー", 400, y, "#f00" ); |
| 291 | if( tmr == 50 ) idx = 0; |
| 292 | break; |
| 293 | } |
| 294 | |
| 295 | setTimeout( indexProc, 100 ); |
| 296 | } |
| 297 | </script> |
| 298 | </body> |
| 299 | </html> |
| 変数名 | 用途 | 何を行っているか |
|---|---|---|
| idx,tmr | インデックスとタイマー | プログラム全体の流れを管理 |
| score | 点数 | ネズミを捕まえた時に増やす 増やす値は (ゲームの残り時間÷10) としている |
| stage | ステージ数 | 1から10ステージまで |
| gtime | ゲーム時間 | 0になったらゲームオーバー |
| norma | クリアに必要なネズミの捕獲数 | ゲーム開始時に値をセット ネズミを捕まえたら1減らし、0になればゲームクリア |
| catCol | 猫の色 | ネズミを捕まえた時に白、犬と接触した時に赤にしている |
| catX,catY | 猫の座標 | タップした位置(tapX,tapY)に猫を移動させる |
| ratX,ratY | ネズミの座標 | |
| ratXP | ネズミの移動の座標の増減値 | X方向に移動する値 ステージが進むほど移動量を大きくしている 画面の左右端に来たら値を反転し向きを変える |
| dogX,dogY | 犬の座標 | |
| dogXP,dogYP | 犬の移動の座標の増減値 | X方向、Y方向に移動する値 ステージが進むほど移動量を大きくしている 画面上下左右に来たら向きを変える |
| var BG_COLOR = [ "#000", "#C00", "#B40", "#A60", "#880", "#4A0", "#0A8", "#08C", "#06F", "#62F", "#A0A", ]; |
いつでもタップとマウスイベントの両方を実装しておけばよいのでは?
タップ入力できるWindows-PCが安いものでは3万円程度で購入できるようになりました。
タップ入力のWindows-PCは今後更に普及するでしょうから、マルチプラットフォーム対応のゲームを作るなら、タップとマウス両方の操作ができると良いでしょう。
その際、注意すべき点があります。
今回のソースコードはタップとマウスの入力値を取得するだけの処理ですから両方実装して問題ありません。
では例えばタップかマウス入力があった時に一度だけ重要な処理を行うプログラムではどうでしょうか?
ユーザーがマウス操作しながら液晶画面に触れた時などに重要な処理が繰り返されないソースコードを書く必要があります。
またスマートフォンとタブレットはタップイベントが前提(第一優先)です。
制作するプログラムの内容によって必要な実装を行いましょう。
function rnd( max ) {//乱数を返す関数
return toInt( Math.random()*max );
}
document.getElementById("mybody").style.backgroundColor = BG_COLOR[stage];
さあ、ここまでくれば、ご自身でゲームを開発できます。 まずは簡単な内容でよいのです。ご自身の手でゲームを制作してみましょう。
