ゲームを作りながら学ぶ！JavaScript レベルアップ講座 part7

こんにちは、[Yui](https://twitter.com/yui_active)です。

今回はプレイヤーの行動リストを表示してみたいと思います。

今回の完成物はこちらです。

<a href="https://gyazo.com/4934bb07877f46a697627594521f570f"><img src="https://i.gyazo.com/4934bb07877f46a697627594521f570f.gif" alt="Image from Gyazo" width="636"/></a>

# 行動リストを表示する

それでは前回作った``game.js``に「たたかう」「ぼうぎょ」「まほう」「どうぐ」の4つのコマンドを追加していきます。

まずはその4つのコマンドを配列で格納します。

``GameManager``クラスの中に、コマンドを表示する部分として``showCommand``として新しいメソッドを追加します。

```javascript:game.js

  showCommand() {
    this.actions = [
      { name: "たたかう" },
      { name: "ぼうぎょ" },
      { name: "まほう" },
      { name: "どうぐ" },
    ]
    this.actions.forEach((action, index) => {
      this.ctx.fillText(action.name, 50, index * 30 + 350)
    })
  }
```

現時点で「たたかう」「ぼうぎょ」「まほう」「どうぐ」の4つのコマンドが画面に表示されたかと思います。それでは次に▷をそのコマンドの隣に表示するようにしましょう。

```javascript
this.ctx.fillText("▷", 20, 350)
```

このコードを上記の``showCommand``の中に入れれば▷が「たたかう」の隣に現れたかと思います。

ただ、現時点では▷は動かしても動きませんね。
これは▷のy座標が350で固定されてしまっているからです。
そこで、y座標を矢印キーの動きに合わせて動かしていきたいと思います。

# ▷を動かす

さて、▷を動かすためには上述の通り現時点のy座標を流動的にする必要があります。
というわけでy座標にあたるものを``this.setAction``として、初期値を350で設定します。
初期値なので、``GameManager``内の``constructor()``で設定します。

```javascript:game.js
class GameManager {
  constructor() {
    const canvas = document.getElementById("canvas")
    this.ctx = canvas.getContext("2d")
    this.setAction = 350; //<=追加
    this.characterList = []; 
  }

...
```

そして``chooseCommand``というメソッドを新しく作り、その中で``this.setAction``の値を変更していきたいと思います。

```javascript:game.js
  chooseCommand(e) {
    if (e.key === "ArrowDown" && this.setAction < 440) {
      this.setAction += 30;
      this.ctx.clearRect(20,300, 25, 480)
      this.ctx.fillText("▷", 20, this.setAction)
    }
    if (e.key === "ArrowUp" && this.setAction > 350) {
      this.setAction -= 30;
      this.ctx.clearRect(20,300, 25, 480)
      this.ctx.fillText("▷", 20, this.setAction)
    }
  }
```

このへんのロジックは前回やったスライムを動かすものと同じです。
this.setActionの値で制限をつけているのは、必要以上に▷が上がりすぎたり下がりすぎたりしないためです。

clearReactで消す部分に関しては、▷が表示されている部分だけで良いので、細かく範囲を調整しています。

あとは、この部分を``addEventListener``で制御すれば動きます。以下のコードを``gameManager``外の部分に書きます。

```javascript:game.js
window.addEventListener("keydown", (e) => {
  gameManager.chooseCommand(e);
})

```

そしてそれに合わせてもちろん初期値で設定していた``this.ctx.fillText("▷", 20, 350)``を``this.ctx.fillText("▷", 20, this.setAction)``に変更します。
これでgame.jsの中身は以下のようになりました。

```javascript:game.js

class GameManager {
  constructor() {
    const canvas = document.getElementById("canvas")
    this.ctx = canvas.getContext("2d")
    this.setAction = 350;
    this.characterList = []; 
  }
  addCharacter(character) {
    this.characterList.push(character);
  }

  showCharacterStatus() {
    this.ctx.clearRect(0,0,640,480)
    this.ctx.font = `28px serif`;
    this.ctx.fillStyle = "black"
    this.ctx.fillText("HP", 20, 50)
    this.ctx.fillText("MP", 20, 90)
    this.ctx.fillText("名前", 20, 130)
    
    this.characterList.forEach((chara, index) => {
      this.ctx.fillText(chara.hp, 190 * index + 140, 50)
      this.ctx.fillText(chara.mp, 190 * index + 140, 90)
      this.ctx.fillText(chara.name, 190 * index + 140, 130)
    })
  }
  showCommand() {
    this.actions = [
      { name: "たたかう" },
      { name: "ぼうぎょ" },
      { name: "まほう" },
      { name: "どうぐ" },
    ]
    this.actions.forEach((action, index) => {
      this.ctx.fillText(action.name, 50, index * 30 + 350)
    })
    this.ctx.fillText("▷", 20, this.setAction)
  }
  chooseCommand(e) {
    if (e.key === "ArrowDown" && this.setAction < 440) {
      this.setAction += 30;
      this.ctx.clearRect(20,300, 25, 480)
      this.ctx.fillText("▷", 20, this.setAction)
    }
    if (e.key === "ArrowUp" && this.setAction > 350) {
      this.setAction -= 30;
      this.ctx.clearRect(20,300, 25, 480)
      this.ctx.fillText("▷", 20, this.setAction)
    }
  }
}

//以下のモンスター表示部分もGameManager内にメソッド追加でできますが、現状は前回のままにしておきます。
class Monster {
  constructor(posX, posY, image) {
    const canvas = document.getElementById("canvas")
    this.ctx = canvas.getContext("2d")
    const img = new Image()
    img.src = image
    this.img = img
    this.posX = posX
    this.posY = posY
    this.sizeX = 100
    this.sizeY = 100
    img.onload = () => this.drawImage()
  }
  drawImage() {
    this.ctx.drawImage(this.img, this.posX, this.posY, this.sizeX, this.sizeY)
  }
}

new Monster(100, 200, "./img/bone.png");
new Monster(260, 200, "./img/pumpkin.png");
new Monster(420, 200, "./img/ghost.png");

class PlayerCharacter {
  constructor(name, hp, mp) {
    this.name = name;
    this.hp = hp;
    this.mp = mp;
  }
}

var chara1 = new PlayerCharacter("アベル", 100, 80);
var chara2 = new PlayerCharacter("カイン", 100, 55);
var chara3 = new PlayerCharacter("プリン", 100, 45);

var gameManager = new GameManager();
gameManager.addCharacter(chara1);
gameManager.addCharacter(chara2);
gameManager.addCharacter(chara3);
gameManager.showCharacterStatus();
gameManager.showCommand();

window.addEventListener("keydown", (e) => {
  gameManager.chooseCommand(e);
})
```

これで無事▷が動くようになったかと思います。
ただ、このままではクリックをしても何も起こりません。
そこで次回はクリック時のアクションを追加していきます。

# おまけ

こちらのコードに関しては簡略的に書いているため、かなり雑な作りとなっています。
もう少しちゃんと書くのであれば、親となるクラスと子となるクラスを細かく分けて、jsファイルも分けてしまってからextendsで継承させて書いていくような書き方のほうが見やすいかと思います。

また、上述のコメントアウトでも書きましたが、モンスター表示部分（Monsterクラス）に関してはGameManager内にメソッドとして入れるなり、そもそもファイルを分けて親クラスGameManagerを継承するなり、色々書き方はありますが、現状ではこのままとしておきます。

ぜひ時間のある際にでも色々と書き換えてみて、試してみてください。