Blazor WebAssembly で作った Web アプリ "snow catch" ゲームを、🎮 ゲームパッド (ジョイスティック) で操作できるようにする

# "snow catch" ゲームとは

昨年 Qiita Advent Calendar 2020 への投稿ネタとして、"snow catch" というゲームを、Blazor WebAssembly を使って Web アプリとして作成しました。

"snow catch" ゲームの画面の様子は下図のとおりで、画面下の雪だるまをキーボードの左右の矢印キーで操作して、上から降ってくる雪を捕まえよう、っていう只それだけの Web アプリです。
時間制限もゲームオーバーもありませんw

![image](https://camo.qiitausercontent.com/372dd9ecaad06fbe305f1954b0227104550f5653/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f35383435302f63323565653234342d623332322d633639312d633565612d3166323138306531666631312e676966)

当時の記事は下記になります。

https://qiita.com/jsakamoto/items/8214211c1fd2ff91f46b

また先日はこの "snow catch" ゲームを .NET 5 から .NET 6 へ移行する記事も書きました。

https://qiita.com/jsakamoto/private/7a500d8413b142b8f597


GitHub Pages に配置もしているので、下記 URL をブラウザで開けば実際に遊ぶこともできます。
なお、ブラウザで開けば即開始となります。

https://sample-by-jsakamoto.github.io/SnowCatch-on-BlazorWasm/

# 実は Web ブラウザ上の JavaScript からは 🎮 ゲームパッドにアクセスできる

さて "snow catch" ゲームの操作はキーボードでの操作となっています。
これがなかなか操作性が悪いです。
手抜きをして Hotkey の仕組みで実装したので (つまり、キーボードの Down/Up イベントを丁寧に拾っていないので)、キーリピートの反応速度でしか雪だるまを左右に動かせません。
多くの OS 環境の初期設定では、キーボードを押してから少し間があってからキーリピートが始まると思います。
そのせいで、降ってくる雪を捕まえようと雪だるまを移動開始しても、一瞬引っかかってから動くような感じになっています。

もっとちゃんと、キーボードイベントを処理するようにすれば、操作性を改善できるとは思うのですが、さてところで、ゲームの操作といえば、**🎮ゲームパッド (ジョイスティック)** ではないでしょうか。

実は、**PC に接続されたゲームパッドデバイスは、Web ブラウザ上の JavaScript コードからアクセス**できます (下記参照)。

https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API

2013～2014 年頃の Google Chrome や Mozilla Firefox あたりからゲームパッド対応が始まり、macOS の Safari ブラウザは 2017年頃から対応されているようです (出典は下記)。

https://caniuse.com/gamepad

ということで、**"snow catch" ゲームを、ゲームパッドでも操作できるように改造**してみることにしました!

# ゲームパッド対応する

前述のとおり、昨今の Web ブラウザでは、JavaScript からゲームパッドデバイスにアクセスするための **Gamepad API** が提供されています。

そして Blazor には、C# 側から JavaScript の各種機能を呼び出したり、逆に JavaScript 側から C# 側実装を呼び出したりできる、**"JavaScript 相互運用" 機能**が提供されています。

https://docs.microsoft.com/ja-jp/aspnet/core/blazor/javascript-interoperability/

これらを組み合わせることで、Blazor WebAssembly アプリでもゲームパッド対応が可能となります。

...しかし、可能とは言えども、JavaScript なら手間無しで即アクセスできる Gamepad API なのに、Blazor では JavaScript 相互運用を介してやりとりしなくてはいけないのは、やや面倒臭い話です。
なんとかならないものでしょうか。

幸い多くのケースでは、JavaScript 用に提供されているブラウザ API をラップした、Blazor 向けクラスライブラリが NuGet パッケージとして提供されています。
つまり、それら NuGet パッケージを利用すれば、**使い慣れた C# 構文のまま、ブラウザ API にアクセスできる**、というわけです。

Gamepad API も多分に漏れず、Blazor 向けクラスライブラリにラップした NuGet パッケージを (私が😁) 提供しています (下記リンク先)。

https://www.nuget.org/packages/Toolbelt.Blazor.Gamepad/

ということで、この NuGet パッケージを利用して、"snow catch" ゲームをゲームパッド対応していきます。

## 1. プロジェクトに "Toolbelt.Blazor.Gamepad" NuGet パッケージ参照を追加

まずは "snow catch" ゲームのプロジェクトに、"Toolbelt.Blazor.Gamepad" NuGet パッケージの参照を追加します。

Visual Studio をお使いなら、「パッケージの管理」GUI から "Blazor Gamepad" などのキーワードで検索して追加するのもよし、dotnet CLI なら、`SnowCatch.csproj` があるフォルダで下記コマンドを実行すればよいでしょう。

```shell
dotnet add package Toolbelt.Blazor.Gamepad
```

## 2. GamepadList サービスを DI に登録

続けて、Gamepad API へのアクセスを提供する開始地点となる、`Toolbelt.Blazor.Gamepad` NuGet パッケージから提供される `GamepadList` サービスを、DI コンテナに登録しておきます。

```csharp:Program.cs
...
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddHotKeys();
builder.Services.AddGamepadList(); // 👈 この行を追加

await builder.Build().RunAsync();
```
こうしておくことで、任意の Razor コンポーネントで `GamepadList` サービスインスタンスを入手してゲームパッドにアクセスできるようになります。

## 3. ゲームループの中でゲームパッドの状態をチェックして動きに反映

準備が整ったので、`App.razor` コンポーネント中でハンドルしているゲームループの中で、ゲームパッドの状態 (方向ボタンが押されているか) をチェックし、雪だるま動きに反映していきます。

`App.razor` 冒頭に、`GamepadList` サービスインスタンスを DI コンテナから注入してもらうよう、`@inject` ディレクティブの記述を追加します。

```html:App.razor
@using Toolbelt.Blazor.HotKeys
@inject HotKeys HotKeys
@inject Toolbelt.Blazor.Gamepad.GamepadList GamepadList @* 👈 この行を追加 *@
@implements IDisposable
...
```
こうして `GamepadList` サービスインスタンスに手が届いたら、`App.razor` に既にあるゲームループのハンドラ (`GameLoopTimer_Elapsed()`) 内に、`GamepadList` サービスインスタンスを介してのゲームパッド状態の確認、および、雪だるまの移動を行なう処理を追記します。

```csharp:App.razor
      // 👇 非同期メソッド化のために async キーワードを追加
private async void GameLoopTimer_Elapsed(object sender, EventArgs e)
{
    // -- 👇 ここから... ---

    // 接続されてるゲームパッドのうち、方向ボタンが装備されているゲームパッドを1つ選び出す。
    var gamepads = await this.GamepadList.GetGamepadsAsync();
    var gamepad = gamepads.FirstOrDefault(g => g.Axes?.Count >= 2);

    // もしそのようなゲームパッドが見つかったら...
    if (gamepad != null)
    {
        // そのゲームパッドの方向ボタンについて、左右いずれかに操作されていたら、
        // その操作方向に雪だるまを移動させる。 
        if (gamepad.Axes[0] < -0.8) this.Context.MoveSnowManToLeft();
        if (gamepad.Axes[0] > 0.8) this.Context.MoveSnowManToRight();
    }

    // -- 👆 ...ここまでを追加 ---

    this.StateHasChanged();
}
```

以上で "snow catch" ゲームを、ゲームパッドで操作できるようになりました!

"snow catch" ゲームをゲームパッド対応させるための差分は下記コミットでも確認頂けます。

https://github.com/sample-by-jsakamoto/SnowCatch-on-BlazorWasm/commit/6b5350ea75d4856c2817d5783c8bc5ff83bf6dd9

あ、ちょっとだけ注意を。

セキュリティ上の都合でしょうか、**Gamepad API は、https プロトコルでホストされている HTML ページ上でないと、動作しない**ようです (MDN の説明ページにそのようなことが書いてありました)。
自分ではちゃんと検証はしていませんが、Gamepad API を使った Web アプリを開発・公開する際はご注意ください。

# さいごに

## 実際にお試しできるライブデモ

冒頭でリンク先を紹介した、**GitHub Pages 上に配置した "snow catch" ゲームは、ゲームパッド対応済み**です。
なので、お手元のゲームパッドを接続しておけば、ゲームパッドで雪だるまの左右移動操作が可能です。

また、今回紹介の `Toolbelt.Blazor.Gamepad` NuGet パッケージの開発リポジトリでも、GitHub Pages としてライブデモサイトを公開しています。
リンク先は下記。

https://jsakamoto.github.io/Toolbelt.Blazor.Gamepad/

下図のような感じで、あくまでもデモンストレーションなので完成したゲームとかではないのですが、ゲームパッドによる操作を試すことができるようになっています。

![movie](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.Gamepad/master/.assets/movie-001.gif)

## ところで、なぜゲームパッド? (キーボードで充分では?)

ところで、わざわざゲームパッドを操作用のデバイスとして採用するメリットは何があるでしょうか。

もちろんゲームのような Web アプリにおける操作性がよいという理由が筆頭にあげられると思います。
しかし、それ以外にもうひとつ自分に思いつくのは、**ブラウザにフォーカスがなくても操作できる**という点が挙げられます。

実際に前述のライブデモでお試し頂くとわかりますが、**ブラウザウィンドウがアクティブでなくても、ゲームパッドで操作ができるのです。**

自分はこの特性を活かして、**ライブカメラ映像を表示する Web ページに、ゲームパッドでカメラの向きを操作する機能を、Blazor を使って実装した**経験があります。
他の作業のためにブラウザ以外のアプリのウィンドウをアクティブにしていても、片手間でゲームパッドを操作すればカメラの撮影範囲を変えることができる、という仕組みです。

このようにゲームパッドは、**ゲーム用途に限らず、工業用途などビジネス的な場面**でも便利に使えることがあります。

# おまけ: ゲームパッドに限らず Blazor アプリから外部機器にアクセスする

以上のとおり、Blazor アプリケーションからもゲームパッドのような外部機器にアクセスできるわけですが、昨今の Web ブラウザ、とくに Chromium ベースのブラウザは、**ゲームパッド以外の外部機器へのアクセス API** もいろいろ追加されています、

例えば、Web ブラウザ内の JavaScript から Bluetooth LE 機器に接続して制御したりすることができます (※実は自分は試したことはないのですが...)。

そしてまた、それら API に対する Blazor 向けラッパー NuGet パッケージも併せて公開されています。
先日開催されたオンラインイベント ".NET Conf 2021" では、**Blazor アプリケーションからそのような外部機器をいろいろ制御する**セッションもありました。

下記のとおりセッション動画が公開されていますので、興味のある方はぜひ視聴してみてください。

<iframe width="560" height="315" src="https://www.youtube.com/embed/-thoDIEshl8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

以上です!