
Vue.jsを用いたWebゲームの作り方 – coconeのフロントエンド業務
-
2020年9月10日
はじめまして、ウェブ開発室で室長とフロントエンド業務を担当している髙山です。
気がつけばもう9月ですね。いやぁ早い。学生の皆さんは夏休みも終え、授業も始まっているのでしょうか?夏休みと言えば、宿題ですよね。夏休み始まったら直ぐに終わらせようと思って、終わらせたことは一度もありません。どうやら、私は夏休みの宿題は最後まで溜め込んでしまうタイプの様です。
今回の記事に関しても、去年辺りから「書きます。書きます。」言いながら、時は流れてしまい、すっかりこんな時期になってしまい、やっとこさ重い腰を立ち上げ書いている始末です。
と言う訳で、そんなこんなで色々ありましたが、今回は、ウェブ開発室が行っている業務と、Vue.jsを用いたWebゲームの作り方をご紹介できればと思っております。
cocone株式会社のプロダクト
まず、cocone株式会社ですが、1600万人以上(2020年9月現在)のお客様に愛されている「ポケコロ」を代表とし、様々なCCP(Character Coordinating Play)サービスを開発、運営しております。
これらのプロダクトの多くは「iPhone、Androidアプリ」などで提供されていることもあって、開発に用いられている技術(言語)は、ほとんどがC++やC#などとなっております。
では、そんな中「ウェブ開発室のフロントエンドは何をやっているの? 」と気になるところではありませんか?
ポケコロ
上記でご紹介されて頂いた、「ポケコロ」ですが、主に着せ替えや飾り付けを行って楽しんで頂くサービスなのですが、これらを弊社ではCCP(Character Coordinating Play)というジャンルを確立し、「ポケコロ」以外にも様々なプロダクトが存在します。
これらのプロダクトには、着せ替えや飾り付けの為のデジタルアイテムが存在するのですが、通常は購入して入手して頂くのですが、定期的に行われている「イベント」に参加する事によって手に入れることも可能となっております。
ポケコロ内イベント
ポケコロ内でも様々なイベントが行われております。
話は少し昔の話となりますが、(と言うわけで、ここから先、冒頭のくだりでは無いのですが、3年以上前の話となってしまって今更感漂うかも知れませんが、Webゲーム開発の何か参考になればと思います。)私が入社した3年以上前の事となりますが、当時はポケコロ内のイベントはクライアントのネイティブ言語(C++)などで作られており、開発、更新などコストの掛かる作業ではありました。
もっとコストのかからない形で、定期的に新しい仕組みだったりイベント自体を提供できる様に「Webで出来ないか?」と言うのが入社する際の私のミッションでもありました。
入社して最初の1ヶ月は「Webゲーム開発基盤」として、cocone assetsと言われる npm private packageを複数個(確か10個くらい)作成し(現在は20個以上存在します。)、今回だけでなく、次回のWebゲーム作成も容易に行える様に準備しました。
合計3ヶ月程で、0からWebゲーム作成の開発基盤と、新規Webゲームイベントを作成致しました。
それが、以下の「農園ゲーム」となります。
Webゲーム動画
上記の「農園ゲーム」の内容等の詳細は割愛させて頂き、簡単に説明させて頂くと、
種を植える → 花が咲く → 収穫・加工 → レベルが上がったりして新しい花が咲く
ある程度したらNPCキャラに納品する。→ 報酬もらえたりミッション達成する
などと言った、サイクルを行い、最終的にポケコロ内のアイテムを手に入れる遊びとなっております。
と言う訳で、上記の農園Webゲームの動画を見て頂けましたでしょうか?こちら、クライアントのネイティブ言語(C++)などで作られている様に見えませんか?(見えない方はお目が高い)なんと、こちら全てHTMLで作成されているのです。
「あぁ。よくあるCanvasですね。。」
と思われた方、残念。全く、Canvas要素を使わず純粋にHPを作成する様に、全てDOM(Document Object Model)で構築されているのです。
分かりやすく画像を用いて説明していきますと、こちらは、
<div class="flower"> <p class="pic"><img src="~" ....></p> </div>
で、構築されていたり。
こちらも、
<div class="lv"> <image-number :num="user.lv"></image-number> <div class="box exp"> <div class="gauge"> <p><span :style="{ width: `${ getExpWidth()}%` }"></span></p> </div> <p class="text gardenName">{{ user.rankName }}</p> </div> </div>
とかで、で、構築されていたり。
こちらなども、
<div class="text getExp"> <p class="icon exp reward"></p> <p>+999</p> </div>
<ul> <li v-for="flower in flowerList"> <div class="wrap"> <p class="image flower"><img src ..></p> <p class="name">{{ flower.name }}</p> <ul class="starList"> <li v-for="rare in flower.rare"></li> </ul> </div> </li> </ul>
とか言った形で構築されています。
上記の様に画面全体のUIパーツなど全て、HTML(DOM(Document Object Model))で構築されています。
DHTML
と言う訳で、もっと分かりやすく表現するのであれば、「すんごいリッチな表現をしたHP」とイメージして貰えば分かりやすいかと思います。
こちらの技術は、特に真新しい技術(今回の記事のWebゲーム製作当時は2017年辺り)でもなく、昔から技術で「DHTML dynamic HTML(死語)」と言われております。有名な所で、2010年5月22日 Googleがパックマン 30周年として、発表されたこちらの画面は見覚えないでしょうか?
Google Pacman
https://macek.github.io/google_pacman/
そう、こちら同様にHTML(DOM)で作成されていまして、当時は大変話題になりました(年齢がバレそう)。所謂DHTMLの技術を用いて作成された事となります。Webインスペクタ等で確認すると分かるのですが、パックマンが食べるドットさえもdivで表現されているのが確認できます。
より深くDHTMLの技術について知りたい方は、かなり古い書籍となりますが、こちらのオライリー社から出版されている「JavaScriptグラフィックス ――ゲーム・スマートフォン・ウェブで使う最新テクニック」などの書籍をお勧めいたします。
なぜDOMでつくるのか
と、ここまでお読みになられた方でこの様に疑問を持たれた方も少なくないかと思います。
「実装が大変そうなのに、何でわざわざDOMで作成するの?」と。
そう。現在ではゲーム開発用のJavaScriptライブラリも沢山存在する中で、何故DOMで構築していくのか。これらの多くのゲーム開発用のJavaScriptライブラリのレンダリングは、Canvasとなっております。端的に言いますとCanvasだと「動かない」からです。
簡単に言ってしまいましたが、Canvasでレンダリングしてしまうと、パフォーマンスが出なく人間が快適と感じるFPS(frames per second)の値が出ないからです。
Android
上記では、「人間が快適と感じるFPS(frames per second)の値が出ない」と説明させて頂きましたが、全部が全部そう言った訳ではありません。
PCであれば、充分なスペックが備わっていれば全然快適に動きますし、iPhoneであればApple社がOSと端末を一緒に販売されていますので機種は特定でき、多くの機種を気にする必要ないのですが、問題はAndroidとなります。AndroidはGoogleがOSを提供し、各メーカーが端末に乗せて販売されています。
各メーカーが販売する機種によっては、スペックが低かったり、特有の仕様によりバグが生じたり etc. と言った所謂機種依存の状態が発生します。
こう言った低スペックな端末でも最低限のパフォーマンスが出る様にと考えた結果となります。
2020年9月現在でも、国内におけるAndroidシェア率はiPhoneを上まわっております。
そう言ったことにより、高スペックの端末をベースにするのではなく、低スペック端末に開発基準を置く事によって多くの機種に対応することが可能となって来ます。
Vue.jsの採用
と言った訳で、DHTMLの導入についての流れを説明させて頂きました。
続いて、Vue.jsの採用した理由なのですが、まず私が入社した時にすでにWebゲーム開発は他のメンバーによって進められていたのですが、Canvasでの実装と言うこともあって見事に上記の問題にぶち当たっていて、色々考慮したり何度かトライするもののリリースまで至らなかった状態でした。
幸いにも(と言うか、その為に入社を誘われたのですがw)この様なWebゲーム開発の経験、知識を持っていましたので、直ぐにDHTMLでの開発に切り替えました。
以前の職場では、AngularJS(1系)を用いて作成した経験があったので、そのままAngularを採用したい所ではあったのですが、Angularは2から完全にフルスペックフレームワーク化されて来た事もあり、スマートフォン且つアプリ内Webビューには大きすぎるフレームワークとなっていました。
Webブラウザでもなく、スマートフォンのアプリ内Webビューとなると仕様が異なったりパフォーマンスが出ない事もあり、通常のWebブラウザ実装以上に軽量化を考慮していかないと快適に動作しません。
と言う事で、React.jsも検討致しましたが、開発期間も多くはなく、また状態変化の多いゲーム開発においてはMVVMフレームワークの方が手っ取り早く安全に開発出来る事もあり、GoogleにおいてAngularJSを使用した開発に携わったエヴァン・ヨーさんによって開発された、Vue.jsを採用しました。
Vue.jsは、
「Angularの本当に好きだった部分を抽出して、余分な概念なしに本当に軽いものを作ることができたらどうだろうか?」
と言う概念で開発されている事もあって、AngularJSで良かった所を継承し、必要ない所やよくないところが省かれた軽量MVVMフレームワークの中において良いフレームワークだと思います。
2017年の当時は、国内でもようやく浸透して様子ではありましたが、今ほど日本語の記事やドキュメントなどが多くなく海外の記事をよく読んだ覚えがあります。
また、上記の低スペックAndroid端末のWebビュー(純正などのWebブラウザではない)で動かした経験も無かったので「本当大丈夫か」と思って導入した覚えがあります。
案の定、特定のOSバージョンで「画面が真っ白になる」と言った現象が発生したのですが、確かVue.jsのマイナーアップデートで解消された(確か)記憶があります。
アーキテクチャ
と言う事で、なんと驚く事に、いよいよメインのお話へと入っていきます。
今までのお話は前振りとなっております。(笑)
では、早速アーキテクチャに関して見て行きたいと思います。
大まかな構成は以下の通りとなっております。
- components - config - model - service - class
- components: Vue.jsでバインドされるView & ViewModelレイヤー
- config: Enumやenvの設定が格納されるレイヤー
- model: ビジネスロジックを担うレイヤー
- service: RPC通信を担うレイヤー
- class: OOPにおけるClass
と言った形となります。
MVVMアーキテクチャと言いながら、ViewModelレイヤーは単独で分離して持たない形を取りました。最後まで悩んだところはあったのですが、時間との兼ね合いもありましたので(少し言い訳)。。過剰実装なのか否かはいつも悩まされる事柄ですよね。
また、classと書かれたところですが、実は、当時から「Flux、Redux、Vuex」などの流れの影響もあって、全然storeでは無いのですが、「store」と命名してしまったところであり、後にメンバーにも混乱させる結果となってしまいこの場をお借りしてお詫びしたいです。(笑)
ModelとViewレイヤーの分離を行なっているものの、ViewModelレイヤーが無い事により結果modelとView依存が残ってしまっている形になったのが心残りではありますが、ViewModelレイヤーなどの作成し、1を100にするのは、現メンバーに期待しつつ(人任せ)。
上記の図の様に本来、ViewModelレイヤーを設けるべきであったが、Viewレイヤー(component)内に含めた形。
データフロー
1ページに対して、Modelは最低1つ存在する形となります。
HTMLは、必要なComponentを呼び出し、そこからModelの生成が行われます。
Modalに必要なデータはClassから生成されます。RPC通信が必要な場合は、Modelから呼び出しClassを生成します。
図で表すと以下の通りとなります。
ViewModelレイヤー単独で存在しない分、通常のMVVMアークテクチャより比較的シンプルになっております。
その他全体アーキテクチャ
アプリケーション全体のアーキテクチャは以下の通りとなります。
Vue.jsのassetsなどは、AWSのS3に格納されています。ポケコロのiOS、Androidの何れかのWebViewからアクセスがあると、API Gatewayを介しLambdaの関数を実行しております。何を行なっているかと言いますと、各端末の正確なディスプレイサイズを取得し、API Gatewayを介しLambdaの関数へと渡ります。
その際、Lambdaの関数は、S3に格納されたHTMLを取得して来てViewportの値を動的に変更しています。
これは、所謂Webっぽさを無くす為、固定サイズで制作し端末に合わせてサイズをフィットさせるために行なっている仕組みとなります。
これらもウェブ開発室が構築を行なったりもします。
抽象Class
さて、アプリケーション及び、全体のアーキテクチャは概ね把握出来たかと思いますので、実装面に関して説明していきます。先程の農園ゲームを例に、以下の箇所の実装についてご説明していきます。
こちら、「花壇」となるのですが、FlowerBedクラスとして抽象化されています。
その中に、「マス」であるSquareクラスが存在します。
FlowerBedクラスは、Squareクラスのインスタンスを配列で持っております。
FlowerBed: { squareList: Square[] }
Squareクラスは、以下の様なプロパティを持っています。
Square { id: number, x: number, y: number, flower: Flower, rare: number, // 花壇のレア度 exp: number, // 肥料の経験値 nextExp: number, // 次のレア度に上がる肥料の経験値 isOpen: boolean // 解放されて要るかどうか }
Modelを介してこれらのクラスの状態は変更されていきます。
これらをVue.jsを用いて、HTMLにバインディングさせ表示の切り替え等を行なっています。
HTMLは以下の様な形となります。(一部省略しております。)
<ul class="flowerSquareList"> <li v-for="(square, index) in flowerBed.squareList"> <div class="sow" v-if="square.isSowSeed"> <p class="pic sowSeed" v-sowSeed="square"></p> </div> <div class="flower" v-if="square.flower"> <p class="pic" v-if="square.flower.isBloom"><img :src="square.flower.images.bottom.m" width="100"></p> </div> </li> </ul>
オブジェクト指向プログラミングの説明で良くある「Animal」だの「Cat」だの「Dog」だの、動物を用いて抽象クラスの実装する説明などがありますが、何も捻りは無くそのまま抽象クラスの扱いを基本に、「カプセル化」「継承」「ポリモーフィズム」など、オブジェクト指向プログミングに準じて作成されております。
アニメーション
お花を収穫したり、タネを植えたり、時間経過と共にお花が成長したりする際にアニメーションが発生します。これらのアニメーションの表現なのですが、基本CSS3とJavaScriptで表現しております。
一つのお花が収穫される所に注目しても見てみましょう。
お花を引っこ抜いて、スポッと抜けた後、フッターにある「加工メニュー」の方へと収穫されて行きます。
こちらのアニメーションは3段階となっております。
- 縦長く変形
- 少し上に移動
- フッターの目的要素へ移動
先程のflowerクラスのdiv要素に対してCSSアニメーションなどを適応して行きます。
<div class="flower" v-if="square.flower"> <p class="pic" v-if="square.flower.isBloom"><img :src="square.flower.images.bottom.m" width="100"></p> </div>
「1.縦長く変形」は、CSSのtransform scaleなどで変形、「2.少し上に移動」は、CSSのtransform translateで移動。
1と2までが、CSSアニメーションとなり、2が終わった時点でanimationend イベントが発火するので、それをトリガーに移動する目的の要素と現在のお花のpositionのx, yの値を取得します。
各値が取得出来たら、Tweenを用いて移動させます。
let tween = Tween.fromData({ start: { top: 開始時のy値, left: 開始のx値 }, end: { top: 終了時のy値, left: 終了のx値 }, option: { duration: 200, easing: 'easeInOutCubic', step: (val: any) => { elem.style.top = val.top; elem.style.left = val.left; }, complete: () => { // 終了時の処理 .... } } });
この様な形で、CSS3とJavaScript(Vue.js)を組み合わせてアニメーションを作成していきます。
タッチレイヤー
スマートフォンの操作は基本、タッチ操作となります。
タッチやスライドによって、お花を収穫したり、タネを植えたり出来るのですが、上記のアニメーション時は各DOM要素が動いていて、その状態ではタッチやスライドなどのきちんとしたイベント取得が行えません。
よって、「アニメーションするレイヤー」と「操作を受け取るレイヤー」と分けていたりします。
そうすることによって、タッチやスライドによる操作もスムーズに行える様に工夫しております。
Atomic Design Base CSSアーキテクチャ
この様に、CSSのクラスの付け替えによって、表示が変更されたりアニメーションが発生したりして行くのですが、こう言ったインタラクティブ且つ拡張性、柔軟性を持たせ流必要がある為、CSSアーキテクチャには、Atomic Designを踏襲した「APBCSS(Atomic Design Parts CSS)」のCSSアーキテクチャを採用しております。
APBCSSの 特徴と致しまして、多くのCSSアーキテクチャでも用いられている、Layoutといった概念はなく、 構成の考え方もその逆で、細部化出来ないUIパーツから、定義していくアーキテクチャとなっております。
LayoutのStyleより先に「細部化出来ないUIパーツ」からStyle定義することによって、より細かい状態変化やアニメーションの実装を可能としております。
こう言った形で最初の「農園ゲーム」は完成され、その後もそれをベースに数本作成されて行くのですが、大体1ヶ月ペースで新規Webゲームを作成する事が出来、コスト削減にも繋がって行きました。
その他のWebサービス
と、Vue.jsを用いたWebゲームの作成の仕方をご紹介させて頂いたのですが、勿論お仕事はこれだけではなく、一般的に言われるWebサービスなどの開発を行っており、昨年の12月には、「わたしの個性を商品にする」をコンセプトに、世界中のクリエイターがデザインしたデジタルファッションアイテムを集め、ココネが提供するサービス(アプリ)を通じて、お客様にお届けする取り組みとして、coconetsをリリースしました。
coconets
こちらに関しては、マイクロアーキテクチャを採用しており、JSフレームワークにはNuxt.jsが使われており、SPAアプリケーションとして構築されております。
その他にも、作業効率化、自動化の一環としてツールの開発や、ジェネレータの開発、またウェブ開発室メンバー全員、npmのプライベートアカウントを保持しており、cocone assetsと言われるライブラリを開発しnpmプライベートレポジトリに登録し活用しています。
枯れた技術を大事にしつつ
Webフロントエンド界隈は、技術やトレンドの移り変わりが早い界隈でもあります。
そういった中で、ただトレンドを追いかけるのではなく、今後も不変することのないであろうCS、アルゴリズムの知識などを元とし、長年使われてきた技術やアーキテクチャなどの所謂枯れた技術を大事にしつつ、パラダイムシフトが起きた際でもどう活用できるのかを考察出来る能力が今後も重要だと思っております。直近では、「Webassembly」などの技術の検証を行いつつ、活用できる箇所などの検討も行なっていたりします。
という訳で、今回は、Vue.jsを用いたWebゲームの作り方を基軸としつつ、ウェブ開発室の業務などについて書かせていただきました。
少しでもcoconeのフロントエンド業務に興味持って頂けたら幸いです。
また、「もっとお話を聞きたい」や「是非、働きたい」と言った気持ちになられた方は、気兼ねなくお問い合わせして頂ければ、カジュアル面談からでも対応可能となっております。
https://www.cocone.co.jp/recruit.html
ではでは。またの日にでも。