こんにちは。もぐめっとです。
仕事をするときはちゃんとスーツを着るタイプです。
今回は豪華二本立てのunity tipsです。
!!Attention!!
前提条件として、nullableを使うためにC#8が使えるunity2020.2(2020/10/08現在、まだベータ版)を使って説明してます。
それ以前は適宜[CanBeNull]とかに置き換えてもらえればきっとできるんじゃないかと思います。(未検証)
Firestoreのenum変換
unityでいい感じにenum変換するにはConverterTypeをかます必要があります。
例えば人狼ゲームで、役職によってチームが人狼と村人チームと別れている。。。みたいなのを表現するとします。
その役職チームはRoleTeamとして表現して、string型なenumでWerewolf, Villageと2チームに表現します。
下記のように表現します
[FirestoreData]publicstructRole{[FirestoreDocumentId]publicDocumentReferencedocument{get;set;}[FirestoreProperty]publicTimestampcreatedAt{get;set;}[FirestoreProperty]publicstringroleName{get;set;}[FirestoreProperty]publicRoleTeamteam{get;set;}// Enumで定義}[FirestoreData(ConverterType=typeof(FirestoreEnumNameConverter<RoleTeam>))]publicenumRoleTeam{Werewolf,Village}
はい、ここで出てきました。MrコンバーターことFirestoreEnumNameConverter君です。
これを使ってあげることによってDocumentSnapshotにあるConvertToメソッドでenumが入っていたとしても簡単にRole型のデータを取得することができるようになります。
では実際にどうやってDecodeしているかの例を見てみましょう。
publicasyncUniTask<Role>GetVillagerRole(){varsnapshot=awaitfirestore.Collection("roles").Document("villager").GetSnapshotAsync();returnsnapshot.ConvertTo<Role>(ServerTimestampBehavior.Estimate);}
とても簡単にDecodeできちゃいますね。
基本はConvertToで変換できるようにするのが一番楽です。
でもnullableがはいってるとまだ対応してねーっすって怒られちゃうんです・・・
おじさん悲しい・・・
Nullableなデータの変換をする
たとえばこのRoleにnullableな値として、占い師などは占う能力(ability)をもっていますが、村人は何も能力を持ちません。このように役職ごとに値があったりなかったりするとしましょう。
こんな感じで新しく定義してみました。
[FirestoreData]publicstructRole{[FirestoreDocumentId]publicDocumentReferencedocument{get;set;}[FirestoreProperty]publicTimestampcreatedAt{get;set;}[FirestoreProperty]publicstringroleName{get;set;}[FirestoreProperty]publicRoleTeamteam{get;set;}// Enumで定義[FirestoreProperty]publicint?ability{get;set;}// nullable}
どうしたもんかと悩んでいたらこんな記事を見つけました。
【Unity×Cloud Firestore(Firebase)】UnityにおけるFirestoreへの書き込み&読み込み
上記からヒントを得て、こんなExtensionを作ってみました。
publicstaticclassConvertExtension{publicstaticT?ConvertNullable<T>(objectdata)whereT:struct{try{return(T)Convert.ChangeType(data,typeof(T));}catch{returnnull;}}}
EnumもDecodeできるようにExtensionを作りました
publicstaticclassEnumExtension{publicstaticboolTryParse<TEnum>(strings,outTEnumenumValue)whereTEnum:struct{returnEnum.TryParse(s,outenumValue)&&Enum.IsDefined(typeof(TEnum),enumValue);}}
そしてDocumentSnapshotからDecodeするメソッドを作っていました。
[FirestoreData]publicstructRole{[FirestoreDocumentId]publicDocumentReferencedocument{get;set;}[FirestoreProperty]publicTimestampcreatedAt{get;set;}[FirestoreProperty]publicstringroleName{get;set;}[FirestoreProperty]publicRoleTeamteam{get;set;}// Enumで定義[FirestoreProperty]publicint?ability{get;set;}// nullable[FirestoreProperty]publicint[]?voices{get;set;}// ArrayなnullablepublicstaticRolefromSnapshot(DocumentSnapshotsnapshot){vardata=snapshot.ToDictionary(ServerTimestampBehavior.Estimate);EnumExtension.TryParse(data[nameof(team)].ToString(),outRoleTeamoutTeam);// EnumのDecode。outTeamという変数にdecodeする。TryParseの返り値がfalseならthrowしたほうがいいかも。returnnewRole(){document=snapshot.Reference,createdAt=(Timestamp)Convert.ChangeType(data[nameof(createdAt)],typeof(Timestamp)),roleName=data[nameof(roleName)].ToString(),team=outTeam,ability=ConvertExtension.ConvertNullable<int>(data[nameof(ability)])// Extension使ってNullableな値にコンバート}}}
これで無事にnullableでもdecodeできるようになりました!!
ただ、Mapで入れ子状にデータを入れることもあると思います。
例えば占い師が占ったプレイヤー情報をRoleに格納するとしましょう。
この場合はまた色々と変換処理をかまさないとできません。
まず、このようなinterfaceを準備します。
publicinterfaceIDictionaryConvertible<T>{TfromDictionary(Dictionary<string,object>dictionary);}
そしてMapなデータをコンバートできるようにExtensionを拡張します
publicstaticclassConvertExtension{publicstaticT?ConvertNullable<T>(objectdata)whereT:struct{try{return(T)Convert.ChangeType(data,typeof(T));}catch{returnnull;}}/// MapからDecodeするpublicstaticTChangeType<T>(objectdata)whereT:IDictionaryConvertible<T>,new(){varobj=(Dictionary<string,object>)Convert.ChangeType(data,typeof(Dictionary<string,object>));returnnewT().fromDictionary(obj);}}
Player情報はこんな感じで定義しておきます。
publicstructPlayer:IDictionaryConvertible<PlayerInfo>{[FirestoreProperty]publicstringusername{get;set;}publicstaticPlayerfromDictionary(Dictionary<string,object>dictionary){returnnewPlayerInfo(){username=dictionary[nameof(username)].ToString()};}}
このPlayer情報をRoleでDecodeできるような感じにすると下記のようになります!
[FirestoreData]publicstructRole{[FirestoreDocumentId]publicDocumentReferencedocument{get;set;}[FirestoreProperty]publicTimestampcreatedAt{get;set;}[FirestoreProperty]publicstringroleName{get;set;}[FirestoreProperty]publicRoleTeamteam{get;set;}// Enumで定義[FirestoreProperty]publicint?ability{get;set;}// nullable[FirestoreProperty]publicint[]?voices{get;set;}// Arrayなnullable[FirestoreProperty]publicPlayer?player{get;set;}// nullableなPlayerpublicstaticRolefromSnapshot(DocumentSnapshotsnapshot){vardata=snapshot.ToDictionary(ServerTimestampBehavior.Estimate);EnumExtension.TryParse(data[nameof(team)].ToString(),outRoleTeamoutTeam);returnnewRole(){document=snapshot.Reference,createdAt=(Timestamp)Convert.ChangeType(data[nameof(createdAt)],typeof(Timestamp)),roleName=data[nameof(roleName)].ToString(),team=outTeam,ability=ConvertExtension.ConvertNullable<int>(data[nameof(ability)]),player=ConvertExtension.ChangeType<Player>(data[nameof(player)])// nullableなMapデータをPlayerに変換する}}}
これでいい感じにnullableを扱えるようになりました!!
所感
unityで、nullableは最近ようやく対応してきたばかりでこんな苦労をしていますが、firestoreでのnullableもいずれ対応してくれるようになると非常に嬉しいですね。
また、nullableなstringのコンバートでいい感じの方法がわからないのでもし誰かわかる方いらっしゃったらコメント欄とかでひっそりと教えて下さい。
おまけ
同じ感じの要領でMapデータのArray版もつくってみたので良かったら使ってみてください
publicstaticList<T>?ConvertNullableArray<T>(objectdata)whereT:IDictionaryConvertible<T>,new(){try{varlist=(List<object>)Convert.ChangeType(data,typeof(List<object>));returnlist.Select(obj=>{vardict=(Dictionary<string,object>)Convert.ChangeType(obj,typeof(Dictionary<string,object>));returnnewT().fromDictionary(dict);}).ToList();}catch(Exceptionexception){Debug.LogError(exception);returnnull;}}
宣伝
ワンナイト人狼のアプリ作ってます!よかったら遊んでみてね!