LocalAuthentication iOSで生体認証(Face ID, Touch ID)のまとめ

はじめに

KeyHolderにFace IDやTouch ID等の、生体認証を導入したので、その際の備忘録を纏めます。

実装方法

info.plist修正 NSFaceIDUsageDescriptionを追加

info.plistにNSFaceIDUsageDescriptionを追加します。
これは、FaceIDの利用をユーザに許諾してもらうために追記します。
KeyHolderでは以下の様に記載しています。

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

LocalAuthenticationをimport

LocalAuthenticationをimportします。

import LocalAuthentication

認証用コードの実装

以下のコードの様に記載することで、Face IDもしくはTouch IDが使える端末且つ、利用を許可されている場合は、認証を行うことが出来ます。
また、認証が通った場合は、それがFace IDなのか、Touch IDはcontextの中身を見れば取れる様になっています。

    let context = LAContext()
    var error: NSError?
    let description: String = "認証します"
    // Face ID, Touch IDが利用できるデバイスか確認する
    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
        // Face ID, Touch IDが利用できる場合は認証用ダイアログを表示
        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: description, reply: {success, evaluateError in
            if (success) {
                // 認証成功
                switch context.biometryType {
                case .faceID:
                    // Face IDが利用出来る
                    break
                case .touchID:
                    // Touch IDが利用できる
                    break
                case .none:
                    // Face ID, Touch IDの利用が出来ない (この場合、この条件には入ってこないはず)
            } else {
                // 認証失敗
            }
        })
    } else {
        // Face ID, Touch IDが利用出来ない
    }

簡単な例ですが、どの認証が扱えるのかのクラスメソッドを用意しました。
それぞれFaceID、Touch IDが利用出来るかをチェックする為のメソッドです。
また、引数に、NSErrorを渡す事で、利用できない場合のエラー内容が返却されます。
エラー内容については、後述で記載しています。

    /**
     Face IDが利用出来るかチェックします
     
     - returns Bool: true 利用可能 / false 利用不可
     */
    class func canUseFaceID(error: inout NSError?) -> Bool {
        if #available(iOS 11.0, *) {
            let context = LAContext()
            if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
                if context.biometryType == .faceID {
                    return true
                }
            }
        }
        
        return false
    }
    
    /**
     Touch IDが利用できるかチェックします
     
     - returns Bool: true 利用可能 / false 利用不可
     */
    class func canUseTouchID(error: inout NSError?) -> Bool {
        let context = LAContext()
        
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            if #available(iOS 11.0, *) {
                if context.biometryType == .touchID {
                    return true
                }
            } else if #available(iOS 8.0, *) {
                return true
            }
        }
        
        return false
    }

使い方は以下のような感じで

var error: NSError?
if クラス名.canUseFaceID(error: &error) == false && クラス名.canUseTouchID(error: &error) == false {
    print ("生体認証が利用できません!!!")
}

エラーコード (LAError.Code)

LocalAuthenticationのエラーは、LAErrorで定義されています。

エラーのドメインは以下の通りです。

com.apple.LocalAuthentication 

以下はエラーコードごとの種別です。
Appleのドキュメントでは、TouchIDが主に言及されていますが、Face IDの場合はTouch IDをFace IDに置き換えて読んで問題なさそうです。
(例えば、Face IDを許可していなかったら-6が返ってくる為) (翻訳はGoogle翻訳を、そのまま使っている所もあります)

LAError.Code種別 エラーコード 説明
authenticationFailed -1 ユーザーが生体認証を利用するにあたり必要な情報を獲得できなかった為、認証に失敗
appCancel -9 アプリ側でキャンセルが掛かった
invalidContext -10 何かしらのエラー
notInteractive -1004 インタラクティブではない?
passcodeNotSet -5 端末にパスコードがセットされていない
systemCancel -4 システムで認証がキャンセルされた
touchIDLockout -8 Face ID/Touch IDがロックアウトされた
touchIDNotAvailable -6 端末でFace ID/Touch IDを使用できない為、認証できなかった
touchIDNotEnrolled -7 指紋認証に失敗
userCancel -2 認証ダイアログでユーザがキャンセルボタンを押した
userFallback -3 ユーザが認証ダイアログでFallbackしたが、認証ポリシーにFallbackが使用できなかった。

最後に

iPhone Xが出て、Face IDが登場してから認証周りのユーザ利便性が非常に上がってきましたね。
何だかんだでずっと対応していなかったKeyHolderでも、やっと対応し、近日中にリリース予定ですので、是非この機会にお試しください!

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の書き方になれていないので、経験が必要ですが…)

swiftでtableViewを表示する…ときのエラー・トラブル対応方法

はじめに

どうも、どうまずです。

Swiftを初めて、最初のほうにやること、『SwiftでTableViewを表示する。』
いくつかのサイトや参考書があり、私もそれらを見ながら作成しました。

しかし、エラーやトラブルで躓いていませんか?
躓きすぎて、匙投げようとしていませんか?

今回は、『swiftでtableViewを表示する』を試みて、よく発生するエラー・トラブルの解決方法を記載しようと思います。

ちなみに、環境は

Swift4
Xcode 9

です。

MainStroryBoardの使い方のトラブル編

ユーティリティエリアが表示されない。(右の作業枠)

XcodeでViewにTableViewを貼り付けようと ユーティリティエリアで説明するように記載されているが、そもそも、ユーティリティエリアってのが表示されていない!

原因

単なる操作ミスで、ユーティリティエリアを隠すボタンをクリックしてしまったようです。

解決策

下記のボタンをクリックして下さい。ユーティリティエリアが表示されます。

f:id:project-unknown:20170925014924p:plain:w150

Table Viewが二つになった!

MainStoryboardでtable viewをViewControllerを貼り付けたら、View Controllerが二つになってしまった。

f:id:project-unknown:20171027211940p:plain:w300

説明

「Table View」じゃなくて、「table View Controller」を選択していた。

正:Table View(枠が四角)

f:id:project-unknown:20171005194137p:plain:w150

誤:Table View Controller(枠が丸、黄色)

f:id:project-unknown:20171005194115p:plain:w150

解決策

新しくできたViewを削除(選択してDeleteキー)する。 うまく選択できない場合は、左側に「Table View Controller Scene」と表示されているので、選択して削除する。 「Table View Controller」をMainStoryboardに貼り付けただけなら、削除しても問題ありません。

MainStoryBoardとソースの紐付けトラブル編

ソースにTableViewを紐づけたら、テーブル変数名を間違えた

MainStoryboardのTableViewとViewController.swiftなど紐付けしたけど、タイプミスなどでテーブルの変数名を間違えてしまった。

説明

ViewController.swiftに作成された変数名を修正するだけでは、MainStoryboardに設定の名残が残ってしまい、実行してもエラーが発生してしまいます。

MainStoryboardのTableViewとViewController.swiftの紐付けを断つことが大切です。

解決策

  1. ViewController.swiftのテーブル変数を削除する。
  2. MainStoryboardのtableViewを2本指でクリックする。
  3. 下の画面キャプチャの✖︎ボタンをクリックする。

f:id:project-unknown:20170925020210p:plain:w200

これで、「Table View」とViewController.swiftの紐付けは断たれました。

確認のためシミュレータで実行してみましょう。

エラーが発生しなければ、成功です。

@IBOutlet var mainStoryboard: [UITableView]と表示された。

MainStroryboardのTableViewとViewController.swiftの紐付けを行なった際、

本来、

@IBOutlet weak var mainTableView: UITableView!

と表示されるはずだが、なぜか

@IBOutlet var mainTableView: [UITableView]!

と表示されてしまった。

説明

MainStroryboardのTableViewとViewController.swiftの紐付けを行なったとき、ダイアログが表示されますが、 Connectionのところをデフォルトの「Outlet」ではなく、「Outlet Collection」を選択してしまったようです。

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

「Outlet Collection」はコレクションつまり配列扱いになります。テーブルを複数使う場合などに活用できますので、完全なミスというわけではありません。

しかし、意図していない場合は・・・単なる操作ミスです。

解決策

やり直しましょう。

「ソースにTableViewを紐づけたら、テーブル変数名を間違えた」を参考にテーブル変数の削除を実施して下さい。

ViewController.swiftの記載編

UITableViewDelegateとUITableViewDataSourceを継承したらエラーになった。

ViewController.swiftでTableViewを操作できるようにUITableViewDataSourceを継承させた。

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

そしたら、

ViewController.swift:11:7: Type 'ViewController' does not conform to protocol 'UITableViewDataSource'

というエラーが発生した。

説明

たぶんですが、デリゲートとデータソースを継承しただけで、作業を止めていませんか?

エラーの内容は
「'ViewController'がプロトコル 'UITableViewDataSource'に準拠していません」
です。

UITableViewDataSourceを継承すると、UITableViewDataSourceのルールに則ったメソッドを用意しなければなりません。

解決策

まず、どこかのサイト、参考書を参照しているのでしたら、このエラーを無視して続きの説明を読みましょう。 大抵の場合は、

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)

を記載しているはずです。

特に記載していない場合、エラーのところにFixと出ているはずです。

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

これをクリックすると、最低限必要なメソッドを自動的に追加してくれます。

補足ですが、
・エラーの左の赤丸の中が白丸の場合は、今回のようにエラー解決案を提示してくれます。
・エラーの左の赤丸の中が!の場合は、エラー解決案を提示してくれませんので、エラー内容から何とか解決しましょう。

TableViewにソースで記載した内容が反映されない。

ある程度なれてきたときに発生するトラブルです。
TableViewの設定や処理をViewController.swiftに記載したのに、シミュレータで実行してみると反映されないというトラブルです。

説明

なにか大切で肝心な何かを忘れています。

解決策

viewDidLoadにデリゲートとデータソースの指定を忘れていませんか?

@IBOutlet weak var mainTableView: UITableView!
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    mainTableView.delegate   = self
    mainTableView.dataSource = self
    
}

「mainTableViewのデリゲートとデータソースはこのソース自身に記載しているよ」という指定ですね。 これがないと、mainTableViewはどのデリゲート・データソースを使っていいかわからず、沈黙してしまいます。

全てのセルが同じ設定になってしまう。

例えば、

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    
    return CGFloat(100.0)
    
}

と記述すると、セルの高さが変更されますが、全部のセルが同じ高さになってしまう。

先頭のセルだけ高さを別の高さを指定したい。

説明

heightForRowAtの引数にindexPathがあります。

indexPathには、どのセクションの何行目のセルかという情報が入りますので、これを活用しましょう。

解決策

下記のように条件分岐を記載します。

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.section == 0 && indexPath.row == 0{
      return CGFloat(50.0)
    }
    return CGFloat(100.0)    
}

indexPath.sectionでheightForRowAtを呼んだセルのセクション番号を取得します。
indexPath.rowでheightForRowAtを呼んだセルの行番号を取得します。

これらを活用すれば、任意のセルを制御できると思いますよ。

シミュレータで実行しようとしたら編

シミュレータで実行しようとしてエラーになった。

プロジェクト作成直後、とりあえずシミュレータ起動しようとしたら、エラーになっちゃったパターンです。

エラーの内容は下記のような内容。

Signing for "blogTest" requires a development team. Select a development team in the project editor.
Code signing is required for product type 'Application' in SDK 'iOS 10.3'

原因

原因はずばり、
何も設定していない実機(○◯のiPhoneなど)を選択している。 です。

Xcodeで作成しているアプリケーションを実機で実行・デバックすることは可能ですが、いろいろと設定が必要です。

その設定をやっていないため、エラーが発生しています。

解決

実機ではなく、シミュレータを選択して、再度実行して下さい。

f:id:project-unknown:20171008134525p:plain:w200

ここではiPhone7を選択していますが、動かしたい任意のシミュレータを選択して大丈夫です。

AppDelegateクラスに「Thread 1: signal SIGABRT」が発生した。

シミュレータで実行したところ、下のようにAppDelegateクラスに「Thread 1: signal SIGABRT」が発生した。

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

ログ(デバックエリアの左側の黒いウインドウ)に下記のエラーが出しまった。

BlogTest *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
 '[<BlogTest.ViewController 0x7fb824907140> setValue:forUndefinedKey:]:
 this class is not key value coding-compliant for the key mainTableView.'

ちなみに、私は、「Thread 1: signal SIGABR」を「初心者殺し」と呼んでます(笑)
エラーが今まで触ってないところに表示され、何がなんだかキョトンとしてしまいますよね?
私は固まりましたよ!

説明

『そもそも、「signal SIGABR」って何よ?』という方は、 signal SIGABRに関する当ブログの記事をご覧下さい。

www.project-unknown.jp

この記事の「その1インターフェースビルダーとコードをOutlet引いたのに、そのOutletが外れてしまっているパターン」にハマっています。

テーブルの変数名を間違えたりして、ViewController.swiftのテーブル変数を一度削除したりしてませんか?
テーブルの変数名をViewController.swift上で変更したりしていませんか?

そういう場合に発生します。

解決策

  1. MainStoryboardのtableViewを2本指でクリックする。
  2. 下の画面キャプチャの✖︎ボタンをクリックする。

f:id:project-unknown:20170925020210p:plain:w200

ちゃんと消してから、再度MainStoryboardのTableViewとViewController.swiftを紐付けしましょう。

だいたいこれで、TableViewの器は作れたと思います。

次回は続きのセルの操作時のエラー・トラブルを記載しますので、少々お待ち下さい。

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だけで設定をリアルタイムにやり取りが出来るので、非常に開発が楽になるかと思います。
(こういう面倒な事を簡単にする仕組みばかりに手を出して、肝心のゲーム開発が遅延しているのはここだけの話…。

iOS11でのクリップボードに文字列を渡す方法 - UIPasteboard

概要

これまでだと、以下の様にする事でクリップボードに文字列を渡すことが出来ました。

let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.setValue("渡したい文字列", forPasteboardType: "public.text")

ただ、このやり方はiOS11からは出来なくなります。
実際にこのやり方で実装されたアプリでコピー&ペーストを行おうと、例えばSafariの検索窓とかに貼り付けても何も起きません。

原因と解決策

元々Appleのドキュメントにも、setVaueはObjectをクリップボードに渡すと記載されているので、あるべき姿となった形でしょうか。
(stringオブジェクトを貼り付けたので、何も起きていない。)
試しに、冒頭の

let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.setValue("渡したい文字列", forPasteboardType: "public.text")

を、標準のメモ帳に貼り付けると、以下のスクショのようになります。

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

では、解決策ですが、以下のように処理を変更するだけです。

let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.string = "渡したい文字列"

見ての通り、UIPasteboardのstringに値を渡すだけで、これまで同等の機能を実現できます。

さいごに

最後に宣伝となってしまいますが、提供しているKeyHolderでも上記で記載した機能を利用しております。
もし興味があればダウンロード宜しくお願いします

www.project-unknown.jp

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に宣言して利用するのが汎用性が高いかもしれません。

FabricのCrashlyticsのアップデートに失敗した時の回避法

Fabricとは

みなさん使ってます?Fabric. 

Twitter社が、Crashlyticsを吸収しTwitterライブラリの導入支援に加え、クラッシュレポートをグラフィカルに確認できるようにしてくれるツールです。  

f:id:project-unknown:20170930152405p:plain
(Fabricツールのイメージ)

最近では、GoogleのFirebaseのクラッシュレポートが、Fabricを採用することが決まり、更に幅広く使えそうな事が見込めます。  

今日は、そんなFabricのCrashlyticsですが、アップデートしようとして見事に詰まってしまったので、その解決法の紹介です。

 

FabricのツールからCrashlyticsを入れようとするとLinkerエラー

 

先程も記載しましたが、FabricはTwitterライブラリを簡単に導入できるのに加え、Crashlytics の導入支援もしてくれます。

 

現在提供しているKeyholderは、元々FabricのツールからCrashlytics を導入していたので、今回も同様の手順でUpdateしようとしましたが、見出しの通りLinkerエラー。  

 

Fabricツール経由ではWorkspaceは適用しない?

 

いろいろ見ていて気になったのは、Fabricツールから起動しようとすると、XCodeのプロジェクトファイルが自動で立ち上がるところ。  

 

Keyholderは、Cocoapodsを使っている兼ね合い上、Workspaceで管理しています。  

且つ、エラーの発生箇所はKeyholderのプロジェクトファイルというよりは、同一Workspaceで管理している別パッケージで発生してました。  

 

Crashlyticsを、Cocoapods経由でインストール(解決策)

 

一気に解決策ですが、Favricツール経由ではなく、Cocoapods経由でCrashlytics を導入する事で、正常にインストールができました。

source 'https://github.com/CocoaPods/Specs.git'

target 'KeyHolder' do
  use_frameworks!

pod 'Fabric'
pod 'Crashlytics'

end

懸念点

 

元々Fabricツール経由で入れていたものをCocoapods経由にした所。  

 

ライブラリ的には問題ないとは思ってますが、ツール経由からCocoapodsに切り替えた事で古いファイルなどが残らないか気になってるところですね。  

 

現状は問題なく動作しているので、もうちょっとテストしたら本番投入してみます。  

 

もし、何か不具合があれば、この記事更新と解決法を探っていきます。

 

さいごに

 

Fabricは本当に強力なツールです。  

前Twitterでボヤきましたが、KeyholderのSwift化対応中にFabricを更新しないといけなかったのですが、本件で詰まってしまった為に、一度取り外してリリースしています。  

 

そのせいで、今現在Keyholderがクラッシュを引き起こしていても詳細を把握する事が出来ないので非常に不安な状態が続いています。

 

取り急ぎ、Fabricを導入したKeyholderをリリースに向けて着手中です。

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

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

さいごに

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

本当に書いた怖いソース(swift編) 強制ダウンキャスト as!

はじめに

どうもどうまずです。

本当に書いちゃった怖いソースをお見せしようと思います。 特に今回はクラッシュするレベルのコードです。

強制ダウンキャスト

まずはこちらのソースをご覧頂きたい。 (ちなみに、コンパイルエラーにはなりません。)

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  return collectionView.dequeueReusableCell(withReuseIdentifier:
      DetailCellIdentifier.memo.rawValue, for: indexPath)
      as! DetailMemoCell
}




おわかり頂けただろうか?
一見なんの変哲もない、collectionViewのセルの内容を返しているだけのソースに見えます。
しかし、「as!」と記載しています。
そう、つまりこれ、
強制ダウンキャストしているんです!
万が一、ダウンキャストが失敗した場合、
アプリが落ちます!

なんとも恐ろしいソースではないでしょうか!

collectionViewやtableViewのサンプルとかみると、結構高い確率で強制ダウンキャストしているので、私も右に倣えでやってしまいましたが、要注意しないといけませんね。 (サンプルを記載している方は、理解しやすいように余計な処理書いていないだけだと思います。)

テスト中ならまだしも、リリースしてからクラッシュはどうしても避けたいところですよね。

修正方法

nilが入らないようにするのは大前提ですが、想定外のことが起きるのは世の常。 万が一nilが入ってしまった場合に、落ちないようにしましょう。

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

  if let memoCell = collectionView.dequeueReusableCell(withReuseIdentifier: DetailCellIdentifier.memo.rawValue, for: indexPath) as? DetailMemoCell {
      return memoCell
  } else {
      return UICollectionViewCell()
  }
}

重要なのは

if let memoCell = collectionView.dequeueReusableCell(withReuseIdentifier: DetailCellIdentifier.memo.rawValue, for: indexPath) as? DetailMemoCell

です。 右辺の結果がnilでなければ、左辺に代入してネストした処理が実行されます。 この例では更に「as!」を「as?」に変更することでダウンキャストを行えるかも一緒にチェック(ダウンキャスト出来なければ nilが返るから、elseに入る)をおこなっています。

これで万が一の事態でもアプリが落ちるようなことはありませんね。

asっておまじないの一種と思ってた・・・

類似事例:強制アンラップ

似たような事象で強制アンラップも怖いです。
簡単な事例を一つ。

var test:String?
test = test2
print(test!)

test2という変数に何が入っているかわかないですが、仮にtest2がnilだった場合、「print(test!)」でクラッシュです。

修正方法

指摘されたのは、「強制アンラップを利用する時は絶対nilではないという自信がある場合のみ」です。 理由は下手するとクラッシュする恐怖があるためです。

そのため、アンラップする必要がある場合、強制ダウンキャストと同じく、「if let」を利用しましょう!

        if let test = test2 {
            print(test)
        } else {
            print("nilだよ")
        }

test2がnilでなければ左辺に代入され、ネスト内の処理が行われます。 ちなみに、testはアンラップされた形式になっています。

つまり
・オプショナルに慣れるまでは強制ダウンキャストや強制アンラップは避けましょう
・どうしても使う場合は、「if let」でnilか調査してから使いましょう

この2点を注意しましょう。

…いや、注意します。

注意

レビューアの観点によって、諸説あります。


リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

本当に書いた、怖いソース (swift編) viewDidLoadの肥大化 Initialization Closure

はじめに

どうも、どうまずです。
本当に書いた、汚いソース第2弾です。

今回はどんな怖いソースが出てくるのか・・・・

viewDidLoad

まずはこちらのソースをご覧頂きたい。

override func viewDidLoad() {
    super.viewDidLoad()
    
    mainCollectionView.delegate   = self
    mainCollectionView.dataSource = self

    // xibの読み込み
    self.mainCollectionView.register(UINib(nibName: MainNibName.title.rawValue, ・・・
    //以下略




おわかり頂けただろうか・・・
一見なんの変哲もないviewDidLoadですが、

書きすぎなんです

サンプルなどでも、delegateやdataSourceはviewDidLoadに記載されていることが多いのですが、本格的なアプリ開発になればなるほど、このviewDidLoadが長くなるんです。

そのため、ViewDidLoadに記載しなければならない処理は極力外だし・メソッド化して、読みやすくしとくようにしましょう。

修正後のソースは下記の通りです。

override func viewDidLoad() {
    super.viewDidLoad()
    
    collectionViewInitialize()
    
    // xibの読み込み
    self.mainCollectionView.register(UINib(nibName: MainNibName.title.rawValue, ・・・
    // 中略
}

func collectionViewInitialize() {
            
    mainCollectionView.delegate   = self
    mainCollectionView.dataSource = self

}

今回はちょっと過剰かもしれませんが、 viewDidLoadを簡素化しておくという発想が大切です。

さらに初期化でいろいろ記載することがあるようならば、 「Initialization Closure」という方法もあるようです。

参考サイト

qiita.com

例えば、下記のように単純なラベルを表示するコードがあるとします。

let testLabel = UILabel()
override func viewDidLoad() {
    super.viewDidLoad()
    
    testLabel.text      = "どうまず"
    testLabel.textColor = UIColor.blue
    testLabel.frame     = CGRect(x: 0, y: 0, width: 100, height: 50)
    
    view.addSubview(testLabel)
}

これもviewDidLoadの簡素化の観点からどうにかしたいソースです。
これを下記のように、クロージャで初期化しちゃいます。

let testLabel = {() -> UILabel in
    let label       = UILabel()
    label.text      = "どうまず"
    label.textColor = UIColor.blue
    label.frame     = CGRect(x: 0, y: 0, width: 100, height: 50)
    return label
}

override func viewDidLoad() {
    super.viewDidLoad()
        
    view.addSubview(testLabel())        
}

これでviewDidLoadには必要最低限の記載だけになりましたね。
私はaddSubViewを殆ど使ったことなかったので、こんなやり方もあるんだとちょっと驚きました。
ダイエット方法はいろいろありますね。頑張って痩せなきゃ・・・

注意

レビューアの観点によって、諸説あります。


リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

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

参考

本当に書いた、怖いソース(swift編) マジックナンバー

はじめに

どうも、どうまずです。

私が本当に書いて指摘してもらった、汚い・危ないswiftのソースを心霊写真の紹介っぽく記載していきます!

反省も踏まえまして…

恐怖・マジックナンバー

まずは、こちらのソースをご覧下さい・・・。

switch indexPath.section {
  // スケジュール名設定
  case 0:
    return "スケジュール名"
         
  // 曜日設定
  case 1:
    // 曜日の先頭のセル
    guard indexPath.row != 0 else {
  // 以下略






おわかり頂けたでしょうか・・・

一見なんの変哲もないswitch文に見えますが、
case文のところよ〜〜く見て頂きたい・・・。

この0と1、意味を持っているんです!

あれです。
今はわかるけど、後で修正や保守しようとしたときに、 『これ、なんだっけ?』ってなるパターンです。 何とも恐ろしい!!!

これらはマジックナンバーと呼ばれ後々暴れ出すのです。

マジックナンバー (プログラム) - Wikipedia

こういう場合は、enumなどを利用し、 利用し、数字をちゃんと定義してわかりやすくしましょう。

コメントで誤魔化そうとしてもダメです。 最初はコメント書いてても、書き忘れて、結局わからなくなるんですよ。

修正後は下記のような感じです。

enum SectionRowNumber: Int {
    case scheduleName = 0
    case week         = 1
}

switch indexPath.section {
  // スケジュール名設定
  case SectionRowNumber.scheduleName.rawValue:
    return "スケジュール名"
         
  // 曜日設定
  case SectionRowNumber.week.rawValue:
    // 曜日の先頭のセル
    guard indexPath.row != 0 else {
  // 以下略

enum部分が長くなっていますが、ソースだけ見ても、意味がわかるようになりました。

直してやったぜ風に記載してますが、ダメなソース書いたの私なんですよね。

頑張ろう・・・

注意
レビューアの観点によって、諸説あります。

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配布場所