Vue + Nuxtでボードゲームを6時間で作ってみた

20年前ぐらいはフリーゲームクリエイターとして活動していたドラレプです。
（大学、社会人になってからはめっきり活動もしなくなって死亡説すら流れていました。）

今はWebエンジニアとしてPHPやらRuby、Javascriptを主戦場にしてゲームからは離れていたのですが、Vueを触っていくうちに「あれ？これボードゲーム作りやすいんじゃ？」という気持ちが芽生えてきました。

# Vueがボードゲーム作りに適していると思った理由

- 状態に合わせたレンダリングはお手の物。
- 状態の変化に合わせたアニメーションもtransitionなどの仕組みで簡単にできる。

つまりただactionをdispatchしてstateを変化させるだけで、破壊のアニメーションや、コマの移動などの動きの部分まで全部やってくれちゃうわけですね。
この辺りについては過去に軽くUnityやcocosを触った感じに比べると、より簡単に出来そう感があります。
（慣れの問題もあるかと思いますが）

とはいえ作ってみないとわからない問題もあるため、ひとまず作ってみることにしました。

# Vueでゲーム作ってみた
Vue + Nuxtで作成開始です。
とにかく、細かいことにこだわらずにスピード重視でまず1つ作りきるを目標にやっていきます。

実際どんなゲームにするか最初の時点では決まっていませんでしたが、作ってるうちにゲームはこんな感じのものになりました。

- ペンギンとヒヨコは、毎ターン縦横斜めランダムに１マスずつ移動する。
- プレイヤーとエネミーは、毎ターン樽を好きな場所に1つ落とすことができる。
- ペンギンとヒヨコは、樽のある位置、フィールド外には移動ができない。
- ペンギンが移動できなくなったらプレイヤーの勝ち。
- ヒヨコが移動できなくなったらエネミーの勝ち。

![vue-boardgame.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/74964/290b8e0f-010e-3c8b-185d-c58e8aac8227.gif)

では実際の作業の流れをかいつまんで説明していきます。

## 画面の固定
まずスクロールを封じます。

```css:layouts/default.vue
<style>
    html, body {margin: 0; height: 100%; overflow: hidden}
```

背景画像も入れておきます。

```css:pages/index.vue
#container {
    background-image: url('~assets/background.png');
    background-size: cover;
```

## stateに合わせた画面の描画
こんな感じのstateを定義して、それに合わせて画面を描画します。

 - board: ２次元配列に[0-1]を入れて、樽の有無を表してます。
 - player{x, y}: ヒヨコのx, y位置。
 - enemy{x, y}: ペンギンのx, y位置。
 - loser: 敗者は誰か。試合中か否かのゲームステートとしても。

この辺りは特筆することもないので省略。

## stateの操作
押した位置のboardを1にしたり、ランダムにキャラクターを動かしたり。
この辺りもゴリゴリ書くだけなので省略。

## stateの変化に合わせたアニメーションの作成
### 準備
ここからが真骨頂ですね！
まずアニメーションライブラリのanimate.cssを入れておきます。

```bash
yarn add animate.css
```

```nuxt.config.js
module.exports = {
  css: [
    'animate.css'
  ],
```

### 樽の落下アニメーション

transitionでenter-active-classをanimated bounceInDownに設定します。
これだけで、boardが1になるだけで落下アニメーションしてくれます！

実質１行の変更です...!

```vue
<template lang="pug">
  transition(enter-active-class="animated bounceInDown")
    img(src="~/assets/barrel.png")
</template>
```

### キャラクターの移動アニメーション
transition-groupなどを使ってうまくできないかなと思っていたのですがなかなかうまくいかなかったので、下記ページのやり方を参考にさせて頂きました。

- [Vueで親コンポーネントを跨いだTransitionを実現する](https://qiita.com/inamori/items/4877aabeec69f88607da)

```components/atoms/SmoothlyMovable.vue
<template lang="pug">
  transition(
    v-on:before-enter="beforeEnter"
    v-on:after-enter="afterEnter"
    v-on:before-leave="beforeLeave"
  )
    slot
</template>

<script>
  let previousPosition = {}

  export default {
    props: {
      uuid: { type:String, default: null },
      duration: { type:Number, default: 300 }
    },
    data: () => {
      return {
        prev: null
      }
    },
    methods: {
      beforeLeave (el) {
        previousPosition[this.uuid] = el.getBoundingClientRect()
      },
      beforeEnter (el) {
        el.hidden = true
      },
      afterEnter (el) {
        el.hidden = false

        if (!previousPosition[this.uuid]) return
        this.move(previousPosition[this.uuid], el.getBoundingClientRect())
      },
      move (previous, current) {
        this.$el.animate([
          { transform: `translate(${previous.x - current.x}px, ${previous.y - current.y}px)` },
          { transform: 'translate(0, 0)' }
        ], { duration: this.duration })
      }
    }
  }
</script>
```

これで移動時にアニメーションさせたい要素をかこうと、スムーズに移動してくれるようになります！

```components/atoms/Player.vue
<template lang="pug">
  smoothly-movable(uuid="player")
    img(src="~/assets/player.gif")
</template>
```
※uuidがuuidになってないのは気にしないでください

これだけでなにも考えずにstateを変更するだけでアニメーションをしてくれるようになりました！

![fallBarrel.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/74964/424154f5-4547-d3c6-bc49-d6443e611ee0.gif)

## 音の再生
まずwebpackがサウンドファイルをimportできるようにするため、nuxt.config.jsにwebpackのloaderの追加設定を書きます。

```nuxt.config.js
    extend (config, { isDev, isClient }) {
      config.module.rules.push({
        test: /\.(ogg|mp3|wav|mpe?g)$/i,
        use: 'file-loader',
        exclude: /(node_modules)/
      });
```

すると、こんな感じでassetsにあるサウンドファイルを再生することができるようになります！

```javascript
import clickSound from '~/assets/sound/click.wav';

const audio = new Audio(clickSound)
audio.play()
```

# というわけで完成

というわけで、だいたい作業6時間ほどで完成しました。
素材探してる時間とか、Audioどうやって鳴らすの？みたいな調査の時間もだいぶ取られてるので、実際のコーディング時間はそこまで長くないと思います。

## Demo
試しに1つ作りきるが目標なので、ゲーム的なクオリティには期待しないでください。
※音がなります

- [DEMO](https://dorarep.github.io/vue-boardgame/)
- [GitHub](https://github.com/dorarep/vue-boardgame)

## 感想
というわけで、「思ったより簡単にできた」のが正直な感想でした！

メモリ管理どうなってるの？とか、規模が大きくなってくるとまた別の問題も出てくるかと思いますので、一概にVueがボードゲーム作りに向いているとはまだいえないかもしれません。

とはいえ新しいことを試すときは「変にハードル上げず作りきってみる」が第一だと思ってるので、一歩ずつ色々試していきたいと思います！