Help us understand the problem. What is going on with this article?

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

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

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

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

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

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

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

Vueでゲーム作ってみた

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

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

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

vue-boardgame.mov.gif

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

画面の固定

まずスクロールを封じます。

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

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

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

stateに合わせた画面の描画

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

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

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

stateの操作

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

stateの変化に合わせたアニメーションの作成

準備

ここからが真骨頂ですね!
まずアニメーションライブラリのanimate.cssを入れておきます。

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

樽の落下アニメーション

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

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

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

キャラクターの移動アニメーション

transition-groupなどを使ってうまくできないかなと思っていたのですがなかなかうまくいかなかったので、下記ページのやり方を参考にさせて頂きました。

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

音の再生

まず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にあるサウンドファイルを再生することができるようになります!

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

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

というわけで完成

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

Demo

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

感想

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

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

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away