【完走賞ゲット-10】gamecontroller.js を使って DualShock 4 を JavaScript で扱う

## はじめに
こちらは、[完走賞ゲットのため小ネタを毎日投稿しようとチャレンジする Advent Calendar 2022](https://qiita.com/advent-calendar/2022/youtoy) の 10日目の記事です。

12月に試していくつか記事を書いてみたりもした「[WebHID](https://developer.mozilla.org/ja/docs/Web/API/WebHID_API)」関連の調べものをしていて、そこからたどり着いた [Gamepad API](https://developer.mozilla.org/ja/docs/Web/API/Gamepad_API/Using_the_Gamepad_API)関連のライブラリ「[gamecontroller.js](https://github.com/alvaromontoro/gamecontroller.js/)」に関する話です。

これを、以下の記事でも扱っていた「DualShock 4」で試します。

●【完走賞ゲット-6】「WebHID DualShock 4 Demo」というサイトを軽く試してみる - Qiita
　https://qiita.com/youtoy/items/468454fe968ff3632511

https://twitter.com/youtoy/status/1601338085031059457

## gamecontroller.js を試す
DualShock 4 を PC と USB接続して試していきます。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">ちなみに、接続は有線。<br>電波環境の悪いところでの展示などを考えると、有線と無線の両方が扱えるのはありがたい <a href="https://t.co/bS7SUIo435">pic.twitter.com/bS7SUIo435</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1601358412234645504?ref_src=twsrc%5Etfw">December 9, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### Node.js版を試そうとした
まずは、Node.js版を試します。インストールは、 `npm i gamecontroller.js` で。

https://twitter.com/youtoy/status/1601326799396679680

サンプルプログラムがいくつかあるようです。
![サンプルプログラムのリスト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/137016e2-dd14-c767-7dd1-5d4c1e12068f.png)

そして、↓こちらを実行してみようとしましたが...
●gamecontroller.js/example-3-buttons-and-joysticks.html at master · alvaromontoro/gamecontroller.js · GitHub
　https://github.com/alvaromontoro/gamecontroller.js/blob/master/examples/example-3-buttons-and-joysticks.html

これは、Node.js を使わないやり方のほうが楽なのでは？、と思い、これはスキップして次に進むことにしました。

### HTMLファイルで CDN のファイルを読み込んで使う
公式のページには、スクリプトタグから読み込むやり方が書いてありました。そして、CDN経由での読み込みもできそうでした。

https://twitter.com/youtoy/status/1601327154306113536

これらを、上で用いようとしていたサンプルで試すことにします。

●gamecontroller.js/example-3-buttons-and-joysticks.html at master · alvaromontoro/gamecontroller.js · GitHub
　https://github.com/alvaromontoro/gamecontroller.js/blob/master/examples/example-3-buttons-and-joysticks.html

以下は、サンプルに少し手を加えたものです。
（CSS の読み込みをコメントアウトして、gamecontroller.js を CDN から読み込むようにしました）

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>GameControl Example: Buttons and Joysticks</title>
    <!-- <link rel="stylesheet" href="./examples.css" /> -->
    <style>
      svg {
        display: block;
        margin: 1rem auto;
        max-width: 500px;
        width: 100%;
      }
      .active {
        fill: yellow;
      }
      path.active {
        fill: red;
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/gamecontroller.js@1.5.0/dist/gamecontroller.min.js"></script>
  </head>
  <body>
    <main>
      <h1>Example: Buttons and Joysticks</h1>
      <p>
        Connect a gamepad to the computer, and click the buttons or move the different
        joysticks/axes. They will highlight as they are pressed.
      </p>
      <svg viewBox="0 0 600 300">
        <g fill="#ddd" stroke="#222" stroke-width="3">
          <rect x="150" y="10" width="100" height="100" rx="5" ry="5" id="button-6" />
          <rect x="350" y="10" width="100" height="100" rx="5" ry="5" id="button-7" />
          <rect x="100" y="25" width="100" height="40" rx="5" ry="5" id="button-4" />
          <rect x="400" y="25" width="100" height="40" rx="5" ry="5" id="button-5" />
          <path
            d="M135,50 C 45,50 20,180 20,240 20,300 80,330 175,220 175,220 
          425,220 425,220 520,330 580,300 580,240 580,180 555,50 465,50 Z"
          />
          <circle cx="200" cy="200" r="35" />
          <circle cx="400" cy="200" r="35" />
          <circle cx="200" cy="200" r="15" id="button-10" />
          <circle cx="400" cy="200" r="15" id="button-11" />
          <path d="M190,182 210,182 200,168 Z" stroke-width="0" id="axe-0-up" />
          <path d="M190,218 210,218 200,232 Z" stroke-width="0" id="axe-0-down" />
          <path d="M218,190 218,210 232,200 Z" stroke-width="0" id="axe-0-right" />
          <path d="M182,190 182,210 168,200 Z" stroke-width="0" id="axe-0-left" />
          <path d="M390,182 410,182 400,168 Z" stroke-width="0" id="axe-1-up" />
          <path d="M390,218 410,218 400,232 Z" stroke-width="0" id="axe-1-down" />
          <path d="M418,190 418,210 432,200 Z" stroke-width="0" id="axe-1-right" />
          <path d="M382,190 382,210 368,200 Z" stroke-width="0" id="axe-1-left" />

          <circle cx="480" cy="160" r="15" id="button-0" />
          <circle cx="510" cy="130" r="15" id="button-1" />
          <circle cx="450" cy="130" r="15" id="button-2" />
          <circle cx="480" cy="100" r="15" id="button-3" />

          <rect x="105" y="85" width="30" height="90" fill="#aaa" stroke="#aaa" />
          <rect x="75" y="115" width="90" height="30" fill="#aaa" stroke="#aaa" />

          <rect x="245" y="145" width="50" height="18" rx="9" ry="9" id="button-8" />
          <rect x="305" y="145" width="50" height="18" rx="9" ry="9" id="button-9" />

          <circle cx="120" cy="160" r="15" id="button-13" />
          <circle cx="90" cy="130" r="15" id="button-14" />
          <circle cx="150" cy="130" r="15" id="button-15" />
          <circle cx="120" cy="100" r="15" id="button-12" />

          <circle cx="300" cy="90" r="20" id="button-16" />
        </g>
        <g
          dominant-baseline="middle"
          text-anchor="middle"
          fill="#222"
          font-size="16"
          font-family="Arial,sans-serif"
        >
          <text x="480" y="160">0</text>
          <text x="510" y="130">1</text>
          <text x="450" y="130">2</text>
          <text x="480" y="100">3</text>
          <text x="150" y="40">4</text>
          <text x="450" y="40">5</text>
          <text x="225" y="30">6</text>
          <text x="375" y="30">7</text>
          <text x="270" y="156">8</text>
          <text x="330" y="156">9</text>
          <text x="200" y="200">10</text>
          <text x="400" y="200">11</text>
          <text x="120" y="100">12</text>
          <text x="120" y="160">13</text>
          <text x="90" y="130">14</text>
          <text x="150" y="130">15</text>
          <text x="300" y="90">16</text>
          <text x="270" y="175" font-size="10">SELECT</text>
          <text x="330" y="175" font-size="10">START</text>
        </g>
      </svg>

      <p class="note">
        This is a 17-button
        <a href="https://w3c.github.io/gamepad/#remapping"
          >standard gamepad layout as defined on the W3C Gamepad API definition</a
        >. <strong>It may not match the gamepad that is connected</strong>, but it can be used to
        see how the buttons/joysticks in that gamepad match the buttons in the standard gamepad.
      </p>

      <h2>How it works</h2>
      <p>Event handlers can be associated to the buttons using the <code>.on()</code> method:</p>
      <p class="code"><code>gamepad.on(DIRECTION_NAME+AXE_ID, CALLBACK);</code></p>
      <p></p>
      <p>
        Only one action is allowed by button/joystick/axe. If you use several
        <code>.on()</code> with the same direction, the latest call to it will be the one that is
        applied when the joystick is pressed.
      </p>
      <pre class="code"><code>gameControl.on('connect', function(gamepad) {
  gamepad.on('select', function() {
    // do something
  });
});</code></pre>
      <p>
        Some buttons/directions have aliases, so it is easier to associate events to them. For
        example, if we only use the name of the direction ("up", "dowm", "right", or "left"), the
        event handler will be associated to that direction in the first joystick/axe:
      </p>
      <pre class="code"><code>gamepad.on('up0', function() {
  // do something
});

gamepad.on('up', function() {
  // do something
});</code></pre>
      <p class="note">
        Using aliases doesn't mean that you will be able to use more than one action per direction,
        as they are the same.
      </p>
      <p>
        The available direction aliases are:
      </p>
      <ul>
        <li><code>up</code>: equivalent to <code>up0</code>.</li>
        <li><code>down</code>: equivalent to <code>down0</code>.</li>
        <li><code>right</code>: equivalent to <code>right0</code>.</li>
        <li><code>left</code>: equivalent to <code>left0</code>.</li>
      </ul>
      <p>
        The available button aliases are:
      </p>
      <ul>
        <li><code>l1</code>: equivalent to <code>button4</code>. Left back button 1</li>
        <li><code>l2</code>: equivalent to <code>button6</code>. Left back button 2</li>
        <li><code>r1</code>: equivalent to <code>button5</code>. Right back button 1</li>
        <li><code>r2</code>: equivalent to <code>button7</code>. Right back button 2</li>
        <li>
          <code>select</code>: equivalent to <code>button8</code>. (Works for <kbd>Select</kbd>/<kbd
            >Back</kbd
          >
          in some gamepads)
        </li>
        <li>
          <code>start</code>: equivalent to <code>button9</code>. (Works for <kbd>Start</kbd>/<kbd
            >Forward</kbd
          >
          in some gamepads)
        </li>
        <li>
          <code>power</code>: equivalent to <code>button16</code>. It is the power button available
          on some gamepads (e.g. the XBox logo button)
        </li>
      </ul>
    </main>
    <script src="../dist/gamecontroller.min.js"></script>
    <script>
      gameControl
        .on('connect', function(gp) {
          for (let x = 0; x < Math.min(17, gp.buttons); x++) {
            gp.on('button' + x, function() {
              document.querySelector('#button-' + x).classList.toggle('active', true);
            });
          }
          for (let x = 0; x < Math.min(2, gp.axes); x++) {
            const directions = ['up', 'down', 'right', 'left'];
            for (let d = 0; d < directions.length; d++) {
              gp.on(directions[d] + x, function() {
                document
                  .querySelector('#axe-' + x + '-' + directions[d])
                  .classList.toggle('active', true);
              });
            }
          }
        })
        .on('beforeCycle', function() {
          const active = document.querySelectorAll('.active');
          for (let x = 0; x < active.length; x++) {
            active[x].classList.toggle('active', false);
          }
        });
    </script>
  </body>
</html>
```

この HTMLファイルをブラウザで開き、DualShock 4 のキーを押してみます。
そうすると、ページ上部のコントローラーの絵のところで、押下したキーと対応する部分が黄色く色がつくという動作が確認できました。

https://twitter.com/youtoy/status/1601341775217831936

ボタンとジョイスティックは、一通り、反応したのを確認できました。

これは今後、活用していきたくなります！

## その他メモ
以下、Gamepad API関連のことをやるのに、便利そうなのでメモ。

https://twitter.com/youtoy/status/1601362591888310272