ES2015(ES6)なブラウザゲームをつくる-3: プレイヤーを動かせられるようにする

# このページについて
[コンパイルしないES2015(ES6)なブラウザゲーム](https://qiita.com/snakada/items/eb86ed81e0eb28e8d0ea)を作ります。ES2015(ES6)を触り始めたとか、これから触ってみたいとか、HTML/CSS/JSがなんとなくわかるとかいう方向けに、コマンドとか難しいものを使わずに、Chromeで動くコンパイル不要なES2015ゲームを作ろうというのをコンセプトに掲げて数回にわけて連載しているチュートリアル記事です。

## TL;DR
前の記事は [各DOMをjsで生成する](https://qiita.com/snakada/items/f082e3db509e97279323) でしたが、何も動かなかくてつまらなかったので、本記事ではせめてプレイヤーだけでも動かせられるようにします。

<details>
<summary>シリーズ目次はこちら</summary>
- phase0: [はじめに、対象者、使う技術、成果物](https://qiita.com/snakada/items/eb86ed81e0eb28e8d0ea) ([demo](https://snakada.github.io/wallgame/))
- phase1: [仕様の確認と設計、基本パーツのHTMLとCSSを作る](https://qiita.com/snakada/items/538067a6cae7d92eb866) ([demo](https://snakada.github.io/wallgame/phase1/))
- phase2: [各DOMをjsで生成する](https://qiita.com/snakada/items/f082e3db509e97279323) ([demo](https://snakada.github.io/wallgame/phase2/))
- phase3: **プレイヤーを動かせられるようにする** ([demo](https://snakada.github.io/wallgame/phase3/))
- phase4: プレイヤーが壁に当たらないようにする ([demo](https://snakada.github.io/wallgame/phase4/))
- phase5: プレイヤーを動かすとスコアが増えるようにする ([demo](https://snakada.github.io/wallgame/phase5/))
- phase6: 壁をランダムに生成する ([demo](https://snakada.github.io/wallgame/phase6/))
- phase7: 壁を自動で動くようにする ([demo](https://snakada.github.io/wallgame/phase7/))
- phase8: プレイヤーと壁が衝突するとゲームオーバーにする ([demo](https://snakada.github.io/wallgame/phase8/))
- phase9: ゲームオーバーになったらタイマーを止める ([demo](https://snakada.github.io/wallgame/phase9/))
</details>

## プレイヤー位置を取得するための関数を追加しておく - js/dom.js

動かすために、まずはプレイヤーがどこにいるのかを知る必要がありまして。そのための関数をいくつか `js/dom.js` に追加しておきます。ひとつの関数にまとめても良かったのですけど、見通しの良さを考慮して敢えて少し細切れにわけてあります。

主に使いたいのは最後の `getDomPositions()` で指定DOM（今回はプレイヤー）の位置情報オブジェクトを取得する関数なのですが、ブラウザネイティブの [window.getComputedStyle()](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle) だと情報量多すぎなので、必要になる `{top, bottom, left, right, height}`だけに絞って返しているという感じです。

あとデフォルトだと文字列で動かすときの計算に扱いづらいので、半ば強引感ありますけど `parseInt()` で数値化しておきます。

````js/dom.js
/**
 * 対象となるDOMの算出スタイルのうち、
 * 指定したCSSプロパティのみを返します。
 * @param  {object} $dom     処理対象のDOM
 * @param  {string} property 取得したいCSSプロパティ名
 * @return {string}          CSSプロパティ値
 */
const getDomStyle = ($dom, property) =>
  window.getComputedStyle($dom, null)[property];

/**
 * 対象となるDOMの算出スタイルのうち、
 * 指定したCSSプロパティのみを配列にして返します。
 * @param  {object}   $dom     処理対象のDOM
 * @param  {string[]} property 取得したいCSSプロパティ名の配列
 * @return {string[]}          CSSプロパティ値の配列
 */
const getDomStyles = ($dom, properties) => {
  const styles = window.getComputedStyle($dom, null);
  return properties.map(property => styles[property]);
};

/**
 * ポジション情報に限定してCSSプロパティの配列を返します。
 * @param  {object}   $dom 処理対象のDOM
 * @return {string[]}      ポジション関連のCSSプロパティ値の配列
 */
const getDomPositionsArray = $dom =>
  getDomStyles($dom, ['top', 'bottom', 'left', 'right', 'height']);

/**
 * 指定したDOMの位置情報をオブジェクトで返します。
 * @param  {object} $dom       処理対象のDOM
 * @return {object} pos        ポジション関連のCSSプロパティ値のオブジェクト
 * @return {string} pos.top
 * @return {string} pos.bottom
 * @return {string} pos.left
 * @return {string} pos.right
 * @return {string} pos.height
 */
const getDomPositions = $dom => {
  const [top, bottom, left, right, height] =
    getDomPositionsArray($dom).map(property => parseInt(property));
  return {top, bottom, left, right, height};
};
````

## プレイヤーは、自分がどう動けるのかを定義しておく - js/player.js

プレイヤーは動ける子なので、そのように定義しておきます。つまり上下左右にそれぞれ1マス(20px)動けるということを書いておきます。 ベースとなるのは `movePlayer()` ですが、それを応用して上下左右の移動も定義してあります。

なお `setDomStyle()` というのが使われていますが、 [前回の記事](https://qiita.com/snakada/items/f082e3db509e97279323) で実装してあったものです。

````player.js
/**
 * プレイヤーDOMを生成します。
 */
const $player = createDivWithId('player');
setDomStyle($player, 'top',  '100px');
setDomStyle($player, 'left', '40px');

/**
 * 現在のプレイヤーの位置を取得します。
 * @return {object} pos        プレイヤーのポジション関連のCSSプロパティ値のオブジェクト
 * @return {number} pos.top
 * @return {number} pos.bottom
 * @return {number} pos.left
 * @return {number} pos.right
 * @return {number} pos.height
 */
const getPlayerPositions = () => getDomPositions($player)

/**
 * プレイヤーDOMを動かします。
 * @param {number} moveTop  垂直方向に動かす距離(px)
 * @param {number} moveLeft 水平方向に動かす距離(px)
 */
const movePlayer = (moveTop, moveLeft) => {
  const {top, left} = getPlayerPositions();
  const newTop  = top  + moveTop;
  const newLeft = left + moveLeft;
  setDomStyle($player, 'top', `${newTop}px`);
  setDomStyle($player, 'left', `${newLeft}px`);
};

/**
 * プレイヤーDOMを上下左右へ1マス分(20px)移動させる関数を
 * それぞれ定義しておきます。
 */
const movePlayerUp    = () => movePlayer(-20, 0);
const movePlayerDown  = () => movePlayer(20, 0);
const movePlayerLeft  = () => movePlayer(0, -20);
const movePlayerRight = () => movePlayer(0, 20);
````

## [NEW!] プレイヤーを操作するコントローラ - js/control.js

本記事で新たに作成するjsです。あとで `index.html` に追加しなきゃですね。なおコントローラというと `MVC` 的なやつを思い浮かべるかもしれないのですが、今回はそっちではなくて、どちらかというと DUALSHOCK 的な、ゲームのリモコン的なアレが近いです。

すなわち、PCキーボードの上を押したら `movePlayerUp()`、右なら `movePlayerRight()` といった具合です。割とシンプルな考え方＆実装なので、以下のコードをご覧になってみてください。`Arrow**` はPCキーボードの上下左右のキーにそれぞれ応じていて、先程定義したプレイヤーがどう動くかという関数に紐づけています。

````control.js
/**
 * キーダウンでプレイヤーを動かします。
 */
document.addEventListener('keydown', event => {
  switch(event.key) {
    case 'ArrowUp':
      movePlayerUp();
      break;
    case 'ArrowDown':
      movePlayerDown();
      break;
    case 'ArrowLeft':
      movePlayerLeft();
      break;
    case 'ArrowRight':
      movePlayerRight();
      break;
  }
});
````

## コントローラを反映する

ここまででプレイヤーを動かすためのものが揃いました。先程の `js/control.js` は今回あらたに作ったものなので `index.html` に追記しておきます。これで `index.html` をブラウザで開いて、キーボードの上下左右のキーをタイプすると 1マスずつ動くようになっていると思います。

ただし今回は動けるようになっただけで、プレイヤーは自分がどこまで行っていいのかまでは知りませんので、枠をはみ出して、なんならブラウザをはみ出してでもどこまでも行ってしまいます。次回はプレイヤーが自分のいるべき場所がどこなのかを知れるように実装していきたいと思います。

````index.html
  <!-- (省略) -->
  <body>
    <script src="./js/dom.js"></script>
    <script src="./js/player.js"></script>
    <script src="./js/wall.js"></script>
    <script src="./js/score.js"></script>
    <script src="./js/field.js"></script>
    <script src="./js/gameover.js"></script>
    <script src="./js/app.js"></script>
    <script src="./js/control.js"></script> <!-- ← これを追加！ -->
  </body>
````

本日は以上です。

ここで提示したコードは https://github.com/snakada/wallgame/tree/phase3 でもご覧いただけます。
また実際の画面は https://snakada.github.io/wallgame/phase3/ でご覧いただけます。

その他、詳しく聞きたい箇所やリクエスト等ありましたら、お気軽にコメントください。
また近日中に続きの記事を書きたいと思います。よろしくお願いします。