【対戦型タイピングゲーム アレンジ】条件を満たすとクリティカルヒットできるようにしよう

タイピングゲーム(Step4-1)

コトゼニ
コトゼニ

タイピングゲーム制作を通してプログラミングに強くなろう!

ということで、オリジナル 対戦型タイピングゲームの制作過程を紹介しています。

このタイピングゲームは

  • HTML
  • CSS
  • JavaScript

の3種類の言語で制作していて初心者でも取り扱いやすい内容になっていて、

高校の情報1のプログラミング実技授業としてもオススメです!

この記事では制作過程の「【アレンジ】条件を満たすとクリティカルヒットできるようにしよう」ということで、CSSとJavaScriptファイルを更新して、クリティカルヒットの機能を加えてみましょう!

アレンジの詳細

checklist2

アレンジの目標

クリティカルヒットとなる条件を自分で考え、条件分岐を使って実装できるようになりましょう。

アレンジの必要時間(目安)

アレンジを終えるには目安として1〜2時間必要です。

アレンジのゴール

アレンジで入力するコードで正しく機能を実装できれば以下のようなプログラムの状態になります。

入力画面

クリティカルヒット

動画のようにクリティカルヒットが実装できました。(通常は20ダメージのところ、30ダメージ与えることができています)

アレンジの流れ

programming

それではアレンジをどのように進めていくか順に説明します。

課題①クリティカルヒットとなる条件と効果を整理しよう

まずはクリティカルヒットとなる条件、そしてクリティカルヒットとなった際の効果を決めましょう。

たとえば、以下のように設定したとします。

クリティカルヒットとなる条件

  1. 制限時間の半分の時間で入力ができる
  2. 連続してミスタイピングなく入力できた

クリティカルヒットの効果

  1. 通常の攻撃では20のダメージのところ、クリティカルヒットだと30になる。
  2. 画面上に「クリティカルヒット」と表示させる。

課題②条件分岐を考え、ソースコードに追記する

課題①を終えた後は、ソースコードに条件分岐などを追記するようにしましょう。

模範コード例

programming-code

今回は、アレンジですので任意課題となります。そのため、課題用コードは省略し模範コードのみ掲載します。

HTMLファイル

HTMLファイルの名前は「index.html」にしましょう。

以下の模範コードを追加しましょう。

模範コード

以下のコードを追加してください。

<!-- クリティカルヒットのメッセージを表示するための領域 -->
<div id="criticalHitDisplay" class="critical-hit">クリティカルヒット!</div>

追加したHTMLファイルが以下となります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>タイピング対戦ゲーム</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="startScreen" class="screen active">
        <h1>タイピング対戦ゲーム</h1>
        <button id="startButton">ゲーム開始</button>
    </div>
    <div id="battleScreen" class="screen">
        <div id="gameContainer">
            <div id="playerStatus" class="status">
                <div id="lifeSection" class="character-container">
                    <div class="playerLifeBar">
                        <div class="imgBox" id="playerImgBox">
                            <img src="image/soldier1.png" class="playerImg" alt="Player Image">
                        </div>
                        <div id="playerLife" class="lifeBar">
                            <!-- 残りのライフ(黄色バー) -->
                            <div class="lifeRemaining" style="width: 100%; background-color: yellow;"></div>
                            <!-- 減ったライフ(グレーバー) -->
                            <div class="lifeLost" style="width: 0%; background-color: darkgrey; position: absolute; top: 0; right: 0;"></div>
                            <span class="lifeText">100</span>
                            <span style="color: white;">プレイヤー</span>
                        </div>

                            <!-- <div class="lifeRemaining" style="width: 100%;"></div> -->
                            <!-- 残ライフを表す黄色のバー -->
                            <!-- <span class="lifeText">100</span> -->
                    </div>
                    <!-- <div class="situation"></div> -->
                    <div class="enemyLifeBar">
                        <div id="enemyLife" class="lifeBar">
                            <!-- 残りのライフ(黄色バー) -->
                            <div class="lifeRemaining" style="width: 100%; background-color: yellow;"></div>
                            <!-- 減ったライフ(グレーバー) -->
                            <div class="lifeLost" style="width: 0%; background-color: darkgray; position: absolute; top: 0; right: 0;"></div>
                            <span class="lifeText">100</span>
                            <span style="color: white;">敵キャラ</span>
                        </div>
                        <div class="imgBox" id="enemyImgBox">
                            <img src="image/soldier2.png" class="enemyImg" alt="Enemy Image">
                        </div>
                    </div>
                </div>
            </div>
            <div id="battleSection">
                <div class="character player">
                    <img src="image/soldier1.png" class="battlePlayerImg" alt="Player Image">
                </div>
                <div id="result"></div>
                <div class="character enemy">
                    <img src="image/soldier2.png" class="BattleEnemyImg" alt="Enemy Image">
                </div>
                <!-- クリティカルヒットのメッセージを表示するための領域 -->
                <div id="criticalHitDisplay" class="critical-hit">クリティカルヒット!</div>
            </div>
            
            <div id="typingSection">
                <span id="currentWord"></span>
                <input type="text" id="typingInput">
            </div>
            
        </div>
    
            

    </div>

    <script src="script.js"></script>
</body>
</html>

CSSファイル

HTMLファイルで読み込むCSSファイルの名称を指定しているので、「style.css」というファイル名にしておきましょう。

模範コード

以下の模範コードをファイルの最後に追加しましょう。

/* クリティカルヒットメッセージのスタイル */
.critical-hit {
  display: none; /* 初期状態では非表示 */
  position: absolute;
  top: 50%; /* 画面の中央に表示 */
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 2em;
  color: red;
  opacity: 0;
  transition: opacity 0.5s ease;
  z-index: 1000; /* 他の要素より前面に表示 */
}

JavaScriptファイル

JavaScriptのファイル名は「script.js」としておきましょう。

模範コード

// スクリプト.js

// ページのコンテンツが読み込まれた後に実行されるイベントリスナー
document.addEventListener('DOMContentLoaded', () => {
    // 各HTML要素を取得
    const startButton = document.getElementById('startButton');
    const startScreen = document.getElementById('startScreen');
    const battleScreen = document.getElementById('battleScreen');
    const typingInput = document.getElementById('typingInput');
    const currentWordSpan = document.getElementById('currentWord');
    const playerLifeBar = document.querySelector('.playerLifeBar .lifeRemaining');
    const enemyLifeBar = document.querySelector('.enemyLifeBar .lifeRemaining');
    const playerCharacter = document.querySelector('.character.player');
    const enemyCharacter = document.querySelector('.character.enemy');
    const resultDisplay = document.getElementById('result');

    // プレイヤーと敵の初期ライフを設定
    let playerLife = 100;
    let enemyLife = 100;

    // 現在の単語を格納する変数
    let currentWord = '';

    // タイマーを管理する変数
    let timer;

    // タイピング用の単語リスト
    const words = ["challenge", "opponent", "battle", "victory", "defeat"];

    // タイピング開始からの経過時間とミスタイプの有無を追跡する変数
    let typingStartTime;
    let hasMistyped = false;

    // ゲームを開始する関数
    function startGame() {
        startScreen.classList.remove('active'); // 開始画面を非表示に
        battleScreen.classList.add('active');   // 対戦画面を表示
        nextWord();                             // 次の単語を表示
    }

    // 次の単語を表示する関数
    function nextWord() {
        currentWord = words[Math.floor(Math.random() * words.length)]; // ランダムな単語を選択
        currentWordSpan.textContent = currentWord; // 単語を表示
        typingInput.value = ''; // 入力フィールドをクリア
        typingInput.focus();    // 入力フィールドにフォーカス
        startTimer();           // タイマーを開始
    }

    // 入力をチェックする関数
    function checkInput() {
          // タイピング開始時間を記録
        if (typingInput.value.length === 1 && typingStartTime == null) {
            typingStartTime = Date.now();
        }

        const typedValue = typingInput.value; // 入力された値を取得
        if (typedValue === currentWord) {     // 入力値が単語と一致した場合
            const isCritical = isCriticalHit();
            // クリティカルヒットかどうかを判定して、適切なダメージ値とともに関数を呼び出す
            const criticalDamage = isCriticalHit() ? 30 : 20;
            attackEnemy(criticalDamage, isCriticalHit());
            typingStartTime = null; // タイピング時間のリセット
            hasMistyped = false; // ミスタイプフラグのリセット
        } else if (currentWord.indexOf(typedValue) !== 0) {
            // ミスタイピングがあった場合
            hasMistyped = true;
        } else {                              // 一致しない場合
            // 入力された部分をグレーで表示
            let matchedPart = currentWord.substring(0, typedValue.length);
            let remainingPart = currentWord.substring(typedValue.length);
            currentWordSpan.innerHTML = `<span style="color: grey;">${matchedPart}</span>${remainingPart}`;
        }
    }

    // クリティカルヒットを判定する関数
    function isCriticalHit() {
        const timeSpent = Date.now() - typingStartTime;
        return timeSpent <= (10000 / 2) && !hasMistyped; // 10秒が制限時間の例
    }

    // クリティカルヒットメッセージを表示する関数
    function showCriticalHit() {
        const criticalHitDisplay = document.getElementById('criticalHitDisplay');
        criticalHitDisplay.style.display = 'block';
        criticalHitDisplay.style.opacity = 1;
    
        // 数秒後にメッセージを非表示にする
        setTimeout(() => {
            criticalHitDisplay.style.opacity = 0;
            setTimeout(() => {
                criticalHitDisplay.style.display = 'none';
            }, 500); // opacity transition の持続時間に合わせる
        }, 2000); // メッセージを表示する時間
    }

    // プレイヤーが敵に攻撃する関数(修正箇所)
    function attackEnemy(damage, isCritical) {
        playerCharacter.classList.add('attacking');
        if (isCritical) {
            showCriticalHit(); // クリティカルヒットのメッセージを表示
        }
        setTimeout(() => {
            playerCharacter.classList.remove('attacking');
            enemyLife = Math.max(0, enemyLife - damage);
            updateLifeBar(enemyLifeBar, enemyLife); // 敵のライフバーを更新(修正箇所)
            checkGameOver();
        }, 1000);
    }
    // 敵がプレイヤーに攻撃する関数(修正箇所)
    function enemyAttacks() {
        enemyCharacter.classList.add('attacking');
        setTimeout(() => {
            enemyCharacter.classList.remove('attacking');
            // プレイヤーのライフをダメージ量だけ減らす(0未満にならないように制限)
            playerLife = Math.max(0, playerLife - 20); // ダメージ量
            updateLifeBar(playerLifeBar, playerLife); // プレイヤーのライフバーを更新(修正箇所)
            checkGameOver();
        }, 1000);
    }

    // ライフバーを更新する関数(修正箇所)
    function updateLifeBar(lifeBarElement, life) {
        // ライフバーの幅をライフの割合に基づいて更新(ライフが0未満にならないようにする)
        const percentage = Math.max(0, life / 100) * 100;

        // ライフテキストの内容を更新(修正箇所)
        lifeBarElement.style.width = percentage + '%'; // ライフの残量に応じて幅を設定
        lifeBarElement.parentElement.querySelector('.lifeText').textContent = life; // ライフテキストを更新
        lifeBarElement.parentElement.querySelector('.lifeLost').style.width = (100 - percentage) + '%'; // 減ったライフの幅を設定
    }

    // ゲームオーバーをチェックする関数
    function checkGameOver() {
        if (playerLife <= 0) {
            gameOver("LOSE"); // プレイヤーの負け
        } else if (enemyLife <= 0) {
            gameOver("WIN"); // プレイヤーの勝ち
        }else{
            nextWord();
        }
    }

    // ゲームオーバー時の処理を行う関数
    function gameOver(result) {
        clearTimeout(timer);                // タイマーを停止
        resultDisplay.textContent = result; // 結果を表示
        typingInput.disabled = true; // 入力フォームを無効化
    }

    // タイマーを開始する関数
    function startTimer() {
        clearTimeout(timer); // 既存のタイマーをクリア
        timer = setTimeout(() => {
            enemyAttacks();   // 敵が攻撃
            nextWord();       // 次の単語へ
        }, 10000);           // 10秒後に実行(入力の制限時間)
    }

    // イベントリスナーの設定
    startButton.addEventListener('click', startGame);
    typingInput.addEventListener('input', checkInput);

    // 入力フォームに常にフォーカスを保つための関数
    function keepFocusOnInput() {
        typingInput.focus();
    }

    // ページ全体にイベントリスナーを追加して、どこをクリックしても入力フォームにフォーカスを戻す
    document.addEventListener('click', keepFocusOnInput);   

});

まとめ

アレンジ案としてクリティカルヒット機能を実装しました。

クリティカルヒット成功時の装飾は生徒に好きにさせても良いでしょう。

オリジナルのクリティカルヒット機能に仕上げてもらえると嬉しいです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA