Unity 1 Week Game Jam お題 「当てる」

今回も参加しました

Alt text

今回も参加させてもらいました。
今回は反省すべき点が多くある(期日から1週間遅れた)ので、レポートを書いていきます。

作ったゲーム「BountyBommer」

Alt text

↑画像クリックでプレイできます。
爆弾を武器に戦う主人公が、マフィアに選挙された街を救うけど、
爆弾の取扱に失敗すると逆に街を破壊して賠償金を請求されると言うゲームです。 (過去にゲームセンターにあった、リアルドライブライクなゲーム)

1週目 月曜〜水曜

この時期はずっと企画を考えていました。
今回の企画は、個人的に考えやすそうだけど、色んな案を考えていると大体似たり寄ったりな着地点に行き着いてしまい、難易度が高かったです。
幾ら考えてもよくありそうな企画に行き着いてしまい、考えては消してを繰り返しました。

以下は実際に企画を考えてた際のマインドマップ

f:id:project-unknown:20180304035903p:plain

1週目 木曜〜土曜

建物が爆破された際の表現をどうするか考えたのですが、以下のAssetを採用しました。

このAssetはSpriteを簡単に細切れに破壊する表現を行うことが出来る為、今回のゲームには打ってつけのAssetです。

詳細な使い方はまた別途纏めようと思うので、どんなイメージで作るのかをサマリますが、

f:id:project-unknown:20180304113707p:plain

Inspector上で、爆破の範囲や細切れにするフラグメント等の見た目の制御を行い、
例えば、以下のコードをGameObjectとしてScene上に配置してあげれば、Scene上のSpriteはどんなものでも破壊できます。

public class SpriteExplode : MonoBehaviour {
    private Exploder2DObject _Exploder;

    void Start () {
        _Exploder = Exploder2D.Utils.Exploder2DSingleton.Exploder2DInstance;
    }

    public void ExplodeObject(GameObject obj) {

        var explodeObject = obj.GetComponent<SelfExplode>();
        if (explodeObject != null) {
            explodeObject.ExplodeObject();
        }
        _Exploder.transform.position = Exploder2DUtils.GetCentroid(obj);

        _Exploder.Radius = 0.1f;

        // DONE!
#if ENABLE_CRACK_AND_EXPLODE
        _Exploder.Crack(OnCracked);
#else
        _Exploder.Explode(OnExplosion);


#endif
    }

    /// 爆破完了後に呼ばれるコールバック
    void OnExplosion(float time, Exploder2DObject.ExplosionState state) {
        if (state == Exploder2DObject.ExplosionState.ExplosionFinished) {
            // Debug.Log("finish!");
        }
    }
}

※爆破の挙動はInspectorだけではなく、コード上からも柔軟に変更できます。

ただ、このAssetは1つだけ使いにくい所があって、このAssetのオブジェクトはシングルトン設計になっているため、複数Objectを指定した所で結局同じところで処理されてしまいます。
これが何を意味するのかと言うと、2D開発時にLayerを用いて奥行きや段差を意識する設計で作ってしまうと、Exploderが作用するLayerを切り替えて実装する事になるのですが、爆破解体されたSpriteまでExploderのLayerを引き継いでしまうため、挙動がおかしくなります。

今回のゲームでは最前面のLayerにだけExploderを作用させ、後ろのLayerはExploderを使わない方向で対応しています。

とまぁ、Exploder2Dの使い方に苦戦しつつも、1週目の土曜にはある程度ゲームとして形になる所までは完成していました。

1週目 日曜

通しでプレイした際に、ゲーム2週目で以下のErrorが出まくる事態に遭遇しました。

MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.

エラーログそのままなのですが、2週目でとあるGameObjectにアクセスしようとした際に、既に開放されたGameObjectにアクセスしようとしてエラーが発生しています。
この不具合が本当に厄介で、別なオブジェクトからアクセスする時のみ発生して、例えば自分のライフサイクル(StartやUpdateメソッド)でアクセスすると、問題なく値が入っている事。
となると、古い参照が残ってしまっているのかと思われるのですが、その参照を残してしまっている所が具体的に分からず、
また、この不具合の質が悪い所は、WebGLの場合、ブラウザリロードしても参照を維持してしまう事です。

似たような所で、以下でも語られていましたがどれも効果はありませんでした。

answers.unity.com

answers.unity.com

answers.unity.com

stackoverflow.com

この日はほぼ、この不具合の調査を行っていたのですが、解決することが出来ず、締め切りの20時に。
まさか間に合わないと思っていなかったので、かなりショックを受けながら他の皆さんのゲームをプレイして癒やされてました。
いやーみなさん本当に凄い…。

2週目 月曜〜金曜

仕事が忙しく、基本終電帰りとなり殆ど着手出来てません…。

2週目 土曜〜日曜

この日にやっと原因がわかりました。
過去にiOSで言うNotification, Androidで言うEventBusライクな実装についての記事を書いたのですが、

www.project-unknown.jp

今回もこの設計を使っており、Scene切り替えのタイミングで通知の解除をし忘れていて、2週目以降は古い参照を保持し続けてしまった為に、古いGameObjectに通知されnullとなっていました。
まさかSubscribeして、Unsubscribeし忘れると言う初歩的なミス…。

この原因に気づいたのは本当にたまたまで、思考整理の為にリファクタしている最中に気づきました。

ここからは、残っていたUIを整えたり、WebGL用に解像度調整を行ったりして、やっとリリースすることが出来ました。

今回の反省点

過去の挑戦でも色々学ぶ事が多かったのですが、
今回は色々と反省すべき点が多い点で、物凄く自分の糧となったと思います。

後学へつなげる為に、反省点とその分析、next tryについて記そうと思います。(主に自分向け)

とにかく、今回は1つのバグを解決するのに凄い時間がかかったが、次回以降も何かに詰まってしまう事は大いに起こり得ると思ってます。
そこにどれだけ時間を掛けることが出来るかが大事で、他の難易度が低い所を以下に効率よく開発するかがポイントになりそうです。
(詰まる内容は読みにくいから、解決する時間を確保する方向で考える)

エターナル現象

今回は、企画の段階でエターナル化していました。
平日開発する時間が無いサラリーマンに、この規模を1週間で作るのは結構しんどいです。
システム開発に必要な不足な事態に対応できる程度のBufferの概念は持ち続けないといけないので、そうなると「捨てる判断」と「捨てる勇気」を成熟させる必要がありそうです。
ただ、逆に良かったのは作っている最中にあれもこれもはやらないように注意していたので、開発中のエターナル化が起きなかったのは良かったと思ってます。
と言うか、作ってる最中にもエターナル化してたらもっと時間がかかっていた事でしょう…。

どうすれば改善できるだろうか?

上記でも書きましたが、企画を作ったら、

  • 何を捨てるか?
  • 作るゲームのコアバリューは何か?

を考え、いちばん大切な所に時間を掛けて作るようにする。
それ以外は捨てる勢いで考えれるようにする。ここが私がかなり弱い所。

ただ、最低限の所(トップやらリザルトやら)は捨てにくい。
こういう所は、他でも使えるようなPrefabやPackage化して使いまわせるようにするべき。

資産を使いこなせていない

ゲームジャムが始まってから必要なアセットを見つけ出して勉強しだすのは効率が悪い。
企画を元に新規のアセットを導入する必要があるのならわかるけど、今まで購入したものについても、これまで一切触っていなくて、必要になって勉強し始めるのでは、ゲームジャムでは間に合わない

どうすれば改善できるだろうか?

  • やはり日々利用するアセットは勉強しておく必要がある。
  • Unityの勉強の意味で1から開発するでも良いが、私の目的はゲームを作って世に出す事。
  • その上で、車輪の再発明となることは可能な限り避けたい。
  • また、これをやる上で、平日取れて1時間くらいしか開発時間の確保が出来ないがここはどうするか。
    • 時間を分割するのが一番手っ取り早いが、そうなると30分がアプリやゲーム開発、もう30分が勉強となると、どちらもエンジンすらかからないと思う。
    • 日ごとに勉強の日、開発の日と分けるのがベスト。
      • 例えば、平日はゲームアプリ開発
      • 休日は半分が開発、もう半分が勉強
  • アセットのドキュメントを読むだけでは駄目。
    • 使っても忘れるのに、読むだけだと頭から速攻抜ける。
      • 実際にアセットを使って開発すべき。
      • 使った内容を、更にブログ等でアウトプットする事で、記憶の定着化を図る。

過去に開発したものを再開発している

アセットと同じ問題だが、過去に作ったものを新しいゲームでも再開発していた。
思い出して作る分、経験値稼ぎには良いが、こんなことをするくらいなら、やった事が無いことに力を注いだほうがスキルアップになりそう。

どうすれば改善できるだろうか?

  • 他でも使えそうなものはprefab化や、package化する。
    • 実はやっていたけど、管理の自分ルールが凄いことになっていて、いくつもプロジェクトが分岐し、何を使って良いのか、また管理コストが凄い事になっていた。
      • まずは、1つのプロジェクトに全てを纏める。

Unityを久々に触った

他のツール系アプリを開発しており、Unityを2,3ヶ月ぶりに触ったので思い出しに時間が掛かった。

どうすうれば改善できるだろうか?

  • 他のアプリを作っている時はUnityを触らないのは仕方ない
    • 上述した勉強の時に触ることで、忘れを防ぐ
    • アセットや共通処理の勉強やってりゃ忘れるのは防げるはず

利用Asset

主にSpriteの爆破解体に使っています。
こちらは主に2D用ですが、以下の用に3D用もあります。

ステートメントAssetです。PlayMakerが有名所ですが、Arborは自分での拡張が本当にやりやすいので、こちらを愛用させてもらっています。

保存機能を簡単に実装、Debugする為のAssetです。

UnityでGameObjectが画面上(カメラ内)に居るのか確認する

はじめに

UnityでGameObjectが画面(カメラ)の外に居るのか中に居るのかを確認したい時がママあります。
例えば、画面に表示された際に処理を行ったり、画面の外に行ったらGameObject自体を破棄したり。
今日はそのやり方の紹介です。

RendererのisVisibleを使う

カメラのviewPortで判断しても良いのですが、RendererのisVisibleを使うと便利です。

以下は、自分自身(GameObject)が画面上に表示されているのかのIsVisibleメソッドの例です。

public class SampleScript : MonoBehaviour {
    private Renderer _Renderer;
    
    void Start () {
        _Renderer = GetComponent<Renderer>();
    }

    void Update() {
        if (IsVisible()) {
            Debug.Lod("表示中");
        } else {
            Debug.Log("非表示中");
        }
    }

    public bool IsVisible() {
        return _Renderer.isVisible;
    }
}

これで、画面上に居るのか確認できます。

iPhone XでUnityのAdMobの表示崩れが起きた時の対処法

はじめに

iPhone XはAppleからDeveloperへの挑戦状だと巷で言われる余波が、UnityのAdMobでも来てました。

iPhone Xが出る前にAdMobを実装していたUnity性のアプリを起動したら見事に表示が崩れてたので、その対処法です。

どんな感じで崩れていたのか?

キャプチャを撮り忘れたのですが、バナーのロードは成功しており、表示の高さが足りていない状態でした。
(横幅は正確だが、高さが足りなく線が描画されているレベル)

対応方法

対応方法は簡単で、AdMobのSDKを更新するだけです。

AdMob SDK Gitから最新のSDKを取得すれば直りました。

(私の場合は、v3.10.0を導入しました。

その他

AdMob SDKを更新することで、Firebaseと競合して不具合が起きるかもしれません。

その場合は、以下の記事を参考になさってください。

www.project-unknown.jp

www.project-unknown.jp

Unityで複数のGameObjectに一斉に通知する方法 Notification, EventBus

はじめに

UnityでGameObjectAがGameObjectBに何かしらのイベントを渡す場合、何パターンか、方法があります。

  • ExecuteEvent (SendMessage)
  • ラムダ式 (delegate通知)
  • System.Action (delegateとほぼ同意)

ただ、上記の場合、GameObjectAがGameObjectBの存在を知っていないと行けないですし、2つのGameObject(クラス)がつながっている必要があります。
この場合やっかいなのが、GameObjectAがGameObjectBの存在を知らなくても、通知をイベントを送りたい時です。

実例で言うと、先日unity 1 weekゲームジャムで作ったちいさなぎんがでは、星と星が結合し1つの星になる際に、ログを表示するのですが、全ての星とログウィンドウとを結びつけるとあまりにも冗長です。
そこでiOSの場合だとNotification、AndroidだとEventBusという考え方を使って、この問題を解決したいと思います。

Notification, EventBusとは?

Observerっぽい考え方です。
1つの代表する通知を管理するクラスが居て、そのクラスに対して通知送信者は通知を送り、通知受信者は通知を受信するよう登録します。

iOSで言うと、Notificationライク考え方の代物で、AndroidだとEventBusですね。

イメージとしては以下な感じです。(上記のちいさなぎんがの時の設計です

f:id:project-unknown:20171210144846p:plain

通知管理部分が、通知を受信するObject, 登録するObjectとの窓口を担います。
各通知を送りたいObjectが通知管理クラスに対し、通知を送ります。
ログウィンドウが、通知購読を行っており、Objectから通知が届いたら、通知管理クラスが通知購読を行っているログウィンドウに対し通知を行う流れです。

実際に作ってみる

通知管理

考え方はシングルトンとObserverの一種なので至ってシンプルです。

ここでは、単純に通知を送る(NotifySomeEvent)と、引数付きの通知を送る(NotifySomeEventWithArgs)を用意しました。
また、通知購読をしているクラスへの受付としてはDelegateとしています。
これは単純に使い勝手の問題なので、ラムダ式で繋いでしまっても問題ありません。

gist.github.com

通知購読

通知管理クラスは出来たので通知購読を行います。
まずは、引数無し通知を受け取る例です。

void SubscribeSample() {
    Notification.Instance.SubscriveSomeEvent((Notification.OnSomeEvent)OnSomeEvent);
}
void OnSomeEvent() {
   /*処理*/
}

NotificationクラスにあるSubscriveSomeEventクラスにDelegateを登録しているだけです。
これで、Notificationクラスに対して通知が行われた際に、このDelegateが呼び出されます。

次に実際に通知を登録する際は以下の通り

Notification.Instance.NotifySomeEvent();

通知と言ってもメソッドを呼び出すだけです。

もう通知が不要になったら、以下のように購読解除を行います (購読解除を行わないとオブジェクトが解放されないため、確実に解除してください)

void UnSubscribeSample() {
  
  Notification.Instance.UnSubscribeSomeEvent((Notification.OnSomeEvent)OnSomeEvent);
}

ちなみに、引数付きの通知は以下の様にやり取りを行います。

通知購読

void SubscribeSample() {
    Notification.Instance.SubscriveSomeEventWithArgs((Notification.OnSomeEventWithArgs)OnSomeEvent);
}
void OnSomeEventWithArgs(string text) {
   /*処理*/
}

通知送信

Notification.Instance.NotifySomeEventWithArgs("hogehoge");

注意点

Objectのハブとして通知管理クラスを立てているので、Object毎に依存関係がなくなり非常に使いやすいのですが、 Debugが非常にやりづらいです
1,2個のオブジェクトであれば、何か問題があっても直ぐに突き止められますが、オブジェクトの数が数百を超える場合は何がなんだかわからなくなります。(それに1,2個のメッセージのやり取りなら、冒頭で記載した通り、ExecuteEventの手法を取ったほうが絶対良いです)
なので、使うなら、自動的にObjectに識別子を付与した通知を行うように工夫すると良いかもしれません。

Unity 1 Week Game Jam お題「space」

今回も参加しました

Alt text

今回も参加させてもらいましたヽ(=´▽`=)ノ
んで、今回もレポート書いてきます。

つくったゲーム 「ちいさなぎんが」

Alt text

↑画像クリックでプレイできます!
画面をぐるぐる回すことで、銀河系の星をまぜまぜして自分だけの銀河を作ろうってコンセプトです。

レポート

1日目

企画を考えます。
いつものように、ゆう@あんのうんが何案か考えて、それをぽぽたにインプットして、二人の中でピンと来たものを作っていくスタイルで、

その時出てきた案をものすごくざっくり列挙すると

  • 銀河をまぜまぜして、自分だけの銀河を作って、他の人とシェアし合えるゲーム
  • 流れ星に載って宇宙を飛び回るゲーム
  • 空間にうまく逃げ込むゲーム
  • 空間を行き来できる中二病設定の何か
  • 空間を埋めるようなパズルゲーム
  • 時間の隙間的な所を何かするゲーム

と色々あったのですが、銀河をまぜまぜするって所が、ピンと来たのと、新規性があって面白いかもーで合意。

ここから開発開始。
まずは、宇宙を作ったことが無い(と言うか3Dがあんまり…)ので、そこからです。

んで、出来たのが↓ ( 長々と見ると酔うので注意

出来上がったのを動かしてみて、速攻酔いました。

2日目 - 3日目

ごりごり開発を進めます。(同時にぽぽたが500以上の星の名前をどうするか考えてました)
平日は1時間の開発時間が取れれば良い方なのですが、今回は調子が良く、この時期には基本的な所は出来てました。

星が他の星に取り込まれると言う所まで出来てます。

ただ星が取り込まれるのは面白く無い為悩んでました。
そこで出た結論が、

そうだ爆発しよう

んで出来上がったのがこれ

宇宙戦争が勃発。
と言うか重くてゲームにならん

後、この時に↓みたいなログを入れて見たら意外に面白く仕様として盛り込むことに

f:id:project-unknown:20171121003956p:plain

4日目

ログの実装。
と言うか星が消滅しただのログに出すには、全てをManagerクラスで管理するのは設計的に宜しくないと思い、オブジェクト指向ライクに星が自分の状態が変わった際にiOSで言う、Notificationっぽい通知を送るのが良いかと良いやり方が無いかと探り、EventBusに行き着き実装。

iOSのNotificationの時にも苦しめられましたが、やはりこの手の設計は非常にデバッグがし辛くバグの温床でした。

しかし、他に良いやり方を知らなかった為、だましだましで実装した為、平日の貴重な1時間を全てこれにつぎ込んでしまいました(;´Д`)

そして出来たのがこれ

既にほぼ完成形です。
またTweetしている通り、この時負荷にも非常に悩んでおり、オブジェクトプールに挑戦。
(あまり負荷軽減にならなかった。

5 - 6日目

とにかく細かい所の作り込みです。
このタイミングで、せいめい機能やちいさなきんか機能が追加されました (ショップはこの段階ではまだ)

6日目に徹夜しての7日目突入

締め切り当日の朝に、やろうとしていた事の半分も出来ていなく、あきらめモードでした(Twitterに言い訳を書きましたが、基本機能しか間に合ってません)

ふとなんで俺はこんなに頑張っているんだろうと悟りの境地に入りましたが、ふと朝飯を買いに行ったコンビニで

f:id:project-unknown:20171121005112p:plain

こんなん見つけた為に、勢いで購入し身体にムチを打って開発着手
激強打破って眠眠打破の最上位系があるんですね・・・

午前中から15時くらいまでにショップ機能のUIと購入後星追加まで実装して、WebGLに書き出し

Bloom部分がくっそダサい

しかしもうクオリティを挙げて居る余裕など無いのと、uGUI部分が尽くバグっていたりメモリリークで落ちたり(WebGLのスタックトレースが出す意味あるの?ってレベルでひどかったので原因究明できず)で、とにかくなんとか動作してるやってレベルになったので、作品公開。

利用アセット

今回もアセットには大変お世話になりました。
ここでは利用させて戴いたアセットを載せていきます。

拡張しやすいFSM:『Arbor 2』

もう私の中では神Assetでとにかくお世話になっています。
凝った所をArbor2に任せようとするとそれなりに役割分担を考えたり設計しないと導入し辛いですが、他のゲームでも共通して使えるような所は、自前のArborスクリプトを突っ込んでパッケージ管理してます。
(単純なSceneやボタン周り、SE周りの自前スクリプトを数十個用意して、unity package化して直ぐに突っ込めるように管理しています)
このAssetのお陰で、平日1時間しか開発時間が取れなくても、間に合わせることが出来ました。

このブログでも何度か登場していますので、以下も御覧ください。

www.project-unknown.jp

www.project-unknown.jp

www.project-unknown.jp

www.project-unknown.jp

G2U

このAssetはGoogleスプレッドシートで入力したデータをUnityに突っ込むことが出来る優れものです。
ゲームのメタデータや、ローカライズ等に威力を発揮します。

ぽぽたが考えてくれた500以上の星の名前を一々コードに落とすのはしんどいので、スプレッドシートに書いてもらって、それをこのAssetを使ってUnityに突っ込みます。

Unity上では、Static Databaseと銘を打ったStaticクラスを自動生成してくれたり、Dynamicと称してGameObjectライクに使えたり、JSONとして扱えたり様々なスタイルに変換してくれるので非常に便利です。

UniRx

最近LINQを使えるようになったのと、iOSのアプリでちょくちょくRxSwiftを使っているので、このアセットは個人的にツボをくすぐられます。
何より、Unityのプロシージャ的機能を、関数型ライクにコードを書いていけるのが非常に素晴らしい。
まだまだ全体の5%も使いこなせていないですが、既に必須アセットの1つとなっています。

また、Arbor2の時の項目にも記載しましたが、Arbor2とUniRxを組み合わせると、Arborでプロシージャ周りの機能を共通ライブラリ的に作り上げていけるので非常におすすめです。

DOTween

かなりの人が使っているんじゃないかしら?というアニメーション周りを簡潔にコードで記載出来るアセットですね。

iTween

DoTweenとかぶる所があるのですが、DOTweenでiTweenで言う所の「ValueTo」の書き方がわからなかったので、iTweenを使いました。

具体的には、スタートボタンを押した後に、uGuiをフェードインで画面描画しているのですが、その際に以下のようなコードでフェードイン処理を行ってます。

   /// <summary>
    /// ログウィンドウを表示します
    /// </summary>
    public void Display() {
        iTween.ValueTo(
            this.gameObject,
                iTween.Hash(
                    "time", 1.5f,
                    "from", 0f,
                    "to", 1f,
                    "onupdate", "DisplayLogWindowAlpha",
                    "EaseType", iTween.EaseType.easeOutSine
            )
        );
    }

    /// <summary>
    /// ログウィンドウのiTweenから1フレーム毎に呼ばれるメソッドです。
    /// フレーム毎のAlpha値をセットします。
    /// </summary>
    /// <param name="newValue"></param>
    void DisplayLogWindowAlpha(float newValue) {
        GetComponent<CanvasGroup>().alpha = newValue;
    }

こんな感じで、timeに設定した間、1フレーム毎に、DisplayLogWindowAlphaメソッドを呼ぶ(その際の引数は、該当フレームでのα値)機能を簡単に作れます。

Post Processing Stack

これも非常に有名なアセットですね。
カメラに視覚効果を追加するアセットで、主に星の輝きに使っています。
(ただWebGLで出力した際にBloomがださくなってしまったために、使いこなせてません)

First Fantasy for Mobile

個人的にすごく好きなアセットで、こういう世界観が好きでいつか自分のゲームに登場させたいと購入していたアセットです。

今回はビッグバンの表現に使っています。(モデリングだけでなく、パーティクルがまた素晴らしい)

Terrainに使える素材も梱包されているので、これ1つあれば中規模のゲームのステージ全部まかなえているんじゃないかな?と思ってます。(これで2000円位で購入出来るので、非常にお買い得かと)

f:id:project-unknown:20171121011932p:plain

f:id:project-unknown:20171121011947p:plain

f:id:project-unknown:20171121012056p:plain

VSCode

VSCodeでコード書く際には必須ですね。
私は数ヶ月前からVSCodeに移住しているので、無いともうあかんです。
(と言うかアセット突っ込まなくてもVSCodeでUnity環境整えるようにして欲しいくらいです….

総評

今回はこれまでと違って、初日から行動できたのはかなりでかく、じゃないと作り慣れていない3Dのゲームなんかできなかったんじゃないかと思います。
が、エターナル現象が発生してしまい、結局ギリギリになり、丁度なボリューム感に収める難しさを学びました。
後は、負荷やら目に見えてバグだとわかっている所とか、時間という言い訳の元、見て見ぬふりするのが本当に辛かったです。
最近Unityがやっと少し使いこなせてきたなーと思っていたのですが、まだまだだと痛感できたのは良い学びでした。

上記に加え、課題とやりたい事がまだまだ沢山あります。

  • 負荷対策
  • セーブ機能
  • 様々な星のイベント (実は購入出来るけど、きいろの星を買っても何も起きません…(;・∀・)
  • アプリ化
  • ●●モード (アプリ化する際に実装予定

とりあえず、負荷がはんぱないのでまずはそれに着手していこうと思います。

UniRxとコルーチンを組み合わせる

はじめに

C#の考え方として、Delegateよりはラムダ式が好まれるようになっているようです。(C#に取り組みだして日が浅いので違ってたらすいません)

ただ、ラムダ式でメソッドを呼び出して、コルーチンの結果を受けてコールバックさせると言う所で詰まってしまい、その解決法の1つの備忘録。

やり方は掲題の通り、UniRxとコルーチンを組み合わせる方法で解決します。

本題

以下は、コルーチンの終了タイミングをUniRxのObservableで待ち構えるやり方です。
以下のFromCoroutineを利用します。

Observable.FromCoroutine(Func<IEnumerator>, bool);

第1引数はコルーチンを宣言し、第2引数はyieldしたタイミングでOnNextを発行するかを設定します。

以下にFromCotoutineを用いたSampleを載せます。

void Hoge() {
    Fuga((System.Action result) => {
        Debug.Log("finish!");
    });
}

void Fuga(System.Action<bool> completion) {
    Observable.FromCoroutine(CoroutineSample, publishEveryYield: false).Subscribe(
                _ => Debug.Log("next"), 
                () => completion(true)
            ).AddTo(gameObject);
}

IEnumerator CoroutineSample() {
    yield break;
}

FugaでCotoutineSampleの結果をうけとり、その結果をHogeに返します。
注意点としては、Subscribeがコールされる度にコルーチンが発行されます。
この解決策としてはこちらの記事で紹介されているHot変換を行えば良いみたいなのです。

コルーチン自体をReactiveな扱いに出来て非常に便利なので是非用いていきたいですね。(まだまだUniRxの書き方になれていないので、経験が必要ですが…)

Unityでパラメータ調整等のレベルデザインをiPhoneやAndroidの端末上で簡単に行う方法

概要

ゲームを作っている時に、キャラクターのステータスや移動スピード等の各種パラメータの細かい調整を何度も行うことになると思います。
ステータス等に関してはUnityのエディター上での確認で十分ですが、
アクションゲーム等の場合、やはり実際にiPhoneやAndroidの端末で触ってみないと分からないと思います。

ただ、ここで1つ問題があります。
それは実機ビルドが非常に時間が掛かると言う事。
私の環境だと、Macbook pro 15inch 2014モデルでメモリを32Gに拡張していますが、それでも簡単なカジュアルゲームのbuildに数分掛かる等あり、細かい調整を何度も行うのはかなりしんどいです。

また、Debugger機能として、各種パラメータを弄るUIを作ってしまうと言うのも考えたのですが、元来の面倒くさがりな私にとってそのUIを作る、且つ、パラメータが増える度にUIを弄って行く苦行に耐えきれる自信がないです。

今回はGUIを作らずに、Unityで再ビルドしなくてもパラメータを弄ってリアルタイムに反映させる方法を紹介します。

FirebaseのRemoteConfigを利用する

このブログでこれまでに何度か登場してきたFirebaseを利用します。
Firebaseはリアルタイムデータベースや、Twitter認証等の他に、RemoteConfigと呼ばれる機能が提供されています。

これは、その名の通り、サーバ上に設定を置いて、それをアプリが参照する仕組みです。
主にアプリなどではA/Bテスト等に用いられます。

やっている事は、jsonライクなパラメータをFirebaseに登録するだけなので、今回の要件を叶えることが出来ます。

扱える型

実際にどんなデータを扱えるのかです。

  • bool
  • IEnumerable
  • double
  • long
  • source
  • string

※詳細はこちらから。

floatが欲しい所ですが、doubleで代用できそうなので良しとしましょう。

実際の入稿画面

現在作っているぱたぱたう〜のんの作りかけの設定のキャプチャです。

f:id:project-unknown:20171022194838p:plain

また、パラメータを追加する時は、以下のような画面から入稿します。

f:id:project-unknown:20171022194906p:plain

値を入稿したら、「変更を公開」ボタンを押して本番反映しましょう。

f:id:project-unknown:20171022195120p:plain

Unityでの準備

FirebaseのRemoteConfigを利用するには、RemoteConfig用のパッケージを導入する必要があります。

こちらより、FirebaseSDKをダウンロードし、その中の「FirebaseRemoteConfig.unitypackage」をimportします。

また、Firebaseのコンソールから以下の設定ファイルをダウンロードします。

  • iOS
    • GoogleService-Info.plist
  • Android
    • google-services.json

細かいやり方は、以下の記事に詳細を纏めて居ますので、まだFirebaseコンソールの初期設定が終わっていない方は参照ください。

www.project-unknown.jp

Unity上でRemoteConfigからデータを取得する

以下はRemoteConfigからデータを取ってくるSampleコードです。

再掲しますが、以下のキャプチャのデータを取得してくるコードです。

f:id:project-unknown:20171022194838p:plain

gist.github.com

設計方針

今回作ったのは、レベルデザインを行うことをメインで考えているので、リリース時は利用しないコードで設計を考えます。
RemoteConfigはゲーム起動開始直後にFetchしてきて、後はシングルトン内に展開したフィールド情報を使いまわす想定としています。 また、RemoteConfigは12時間キャッシュが聴くのですが、今回は微調整をリアルタイムで確認したい為、キャッシュは行わないようにします(Debugモードで実行)

使い方

ゲーム起動時のタイミングにでも、以下を埋め込みます。

FirebaseRemoteConfig.Instance().fetch((bool result) => {
    // データ取得成功時の処理
});

RemoteConfigからデータの取得が完了した際に、resultに成功/失敗のフラグを持たせてコールバックされるようにしているので、適宜処理を埋め込みます。

後は、コード見るとわかりますが、シングルトンなので以下のようにGetterを呼び出せば値を取り出せます。

FirebaseRemoteConfig.Instance().CatOnStageTimeMin;

処理の簡単な説明

キャッシュをOFFにする

FirebaseのRemoteConfigはDebugモードで実行すればキャッシュがかかりません。
その設定の方法は、以下のように行います。

var settings = Firebase.RemoteConfig.FirebaseRemoteConfig.Settings;
settings.IsDeveloperMode = true;
Firebase.RemoteConfig.FirebaseRemoteConfig.Settings = settings;
キャッシュデータをFetchして利用

コード上だと、以下の部分でローカルにキャッシュが完了しています。

System.Threading.Tasks.Task fetchTask = Firebase.RemoteConfig.FirebaseRemoteConfig.FetchAsync (new System.TimeSpan (0));

fetchTask.ContinueWith(task => {

});

が、実際にUnityで使おうとすると、キャッシュデータをFetchする必要があります。
それを行っているのが、以下のコードです。

Firebase.RemoteConfig.FirebaseRemoteConfig.ActivateFetched ();

RemoteConfigのFetchデータから値を取り出す。

データを取り出すのは、以下の様にGetValueを使います。

Firebase.RemoteConfig.FirebaseRemoteConfig.GetValue("version")

GetValueで返却されるデータは、「ConfigValue」で返ってきます。
ConfigValueは、値を様々な型に変換するプロパティが用意されています。
上記のversionは、string型で登録されているので、ConfigValueでStringValueプロパティを指定して取得します。

Firebase.RemoteConfig.FirebaseRemoteConfig.GetValue("version").StringValue;

ログの部分

Utilitys.LogCurrentMethod("Faild.");

この部分ですが、クラス名・メソッド名と一緒にログ出力を行う拡張機能です。
詳細は、過去に記載してますので、以下を参照ください。

www.project-unknown.jp

それ以外のやりかた

他に思いつくやり方ですが、思いつくところだと以下でしょうか。

UnityRemoteを使う

Unityが提供しているiPhone, Androidのアプリで、一番手っ取り早く実機確認できるのがこれです。

docs.unity3d.com

ですが、この欠点が遅延が発生しまくると言う所が厳しく、アクションゲームの細かい調整には適していないかと思います。

l2 Localizationを使う

ローカライズがメインのAssetですが、l2 Localizationを使うと言うのも選択肢として考えて良いかもしれません。
こちらは、Googleスプレッドシートでの変更が端末であってもリアルタイムに反映されるみたいです…が、有料アセットと言うところもあり私は試せていません。

さいごに

今回はFirebaseのRemoteConfigを利用してレベルデザインを行う方法について記載しました。
最初の下準備さえ行えば、後はプロパティを追加していくだけで、再度ビルドしなくても端末とFirebaseだけで設定をリアルタイムにやり取りが出来るので、非常に開発が楽になるかと思います。
(こういう面倒な事を簡単にする仕組みばかりに手を出して、肝心のゲーム開発が遅延しているのはここだけの話…。

UnityのSpriteを引き伸ばす際に綺麗に引き伸ばす方法 SpriteEditor

概要

Unityで例えば、ダイアログを作る際など、文字の長さに応じて背景に設定しているuGUIのImage(Sprite)を引き伸ばすケースは良くあるかと思います。

単純な塗りつぶしされたSpriteであれば特に問題は無いのですが、装飾を入れたSpriteの場合、引き伸ばしたく無い箇所も出てきます。

例えば、以下のSpriteを背景に使おうとして、

f:id:project-unknown:20171014135520p:plain

文字数が足りない為、縦と横に伸ばします。

f:id:project-unknown:20171014135617p:plain

この場合、周りの枠組みまで引き伸ばされてしまって昔のHTMLページを彷彿とさせることになってます。

今回は、この状態を解決する方法です。

Sprite EditorのSlice機能を使う

ProjectEditorからSpriteを選択すると、Inspectorに以下のキャプチャのように、Sprite Editorボタンが表示されるので、それをクリックします。

f:id:project-unknown:20171014135907p:plain

表示された、Sprite Editorと一緒に表示されるウィンドウの、Border部分に数値を入れてみます。

f:id:project-unknown:20171014140032p:plain

入力すると分かるのですが、Borderに値を入れると、Sprite Editor上のSpriteに線が描画されます。

f:id:project-unknown:20171014140113p:plain

上記の例では、左30, 上49, 右49, 下30に境界線を置いてSliceする状態となります。
ここで、Sliceされた上下左右の装飾はSpriteの中心から切り離された状態となるので、Spriteを拡大縮小しても一緒にサイズ変更されることはなくなります。

ここまで実施したら、Sprite Editor上部のApplyをクリックして、閉じます。

これで準備が整いました、後は先程と同様に再度Spriteを設置して、拡大してみます。

f:id:project-unknown:20171014140428p:plain

綺麗に引き伸ばしする事ができました。

Unity C# でメソッド名やクラス名を簡単にログ出力する方法

概要

Unityでは、swift等の他の言語にあるような「FILE」や「LINE」に相当するものはプリプロセッサが無いため、利用できません。
その為、例えば、メソッドがどのタイミングでコールされたかを埋め込むには以下のように記載しなければなりません。

Debug.Log("[" + this.GetType().FullName + "] " + System.Reflection.MethodBase.GetCurrentMethod().Name);

上記の実行結果は、例えば「AdManager」というクラスの、「RequestBanner」メソッドで記載すれば以下の様に出力されます。

[RequestBanner] AdManager

これで、やりたい要件は満たせては居るのですが、毎回このように記載するのは冗長なので、なんとか良いやり方は無いか?が今回のお題です。

MonoBehaviourの基底クラスを作って、StackFrameを使う

基底クラスを作成出来るのであれば、基底クラスにログを仕込んでそれを継承して利用することが出来ます。
後述のstaticと違うところは、わざわざクラス名(もしくは名前空間)を意識しなくても利用できる所です。

public class BaseMonoBehaviour : MonoBehaviour {
    /// <summary>
    /// 現在のClass名, Method名をログ出力します。
    /// 引数を付けた場合は、引数の中身を文字列として出力します。
    /// </summary>
    /// <param name="logMessage">ログ出力したい場合は文字列を指定</param>
    protected void LogCurrentMethod(string logMessage = "") {

        // 1つ前のフレームを取得
        System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(1);
 
        // 呼び出し元のメソッド名を取得する
        string methodName = objStackFrame.GetMethod().Name + "()";

        string msg = "";
        if (logMessage != "") {
            msg = " / [LogMessage] " + logMessage;
        }

        Debug.Log("*** [" + this.GetType().FullName + "] " + methodName + msg + " ***");
    }
}

上記を継承した、先で以下のように呼ぶだけで要件を満たせるようになります。

LogCurrentMethod();  // クラス名とメソッド名だけで良い場合
LogCurrentMethod("hoge"); // 上記と一緒にメッセージも表示したい場合

※usingディレクティブに敢えて「using System.Diagnostics」を指定していません。
指定してしまうと、このクラスを継承したクラスで「Debug.Log」を記載しようとした際に、「UnityEngine.Debug.Log」と指定しないといけなくなるためです。

上記の出力結果は以下です。

*** [AdManager] RequestBanner()***
*** [AdManager] RequestBanner() / [LogMessage] hoge ***

基底クラスが作れない場合は、static methodから呼ぶ

上述の基底クラスをstaticとして抜き出して実現します。
static化するにあたり、基底クラスと違うところは、クラス名をUnityEngineの

this.GetType().FullName

が使えない為、class名に関してもStackFrameから取得するようにします。
今回はUtilityクラスにstaticなログメソッドを宣言します。

class Utility {
    /// <summary>
    /// 現在のClass名, Method名をログ出力します。
    /// 引数を付けた場合は、引数の中身を文字列として出力します。
    /// </summary>
    /// <param name="logMessage">ログ出力したい場合は文字列を指定</param>
    public static void LogCurrentMethod(string logMessage = "") {

        // 1つ前のフレームを取得
        System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(1);
 
        // 呼び出し元のメソッド名を取得する
        string methodName = objStackFrame.GetMethod().Name + "()";

        // 呼び出し元のクラス名を取得する
        string className = objStackFrame.GetMethod().ReflectedType.FullName;

        string msg = "";
        if (logMessage != "") {
            msg = " / [LogMessage] " + logMessage;
        }

        Debug.Log("*** [" + className + "] " + methodName + msg + " ***");
    }
}

利用方法は以下の通りです。

Utility.LogCurrentMethod();

LogメソッドをWarning, Errorにも対応する

更に使い勝手を良くしてみましょう。
これまではInfo系ログだけ用意しましたが、ErrorやWarningも用意します。
上記で作成したメソッドにラッパーメソッドを用意して実現します。
※以下は、staticのケースでコードを載せていますが、基底クラスの場合もやり方は同じです。

public class Utilitys : MonoBehaviour {

    enum LogKind {
        info, warning, error
    }

    /// <summary>
    /// 現在のClass名, Method名をログ出力します。
    /// 引数を付けた場合は、引数の中身を文字列として出力します。
    /// </summary>
    /// <param name="logMessage">ログ出力したい場合は文字列を指定</param>
    public static void LogCurrentMethod(string logMessage = "") {
        Utilitys.OutputLogMethod(LogKind.info, logMessage);
    }

    public static void LogCurrentMethodWarning(string logMessage = "") {
        Utilitys.OutputLogMethod(LogKind.warning, logMessage);
    }

    public static void LogCurrentMethodError(string logMessage = "") {
        Utilitys.OutputLogMethod(LogKind.error, logMessage);
    }

    static void OutputLogMethod(LogKind kind, string logMessage = "") {
        // 2つ前のフレームを取得 (ラッパー経由の為2つ遡る)
        System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(2);
 
        // 呼び出し元のメソッド名を取得する
        string methodName = objStackFrame.GetMethod().Name + "()";

        // 呼び出し元のクラス名を取得する
        string className = objStackFrame.GetMethod().ReflectedType.FullName;

        string msg = "";
        if (logMessage != "") {
            msg = " / [LogMessage] " + logMessage;
        }

        string outputLogString = "*** [" + className + "] " + methodName + msg + " ***";

        switch (kind)
        {
            case LogKind.warning:
                Debug.LogWarning(outputLogString);
            break;
            case LogKind.error:
                Debug.LogError(outputLogString);
                break;
            default:
                Debug.Log(outputLogString);
                break;
        }
    }

}

最後に

2つのやり方を紹介しましたが、全てのクラスがMonoBehaviourを継承出来ない事を考えるとstaticに宣言して利用するのが汎用性が高いかもしれません。

Firebaseのルールを設定する - UnityでFirebaseを使ったオンラインランキングシステムを作るvol4

この記事はシリーズ物です。
シリーズの記事は以下を参照ください。

www.project-unknown.jp www.project-unknown.jp www.project-unknown.jp

はじめに

この記事はFirebase RealtimeDatabaseのルールについて記載します。
ルールについては、Unity以外でも(iOS/Android Native)利用できる所ですので、可能な限りUnity以外でも適用できるように記載します。

本稿は、Firebaseで実際にTwitter認証を用いてデータを登録し、参照出来るようになったのは良いのですが、今のままでは誰でもデータを書き込みできるようになってしまっています。
性善説がまかり通る世の中であれば、特に気にしなくても良いのですが、そうは問屋が卸さないので、適切な権限を付与する必要があります。

Firebase RealtimeDatabaseは、データに対する書き込み権限や、どんなデータを登録できるのか?を「ルール」と言う概念で設定することが出来ます。

ここで紹介するお話は、初見だとかなり「うっ!?」となるかもしれませんが、実際にやってみると直感的で慣れてくると非常に管理しやすいですので、諦めずに頑張りましょう!

今回は、Firebaseのルールを用いてアクセス制限を注目して記載しています。
ルールの機能として、他には値を登録する際のValidate的な使い方もできますが、今回は取り上げません。

ルールを開く

Firebaseのコンソールを開き、プロジェクトを選択して、「Database>ルール」を選択します。

恐らく、デフォルトは以下のようになっているかと思います。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

上記は、「Firebase Authenticationで認証したユーザなら、誰でもデータの読み書きが出来る」の設定がされている状態です。

ルールの説明に行く前に

ルールとデータは表裏一体と考えても良いです。
ですので、ルールを設定する際は、常にデータ構造を意識しましょう。
ここで説明するルールは、前回Firebaseのデータを登録する際に設定した以下のデータ構造を前提に説明していきます。

root/
  UnitySample/
    Ranking/
      uid/
        name
        id
        score
        updatedate

ルールを設定する

細かく説明してもわかりにくいと思うので、今回やりたいルールを一気に設定しちゃいます。
今回は、以下のルールを設定したいと思います。

  • ランキングなので、他の人のScoreは見れるようにする
  • ユーザのScoreは、ユーザ本人だけが更新できるものなので、ユーザのデータへの書き込み権限は本人しか持たない

これのルールを設定すると、以下の通りになります。(見ての通り、ルールはjsonデータとして作成します)

{
  "rules": {
    "UnitySample": {
      ".read": "auth !== null",
      "Ranking": {
        "$user_id": {
          ".write": "auth.uid === $user_id"
        } 
      }
    }
  }
}

では、上記に出てきたものを細かく説明していきます。

rules

これは、これからルールを設定していくと言う宣言となります。
このrulesに囲われた範囲が、ルールについての定義を行っていることを指し示します。

UnitySample

データ構造にある、「UnitySample」に対する、ルールをこれから設定していく事を指し示します。
このようにルールでは、データ構造と密接した宣言になります。
上記より、UnitySample/Rankingに対するルール設定の場合は以下のように記載します。

"UnitySample": {
  "Ranking": {
    
  }
}

".read": "auth !== null"

.readは、読み取り設定のプロパティを指します。この場合は、UnitySampleの読み取り設定は、右辺の「auth !== null」が設定されます。

auth(auth変数)には、データにアクセスした際のFirebaseの認証情報が入っています。
authには、以下のプロパティがあります。

  • auth.provider
    • 認証情報のプロバイダを取得できます。 ("twitter", "fasebook"など)
  • auth.uid
    • ユーザを一意に示すIDを取得できます。主に、以下のキャプチャの部分を指します。

f:id:project-unknown:20170830001015p:plain

今回の場合、「auth !== null」ですので、auth情報を持っているユーザ。つまり何かしらの形でFirebaseの認証が終わっているユーザについては読み取り権限が付与されます。

親要素と、子要素で違う権限を付与したらどうなるの?

例えば、以下の場合はどうなるのでしょうか?

"UnitySample": {
  ".read": "auth !== null"
  "Ranking": {
    ".read": "auth === null"
  }
}

上記設定は、UnitySample以下は、auth認証を必要とするとしていますが、Ranking以下は不要と言う設定になっています。
このようなルールがコンフリクトする場合は、浅いレイヤーのルールが優先されます
なので、上記のような場合は、Ranking以下でも「auth !== null」が適用されます。

余談 「==」と「===」について

言語によっては、「===」や「!==」に馴染みのない方もいらっしゃるかもしれないので、補足すると、

  • 「==」は良く見る「等価演算子」
  • 「===」は「厳密等価演算子」

と呼ばれます。
等価演算子は、例えば

int val = 0;

if (val == false) {
    echo "hoge";
} else {
    echo "fuga";
}

上記の例の場合、出力結果は「hoge」になります。
このように等価演算子は型が異なっても評価を行うことが出来るのに対し、厳密等価演算子を用いた場合

int val = 0;

if (val === false) {
    echo "hoge";
} else {
     echo "fuga";
}

上記のケースでは、出力結果は「fuga」になります。
これは、「val」と「false」が型が違うので、評価対象にならずelseに流れるからです。
なので、通常の等価演算子よりも型を気にしてより厳密に評価を行うので厳密等価演算子と呼ばれています。

"$user_id":

この$user_idは、$location変数と呼ばれる変数です。別に「$user_id」じゃなくて「$hoge」でも良い、好きな名前を付けれます。
$location変数の意味ですが、その階層にあるNodeのKeyが入ります。
今回のデータ構造で言うと、「ユーザID」がここに入ります。
「ユーザID」はユーザ毎に異なるIDですので、$location変数を使って、その後の評価に使えるようにしています。

".write": "auth.uid === $user_id"

.writeは、.readを見てきたので薄々感づかれると思いますが、書き込み権限を表し、右辺の条件が設定されます。

右辺の、"auth.uid"は、上述説明の通り、ユーザを一意に表すIDが入っているんでしたね。
また、$user_idはユーザID、auth.uidと同等の値が入ります。

なので、この条件では、"auth.uid"と$user_idが一致する要素の場合、.write権限が付与されるようになります。

ルールをシミュレートする

さて、ルールの設定が出来ました。
次はうまくルールが効いているのかをシミュレートします。

Firebaseには、ルールが正常に設定されているかを検証するシミュレータが備わっています。 (以下キャプチャ参照)

f:id:project-unknown:20170830002155p:plain

ここで、実際に読み書きが出来るのかを確認できます。

読み取りをシミュレート

確認するのは、自分含めて、他のユーザのデータを読み込みが出来るか?です。
まずシミュレーションタイプを「読み取り」にします。

f:id:project-unknown:20170929010510p:plain

次に、ルート「/」且つ認証していない状態でアクセス出来るか確認します。
ここでの期待値は、ルートへのアクセスのルールは設定していないのでエラーになる事を期待します。

ロケーションを「/」に、認証済みのチェックを外して実行を押します。

f:id:project-unknown:20170929011519p:plain

画面の上部に「シミュレートされた read が拒否されました」と表示されれば成功です。

f:id:project-unknown:20170929010806p:plain

次に、認証済みにチェックを入れます。
プロバイダを選択する項目があるので、これまでのシリーズでTwitter認証でやってきましたので、Twitterを選択。

UIDの部分はご自身が登録した、Authenticationをコピペしてください。

f:id:project-unknown:20170830002717p:plain

これで実行しましょう。

f:id:project-unknown:20170929010806p:plain

エラーが起きましたね。これで問題ありません。
先程も記載しましたが、読み取りの設定が入っているのは、認証ユーザ且つ、UnitySample以下に対して読み取り出来るようにしているからです。

では、次に読み取り出来るはずの、UnitySampleのシミュレートをしてみましょう。

ロケーションに「/UnitySample」と入力し、実行ボタンを押します。

f:id:project-unknown:20170929011758p:plain

無事シミュレート成功しました。
念のため、自分のデータが見れるかまで見ておきましょうか、
ロケーションを「/UnitySample/Ranking/{$UID}/score」として、実行ボタンを押します。 ({$UID}の部分は登録したUIDを入力してください。)

f:id:project-unknown:20170929011625p:plain

こちらも問題なく読み取りが出来ることが確認できました。

読み取りは想定通り設定出来ているので、書き込みを見てみます。

書き込みをシミュレート

確認するのは、指定されたユーザのデータへ書き込みが出来るか?です。

まず、シミュレーションタイプを「書き込み」にします。

f:id:project-unknown:20170830002437p:plain

次に、ロケーションを、「UnitySample/Ranking/ユーザID」にします。
(ユーザIDはAuthenticationのUIDを記載してください)

データは、デフォルトである、以下で十分だと思います。

{
  "key": "value"
}

プロバイダは、認証済みにチェックを入れ、読み取り同様Twitterとします。

UIDも、先程と同じく、AuthenticationのUIDを記載します。

f:id:project-unknown:20170830002717p:plain

ここまで設定したら実行ボタンをクリックしてください。

「シミュレートされたwriteが許可されました」と表示されれば成功です。

f:id:project-unknown:20170830002824p:plain

また、ちゃんと他のUIDだと弾かれるか確認します。

プロバイダのUIDを適当な文字列に変更します。

f:id:project-unknown:20170830002951p:plain

この状態で、実行を押すとエラーが発生しています。

f:id:project-unknown:20170830003032p:plain

これで、他のユーザへの書き込みは出来ないことまで確認できました。

さいごに

今回の記事は、ただ読むだけだとイメージが湧きにくいかもしれませんが、
実際にご自身で触りながら読んでいただくと、そこまで難しく無いかと思います(冒頭でも記載しましたが、ルールは直感的に設定できるので)
逆に言うと、これだけの設定でそこそこ強固なアクセス制限等を掛けられるので、是非ものにしましょう!

Unity Cloud Buildでエラーになった際の解決法 vol3

はじめに

今回の記事で三回目になりますが、Unity Cloud Buildで盛大に詰まった(1週間程このせいで開発が止まった)際の解決方法を記します。

本記事は、UnityにFirebaseとTwitter Kit for Unity, Admobを導入した際に出くわした内容ですので、記事で出て来るエラー内容はこれに付随しています。

が、対応方法を見ていただくと分かる通り、この例以外でも役に立つ方法ですので、同様のエラーに苛まれた際のお役に立てればと思います。

エラーとその解決方法

ファイル名に「error」が付いているとエラーに見える

事象

具体的には、以下のようなログです。

3: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkit.framework/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@2x.png 
4: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkit.framework/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@3x.png 
5: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@2x.png 
6: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@3x.png 

実際に画面上だと、本当にエラーのように見えます。

f:id:project-unknown:20170907012001p:plain

原因と解決策

これはUnity Cloud Buildが失敗しているのと何ら関係ありません
ログのサマリが「error」という文字に反応して色味を付けているだけです。
私自身、これを疑い該当ファイルを消してbuildしてみたりしたのですが、上述通りbuildが失敗するのと関係ないので勿論コケます。

これについては、forumでも中の人が関係ないと断言しています

なので、解決策は何もする必要が無いです。

Google産SDKで必要な設定ファイルが無い

事象
 [Unity] Player export failed. Reason: No GoogleService-Info.plist files found in your project.
原因と解決策

これはCloud Buildじゃなくて実機テストでも発生するものです。
タイトル通り、必要な設定ファイルがありません。
上記エラーが出た際は、Firebaseで発行するGoogleService-Info.plistファイルをUnityに突っ込み忘れたのが原因です。

なので、解決策は、FirebaseやAdMob等Google産SDKの場合の設定ファイルをちゃんとUnityに入れる事です。

shader snippetでエラー

事象
[Unity] Shader compiler: internal error compiling shader snippet type=0 platform=5: Protocol error - failed to read correct magic number
原因と解決策

OnGUI等、古いAPIを使っていると発生し易いみたいです。
これについては、再度Buildを掛けると直ります。
(何度かお目にかかっているのですが、直接的な発生原因は不明、OnGUIを使った実装を避けるくらいですかね

Twitter KitでLogErrorが出ている。

事象
[Unity] TwitterKit.Internal.Utils:LogError(String) (at Assets/Twitter/Scripts/Utils.cs:33)
原因と解決策

ポカミスが原因

私が出くわしたTeitter Kit for Unity限定かもしれませんが、サマリログだとこの部分しかログが落ちていません。
この場合は、実際にSDKの中を読み込んでこのログが吐かれるきっかけを見たほうが早いです。

以下VSCodeの該当箇所を見た結果、(参照されている所が直ぐ追えるのが本当に便利…)

f:id:project-unknown:20170907013414p:plain

ここで分かるのは、Twitter Kit for Unityの設定にAccessTokenを仕込んでいない事が伺えます。
実際にProjectを作り直した直後で、たしかに仕込んで居なかったので、AccessToken等を埋め込むことで、このエラーは出なくなりました。

Bundle IDでエラー

事象
[Unity] Player export failed. Reason: Project Bundle ID {Bundle ID} does not match any bundle IDs in your GoogleServices-Info.plist files
原因と解決策

ポカミスが原因

これは、Firebaseで設定した際のBundile ID(アプリのID)と、Unity Cloud Buildに設定したBundle IDに差異がある時に発生するエラーです。
IDを揃えることで発生しなくなります。 (ポカミス多い…)

iOSのバージョンがサポートしていない

事象
73:        [xcode] /BUILD_PATH/{BUILD_PATH}/temp.44tpIu/Frameworks/Plugins/iOS/TwitterCore.framework/Headers/TwitterCore.h:24:2: error: "TwitterCore doesn't support iOS 7.x and lower. Please, change your minimum deployment target to iOS 8.0"
74:        [xcode] #error "TwitterCore doesn't support iOS 7.x and lower. Please, change your minimum deployment target to iOS 8.0"
75:        [xcode] /BUILD_PATH/{BUILD_PATH}/temp.44tpIu/Frameworks/Plugins/iOS/TwitterKit.framework/Headers/TwitterKit.h:15:2: error: "TwitterKit doesn't support iOS 8.x and lower. Please, change your minimum deployment target to iOS 9.0"
76:        [xcode] #error "TwitterKit doesn't support iOS 8.x and lower. Please, change your minimum deployment target to iOS 9.0"
原因と解決策

エラーの内容通り、TwitterKitがiOS9以上のAPIを使っているため、UnityのProject設定をiOS9以上にしないといけません。
このエラーを引き起こした際に、UnityのProject設定のiOS SDKの最低Versionを7.0にしていたのが原因でした。
こちら、最低Versionを9.0にすることでかいけつ出来ます。

linker command faild

事象
[xcode] clang: error: linker command failed with exit code 1 (use -v to see invocation)
原因と解決策

iOSでアプリ開発している人ならお馴染みで、たまにドツボに入るエラーです。
この手のエラーは利用しているライブラリのPathが足りていなかったりする時に発生し易いのですが、確実にこれじゃないのが辛い所です。
ましてや、Unity Cliud Buildは手元のXCodeじゃないので尚更調査し辛い所があります。

この手のエラーは汎用的なエラーであるために、このエラーを見ただけだと何が原因か推測し辛いので、Buildログを見て、何処らへんでエラーが発生したのかを見てアタリを付けていきます。

サマリログの一番したに詳細のログを見るボタンがあるので、これをクリックして中身を見てみます。

f:id:project-unknown:20170907013201p:plain

遷移先の詳細ログで、該当する箇所を検索します。

22370:        [xcode] ld: warning: directory not found for option '-L /BUILD_PATH/Library/Developer/Xcode/DerivedData/Unity-iPhone-gnshgjhnidtrmtbkltwpchzfnjbj/Build/Intermediates/ArchiveIntermediates/Unity-iPhone/BuildProductsPath/Release-iphoneos/GTMSessionFetcher /BUILD_PATH/Library/Developer/Xcode/DerivedData/Unity-iPhone-gnshgjhnidtrmtbkltwpchzfnjbj/Build/Intermediates/ArchiveIntermediates/Unity-iPhone/BuildProductsPath/Release-iphoneos/GoogleToolboxForMac /BUILD_PATH/Library/Developer/Xcode/DerivedData/Unity-iPhone-gnshgjhnidtrmtbkltwpchzfnjbj/Build/Intermediates/ArchiveIntermediates/Unity-iPhone/BuildProductsPath/Release-iphoneos/nanopb'
22371:        [xcode] ld: warning: directory not found for option '-F /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseAnalytics/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseAuth/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseCore/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseInstanceID/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/Google-Mobile-Ads-SDK/Frameworks/frameworks'
22372:        [xcode] ld: library not found for -lz}
22373:        [xcode] clang: error: linker command failed with exit code 1 (use -v to see invocation)

どうやら、Firebase SDKがCocoaPodsを経由して「Firebase/Core」等を入れようとしたり、AdMobが「Google-Mobile-Ads-SDK」をCocoaPodsを用いて導入しようとしているのですが、それがうまく言っていないのか、FirebaseやAdMobが必要としているライブラリのリンクが貼れなくてエラーになっているみたいです。

ここでCocoaPodsについて軽く説明すると、
CocoaPodsはiOSエンジニアだと、結構当たり前に使うツールで、簡単に言うとOSSのパッケージ管理ツールです。
通常だと、OSSなどはGitや配布されているファイルを自分でDLしてXCodeに突っ込む等の作業が必要なのですが、CocoaPodsはそこら辺のOSSのinstallをサポートしてくれます。
また、OSSのVersion管理もしてくれて、OSSのVersionが上がったらそのinstall支援もしてくれる優れものですし、そのOSSが動くために必要な為の他のOSSも、ツールの作成者が登録してくれていれば自動でinstallしてくれます。私とかは無いと生きていけません。
FirebaseやAdMobもその恩恵にあやかってCocoaPodsを採用しているのでしょうね。

さて、本題に戻します。
ココらへんを見る限り、現在Unity Cloud BuildはCocoaPods経由でライブラリは入れられないっぽいです。
AdMobやFirebaseのSDKをCocoaPods経由でも入れられるようにupdateするみたいですね。
なので、現状SDKがCocoaPodsからライブラリを入れようとする所を止めて、手動でライブラリを入れる必要があります。

まず、CocoaPodsを使おうとしているコードを特定します。
私の場合は、FirebaseとAdMobが該当するので、以下、ファイルと修正内容を記します。基本は該当する所全てをコメントアウトします。(コードは編集後のコードを載せてます)

AdMobDependencies.cs

#elif UNITY_IOS
        // Type iosResolver = Google.VersionHandler.FindClass(
        //     "Google.IOSResolver", "Google.IOSResolver");
        // if (iosResolver == null) {
        //     return;
        // }
        // Google.VersionHandler.InvokeStaticMethod(
        //     iosResolver, "AddPod",
        //     new object[] { "Google-Mobile-Ads-SDK" },
        //     namedArgs: new Dictionary<string, object>() {
        //         { "version", "7.13+" }
        //     });
#endif  // UNITY_IOS

AppDeps.cs

#elif UNITY_IOS
        // Type iosResolver = Google.VersionHandler.FindClass(
        //     "Google.IOSResolver", "Google.IOSResolver");
        // if (iosResolver == null) {
        //     return;
        // }
        // Google.VersionHandler.InvokeStaticMethod(
        //     iosResolver, "AddPod",
        //     new object[] { "Firebase/Core" }, 
        //     new Dictionary<string, object>() { 
        //         { "version", "4.1.0" },
        //         { "minTargetSdk", null },
        //         { "sources", null }
        //     });
#endif

AuthDeps.cs

#elif UNITY_IOS
        // Type iosResolver = Google.VersionHandler.FindClass(
        //     "Google.IOSResolver", "Google.IOSResolver");
        // if (iosResolver == null) {
        //     return;
        // }
        // Google.VersionHandler.InvokeStaticMethod(
        //     iosResolver, "AddPod",
        //     new object[] { "Firebase/Auth" }, 
        //     new Dictionary<string, object>() { 
        //         { "version", "4.1.0" },
        //         { "minTargetSdk", null },
        //         { "sources", null }
        //     });
#endif

次に必要となるライブラリを入れます。
何が必要になってくるのかは、コードから判断します。
例えば、AppDepsを例に取ると、

new object[] { "Firebase/Core" }, 

この部分がCocoaPodsから入れたいライブラリ名となっています。
上記などを参考に必要なファイルを入れます。
この例の場合は、以下からライブラリを入手します。

Firebaseのセットアップのサイトの、CocoaPodsを使用せずに結合する項目から、「フレームワーク SDK の zip ファイルをダウンロードします」のリンクをクリックして、zipファイルをダウンロード。

f:id:project-unknown:20170907020639p:plain

ダウンロードしたzipファイルを解凍し、必要なFrameworkを探し出します。
まず上記のFirebase/Coreに当たるものを探すと、「Analytics / FirebaseCore.framework」が見つかります

f:id:project-unknown:20170907020841p:plain

このFirebaseCore.frameworkを、Unityの「Plugin/iOS」以下に突っ込みます。

また、今回私の場合、Firebaseの以下の機能を使おうとしています。

  • Auth(Authentication)
  • Database(RealtimeDatabase)

なので、このライブラリも探します。

f:id:project-unknown:20170907021029p:plain

f:id:project-unknown:20170907021043p:plain

上記が見つかりました。
キャプチャに出ている、以下のライブラリは全てUnityの「Plugin/iOS」以下に突っ込んでください。

  • FirebaseAuth.framework
  • GTMSessionFetcher.framework
  • FirebaseDatabase.framework
  • leveldb-library.framework

コメントアウトしたコードには、Authとかしか無いじゃん!とお思いかもしれませんが、上述の通り、CocoaPodsはinstallしようとするライブラリが必要なライブラリを勝手にinstallしてくれます。なので、FirebaseAuth.frameworkをCocoaPods経由でinstallしようとしたら、GTMSessionFetche.frameworkはCocoaPods側で勝手にinstallしてくれます。
今回は手動でライブラリを入れようとしているので、上記4種類のライブラリを入れる必要があります。
(Firebaseの場合は、Core以外、どういつディレクトリ内のライブラリが依存関係にあるように配置されているので非常にわかりやすくなっています。)

後は、AdMobのライブラリを突っ込む作業です。

FirebaseのライブラリからAdMobを入れた場合はzipファイルの中のAdMobフォルダ内に該当するライブラリがあるのでそれを突っ込めば良いのですが、私の場合は別途AdMobのサイトからUnity用SDKを導入しているので、念のためそちらのファイルからライブラリを取得します。
落としてきたzipに「GoogleMobileAdsSDK.framework」があるので、それをこれまでと同様に「Plugin/iOS/」以下に突っ込みます。

最終的に私のPlugin/iOS以下は下のキャプチャの様になりました。

f:id:project-unknown:20170907022125p:plain

まだここで終わりではありません

GoogleなどのSDKは、Objective-Cの「@import」を利用しています。
これについて詳細に記載すると、別途記事を起こすだけの分量になるのでかいつまんで説明すると、C#等でコードを書く時に

using UnityEngine;

とか書くと思うのですが、毎回これを書くのが冗長な為、「@import」で設定されたフレームワークは自動読み込みされるようにでき、このお陰で毎回必要なフレームワークをプロジェクトの設定でimportしなくても済む機能です。

この「@import」機能ですが、古いXCodeだとデフォルト設定で利用できたのですが、、最新のXCodeだとデフォルトではOFFになっています。

これまで投入してきたライブラリは「@import」の機能を利用しているため、これを使えるようにしてあげる必要があります。

やりかたは、Assets/Editor以下のどこでも構いませんので、「XcodePostProcessBuild.cs」のファイルを使って以下のコードを記載してください。

using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

public static class XcodePostProcessBuild {
    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget target, string path) {
        if (target != BuildTarget.iOS) {
            return;
        }

        var xcodeProject = new PBXProject();
        xcodeProject.ReadFromFile(PBXProject.GetPBXProjectPath(path));

        var projectGuid = xcodeProject.TargetGuidByName(PBXProject.GetUnityTargetName());
        xcodeProject.SetBuildProperty(projectGuid, "CLANG_ENABLE_MODULES", "YES");

        xcodeProject.WriteToFile(PBXProject.GetPBXProjectPath(path));
    }
}

これで、先程記載した@import機能が使えるようになります。

長くなったので纏めると

  • この手のエラーはログの詳細を読み込んで、前後のエラーでアタリを付ける
  • 今回の場合の対応は
    • CocoaPodsがやってくれる作業を手動でやる
      • CocoaPods経由でinstallしようとしているライブラリのコードを消す
      • 必要なライブラリを自分で入れる
      • XCodeの設定を自前で設定する

さいごに

Unity Cloud Buildは非常に便利な反面、一度詰まってしまうと凄い時間がかかります。
ただ、それを鑑みても恩恵がでかすぎるので是非とも利用していきたい所。
私と同様のエラーで躓いてしまうのは非常にもったいないので、同様の所で躓いてしまった場合はその助けになれば幸いです。

ちなみに、私は最終的にUnity Cloud Buildのbuildを通すまでに冒頭で申し上げた通り1週間位かかっています。

以下実行履歴 (途中Projectを作り直しているので、20回以上はbuild挑戦しています。無料プランなので1buildが終わったら1時間のクールタイムが発生するので、単純計算でも20時間はかかってます(;´Д`))

f:id:project-unknown:20170907023645p:plain


番外編

実機でTwitterのSession周りでエラーが起きている

現象
(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

NullReferenceException: A null value was found where an object instance was required.
  at TwitterKit.Unity.Twitter.get_Session () [0x00000] in <filename unknown>:0 
 
(Filename: currently not available on il2cpp Line: -1)
原因と解決策

Twitter InitのScriptをアタッチしていない。
これ本当に忘れやすいので注意です。

過去記事

www.project-unknown.jp

www.project-unknown.jp

www.project-unknown.jp

FirebaseとAdmobをUnity上に共存させて盛大に詰まった時の対処法

はじめに

標題の通りです。Firebaseでシステムを作ったのは良いけど、AdMobの設定が終わっているUnityのProjectに持っていったら盛大にエラーが出まくって詰みまくった時にどう解決したか、をレポートしていきます。

AdMobについては至る所で説明されているので割愛させていただきます。
Firebaseについては、本ブログで色々と記事を記載していますので、そちらを御覧ください。

www.project-unknown.jp

事象

AdMobとFirebaseをUnity上で同居させると、Editorエラーに始まりスクリプトエラーやらマニュフェストがどうたらとか、、、もちろんゲームを実行させることも出来ません。
(あまりのエラーの多さにキャプチャやらログを控えるのを諦めてしまった(;・∀・)

原因

心をポッキリ折ってくれた詰みポイントを交えて、原因を紹介します。

エラーの内容が多すぎ(詰みポイント1)たのですが、私が解決した際の助けになったエラーログが以下です。

TypeLoadException: Could not load type 'GooglePlayServices.UnityCompat' from assembly 'Firebase.Editor'. 

↓を見る限りだとどうもPlayServicesResolverを常に最新にしないと行けないとなっています。

github.com

PlayServicesResolverはGoogleから提供されるPackageに大体梱包されています。

が、どれが最新かわかりません。(詰みポイント2)

また、PlayServicesResolverはUnityの不具合なのかわかりませんが、Project Viewを綺麗にしようとして、別フォルダ等に移動していたら、同一ファイルと認識しません(詰みポイント3)

と言うか、Firebaseから入ってくるPlayServicesResolverはファイル名にバージョンが示されており、またAdMobから入ってくるPlayServicesResolverはファイル名にバージョンが入って居ないため、やっぱりどれが最新かわかりません(詰みポイント4)

解決策 (2017/9/3時点)

FirebaseとAdMobの共存で不具合が起きた場合は大体がPlayServicesResolverに行き着くと思います。
対応方針は、最新のPlayServicesResolverにすることです。

2017/9/3時点では、Firebaseから導入するPlayServicesResolverの方が最新でしたので、こちらを採用しました。(と言うか2つとも試して、エラーにならない方を採用した)

なので、現状私の構成は以下の通りです

f:id:project-unknown:20170903211533p:plain

参考

blog.naichilab.com

UnityでFirebaseのRealtimeDatabaseとデータのやり取りをする - UnityでFirebaseを使ったオンラインランキングシステムを作るvol3

この記事はシリーズ物です。
シリーズの記事は以下を参照ください。
特に本稿は、以下の2つの記事を読み終えている事前提で記載していますのでご注意ください。

www.project-unknown.jp www.project-unknown.jp www.project-unknown.jp

https://assetstore.unityhttps://assetstore.unity.com/packages/3d/environments/fantasy/alchemist-s-house-interior-47318.com/packages/3d/environments/fantasy/alchemist-s-house-interior-47318

はじめに

今回は、FirebaseのRealtimeDatabaseとのデータのやり取りを行います。
これまで、Twitter認証・Firebase認証とやってきて、やっとランキングのデータのやり取りを行います。
試行錯誤した結果を記載していますので、もし手順などの漏れで動かない等御座いましたら、@YuwUnknownまでご連絡ください。

注意

アクセス制限について

今回の記事では、Debug・実装のやりやすさから、FirebaseのRealtimeDatabaseに対しての読み書きの制限を行いません。
実際に運用する際には、セキュリティの観点からも、読み書きの制限を行うことを強くお勧めします
また、読み書きの制限については、次の回で説明する予定です。

データ通信のやり取りについて

今回の記事は、Firebaseの無料の範囲内で記載しています。
無料で出来る範囲は、公式の料金表に詳細が記載されています。

料金表に載っていない突っ込んだ情報は、以下の記事で実際にヒアリングした結果をレポートしていますので、ご確認ください。

www.project-unknown.jp

今回のランキングシステムは、データ量もさほど多くないですし、スコアを見る時だけ接続する等を実施すれば無料の範囲内で十分に使えるはずです。
またFirebaseのRealtimeDatabaseはローカルにキャッシュし、データ更新があれば実際にデータのやり取りを行う優れた機能ですので、そこまで気にする必要は無いかもしれません。

本稿の構成

以下で進めます。

  • FirebaseのRealtimeDatabaseにスタブデータ(自前で用意したデータ)を登録して、Unityから参照できるようにする
  • UnityからRealtimeDatabaseに書き込みをする

では行きます。

FirebaseのRealtimeDatabaseにスタブデータ(自前で用意したデータ)を登録して、Unityから参照できるようにする

Firebaseコンソールにスタブデータを登録し、ルールを編集して簡単にアクセスできるようにする

FirebaseのRealtimeDatabaseを見てみる

まずは、実際にランキングデータを格納することになる、FirebaseのRealtimeDatabaseを見てみます。
Firebase Consoleのプロジェクトに移動して、以下のキャプチャが示す所を見てください。

f:id:project-unknown:20170830222145p:plain

これが実際にデータを格納する所です。最初のデータが無い状態だとrootを指し示すものだけが表示されていると思います。
今開いているのが、データを弄るエディタにもなっているので、実際にデータを手入力で入れてみます。

RealtimeDatabaseにスタブデータを登録する

rootノードをマウスオーバーすると、「+」が出て来るのでクリックします。

f:id:project-unknown:20170831230219p:plain

すると、子要素を入れれるようになるので、ここにデータを追加していきます。

f:id:project-unknown:20170831230304p:plain

ここでは、子要素を更に入れ子にしたいので、「名前」のところだけ入力して、更に「+」ボタンを押します。

f:id:project-unknown:20170831230433p:plain

最終的に、以下のように入力していきます。

f:id:project-unknown:20170831230734p:plain

それぞれの要素の説明は以下です。

  • UnitySample
    • Firebaseでは複数のProjectをぶら下げる事が出来ますし、見て分かるように階層ごとにデータの意味合いをもたらして管理することも出来ます。ここではUnitySampleという名前のProjectという意味での識別子としています
  • Ranking
    • Rankingデータをこの要素の以下にぶら下げるように作っています
  • uid
    • ユーザを一意に特定する為のものです。TwitterのIDでも良いのですが、ここではFirebaseのAuthenticationが発行するuidを入れる用途にしています。
    • 上述の通り、このuidはユーザ毎のrankingデータを格納する為に用意しています
  • name
    • ユーザ名です。Twitterから取得します
  • id
    • ユーザIDです。Twitter IDを入れます
  • score
    • ユーザのScoreです。
  • updatedate
    • データの更新日を入れておきます。

これで、スタブデータを入れることが出来ました。
ここまでデータを入れてきておわかりの通り、FirebaseのRealtimeDatabaseはJSONライクのNoSQLっぽいデータの取扱となります。
SQL周りを知らなくてもなんとかなると言うのが気軽に使えて便利ですね。
ただ、その分DBMSでよく使うリレーションシップ周りのデータ管理をしようと思うと、自力でデータ構造を設計してあーだこーだ考えないといけなくなるので、そこはデメリットになるかも…。

RealtimeDatabaseのルールを編集する。

冒頭で記載しました通り、Firebaseへのアクセスに制限を加えることができます。
デフォルトでは、Authenticationの認証が終わっていないとアクセスする事ができないので、今回の説明を行う上でちょっと不便となるので、アクセス制限を開放します。
ルールについては、次回に詳細を書く予定ですので、本番リリースする際は、確実に適切なルール設定をお願いします
というのと、アクセス制限を開放するのは、テスト時であっても危険ですので、出来ることなら、開発が一段落したらルールを設定して制限するなど自衛を行うように出来ると良いですね。

まず、Database > ルールでルール画面を開きます。

f:id:project-unknown:20170831232437p:plain

恐らくデフォルトでは、以下が設定されていると思います。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

この状態だと、Firebaseの認証しているユーザのみ読み書きが出来る状態となっています。
これを以下に置き換えてください

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

これで、全てのユーザが読み書き出来るようになります。

UnityからFirebaseのRealtimeDatabaseとデータのやり取りを行う

UnityからFirebaseのRealtimeDatabaseのデータを参照する

前提として、FirebaseSDKがimportされている必要があります。
まだの方はVol2の記事に導入の仕方を載せてますので、importをお願いします。

さぁいよいよRealtimeDatabaseとUnityとを接続させます!

ここでの例は、Firebase Realtime Databaseにあるscoreが一番高いデータを取得する。を目標に実装を行います。

UnityからFirebaseへ接続する設定は、ここまでで全部終わっているので、コードをゴリゴリ書くだけです。

説明は後ほど記載しますので、まずは完成形のコードを記載します。

前回のFirebaseへTwitterアカウントでログインする際のコードを拡張します。

gist.github.com

では、以下に今回追加分になったコードの説明を記載していきます。

コードの詳細

フィールド

フィールドには、以下を追加しています。

// Firebase
private DatabaseReference _FirebaseDB;
private Firebase.Auth.FirebaseUser _FirebaseUser;
  • _FirebaseDB
    • Firebase RealtimeDatabaseへ参照を持ったObjectを保持します。(参照の仕方は後述)
    • 以後は、このObjectに対して読み書きの処理を行えば、適切なタイミングでデータを引っ張り出してくれたり、書き込みの通信を行ってくれるようになります。
  • _FirebaseUser
    • 前回の記事に載せるのを失念していたのですが、Firebaseに接続した際の認証情報を保持し、Firebaseとユーザ認証周りでやり取りする際には、このObjectに保持されている情報を利用します
Startメソッド

Startメソッドには、以下を追加しています。

// Firebase RealtimeDatabase接続初期設定
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("Firebase Realtime Database URL");

Firebaseの接続設定を行います。
中身まで読み取っていないですが、シングルトン設計になっているっぽくて、引数に参照先のURL(Firebase Realtime Database URL)を記載する事で、以後RealtimeDatabaseの参照先はそのURLを見に行くようになります。
URLは、以下のキャプチャの場所を参照して入力してください。

f:id:project-unknown:20170902125359p:plain

// Databaseの参照先設定
_FirebaseDB = FirebaseDatabase.DefaultInstance.GetReference("UnitySample/Ranking");

フィールドにある_FirebaseDBに、どの要素を参照するのかを保持します。
冒頭で作成したスタブデータの、

f:id:project-unknown:20170831230734p:plain

Ranking以下を参照して行きたいので、「UnitySample/Ranking」と記載します。
このように階層は「/」区切りで指定していけば、該当の要素まで一気に絞って参照することができます。

もちろん、「UnitySample/Ranking」ではなく「UnitySample」としても良いのですが、
そうすると、今回必要なのはRanking以下のデータなのに、UnitySampleにぶら下がった全てのデータを取得するようになり、データ通信的に無駄になりますし、後の処理も一々Ranking以下を参照するコードを書かないといけなくなり冗長となってしまいます。
この例の用に、参照する要素が特定出来るのであれば、参照先は一気にその場所まで指定してしまった方が良いです。

FirebaseLoginメソッド

このメソッドは殆ど変更はありません。
認証が完了した後の、以下2行を追加しています。

_FirebaseUser = task.Result;

// 認証完了したらデータを読み込む
this.GetRankingData();

最初の_FirebaseUserに、task.Resultを突っ込んでいるのは、後ほどFirebase認証情報を利用したいので、保持するようにしています。
二つ目の、this.GetRankingData()が、後述するランキングデータを読み込むメソッドを認証が完了した際に実行しようとしています。

GetRankingDataメソッド

今回の肝となる部分です。
ここでは、先程Firebase参照Objectである、_FirebaseDBを用いて実際にデータを取得しています。

_FirebaseDB.OrderByChild("score").LimitToLast(1).GetValueAsync().ContinueWith(task => {
});

ここで、Firebaseからscoreが1番高いユーザのデータを1件だけ取得する。
事を実現しています。

GetValueAsync

GetValueAsyncを使う事で、指定されたPath(UnitySample/Ranking)を1度読み込む事が出来ます。
読み込んだデータはFirebaseでスナップショットと呼ばれます。
なので、データを取得するだけであれば、以下でも十分です。

_FirebaseDB.GetValueAsync().ContinueWith(task => {
});

ただ、これだと「UnitySample/Ranking」パス以下のデータを全て取得してしまうので、不要なデータも受け取ってしまい通信量が増えますし、その後の処理も要るデータ/要らないデータとを分けて処理を行う必要が出てきてしまうため、非常に無駄な事をしてしまいます。
ですので、後述にある通り、ある程度必要なデータを絞って取得するようにします。

今回の例では、1度読み込むだけにしていますが、Realtime Databaseの旨味である、データに変更があったら通信を行うことも出来ます。
これについては別途記事にします。

OrderByChild

SQLを利用した事がある方だとなんとなく意味が理解できそうですが、これで結果を並べ替えることが出来ます。

  • OrderByChildは指定した子キーの値で結果を並べ替える事ができます。
    • また、子キーは指定したPath直下じゃなくても、子の子でも指定できます。
      • 今回の場合は、「UnitySample/Ranking」配下の「uid/score」で並べ替えを行います。
  • OrderByChild("score")とすることで、scoreを昇順に並び替えます。
  • OrderByChildは降順には並び替えることが出来ません。
    ただ、今回はランキングが一番高いユーザ情報を取得したいので、降順なデータが欲しい所。
    そんなときは、以下の、LimitToLastを使って降順なデータ取得を実現します。

データの並び替えには、他にも以下の機能がサポートされています。

  • OrderByKey()
    • 子キーで結果を並べ替える
  • OrderByValue()
    • 子の値で結果を並べ替えます
LimitToLast

これは、OrderByChild等で並び替えられたデータを、末尾から返却する最大数を設定します。
なので、OrderByChildで降順に並び替えを行い、LimitToLastで最後の方のデータを取得する事で降順が実現できます。
以下は具体例です。

// 元のデータの並び
1,3,2,6,5,4,8,7,9

// OrderByChildを掛けた後
1,2,3,4,5,6,7,8,9

// LimitToLast(2)で最後から2番目まで取得
8,9

// これをローカルで大きい順に並び替える
9,8

本当だったら、OrderByChildDeskとかあればいいんですけどね…。
LimitToLastと似た機能として、以下が提供されています。

  • LimitToFirst()
    • LimitToLastの反対で最初の方からデータを取得します
  • StartAt()
    • 指定したキーまたは値以上のデータを取得します
  • EndAt()
    • 指定したキーまたた値以下のデータを取得します
  • EqualTo()
    • 指定してキーまたは値に等しいデータを取得します
まとめると
_FirebaseDB.OrderByChild("score").LimitToLast(1).GetValueAsync().ContinueWith(task => {

これは、
scoreを降順で並び替えたデータの最後から1つ目までのデータを取得する
という事を行っています。

DataSnapshot snapshot = task.Result;

上述していますが、Firebaseで取得したデータをshapshotと呼称しており、
取得した結果(task.Result)をDataSnapshot型のオブジェクトに突っ込みます。

IEnumerator result = snapshot.Children.GetEnumerator();

snapshotに含まれる結果セットをIEnumeratorに一度突っ込み、後のデータを取り出しやすくします。

while (result.MoveNext())

ここで、取得した結果セットをあるだけwhileを回し、データの中にアクセスします。
このwhileの中で、Rankingの子要素にアクセスして、データを受け取っています。

駆け足でしたが、ここまででデータの取得処理が完了です。
一度実行してみましょう。

UnityEditor上だと、Firebaseの接続がうまくいかない事があるので、実機で確認します。
以下は、XCode上からiPhoneで起動してDebugLogの中身を表示しています。

f:id:project-unknown:20170902211227p:plain

無事にデータを取得する事が出来ています。
ただ、スタブデータを1件しか登録していなかったので、OrderByChildやLimitToLastが効いているかわかりにくいので、もう1つデータを登録します。

以下のように、uid2にscoreが200のユーザを追加します。

f:id:project-unknown:20170902211519p:plain

これで再度実行してみましょう。

f:id:project-unknown:20170902211624p:plain

無事取れていますねヽ(=´▽`=)ノ

さて、ここまででFirebase Realtime Database上にあるデータを取得するところまで出来ました。
次は、スタブデータでは無くて、実際にデータを登録します。

UnityからRealtimeDatabaseに書き込みをする

データ取得と同様、Firebaseの設定は終わっているので、コードを書いていきます。
これまでのコードに処理を書き足します。
まずは、最初に完成形のコードをお見せします。

gist.github.com

処理の流れを以下の通りに変えています。

before

  1. Teritterログイン
  2. Firebaseログイン
  3. Realtime Databaseからデータ取得

after

  1. Twitterログイン
  2. Firebaseログイン
  3. Realtime Databaseへデータ書き込み
  4. Realtime Databaseからデータ取得

細かい処理の流れの変更点は上記コードを見てもらうこととして、今回の肝であるデータを追加しているロジックを見ていきます。

WriteNewScoreメソッド

引数に渡されたScoreをFirebase Realtime Databaseへ書き込みに行く用途のメソッドです。
データはDictionary形式でデータ構造を作成します。

しつこいかもしれませんが、今回作成するデータ構造は以下の通りでしたね。

f:id:project-unknown:20170831230734p:plain

「_FirebaseDB」に「UnitySample/Ranking」までのPathを参照するようになっているので、それ以下のデータを作成していきます。

まず、最下層に位置する「name」「id」「score」「updatedate」のデータセットを作成します。

Dictionary<string, object> itemMap = new Dictionary<string, object>();
itemMap.Add("name",       _FirebaseUser.DisplayName);
itemMap.Add("id",         _UserName);
itemMap.Add("score",      score);
itemMap.Add("updatedate", System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));

前回の記事でも書きましたが、Display NameはTwitter Kit for Unityではサポートしていないので、Firebase認証情報から取得するんでしたね。

次に、「uid」部分のデータ・セットを造ります。

Dictionary<string, object> map = new Dictionary<string, object>();
map.Add(_FirebaseUser.UserId, itemMap);

冒頭でも記載しましたが、uidには、FirebaseのAuthenticationが発行するuidを登録したいので、「_FirebaseUser.UserId」からデータを取得しています。
そして、uidをkeyに作成した「name」「id」「score」「updatedate」を纏めたitemMapをvalueに登録します。

最後に

_FirebaseDB.UpdateChildrenAsync(map);

上記、Firebaseへ参照を持ったオブジェクトの、UpdateChildrenAsyscメソッドに、作成したデータをセットすれば、「UnitySample/Ranking」以下にデータを登録できます。

余談

ここまで記載した通り、Dictionary形式でデータセットを作成し、階層構造のデータをつくる分には分かりやすいのですが、毎回このような書き方をすると非常に面倒だったりする時があります。
この場合便利な記載方法があります。

例えば、もう既にuidまで登録されていて、そのuidに紐づくscoreを書き直したいんだと言う場合は、以下のようにも書けます。

Dictionary<string, object> map = new Dictionary<string, object>();
map.Add(_FirebaseUser.UserId + "/score/", itemMap);

_FirebaseDB.UpdateChildrenAsync(map);

このように、書き直したいデータまでKeyに一気にパスを貼ってしまって、それに該当するデータだけセットすれば、データ登録を行うことが出来ます。
極論で言うと、以下のような書き方も出来ます。

Dictionary<string, object> map = new Dictionary<string, object>();
map.Add("/Ranking/" + _FirebaseUser.UserId + "/score/", itemMap);

FirebaseDatabase.DefaultInstance.GetReference("UnitySample").UpdateChildrenAsync(map);

上記の例は、Firebaseへの参照を「UnitySample」までしか貼っていないケースで、RankingからscoreまでのPathを一気に貼る例です。

余談2

Sampleの以下のコードの説明がねーよって思っている人がいるかもしれません、

string key = FirebaseDatabase.DefaultInstance.GetReference("UnitySample").Child("Ranking").Push().Key;

今回の事例では利用していないのですが、Firebaseのデータ管理の意味で重要で便利な意味合いを持つ所なので、今回ご紹介しています。

この部分は、UnitySample/Ranking以下に1つユニークな文字列を作成する
これをどういう時に利用するのか?と言うと、
例えば、今回の例では、ユーザ毎にデータを登録して、更新があれば上書きする仕組みにしていますが、例えばスコアを登録する度に、Updateでは無く追加する場合に大いに役に立ちます。
具体的に言うと、

今回の例

  • 1位 : ゆう@あんのうん
  • 2位 : ぽぽた
  • 3位:どうまず

Push().Keyを使った具体例

  • 1位:ゆう@あんのうん
  • 2位:ゆう@あんのうん
  • 3位:ゆう@あんのうん

上記の用に、ユーザ名でユニークなデータ構造ではなくて、毎回スコア更新する度に別なデータとして登録する事で、まさに昔のゲームセンターにハイスコアを一人のユーザで埋め尽くすような機能も実装出来ます。
ただ、この場合問題となるのが、ユーザ名でユニーク化をしないので、何かしらデータを特定出来るユニークなKeyが必要となり、この例の様なKeyを使うことで同一ユーザでも別なデータとして取り扱うことが出来るようになります。

ただ、Keyは発行する度に異なりますので、自前で何処かに保存しておかないと、自分自身ですら分からないことになるので、取扱注意です。(Firebase上でこれを解決する場合は、「UnitySample/Ranking」とは別に「UnitySample/User/uid/」以下にそのユーザが発行したKeyをぶら下げると言う管理の仕方もありですね。

実行する

余談のほうが長いんじゃないかのレベルで記載してきましたが、さぁ実行してみましょう。
今回は、実際にデータを登録しているので、実機ログではなくて、Firebase Consoleのデータを見てみます。

f:id:project-unknown:20170902224140p:plain

(uid部分は私の実際にuidが入り込んでいるのでモザイクにしています。実際に見てもらえば分かるのですが、英数字の文字列が羅列されていると思います)

これでデータの登録も出来るようになりましたね!

また、例えば、以下の

this.WriteNewScore(100);

をスコア更新の意味で、

this.WriteNewScore(300);

に変更して、再度実行してみましょう。
新規にデータが登録されるんじゃなくて、既にあるユーザのScoreが上書きされたかと思います。

自分自身のデータを取得する

これまではランキングの上位10件を取得する機能を作って来ましたが、自分自身のデータが欲しい時があります。

再掲ですが、以下のデータから、自分自身のuserIDにぶら下がる情報を取得します。

https://cdn-ak.f.st-hatena.com/images/fotolife/p/project-unknown/20170831/20170831230734.png

欲しい情報は、Ranking直下にある、自分自身のUIDの情報です。
このような時は、Childメソッドを使って自分自身のデータを取得します。

_FirebaseDB.Child(_FirebaseUser.UserId).GetValueAsync().ContinueWith(task => {
    if (task.IsFaulted) {
        // 失敗時の処理
    } else if (task.IsCompleted) {
        // 成功時の処理
    }
});

_FirebaseDBは、

_FirebaseDB = FirebaseDatabase.DefaultInstance.GetReference("UnitySample/Ranking");

UnitySample/RankingまでのPathが貼られているので、その直下にある、自分自身のUserIDを指定し取得します。

まとめ

実際にFirebase Realtime Databaseへデータの読み書きを行う所まで行いました。
今回の所で、オンラインランキングシステムが実現できるようになりましたが、何度も記載している通り、今のままだとセキュリティリスクが非常に高いですので、次回はセキュリティ対策の意味を込めた「ルール」造りをしていきます。

参考

UnityでFirebaseにTwitterアカウントでログインする機能 - UnityでFirebaseを使ったオンラインランキングシステムを作るvol2

この記事はシリーズ物です。
シリーズの記事は以下を参照ください

www.project-unknown.jp

www.project-unknown.jp

www.project-unknown.jp

やりたい事を簡潔に纏めるskill皆無な為、長いタイトルになってしまった…。

最初に懺悔いたしますと、かなり試行錯誤しながらFirebaseの実装を進めており、思い出しながら書いています。
もしこれで動かなかったなどありましたら、お気軽にTwitter@YuwUnknown までご連絡くださいmm

ここに載せている情報は、公式を参考に記載しているものです、
情報が古くなっている場合は、一度公式を参考くださいませ。

はじめに

Firebaseで用意されているAuthenticationは、簡単に言うとユーザ認証を指します。
独自でユーザ認証システムを行うとシステム構築が大変なので、TwitterやFacebook、Googleアカウント等の認証を作ってユーザのログイン機能を担ってくれます。

前回のUnityでTwitter認証を行うの記事でも書いたのですが、とりあえず今のところの目標はFirebaseでTwitter認証を行い、認証情報で取得したユーザ名でランキングシステムを作ると言う所を目指します。
そこで今回は、Firebase AuthenticationでTwitter認証を行います。

前回は、UnityでTwitter認証をする方法を記載しました。
そこで記載されている、AccessTokenとSecretが必要となるので、まだ見ていない or AccessTokenの取得方法が分からない方は、まず前回の記事を参考に取得できるようにしておいてください。

project-unknown.hatenablog.com

Firebaseの初期設定

Firebaseをまだ登録されていない方は登録しちゃいましょう!

firebase.google.com

また、契約やサービス内容についてヒアリングした結果も過去のブログに載せているので、もし料金が〜など不安な方が居ましたら、以下を一読頂ければと思います。
project-unknown.hatenablog.com

Firebaseのコンソールに移動して、プロジェクトを追加します。(私はfirebase-sampleというプロジェクトを作っています。)
f:id:project-unknown:20170828000653p:plain

プロジェクトの作成が完了したら、作成したプロジェクトをクリックしましょう。Overviewに移動するかと思います。

次に、Authentication -> ログイン方法 -> Twitterをクリックします。

f:id:project-unknown:20170828003602p:plain

ここで、前回のTwitter Developerに登録した発行した、AccessTokenとシークレットを入力します。
(混同しやすいのですが、これは開発者が登録した際のAccessTokenです)

iOS/Android用の設定を行う

OverViewからアプリを追加してください。
iOSとAndroid両方に展開予定であれば、iOS用とAndroid用2つ必要です。

f:id:project-unknown:20170904164755p:plain
追加したらこんな感じになっていると思います。

後は、設定ボタンをタップ

f:id:project-unknown:20170904165013p:plain

以下のようにinfoファイルをダウンロードして、Unityに突っ込みます。

f:id:project-unknown:20170904165101p:plain

ファイル名は以下の通りです。

  • iOS
    • GoogleService-Info.plist
  • Android
    • google-services.json

ここまで実施したらFirebaseの設定は完了です。

Firebase SDKを導入する。

公式からSDKをダウンロードします。
この中に、複数のPackageが梱包されています。
機能毎にPackage名が名付けられているので特に悩まないと思います。

ここでは、以下の2つをimportしてください。

  • FirebaseAuth.unitypackage
  • FirebaseDatabase.unitypackage

(FirebaseDatabase.unitypackageは次回以降で紹介するRealtimeDatabaseで利用しますので、今のうちに入れておきましょう!認証だけでいいんだよと言う人は、FirebaseAuth.unitypackageで十分です)

Firebase Authenticationのコードを書く。

やっとここでコードが掛けます。
やっている事は、以下です。

  • Game起動と同時にUnityからユーザのTwitter認証(Twitterログイン)を行います。
  • Twitter認証が完了したら、取得したAccessToken, シークレットの情報を元に、Firebaseへログインします。

gist.github.com

ここで一点注意点なのですが、今回使ってきているTwitter Kit for UnityにはDisplay Nameを取得する機能はありません。(2017/9/2現在)
Display Nameとは、以下のキャプチャの通り他のユーザに見える名前です。

f:id:project-unknown:20170902215427p:plain

これかなり不便ですね…。
ただ、Firebaseの方で取得する機能があります。
上記の例ですと、以下のところです。

newUser.DisplayName

今回はFirebaseと一緒に使うから良いものの、早めにここは追加してもらいたいですね。

では、これを実機で起動してみてください。
(Unity Editor上でも出来るのですが、Editor上だとFirebaseの動作がなんとなく怪しい動作を行うので、可能なら実機で確認しておいたほうが良いです)
以下はXCodeにFirebaseのログインが成功した際に出力しているログを載せておきます。

f:id:project-unknown:20170828010828p:plain

さいごに

ここまでで、FirebaseにTwitterログインを実現する所まで完了しました。
次はいよいよ、FirebaseのRealtime Databaseに認証したTwitter情報で書き込む所を記載しようと思います。

参考

project-unknown.hatenablog.com

UnityでFirebase Authenticationを使ってみる(公式資料)

SDK配布場所

Twitter Kit for UnityでTwitter認証を行う - UnityでFirebaseを使ったオンラインランキングシステムを作るvol1

はじめに

この記事はシリーズ物です。
シリーズの記事は以下を参照ください

www.project-unknown.jp www.project-unknown.jp www.project-unknown.jp

今日はTwitter Kit for UnityでTwitter認証の記事を書きます。

アセットはこれ

最初の背景はどちらかと言うと愚痴りたかったので書いているだけで、中身がありません。急いでいる方は「アセットのimport」から読み進めてください

背景

FirebaseとUnityとを連携させたかったので、公式を見ながら始めようとしたのですが、、

f:id:project-unknown:20170827033146p:plain

思いっきりNativeコードでOAuth認証まで終わらせて来いやって書いてあり衝撃を受け、UnityでのTwitter認証について調べた次第です。

ちなみに、その時の愚痴がこれ

また、何故Twitter Kit for Unityなのかと言うと、よく調べると至るサイトがFabricでやれって書いてあったのですが、

FabricってGoogle(Firebase)に吸収されるから今後どうなるかわからんやん
というか、FabricのTwitterKitが見当たらん

その時の愚痴がこれ

んで、途方に暮れてNativeで書こうか悩んだんですが、折角このAssetがあるなら使ってみようと思った次第です。

なので、最終的にTwitter Kit for UnityでTwitter認証を行い、そこから取得したToken情報を使ってFirebaseまでを目的にしており、
Twitter Kit for Unityの使い方にフォーカスしてます。
(Firebaseだけじゃなくて、このAssetの使い方で戸惑っている人も活用いただけると思い

と言うか、他のサイトで記載されているのが、PINコード認証の古い認証のやり方かFabricでの認証くらいしか見当たらなくて私自身が本当に困ったのが、この記事を書く一番の要因だったのですがね(;・∀・)

アセットのimport

まずは、アセットをダウンロードしましょう。
上記のTwitter Kit for UnityのAsset Storeでimportするか、公式のInstallationからの直リンでpackageを落としてUnityにimportします。
公式からDLする場合は、以下のキャプチャを参考にしてください。

f:id:project-unknown:20170827034004p:plain

Twitter Developerの設定

Twitter API初期設定

https://dev.twitter.comでTwitterの設定を行います。
アカウントを取得していないのであれば、アカウント取得から実施してください。

画面上部のMy Appから、以下の画面が開くのでCreate New Appをクリックします。
キャプチャでは、Firebase用のSampleを造りたかったので、「UnityFirebaseSample」としてます。

f:id:project-unknown:20170827034326p:plain

次が、登録の入力例です。

f:id:project-unknown:20170827034442p:plain

コールバックURLは、Firebaseの場合は指定されています。
これについては、Firebaseの記事を直ぐに執筆予定ですので、そこで説明します。

Twitter APIのPermission設定

ここまで、来ると、以下の画面になっていると思いますので、Permissionをクリックします。

f:id:project-unknown:20170827034621p:plain

特に認証をするだけなら、Read onlyだけで良いのですが、Tweetとかもさせたい場合は、 Read and Writeとかに設定しておきます。

f:id:project-unknown:20170827034724p:plain

Twitter APIのSetting

次はSettingをクリックします。

f:id:project-unknown:20170827034803p:plain

公式にある通り、Call backのロックを解除しておきます。(デフォルトは解除されていると思います)

f:id:project-unknown:20170827034829p:plain

Twitter APIのKeys and Access Tokensの API Keyと API Secretを控える

以下のConsumer Key (API Key)と、Consumer Secret (API Secret)を控えます。後で使います。 f:id:project-unknown:20170827034951p:plain

ここまでで、Twitter APIの設定が完了です。
お次はUnityの設定を行っていきます。

Unityの設定。

Twitter Kitのセットアップ

冒頭で書いた通り、ここまででTwitter Kit for Unityがimportされていると思うので、その体で進めます。

まず、importが正常に完了していると、メニューにTools > Twitter Kitとあると思うので、それをクリックします。

f:id:project-unknown:20170827035202p:plain

inspectorが、以下のキャプチャの通りの画面になっていると思うので、ここに先程控えていた Consumer Keyと Consumer Secretを入力します。

f:id:project-unknown:20170827035233p:plain

TwitterInit.csをGameObjectにアタッチ

importパッケージの中に、「TwitterInit.cs」が梱包されているので、これをScene上のGameObjectにアタッチします。

f:id:project-unknown:20170827035446p:plain

これを忘れると動きませんのでご注意ください(私が、この作業をすっ飛ばしたせいで、3時間位悩みました)

コードを記載

さぁやっとここでコードの記載です。
ここでは、Gameを起動した瞬間にTwitter認証を行おうとしています。

gist.github.com

上記をTwitterKitForUnitySample.csというファイル名で保存して、適当なGameObjectにアタッチします。

注意
ファイル名を別にしたい場合は、クラス部分の

public class TwitterKitForUnitySample : MonoBehaviour {

public class 変更したいファイル名 : MonoBehaviour {

に変更してください。
ファイル名とクラス名が違う場合はアタッチができなくなるのでご注意ください。

Twitterの認証が成功した際に、

public void LoginComplete(TwitterSession session) {

上記メソッドが呼ばれ、TwitterSessionの中に、ユーザを識別する情報が入っています。
ここでは、その他のTweetや、別なサードパーティのシステム(Firebase等)の認証にAccessTokenとAccessTokenのSecretが必要になってくるので、それを抜き出しています。

Build

iOSの場合は、Twitter Kit for UnityがiOS 7.0をサポートしていないので、8とか9以上に設定しておきましょう。
(デフォルトが7になっているはずです)

f:id:project-unknown:20170827040645p:plain

実際に動かす。

すんません、認証のキャプチャ取るのを完全に忘れてました
しかも一度認証すると次は自動で認証が走るので取れませんでした(;・∀・)

XCode等でのログで以下が流れていればOKです

[Info] : Login success. アクセストークン"

さいごに

次はFirebaseと認証して、Firebase上でTwitterユーザでランキングを作るって所まで最終的に記事に書いていきたいと思います。
また、UnityのTwitter認証で調べると、大体がFabricかSocialConnectorとかが引っかかってアプリ内でTwitter認証, Tweetを実施したかった人の助けになれば幸いです。

余談

Twitter Kit for UnityをUnity Cloud buildに掛けると、以下のようなログが出ます。

[Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkit.framework/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@2x.png 

(GUID}は人によって違うランダムな英数が出ると思います。
こちらですが、単純に「twttr-icn-tweet-place-holder-photo-error」のerrorがエラーが起きたかのように解釈されてエラーっぽく見えてしまっていますが、これについては問題ありません。
(私がこれに凄いハマった)

余談2

Twitter Kit for Unityは米国在住の13歳以下の子供への提供は出来ません。
(と言うかTwitterのサービス自体がCOPPA(Children's Online Privacy Protection Act)に引っかかっているので、Twitterを使ったアプリを展開する時点で13歳以下の利用が出来ないように対策を練らないといけないかと思います。)

日本在住の子供向けであれば問題はありませんが、海外展開を考えている場合は十分に注意しましょう。

詳細はTwitter Kit for Unity licenseの「7. End Users」に記載がありますので一読ください。

参考