メモ帳で作るシューティングゲーム

概要

HTMLとJavascriptの勉強がてら作成してみたシューティングゲームです。
画面のリフレッシュレートに依存しているので環境によって超鬼畜仕様だったりします。
※コード中の"変更できる値"を変えるととりあえず難易度変更可能です

遊びかた

  1. 下のコード全文をメモ帳に貼り付け

  2. xファイル名を"htmlstg2.html"、ファイルの種類を"すべてのファイル (*.*)"にして適当な場所に保存

  3. 保存したファイルを開くとブラウザでゲームが立ち上がります

こんな感じのがいきなり出てきます

コード全文

<!DOCTYPE html>

<html>

<head>

   <title>HTMLSTG2</title>
</head>
<body>

   <div id="scoreboard">score:0</div>

   <canvas id="canvas" width="600" height="400"></canvas>
    <div id="HowToPlay"><操作方法></br></div>
    <div id="move">     移動:枠内のマウスカーソルに追従</br></div>
    <div id="attack">   攻撃:自動発射</br></div>
    <div id="remarks">  自機は□、敵機は■で表示されます。</br>
                        移動方法は[M]キー、攻撃方法は[A]キーで変更可能です。</div>
    <script>
        // 変更できる値
        const startX   = 275; // 自機の初期X座標
        const startY   = 300; // 自機の初期Y座標
        const Ssize    = 10;  // 自機のサイズ
        const Sspeed   = 3;   // 自機の移動速度
        const Bnum     = 5;   // 弾丸の数
        const BsizeX   = 2;   // 弾丸の横幅
        const BsizeY   = 10;  // 弾丸の縦幅
        const Bspeed   = 5;   // 弾丸の進む速度
        const Enum     = 20;  // 敵機の数
        const EsizeX   = 30;  // 敵機の横幅
        const EsizeY   = 20;  // 敵機の縦幅 
        const Espeed   = 0.5; // 敵機の移動速度
        const interval = 200; // 弾丸発射のインターバル[ms]
        let Atype = true;     // 攻撃方法(true:自動 false:スペースキー)
        let Mtype = true;     // 移動方法(true:マウス false:矢印キー)
 
        // HTML要素の取得
        const move = document.getElementById('move');             // 移動方法
        const attack = document.getElementById('attack');         // 攻撃方法
        const scoreboard = document.getElementById('scoreboard'); // 得点表示
        const canvas = document.getElementById('canvas');         // 自機、敵機、弾丸の描画範囲
        const ctx = canvas.getContext('2d');                      // 描画部分のコンテキスト
        const targetArea = document.querySelector('canvas');      // 描画部分をマウス操作範囲に
 
        // コンストラクタ:自機
        function Ship(x, y, si, sp){
            this.size = si;   // 自機のサイズ(1辺の長さ)
            this.speed  = sp; // 自機の移動速度
            this.x = x;       // 自機のx座標(左上の角)
            this.y = y;       // 自機のy座標(左上の角)
            this.moveX = 0;   // x軸移動量
            this.moveY = 0;   // y軸移動量
        }
 
        // コンストラクタ:弾丸
        function Bullet(sX, sY, sp){
            this.sizeX = sX;   // 弾丸の横幅
            this.sizeY = sY;   // 弾丸の縦幅
            this.speed = sp;   // 弾丸の進む速度
            this.x = 0;        // 弾丸のx座標
            this.y = 0;        // 弾丸のy座標
            this.flag = false; // 弾丸の描画フラグ
        }
 
        // コンストラクタ:敵機
        function Enemy(sX, sY, sp){
            this.sizeX = sX;   // 敵機の横幅
            this.sizeY = sY;   // 敵機の縦幅
            this.speed = sp;   // 敵機の移動速度
            this.x = 0;        // 敵機のx座標
            this.y = 0;        // 敵機のy座標
            this.flag = false; // 敵機の描画フラグ
        }
 
        // 自機、弾丸、敵機のインスタンス生成
        let ship = new Ship(startX, startY, Ssize, Sspeed);                         // 自機
        let bullet = new Array(Bnum);
        for(let i = 0; i<Bnum; i++) bullet[i] = new Bullet(BsizeX, BsizeY, Bspeed); // 弾丸
        let enemy = new Array(Enum);
        for(let i = 0; i < Enum; i++) enemy[i] = new Enemy(EsizeX, EsizeY, Espeed); // 敵機
 
            // キーボード入力イベントの設定
            document.addEventListener('keydown', keydownEvent,false);
            document.addEventListener('keyup', keyupEvent,false);
 
            function keydownEvent(ed){
                if(!ed.repeat)
                switch (ed.key) {
                    case 'ArrowUp':
                        ship.moveY -= ship.speed;
                        break;
                    case 'ArrowDown':
                        ship.moveY += ship.speed;
                        break;
                    case 'ArrowLeft':
                        ship.moveX -= ship.speed;
                        break;
                    case 'ArrowRight':
                        ship.moveX += ship.speed;
                        break;
                    case ' ':
                        if(!Atype)
                            for(let i = 0; i < Bnum; i++)
                                if(!bullet[i].flag){
                                    bullet[i].flag = !bullet[i].flag;
                                    bullet[i].x = ship.x+(ship.size/2);
                                    bullet[i].y = ship.y;
                                    break;
                                }
                        break;
                    case 'a':
                        Atype = !Atype;
                        break;
                    case 'm':
                        Mtype = !Mtype;
                        break;
                    case 'r':
                        reset();
                        break;
                }
                // 移動量の上限処理(チャタリング?対策)
                if(ship.moveX<-ship.speed)ship.moveX=-ship.speed;
                if(ship.moveX>ship.speed)ship.moveX=ship.speed;
                if(ship.moveY<-ship.speed)ship.moveY=-ship.speed;
                if(ship.moveY>ship.speed)ship.moveY=ship.speed;
            }

            function keyupEvent(eu){
                switch (eu.key) {
                    case 'ArrowUp':
                        ship.moveY += ship.speed;
                        break;
                    case 'ArrowDown':
                        ship.moveY -= ship.speed;
                        break;
                    case 'ArrowLeft':
                        ship.moveX += ship.speed;
                        break;
                    case 'ArrowRight':
                        ship.moveX -= ship.speed;
                        break;
                }
            }
 
        // マウス操作イベントの設定
        targetArea.addEventListener('mousemove', moveEvent, false);
        document.addEventListener('click', clickEvent, false);
 
        function moveEvent(em){
            if(Mtype){
                ship.x = em.offsetX; // マウスのx座標を自機のx座標に
                ship.y = em.offsetY; // マウスのy座標を自機のy座標に
            }
        }

        function clickEvent(){
            if(GameOver)reset();
        }

        function reset(){
            time = 0;
            GameOver = false;
            Edirection = 0;
            score = 0;
            ship.x = startX;
            ship.y = startY;
            ship.moveX = 0;
            ship.moveY = 0;
            for(let i of Object.keys(enemy))enemy[i].flag = false;
            for(let i of Object.keys(bullet))bullet[i].flag = false;
        }
 
        let time = 0;          // インターバル計算用変数
        let HowtoAttack = " "; // 攻撃方法
        let HowtoMove = " ";   // 移動方法
        let GameOver = false;  // ゲームオーバーフラグ
        let Edirection = 0;    // 敵機の出現する方向(0:上 1:右 2:下 3:左)
        let score = 0;         // 得点
 
        // 無限ループ
        function draw(){
            ctx.clearRect(0, 0, canvas.width, canvas.height);  // 描画の初期化
            ctx.strokeRect(0, 0, canvas.width, canvas.height); // 枠線を描画
            scoreboard.textContent = "score: "+score;          // 点数の更新
 
            // 攻撃方法の描画
            if(Atype)HowtoAttack = "自動発射";
            else HowtoAttack = "スペースキー";
            attack.textContent = "攻撃:"+HowtoAttack;
 
            // 移動方法の描画
            if(Mtype)HowtoMove = "枠内のマウスカーソルに追従";
            else HowtoMove = "矢印キー";
            move.textContent = "移動:"+HowtoMove;
 
            // GameOver後の描画
            if(GameOver){
                ctx.lineWidth = 1;
                ctx.fillStyle = "#000";
                ctx.font = "50px serif";
                ctx.fillText("Game Over",170,170);
                ctx.font = "25px serif";
                ctx.fillText("score:"+score,250,220);
                ctx.font = "15px serif";
                ctx.fillText("画面クリック か [R]キー で再スタート",170,300);
            }
            else {
                // 自機の描画処理
                if(!Mtype){
                    ship.x += ship.moveX;
                    ship.y += ship.moveY;
                }
                if(ship.x < 0) ship.x = 0; // x座標の下限
                else if(ship.x > canvas.width - ship.size) ship.x = canvas.width - ship.size;   // x座標の上限
                if(ship.y < 0) ship.y = 0; // y座標の下限
                else if(ship.y > canvas.height - ship.size) ship.y = canvas.height - ship.size; // y座標の上限
                ctx.strokeRect(ship.x, ship.y, ship.size, ship.size); // 自機を描画
 
                // 弾丸の自動発射
                if(Atype)
                    if(Date.now() - time >= interval)              // インターバルを計算
                        for(let i = 0; i < Bnum; i++)
                            if(!bullet[i].flag){                        // i番目の弾丸が描画されていなければ
                                bullet[i].flag = !bullet[i].flag;       // 描画するようにフラグ変更
                                bullet[i].x = ship.x + (ship.size / 2); // 発射x座標を自機中央に指定
                                bullet[i].y = ship.y;                   // 発射y座標を自機上辺に指定
                                time = Date.now();                      // インターバル計測開始
                                break;
                            }
 
                // 弾丸の描画処理
                for(let i = 0; i < Bnum; i++)
                    if(bullet[i].flag){                                              // i番目の弾丸が描画されるなら
                        bullet[i].y -= bullet[i].speed;                                   // 弾丸の描画位置を上に進める
                        ctx.strokeRect(bullet[i].x, bullet[i].y, bullet[i].sizeX, bullet[i].sizeY); // 弾丸を描画
                        if(bullet[i].y+bullet[i].sizeY < 0)bullet[i].flag = !bullet[i].flag; // 上端に達したら描画フラグ変更
                    }
 
                // 敵機の描画&弾丸に当たったときの処理
                for(let i = 0; i < Enum; i++){
                    if(!enemy[i].flag){                                              // i番目の敵機が描画されていなければ
                        switch (Edirection){
                            case 0:                                                            // 上に描画
                                enemy[i].x = Math.random() * (canvas.width - enemy[i].sizeX);  // X座標はランダム
                                enemy[i].y = 0;                                                // Y座標は上端に固定
                                Edirection++;
                                break;
                            case 1:                                                            // 右に描画
                                enemy[i].x = canvas.width - enemy[i].sizeX;                    // X座標は右端に固定
                                enemy[i].y = Math.random() * (canvas.height - enemy[i].sizeY); // y座標はランダム
                                Edirection++;
                                break;
                            case 2:                                                            // 下に描画
                                enemy[i].x = Math.random() * (canvas.width - enemy[i].sizeX);  // X座標はランダム
                                enemy[i].y = canvas.height - enemy[i].sizeY;                   // Y座標は下端に固定
                                Edirection++;
                                break;
                            case 3:                                                            // 左に描画
                                enemy[i].x = 0;                                                // X座標は左端に固定
                                enemy[i].y = Math.random() * (canvas.height - enemy[i].sizeY); // y座標はランダム
                                Edirection = 0;
                                break;
                        }
                        enemy[i].flag = !enemy[i].flag;                                  // 描画フラグを変更
                    }
 
                    // 敵機の移動(自機に近づく)
                    if((enemy[i].x + enemy[i].sizeX/2) - (ship.x + ship.size/2) < 0)enemy[i].x += enemy[i].speed;
                    else enemy[i].x -= enemy[i].speed;
                    if((enemy[i].y + enemy[i].sizeY/2) - (ship.y + ship.size/2) < 0)enemy[i].y += enemy[i].speed;
                    else enemy[i].y -= enemy[i].speed;
 
                    // 敵機に自機が当たったら
                    if((enemy[i].x <= ship.x+ship.size) && (enemy[i].x+enemy[i].sizeX >= ship.x) &&
                       (enemy[i].y <= ship.y+ship.size) && (enemy[i].y+enemy[i].sizeY >= ship.y))GameOver = !GameOver;
 
                    // i番目の敵機が描画されるなら
                    if(enemy[i].flag){
                        ctx.fillRect(enemy[i].x, enemy[i].y, enemy[i].sizeX, enemy[i].sizeY); // 敵機を描画
                        for(let j = 0; j < Bnum; j++)
                            // 敵機に弾丸が当たったら
                            if((enemy[i].x <= bullet[j].x+bullet[j].sizeX) && (enemy[i].x+enemy[i].sizeX >= bullet[j].x) &&
                               (enemy[i].y <= bullet[j].y+bullet[j].sizeY) && (enemy[i].y+enemy[i].sizeY >= bullet[j].y)){
                                enemy[i].flag = !enemy[i].flag;   // 敵機の描画フラグ変更
                                bullet[j].flag = !bullet[j].flag; // 弾丸の描画フラグ変更
                                score++;                          // 点数を増やす
                                break;
                            }
                    }
                }
            }
            window.requestAnimationFrame(draw); //draw()の再呼び出し
        }
        draw(); // 初回の呼び出し
    </script>
</body>
</html>

あとがき

アルゴリズムの方に意識がいっちゃってHTMLについてはほとんど勉強が進んでないです…おかげで画面が味気ない。
書き方がめちゃくちゃだったり、説明がおかしかったりするのでご指摘いただけると大変助かります。
ゲーム内容についてもご意見いただけると次の勉強の種になりますのでとてもありがたいです。

いいなと思ったら応援しよう!