これまでシューティングゲームをいくつか作ってきましたが、ひとつひとつの敵をランダムに出現させて移動させるだけで編隊攻撃を仕掛けてくるようなものは作ってきませんでした。
いつもお世話になっているT.Umezawaさんにスクランブルを真似してつくったゲームのテストプレイをしていただいたのですが「面白くない」とのこと。鳩が作ったゲームが面白くないのではなく、元にしたスクランブル自体が面白くないという言葉にT.Umezawaさんの愛を感じます。
うーん。たしかにこっちのほうが面白そうに見えます。
ということで、今回は編隊攻撃を実装することにします。
Contents
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 |
<!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"> <style> #canvas { display: block; margin-bottom: 10px; } </style> </head> <body> <canvas id = "canvas"></canvas> <button onclick="formation()">編隊攻撃</button> <script src= "./index.js"></script> </body> </html> |
ページが読み込まれたときの処理
ページが読み込まれたらcanvasサイズを調整して背景を黒で塗りつぶします。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const CANVAS_WIDTH = 360; const CANVAS_HEIGHT = 480; const $canvas = document.getElementById('canvas'); const ctx = $canvas.getContext('2d'); let enemies = []; window.onload = () => { $canvas.width = CANVAS_WIDTH; $canvas.height = CANVAS_HEIGHT; ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); } |
Enemyクラスの定義
敵を移動させ描画するためにEnemyクラスを定義します。Update関数で敵を移動させます。また敵の座標はthis.Timeから計算されますが、これを変えることで座標をずらして編隊のようなものをつくります。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Enemy{ constructor(delay){ this.X = 0; this.Y = 0; this.Time = -delay; this.IsDead = false; } Draw(){ ctx.fillStyle = '#fff'; const size = 32; // 敵のサイズ if(!this.IsDead) ctx.fillRect(this.X, this.Y, size, size); } Update(){ } } |
編隊の生成
ボタンをクリックしたら敵を生成して編隊をつくります。
1 2 3 4 |
function formation(){ for(let i = 0; i < 8; i++) enemies.push(new Enemy(10 * i)); // 10を増減させることで編隊の間隔を調整できる } |
更新処理
1 2 3 4 5 6 7 8 |
setInterval(() => { ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); enemies.forEach(enemy => enemy.Update()); enemies = enemies.filter(enemy => !enemy.IsDead); enemies.forEach(enemy => enemy.Draw()); }, 1000 / 30); |
編隊攻撃を実装する
Enemy.Update関数を定義することで編隊攻撃が可能になります。
急接近して待避する編隊
急接近してそのあと上方に待避する動作をする編隊を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Enemy{ Update(){ const size = 32; // 敵のサイズ const max = CANVAS_HEIGHT - 100; // 下から100pxまで接近しそのあと待避 const vx = 2; // 1更新あたりのXY方向の移動量 const vy = 8; this.Time++; this.X = this.Time * vx; if(this.Time < max / vy) // 下に移動中 this.Y = this.Time * vy; else{ // 上に待避中 this.Y = max - (this.Time * vy - max); if(this.Y < -size) // canvasの外に待避したので死亡フラグをセットする this.IsDead = true; } } } |
ジグザグ攻撃
ジグザグ攻撃をする編隊を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Enemy{ Update(){ const size = 32; // X座標はminとmaxの間を往復する // Y座標はvyずつ増加させる const min = 50; const max = CANVAS_WIDTH - size - 50; const vx = 8; const vy = 4; this.Time++; const x = Math.abs(this.Time * vx); const moveWidth = max - min; if(Math.floor(x / moveWidth) % 2 == 0) this.X = x % moveWidth + min; else this.X = moveWidth - x % moveWidth + min; this.Y = this.Time * vy; if(this.Y > CANVAS_HEIGHT) this.IsDead = true; } } |
回転攻撃
回転しながら下に移動する編隊を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Enemy{ Update(){ const radius = 80; const centerY = (CANVAS_WIDTH - 32) / 2; // Y座標の中心 this.Time++; this.X = radius * Math.cos(this.Time * 0.12) + centerY; this.Y = radius * Math.sin(this.Time * 0.12) + 1.5 * this.Time; // 回転させるとともに全体を1更新あたり1.5pxだけ下に移動させる if(this.Y > CANVAS_HEIGHT + radius) this.IsDead = true; } } |
8の字攻撃
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Enemy{ Update(){ const radius1 = 120; const radius2 = 60; const centerY = (CANVAS_WIDTH - 32) / 2; this.Time++; this.X = radius1 * Math.cos(this.Time * 0.12) + centerY; this.Y = radius2 * Math.sin(this.Time * 0.12 * 2) + 1.5 * this.Time; if(this.Y > CANVAS_HEIGHT + radius2) this.IsDead = true; } } |