ハイパーカジュアルゲーム「Hoppy Japan」をUnityで1ヶ月で作った話

はじめに

こんにちは!Unityエンジニアの佐藤です。
この記事はカヤックUnityアドベントカレンダー2018の4日目の記事になります。
今年の10月にリリースしたKAYACの新作ハイパーカジュアルゲーム、「Hoppy Japan」の開発についてお話します。
f:id:takashicompany:20181203102625p:plain

AppStore
Google Play

Hoppy Japanとは

「Hoppy Japan」は、弊社のソーシャルゲーム事業部の「天下一企画会」という制度から生まれたゲームです。

f:id:takashicompany:20181203102932p:plain

Hoppy Japanは手軽にスピード感と高揚感を体験できるワンタップアクションゲームです。


Hoppy Japan

開発メンバーは

  • プロデューサー
  • ディクレター
  • プランナー / Unityエンジニア (私)
  • 3Dアーティスト / デザイナー
  • サウンドアーティスト

の5人です。

製品版の制作は1ヶ月で行いました。

技術的なコンセプト

  • 全世界での配信を想定しているので、低スペックな端末でも動作するように
  • 短期間で確実に開発を進めるために、シンプルな構造や設計を意識する
  • アーティストがUnityで作業・確認をしやすいように

ということを念頭に置きました。

背景オブジェクトの再利用

「Hoppy Japan」は町並みを楽しむゲームです。
キャラが進むにつれて、「背景オブジェクトを配置する・後方で映らなくなった背景オブジェクトを消す」という実装をしています。
その際に、Instantiate関数やDestroy関数を用いるとCPU処理に負荷がかかりカクつく原因になり、ゲームの体験を損ねてしまいます。

f:id:takashicompany:20181203102654p:plain

上の図のようにカメラから描画されなくなった街の3DモデルをDestoryせず、前方へ再配置し直すという仕組みにしています。

f:id:takashicompany:20181203102843g:plain

上の図のようにキャラが進むにあわせて、非表示化と配置(再利用)を逐次実行しています。

シーンを分割 / 一括ロード

Hoppy Japanではタイトル画面・ゲームプレイ画面・リザルト画面・スキン一覧画面の4つのシーンでゲームを構成しています。

1つのシーンファイルでも作れそうなコンパクトなゲームですが、シーンを分割することで

  • 機能の切り分けがしやすい
  • 機能の確認を単独でできる
  • 複数人で作業した時に変更がぶつからない

といったことを実現する狙いがあります。

加えて、アプリ起動時に全てシーンをロードし、シームレスに遊べるように努めました。 f:id:takashicompany:20181203102829p:plain

これは「Hoppy Japan」の総メモリ使用量が約60MBだったため採用できた...という側面もあります。

作ったものを確認して調整するサイクルを早くするために、各シーンは単体でエディタ再生しても動作するようにしました。

独自のタイムスケール

このゲームには「スーパースロー」という機能が入っています。
長押しをするとキャラの移動速度がスローになって着地点が狙いやすくなる...というものです。
UnityにはTime.timeScaleというゲーム全体のスピードを調整する機能がありますが、今回は使いませんでした。
「キャラの移動やモーションだけスローにして、UIは通常のスピードで動かす」といったように要素毎に調整できる方が柔軟に対応できると考えたためです。
「Hoppy Japan」ではTime.timeScaleは使用せず、独自のtimeScaleを定義し、アニメーションインスタンス(DOTween)や、Animator、パーティクルなどスーパースローを適用したい要素に対して同期させる処理を書きました。

void Update()
{
    // GameManager.timeScaleという変数を定義して、同期させたいアニメーションに適用する
    
    // Animatorの場合
    _animator.speed = _gameManager.timeScale;

    // DOTweenのSequenceインスタンスの場合
    _doTweenSequence.timeScale = _gameManager.timeScale;

}

void LateUpdate()
{
    // ParticleSystemのみ、LateUpdateで同期させる(Updateだと動きが荒くなったので)
    _particleSystem.Simulate(
        Time.deltaTime * _gameManager.timeScale,
        true,
        false
    );
}

アーティストとの連携

アーティストが自ら作ったアートをゲームに組み込んで確認できるようにいくつか工夫をしました。

まず、「Header属性でアートの反映方法を書いておく」というものです。
Header属性はシリアライズ変数に定義することにより、UnityのInspectorビューに任意のテキストを表示することができます。
主にInspectrビューでの変数の説明に用いられます。

[Header("アートをゲームに組み込む手順")]
[Header("1. 作ったBlockにGameBlockコンポーネントを設定")]
[Header("2. StageCheckSceneを開く")]
[Header("3. GameStageコンポーネントのプレハブを修正する")]
[Header("- フィールドを修正する場合はField階層以下と同じように設定する")]
[Header("- ステップを追加する場合はStepPoolにプレハブを追加する")]
[Header("- ブロックを追加する場合はBlockPoolsにプレハブを追加")]
[Header("4. Stageのプレハブを保存したら「StageBundleData」を検索")]
[Header("5. StageBundleDataにプレハブを設定したらSaveProjectとシーン保存")]
[Header("6. GameSceneを再生するとゲームに登場する")]

f:id:takashicompany:20181203102629p:plain

やや力技な感じですが、プログラマは別途wiki等を用意せずに説明が書けるので手軽かと思います。

また、開発初期はシーン内のオブジェクトにInspectorで作ったアートを設定してもらう という方式でしたが シーンファイルをお互いに触る場合コンフリクトが生じてしまうので ScriptableObjectを用いてアーティストが簡単にアートを設定できるようにしました。

f:id:takashicompany:20181203102639p:plain

パフォーマンス・チューニング

一部の端末では、GPUの負荷が高くFPSの低下を招いていたため、 特定の条件を満たした端末(FPSが一定数を連続で下回り続けた場合など)では、解像度を調整して描画負荷を軽減するという手法をとりました。

Screen.SetResolution(Screen.width / 2, Screen.height / 2, true);

終わりに

以上のようにして「Hoppy Japan」は作られました。
ハイパーカジュアルゲームはUnityと相性が良く、既存の機能を適切に使うだけでも良いゲームが作れます。
ぜひ、チャレンジしてみてください!

明日は宮野有史の「BluetoothでUnityと接続するコントローラをESP32マイコンで作る」になります!

絶対に真似してはいけない。闇のRedash入門

Lobi事業部データエンジニア(自称)の池田です。
この記事はKAYAC Advent Calendar 2018の4日目です。

Redashとは

Redashは様々な種類のデータソースにアクセスできる、OSSの素晴らしいダッシュボードツールです。
以前、本ブログでは、健康的な使用事例を紹介しました。

techblog.kayac.com techblog.kayac.com

今現在のKayacでも、事業の様々な数値や状態を可視化、お問い合わせからの調査等の業務支援ツールとして広く活用されています 本日は、Redashの非健康的な使用事例を紹介します。

すべての始まり【Pythonデータソース】

Lobi - Chat & Game Communityは今年で開発・運用が8年目になるスマホゲーマーSNSサービスです。
Lobiを開発・運用するLobi事業部では、Redashを活用する上で2つの問題を抱えていました。

  1. ShardingされたDBが参照しづらい問題
  2. Detail JSON Parse辛い問題

1つ目の問題は、Sharding*1されたDBをRedashからアクセスする場合、単純なSQL文では水平分割の台数分だけRedashのクエリが必要になります。 2つ目の問題は、8年という長い期間で生まれた、detail_json という名前の、どうみてもJSONな文字列が入ったTEXTカラムが存在するテーブルがあります。単純なSQL文では特定のキーの情報だけ参照するのはとても大変です。

この問題を解決するソリューションとして、Pythonデータソース
このようなPythonデータソースを使うRedashのクエリが誕生する。

import json 
import re 

# query_params validation
validator = re.compile('[0-9]+(,[0-9]+)*')
ids = '{{{ids}}}'
if not validator.match(ids):
    print 'plz Comma-Separated Values format'
    return 

#shard_map access 
shard_map_query = '''
select user_id, shard
from shard_map
where user_id in (%s)
''' % ids
rows = execute_query('mysql main',shard_map_query)['rows']
ids_by_shard = {}
for row in rows:
    shard = row['shard']
    if not shard in ids_by_shard:
        ids_by_shard[shard] = []
    ids_by_shard[shard].append(row['user_id'])
del rows

#query detail json
data_source_by_shard = {
    'shard01': 'mysql shard1',
    'shard02': 'mysql shard2',
}
for current_shard, current_ids in ids_by_shard.items():
    query = '''
    select id, detail_json
    from user_opt 
    where id in (%s)
    ''' % (','.join(map(lambda x:str(x), current_ids)))
    data_source = data_source_by_shard[current_shard]
    rows = execute_query(data_source,query)['rows']
    for row in rows:
        data1 = ''
        data2 = ''
        if row['detail_json'] != '':
            detail = json.loads(row['detail_json'])
            if 'data1' in detail:
                data1 = detail['data1']
            if 'data2' in detail:
                data2 = detail['data2']
        add_result_row(result, {
            'id': row['id'],
            'data1': data1,
            'data2': data2,
        })

add_result_column(result, 'id', '', 'integer')
add_result_column(result, 'data1', '', 'string')
add_result_column(result, 'data2', '', 'string')

このときは最強のソリューションに見えた。

参考文献

tbpgr.hatenablog.com techblog.lclco.com

気軽にエンジニア以外でも扱える【Googleスプレットシートデータソース】

とある平日。
カスタマーサポート担当から『こちらのリストにのっている方のアクセス情報を一覧できない?』 別のある平日。 営業担当から『このリストにのっているグループの発言量を一覧できない?』 そして、一ヶ月後のある平日 『この間の一覧した情報、定期的に自動更新できない?』

等々、様々な業務を行う関係各所から このリストにのっている○○ シリーズの業務支援を依頼されることがあります。 Kayacでは多くの場合、Google スプレットシート*2を使ってリストが渡されます。

そんな依頼にこちらのソリューション

なんと! エンジニア以外でも扱える素敵なデータソースとして、Googleスプレットシートを使えます。
もちろん!Google Apps Scriptも併用して更に便利になるチャンス!
これは仕事が捗ります

そして、簡易アプリケーション爆誕

ここまでは、健康的な範疇の活用方法ですが、 皆様お気づきだろうか、ここまで環境を整えたRedashは以下のように捉えられるということに。

Redash機能 要素
様々なデータソース Model 特に、柔軟に入出力可能なGoogleスプレットシートは扱いやすい
様々なVisualization View 折れ線グラフや棒グラフ、箱ひげ図、サンキーチャートなど。
Pythonデータソース ViewModel 柔軟なデータ処理を行える。module importすれば可能性は無限大

そう、環境の整ったRedashはMVVMとみなすこともできる!!?
そして、いつの間に、Redash上で動く簡易アプリケーションの爆誕することになります。
もちろん、Git管理されてない
そして、事業の中核になるアプリケーションのリポジトリ外なのでコードレビュー対象外 この簡易アプリケーションは非健康的です。保守の観点で…

現在のLobi事業部のRedash運用

保守が必要になるような割と業務で重要なものは、アプリケーションのリポジトリに

redash/query_3121.py
redash/query_3122.py
︙

のように保存してGit管理するケースが出てきています。

まとめ

Redashの登場により、Kayacではいたるところで業務改善が行われました。
PythonデータソースもGoogleスプレットシートデータソースも適切に利用すれば、とても『イノベーティブ』です。
しかし、過度の利用は保守が大変な簡易アプリケーションの生んでしまいます。
用法・用量を守って素晴らしきRedashライフを送りましょう!

そんな感じで、あらゆる業務改善・事業可視化をするエンジニアもカヤックでは募集しております!

次回予告

明日は、id:wyumikokhさんが『gitbookで幸せ計画』について書いてくれるようです。