22 KiB
イベントを使ったゲームの作成
レッスン前の小テスト
イベント駆動型プログラミング
ブラウザベースのアプリケーションを作成するとき、私たちは、構築したものと対話するときに使用するグラフィカル・ユーザー・インターフェース (GUI) を提供します。ブラウザと対話する最も一般的な方法は、様々な要素をクリックしたり入力したりすることです。開発者として直面する課題は、ユーザーがこれらの操作をいつ実行するかわからないことです。
イベント駆動型プログラミングは、GUIを作成するために必要なプログラミングの種類の名前です。このフレーズを少し分解すると、ここでの核となる単語は イベント です。イベントは、Merriam-Webster によると、「何かが起こる」と定義されています。これは私たちの状況を完璧に説明しています。何かが起こりそうで、それに対応してコードを実行したいのですが、それがいつ行われるかはわかりません。
実行したいコードの節をマークする方法は、関数を作成することです。手続き型プログラミングを考えると、関数は決まった順番で呼び出されます。これはイベント駆動型プログラミングでも同じことが言えそうです。違いは、関数がどのように呼ばれるかということです。
イベント (ボタンクリックやタイピングなど) を処理するために、イベントリスナー を登録します。イベントリスナーとは、イベントが発生するのを待ち受けて、それに応じて実行する関数のことです。イベントリスナーは、UI を更新したり、サーバーへの呼び出しを行ったり、ユーザーのアクションに反応して実行することができます。addEventListener を使用してイベントリスナーを追加し、実行する関数を提供します。
注: イベントリスナーを作成する方法は数多くあります。匿名の関数を使うこともできますし、名前付きの関数を作ることもできます。
click
プロパティを設定したり、addEventListener
を使用したりと、様々なショートカットを使用することができます。今回の演習ではaddEventLister
と匿名関数に焦点を当てます。また、addEventListener
はすべてのイベントに対して動作し、イベント名をパラメータとして指定できるので、最も柔軟性があります。
共通イベント
アプリケーションを作成するときに聞くことができる多数のイベントがあります。基本的に、ユーザーがページ上で何かをするとイベントが発生しますが、これはプレイヤーがあなたの意図した経験を得られるようにするうえで大きな力となります。幸いなことに、通常はほんの一握りのイベントしか必要ありません。ここでは、(ゲームを作成する際に使用する 2 つのイベントを含む) いくつかの一般的なイベントを紹介します:
- click: ユーザーが何かをクリックした場合、通常はボタンやハイパーリンクをクリックします
- contextmenu: ユーザーがマウスの右ボタンをクリックした場合
- select: ユーザーがテキストをハイライトした場合
- input: ユーザーが何かテキストを入力した場合
ゲームの作成
私たちは、JavaScript でイベントがどのように機能するかを探求するゲームを作成する予定です。私たちのゲームはプレイヤーのタイピングスキルをテストしますが、これはすべての開発者が持つべき最も過小評価されているスキルの一つです。私たちは皆、タイピングの練習をするべきです! ゲームの一般的な流れは以下のようになります。
- プレイヤーがスタートボタンをクリックすると、入力する名言が表示されます
- プレイヤーは、テキストボックスにできるだけ早く名言を入力します
- 各単語が完成すると、次の単語が強調表示されます
- プレイヤーにタイプミスがあった場合、テキストボックスが赤に更新されます
- 名言が完了すると、経過時間とともに成功メッセージが表示されます
ゲームを作ってイベントを覚えましょう!
ファイル構造
index.html、script.js、style.css の 3 つのファイルが必要です。まずはこれらを設定して、生活を少し楽にしていきましょう。
- コンソールまたはターミナルウィンドウを開き、以下のコマンドを実行して、作業用の新しいフォルダを作成します
# Linux または macOS
mkdir typing-game && cd typing-game
# Windows
md typing-game && cd typing game
- Visual Studio Code を開きます
code .
- Visual Studio Code のフォルダに以下の名前で3つのファイルを追加します
- index.html
- script.js
- style.css
ユーザーインターフェースの作成
要件を探ってみると、HTML ページには一握りの要素が必要になることがわかります。これはレシピのようなもので、いくつかの材料が必要です。
- ユーザーが入力するための名言を表示する場所
- 成功メッセージのようなメッセージを表示する場所
- 入力用のテキストボックス
- スタートボタン
これらのファイルにはそれぞれ ID が必要なので、JavaScript でそれらを扱うことができます。また、これから作成する CSS と JavaScript ファイルへの参照を追加します。
index.html という名前の新しいファイルを作成します。以下の HTML を追加します。
<!-- index.html の中身 -->
<html>
<head>
<title>タイピングゲーム</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>タイピングゲーム!</h1>
<p>シャーロック・ホームズの名言を使ってタイピングの練習をしましょう。**スタート** をクリックしてください。</p>
<p id="quote"></p> <!-- これで名言が表示されます。 -->
<p id="message"></p> <!-- これは、すべてのステータスメッセージを表示します。 -->
<div>
<input type="text" aria-label="current word" id="typed-value" /> <!-- 入力用のテキストボックス -->
<button type="button" id="start">スタート</button> <!-- ゲームを開始します -->
</div>
<script src="script.js"></script>
</body>
</html>
アプリケーションの起動
物事がどのように見えるかを確認するためには、常に反復的に開発するのがベストです。アプリケーションを起動してみましょう。Visual Studio Code には Live Server と呼ばれる素晴らしい拡張機能があり、アプリケーションをローカルにホストし、保存するたびにブラウザを更新します。
- リンクを辿り、Install をクリックして、Live Server をインストールします
- ブラウザで Visual Studio Code を開き、Visual Studioコードでインストールを実行するように促されます
- プロンプトが表示されたら Visual Studio Code を再起動します
- インストールしたら、Visual Studio Code で Ctl-Shift-P (または Cmd-Shift-P) をクリックして、コマンドパレットを開きます
- Live Server: Open with Live Server と入力します
- Live Server がアプリケーションのホスティングを開始します
- ブラウザを開き、https://localhost:5500 に移動します
- これで作成したページが表示されるはずです!
機能を追加してみましょう。
CSS の追加
HTML を作成したので、コアスタイリング用の CSS を追加してみましょう。プレイヤーが入力すべき単語をハイライトし、入力した内容が間違っている場合はテキストボックスに色をつけます。これには 2 つのクラスを使用します。
style.css という名前のファイルを新規作成し、以下の構文を追加します。
/* style.css の中身 */
.highlight {
background-color: yellow;
}
.error {
background-color: lightcoral;
border: red;
}
✅ CSS に関しては、あなたが好きなようにページをレイアウトすることができます。少し時間をかけて、より魅力的なページにしてみましょう。
- 別のフォントを選択します
- ヘッダーに色をつけます
- アイテムのサイズを変更します
JavaScript
UI を作成したので、ロジックを提供する JavaScript に注目してみましょう。これをいくつかのステップに分けて説明します。
しかし、まず、script.js という名前のファイルを新規作成します。
定数の追加
プログラミングの生活を少しでも楽にするために、いくつかのアイテムが必要になります。繰り返しになりますが、レシピに似ていますが、必要なものは以下の通りです。
- すべての名言のリストを含む配列
- 現在の名言のすべての単語を格納する空の配列
- プレイヤーが現在入力している単語のインデックスを格納するスペース
- プレイヤーがスタートをクリックした時間
UI 要素への参照も欲しいところです。
- テキストボックス (typed-value)
- 名言の表示 (quote)
- メッセージ (message)
// script.js の中身
// すべての名言
const quotes = [
'When you have eliminated the impossible, whatever remains, however improbable, must be the truth.',
'There is nothing more deceptive than an obvious fact.',
'I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.',
'I never make exceptions. An exception disproves the rule.',
'What one man can invent another can discover.',
'Nothing clears up a case so much as stating it to another person.',
'Education never ends, Watson. It is a series of lessons, with the greatest for the last.',
];
// 単語のリストと、プレイヤーが現在入力している単語のインデックスを格納します。
let words = [];
let wordIndex = 0;
// 開始時刻
let startTime = Date.now();
// ページ構成要素
const quoteElement = document.getElementById('quote');
const messageElement = document.getElementById('message');
const typedValueElement = document.getElementById('typed-value');
✅ 先に行き、あなたのゲームに多くの名言を追加します。
注:
document.getElementById
を使用することで、コード内で必要なときにいつでも要素を取得することができます。これらの要素を定期的に参照することになるので、定数を使用して文字列リテラルのタイプミスを回避します。Vue.js や React などのフレームワークは、コードを集中管理するのに役立ちます。
1分ほどかけて、const
, let
, var
の使い方のビデオを見てみましょう。
開始ロジックの追加
ゲームを始めるには、プレイヤーはスタートをクリックします。もちろん、プレイヤーがいつスタートボタンをクリックするかはわかりません。ここでイベントリスナーの出番です。イベントリスナーを使うと、何か (イベント) が発生するのを待ち受けて、それに応じてコードを実行することができます。この例では、ユーザーがスタートをクリックしたときにコードを実行したいと思います。
ユーザーが スタート をクリックしたときに、名言を選択し、ユーザーインターフェースを設定し、現在の単語とタイミングのトラッキングを設定する必要があります。以下は追加する必要のある JavaScript です。スクリプトブロックの後で説明します。
// script.js の最後の方に
document.getElementById('start').addEventListener('click', () => {
// 名言の取得
const quoteIndex = Math.floor(Math.random() * quotes.length);
const quote = quotes[quoteIndex];
// 名言を言葉の配列に入れる
words = quote.split(' ');
// トラッキング用の単語インデックスをリセットする
wordIndex = 0;
// UI の更新
// span 要素の配列を作成し、クラスを設定できるようにします。
const spanWords = words.map(function(word) { return `<span>${word} </span>`});
// 文字列に変換して、名言を表示する innerHTML として設定します。
quoteElement.innerHTML = spanWords.join('');
// 最初の単語を強調表示します。
quoteElement.childNodes[0].className = 'highlight';
// 前のメッセージをクリアします。
messageElement.innerText = '';
// テキストボックスの設定
// テキストボックスをクリアします。
typedValueElement.value = '';
// フォーカスを合わせます。
typedValueElement.focus();
// イベントハンドラを設定します。
// タイマーを開始します。
startTime = new Date().getTime();
});
コードを分解してみましょう!
- 単語のトラッキングを設定します
- Math.floor と Math.random を使うと、
quotes
配列から名言をランダムに選択することができます quote
をwords
の配列に変換することで、プレイヤーが現在入力している単語を追跡することができますwordIndex
は 0 に設定されます。プレイヤーは最初の単語から始めます
- Math.floor と Math.random を使うと、
- UI を設定します
spanWords
の配列を作成し、その中にspan
要素内の各単語を格納します- これにより、ディスプレイ上の単語を強調表示することができます
- 配列を
join
して文字列を作成し、これを用いてquoteElement
のinnerHTML
を更新することができます- これにより、プレイヤーに名言が表示されます
- 最初の
span
要素のclassName
をhighlight
に設定し、黄色で強調表示します innerText
を''
に設定することでmessageElement
をクリーンにします
- テキストボックスを設定します
- 現在の
typedValueElement
のvalue
をクリアします focus
をtypedValueElement
に設定します
- 現在の
getTime
を呼び出してタイマーを起動します
タイピングロジックの追加
プレイヤーが入力すると input
イベントが発生します。このイベントリスナーは、プレイヤーが単語を正しく入力しているかどうかをチェックし、ゲームの現在の状態を処理します。script.js に戻り、最後に以下のコードを追加します。この後に分解していきます。
// script.js の最後の方に
typedValueElement.addEventListener('input', () => {
// 現在の単語を取得します
const currentWord = words[wordIndex];
// 現在の値を取得します
const typedValue = typedValueElement.value;
if (typedValue === currentWord && wordIndex === words.length - 1) {
// 文の終了
// 成功を表示します
const elapsedTime = new Date().getTime() - startTime;
const message = `CONGRATULATIONS! You finished in ${elapsedTime / 1000} seconds.`;
messageElement.innerText = message;
} else if (typedValue.endsWith(' ') && typedValue.trim() === currentWord) {
// 単語の終了
// 新しい単語用に 'typedValueElement' をクリアします
typedValueElement.value = '';
// 次の単語に移ります
wordIndex++;
// 名言内のすべての要素のクラス名をリセットします
for (const wordElement of quoteElement.childNodes) {
wordElement.className = '';
}
// 新しい単語を強調表示します
quoteElement.childNodes[wordIndex].className = 'highlight';
} else if (currentWord.startsWith(typedValue)) {
// 現在正しく入力されている状態
// 次の単語を強調表示します
typedValueElement.className = '';
} else {
// エラー状態
typedValueElement.className = 'error';
}
});
コードを分解してみましょう! まず、現在の単語とプレイヤーがこれまでに入力した値を取得します。次にウォーターフォールロジックがあり、ここでは引用が完全であるかどうか、単語が完全であるかどうか、単語が正しいかどうか、(最後に) エラーがあるかどうかをチェックします。
- 名言が完成しており、
typepedValue
がcurrentWord
と等しく、wordIndex
がwords
のlength
よりも 1 つ小さい値であることを示している場合- 現在の時刻から
startTime
を引くことでelapsedTime
を計算します elapsedTime
を 1,000 で割り、ミリ秒から秒に変換します- 成功メッセージを表示します
- 現在の時刻から
- 単語は完成しており、
typedValue
がスペース (単語の終わり) で終わり、typedValue
がcurrentWord
と同じであることを示している場合- 次の単語が入力されるように
typedElement
のvalue
を''
に設定します - 次の単語に移動するために
wordIndex
をインクリメントします quoteElement
のすべてのchildNodes
をループしてclassName
を''
に設定し、デフォルトの表示に戻します- 現在の単語の
className
をhighlight
に設定して、それを次のタイプの単語としてフラグを立てます
- 次の単語が入力されるように
- 単語は現在 (完全ではないが) 正しく入力されており、
typedValue
で始まるcurrentWord
で示される場合className
をクリアすることでtypedValueElement
がデフォルトで表示されるようにします
- ここまで来たら、エラーが発生しています
typedValueElement
のclassName
をerror
に設定します
アプリケーションのテスト
最後までやりましたね! 最後のステップは、私たちのアプリケーションが動作することを確認することです。試してみてください。エラーがあっても心配しないでください。すべての開発者 がエラーに遭遇します。メッセージを調べて、必要に応じてデバッグしていきましょう。
スタート をクリックして、入力を開始してください。前に見たアニメーションに少し似ているはずです。
🚀 チャレンジ
より多くの機能を追加しましょう。
- 完了時に
input
イベントリスナーを無効にし、ボタンがクリックされたときに再度有効にします - プレイヤーが名言を完了したときにテキストボックスを無効にします
- 成功メッセージを含むモーダルダイアログボックスを表示します
- localStorage を使ってハイスコアを保存します
レッスン後の小テスト
復習と自己学習
Web ブラウザを介して開発者が利用できるすべてのイベントを読んで、それぞれを使用するシナリオを検討してください。