これまで有名なゲームを真似したものしか作ってきませんでしたが、今回は完全オリジナルのゲームを作ってみました。その名も「ワイはあの娘とつながっているんや!」
どんなゲームかというとこんなゲームです。
主人公は「あの娘」と赤い糸でつながっていて(実はそう思い込んでいるだけw)上からハサミが降ってきます。ハサミにあたると糸がダメージをうけ耐久度が0になると切れて「あの娘」はどこかへ飛んでいってしまいます。そして今日もめでたくフラれてしまうというメチャクチャなゲームです。
糸は時間の経過とともに長くなっていきますが、アイテムを取ると糸の耐久度が上がったり糸が短くなってゲームオーバーになりにくくなる仕様になっています。
では、さっそくつくってみましょう。
HTML部分
HTML部分を示します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ワイはあの娘とつながっているんや!</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel = "stylesheet" href = "./style.css" type = "text/css" media = "all"> </head> <body> <div id = "container">     <div id = "field">         <div>             <canvas id = "canvas"></canvas>         </div>         <button id = "start">ゲーム開始</button>         <button id = "up" class = "button">UP</button>         <button id = "left" class = "button">LEFT</button>         <button id = "right" class = "button">RIGHT</button>         <button id = "down" class = "button">DOWN</button>     </div>     <p>音量:         <input type = "range" id = "volume" min = "0" max = "1" step = "0.01">         <span id = "vol_range"></span>         <button onclick = "playSound()">音量テスト</button>     </p> </div><!-- .#container --> <script src= "./index.js"></script> </body> </html> | 
style.css
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | body {     background-color: #000;     color: #fff; } #container {     width: 360px;     color: #fff;     background-color: #000; } #field {     position: relative; } .button {     width: 120px;     height: 70px;     position: absolute;     background-color: transparent;     color: #fff;     font-weight: bold;     font-size: 120%;     display: none; } #up {     left: 120px;     top: 320px; } #left {     left: 10px;     top: 400px; } #right {     left: 230px;     top: 400px; } #down {     left: 120px;     top: 480px; } #start {     width: 140px;     height: 70px;     position: absolute;     background-color: transparent;     color: #fff;     font-weight: bold;     font-size: 120%;     left: 110px;     top: 280px; } | 
定数とグローバル変数
JavaScript部分の定数とグローバル変数を示します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | // canvasのサイズ const CANVAS_WIDTH = 360; const CANVAS_HEIGHT = 570; // プレイヤーの初期座標 const INIT_PLAYER_X = CANVAS_WIDTH / 2; const INIT_PLAYER_Y = 440; // プレイヤーの移動速度 const PLAYER_SPEED = 6; // 「あの娘」とプレイヤーの距離の初期の値、最小値、最大値 const INIT_DISTANCE = 120; const DISTANCE_MIN = 80; const DISTANCE_MAX = 180; // 糸の耐久度の初期の値と最大値 const INIT_LIFE = 3; const LIFE_MAX = 5; // 各キャラクタを描画するときの大きさ const PLAYER_SIZE = 48; const ENEMY_WIDTH = 28 const ENEMY_HEIGHT = 38; const ITEM_SIZE = 28; // 当たり判定用のキャラクタの大きさ const CHARCTER_SIZE = 32; // 描画のイメージ const imagePlayerMain = new Image(); const imagePlayerSub = new Image(); const imageEnemy = new Image(); const imageItem1 = new Image(); const imageItem2 = new Image(); // 効果音 const soundGameover = new Audio('./sounds/gameover.mp3'); const soundByebye = new Audio('./sounds/byebye.mp3'); const soundHihihi = new Audio('./sounds/hi.wav'); const soundMiss = new Audio('./sounds/miss.wav'); // canvas要素 const $canvas = document.getElementById('canvas'); const ctx = $canvas.getContext('2d'); // 操作用のボタン const $up = document.getElementById('up'); const $down = document.getElementById('down'); const $left = document.getElementById('left'); const $right = document.getElementById('right'); const $start = document.getElementById('start'); // スコア let score = 0; // trueならキャラクタの移動の処理を止める let stopUpdate = false; // 描画更新処理用のタイマーのID let setIntervalId = null; // オブジェクトを格納する変数、配列 let player = null; let enemies = []; let items1 = []; let items2 = []; | 
Playerクラスの定義
プレイヤーを描画するためにPlayerクラスを定義します。プレイヤーは主人公の周りを「あの娘」が回っているという通常とは変わった形態なのでMainX、MainYとSubX、SubYという二つの座標を定義します。また両者の距離、「あの娘」の回転角、糸の耐久度、移動方向などのプロパティも定義します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Player {     constructor(){         this.Init();     }     Init(){         this.MainX = INIT_PLAYER_X;         this.MainY = INIT_PLAYER_Y;         this.SubX = this.MainX;         this.SubY = this.MainY;         this.Angle = 0;         this.Speed = PLAYER_SPEED;         this.MoveUp = false;         this.MoveDown = false;         this.MoveLeft = false;         this.MoveRight = false;         this.Distance = INIT_DISTANCE;         this.Life = INIT_LIFE;     } } | 
更新処理部分を示します。
フラグを参照して移動中であるのであれば移動させます。ただしフィールドの外に移動してしまわないように注意します。そのあと「あの娘」を少しずつ遠ざけながら主人公の周りを回転させます。そして糸の耐久度が0の場合は「あの娘」を遠心力で遠くへ飛んでいくように移動させます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class Player {     Update(){         if(this.MoveRight)             this.MainX += this.Speed;         if(this.MoveLeft)             this.MainX -= this.Speed;         if(this.MoveUp)             this.MainY -= this.Speed;         if(this.MoveDown)             this.MainY += this.Speed;         // フィールドの外に移動してしまう場合は止める         if(this.MainX <= PLAYER_SIZE / 2){             this.MainX = PLAYER_SIZE / 2;             this.MoveLeft = false;         }         if(this.MainX >= CANVAS_WIDTH - PLAYER_SIZE / 2){             this.MainX = CANVAS_WIDTH - PLAYER_SIZE / 2;             this.MoveRight = false;         }         if(this.MainY <= PLAYER_SIZE / 2){             this.MainY = PLAYER_SIZE / 2;             this.MoveUp = false;         }         if(this.MainY >= CANVAS_HEIGHT - PLAYER_SIZE / 2){             this.MainY = CANVAS_HEIGHT - PLAYER_SIZE / 2;             this.MoveDown = false;         }         // 「あの娘」を回転させる         this.Angle += 0.02;         // 「あの娘」を少しずつ遠ざける         if(this.Distance + 0.05 < DISTANCE_MAX)             this.Distance += 0.05;         // 糸の耐久度が0になったら「あの娘」を遠心力で飛ばしてしまう         if(this.Life <= 0)             this.Distance += 12;         // 「あの娘」の座標を主人公の座標と回転角から計算しセットする         this.SubX = this.MainX + this.Distance * Math.cos(this.Angle);         this.SubY = this.MainY + this.Distance * Math.sin(this.Angle);     } } | 
GetHitJugePositions関数は当たり判定に使う座標の配列を返します。当たり判定の対象が自機ではなくふたつの点をつなぐ線分なので中心からの距離が32の倍数でthis.Distanceを超えない点の座標を複数用いています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Player {     GetHitJugePositions(){         const arr = [];         let distance = 0;         while(distance < this.Distance){             arr.push({                 x: this.MainX + distance * Math.cos(this.Angle),                 y:this.MainY + distance * Math.sin(this.Angle)             });             distance += 32;         }         return arr;     } } | 
プレイヤーを描画する処理を示します。糸の耐久度が1以上の場合は糸を描画します。耐久度が高い場合は太く描画します。また糸の耐久度と「あの娘」との距離も描画します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Player {     Draw(){         // 糸の耐久度が1以上の場合は糸を描画する         if(this.Life > 0) {             ctx.strokeStyle = '#f00';             ctx.lineWidth = this.Life;             ctx.beginPath();             ctx.moveTo(this.MainX,this.MainY);             ctx.lineTo(this.SubX,this.SubY);             ctx.stroke();         }         ctx.drawImage(imagePlayerMain, this.MainX - PLAYER_SIZE / 2, this.MainY - PLAYER_SIZE / 2, PLAYER_SIZE, PLAYER_SIZE);         ctx.drawImage(imagePlayerSub, this.SubX - PLAYER_SIZE / 2, this.SubY - PLAYER_SIZE / 2, PLAYER_SIZE, PLAYER_SIZE);         // 糸の耐久度と「あの娘」との距離も描画する         ctx.font = '18px Arial';         ctx.textBaseline = 'top';         ctx.fillStyle = '#fff';         ctx.fillText('LIFE  ' + this.Life, this.MainX + 32, this.MainY);         if(this.Distance < DISTANCE_MAX + 10)             ctx.fillText('DISTANCE  ' + Math.floor(this.Distance), this.MainX + 32, this.MainY + 20);     } } | 
EnemyクラスとItemXクラス
敵とアイテムを描画するためのクラスを定義します。まず基底クラスになるCharcterクラスを定義します。
引数は初期のXY座標とXY方向の移動量です。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | class Charcter {     constructor(x, y, vx, vy){         this.X = x;         this.Y = y;         this.VX = vx;         this.VY = vy;         this.IsDead = false;     }     Update(){         this.X += this.VX;         this.Y += this.VY;     } } | 
Enemyクラス、Item1クラス、Item2クラスを以下のように定義します。Draw関数で描画するイメージが違うだけです。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Enemy extends Charcter {     constructor(x, y, vx, vy){         super(x, y, vx, vy);     }     Draw(){         ctx.drawImage(imageEnemy, this.X - ENEMY_WIDTH / 2, this.Y - ENEMY_HEIGHT / 2, ENEMY_WIDTH, ENEMY_HEIGHT);     } } class Item1 extends Charcter {     constructor(x, y, vx, vy){         super(x, y, vx, vy);     }     Draw(){         ctx.drawImage(imageItem1, this.X - ITEM_SIZE / 2, this.Y - ITEM_SIZE / 2, ITEM_SIZE, ITEM_SIZE);     } } class Item2 extends Charcter {     constructor(x, y, vx, vy){         super(x, y, vx, vy);     }     Draw(){         ctx.drawImage(imageItem2, this.X - ITEM_SIZE / 2, this.Y - ITEM_SIZE / 2, ITEM_SIZE, ITEM_SIZE);     } } | 
続きは次回。
