スマホのWebブラウザで動作するブラウザゲームを、Cocos CreatorやGoogle Cloud Platform(GCP)を使って開発したお話を紹介します。

はじめに


今年(2017年)1月からスマホ版アメーバにかんたんゲーム カテゴリが追加されています。このカテゴリに入っている次の5つのブラウザゲームを、私の所属しているチームが開発しました。

ハリピットバブル

ハリピットと呼ばれるキャラクターをモチーフにしたマッチスリーパズル。
こちらから遊べます。

大好物のスイーツを求め、スイーツが実る木があるという不思議な森へお出かけ。バブルに覆われている沢山のスイーツが地面に落ちる前に、たくさん収穫しよう。

そろえて!ミルチョ

ミルチョと呼ばれるキャラクターをコレクションできるマッチスリーパズル。
※近日公開予定です。

その他、次のようなタイトルがあります。

  • とんでよ!ミルチョ
  • ブーシュカパニック
  • 突撃!千人斬りカーニバル
  • 開発体制

    開発体制はプランナー兼デザイナー1名、エンジニア2名(後に3名)です。エンジニアは主な担当をフロント/サーバサイドで分けましたが、どちらも分け隔てなく担当するマインドで動いています。

    また初めて導入する技術も多かったため、経験のあるエンジニアからアドバイスを頂いたり、インフラエンジニア、プラットフォームチーム、QAチームには都度相談させてもらいました。

    以下ではプロジェクト立ち上げにあたって、少人数、短期間で効率よく開発できるように選択した環境などをご紹介したいと思います。試行錯誤の様子が多々含まれていますがご参考になれば幸いです。

    フロント構成


    Cococs Creator

    フロント開発環境はCocos Creatorを利用しています。Cocos Creator は Cocos2d-x を開発している cocos2d-x.org が新しく開発したGUIのゲーム開発ツールで、昨年2016年3月にリリースされており、現在も活発に開発されています。

    次のような点が決め手になり、Cocos Creatorで開発しています。

    • スマホブラウザ向けにビルドできる
    • 実績のある Cocos2d-x (Cocos2d-js) で動作する
    • GUIでゲーム画面を作成できるため、生産性が高い

    開発環境の候補としては、Unityが真っ先に候補に上がりましたが、スマホブラウザ向けにビルドできないため候補から外れました。Cocos CreatorのUIや使い勝手はUnityに似ていいるため、Unityに慣れている開発者であれば、導入のハードルは低いと思います。

    5月にリリースされたバージョン1.5で次のような大きな機能追加がありました。

    • 物理エンジンとの統合
    • カメラの導入
    • TypeScriptとES6 Classの公式サポート

    Cocos Creatorを使い始めた時点では物理エンジンと統合されておらず、Cocosのオブジェクトと物理エンジンのオブジェクトとの橋渡しをプログラムする必要がありました。カメラもないため、視点を固定しておいてゲームステージの方を動かすなど、いろいろな難しさがあったのですが、1.5からはそのような苦労も不要になりそうです。

    またTypeScript と ES6 Class が公式サポートされており、特別な設定は不要で、すぐに使い始めることが出来ます。

    テキストエディタとしては公式のフォーラムでもアナウンスされていますが、 Visual Stduio Codeの使用が推奨されており、プラグインを入れると Visual Studio Code を使って、Cocos Creatorで作ったゲームのデバッグができます。

    Cocos Creatorの今後の開発方針については Trelloにまとまっています。

    Cocos Creator のビルド自動化

    Cocos Creatorで実装したソースはWebモバイルをターゲットとしてビルドしています。ビルドするとminfiyされたJavaScriptファイルと多数のJSONファイルが生成されます。当初はこれらのファイルもGitリポジトリに入れていたのですが、複数人が同時に作業するとコンフリクトが多発し開発スピードを落とす原因になっていました。

    余りにもコンフリクト対応に時間がかかりコードレビューの阻害にもなっていたため、Jenkinsに詳しいエンジニアの手を借りつつ、最終的には次のような構成にしてビルドファイルをGitリポジトリから追放しています。

    Google Compute Engine上で WindowサーバがJenkinsのスレーブサーバとして起動しており、そのサーバにはCocos Creatorがインストールされています。
    次のようなフローでデプロイします。

    • Jenkinsマスターサーバから、WindowsサーバのCocos Creatorビルドタスクを実行
    • Windowsサーバのビルド結果ファイルを、Jenkinsマスターサーバが読み取りAppEngineへデプロイ

    幸いなことに Cocos Creatorはコマンドラインからビルドできる仕組みが備っているため、このような構成が可能でした。

    また、Windowサーバは n1-standard-1 (1vCPU, 3.75GB memory) インスタンスで動かしており、 5分〜10分でビルドが終わっています。Cocos Creator 1.3では突発的に15分以上かかることもありましたが、1.4にアップデートしてからはビルド時間が安定して短縮されました。

    AppEngineで静的ファイル配布

    Cocos Creatorのビルド結果ファイルなどの静的ファイルはAppEngineで配布しています。

    AppEngineで配布されている静的ファイルはGoogleのCDNであるエッジキャッシュでキャッシュしてくれますので、AppEngineで配布さえすれば自動的にGoogleのエッジキャッシュに乗ることになります。

    またAppEngineのデプロイはコマンド一発できることや、バージョン管理とその切り戻しが簡単にできるという特徴がありましたので、少ない手間で静的ファイルを公開、管理することに向いていると考えました。

    サーバサイド構成


    プラットフォームはGoogle Cloud Platform、APIサーバのプログラミング言語はGo、Webフレームワークとしてechoを使っています。ビルドしたAPIサーバは、Google Container Engine で実行されており、データベースには Cloud Datastoreを利用しました。

    現在5つのゲームのAPIサーバが稼働していますが、すべて同じGKEクラスタに入れていることもあり、料金は安価に抑えることができています。APIサーバもAppEngineで動かすことも検討したのですが、AppEngine Runtimeに依存してしまうため今回は利用を見送りました。

    Go言語

    Go言語を使った経験はなかったのですが、シンプルな仕様・型安全性・組み込みの並行処理・ライブラリやIDEの充実など、経験のあるエンジニアからすばらしい特徴を聞いていましたので、素早く立ち上げるプロジェクトに向いていると判断して採用しました。実際に使ってみるとカジュアルゲームの仕様がシンプルだったこともあり、Go言語のシンプルさとよく合っているように感じました。
    ただ複数のゲームから利用する共通ライブラリを作るにあたって型の継承が出来ないこと、Genericsが無いことがネックになるケースがありました。このあたりはGo言語でのやり方に慣れればまた違うのかもしれません。

    StackDriver Logging

    GKEで動作するAPIサーバのログはまずStackDriver Loggingへ送信し、さらにLoggingのExport設定を使ってBigQueryとCloud Strageへバックアップしています。 一度Export設定してしまえば、自動的にそれぞれのソースへ日別にログを出力してくれるため、ログをアーカイブしたりローテートする必要がありません。

    またStackDriver Loggingの保存期間はプレミアム版でも30日ですが、BigQueryやCloud Strageは使用料もそれほどかからないため、長期間保存しておくこともできます。

    さらにログの調査/解析にBigQueryのクエリが使えるため、特定の条件でフィルタリングすることがすぐにできます。将来的には、Goプログラムから出力するログのフォーマットを、BigQueryで扱いやすいフォーマットにすればさらにログの解析が簡単になりそうです。

    CIサーバ

    CIサーバとしてJenkins2を使っています。さきほども記載しましたが、フロントのプログラムをビルドするために、Cocos CreatorをWindowsサーバで動作させる必要があるのですが、Jenkinsスレーブとして自然にCI化できています。ビルドスクリプトは、ほぼGroovyスクリプトで記載しGit管理しています。

    Cloud SQL から Cloud Datastore へ

    当初はGCPのリレーショナル・データベースサービスである Cloud SQLを使用して実装を進めていましたが、ゲームの仕様がシンプルなこともあり、思い切って開発途中に Cloud Datastore へ切り替えてみました。

    Datastoreは以前からAppEngine限定のサービスでしたが、 昨年(2016年)8月にGAリリースされてからはGKEなどAppEngine以外からも利用できるようになりました。

    次のような特徴が謳われています。

    スケーラビリティの高い NoSQL データベース
    Cloud Datastore はアプリケーション向けのスケーラビリティの高い NoSQL データベースです。 シャーディングとレプリケーションを自動的に処理し、アプリケーションの負荷にあわせて自動的にスケールする、可用性と耐久性に優れたデータベースを提供します。ACID トランザクション、SQL ライクなクエリ、インデックスなどの多くの機能を備えています。

    (Datastoreドキュメント より)

    実際に使ってみると上記の特徴のおかげで、一般的なリレーショナルDBサーバの場合よりも管理コスト・開発コストが少なくなりました。具体的には次のようなコストが削減されています。

    • DBインフラ管理コストが不要
    • 冗長化の仕組みが不要
    • NoSQLのためスキーマ変更の負担が少ない

    レプリカサーバの管理などが不要になるためメンテナンスコストが圧倒的に減りました。またDatastoreはNoSQLですので、スキーマを変更する場合はGo言語のstructを変更するのみでよく、リレーショナル・データベースにあるようなマイグレーション作業は不要です。

    Cloud Datastoreのトランザクションでつまずいた話

    ただDatastoreのトランザクションには次のような特徴があり注意が必要でした。
    (下記は Datastoreドキュメントから引用しています。)

    (1) 楽観的同時実行制御である

    1 つまたは複数の共通エンティティ グループに対し、複数のトランザクションがエンティティを同時に変更しようとした場合は、変更を commit した最初のトランザクションだけが成功し、他のすべてのトランザクションは commit に失敗することになります。

    複数のアップデート処理が同時に実行された場合は、片方は実行を待つ(暗黙的に排他ロックがかかる)と想定してたのですが、実際には同時にアップデート処理が実行され、先にcommitした方が採用されます。

    (2) トランザクション成功時にもエラーが返されることがある

    トランザクションを commit し、正常に適用された場合でも、エラーが返されることがあります。

    トランザクションを何度繰り返しても同一の結果が得られるように、可能な限り Cloud Datastore トランザクションのべき等性を確保する必要があります。

    スコアの更新など、トランザクションエラーが許容されづらい箇所には、トランザクションのリトライ処理を入れました。またリトライされても同じ結果になるように、プログラム側が気をつける必要があります。

    (3) トランザクションとスナップショット

    トランザクション内の各クエリおよび検索は、トランザクション開始時の Cloud Datastore に対する単一の一貫したスナップショットを参照することが保証されています。

    ほとんどのデータベースと異なり、Cloud Datastore トランザクション内の query と get オペレーションは、トランザクション内の以前の書き込み結果を参照しません。

    トランザクション内でエンティティを追加しても、同じトランザクション内からはそのエンティティが見えません。

    開発当初は上記の特性をよく理解せずに開発を進めてしまい、負荷テストの段階で並列・大量に更新処理かけた際にやっとおかしいことに気づき慌てて修正しました・・・

    おわりに


    スマホ向けブラウザゲームを開発したお話を紹介させていただきました。Cocos Creator、GCPなど便利なツールやサービスを使うことで、開発や運用のコストを下げることが出来たと感じています。

    まだまだ改善の余地はありそうですが、こちらの記事が何かのお役に立てば幸いです。

    大阪在住です。 サーバサイドエンジニア・Androidエンジニアとしてペコリなど複数のアプリを担当しました。 現在はカジュアルゲームを開発しています。