みんなの「作ってみた」

Atomic DesignでAtomが走るゲームを作った話

2019/06/05

daitasu
daitasu
Frontend Engnieer。vue/nuxt/react native/node/ts/webrtc。最近はdenoに挑戦中。20代×ITコミュニティ(https://youthtechtokyo.peatix.com )を運営してます。

作ったもの

Atomic DesignでAtomが走りジャンプするゲームを作りました。
原子が障害物を越えて走るだけのシンプルなゲームです。

github: https://github.com/daitasu/atomsDash
ここから遊べます: https://atomsdash.firebaseapp.com/

注: 現在PCでのみ遊べます。スマホ対応はしておりません。
タッチでもジャンプはできますが、レスポンシブじゃないので急にゲームオーバーになります。

ゲーム画面

原子が走ります。スペースキーでジャンプし障害物を避けます(2段ジャンプまで可能です)。

キャラクター選択画面

好きな原子をキャラクターとして選べます。集合体が苦手な方は水素をオススメします。

構成

以下の構成で作成しました。
フロントエンド: Vue(Nuxt.js)
CSSフレームワーク: Vuetify
ホスティングサービス: Firebase Hosting

特にサーバから取得するデータもないので、Nuxtで静的ファイルをgenerateしたものをおいてるだけの構成になります。

作り始めた経緯

なぜこのゲームを作ったか

「Atomic Designをしっかり0から設計してみたい」
これが一番大きな理由です。
また、普段は業務システムの作成に従事しているため、全く別分野の物が作りたいと思い、ゲームを作ることにしました。

なぜAtomを走らせた?

この記事に惹かれ、CSSアニメーションをバンバンやってみよう!画像は極力使わないぞ!という思いを持って挑み始めました。
しかし、画像を使わないゲームはいかんせんダサく、特にメインともいえるキャラクターをどうしていいか分かりませんでした。
そんな矢先にAtomic Designの代名詞ともいえるこの画像に出会います。

...これを走らせればいいのでは?
安易ですが、Atomic Designの学習がメインなので。

設計について

Atomic Designに基づいて設計

そもそもAtomic Designって何?

UIコンポーネント設計のためのデザインフレームワーク。
UIをコンポーネント単位に区切って考えていくことで、コンポーネント単位でテストすることをできるようにしたり、再利用による実装量の減少、不具合時や手戻り時に解決すべき部分を最小に抑えることができます。

Atomic Designはこのコンポーネント設計を原子に見立てて大きく5つの枠組みに区切ったものです(上記に挙げた図です)。
Atomic Designはこの5つの機構に分けることで責務を切り分け、それぞれの役割に集中できるようにしています。

粒度 関心事
Atom 原子 デザインの統一性
Molecules 分子 行動を阻害しない統一性
Organisms 有機体 ユーザの行動を促すコンテンツ
Templates テンプレート 画面全体のレイアウト
Page ページ コンテンツやルーティングとコンポーネントをつなぐ(再利用しない)

大きなコンポーネントから小さなコンポーネントに依存関係を持たせていく事でデザインの仕様変更時はAtomsやMoleculesのみを修正、fetch周りの修正はPagesのみといったように切り分けや作業の容易さが変わってきます。

※ この辺りは他の方の記事や書籍を参考にした方が良いかと思うので、深くは割愛致します。書籍のオススメは「こちら」です(表はこの本から引用してます)。

AtomsDashにおけるAtomic Design

ではAtomsDashではどのようにしたかを見ていきます。

Atoms

Atomsでは、キャラクターや背景、障害物のデザインを作成し、上層コンポーネントからsizeやheightなどを渡すようにして設計しました。
一般的にAtomsはButtonやBadgeなどになると思うのですが、ゲームの場合どうすべきか分からなかったので、コアとなるもののデザインをディレクトリに分けて置きました。

Molecules

Moleculesにはいくつか必要なDialogと、先ほどのAtomsのキャラクターや障害物たちに意味合い(ジャンプや当たり判定)を持たせるための Character.vueObstacle.vue などを置いています。ロジックはAtomsに持たせず、極力Moleculesで賄うようにしています。
(このあたりは意見が色々あると思うのでツッコミ等頂けると幸いです)

Organisms

Organismsはキャラクター選択画面の元素記号表を置きました。(特に他に思いつかなかった...)

Templates

Templatesは各ページの全体です。Pagesでルーティングやfetchを担い、レイアウトはtemplatesに任せるイメージです。

作成過程の課題もろもろ

作成過程で色々考えた部分、肝となった部分を書いていきます。

原子モデルの設計

メインキャラクターとなる原子の設計です。

こちらには、Vueでトランジションを使う際に用いる、Enter/Leaveを利用しています。
Enter/Leaveを使うと、v-ifやv-showなどでの描画時、または消滅時にjsフックができ、カスタムイベントを作ることができます。

transition(
 appear
 v-on:before-appear="customBeforeAppearHookK"
 v-on:appear="customAppearHook"
)
  div.electron-shell(:style="`width: ${size * 2 / 3 }px; height: ${size * 2 / 3 }px;`")
    div.electron-1(v-if="selectedAtomNo > 0" :class="isOutermostK")
    div.electron-2(v-if="selectedAtomNo > 1" :class="isOutermostK")
div.neutron(:style="`width: ${size / 3}px; height: ${size / 3}px;`")

上記は電子殻部分の一例です。pugというjadeのような記法で書いています。

methods: {
 customBeforeAppearHookK: function(el) {
  Velocity(el, { rotateZ: "360deg" }, { duration: 5000, loop: true, easing: "linear" });
 }
}

Enter/Leaveでのjsフックで要素を取得することができ、Velocity.jsというアニメーションライブラリを組み合わせることで回転を実現させています。
こちらもご参照ください。 VueでのCSSアニメーションの話

当たり判定

キャラクターと障害物の衝突判定部分。色々悩んだが下のやり方が一番しっくりきました。
横軸と縦軸に対して、
衝突 = (2物体の中心点間の距離 < 2つの物体の半径の和) として計算を行いました。

X1: 2つの物体の中心点のX方向距離
Y1: 2つの物体の中心点のY方向距離
X2: 2つの物体のX方向半径の和
Y2: 2つの物体のY方向半径の和

if (x1 < x2 && y1 < y2)

ジャンプ

ジャンプは最初、bottomをsetTimeoutで徐々に増減する方式をとったのですが、等間隔で増減するとジャンプが以下のグラフのような動きになります。引用
こんなジャンプは気持ち悪い。少なくとも円弧を描きたい。
ということでsin関数を使うことにしました。
sin関数は0を始点とした円軌道を描くので、用いると、以下のようになります。
y=sin(x)

jsだとこんな感じになります。
以下を2段組み合わせて空中ジャンプを実現しています。

onJump(event) {
  // ジャンプ中は許可しない
  if (this.jumping) {
    return;
  }

  this.jumping = true;
  const radius = 200;
  let t = 0;
  const timer = setInterval(() => {
    const jumpHeight = Math.sin((Math.PI * t++) / 80) * radius;
    // position:absolute に対し、ベースとなるbottomにsin関数の高さを足したものをjump中の高さにする
    this.bottom = this.center + jumpHeight;
    // 地面以下となったら元の位置に戻す    
    if (jumpHeight < 0) {
      clearInterval(timer);
      this.bottom = this.center;
      this.jumping = false;
    }
}, 20);

storeについて

Atomic Designを用いているため、極力propsでの依存関係を意識し、グローバルな値で汚染したくありませんでした。
そのため、storeを用いて作成しているのは現在選択している原子番号のみとしています。

export const state = () => ({
  atomNo: 10
});

export const mutations = {
  SET_ATOM_NO: function(state, data) {
    state.atomNo = data;
  }
};

そのうちログイン等を取り入れる時は、ログイン情報などを持たせようかなと思っています。

今後

いつやるかとかは未定ですが、スマホ対応したいなと思ってます。PWAにしたいです。スマホで遊べたほうがいいと思うので。
あとは、Googleの某恐竜くんみたいにNightモードを入れたり、電子を打つシューティングモードを入れたりとかしたいですね。

感想

  • この規模感でAtomic Designを取り入れる必要はない
    正直この規模感ではここまで設計をこだわる必要はないかなと思います。
    あと、設計について悩んでると「設計とは?」という迷走に入り鬱になるため、分けやすい部分だけ分けて、あとは気になりだすまでは、templatesなどに書いてていいかと思います。

  • position: fixedと absoluteの使いすぎは発狂しそうになる
    ゲームの作り方がよくわからず多用しましたが、あんまり使わないほうがいいです。
    レスポンシブとかやろうと思った時が大変です。

  • 設計に関して結構こだわりを持ってやれた
    0から作ったことで試行錯誤しながら開発でき、Atomic Designってなんぞ?みたいなところは語れるようになったかと思います。

  • 普段はフォームが多い業務システムを作っているが、ゲーム作りはイベント発火のタイミングとか異なる部分での学びが多かった
    普段と全く違うシステムを作ると学びが多いのでオススメです。

  • とりあえず楽しい
    「Atomic DesignでAtomを作る」という半分ネタですが、作ってるとやりたいことが色々出てきて楽しかったです。

正直迷走した部分が多く、ツッコミ等色々ご教授頂けると嬉しいです。
よかったらぜひ遊んで下さい。

Github: https://github.com/daitasu/atomsDash
AtomsDash: https://atomsdash.firebaseapp.com/