By Ruth Ikegah, Jules Kris
前回のチュートリアルでは、HTMLの要素の設定とスタイリングを学び、ゲームボーイエミュレータを作成してスネークゲームの基礎を準備しました。このチュートリアルでは、ゲームプレイ中にユーザーがゲームボーイとどのように対話できるかを探ります。主な焦点は、ユーザー入力とゲームを制御するためのボタンの作成です。
前提条件
ステップ1 – ボタンの作成とスタイリング
HTMLの作成とスタイリングチュートリアルで完成したゲームボーイエミュレータから始めましょう。必要な場合はこのサンプルを複製できます。
p5.jsでボタンを作成するには、HTMLのボタン要素を作成するcreateButton()
を使用します。これにより、マウスクリックなどのユーザー入力に応答するボタンのプログラミングが容易になります。この関数を使用すると、複雑なコードを書くことなくプロジェクトに簡単にボタンを追加でき、ユーザーがプログラムと対話して結果を確認できるようになります。
ボタン要素を作成して変数として保存する構文:
createButton(label);
「click me」というラベルの付いた単純なボタンの例:
function setup() {
createCanvas(400, 300);
// 単純なボタンを作成
let button = createButton('click me');
}
矢印ボタンコンテナの下にsetup()
に以下のコードを追加します:
// ... 前のコード
// 上ボタンを作成
let upButton = createButton('▲');
// IDを設定
upButton.id('up');
// スタイルを設定
upButton.style('color', 'white');
upButton.style('background-color', 'red');
upButton.style('width', '40px');
upButton.style('height', '40px');
upButton.style('margin-bottom', '10px');
upButton.style('border-radius', '5px');
arrowButtons.child(upButton);
このステップでは、ゲームプレイ中にユーザーがゲームボーイと対話できるようにするボタンを作成します。各ボタンには変数が割り当てられ、それを使用してボタンのスタイルを設定し、HTMLの矢印ボタンコンテナdiv要素の子要素として設定します。.id()
を使用してボタンにID up
を割り当て、.style()
を使用して色、サイズ、間隔などのプロパティを設定し、.child()
を使用してボタンを矢印ボタンコンテナの子要素として追加します。
upButton
変数は、前回のチュートリアルで作成した<div>
要素と同様に、p5.Element
クラスのボタンを参照します。ここでは、変数名を使用して.id()
メソッドでボタンにIDを付け、.style()
メソッドでスタイリングを行い、.child()
メソッドで他の要素の子要素として追加します。ここでは、background-color
、color
、font-size
、間隔などのプロパティを設定してボタンをスタイリングしました。各メソッドの構文については前回のチュートリアルを参照してください。同様に、左、右、下ボタンを適切なコンテナに追加し、setup()
にコードを追加してスタイリングします。
leftRightContainer
のコードの下に左、右、下ボタンをsetup()
に追加するには、以下のコードを使用します:
//...leftRightContainerまでの前のコード
arrowButtons.child(leftRightContainer);
// 左ボタンを作成
let leftButton = createButton('◀');
// IDを設定
leftButton.id('left');
// スタイルを設定
leftButton.style('color', 'white');
leftButton.style('background-color', 'red');
leftButton.style('width', '40px');
leftButton.style('height', '40px');
leftButton.style('margin-right', '30px');
leftButton.style('border-radius', '5px');
// leftRightContainerに追加
leftRightContainer.child(leftButton);
// 右ボタンを作成
let rightButton = createButton('▶');
// IDを設定
rightButton.id('right');
// スタイルを設定
rightButton.style('color', 'white');
rightButton.style('background-color', 'red');
rightButton.style('width', '40px');
rightButton.style('height', '40px');
rightButton.style('margin-left', '5px');
rightButton.style('border-radius', '5px');
// leftRightContainerに追加
leftRightContainer.child(rightButton);
// 下ボタンを作成
let downButton = createButton('▼');
arrowButtons.child(downButton);
// IDを設定
downButton.id('down');
// スタイルを設定
downButton.style('color', 'white');
downButton.style('background-color', 'red');
downButton.style('width', '40px');
downButton.style('height', '40px');
downButton.style('margin-top', '10px');
downButton.style('border-radius', '5px');
上記では、残りの方向ボタンを作成します。これらはゲームプレイ中にゲームボーイと対話するためのものです。p5.jsのcreateButton()
を使用してボタンを作成します。次に、.id()
でIDを割り当て、.style()
でスタイルを設定します。スタイリングでは、すべてのボタンに白色、赤色の背景色、高さと幅それぞれ40px
、border-radius
は5px
を設定します。
注意
ボタン間の適切な間隔を得るためにマージンを調整してください。
actionButtons
のコードの下に再生ボタンと一時停止ボタンをsetup()
に追加するには、以下のコードを使用します:
//... actionButtonsまでのsetup内のコード
// 再生ボタンを作成
let playButton = createButton('▶');
actionButtons.child(playButton);
// IDを設定
playButton.id('play');
// スタイルを設定
playButton.style('background-color', 'blue');
playButton.style('color', 'white');
playButton.style('width', '60px');
playButton.style('height', '60px');
playButton.style('font-size', '24px');
// 円形にする
playButton.style('border-radius', '50%');
// 間隔を追加
playButton.style('margin-right', '10px');
// 一時停止ボタンを作成
let pauseButton = createButton('❚❚');
// IDを設定
pauseButton.id('pause');
// スタイルを設定
pauseButton.style('background-color', 'blue');
pauseButton.style('color', 'white');
pauseButton.style('width', '60px');
pauseButton.style('height', '60px');
pauseButton.style('font-size', '24px');
// 円形にする
pauseButton.style('border-radius', '50%');
actionButtons.child(pauseButton);
同様に、アクションボタン(再生ボタンと一時停止ボタン)についても、ボタンを作成してIDを付けます。そして、それらのボタンを親コンテナであるactionButtons
に追加します。スタイリングについては、白色と青色の背景色を設定します。また、幅と高さを60px
に、font-size
を24px
に設定し、border-radius
を50%
に設定して円形にします。
注意
適切な間隔を得るために、いずれかのボタンにマージンを追加してください。
試してみよう!
ボタンに好みのスタイルを追加してカスタマイズしてください。より多くのCSSプロパティについては、このリソースをチェックしてください。
ステップ2 – スネークゲームのロジックを実装する
スネークのロジック、餌のロジック、ゲームの動作ロジックを含むスネークゲームのロジックを作成する時が来ました。コードが大きくなりすぎないように、この部分のために新しいファイルを作成します。
p5.js Web Editorの左上にある、Sketch Filesの+アイコンをクリックし、Create Fileを選択します。ファイル名をsnakeGame.js
とし、Add fileをクリックします。
index.html
ファイルの<head>
内のsketch.js
ファイルの<script>
タグの下に新しい<script>
タグを追加して、新しいJavaScriptファイルをリンクします:
<script src="snakeGame.js"></script>
snakeGame.js
ファイルで、ゲームロジックで使用する変数を宣言することから始めます。これらの変数は、ゲームの状態、プレイヤーのスコア、ユーザー入力に関する重要な情報を保存します。最初に変数を宣言することで、コードがより整理され、分解して保守しやすくなります。これらの変数は、ゲームの初期条件を定義するパラメータとして使用されます。以下のコードをsnakeGame.js
ファイルに追加します:
let cols = 20;
let rows = 15;
let gridSize = 20;
let winWidth = cols * gridSize;
let winHeight = rows * gridSize;
let food = { x: 0, y: 0 };
let snake = {
x: 0,
y: 0,
xSpeed: 0,
ySpeed: 0,
body: [],
};
let score = 0;
let gamePaused = false;
let gameOver = false;
let gameStarted = false;
let fps = 5;
cols
とrows
変数は、ゲームウィンドウのサイズと寸法を決定するグリッドの列と行を表します。gridSize
変数は、各グリッドセルのサイズを表します。各セルの幅と高さを定義し、ゲームの全体的なレイアウトとスケールに影響を与えます。winWidth
とwinHeight
変数は、列数(cols
)、行数(rows
)、各グリッドセルのサイズ(gridSize
)に基づいてゲームウィンドウの総幅と高さを計算します。これらはキャンバスまたはゲームウィンドウの寸法を設定します。- food変数は、ゲーム内の餌の現在位置を表すJavaScriptオブジェクトとして保存されます。餌を配置すべき座標を保存するxとyのプロパティを持っています。データ構造ガーデンチュートリアルを参照して、JavaScriptオブジェクトを復習してください。
- snake変数は、ゲーム内のヘビに関する情報を含むオブジェクトです。現在の位置(
x
とy
)、x方向とy方向の移動速度(xSpeed
とySpeed
)、そしてヘビの体のセグメントを保存する配列(body
)が含まれています。データ構造ガーデンチュートリアルを参照して、配列を復習してください。 score
変数は、ゲーム内のプレイヤーのスコアを追跡します。ヘビが餌を食べるたびに増加します。gamePaused
、gameOver
、gameStarted
変数は、ゲームの状態を表すブール値を持ちます。初期値はfalse
に設定されています。ループによる繰り返しチュートリアルを参照して、状態変数を復習してください。fps
変数は、ゲームループのフレームレートを定義します。ゲーム(描画)が更新されレンダリングされる速度を決定します。
次に、ゲーム内の餌の動作を処理する関数を書きます。以下のコードをsnakeGame.js
ファイルに追加します:
//...前のステップのコード
// 餌を赤い四角形として
// 描画します。
function drawFood() {
let x = food.x * gridSize;
let y = food.y * gridSize;
fill(255, 255, 0);
square(x, y, gridSize);
}
// 餌をランダムな位置に
// 移動させます。
function moveFood() {
food.x = floor(random(cols));
food.y = floor(random(rows));
}
drawFood()
関数は餌のピクセル位置を処理します:let x = food.x * gridSize;
とlet y = food.y * gridSize;
文は、餌のグリッド位置をピクセル座標に変換します。- 色を黄色に設定し、餌を表す四角形を描画します。
moveFood()
関数は次の餌のためのランダムなグリッド位置を生成し、そのxとy座標を更新します。floor()
関数は、ランダム値が整数になることを保証します。
関数によるコードの整理チュートリアルを参照して、カスタム関数の構文と使用法を復習してください。
これらの関数は、ゲーム内での餌の視覚的な表現と移動を処理します。グリッドベースのアプローチにより、餌が画面上のゲームのグリッドシステムに確実に整列します。
最後に、ヘビの動作を作成し制御する関数を書きます。以下のコードをsnakeGame.js
ファイルに追加します:
//...前のステップのコード
// ヘビのデータをリセットします。
function resetSnake() {
// 中央からスタート。
snake.x = floor(cols / 2);
snake.y = floor(rows / 2);
// 右に移動。
snake.xSpeed = 1;
snake.ySpeed = 0;
// 体を管理する配列を
// 追加。
snake.body = [];
// 頭のセグメントを作成。
let head = {
x: snake.x,
y: snake.y,
};
// 頭を体に追加。
snake.body.push(head);
}
// ヘビが端に衝突したら
// ゲームを終了。
function checkEdges() {
// 右端。
if (snake.x === cols) {
gameOver = true;
return;
}
// 左端。
if (snake.x === -1) {
gameOver = true;
return;
}
// 上端。
if (snake.y === -1) {
gameOver = true;
return;
}
// 下端。
if (snake.y === rows) {
gameOver = true;
return;
}
}
// ヘビを前に進める。
function moveSnake() {
snake.x = + snake.xSpeed;
snake.y = + snake.ySpeed;
}
// ヘビの体のセグメントの
// 位置を更新。
function updateBody() {
// 尾の端を更新。
for (let i = snake.body.length - 1; i > 0; i -= 1) {
snake.body[i].x = snake.body[i - 1].x;
snake.body[i].y = snake.body[i - 1].y;
}
// 頭を更新。
snake.body[0].x = snake.x;
snake.body[0].y = snake.y;
}
// ヘビを描画。
function drawSnake() {
fill(0, 255, 0);
for (let segment of snake.body) {
let x = segment.x * gridSize;
let y = segment.y * gridSize;
square(x, y, gridSize);
}
}
// ヘビの移動を更新。
function changeDir(xSpeed, ySpeed) {
snake.xSpeed = xSpeed;
snake.ySpeed = ySpeed;
}
// ヘビの食事を管理。
function checkFood() {
if (snake.x === food.x && snake.y === food.y) {
// 新しい体のセグメントを追加。
let segment = { x: -1, y: -1 };
snake.body.push(segment);
// スコアを更新。
score += 10;
// 餌をランダムな位置に
// 配置。
moveFood();
}
}
// ヘビが自分の尾に衝突したら
// ゲームを終了。
function checkSelf() {
for (let i = 1; i < snake.body.length; i += 1) {
let segment = snake.body[i];
if (snake.x === segment.x && snake.y === segment.y) {
gameOver = true;
return;
}
}
}
resetSnake()
関数は、ヘビをグリッドの中央の初期状態にリセットします:snake.x = floor(cols/2);
とsnake.y = floor(rows/2);
文は、ヘビのxとyの開始位置を計算し、中央の列と行を見つけます。floor()
は、計算が小数点をサポートしないため、確実に整数になるようにします。snake.xSpeed = 1
とsnake.ySpeed = 0
で初期の方向を右に設定し、- ヘビの体を管理する空の配列を作成し、現在の位置で頭のセグメントを作成して、体の配列に追加します。
checkEdges()
関数はif文を使用して、ヘビが右端(cols
)に到達したか、左端(-1
)に到達したか、上端(-1
)に到達したか、下端(rows
)に到達したかをチェックします。条件が満たされた場合は、gameOver
をtrue
に設定します。これにより、ヘビが端に到達した場合にゲームが終了するというルールが作成されます。moveSnake()
関数は、現在の速度に基づいてヘビの位置を更新します。それぞれxSpeed
とySpeed
を加算することで、xとyの位置を更新します。if文と変数の更新については、条件と対話性チュートリアルを参照してください。
updateBody()
関数は、ヘビの体のセグメントの位置を更新します。これは、末尾から始めてヘビのbody
配列を反復処理し、各体のセグメントの位置を前のセグメントの位置に更新することで行います。その後、頭の位置をヘビの現在の位置に更新します。ループと反復については、ループによる繰り返しチュートリアルを参照してください。drawSnake()
関数は、キャンバス上にヘビを描画します。色を緑に設定し、各体のセグメントを反復処理して、その位置に四角形を描画します。changeDir(xSpeed, ySpeed)
関数は、2つのパラメータxSpeed
とySpeed
を受け取ります。ヘビのxSpeed
(x軸の移動)をパラメータxSpeed
に、ヘビのySpeed
(y軸の移動)をパラメータySpeed
に設定し、ヘビの移動を変更します。関数、引数、パラメータについては、関数によるコードの整理チュートリアルを参照してください。checkFood()
関数は、ヘビと餌の位置が一致しているかをチェックし、ヘビが餌を食べたことを示します。確認後、.push()
メソッドを使用して初期座標{ x: -1, y: -1 }
を持つ新しいセグメントをヘビの体の配列に追加します。この.push()
の使用は配列に追加し、ヘビを長くします。セグメント{ x: -1, y: -1 }
は一時的なオフグリッドのプレースホルダーです。これにより、次のヘビの体の更新(updateBody()
)でゲームが位置を設定する前に、新しいセグメントがプレイエリアにランダムに表示されることを防ぎます。さらに、ゲームはスコアを10増加させ、moveFood()
関数を呼び出して餌を再配置します。checkSelf()
関数は、ヘビが自分の体に衝突したかをチェックします。ヘビの体のセグメント(頭を除く)を反復処理し、頭の位置が体のセグメントの位置と一致するかをチェックし、一致する場合はgameOver
をtrue
に設定します。これにより、ユーザーが自分の体にヘビを衝突させた場合にゲームが終了するというルールが追加されます。
これらの関数は、ヘビのリセット、衝突のチェック、ヘビの体の更新、ヘビの描画、餌との相互作用、自己衝突の管理など、スネークゲームのさまざまな側面を集合的に処理します。プロジェクトはこのような見た目になるはずです。
試してみよう!
5回餌を食べるごとにボーナス餌を追加する関数を作成してください。ヒント: ボーナス餌用の新しい変数を作成し、checkFood()
関数を修正してボーナス餌を処理し、そしてボーナス餌用の関数を作成します。
ステップ3 – ボタンコントロール用の関数を作成する
ゲームコントロール用の関数も作成できます。snakeGame.js
ファイルに以下のコードを追加します:
// 方向:上、下、左、右
function goUp() {
snake.xSpeed = 0;
snake.ySpeed = -1;
}
function goDown() {
snake.xSpeed = 0;
snake.ySpeed = 1;
}
function goLeft() {
snake.xSpeed = -1;
snake.ySpeed = 0;
}
function goRight() {
snake.xSpeed = 1;
snake.ySpeed = 0;
}
goUp()
関数は、ヘビを上向きに移動するように設定します。xSpeed
を0(水平方向の移動なし)に、ySpeed
を-1(y軸上で上向きに移動)に更新します。- 同様に、
goDown()
関数は、ヘビを下向きに移動するように設定します。xSpeed
を0(x軸上で下向きに移動)に、ySpeed
を1に更新します。 goLeft()
関数は、ヘビを左向きに移動するように設定します。xSpeed
を-1(x軸上で左向きに移動)に、ySpeed
を0に更新します。- 同様に、
goRight()
関数は、ヘビを右向きに移動するように設定します。xSpeed
を1(x軸上で右向きに移動)に、ySpeed
を0に更新します。
次に、キーボードの矢印キーでも方向アクションを制御できるように、keyPressed()
関数を使用して矢印ボタンのクリック用の関数を作成します。snakeGame.js
ファイルに以下のコードを書きます:
function keyPressed() {
if (keyCode === UP_ARROW) {
goUp();
}
if (keyCode === DOWN_ARROW) {
goDown();
}
if (keyCode === LEFT_ARROW) {
goLeft();
}
if (keyCode === RIGHT_ARROW) {
goRight();
}
}
keyPressed()
関数は、キーが押されるたびに呼び出されるp5.jsの組み込み関数です。キーボードで最後に押されたキーの値を保存する組み込み変数keyCode
をチェックし、ユーザーが押した矢印キーに基づいて対応する方向関数(例:goUp()
)を呼び出します。
以下はkeyPressed()
関数の簡単な例です。プレビューをクリックしてからキーを押してみてください:
次に、一時停止と再生のアクションを処理する関数を定義します。snakeGame.js
ファイルに以下のコードを書きます:
//...前のステップのコード
function startGame() {
score = 0;
gameStarted = true;
gamePaused = false;
if (gameOver === true) {
resetSnake();
gameOver = false;
}
loop();
}
// ゲームを一時停止
function pauseGame() {
gamePaused = true;
}
最後に、ボタン要素がクリックされたときにこれらの関数を呼び出すために、p5.Element
のmouseClicked()
メソッドを使用します。例えば、sketch.js
ファイルのupButton
の下に以下のコードブロックを追加します:
upButton.mouseClicked(goUp);
.mouseClicked()
メソッドは、特定の要素(この場合はupButton
)の上でマウスがクリックされたときに呼び出す関数を設定します。コントロール関数を引数として渡します。keyPressed()
関数のように動作しますが、マウスクリックを使用します。ボタンがクリックされると、goUp()
関数が実行されます。
以下のように、各ボタンの下に関数を呼び出します:
button.mouseClicked(buttonFunction)
ステップ4 – メッセージプロンプト用の関数を作成する
ゲームのユーザーインターフェースとフィードバック面のためのメッセージプロンプトが必要です。snakeGame.js
ファイルに以下のコードを書きます:
// 開始メッセージ
function displayStartMessage() {
textSize(16);
textAlign(CENTER);
fill(255, 0, 0);
text('Press ▶ to Start', width / 2, height / 2);
}
// 終了メッセージ
function displayEndMessage() {
background(0);
textSize(50);
textAlign(CENTER);
fill(255, 0, 0);
text('Game Over', width / 2, height / 2);
textSize(14);
text('Press ▶ to Start', width / 2, height / 2 + 50);
}
displayStartMessage()
関数は、ゲームが開始されていないときに呼び出され、プレイヤーに再生ボタンを押すように促します。一方、displayEndMessage()
関数は、ゲームが終了したときに呼び出され、「Game Over」メッセージとゲームを再開するための指示を表示します。
ステップ5 – スケッチにゲームロジックを実装する
ゲームロジックと関数は完成しましたが、スケッチに追加するまでゲームは完成しません。まず、ゲームの初期状態を設定するために重要なアクションから始めます。これらのアクションには、画面のフレームレート、ヘビの初期化、餌の初期位置が含まれます。sketch.js
ファイルのsetup()
関数の最後に、以下のコードブロックを書きます:
// 画面のフレームレートを設定
frameRate(fps);
// 餌をランダムな位置に
// 移動させます。
moveFood();
// ヘビをリセット。
resetSnake();
このアクションはmoveFood()
関数を呼び出し、キャンバス上でランダムに餌の位置を変更します。また、resetSnake()
関数を呼び出してヘビをリセットします。
frameRate()
関数は、1秒あたりに描画するフレーム数を設定します。その値としてfps
変数を渡します。
最後に、draw関数に残りのゲームロジックを実装します。sketch.js
ファイルのdraw()
関数内に、以下のコードを書きます:
// ゲームが開始されていない場合は
// draw関数の残りをスキップ。
if (gameStarted === false) {
displayStartMessage();
return;
}
// ゲームの状態を更新。
// print(`${snake.x}, ${snake.y}`);
if (gamePaused === false) {
moveSnake();
checkEdges();
checkSelf();
checkFood();
updateBody();
}
// ヘビと餌を描画。
drawFood();
drawSnake();
// ゲームが終了した場合は
// draw関数の残りをスキップ。
if (gameOver === true) {
displayEndMessage();
noLoop();
}
// スコアを更新。
textAlign(RIGHT);
fill(255);
text(`Score: ${score}`, width - 10, 20);
- コードは、ゲームが開始されていないか(
gameStarted === false
)をチェックします。ゲームが開始されていない場合、displayStartMessage
関数を呼び出し(開始メッセージを表示するため)、その後すぐにdraw()
関数をreturn
で終了します。これにより、ゲームが開始されていない場合はdraw()
関数の残りの部分がスキップされます。 - ゲームが一時停止されていない場合(
gamePaused === false
)、ゲームの状態を更新します。moveSnake()
、checkEdges()
、checkSelf()
、checkFood()
、updateBody()
関数が呼び出され、ヘビの移動、衝突のチェック、餌の消費の処理、ヘビの体の更新など、ゲームロジックのさまざまな側面を処理します。 drawFood()
とdrawSnake()
関数を呼び出して、キャンバス上に餌とヘビを視覚的にレンダリングします。- ゲームが終了したか(
gameOver === true
)をチェックします。ゲームが終了した場合、displayEndMessage();
関数を呼び出し、その後noLoop()
を使用して描画ループを停止します。これにより、ゲーム終了後は更新やレンダリングが行われなくなります。 - 最後に、キャンバス上でスコアを更新して表示します。スコアは
textAlign(RIGHT)
とtext(`Score: ${score}`, width - 10, 20)
を使用してキャンバスの右上隅に配置されます。
このコード構造は、ゲームのさまざまな側面を効果的に処理します。開始、更新、描画、終了が、ゲームの状態に基づいて管理されることを保証します。ゲームの状態には、未開始、一時停止されていない、ゲームオーバーが含まれます。ゲームのロジックは関数にカプセル化され、可読性とメンテナンス性を高めています。draw()
関数は、その状態に基づいてゲームを継続的に更新しレンダリングする責任を持つメインループです。
ゲームプレイは以下のようになるはずです:
お疲れ様でした。あなたは独自のスネークゲームを作成することに成功しました!素晴らしい成果を達成しました。
ボタンは便利な入力タイプですが、探索すべき入力はまだたくさんあります。他の入力がどのように機能するかを確認するには、p5.jsリファレンスのDOMセクションをチェックしてください。
結論
このチュートリアルを完了したことを祝福します。p5.jsでの入力応答の習得は、Webプロジェクトにインタラクティビティを追加する重要なステップです。このチュートリアルでは、応答性のあるボタンの作成方法を徹底的に探索しました。また、それらを関数と結びつけてから、最後にクラシックなスネークゲームのロジックをまとめました。これらの要素を統合することで、コーディングのレパートリーを強化しただけでなく、懐かしいコンセプトを現代のWeb技術で新しい命を吹き込みました。ここで磨いたスキルは、さらに複雑で魅力的なインタラクティブアプリケーションを開発するための堅固な基盤となるでしょう。
参考として、完全な入力への応答コードをご覧ください。