KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

はじめまして。普段はiOSを使っているのに開発中はAndroid周りについて見ること多いsuzu-jです。

最近はHuawei製の端末等、国内でも搭載しているSoCがSnapdragonではない端末が増えてきました。 そんな端末でも、読み込まれているテクスチャや、描画順の確認等、グラフィックス周りのデバッグをするために、今回は Mali Graphics Debugger の使い方を紹介します。

対象はCPUがARMでGPUがMaliのAndroidとなります。 x86系統のCPUだとrootedな端末が必要な上、手順が違うため今回の記事では対象外です。

説明は dtab Compact d-02HWindows 上で行いますので、Mac等その他のOSを使っている方は適宜読み替えてください。

なお、大体のアプリに繋げられてしまうSnapdragon系列と違い、こちらは残念ながら自分でビルドするアプリケーションでしか見れません。 むしろセキュリティ的に見えるのがおかしいと思うのですが。

PC側の準備

まずはダウンロード、公式サイトから自分のプラットフォームに応じてダウンロードしてインストールします。

インストール後は Edit->PreferencesPath to ADB にADBのパスを指定しておきましょう。

mgd_adb_path

端末側の準備

端末側にはDebuggerと繋げるためのアプリケーションをインストールする必要があります。

Windowsでインストール先を変えていなければ、 C:\Program Files\Arm\Mali Developer Tools\Mali Graphics Debugger v4.8.0\target\android\MGD.apk があるため adb install でインストールしましょう。

アプリケーション側の準備

アプリケーション側にはPluginを入れる必要があります。

Pluginは C:\Program Files\Arm\Mali Developer Tools\Mali Graphics Debugger v4.8.0\target\android\ 以下に、各CPUに応じたlibMGD.soがあるので、デバッグしたい端末に応じて配置していきましょう。

今回の端末はarm-v7aの端末なので C:\Program Files\Arm\Mali Developer Tools\Mali Graphics Debugger v4.8.0\target\android\arm\unrooted\armeabi-v7a\libMGD.soAssets\Plugins\Android\libs\armeabi-v7a 以下に配置します。

通常であればこれで準備完了となります。

Debuggerとアプリケーションの接続

対象アプリのAPKをインストール後に、PCとAndroidを繋げたまま、Android側の Mali Graphics Debugger を起動しましょう。 Enable MGD Deamon: の項目をONにして下のアプリケーション一覧からDebuggerに繋げたいアプリを起動しましょう。

mgd_android

アプリを起動したらPC側のDebuggerから Debug->Open the Device Manager を選択すると、接続している端末の一覧を開きます。

mgd_test_select_device

接続したい端末名の左にあるアイコンを選択すれば接続完了。晴れて各種描画関連の確認が出来るようになりました!

mgd_test_connected

上手く接続できない場合

Debugger起動前にアプリを開いている場合、うまく接続できないことがあるようなので、アプリのタスクキルをしてから再度開きなおしてみましょう。

Activityをカスタマイズしている等の場合、Activity側にも手を入れる必要があります。UnityPlayernew するより前に System.loadLibrary("MGD"); を呼んであげましょう。 よくわからないけれど使っている外部PluginがActivityを書き換えている、という場合は onCreate をOverrideしたActivityを作ってあげると動くことが多いと思います。

それではよい最適化ライフを!

明日、20日目はやまださんです。よろしくお願いします。

この記事はKLab Advent Calendar 2017の18日目の記事です

皆さんこんにちは、 jukey17 です

私はクライアントサイドエンジニアとしてゲームのプロトタイプを作る仕事を何度か経験しているのですが、今回はその際に体得した自分なりのノウハウをまとめてみようと思います よろしくお願いします

はじめに

この記事では、Unityを使ったプロトタイプ開発をベースに少人数体制(プログラマ・プランナー・デザイナー各1名ずつなど)で開発を進めていった経験を元に話を進めていきます

なのでUnityを使ったゲーム開発の経験がある方を対象とした内容になっています

プロトタイプ開発の仕事での経験ベースの話ではありますが、個人制作や学校・サークルでのチーム製作にも置き換えて考えられるような内容にまとめているので、参考にして頂ければと思います

仕様を素早く実装する → 仕様の変化を先読みした実装をする

最初に どういった考え方でプロトタイプ開発をすべきか というまとめっぽい話から始めます

大枠としてこれを念頭に置いて開発を進めておきたいということが伝わればと思います

プロトタイプ開発は「どのようなゲームを作っていくべきかを模索する」段階であるため、仕様がキッチリと決まっていることはほぼありません

アレを試してみよう、コレを組み合わせてみよう、やっぱりソレは削ろう... このようにして目まぐるしく実装内容が変わっていくのがプロトタイプ開発です

そうなってくるとついつい「とにかく数をこなすために急いで実装しなければ」と考えてしまうのですが、そうしてしまうと使い回しの効かない書き捨て品質なコードを書いてしまいがちになってしまいます

1回実装して終わりであればその実装の速度を重視して書き捨て品質にコードを書いてもよいと思うのですが、プロトタイプ開発では複数の種類の実装をしたり、その中で幾つかの機能を使い回してみたりと、とにかく色々な要素を組み合わせて面白さを追求していくため1回実装して終わりということは絶対にありません

  • 「先週試して外した機能をもう一度復活させたい」
  • 「パターンAで調整したパラメータをパターンBでも使いたい」
  • 「別の機能で使っている演出をとりあえず使い回してほしい」
  • などなど...

プロトタイプ開発では上記のような試行錯誤が沢山繰り返されます(特に後半になればなるほどこのような内容が増えます)

期間内で継続して開発スピードを出すにはこれらの試行錯誤を如何に予測して捌き続けることができるかが鍵だと個人的には思っています

ということで、上記の例を元にこれらの考え方を持ってどのように開発を進めていくのかを紹介していきたいと思います

プロトタイプ開発でありがちなケースとその対応

「先週試して外した機能をもう一度復活させたい」

仕様の定まっていないプロトタイプ開発ならではのよくあるケースの一つです

これをシューティングゲームにて敵を追尾していく弾を発射するという仕様を例に2つのコードを比較してみましょう

  • サンプルコードA
class Player : MonoBehaviour
{
    [SerializeField] GameObject homingBulletPrefab;
    [SerializeField] float homingBulletProgress;
    [SerializeField] float homingSpeed;
    [SerializeField] GameObject homingFinishEffectPrefab;
    
    GameObject homingBulletObject;

    void Update()
    {
        // 追尾弾発射
        if (Input.GetKeyDown(KeyCode.H))
        {
            // 連続では撃てない
            if (homingBulletObject == null)
            {
                homingBulletObject = Instantiate(homingBulletPrefab);
                homingBulletProgress = 0.0f;
            }
        }

        // 追尾弾の処理
        if (homingBulletObject != null && enemy != null)
        {
            homingBulletObject.transform.position = Vector3.Lerp(transform.position, enemy.transform.position, homingBulletProgress);
            homingBulletProgress += Time.deltaTime * homingSpeed;

            if (homingBulletProgress > 1.0f)
            {
                Destroy(homingBulletObject);
                homingBulletObject = null;

                // 着弾したら自機に演出が出る
                var effect = Instantiate(homingFinishEffectPrefab);
                Destroy(effect, 2.0f);
            }
        }
    }
}
  • サンプルコードB
class Player : ShipBase
{
    // 追尾弾の処理は特になし
}

[ReqiredComponent(typeof(ShipBase))]
class HomingBulletFirer : MonoBehaviour
{
    [SerialzieField] HomingBullet homingBullet;
    [SerializeField] float speed;

    ShipBase parentShip;

    void Start()
    {
        parentShip = gameObject.GetComponent<ShipBase>();
    }

    void Update()
    {
        // 追尾弾発射
        if (Input.GetKeyDown(KeyCode.H))
        {
            // 連続では撃てない
            if (!homingBullet.IsActive)
            {
                homingBullet.Fire(parentShip.transform, parentShip.Target.transform, speed, OnLanded);
            }
        }
    }

    void OnLanded()
    {
        // 着弾したら自機に演出が出る
        EffectManager.SpawnPlayerHomingFinishEffect();
    }
}

class HomingBullet : MonoBehaviour
{
    public void Fire(Tranform start, Transform end, float speed, Action onLanded)
    {
        // 弾を発射する処理
    }
}

サンプルコードAでは追尾弾をそのまま自機(Player)クラスに書いていますが、サンプルコードBでは追尾弾を発射するクラスと追尾弾自身のクラスを分けて実装しています

サンプルコードAの場合は表題の対応をする場合は、該当コードをコメントアウトしておいてそれを解除するか、削除しておいてgitなどのバージョン管理システムから掘り起こして復活させなければなりません

サンプルコードBの場合は HomingBulletFirer クラスを Player クラスが付いている GameObject に付けたり外したりするだけで対応が可能です

今回のようなシンプルな実装内容であれば、サンプルコードAのような実装でも一括でコメントアウトするだけでなんとかなりそうですが、もっと処理が複雑になって色んな所に該当コードが散らばっていくとそう簡単にはいかなくなります

サンプルコードBのように 「機能単位でパーツ(部品)を分けておく」 ことで繰り返される試行錯誤にも素早く対応していくことが可能です

Unityではこのようなまとまった処理をパーツ(部品)として扱うコンポーネント指向を基本とした設計がなされているので、サンプルコードBのようなパーツを分けた実装がしやすい構造になっています

実装を進めながらドンドンとパーツを増やしていき、それらを付けたり・外したり・組み合わせたりするだけで素早く沢山のパターンを試せるような状況を作っていくようにしましょう!

「パターンAで調整したパラメータをパターンBでも使いたい」

プロトタイプ開発では複数パターンを用意して遊び比べることが多くこういった話もよくあるできごとです

こちらもシューティングゲームを題材に2つのサンプルコードでどのように対応すべきかを見ていきましょう

サンプルコードA

// 撃てる弾の種類が複数ある・ボムが使える自機
class PlayerA : MonoBehaviour
{
    [SerializeField] int baseHp;
    [SerializeField] int baseAttack;
    [SerializeField] float moveSpeed;
    [SerializeField] BulletType[] usableBulletTypes;
    [SerializeField] int maxBombCount;

    ...
}

// 撃てる弾は1種類で特殊なスキルが発動できる自機
class PlayerB : MonoBehaviour
{
    [SerializeField] int baseHp;
    [SerializeField] int baseAttack;
    [SerializeField] float moveSpeed;
    [SerializeField] float skillIntervalTime;

    ...
}

サンプルコードB

class PlayerParam : ScriptableObejct
{
    [SerializeField] int baseHp;
    [SerializeField] int baseAttack;
    [SerializeField] float moveSpeed;
}

// 撃てる弾の種類が複数ある・ボムが使える自機
class PlayerA : MonoBehaviour
{
    public void Initialize(PlayerParam initialParam, BulletType[] usableBulletTypes, int maxBombCount)
    {
        // パラメータの保持などの初期化処理
    }

    ...
}

// 撃てる弾は1種類で特殊なスキルが発動できる自機
class PlayerB : MonoBehaviour
{
    public void Initialize(PlayerParam initialParam, float skillIntervalTime)
    {
        // パラメータの保持などの初期化処理
    }

    ...
}

サンプルコードAでは2パターンの自機をクラスに必要なパラメータを SerializeField 属性を付けて定義していますが、サンプルコードBではパラメータクラスを用意して初期化処理を呼んだ際にそれを設定しています

どちらのコードも外からパラメータを設定できるようにしている点は同じですが、サンプルコードBではパラメータを専用のクラスに逃している点に注目してください

このようにロジック(自機の挙動を定義する Player) とデータ(自機のパラメータを定義する PlayerParam)に分けることでパラメータの使い回しが容易になります

今回は自機を対象としていますが、シューティングゲームであれば、敵や弾、ボムなどある程度の機能単位でデータを別クラスに逃しておくのをオススメします

このようにして先にデータを切り出しておくことで、ある程度内容が固まったあとにプランナーの人を中心にパラメータ調整を行うといったことになってもスムーズに移行することができます

Unityの場合、データとして扱うために最適化された ScriptableObject やJSONを扱うための JsonUtility などがあるのでそれを活用しましょう!

Tips パラメータを外部から設定できるようにするためには何を使うべきか?

SerializeField 属性(Inspector)を使う

一番手っ取り早いのはサンプルコードAでも紹介している SerializeField 属性を使ってInspectorから設定できるようにする対応です

これは複数パターンを試したり色んなところで使い回したりしないようなパラメータ(ゲーム全体の設定など)を弄れるようにしたい場合に使いましょう

ScriptableObject を使う

SerializeField を使う場合に比べて実装コストは少し上がりますが、サンプルコードBのように複数パターンを試したり色んなところで使い回したりするような場合に効果を発揮できます

一度構造を作ってしまえばデータを複製してパターンも作れますし、そのままAssetBundle化することも可能です

のちのちエンジニアの手を使わずにデータのチューニングをしていくことを見越しておくと先にデータを外部ファイルに逃がしておいたほうがよいです

JSONを使う

利点は ScriptableObject と同じです

最終的にそのデータをどのように取得してくるかを想定して使い分けるとよいと思います

APIを叩いてレスポンスを受け取ってから次の処理に進むようなケースが想定されている場合はデータをJSONで定義しておいたほうが都合が良いでしょう(ステージの難易度情報やデッキの情報など)

「別の機能で使っている演出をとりあえず使い回してほしい」

仮で用意しておき、後から違うものに入れ替えるという状況もプロトタイプ開発ではよくあるケースです

プロトタイプ開発ではゲームの中身をどれだけ面白くできるかに注力して進めていくので、見た目の作り込みが後追いになることが多いです

進め方によってはデザイナーは関与せずにプリミティブなオブジェクト(簡単な記号や図形)のみを組み合わせただけでプロトタイプ開発を行うこともあります

そういった場合でも手戻り少なく実装を進めるにはどうするべきか、こちらもコードを見てみましょう

サンプルコードA

class Player : MonoBehaviour
{
    // HPを回復したときに呼ばれる
    void OnHeal()
    {
        // 回復演出は出しっぱなしにしてそのまま消す
        var effect = Instantiate(healEffectPrefab, transform);
        Destroy(effect, 1.5f);
    }

    // HPがゼロになったときに呼ばれる
    void OnDead()
    {
        if (isDead)
        {
            return;
        }
        StartCoroutine(PlayDeadEffectCoroutine);
    }

    IEnumerator PlayDeadEffectCoroutine()
    {
        var effect = Instantiate(deadEffectPrefab, transform);
        yield return new WaitForSecond(2.0f);

        Destroy(effect);
        // ゲームオーバー処理
        GameOver();
    }
}

サンプルコードB

class Player : MonoBehaviour
{
    // HPがゼロになって死亡演出が完了した後に呼ばれる
    public Action OnFinishDeadEffect { get; set; }

    // HPを回復したときに呼ばれる
    void OnHeal()
    {
        EffectManager.EmitHealEffect();
    }

    // HPがゼロになったときに呼ばれる
    void OnDead()
    {
        EffectManager.EmitDeadEffect(OnFinishDeadEffect);
    }
}

サンプルコードAでは Player クラスに直接エフェクトの発生処理を書いているのに対し、サンプルコードBでは エフェクトの発生処理を EffectManager にお願いする形になっています

自機のための演出になるので Player クラスに処理を書くと言う考え方は間違ってはいませんが、ゲームでの演出処理は「演出が終わったら」や「途中で中断したい」などの複雑な対応をするケースが多いです(そして調整の度にそのタイミングが代わります)

Prefabなどにまとめて全て演出側に処理を逃がせれば理想的ですが、内容がコロコロ入れ替わるプロト開発ではいきなり全ての処理を外に逃がすのも難しいです

そこでサンプルコードBでは EffectManager なる演出の複雑な遷移などをアレコレ吸収してくれる機能を外に用意しました

このようにしておくことで演出の差し替えが後で入ったとしても基本的には EffectManager に手を入れるだけにすることができます

ただし EffectManager をそのままエフェクトを何でも処理してくれる便利屋として扱っていくと今度は EffectManager が肥大化していって整理が難しくなってきてしまうので、その場合は様子を見つつ EffectManager も分割していきましょう

まとめ

ということでここまでつらつらと書き連ねて来ましたが、内容的にはプログラミングを設計する上で気をつけた方がよい基本的な事柄をまとめているということに気づきましたでしょうか

要は プロトタイプ開発であろうと速度重視の書き捨てコードを書くべきではない というのが私の結論です

少人数(1人)で開発を進めていくとついついルーズにコードを書いてしまいがちですが、開発が進んでいけば自分以外の人も多く関わってくることを考えると常に整理しながら開発を進めていけるのが理想です

個人の開発だったとしても、何日か経ったあと自分の書いたコードを見返して何が書いてあるのかよく分からなくなると言う経験はしたことがあるはずです

未来のチームメンバー(自分)が少しでもラクにできるコードを書くように心がけていきましょう!

明日の19日目はsuzu-jさんです。よろしくお願いします。

はじめに

この記事は KLab Advent Calendar 2017 15日目の記事です。
こんにちは。S-Typeと申します。普段はプランナーをしている企画の人間ですが、何か書いてみたいと言ってみたところ参加させてもらえることになりました。
Google Home miniから勤怠メールを送る方法をまとめます。

勤怠報告は大変

季節の変わり目は体調を崩しがちです。無理してもパフォーマンスは上がりませんし、そういうときは休みましょう。
しかし、重い体を動かしながら、一生懸命勤怠メールを打とうにも誤字脱字が多発し、修正に時間を取られてしまう……なんてことはありませんか。

byouki_oldman

そこで、Google Home miniの出番です。
Google Home miniに勤怠を送ってほしいと、寝たまま声を発するだけで送ってくれる仕組みをご紹介します。

Google Home miniとは

Google Home miniは、Google アシスタントが利用可能なスマートスピーカーであるGoogle Homeの小型版です。価格も約半額の6,000円で購入することが可能です。
「OK, Google」もしくは「ねえ、Google」から会話を開始し、以降に続けた音声コマンドを実行してくれます。例えば「OK, Google. 明日の天気は?」と聞くと「明日の〜は最高気温〜、最低気温〜で晴れるでしょう」と回答してくれます。
その他にも計算式、radikoやNHKニュースの再生、タイマーや目覚ましアラームなど、日常においての頼れるパートナーとしての役目を担ってくれます。

IFTTTとは

IFTTTは、 If This Then That をコンセプトに生まれたウェブサービスです。
トリガーである【This】とアクションである【That】、それぞれ別々のサービスを設定し、連携させることができます。
IFTTTで特筆すべきはトリガーとアクションそれぞれに設定可能なウェブサービスが豊富にあることと、こうした流れを コードを書くことなく ユーザーが任意に設定できるということです。
そして、こうした設定をレシピと呼び、公開すれば多数のユーザー間で共有することが可能です。
今回は、このIFTTTのトリガー部分にGoogle Homeで利用可能なGoogleアシスタントを設定し、Gmailから勤怠メールを送ってみることを試してみようと思います。

IFTTTレシピ作成前の準備

では、さっそく、レシピを作ってみましょう。準備するものは以下の通りです。

  • Googleアカウント(Gmailで送る際に使用)
    • IFTTTに登録可能なGmailアカウントは一つだけです
  • IFTTTのアカウント

そして、処理の流れは以下の通りとなります。

  • This:Google Home miniに「今日は会社を休みたい」と言う。
  • That:Gmailから勤怠メールが送られる。Google Home miniから「お大事にして下さい」と返される。

レシピの追加

  1. IFTTTにログインし、My AppletからNew Appletをクリックします。

recipe_0

  1. この画面が表示されるのでThisをクリックします。

recipe_1

  1. Google Assistantをクリックします。探しにくい場合は検索で見つけましょう。

recipe_2

  1. Say a simple phraseをクリックします。
    これは「今日は会社を休みたい」という単一のフレーズでの処理結果が確定しているためです。
    今回は解説しませんが、今日ではなく明日だったり、休む理由を体調不良や私用など条件分岐させたい場合はSay a phrase with a text ingredientを選択します。

recipe_3

  1. Google Homeに話しかけるときのフレーズを指定します。
    対象の項目は
    What do you want to say?
    What's another way to say it? (optional)
    And Another way? (optional)
    以上の3つです。
    What do you want the Assistant to say in response? はどんな返事をしてほしいかを設定します。
    日本語で指示し、日本語で返してほしいので Language はJapaneseにしてください。
    ここまで終わったらCreate triggerをクリックしてトリガーの作成を終わらせましょう。

recipe_4

  1. 次にThatをクリックし、トリガーをきっかけに実行される内容を設定しましょう。

recipe_5

  1. Gmail をクリックします。

recipe_6

  1. メールを送りたいので、Send an emailをクリックします。

recipe_7

  1. メール送信にあたって必要な情報を埋めていきます。
    ここで設定しているのは以下の項目です。
    To address で送信先を、Subject で件名を、そして Body で本文を設定します。
    Bodyに改行を含む場合は <pre> タグで文章を全て囲わないと、改行されませんので注意してください。
    ここまで終わったらCreate actionをクリックしてアクションの作成を終わらせましょう。

recipe_8

  1. 最後にレシピの名前を設定します。デフォルトでIfから始まる文章が入っていますので、このままでも良いでしょう。
    Receive notifications when this Applet runs のスイッチはOFFにしておくことをオススメします。
    このスイッチがONになっていると、そのレシピが実行されたらその度にメールで通知されます。
    Finishで完了します。

recipe_9

検証

では、さっそく検証してみましょう。

前提条件

  • メールの送り元は私用のアドレスです
  • メールの送り先は社用のアドレスです

実践

私「ねえ、Google。今日は会社を休みたい」
Google Home mini「お大事にして下さい」
……しばらくしてから ピコン!(メールの着信音)

スクリーンショット

出来ましたね!!

recipe_10

最後に

今回はGoogle Home miniとIFTTTの連携によるメールの送信を紹介しました。
100%衝動買いだったGoogle Home miniでしたが、IFTTTのおかげで様々なウェブサービスとの連携が楽しめるので、無駄にはならなさそうです。
何よりもコードを書く必要がなく、GUIで入力項目を埋めていきながら設定が出来るため、私のような開発職以外の人間でも簡単に出来ました。

余談

Google Home miniを今後も活用していきたいので、nature remoを注文しました。
nature remoと組み合わせることで、音声による赤外線リモコン家電のコントロールが可能になります。
これが出来るようになると、寝たまま部屋のリモコン照明のON/OFFが出来るようになります。もう完全に未来ですね。
本稿を書くまでに届いていれば、そちらをテーマに書こうと思っていたぐらいでしたが、間に合わなかったため、またいつか別の機会で書かせてもらえたらと思っています。

↑このページのトップヘ