[JavaScript] 仮想ボタンの実装とタッチ・マウス操作対応

マウスとタッチ操作に対応した仮想ボタンの実装

このガイドでは、仮想ボタンをマウスとタッチ操作で扱う方法について、簡単に説明します。仮想ボタンは、画面上でタッチやクリックによって操作できるボタンで、主にモバイルゲームやWebアプリケーションで使用されます。今回は、ドラッグ操作も実装し、ボタンの位置を動かすことができるようにします。

1. 仮想ボタンの基本構成

仮想ボタンは、ユーザーが画面上で操作できるボタンのことです。ゲームやアプリケーションでよく使用されます。特にモバイルデバイスやタッチ操作において、物理ボタンが存在しない場合に仮想ボタンを使います。

まずは、仮想ボタンのUIを作成しましょう。ボタンには位置情報(x , y )やサイズ(width, height )が設定され、ボタンごとにテキスト(例:左ボタン、右ボタン、魔法ボタン)があります。

以下のように、gameConfigオブジェクトに仮想ボタンの設定を記述します:

const gameConfig = {
  virtualButton: {
    left: { x: 100, y: 100, width: 50, height: 50, text: '左' },
    right: { x: 200, y: 100, width: 50, height: 50, text: '右' },
    magic: { x: 300, y: 100, width: 50, height: 50, text: '魔法' },
    pause: { x: 400, y: 100, width: 50, height: 50, text: 'ポーズ' },
  },
};

ここでは、left(左ボタン)、right(右ボタン)、magic(魔法ボタン)、pause(ポーズボタン)の仮想ボタンが設定されています。それぞれのボタンは、xyで指定された位置に描画され、ボタンのサイズ(幅と高さ)や表示されるテキストも設定されています。

ボタン配置の考え方

仮想ボタンは、ゲームやアプリのUIに配置する際に、ユーザーが押しやすいように設計することが大切です。例えば、左・右ボタンは画面の下部に配置し、中央には魔法ボタンやポーズボタンを置くことが一般的です。この配置は、使用するデバイスやゲームの内容に応じて柔軟に変更できます。

2. マウス操作の処理

仮想ボタンをドラッグで移動するためには、マウス操作を適切に処理する必要があります。ここでは、マウスが押されたとき(mousedown)、移動したとき(mousemove)、そして離されたとき(mouseup)の各イベントに対応した処理を実装します。

マウスダウン時の処理

まず、ユーザーがボタンをクリックしたとき、そのボタンがドラッグ可能かどうかを判定します。ボタンがクリックされた場合、ドラッグフラグ(isDragging)を設定し、クリック位置のオフセット(offsetX, offsetY)を保存します。

let isMouseDown = false; // マウスダウン中かどうか
let isDragging = false; // ドラッグ中かどうか
let draggedButton = null; // ドラッグ中のボタン
let offsetX = 0; // ドラッグ開始位置とのオフセット
let offsetY = 0; // ドラッグ開始位置とのオフセット

function mouseDownHandler(event) {
  if (!isMouseDown) {
    isMouseDown = true; // マウスダウン状態をON
    offsetX = event.clientX - draggedButton.x; // クリック位置からのオフセット
    offsetY = event.clientY - draggedButton.y; // クリック位置からのオフセット
  }
  handleStart(event, 'start'); // ドラッグ開始処理を呼び出す
}

この処理では、clientX(X座標)とclientY(Y座標)を使って、クリック位置とボタンの位置の差をオフセットとして保存しています。このオフセットは、ボタンがドラッグ中にどれくらい動いているのかを追跡するために使用します。

マウス移動時の処理

次に、ユーザーがマウスを動かすと、ボタンの位置が更新され、ドラッグ中のボタンが画面上で動いているように見えます。この処理では、マウスの現在位置を取得し、それを元にドラッグ中のボタンの位置を更新します。

function handleMove(event) {
  if (!isMouseDown || !isDragging || !draggedButton) return; // ドラッグ中かつボタンが存在する場合のみ実行

  const x = event.clientX; // マウスのX座標を取得
  const y = event.clientY; // マウスのY座標を取得

  draggedButton.x = x + offsetX; // ドラッグ先のX座標を更新
  draggedButton.y = y + offsetY; // ドラッグ先のY座標を更新

  drawVirtualButton(ctx); // ボタンを再描画
}

このhandleMove関数は、マウスが動くたびに呼ばれ、ボタンの位置を更新します。draggedButton.xdraggedButton.yをマウスの位置に合わせて更新し、drawVirtualButton(ctx)を使って新しい位置でボタンを再描画します。

マウスアップ時の処理

最後に、ユーザーがマウスボタンを離したときに、ドラッグを終了させ、ボタンの最終位置を保存します。

function mouseUpHandler(event) {
  isMouseDown = false; // マウスダウン状態をOFF
  isDragging = false; // ドラッグ中フラグをOFF
  draggedButton = null; // ドラッグ中のボタンをリセット
  offsetX = 0; // オフセットリセット
  offsetY = 0; // オフセットリセット

  handleStart(event, 'end'); // ドラッグ終了処理を呼び出す
}

mouseUpHandler関数では、マウスボタンを離した時に、ドラッグ状態を終了し、ボタンの位置を保存します。また、handleStart関数を呼び出して、必要な処理を実行します。


まとめ

  • マウス操作の流れ:

    1. マウスダウン時に、ボタンがクリックされた位置を取得し、ドラッグ可能かどうかを確認。
    2. マウス移動時に、ボタンの位置を更新してドラッグを反映。
    3. マウスアップ時に、ドラッグを終了し、最終位置を確定。
  • ドラッグ処理の基本:

    • clientXclientYを使って、マウスの位置を取得し、ボタンの位置を動かす。
    • offsetXoffsetYを使って、ボタンがドラッグされる際に、マウス位置に対するオフセットを調整。
  • タッチ操作への対応: マウスとタッチ操作は非常に似ているため、touchstarttouchmovetouchendを使って、同様の処理を実装します。

3. タッチ操作の処理

タッチ操作は、基本的にはマウス操作と同じロジックを適用できますが、マウスイベントと異なり、タッチイベント(touchstarttouchmovetouchend)を使用します。タッチ操作の場合、指が画面を触ったとき、動かしたとき、指を離したときの各イベントを処理する必要があります。

タッチ開始時の処理

タッチ操作の最初のイベントであるtouchstartでは、ユーザーが画面に指を触れた位置(clientX, clientY)を取得し、その位置からボタンの位置を移動するためのオフセット(offsetX, offsetY)を計算します。

function touchStartHandler(event) {
  const x = event.touches[0].clientX; // タッチされたX座標を取得
  const y = event.touches[0].clientY; // タッチされたY座標を取得

  if (!isMouseDown) {
    isMouseDown = true; // タッチダウン状態をON
    offsetX = x - draggedButton.x; // クリック位置からのオフセットを計算
    offsetY = y - draggedButton.y; // クリック位置からのオフセットを計算
  }

  handleStart(event, 'start'); // ドラッグ開始処理を呼び出す
}
  • event.touches[0]:タッチされた位置のXおよびY座標を取得します。
  • offsetXoffsetY:タッチされた位置とボタンの位置との差を保存し、後でボタンを移動させるために使います。

タッチ移動時の処理

ユーザーがタッチしたまま指を動かすと、touchmoveイベントが発生します。touchmoveイベントでは、タッチ中の位置を追跡し、ボタンをその位置に更新します。

function touchMoveHandler(event) {
  if (!isMouseDown || !isDragging || !draggedButton) return; // ドラッグ中かつボタンが存在する場合のみ実行

  const x = event.touches[0].clientX; // タッチされたX座標を取得
  const y = event.touches[0].clientY; // タッチされたY座標を取得

  draggedButton.x = x + offsetX; // タッチ移動先にボタン位置を更新
  draggedButton.y = y + offsetY; // タッチ移動先にボタン位置を更新

  drawVirtualButton(ctx); // ボタンを再描画
}
  • event.touches[0]:タッチされている最初の位置を取得します。
  • ボタンの位置をdraggedButton.xdraggedButton.yで更新し、その後drawVirtualButton(ctx)でボタンを再描画します。

タッチ終了時の処理

指を画面から離すと、touchendイベントが発生します。このイベントでは、ドラッグを終了し、ボタンの最終位置を確定させます。

function touchEndHandler(event) {
  isMouseDown = false; // タッチダウン状態をOFF
  isDragging = false; // ドラッグ状態をOFF
  draggedButton = null; // ドラッグ中のボタンをリセット
  offsetX = 0; // オフセットをリセット
  offsetY = 0; // オフセットをリセット

  handleStart(event, 'end'); // ドラッグ終了処理を呼び出す
}
  • タッチ操作が終了した時に、isMouseDownfalseにして、ドラッグ状態をリセットします。
  • ボタンの位置を確定し、最終的な更新処理を呼び出します。

まとめ

  • タッチ操作の流れ:

    1. タッチ開始時に、タッチ位置を取得し、オフセットを計算。
    2. タッチ移動時に、ボタンの位置をオフセットに基づいて更新。
    3. タッチ終了時に、ドラッグを終了し、最終位置を確定。
  • タッチ操作とマウス操作:タッチとマウス操作は非常に似ており、touchstarttouchmovetouchendイベントを使ってマウス操作とほぼ同じ処理が可能です。タッチイベントとマウスイベントの違いを理解し、両方に対応できるようにすることで、ユーザーインターフェースをより多様に対応させることができます。

4. 仮想ボタンの位置をリセット

仮想ボタンをリセットする際、ユーザーがボタンの位置を変更した場合でも、リセット操作を行うことでデフォルトの位置に戻すことができます。この操作は、localStorageを利用して、ボタンの位置を保存し、リセット後にそのデフォルト位置を復元します。

リセット操作の実装

まず、リセットボタンがクリックされた際に、確認ダイアログを表示し、ユーザーの同意を得た後に位置をリセットする処理を実装します。

document.getElementById('resetVirtualButton').addEventListener('click', async function () {
  const confirmed = await ConfirmationDialog({
    title: '確認',
    message: '仮想ボタンの位置をデフォルトに戻しますか?',
    okOnly: false, // OKとキャンセルボタンを表示
  });

  if (confirmed) {
    localStorage.removeItem('BreakingBlocks_virtualButtonEnabled'); // 仮想ボタンの位置を保存しているlocalStorageを削除
    resetVirtualButtonPosition();  // 仮想ボタンの位置をリセット
  }
});

説明

  • ConfirmationDialogConfirmationDialogは、ユーザーに確認を求めるダイアログを表示する関数です。ユーザーがOKを押した場合にのみ、ボタンの位置をリセットします。

  • localStorage.removeItem('BreakingBlocks_virtualButtonEnabled'): 仮想ボタンの位置がlocalStorageに保存されている場合、その情報を削除します。このステップは、仮想ボタンの位置をリセットするために必要です。

  • resetVirtualButtonPosition(): この関数は、仮想ボタンの位置をデフォルトの位置に戻します。具体的には、gameConfig内に設定した初期位置にボタンを移動させる処理を行います。

位置リセットの実装例

resetVirtualButtonPosition()関数内で、仮想ボタンの位置をデフォルトに戻す処理を実装します。以下は、その例です:

function resetVirtualButtonPosition() {
  gameConfig.virtualButton.left.x = 100;  // 左ボタンの初期位置
  gameConfig.virtualButton.left.y = 100;  // 左ボタンの初期位置

  gameConfig.virtualButton.right.x = 200; // 右ボタンの初期位置
  gameConfig.virtualButton.right.y = 100; // 右ボタンの初期位置

  gameConfig.virtualButton.magic.x = 300; // 魔法ボタンの初期位置
  gameConfig.virtualButton.magic.y = 100; // 魔法ボタンの初期位置

  gameConfig.virtualButton.pause.x = 400; // ポーズボタンの初期位置
  gameConfig.virtualButton.pause.y = 100; // ポーズボタンの初期位置

  drawVirtualButton(ctx); // ボタンをリセット後に再描画
}

この関数は、ボタンの位置をあらかじめ設定したデフォルト位置にリセットします。その後、drawVirtualButton(ctx)を呼び出して、ボタンを新しい位置に再描画します。


まとめ

  • 仮想ボタンのリセット操作:

    • localStorageを使用してボタンの位置を保存し、リセット後にデフォルトの位置に戻す。
    • ユーザーの確認を得た後に位置をリセットすることで、ユーザーが意図的にリセット操作をしたことを確認する。
  • 位置リセットの実装:

    • ボタンの位置をデフォルトに戻すために、gameConfig内のボタン位置を初期化。
    • localStorage内の保存された情報を削除し、ボタンの位置を再設定。

以下は、まとめに関する解説記事の最終部分です:


5. まとめ

仮想ボタンの実装におけるマウス操作とタッチ操作の扱いについてのポイントを整理します。

マウスとタッチ

基本的に、マウス操作とタッチ操作は非常に似ていますが、イベントの取り扱いが少し異なります。特に、タッチ操作では複数のタッチポイント(touches)が扱えるため、マウスの座標に加えてタッチイベント専用のプロパティを利用する必要があります。このため、タッチ操作にはtouchstarttouchmovetouchendのイベントを使い、マウス操作にはmousedownmousemovemouseupを使います。

  • マウス操作:mousedown, mousemove, mouseup
  • タッチ操作:touchstart, touchmove, touchend

それぞれの操作に対応したイベントリスナーを追加することで、両方の操作をスムーズに処理できます。

共通化

マウス操作とタッチ操作のロジックを共通化することで、コードの冗長さを減らし、効率的に開発を進めることができます。例えば、touchstartmousedownのイベント処理が同じである場合、処理を共通の関数にまとめ、eventオブジェクトがタッチかマウスかに応じて適切に処理を振り分けることが可能です。

function handleStart(event, action) {
  const x = event.touches ? event.touches[0].clientX : event.clientX;
  const y = event.touches ? event.touches[0].clientY : event.clientY;

  // ボタンの処理
  if (isButtonClicked(gameConfig.virtualButton.left, x, y)) {
    // ボタンが押されたときの処理
  }
}

このように、共通化することで、コードがシンプルになり、保守性や拡張性が向上します。

責務の分散

マウス操作とタッチ操作を分けておくことで、特有の処理を加える際にも柔軟に対応できます。例えば、タッチ操作特有のジェスチャーや、マウス操作でのダブルクリックなど、異なるデバイスでの特有の挙動に対応する際にも、柔軟に処理を追加できます。

  • マウス操作の特有の処理:ホイールスクロールやダブルクリックなど。
  • タッチ操作の特有の処理:複数タッチのサポートやスワイプ操作など。

それぞれの操作の責任を分けて管理することで、後々の拡張やバグ修正が容易になります。例えば、タッチイベントに新しいジェスチャーを追加する際、マウス操作のコードに影響を与えずに変更を加えることができます。


結論

  • マウス操作とタッチ操作は似ているが、異なるイベントを使う必要がある。
  • 共通化することで、コードをシンプルにし、開発を効率的に進めることができる。
  • 責務の分散によって、後の拡張やバグ修正が容易になり、異なる操作に柔軟に対応できる。

このように、マウスとタッチの両方に対応した仮想ボタンの実装は、共通化と責務の分散を意識することで、シンプルで柔軟なコードを作成することができます。