VUE と Canvas を使用して Thunder Fighter タイピング ゲームを実装する方法

VUE と Canvas を使用して Thunder Fighter タイピング ゲームを実装する方法

今日は、サンダーファイタータイピングゲームを実装します。ゲームプレイは非常に簡単です。それぞれの「敵」はいくつかの英語の単語です。キーボードで単語の文字を正しく入力すると、飛行機が弾丸を1つずつ発射して「敵」を破壊します。次の「敵」を殺す前に、現在の「敵」を殺す必要があります。これは、手の速さと単語の熟練度をテストするゲームです。

まず、最終的な効果を見てみましょう。

えーっと、UI インターフェースは非常にシンプルです。まずは基本機能を実装し、その後に高度な UI を検討します。

まず、インターフェースの構成を分析しましょう。

(1)平面は絵の下部の中央に固定されている。

(2)画面上部からランダムに生成される敵(単語)

(3)航空機の前方から発射され、敵に向かってまっすぐに進んだ弾丸。

(4)ゲーム終了後のスコア表示。

以前のゲームと比較すると、今回はスポーツ部分が多く、より複雑になっているようです。 Flappy Birdでは、パイプは動いていますが、鳥のx座標とパイプの間隔と幅は変わらないため、境界を計算するのが簡単です。ピンボールとレンガのゲームでは、木の板とレンガは比較的単純または固定座標であるため、ピンボールの境界とレンガの接触面積を決定するだけで済みます。 Thunder Fighter Word Elimination ゲームでは、着地するターゲットの単語でも、飛んでくる弾丸でも、すべて独自の移動軌道を持っていますが、弾丸はターゲットを追従する必要があるため、リアルタイムの軌道計算操作が行われます。

高い建物は地面から始まります。ここまで説明してきましたが、まずは一番簡単なものから始めましょう。

1. 画面下部の中央に固定された平面

これは非常にシンプルで、特に言うことはありません。ここでは、航空機のデフォルトの幅と高さは 40 ピクセルで、航空機は画面の中央下部に描画されます。

平面を描画する() {
      _this = this とします。
      _this.ctx.save();
      _this.ctx.drawImage(
        _this.planeImg、
        _this.clientWidth / 2 - 20,
        _this.clientHeight - 20 - 40、
        40,
        40
      );
      _this.ctx.restore();
},

2. 画面上部からランダムに生成される敵

ここでは、デフォルトでは、一度に画面に表示される単語ターゲットは 3 つだけ、ターゲットの Y 軸移動速度は 1.3、ターゲットの半径は 10 です。

const _MAX_TARGET = 3; // 一度画面に表示されるターゲットの最大数 const _TARGET_CONFIG = {
  // 目標速度の固定パラメータ: 1.3、

  半径: 10

};

次に、まず単語ライブラリ配列から _MAX_TARGET 個の繰り返しのない単語をランダムに取り出し、残りの単語を循環単語ライブラリ this.wordsPool に格納します。

単語を生成する(数値) {
      // 表示されている単語と重複しない単語をプールからランダムに選択します。let arr = [];
      (i = 0; i < 数値; i++) の場合 {
        random = Math.floor(Math.random() * this.wordsPool.length); とします。
        arr.push(this.wordsPool[ランダム]);
        this.wordsPool.splice(ランダム、1);
      }
      arr を返します。
},
ターゲットを生成する() {
      // ターゲットをランダムに生成する let _this = this;
      長さを_this.targetArr.lengthとします。
      長さ < _MAX_TARGET の場合 {
        txtArr = _this.generateWord(_MAX_TARGET - 長さ) とします。
        (i = 0 とします; i < _MAX_TARGET - 長さ; i++) {
          _this.targetArr.push({
            x: _this.getRandomInt() 関数
              _TARGET_CONFIG.半径、
              _this.clientWidth - _TARGET_CONFIG.radius
            )、
            y: _TARGET_CONFIG.半径 * 2、
            txt: txtArr[i],
            タイプインデックス: -1,
            ヒットインデックス: -1,
            dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2,
            dy: _TARGET_CONFIG.speed * Math.random().toFixed(1)、
            回転: 0
          });
        }
      }
}

this.targetArr はターゲット オブジェクトを格納する配列であることがわかります。

初期ターゲットでは、 x が画面の幅全体にランダムに分布します。

Y軸の値は直径です。

txt はターゲットによって表される単語を記録します。

typeIndex は、「bombing」という単語が入力されたときに入力される文字のインデックス添え字を記録します(入力された文字と入力されていない文字を区別するために使用されます)。

hitIndex は、「bombing」という単語を爆撃する弾丸のインデックスを記録します (弾丸は単語に当たった後に実際にターゲットを爆撃するため、弾丸には飛行時間があり、そのため、hitIndex は弾丸が粉砕されて消えるタイミングを決定するために使用されます)。

dx は、フレームごとの x 軸上のターゲットのオフセット距離です。

dy は、フレームごとの y 軸上のターゲットのオフセット距離です。

rotate はターゲットの回転角度を設定します。

さて、3 つのターゲットを生成したので、上から下へ移動してみましょう。

描画ターゲット() {
      // フレームごとにターゲットを描画します。let _this = this;
      _this.targetArr.forEach((アイテム、インデックス) => {
        _this.ctx.save();
        _this.ctx.translate(item.x, item.y); //回転の中心点を設定します_this.ctx.beginPath();
        _this.ctx.font = "14px Arial";
        もし (
          インデックス === _this.currentIndex ||
          アイテム.typeIndex === アイテム.txt.length - 1
        ){
          _this.drawText(
            アイテム.txt.substring(0, アイテム.typeIndex + 1),
            -item.txt.長さ * 3、
            _TARGET_CONFIG.半径 * 2、
            "グレー"
          );
          幅を_this.ctx.measureText(とする
            アイテム.txt.substring(0, アイテム.typeIndex + 1)
          ).width; // タップされたテキストの幅を取得します_this.drawText(
            アイテム.txt.substring(アイテム.typeIndex + 1, アイテム.txt.length),
            -item.txt.長さ * 3 + 幅、
            _TARGET_CONFIG.半径 * 2、
            "赤"
          );
        } それ以外 {
          _this.drawText(
            アイテム.txt、
            -item.txt.長さ * 3、
            _TARGET_CONFIG.半径 * 2、
            "黄色"
          );
        }
 
        _this.ctx.closePath();
        _this.ctx.rotate((item.rotate * Math.PI) / 180);
        _this.ctx.drawImage(
          _this.targetImg、
          -1 * _TARGET_CONFIG.半径、
          -1 * _TARGET_CONFIG.半径、
          _TARGET_CONFIG.半径 * 2、
          _TARGET_CONFIG.半径 * 2
        );
        _this.ctx.restore();
        アイテム.y += アイテム.dy;
        アイテム.x += アイテム.dx;
        (item.x < 0 || item.x > _this.clientWidth)の場合{
          アイテム.dx * = -1;
        }
        if (item.y > _this.clientHeight - _TARGET_CONFIG.radius * 2) {
          // 一番下をヒット_this.gameOver = true;
        }
        // 回転 item.rotate++;
      });
}

このステップでターゲットを描画することに関して特別なことは何もありません。dx と dy をランダムに増加させて、ターゲットが左端と右端に当たったときに跳ね返るだけです。主なポイントは単語の描画です。単語は typeIndex によって 2 つの部分に分割され、タップされた文字は灰色に設定され、次に、タップされた文字の幅が measureText によって取得され、タップされていない文字の X 軸オフセットが設定され、タップされていない文字が赤に設定されて、この単語が攻撃対象であることをプレイヤーに通知します。

3. 弾丸は飛行機の前方から発射され、敵に向かってまっすぐ進みます。

弾丸はこのゲームの重要な要素です。弾丸を描くときに考慮すべき点は何ですか?

(1)標的は常に動いており、発射された弾丸は常に標的を「追跡」する必要があるため、軌道は動的に変化します。

(2)ターゲットを破壊するには一定数の弾丸が必要ですが、弾丸はいつ画面から消えるのでしょうか?

(3)ターゲットワードにヒットすると、次の弾丸が次のターゲットに発射されるため、弾丸の軌道は一意になります。

(4)弾痕効果の描き方

(5)ターゲット単語がロックされている場合、プレイヤーは次の単語を入力する前に現在の単語の入力を完了する必要があります。

設定する変数は次のとおりです。

bulletArr: [], // 弾丸オブジェクトを保存

currentIndex: -1 //targetArr 内の現在ロックされているターゲットのインデックス

まず、キーボードが押されたときにトリガーされる関数を記述します。

handleKeyPress(キー) {
      //キーボードが押されたら、現在のターゲットを決定します let _this = this;
      _this.currentIndex === -1の場合{
        // 現在、撃たれているターゲットはありません。let index = _this.targetArr.findIndex(item => {
          item.txt.indexOf(key) === 0 を返します。
        });
        (インデックス!== -1)の場合{
          _this.currentIndex = インデックス;
          _this.targetArr[インデックス].typeIndex = 0;
          _this.createBullet(インデックス);
        }
      } それ以外 {
        // ターゲットがすでに撃たれている場合(
          キー ===
          _this.targetArr[_this.currentIndex].txt.split("")[
            _this.targetArr[_this.currentIndex].typeIndex + 1
          ]
        ){
          // ターゲットオブジェクトを取得します_this.targetArr[_this.currentIndex].typeIndex++;
          _this.createBullet(_this.currentIndex);
 
          もし (
            _this.targetArr[_this.currentIndex].typeIndex ===
            _this.targetArr[_this.currentIndex].txt.length - 1
          ){
            // このターゲットはすでに撃たれています_this.currentIndex = -1;
          }
        }
      }
},
// 弾を発射する createBullet(index) {
      _this = this とします。
      this.bulletArr.push({
        度数: 1,
        dy: 4,
        x: _this.clientWidth / 2,
        y: _this.clientHeight - 60,
        ターゲットインデックス: インデックス
      });
}

この関数が行うことは非常に明確です。キーボードで現在押されている文字を取得します。currentIndex === -1 の場合、攻撃されているターゲットがないことが証明されるため、ターゲット配列に移動して、その文字と等しい最初の文字を持つ単語を確認し、currentIndex を単語のインデックスに設定して、弾丸を発射します。攻撃されているターゲットがすでにある場合は、ヒットしていない単語の最初の文字が一致するかどうかを確認します。一致する場合は、ターゲット オブジェクトの typeIndex を増やして、弾丸を発射します。現在のターゲットがヒットしている場合は、currentIndex を -1 にリセットします。

次は弾丸を描きます。

箇条書きを描画する(){
      // フレームごとに弾丸を描画します。let _this = this;
      // 弾がターゲットに当たったかどうかを判定します if (_this.bulletArr.length === 0) {
        戻る;
      }
      _this.bulletArr = _this.bulletArr.filter(_this.firedTarget);
      _this.bulletArr.forEach(item => {
        targetX = _this.targetArr[item.targetIndex].x; とします。
        targetY = _this.targetArr[item.targetIndex].y とします。
        k = とします
          (_this.clientHeight - 60 - targetY) /
          (_this.clientWidth / 2 - targetX); // 航空機のヘッドとターゲットの傾斜 let b = targetY - k * targetX; // 定数 b
        item.y = item.y - bullet.dy; // y軸を1単位オフセット item.x = (item.y - b) / k;
        (i = 0; i < 15; i++ とします) {
          // 末尾の effect_this.ctx.beginPath() を描画します。
          _this.ctx.arc(
            (item.y + i * 1.8 - b) / k、
            項目.y + i * 1.8、
            4 - 0.2 * 私、
            0,
            2 * 数学.PI
          );
          _this.ctx.fillStyle = `rgba(193,255,255,${1 - 0.08 * i})`;
          _this.ctx.fill();
          _this.ctx.closePath();
        }
      });
},
firedTarget(アイテム) {
      // ターゲットがヒットしたかどうかを判断します let _this = this;
      もし (
        item.x > _this.targetArr[item.targetIndex].x - _TARGET_CONFIG.radius &&
        item.x < _this.targetArr[item.targetIndex].x + _TARGET_CONFIG.radius &&
        item.y > _this.targetArr[item.targetIndex].y - _TARGET_CONFIG.radius &&
        アイテム.y < _this.targetArr[item.targetIndex].y + _TARGET_CONFIG.radius
      ){
        // 弾丸がターゲットに命中しました。let arrIndex = item.targetIndex;
        _this.targetArr[arrIndex].hitIndex++;
        もし (
          _this.targetArr[arrIndex].txt.length - 1 ===
          _this.targetArr[arrIndex].hitIndex
        ){
          // すべての弾丸がターゲットに命中する let word = _this.targetArr[arrIndex].txt;
          _this.targetArr[arrIndex] = {
            // 新しいターゲット x を生成する: _this.getRandomInt(
              _TARGET_CONFIG.半径、
              _this.clientWidth - _TARGET_CONFIG.radius
            )、
            y: _TARGET_CONFIG.半径 * 2、
            txt: _this.generateWord(1)[0],
            タイプインデックス: -1,
            ヒットインデックス: -1,
            dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2,
            dy: _TARGET_CONFIG.speed * Math.random().toFixed(1)、
            回転: 0
          };
          _this.wordsPool.push(word); // ヒットしたターゲット単語がプールに戻ります_this.score++;
        }
        false を返します。
      } それ以外 {
        true を返します。
      }
}

実は、これも非常に簡単です。弾丸オブジェクトの targetIndex を使用して、弾丸が攻撃したターゲット インデックスを記録します。次に、方程式 y = kx+b を解いて、航空機のヘッド (弾丸の開始点) とターゲットの軌道関数を取得します。各フレームで各弾丸の移動座標を計算し、弾丸を描画できます。

トレーリング効果は、トラックの y 軸の成長方向に沿って、透明度と半径が徐々に減少する多数の円を描くことによって実現されます。

firedTarget() 関数では、ターゲットにヒットした弾丸をフィルタリングするために使用されます。targetArr でまだ攻撃されている他のターゲットのインデックスに影響を与えないように、スプライス削除は使用されず、破壊されたターゲットの値が直接リセットされ、wordPool から新しい単語が選択され、現在の壊れた単語がプールに戻され、画像に重複したターゲットが表示されないようにします。

4. ゲーム終了後のスコアテキスト効果

ターゲットが底に触れるとゲームは終了します。

これは実はイースターエッグです。キャンバスを使用して、このような点滅する光輪のテキストを描画するにはどうすればよいでしょうか?色を切り替えてバフを積み重ねるだけ、いや、ストロークを積み重ねるだけです。

ゲームオーバーを描画する() {
      _this = this とします。
      //コンテキストの状態を保存します object_this.ctx.save();
      _this.ctx.font = "34px Arial";
      _this.ctx.strokeStyle = _this.colors[0];
      _this.ctx.lineWidth = 2;
      //Halo_this.ctx.shadowColor = "#FFFFE0";
      let txt = "ゲームオーバー、スコア: " + _this.score;
      幅を_this.ctx.measureText(txt).widthとします。
      (i = 60、i > 3、i -= 2 とします) {
        _this.ctx.shadowBlur = i;
        _this.ctx.strokeText(txt, _this.clientWidth / 2 - width / 2, 300);
      }
      _this.ctx.restore();
      _this.colors.reverse();
}

はい、はい、ここまででかなり完成したゲームができましたが、UI が少し粗いです。本物の Thunder Fighter のようなかっこいい爆発効果を実現したい場合は、大量のマテリアルとキャンバス描画が必要になります。

キャンバスはとてもパワフルで楽しいです。想像力が十分あれば、このキャンバスに何でも描くことができます。

いつものように、参考までに Vue の完全なコードを以下に示します。

<テンプレート>
  <div class="type-game">
    <キャンバスid="type" 幅="400" 高さ="600"></キャンバス>
  </div>
</テンプレート>
 
<スクリプト>
const _MAX_TARGET = 3; // 一度画面に表示されるターゲットの最大数 const _TARGET_CONFIG = {
  // 目標速度の固定パラメータ: 1.3、
  半径: 10
};
const _DICTIONARY = ["apple", "orange", "blue", "green", "red", "current"];
エクスポートデフォルト{
  名前: "TypeGame",
  データ() {
    戻る {
      ctx: null、
      クライアント幅: 0,
      クライアントの高さ: 0,
      bulletArr: [], // 画面上の弾丸 targetArr: [], // 現在のターゲットを保存 targetImg: null,
      平面画像: null、
      現在のインデックス: -1,
      単語プール: [],
      スコア: 0,
      ゲームオーバー: false、
      色: ["#FFFF00", "#FF6666"]
    };
  },
  マウント() {
    _this = this とします。
    _this.wordsPool = _DICTIONARY.concat([]);
    コンテナを document.getElementById("type");
    _this.clientWidth = コンテナの幅;
    _this.clientHeight = コンテナの高さ;
    _this.ctx = コンテナ.getContext("2d");
    _this.targetImg = 新しい画像();
    _this.targetImg.src = require("@/assets/img/target.png");
 
    _this.planeImg = 新しい画像();
    _this.planeImg.src = require("@/assets/img/plane.png");
 
    document.onkeydown = 関数(e) {
      キーを window.event.keyCode とします。
      (キー >= 65 && キー <= 90) の場合 {
        _this.handleKeyPress(String.fromCharCode(key).toLowerCase());
      }
    };
 
    _this.targetImg.onload = 関数() {
      _this.generateTarget();
      (関数animloop() {
        もし(!_this.gameOver) {
          _this.drawAll();
        } それ以外 {
          _this.drawGameOver();
        }
        window.requestAnimationFrame(アニメーションループ);
      })();
    };
  },
  メソッド: {
    ゲームオーバーを描画する() {
      _this = this とします。
      //コンテキストの状態を保存します object_this.ctx.save();
      _this.ctx.font = "34px Arial";
      _this.ctx.strokeStyle = _this.colors[0];
      _this.ctx.lineWidth = 2;
      //Halo_this.ctx.shadowColor = "#FFFFE0";
      let txt = "ゲームオーバー、スコア: " + _this.score;
      幅を_this.ctx.measureText(txt).widthとします。
      (i = 60、i > 3、i -= 2 とします) {
        _this.ctx.shadowBlur = i;
        _this.ctx.strokeText(txt, _this.clientWidth / 2 - width / 2, 300);
      }
      _this.ctx.restore();
      _this.colors.reverse();
    },
    すべて描画() {
      _this = this とします。
      _this.ctx.clearRect(0, 0, _this.clientWidth, _this.clientHeight);
      _this.drawPlane(0);
      _this.drawBullet();
      _this.drawTarget();
      _this.drawScore();
    },
    平面を描画する() {
      _this = this とします。
      _this.ctx.save();
      _this.ctx.drawImage(
        _this.planeImg、
        _this.clientWidth / 2 - 20,
        _this.clientHeight - 20 - 40、
        40,
        40
      );
      _this.ctx.restore();
    },
    単語を生成する(数値) {
      // 表示されている単語と重複しない単語をプールからランダムに選択します。let arr = [];
      (i = 0; i < 数値; i++) の場合 {
        random = Math.floor(Math.random() * this.wordsPool.length); とします。
        arr.push(this.wordsPool[ランダム]);
        this.wordsPool.splice(ランダム、1);
      }
      arr を返します。
    },
    ターゲットを生成する() {
      // ターゲットをランダムに生成する let _this = this;
      長さを_this.targetArr.lengthとします。
      長さ < _MAX_TARGET の場合 {
        txtArr = _this.generateWord(_MAX_TARGET - 長さ) とします。
        (i = 0 とします; i < _MAX_TARGET - 長さ; i++) {
          _this.targetArr.push({
            x: _this.getRandomInt() 関数
              _TARGET_CONFIG.半径、
              _this.clientWidth - _TARGET_CONFIG.radius
            )、
            y: _TARGET_CONFIG.半径 * 2、
            txt: txtArr[i],
            タイプインデックス: -1,
            ヒットインデックス: -1,
            dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2,
            dy: _TARGET_CONFIG.speed * Math.random().toFixed(1)、
            回転: 0
          });
        }
      }
    },
    ランダム整数を取得する(n, m) {
      Math.floor(Math.random() * (m - n + 1)) + n を返します。
    },
    テキストを描画します(txt, x, y, 色) {
      _this = this とします。
      _this.ctx.fillStyle = 色;
      _this.ctx.fillText(txt, x, y);
    },
    スコアを描画する() {
      // スコア this.drawText("スコア: " + this.score, 10, this.clientHeight - 10, "#fff");
    },
    描画ターゲット() {
      // フレームごとにターゲットを描画します。let _this = this;
      _this.targetArr.forEach((アイテム、インデックス) => {
        _this.ctx.save();
        _this.ctx.translate(item.x, item.y); //回転の中心点を設定します_this.ctx.beginPath();
        _this.ctx.font = "14px Arial";
        もし (
          インデックス === _this.currentIndex ||
          アイテム.typeIndex === アイテム.txt.length - 1
        ){
          _this.drawText(
            項目.txt.substring(0, 項目.typeIndex + 1),
            -item.txt.長さ * 3、
            _TARGET_CONFIG.半径 * 2、
            "グレー"
          );
          幅を_this.ctx.measureText(とする
            アイテム.txt.substring(0, アイテム.typeIndex + 1)
          ).width; // タップされたテキストの幅を取得します_this.drawText(
            アイテム.txt.substring(アイテム.typeIndex + 1, アイテム.txt.length),
            -item.txt.長さ * 3 + 幅、
            _TARGET_CONFIG.半径 * 2、
            "赤"
          );
        } それ以外 {
          _this.drawText(
            アイテム.txt、
            -item.txt.長さ * 3、
            _TARGET_CONFIG.半径 * 2、
            "黄色"
          );
        }
 
        _this.ctx.closePath();
        _this.ctx.rotate((item.rotate * Math.PI) / 180);
        _this.ctx.drawImage(
          _this.targetImg、
          -1 * _TARGET_CONFIG.半径、
          -1 * _TARGET_CONFIG.半径、
          _TARGET_CONFIG.半径 * 2、
          _TARGET_CONFIG.半径 * 2
        );
        _this.ctx.restore();
        アイテム.y += アイテム.dy;
        アイテム.x += アイテム.dx;
        (item.x < 0 || item.x > _this.clientWidth)の場合{
          アイテム.dx * = -1;
        }
        if (item.y > _this.clientHeight - _TARGET_CONFIG.radius * 2) {
          // 一番下をヒット_this.gameOver = true;
        }
        // 回転 item.rotate++;
      });
    },
    handleKeyPress(キー) {
      //キーボードが押されたら、現在のターゲットを決定します let _this = this;
      _this.currentIndex === -1の場合{
        // 現在、撃たれているターゲットはありません。let index = _this.targetArr.findIndex(item => {
          item.txt.indexOf(key) === 0 を返します。
        });
        (インデックス!== -1)の場合{
          _this.currentIndex = インデックス;
          _this.targetArr[インデックス].typeIndex = 0;
          _this.createBullet(インデックス);
        }
      } それ以外 {
        // ターゲットがすでに撃たれている場合(
          キー ===
          _this.targetArr[_this.currentIndex].txt.split("")[
            _this.targetArr[_this.currentIndex].typeIndex + 1
          ]
        ){
          // ターゲットオブジェクトを取得します_this.targetArr[_this.currentIndex].typeIndex++;
          _this.createBullet(_this.currentIndex);
 
          もし (
            _this.targetArr[_this.currentIndex].typeIndex ===
            _this.targetArr[_this.currentIndex].txt.length - 1
          ){
            // このターゲットはすでに撃たれています_this.currentIndex = -1;
          }
        }
      }
    },
    // 弾を発射する createBullet(index) {
      _this = this とします。
      this.bulletArr.push({
        度数: 1,
        dy: 4,
        x: _this.clientWidth / 2,
        y: _this.clientHeight - 60,
        ターゲットインデックス: インデックス
      });
    },
    firedTarget(アイテム) {
      // ターゲットがヒットしたかどうかを判断します let _this = this;
      もし (
        item.x > _this.targetArr[item.targetIndex].x - _TARGET_CONFIG.radius &&
        item.x < _this.targetArr[item.targetIndex].x + _TARGET_CONFIG.radius &&
        item.y > _this.targetArr[item.targetIndex].y - _TARGET_CONFIG.radius &&
        アイテム.y < _this.targetArr[item.targetIndex].y + _TARGET_CONFIG.radius
      ){
        // 弾丸がターゲットに命中しました。let arrIndex = item.targetIndex;
        _this.targetArr[arrIndex].hitIndex++;
        もし (
          _this.targetArr[arrIndex].txt.length - 1 ===
          _this.targetArr[arrIndex].hitIndex
        ){
          // すべての弾丸がターゲットに命中する let word = _this.targetArr[arrIndex].txt;
          _this.targetArr[arrIndex] = {
            // 新しいターゲット x を生成する: _this.getRandomInt(
              _TARGET_CONFIG.半径、
              _this.clientWidth - _TARGET_CONFIG.radius
            )、
            y: _TARGET_CONFIG.半径 * 2、
            txt: _this.generateWord(1)[0],
            タイプインデックス: -1,
            ヒットインデックス: -1,
            dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2,
            dy: _TARGET_CONFIG.speed * Math.random().toFixed(1)、
            回転: 0
          };
          _this.wordsPool.push(word); // ヒットしたターゲット単語がプールに戻ります_this.score++;
        }
        false を返します。
      } それ以外 {
        true を返します。
      }
    },
    箇条書きを描画する(){
      // フレームごとに弾丸を描画します。let _this = this;
      // 弾がターゲットに当たったかどうかを判定します if (_this.bulletArr.length === 0) {
        戻る;
      }
      _this.bulletArr = _this.bulletArr.filter(_this.firedTarget);
      _this.bulletArr.forEach(item => {
        targetX = _this.targetArr[item.targetIndex].x; とします。
        targetY = _this.targetArr[item.targetIndex].y とします。
        k = とします
          (_this.clientHeight - 60 - targetY) /
          (_this.clientWidth / 2 - targetX); // 航空機のヘッドとターゲットの傾斜 let b = targetY - k * targetX; // 定数 b
        item.y = item.y - 4; // y軸を1単位オフセット item.x = (item.y - b) / k;
        (i = 0; i < 15; i++ とします) {
          // 末尾の effect_this.ctx.beginPath() を描画します。
          _this.ctx.arc(
            (item.y + i * 1.8 - b) / k、
            項目.y + i * 1.8、
            4 - 0.2 * 私、
            0,
            2 * 数学.PI
          );
          _this.ctx.fillStyle = `rgba(193,255,255,${1 - 0.08 * i})`;
          _this.ctx.fill();
          _this.ctx.closePath();
        }
      });
    }
  }
};
</スクリプト>
 
<!-- CSS をこのコンポーネントのみに制限するために "scoped" 属性を追加します -->
<スタイル スコープ lang="scss">
.type-ゲーム {
  #タイプ {
    背景: #2a4546;
  }
}
</スタイル>

以上が、VUEとCanvasを使用してThunder Fighterタイピングゲームを実装する方法の詳細です。VUE Thunder Fighterゲームの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • VueはCanvasを使用してランダムなサイズで重なり合わない円を生成します
  • Vueはキャンバスを使用して画像圧縮アップロードを実現します
  • vue+canvasでタイムラインを描く方法
  • VUE+CanvasはシンプルなGobangゲームの全プロセスを実現します
  • VUE+Canvasは、インゴットを受け取る富の神のゲームを実装します
  • VUE+Canvasはデスクトップピンボールブロック破壊ゲームのサンプルコードを実装します
  • Vueはマウスを使ってキャンバス上に四角形を描きます
  • Vueはキャンバスを使用してモバイル手書き署名を実装します
  • Vue+canvasでパズルゲームを実現
  • Vueはキャンバスの手書き入力を使用して中国語を認識します

<<:  MySQL 4.1/5.0/5.1/5.5/5.6の主な違い

>>:  nginx proxy_cache バッチキャッシュクリアスクリプトの紹介

推薦する

Linux で環境変数 JAVA_HOME を変更/設定する方法について簡単に説明します。

1. 永久的な変更、すべてのユーザーに有効# vi /etc/プロファイル//キーボードの[Shi...

CentOS8 デプロイメント LNMP 環境で mysql8.0.29 をコンパイルしてインストールする方法の詳細なチュートリアル

1. 前提条件何度かインストールしているので、エラーについてはこれ以上説明しません。ちょっとわかりに...

React Native が「NSArray<id<RCTBridgeModule>>型のパラメータを初期化できません」というエラーを報告する (解決方法)

最近、古い RN プロジェクトを Xcode で実行すると、次のコード エラーが報告されました。 &...

HTML メタタグの一般的な使用例のコレクション

マタタグとは<meta> 要素は、検索エンジン向けの説明やキーワード、更新頻度など、ペー...

検索テキストボックスがフォーカスを外れたときにテキストの位置がジャンプする問題の解決方法

検索テキストボックスにテキストを設定すると、フォーカスを外すと位置がジャンプしますコードをコピーコー...

表のセル間隔とセルパディングの違いの詳細な説明

テーブルとは何ですか?セルセルで構成されています。表では、<td> の数は、<tr...

mysql+mycat、負荷分散、マスタースレーブレプリケーション、読み取り/書き込み分離操作に基づく安定した高可用性クラスタを構築します。

データベースのパフォーマンス最適化には、一般的にクラスタリングが採用されています。Oracle クラ...

JSONP クロスドメインシミュレーション Baidu 検索

目次1. JSONPとは何か2. JSONPクロスドメインリクエスト3. Baidu検索をシミュレー...

HTML テーブルタグと関連する改行の問題の詳細な分析

テーブルとは何ですか?テーブルは、データのキャリアである HTML テーブルです。以下は比較的標準的...

Vue3.xはコンポーネント通信にmitt.jsを使用します

目次クイックスタート使い方基本原則Vue2.x はコンポーネント通信に EventBus を使用しま...

MySQLインデックスが失敗するいくつかの状況の詳細な分析

1. 先頭のあいまいクエリではインデックスを使用できません (「%XX」や「%XX%」など)コード値...

Vueフロントエンドの効率的な開発のためのレンダリング手順をリストします

v-for ディレクティブリストといえば、ループについても触れなければなりません。v-for 命令は...

CentOS 7 での Nginx ログタイミング分割の実装手順の詳細説明

1. 分割スクリプト (splitNginxLog.sh) を作成します。 * この例では、ログ分割...

MySQL初心者はグループ化や集計クエリの煩わしさから解放されます

目次1. グループクエリの概略図2. groupbyキーワード構文の詳細な説明3. 簡単なグループク...

Vue は div の高さをドラッグ可能にします

この記事では、divのドラッグ可能な高さを実現するためのVueの具体的なコードを参考までに共有します...