FreelyApps

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


    アプリ『トランプ・7並べ』を公開しました。
    Android/iOS https://goo.gl/zsFces

    カテゴリ:Unity > Script

      このエントリーをはてなブックマークに追加 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
    Invokeという便利な関数があります。これは好きなメソッドを何秒か遅れて呼ぶという機能です。ある時点で発生することは確定するが、少し遅らせて処理をしたいという時に使うと簡単に処理ができて便利です。

    「ボタンを押したときにSEが流れて、それからWebページに飛ぶ」という処理を作るときにこれを使いました。ボタンを押した瞬間でSEを再生するのですが、同じタイミングでWebページに飛ばしてしまうと制御がブラウザーの方に切り替わってしまい、SEが途切れてしまいます。
    これを防ぐため、 ボタンを押した瞬間でSEを再生し、同じタイミングでWebページに飛ばす処理をInvokeで予約しておくのです。0.5秒ほど(SEの長さによります。)遅れてWebページに飛ばすにはInvoke("GoWebpage", 0.5f);というようにします。

    すぐに処理を行わないので、1度しか実行したくない場合でも複数回実行してしまうミスが起こりやすいです。フラグによる制御でも良いし、CancelInvokeで呼ばれているものを消してからInvokeしても良いです。IsInvokingを調べて予約されていたら、Invokeしないというのでも同じことです。

    何回も処理を呼ぶInvokeRepeatingというのもあります。 間違って使うと処理の流れを複雑にしやすいですが、処理の遅延を簡単に書けるのは重宝すると思います。
    Invokeで呼んだ中でInvokeを呼ぶような構造にすると、厄介なので遅延のために1回だけ使うような単純な使い方が良いと思います。

    InvokeはMonobehaviourのメソッドで、アタッチしたゲームオブジェクトが消えると呼び出しは破棄されるようです。Invokeで重要な処理を呼ぶときは、ゲームオブジェクトが破棄されないようにしておくことになります。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    レビュー依頼をすることはGoogle Playで可能です。
    規約に違反するような可能性もあるので注意が必要なものではあります。公式の説明に違反の例があります。評価に対し報酬を与えるなどして、評価を操作することがいけないとあります。「★5の評価をしてくれたら、○○のアイテムをあげます!」とかは違反だということです。

    レビューについてやれることは、レビューのページ(アプリのあるページのこと)へのリンクをアプリ内に置くことだけです。ボタンを押したらレビューのページに飛ばすということが唯一許されています。以前は公式のページにもユーザーにレビューを求める例としてダイアログの画像があったのですが、今は消えています。したがって、将来的にはレビューをすることを頼むことも禁止されるかも知れません。

    良いレビューが増えるとランキングや新しくアプリをダウンロードしようという動機付けに一役買うでしょうから、アプリにレビューを依頼する処理を作ることは良いかもしれません。レビューしやすくなったために悪口ばかり増える可能性もあるので、単純にリンクを作るというのは駄目な気がしています。
    レビュー内容についての操作は違反なので、レビューの依頼で表示する文言は「レビューをお願いします。」というだけになります。下のような感じです。(レビュー依頼の実装は『トランプ・スピード』のものです。)
    request-review-dialog

    しかしながら開発者側に意味あるレビュー(アプリの内容に具体的に触れていて、使ったことのない人にもわかるようなもの。その上で……できれば好意的なもの。)が増えてほしいから、レビュー依頼を作るわけです。文言を変えずにそのようなことをするには、レビュー依頼のダイアログを表示するタイミングを設定するのが良いと思います。
    良いレビューをするには繰り返しアプリを遊んでもらう必要があると思います。そのため何回もリザルト画面に来ているなら、レビュー依頼をするというようにしてみました。「後で」以外のボタンを押した場合は、この後でダイアログを出すことはありません。そうすることで望んだ人以外はダイアログを何度も見ることにならずゲームの進行の邪魔をしないようにしています。

    レビューページへの遷移は、
    Google Play上のアプリのURLに飛ばすだけで可能です。レビュー依頼をするかというフラグとリザルトに何回来たかをセーブデータに持つことで何回も遊んだ人だけにダイアログを出すということが実現できます。やってみると意外と簡単な処理だと思います。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    3Dではキャラクターを動かす処理を作ることが多いです。UnityでもCharacter Controllerというものが用意されています。これをゲームオブジェクトにつけてスクリプトでMoveを処理してやれば、移動ができます。

    Character Controllerは簡単に移動処理が作れるのですが、物理挙動がなされないという特徴があります。コライダーとの衝突を検知してめり込まないとかは行われるのですが、他のコライダーにぶつかってもそれに衝撃をあたえるということはデフォルトでは起こりません。OnControllerColliderHitを使うと、他のコライダーにぶつかったときにどうするかが制御できますが、どのような力を相手に与えれば適切なのかというのは物理の知識が必要です。
    リアリティを追及するには多少の知識が要りますが、入り口が緩いのがCharacter Controllerだと思います。

    Rigidbodyを制御して移動を行う方法があります。Rigidbodyを使えば物理に沿った動きを自動的に実現してくれます。移動の制御をおこなうには、力を加えるか速度を指定するかで可能です。
    力を加える場合は自然な動きになります。AddForceを使って移動方向に押してやることになります。慣性があるので動き出しと停止時にすぐに反応することはなくなります。ゲーム的にレスポンスが良くなくなるので、使いにくいことも多いでしょう。また、力の働き方によっては移動ではなく回転の方に使われてしまい、毎回挙動が変わるということも起こります。とくに問題になるのが、短い時間に力が何度も入力されることです。この場合、一気に加速されることになります。
    速度を指定する方法は自然に反しますが、ゲーム的には使いやすいです。 ある速度に強制することで瞬時の反応が可能であり、入力が連続で起こっても問題が起きにくいです。
    これはRigidbodyを使ったキャラクターの移動になります。カプセル型のオブジェクトがキャラクターです。ジャンプの処理は、地面に乗っているかを判定し上向きに速度を設定するだけで可能です。地面に乗っているかの判定は意外と難しい問題がありますが、レイキャストをキャラクターから下向きに発射しぶつかったものが何かで地面に立っているかを調べています。

    このツイートで重要なのは、TPS的な操作を実現していて物理的な動作も可能になっているということです。カメラはキャラクターの周り(キャラクターを原点とする極座標として計算)に位置するようにしています。
     

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unityでゲームを作るとしてもやはりプログラミングが必要です。通常のゲームプログラミングとは異なる考えで作ることにもなるので、プログラミングをやったことがある人はむしろ混乱するかもしれません。
    Unityのプログラムはスクリプトと呼ばれ、 これをゲームオブジェクトというものにくっつけることで動きます。ゲームオブジェクトは様々な機能のスクリプトをくっつけることができるものです。”もの”というのはそれ自体で何かするのではなく、機能をつけることでライトになったり、カメラになったり、モデルになったりと何でもなれるけれど何でもないという置き場所のような意味です。

    何かを始めるときに覚えることが多いと嫌になります。UnityのGUI操作は覚えることがそれなりに多いので、プログラミングはとりあえず作れるようになるくらいで始めるのが良いと思います。少なければいくつでも良いですが、3つだとわかりやすいと思ったので3つの関数について説明します。

    ゲームを作るために必要な3つの関数とは、
    1. Update
    2. Start
    3. Awake 
    のことです。この名前をつけたメソッドはUnityから呼び出されて処理されるという特徴があります。これはイベント関数と呼ばれています。Updateは毎フレーム呼ばれる最も重要な関数です。ゲームの処理はここに書いていくことになるでしょう。この関数だけあればゲームを作ることもできますが、初期化は別に分けられていた方が都合が良いです。そのための処理がStartとAwakeです。 
    名前からだと微妙なところですが、Awakeが先でその後にStartが呼ばれます。通常はStartだけ使うだけで十分だと思います。AwakeとStartの微妙な違いを理解できるようになったら、他のイベントも覚えていることでしょう。

    よくAwakeとStartが呼ばれないということで問題になることがあります。大抵の場合、そのスクリプトがついているゲームオブジェクトがアクティブでないかスクリプト自身が有効でないと思います。
    それ以外で呼ばれないとすれば、ゲームオブジェクトにつけてないか名前の打ち間違えでしょう。 

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unity5.3からJsonUtilityという機能が追加されました。
    オブジェクトをJSONにすることとJSONからオブジェクトを作る・値を入れることができます。JSONはオブジェクトを文字列で表現する方法のことであり、単純な仕組みでエンコードされるために広く使われているフォーマットです。C#ではxmlやバイナリの形式にオブジェクトをシリアライズ(ファイルに保存できるような形式にすること)できますが、JSONはライブラリを使わないと使えません。 
    Unityで公式にサポートしてくれると使いやすくなるので、データの持ち方に1つ選択肢が増えたことになります。

    JSONの形式は文字列であるので、拡張性が高く柔軟だというのが特徴だと思います。あるオブジェクトAをJSONにして、別のオブジェクトBにデシリアライズ(オブジェクトに戻すこと)が可能です。なぜならJSONが文字列であり、データに何が含まれているのかを表しているだけだからです。

    これと異なる特性をもつのがバイナリです。データをメモリ上での表現をそのまま出力したような機械にとってわかりやすく、人間にわかりにくい表現です。
    バイナリ形式(BinaryFormatterを使った場合)は保存したオブジェクトと同じ型でデシリアライズしないとエラーになったり、バイナリにはフィールドがあるのに復元するクラスにはフィールドがなくなった場合にもエラーが起きます。これはBinaryFormatterで作ったデータになんらかの型情報が含まれていることを表しているでしょう。データがどういったクラスにより作られ、どういった要素が含まれているかがデータにあるために変更すると一致しなくなるのです。(どういった条件でデシリアライズできなくなるかまで調べていませんが、基本的にはフィールドの変更はしない方が良いと思います。)
    バイナリ形式はクラスの型を変更できないデメリットがありますが、文字列の解釈がないため一般的に変換が高速です。バイナリ形式の場合で将来拡張するなら、クラスに余分なフィールドを入れておくことが一番良い対策になります。余りに多くの使わないパラメータがあると無駄が大きいので、どれくらいの容量を取るかは判断が難しいところです。

    JSONを使う方が頻繁にアップデートが存在するアプリには合っているので、これを使っていた方が困らないことが多いでしょう。
    JsonUtilityは現在のところ型を利用する方法のみが使えるようです。JSONにシリアライズ、JSONからデシリアライズするためには変換用にクラスや構造体が必要だということです。 (intの配列とかをそのまま渡してもシリアライズできないです。)JSONに対してそのままデータを検索したり、データを追加したりするのは将来的にはあり得ると思います。(ドキュメントに「The JSON Serializer does not currently support working with ‘unstructured’ JSON」とあります。currentlyなので将来的にはサポートする気がします。)


    JsonUtility.ToJsonはそこまで注意することはないです。publicなフィールドをJSON化してくれます。NonSerialized属性をつけるとそのフィールドは無視されます。JSON化されるものが何かはインスペクタ―上で表示されているものと一致しているようです。例えば[SerializeField]がついたprivateなフィールドはJSON化されます。また逆に[System.NonSerialized]をつけるとpublicなフィールドでもJSON化されなくなります。
    ToJsonの第一引数はオブジェクトを渡します。
    [System.Serializable]
    public class Test
    {
        [SerializeField]
        private int a = 1;
        public int b = 2;
        public int[] c = { 12345 };
     
        public float f = 0.22f;
    }
    というクラスを定義して、これを作りJSONにしてみます。
    Test test = new Test();
    var json_test = JsonUtility.ToJson(test);
    というようにすると、json_testには以下の文字列が入っています。
    jsonNotPrettyPrint
    fの値が0.22に一致しないのは誤差のせいでしょう。JSONのデータはわかりやすい構造をしていて、データが正しいかのデバッグがしやすいです。

    第ニ引数にtrueを渡すと、下のように見やすく出力されます。改行とかインデントのための空白やタブは情報を持っていないので、情報としては無駄です。保存する場合やプログラム内でのやり取りにはPrettyPrintは使わないようにし、デバッグ用に使うのが良いです。
    jsonPrettyPrint


    JSONからオブジェクトを作るのは2通りあります。
    JsonUtility.FromJsonJsonUtility.FromJsonOverwriteです。
    Test obj = JsonUtility.FromJson<Test>(json);
    のようにすると、文字列jsonからTestクラスが作られます。使い方はシンプルなのですが、与える文字列jsonには注意が必要です。文字列はJsonUtility.ToJson等を使って同じクラスを表すJSONを使うのが通常でしょうが、特に制限はないです。全く異なるクラスから作られたJSONを渡したり、自分で文字列を作って渡しても問題ありません。
    そういった場合、余分なフィールドや型の異なるフィールドが出てくるでしょう。FromJsonはうまくできているようで、これらのものはすべて無視されます。FromJsonは型パラメータに渡したクラスのシリアライズできるフィールドを対象にし、JSONからフィールドの名前と型が一致したものだけ解釈しています。
    対応するものがないフィールドについては注意が必要で、このときフィールドはデフォルト値にされます。(例えばintだったら0です。)

    Unity5.3.4f1で試したところ、対応するものがなくてもデフォルト値にされてないように思います。
    [System.Serializable]
    public class Test2
    {
        public int a = 0;
        public int c = 5;
    }
    というクラスを新たに作り、
    Test test = new Test();
    var json = JsonUtility.ToJson(test);
    print("json:" + json);
     
    var from_json = JsonUtility.FromJson<Test2>(json);
    print("from_json:" + JsonUtility.ToJson(from_json));

    のように処理を書いてみました。 Test型のインスタンスをJSONにしたものを出力し、そのJSONからTest2のインスタンスを作っています。作ったTest2型のインスタンスのフィールドがどうなっているか知るため、JSONで出力しています。
    実行した結果は以下のようになりました。
    fromjson

    jsonにはcというフィールドがないので、Test2としてデシリアライズするときにcがデフォルト値0になるかと思ったのですが、そうはなりませんでした。JsonUtilityのバグかドキュメントのミスかも知れません。


    JsonUtility.FromJsonOverwriteはデシリアライズするJSONに復元しようとするクラスの対応するフィールドがない場合、そのフィールドには何もしません。
    上に述べたようにJsonUtility.FromJsonでも何故かフィールドの値が変更されてないため、現状ではインスタンスの生成の有無で使い分けることになると思います。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unityで新しいC#スクリプトを作成すると以下のような状態になっていると思います。
    これをそのまま使っても、編集しても良いのですが、いつも使う名前空間などはテンプレートに入っていると楽で良いと思っていました。System.Collections.Genericを使うのでSystem.Collectionsは使ったことがないですし、英語のコメントも慣れてくると要らないです。

    あるC#スクリプトをDuplicate(Ctrl+DかEdit->Duplicateでアセットの複製ができます。)してリネームするでも良いのですが、ちょっと手間なのでテンプレートを変更することでより便利になります。
    スクリプトのテンプレートを追加する」 Unityな日々(Unity Geek)より
    でその方法を解説がされているので参考に試してみました。
    スクリプトのテンプレートが保存されているのは、私の場合
    C:\Program Files\Unity\Editor\Data\Resources\ScriptTemplatesでした。Unityのインストール先を見つけてEditor\Data\Resources\ScriptTemplatesと探していけば良いでしょう。
    script-template
    フォルダには以上のようなテンプレートが並んでいます。C#のスクリプトのテンプレートは81-C# newScript-NewBehaviourScript.cs.txtです。これを自分の好きなように編集して上書きしてやると、Unityで作られる新しいスクリプトが変更されます。(Unityが読み込むファイルの変更なので、全プロジェクトに影響します。)

    このファイルを「81-C# newScript-NewBehaviourScript.cs」という名前でスクリプトのテンプレートが保存されているフォルダにコピーすると、Createメニューに項目が以下のように追加されるはずです。(別名にすることで元々のテンプレートを保持したままメニューに新しいものを追加されるようです。古いのが要らなければ上書きでも構いません。テンプレートのファイル名はハイフン区切りで意味を持つようです。)
    myTemplate
    この新たなメニューでスクリプトを追加すると以下のようになります。 
    usingにSystem.Collections.GenericやUnity5になってから追加されているよく使う名前空間を入れています。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unityでアプリを作成したとき、SNSへの投稿を簡単にすることができるものがあります。「Social Connector」というものでUnityの人が作っていて有名なようです。

    AndroidとiOS用に作られていて各種SNSに投稿できるようになるプラグインとなっています。(正確に言うとAndroidはC#のスクリプト上で実装されているので外部コードを含まずプラグインではないです。iOSはObjective-Cを含んでいるのでプラグインです。まあ、気にしなくていいことですが。)

    テキストとURLと画像を1枚まで投稿することができます。URLや画像はオプションです。画像を使う場合は画像ファイルのパスを渡します。画像を使う場合はその画像にアクセスできるようになっていることが重要です。(権限や存在を確認する方が良いです。)
    利用に関してのライセンスも制限が少なくて利用しやすいです。


    今のところ私はAndroidでしか利用していないのですが、将来的にはiOS用のアプリを使って利用したいと思っています。そこで少し気になることがあるのです。

    iOSではLINEのアイコンを入れることが必要です。(Androidでは入れなくても問題なく使えます。)
    このLINEのアイコンはここでダウンロードできるのですが、 してはいけないこと変形・加工と明記されています。アイコン(PNG)をダウンロードすると、350x350の画像しかないので、Social Connectorの要求する57x57、114x114、120x120、180x180、72x72、144x144をどうやって用意すればいいのかがわかりませんでした。加工が禁止されているので、画像を縮小することもできないので、どこかに他のサイズを公式に提供しているのかもしれませんが……

    上に書いた懸念はロゴを改変してないということでおそらくは杞憂なのでしょう。そうでなければサイズ違いのアイコンを使うアプリで使用できなくなってしまいますから。

    このページのトップヘ