今日のエッセイ

気まぐれなゲーム作りをしました。
JavaScriptのプログラムです。
ブラウザで開くだけで、オンラインでなくてもプレイできます。

ゲームタイトル:「Delta and Square」

三角形の宇宙戦闘機っぽいのが、四角形を撃つというシューティング・パズルゲームです。


ゲーム画面

カーソルキーで操作してスペースキーを押すと、緑の三角形が移動して、ピンクのショットを撃ちます。
オレンジの四角形に当たると、スコアがアップします。

Eはエネルギー数値。
四角形が画面にいる間、その数だけどんどんと減っていきます。
1つの場合は1つずつ、2つの場合は2つずつです。
画面に四角形が無い間は減りません。

四角形にショットを当てると20回復します。
スコアが2000になると100、
4000になると200回復します。

エネルギーが0になるとゲームオーバーです。
表示が出てゲームが止まります。


ゲームオーバーの画面

○プログラム

HTML(shooting.html):

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>シューティングゲーム</title>
    <style>
        canvas {
            display: block;
            margin: auto;
            background-color: #222;
        }
    </style>
</head>
<body>
    <canvas id="gameScreen" width="730" height="550"></canvas>
    <script src="shooting.js"></script>
</body>
</html>

<script src="shooting.js"></script>でJavaScriptを呼び出しています。
この場合、スクリプトのファイル名はshooting.jsです。
HTMLと同じフォルダに置きます。
別の所に置く場合はその場所を指定します。
例えば、Homeのgameフォルダの場合は
<script src="/Home/user-name/game/shooting.js"></script>
となります。
画面のサイズはcanvasタグで設定します。
<canvas id="gameScreen" width="730" height="550"></canvas>

JavaScript(shooting.js):

const canvas = document.getElementById('gameScreen');
const ctx = canvas.getContext('2d');

// プレイヤーの設定
const player = {
    x: canvas.width / 2,
    y: canvas.height - 50,
    width: 50,
    height: 50,
    speed: 5,
    bullets: []
};

// 敵の設定
const enemies = [];

// 弾丸の設定
const bulletSpeed = 10;
const bulletSize = 9;

// 敵を追加する関数

function addEnemy() {
    const enemy = {
        x: Math.random() * (canvas.width - 50),
        y: 0,
        width: 50,
        height: 50,
        speed: 2
    };

    enemies.push(enemy);
}

function addEnemyb() {
    const enemy = {
        x: Math.random() * (canvas.width - 100),
        y: 0,
        width: 100,
        height: 100,
        speed: 3
    };

    enemies.push(enemy);
}

// キー入力の処理
const keys = {};
document.addEventListener('keydown', (e) => {
    keys[e.code] = true;
});
document.addEventListener('keyup', (e) => {
    keys[e.code] = false;
});

// プレイヤーの描画 fillRect:塗りつぶし四角
function drawPlayer() {
    ctx.fillStyle = '#00FF33';
    //ctx.fillRect(player.x, player.y, player.width, player.height);
    ctx.beginPath();
    ctx.moveTo(player.x, player.y);
    ctx.lineTo(player.x-25, player.y+50);
    ctx.lineTo(player.x+25, player.y+50);
    ctx.fill();
    ctx.closePath();
}

// 敵の描画
function drawEnemies() {
    ctx.fillStyle = '#FF9900';
    for (let enemy of enemies) {
        ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
    }
}

// 弾丸の描画
function drawBullets() {
    ctx.fillStyle = '#FFAAAA';
    for (let bullet of player.bullets) {
        ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
    }
}

function drawScore() {
    ctx.font = "25px Arial";
    ctx.fillStyle = "#00DDff";
    ctx.fillText("Score:" + score.toFixed(), 550, 40);
      }

function drawHp() {
    ctx.font = "25px Arial";
    ctx.fillStyle = "#00DD00";
    ctx.fillText("E:" + hp.toFixed(), 50, 40);
      }
      
function drawGameover() {
    ctx.font = "50px Arial";
    ctx.fillStyle = "#00FF99";
    ctx.fillText("Game Over. Try again.", 100, 350);
      }

let score=0;
let hp=2000;
var er=0;
let gameover=false;
// ゲームの更新
function update() {
er=Math.floor(Math.random()*3);

    // プレイヤーの移動
    if (keys['ArrowLeft'] && player.x-25 > 0) {
        player.x -= player.speed;
    }
    if (keys['ArrowRight'] && player.x-25 < canvas.width - player.width) {
        player.x += player.speed;
    }
    if (keys['ArrowUp'] && player.y >  0) {
        player.y -= player.speed;
    }
    if (keys['ArrowDown'] && player.y <  canvas.height - player.height) {
        player.y += player.speed;
    }

      // 弾丸の発射
    if (keys['Space']) {
        const bullet = {
            x: player.x-25 + player.width / 2 - bulletSize / 2,
            y: player.y,
            width: bulletSize,
            height: bulletSize,
            speed: bulletSpeed
        };
        player.bullets.push(bullet);
        keys['Space'] = false;
    }

    // 弾丸の移動
    for (let bullet of player.bullets) {
        bullet.y -= bullet.speed;
    }

    // 敵の移動
    for (let enemy of enemies) {
        enemy.y += enemy.speed;
        if(enemy.y > canvas.height){
            enemies.splice(0,1);
                                     }
    }

    // 弾丸と敵の衝突判定
    for (let i = 0; i < player.bullets.length; i++) {
        for (let j = 0; j < enemies.length; j++) {
            const bullet = player.bullets[i];
            const enemy = enemies[j];  

            if (bullet.x < enemy.x + enemy.width &&
                bullet.x + bullet.width > enemy.x &&
                bullet.y < enemy.y + enemy.height &&
                bullet.y + bullet.height > enemy.y) {
                player.bullets.splice(i, 1);
                enemies.splice(j, 1);
                score+=100;
                if(score==2000){hp+=100;}
                if(score==4000){hp+=200;}
                hp+=20;              
                break;
            }
        }
    }


    // 敵の生成
    if (Math.random() < 0.02 && er!=1) {
        addEnemy();
    }
   if (Math.random() < 0.02 && er==1) {
        addEnemyb();
    }
   hp-=enemies.length;

     if(hp < 1){
     hp=0;
     gameover=true;
     }                       
    // 画面のクリア
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // オブジェクトの描画
    drawPlayer();
    drawEnemies();
    drawBullets();
    drawScore();
    drawHp();
    if(gameover==true){
    drawGameover();
    }

    // ループ
    if(gameover==false){
    requestAnimationFrame(update);
                             }
}

// ゲームの開始
update();

描画には画像を用意することなく、図形を描くことで表示します。

// プレイヤーの描画 fillRect:塗りつぶし四角
function drawPlayer() {
    ctx.fillStyle = '#00FF33';
    //ctx.fillRect(player.x, player.y, player.width, player.height);
    ctx.beginPath();
    ctx.moveTo(player.x, player.y);
    ctx.lineTo(player.x-25, player.y+50);
    ctx.lineTo(player.x+25, player.y+50);
    ctx.fill();
    ctx.closePath();
}

ctx.fillRect()は四角形を描く場合の関数です。
この場合は使わないので、「//」でコメントアウトしています。
ctx.beginPath()以下が三角形を描く関数になっています。

function drawScore() {
    ctx.font = "25px Arial";
    ctx.fillStyle = "#00DDff";
    ctx.fillText("Score:" + score.toFixed(), 550, 40);
      }

drawScore()は、スコア表示用関数です。
ctx.fillText()の「score.toTixed」が変数です。
変数scoreに値を入れることで画面に表示されます。
数値の「550,40」は表示位置を表します。

    for (let enemy of enemies) {
        enemy.y += enemy.speed;
        if(enemy.y > canvas.height){
            enemies.splice(0,1);
                                     }
    }

四角形の敵の移動を表示する部分です。
敵は配列enemiesに格納されます。
配列・・・箱のような入れ物で一つずつ入ります。
enemies.lengthで配列の長さを見ることができます。
格納された敵は消えない限りそのままなので、
画面の外に消えた場合は消去します。
enemies.splice()関数を使います。

                player.bullets.splice(i, 1);
                enemies.splice(j, 1);

ショットが当たった時も弾丸と敵が消えるように設定しています。

    if(gameover==false){
    requestAnimationFrame(update);
                             }

requestAnimationFrame()関数でゲームをループしています。
この場合は、ゲームオーバーのフラグの変数「gameover」がfalseである限りループします。
フラグがtrueになると、ゲームは止まります。