FreelyApps

個人によるアプリ開発の日記です。アプリの収入だけで生活できるようになるのが目標です。UnityでAndroid向けのゲームアプリを作成しています。


    アプリ『トランプ・ジン・ラミー』を公開しました。
    Android/iOS https://goo.gl/PYKFYG

    カテゴリ: Unity

      このエントリーをはてなブックマークに追加 Clip to Evernote
    次のようなログが出続けるバグがUnity 5.5.1.f1 Personalで発生しました。
    many-log

    調べてみると原因は5.5.0f3の時からあるバグのようでした。
    issue


    私の環境だと、以下の条件を満たすと発生するようです。
    1. シーンビューが2Dモードになっている
    2. Transform ToolsがRectTransform用になっている
    3. CanvasにRectTransformのwidthかheightが0のゲームオブジェクトがあり、それに子がある
    4. 上記の子のゲームオブジェクトを選択している
    非常にまれな条件で発生するように見えますが、UIを作っていると発生する可能性があります。

    問題としてはログが大量に出ることによりFPSが極端に低下しゲームが動かなくなってしまいます。
    暫定的対処として、とりあえずTransform Tools(Editorの左上にある手とか移動とか回転のツールのこと)をRectTransform用のものにしなければ良いと思います。もしくは選択しているものを変えると良いです。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    5.5になり、UnityEngine.Typesが使えなくなりました

    `UnityEngine.Types.GetType(string, string)' is obsolete: `This was an internal method which is no longer used'
    というエラーログが出ます。 

    エディター拡張(スクリプトからそれがアタッチされたゲームオブジェクトを作成する拡張)でこれをつかっていたものがあったので対応する必要がありました。 

    対応方法は別のメソッドを使うだけです。Unityで有名なこちらのブログ
    【Unity】クラス名からTypeを取得する(テラシュールブログより)
    の記事がわかりやすいと思います。

    私の場合は、
    UnityEngine.Types.GetType(className,"Assembly-CSharp");
    と書いていたところを 
    System.Reflection.Assembly.Load("Assembly-CSharp").GetType(className);
    と直して問題なく動きました。 


    少し調べてみたところ上記の方法でなくても行う方法がありました。
    System.Type.GetTypeというメソッドでもTypeを取得できるようでした。型の名前を引数にとり、型を返すメソッドです。実行中のアセンブリ(dllと考えていい)かMscorlib.dllに含まれる型であれば名前空間で修飾した型名で型が取れるようです。
    Unityで作ったスクリプトはAssembly-CSharp.dllにあり、エディタースクリプト実行時には当然実行中のアセンブリではないです。System.Type.GetTypeはアセンブリ名を修飾した名前で呼べば型を取得できるようになっているので、
    System.Type.GetType(className+ ",Assembly-CSharp");
    というようにしてどのアセンブリにいるかを指定すれば型が取れます。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unity5.3から5.4になったときにNetworkMatchつまりマッチメイキングの処理の呼び出し方法が変更されました。5.3以前の処理を書いている場合は多少修正が必要になると思います。
    書き直すのは面倒ですが、修正されているところ(マッチングのフィルタリング)があるのでやってみる価値があります。
    34
    左が5.3で右が5.4のUnityEngine.Networking.Matchのクラスの一覧です。5.3では~Requestと~Responseがたくさんあります。これらはマッチングを行うときのメソッド(NetworkMatch.CreateMatchなど)に渡す情報とマッチング処理を行ったときの結果を返すためのものです。
    5.4ではマッチングを行うときのメソッドに直接値を渡す方式のみになったため、~Requestがなくなったようです。(5.3では~Requestを渡す方法と直接値を渡す方式がありました。値を渡す方式だけ生き残ったということです。)
    ~Responseは元々冗長なクラスでした。~Responseは成功したかどうかとサーバーでのエラーについての情報とそれぞれのマッチング処理の情報という形式です。3つのメンバーしかないので、わざわざクラス化しなくても良いという判断で5.4でなくなったのではないかと思います。5.3のCreateMatchResponseJoinMatchResponseは同じ情報を持つのに違うクラスでしたし、メンバーもわざわざ基本型で書いてありました。5.4では整理されてきれいにまとまった感じです。
    あとはMatchDescMatchInfoSnapshotに変わりました。なぜ……

    5.3でCreateMatchなどを使っていた場合は、5.4のCreateMatchに合わせる必要があります。呼び出し引数が変わっているのでその対応をすれば動くはずです。いくつかのパラメータは空文字や0で無効にして呼ぶことが多いでしょう。
    requestDomainという値が重要でこれが同じ値のものとマッチングするようになっています。この値が違うとマッチングしないようになっているので、通信する内容が変わった場合は値を変えておくと問題が起きません。アプリのバージョンとかと同じように通信のバージョンを管理しておくと良いと思います。

    以前はrequestDomainに対応しているような機能があったのですが、うまく機能してなくて互換性がない状態でマッチングが起こっていました。当然、通信が発生したときに情報が解釈できずエラーになって失敗します。5.4からはこれが動いているというのを確認しました。(Windows、Mac、Android、iOSにて)
    これがあるとUnity Multiplayerに変更のあるアプリのバージョンアップを行うことができます。バージョン違いのものを住み分けることで共存できます。 

      このエントリーをはてなブックマークに追加 Clip to Evernote
    MonoBehaviourを継承したゲームオブジェクトにアタッチできるスクリプト(C#)を作るとします。通常はヒエラルキーにゲームオブジェクトを用意してそこにスクリプトをドラッグアンドドロップしてアタッチします。
    この作業はドラッグに失敗したりすると、非常にイライラします。
    いくつかのスクリプトをつけるときはコンポーネントの追加ボタンを押せばいいのですが、新たにゲームオブジェクトを作るときはゲームオブジェクトを作り、そこにアタッチするというちょっと面倒な手順があります。

    以前スクリプトをヒエラルキーにドラッグしたら、それがアタッチされたゲームオブジェクトを作成するようなことを試していました。
    スクリプトをヒエラルキーにドラッグアンドドロップしてゲームオブジェクトが作られるようにする」 という記事にこれが書いてあります。大体やりたいことはできていたのですが、ドラッグしたままにすると問題があったりしました。(理解して使うなら十分使えるというくらいの出来でした。)

    これをショートカットキーでやれば良いのではないかと思いつきました。 
    こんな感じに改造しました。スクリプトを見つけるところはSelectionに任せ、作るかどうかの決定はメニューを押すことになりました。
    イベントを確実に取ることが簡単なので、この方法が一番良い気がします。 

    使い方は簡単でアタッチできるスクリプトを選択した状態でCtrl+Gを押すかコンテキストメニューのCreate->GameObject from Scriptを押すと、選択されているスクリプトがアタッチされたゲームオブジェクトをヒエラルキーに作成します。
    CreateGameObjectFromScript

    複数のスクリプトから同時にゲームオブジェクトが作成でき、C#のスクリプト以外を選択してあっても無視されます。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    List<T>クラスは非常に便利なクラスです。配列のように大きさが決まっていないので、要素が増えたり減ったりするものを扱うのに適しています。
    トランプゲームを作るときにカード一つ一つにint型のIDを割り当てています。一度に複数のカードを出すといったときにList<int>で複数のカードを表すと扱いが簡単です。リストの分だけ処理をforeachで行えば良いからです。

    Listを使うときに失敗したことをメモしておきます。たまに「あれ、どうなってるんだっけ?」となるもので、先日テストコードを書きました。

    1.Listを引数に渡したとき、内部で要素を足したり消したりすると影響が残る
    このスクリプトをUnityで適当なゲームオブジェクトにアタッチしてみた結果は次のようになりました。
    printListTest
    見てもらえば大体わかると思いますが、ListAddやListRemoveに渡したリストの内容が変わります。List<T>はクラスなので、参照型ということですね。参照型を引数に渡したときの挙動は気を付けないと、バグになりやすいです。

    2.リストのループ内でリストを変更してはいけない
    このスクリプトをUnityで適当なゲームオブジェクトにアタッチしてみた結果は次のようになりました。
    printListTest2
    この例は実行するとエラーを起こしますが、コンパイルは通ります。これはforeachループ内でリストに変更をしていることが問題です。(list.Remove(2)のところです。)
    実行すればわかるバグですが、この例よりも複雑なことをする場合には意外と気づきにくいバグになります。
    リストの要素を一つずつ検査していって、ある条件になっているものを除きたいというときに同じループ内で決してしまうということをやってしまうとエラーになります。RemoveAllメソッドを使うか、いったん削除対象を別のリストに入れてからそのリストのループ内で元のリストの要素を消していくことになります。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unity5.4以前ではAnalyticsのメソッドには以下の
    public static AnalyticsResult CustomEvent(string customEventName, IDictionary<string, object> eventData);
    という形式でカスタムイベントが発生したことを伝えていました。
    この形式しかなかったので、カスタムイベントに付随するパラメータがない場合は、eventDataにnullを渡し
    Analytics.CustomEvent("something" , null);
    というようにしていました。
    見た目からしてもnullを置くのは変な感じですが、これでうまくいっていたのです。 

    Unity5.4.0f3にしたところ、パラメータなしのカスタムイベントでエラーが発生しました。(NullReferenceExceptionが発生するので、とても危険です。Unityはこのエラーが発生しても落ちないのですが、ふつうは落ちます。なぜか処理が一部飛ばされて動き続けるようです。)
    ce_nre


    5.4では、Analyticsクラスに以下の2つのカスタムイベント用のメソッドが追加されています。
    public static AnalyticsResult CustomEvent(string customEventName); 
    public static AnalyticsResult CustomEvent(string customEventName, Vector3 position); 
    パラメータなしのカスタムイベントは上の引数が1つだけのものを呼び出すとエラーがなくなりました。おそらくこの対応で問題ないと思います。
    下の位置を渡すものはヒートマップに使うと思われます。 

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Pokémon GO(ポケモンGO)が配信され、日本中で遊ばれていると思います。このPokémon GOがUnityで作られているらしいので、どのようにして作られているのか考えています。(あくまでそれっぽいものを作るにはどうしたら良いかというものです。)Pokémon GOっぽいものとは、Pokémon GOを作ろうとしてできあがる妥協の産物のことです。

    まずPokémon GOがどんなゲームなのかを特徴を挙げてみる必要があります。
    • 位置情報を利用している
    • 現実の地図を簡易的に表示
    • 地図上にデータを配置
    • ARを利用し、ポケモンを現実の背景に配置
    特徴的なのはこんなところでしょうか?

    位置情報は端末がGPSを利用する機能を持っているAndroidとiOSであれば、LocationServiceを利用すれば取得できます。LocationService.Startにサンプルコードが載っているので、これは実に簡単に利用できます。必要な情報は緯度(latitude)と経度(longitude)です。自分の向いている向きはこれだとわからないので、他の方法が使われている気はします。

    緯度と経度が分かれば現在地点が特定されるので、周辺の地図データを取得しそれを描画するということができます。緯度と経度が変化すれば、データを更新するということです。データはサーバー側に保存するため、クライアント側の実装にはあまり関係がありません。(サーバー側はここが最も大変だと思いますけど……)

    ARについてはおそらく単純です。カメラを有効にしてそれを背景にしているだけだと思います。WebCamTextureを使えばカメラが撮っているものをテクスチャとして取得できるので、それを背景にすれば良いです。端末を移動させるとポケモンが現れるということを行うには、加速度センサー(Input.acceleration)を使ってカメラの向きを変えれば良いです。

    「Pokémon GOを作る」ためにはデータを作ることが最も大変であると思います。全世界の地図上にデータを置いていくというのは気の遠くなるほどのデータ作成になるでしょう。(ユーザーにデータを置かせて運営で判断する方法はとても賢い方法だったと思います。それでも初期のプレイ用データは作る必要がありますが。)

    「Pokémon GOを作る」ことは可能だと思いますが、作っても意味はない気がします。 それは位置情報を利用したゲームは常時起動しっぱなしで移動するため、2つのゲームを起動することはないからです。(メモリ使用の都合上2つ起動すると、おそらくはどちらかがメモリから消されるでしょう。)
    位置情報を利用したゲームを作ると、Pokémon GOに勝てるほどのものでないと遊ばれることはないでしょう。

    「Pokémon GOを作ろう」となったら、「まあ、Pokémon GOでもやって落ち着け」と言いましょう。遊ぶ方が楽しいですから。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unity Analyticsのカスタムイベントにはパラメータをつけることができます。Custom Eventについてでも書きましたが、Analytics.CustomEventの第2引数にパラメータをIDictionary<string,object>として渡します。パラメータの名前と値をセットにした配列を渡すだけなので、使い方を覚えれば簡単です。

    こんな感じに使います。実際のアプリで使っているコードを撮ったので、VariableManagerだのrefResultDataだのがありますが、気にしないで良いです。名前と値がセットになって並んでいるということが重要です。
    ts-old-analytics

    このカスタムイベントの情報を集めると、管理サイトでは以下のようにパラメータの一覧が見れます。上の2つはデフォルトで存在するもので、timeから下がスクリプトで書いたパラメータになります。(大文字小文字の区別はなくなるので、アンダーバーを使って名前を見やすくするのがコツらしいです。)
    ts-params

    levelパラメータは数字を区別したいため、文字列化(数字が頭につくと数字と解釈されているらしい。)して送っています。ゲームの難易度ごとでユーザーがどれくらい遊んでいるかを知ることができます。
    ts-level

    resultパラメータはユーザーの勝敗です。どれくらい勝ったり負けたりしているかわかれば、強すぎたり弱すぎたりするのを改善できます。
    ts-result

    これらの情報は非常に有用なのですが、実は失敗したことがあります。
    ある難易度を選んだユーザーの結果がどうなったのかということを知ることができないのです。Level1を遊んだ人がどれくらい勝ったのか負けたのかはわかりません。

    あるカスタムイベントに付属するパラメータのそれぞれは独立して記録されているため、組み合わせた分析ができないのです。上で言うと、levelがいくつでresultが何々のユーザーのグラフを表示することができないです。(いずれできて欲しいところです。)

    現状ではいくつかのパラメータを組み合わせた分析は以下のようにstringに情報を含めることになるでしょう。組み合わせにより、stringの種類が莫大になりやすいのでそこに注意する必要があります。
    ts-new-analytics

    Summaryという新しい項目に難易度とレベルと結果をすべて含めました。難易度の数、レベルの数、結果の種類の掛け合わせ分でも100いかない位だったので、とりあえずこのようにしました。Summaryという新しいパラメータ名にしたのは、過去のデータと区別するためです。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    正確なことはわかりませんが、おそらくは4月中旬くらいにUnityAnalyticsの管理サイトが更新されました。(UNITEの後で変わったのではないかと思います。UNITEで新機能など発表→いろいろ更新という流れ。)

    EVENT MANAGERという項目が以前と大分変わりました。それ以外は見た目が少し変わっていますが、機能的には大きくは変わってないと思います。以前の記事のスクリーンショットを見たら、EVENT WATCHERというものだったようです。名前も変わっているようです。

    EVENT MANAGERはこんな感じです。
     analytics-em-ts

    カスタムイベントのポイントの上限が1000から5000へと増えました。多くなったことでよりたくさんのカスタムイベントを作っても記録されるようになり、色々解析できるようになります。普通に使う分では1000でも多いので、増やす必要があるのかと思います。
    しかし、今後ヒートマップという新機能が作られるのでそのためなのかもしれません。これがどれくらいポイントを消費するの不明であり多くのポイントを消費しそうなのです。(位置情報を文字列にして、これをパラメータとして持つカスタムイベントを作れば現時点でもヒートマップに似たようなものを実装できます。位置情報の空間分解能によりますが、1mの立方体で10mの立方体の空間を覆うなら1000個の区別がされます。これだとポイントの消費が大きすぎて実用的ではないです。)

    カスタムイベントの消費ポイントが以前と異なるようです。
    以前は0か1個のパラメータを持つ場合の消費ポイントは1でした。今回からはカスタムイベント1つにつきまず1消費します。そしてパラメータごとに1ずつ消費ポイントが増えていくようです。文字列の場合は送る取りうる値の数だけポイントを消費することは以前と変わらないようです。
    消費するポイントは、カスタムイベントの分1と取りうる値の数分(数値のみパラメータの個数分)です。

    analytics-em-ts-custom
    EVENT MANAGERではカスタムイベントごとにどのようにポイントを消費しているか見れます。これで簡単に内訳が分かるようになりました。(やったね!超便利。) 
    またカスタムイベントの有効、無効が選択できるようになります。新しい実行ファイルでは使わなくなったカスタムイベントがある場合などはDisabledにするとよいでしょう。無効化してあるとポイントの消費が0になりますが、データの参照などそのカスタムイベントはアナリティクスでは全く使えなくなります。
    Disabledにしても後でEnabledにして復活することはできます。無効にしていた間のデータは存在しませんが、Enabledにしたときから集計が行われます。

     

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Asset Storeに「Gem Shader」 というものがあります。名前の通り宝石のような見た目を表現するシェーダーを提供するアセットです。デモシーンは以下のような感じです。

     
    宝石のようなモデルに専用のシェーダーがついています。デモシーンはリフレクションプローブを使用しているので、Quality Settingsでリアルタイムのリフレクションプローブを有効になっていないと、反射がされず真っ黒になってしまいました。赤く囲ったところのチェックを入れればちゃんと表示されます。
    gem-qs

    gems-noreflection

    言うまでもないかもしれませんが、きれいな表示がされる反面描画にはそれなりのコストがかかります。モバイルで使うにはドローコールが多くなりすぎる気がします。(ひとつのマテリアルごとに2ドローコール。)
    アセットに入っているモデルにStandardシェーダーを適用したものを使ったりすると、描画負荷を減らすことができます。 

    このページのトップヘ