FreelyApps

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


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

    カテゴリ:Unity > Script

      このエントリーをはてなブックマークに追加 Clip to Evernote
    以前、同じ内容で記事を書いたのですが、より良い方法がUnity5.3では行えるようです。

    rn-custom_coroutine
    Unity5.3で新しく使えるようになったカスタムコルーチンというものを使います。 公式の説明を見るとCustomYieldInstructionクラスを継承して、keepWaitingプロパティのgetをオーバーライドすれば新しいyieldオペレーションが作れます。
    yieldオペレーションとは、yield return ~とするときの~の処理のことを言っているのだと思います。(この記事ではそうだと思って書くことにします。)
    keepWaitingがtrueであればyieldオペレーションが処理を返さず継続し、falseになると処理を抜けます。このプロパティにDebug.Logを仕込んだところ毎フレーム呼ばれていたので、コルーチンで無限ループ内にyield return 0;というような書き方で行うのと同じと考えて良いでしょう。

    以前の方法のあまり良くない点は、処理をクラス内に書いていなければいけなかったため、あるクラスのstaticメソッドにするかクラス内での利用しかできないようになっていたことです。色々なところから自由に呼び出すということを考えると、その処理は独立した1つのクラスになっている方が見通しが良いのでそうすべきだと思っていました。
    メソッドに処理がある場合は、呼び出し方がStartCoroutineを介するというのもいまいちだと思います。StartCoroutineを使うと、渡すパラメータの個数の制約があること、わずかにコードが増えること、文字列での呼び出しだとエディター上で編集しにくいことが良くないです。
    新たな方法では導入が手間ですが、その後の作業を減らしてくれるため良い方法だと思います。WaitUntilやWaitWhileを使っても同じことができますが、よくある同じ処理をいちいち書くのは無意味なので、以下のようにカスタムコルーチンを作りました。

    上のソースは、ある時間処理を待たせるがスキップも可能であるというyieldオペレーションになります。使い方は
    yield return new SkippableWaitForSeconds(10, () => Input.GetMouseButtonUp(0));
    のようにします。この例だと10秒待つ処理であり、もしその間にマウスの左ボタンが上げられたらスキップするということになります。
    スキップするかの判定を外から渡せるため、色々と使えるようになっています。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Debug.Logやprintはデバッグのために非常に役立ちます。
    重要な処理をする前後に置いて処理が正しく終了まで到達したかを調べたり、 UIがない画面での処理結果を表示したりして確認に使えます。

    使いやすくて色々な所に書きすぎることになるかもしれません。そうなってくると便利なはずのログがとたんに牙をむきます。どのログが重要なのかがわからなくなり、あまりに多くなってくると無視しても良いのだろうかと疑念を抱くことになります。
    作るときに仮に置いたものなどは役目を終えた時点で破棄していくべきですが、そう簡単に消しきれないこともあります。

    Debug.Logをそのまま使うのではなくこれをラップしたメソッドを作って、それを呼び出すようにするのが良いと思います。

    public void MyLog(string message)
    {
    Debug.Log(message);
    }
    とかでも十分です。 こうやってDebug.Logを呼ぶだけだと変わらないと思われるでしょうが、MyLogを通して呼び出していることが重要なのです。
    色々な拡張ができますが、MyLogの中のDebug.Logをコメントアウトすれば、ログが出なくすることもできます。 前後に余分な情報をつけるといったことも簡単です。自分で改造できるメソッドなので、直接Debug.Logを呼び出すよりも融通がきくわけです。

    ログをカスタマイズするという意味では上の方法で良いと思います。ログをリリースまでは出しておき、リリース時になくすというような使い方をする場合はConditional属性を使うとより良いと思います。 

    [System.Diagnostics.Conditional("MY_LOG")]
    public void MyLog(string message)
    {
    Debug.Log(message);
    }
    というように書きます。 このようにするとMY_LOGというシンボルが定義されているときにMyLogは実行されます。色々なところでMyLogは他のメソッドと同じように呼び出すことができます。使う側では特に意識することなく使えるのにシンボルが定義されているかによって呼び出しが行われなくなるというのが使いやすいのです。

    #ifを使っても似たようなことはできます。
    #if MY_LOG
    public void MyLog(string message)
    {
    Debug.Log(message);
    }
    #endif 
    とすればいいのですが、呼び出す側でも#ifで囲わなければなりません。Conditional属性を使うと、これがなくても良いのでコードが読みやすくなり、書く分量も減ることになります。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    少し前にツイッターで他の開発者の方のアプリでBackキーでアプリが終了しないとのクレームが来たというのを耳にしました。
    nback
     
    Androidの画面の下の方にある3つのボタンの左側のものです。このBackキーというのはUnityでアプリを作ると特に機能が割り当てられず押せるだけのボタンになっています。真ん中のホームボタンと右側のメニューボタンはAndroidの本体の機能がついているので、特に何もしなくても普段通りの機能をします。

    Backを押したときにどのような機能をするかは開発者が自由に決められます。前の画面に戻っても良いし、
    アプリを終了するボタンにしても良いです。わかりやすく多い機能としてはこんなところでしょうか。
    Android4.4以降ではこのボタンたちを消すことができてフルスクリーンを利用できるようになっています。以前書いた記事があるのでリンクを張っておきます。

    Unity5ではデフォルトでフルスクリーンなのですが、それをやめてナビゲーションバーを出すようにも設定できます。それについても以前解決したことなので記事にしていました。ある画面ではフルスクリーン、別の画面ではナビゲーションバーを出すということも可能になっています。
    ゲーム中は全画面を使えた方が良いと思うのでフルスクリーンにして、メニューではゲームを止めることも考えナビゲーションバーを出すようにするのが良いと思います。

    Backキーを押したときにアプリを終了したい場合は以下のコードを適当なゲームオブジェクトにつけておけば良いです。全シーンにわたって同じ挙動をするなら、破棄されないゲームオブジェクトにつけておくと簡単です。
    KeyCode.EscapeというのがBackキーに割り当てられているようです。GetKeyUpでなくてももちろん構いません。

    void Update()
    {
    if (Input.GetKeyUp(KeyCode.Escape))//backキー.
    {
    Application.Quit();
    }
    }
     

      このエントリーをはてなブックマークに追加 Clip to Evernote
    アニメを作る方法はいくつかありますが、ここではコルーチンを使った方法を想定しています。コルーチンは逐次的に進んでいく処理を作ることが簡単なので、アニメーションを実装するには便利な方法です。無限ループを持つことで毎フレーム処理することも可能です。

    アニメを作った後でスキップしたいということがあると思います。特に頑張って作ったアニメは時間がかかりがちなので、ゲームのテンポを悪くしてしまいます。画面をタップしたり、タッチしている間はアニメをスキップするといったことを望まれることでしょう。
    スキップ処理はかなり面倒なことが多いです。本来処理すべき内容を行わないのですから当然です。
    処理を先に行い、表示だけを変化させるアニメを作る設計がふつうかと思います。アニメする状態とアニメが終わったときの状態を作り、最後にはアニメが終わったときの状態を通るように設計すれば、スキップの実装はそれほど難しくはありません。アニメ状態中に強制的にアニメが終わった状態に遷移させれば、スキップになります。
    単純なアニメはそれで可能ですが、色々絡み合ってしまうと非常に難しくなります。

    最も単純なアニメーションは待つことです。表示して、少し待つということを繰り返すだけでも躍動感は出ます。結果表示とかでよくあるものです。
    これを簡単に作るにはWaitForSecondsを使います。
    anime


    上のコードでは、WaitForSecondsを呼び出し1.0秒待機しています。表示するというコメントのところに何らかの処理を書けば良いわけです。
    WaitForSecondsを呼び出すと処理が止められてしまうので、これではスキップができません。スキップを行うには入力の受付が必要なので、毎回処理するところが必要なのです。

    スキップを行えるよう入力を受け付けるコルーチンを書いてみました。 
    コルーチンの中で新たなコルーチンを呼び出すということをしています。yield returnにStartCoroutineを渡すと、渡したコルーチンが終わるまで処理が遅延されます。SkippableWaitがスキップが行える「待ち」の処理です。WaitForSecondsと同じように使えるので、使いやすいと思います。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Resources.Loadを使うと、動的にアセットを読み込むことができます。 動的にというのはシーン内に置かれていないかシーン内に置かれたゲームオブジェクトからの参照もないアセットを読み込むという意味です。
    Unityはシーンに必要な全てのアセットを自動でロードするので、シーンに置かれているもの、シーンに置かれているものが参照しているものは全てシーンの起動前にロードされるのです。

    使うためにはアセットをまず特別なフォルダに置かなくてはいけません。Resourcesというフォルダをプロジェクトのどこかに作ります。プロジェクトビューの直下(Assetsの直下)に置いても良いですし、どこか別のフォルダの下でも構いません。複数作ることも可能ですが、慣れないうちはひとつにしておいた方がわかりやすいです。
    Unityはいくつかのフォルダ名を特別扱いしています。 Resourcesもその中のひとつでResources.Loadでアクセスできる場所になっています。
     
    Resources.Loadの引数は文字列であり、これはパスを表します。Resourcesフォルダ内のどこにあるかをパスで記述します。Resourcesフォルダ内のPlayerという名前のプレハブであればResources.Load("Player")で読み込むことができます。Object型で返ってくるので、キャストが必要になります。
    Resourcesフォルダ内にさらにフォルダAがあり、その下のBGMというオーディオクリップを取得したい場合はResources.Load<AudioClip>("A/BGM")として読み込むこともできます。
    /を使って階層的になっているフォルダも対応できます。

    どのようなアセットでも動的に読みだせるので、使い道は広いです。共通で使うものはシーンに置いた方が便利ですが、特定の条件でしか使わないものや容量の大きいアセットの読み込みをどこかで行うといった使い方がありえます。シーンに全て置くことが可能なら、それも扱いやすいので無理にResources.Loadを使う必要はありません。

    Resources.Loadで読み込んだアセットはシーンが変わると破棄されます。シーンがひとつしかないとかしばらくシーンを変更しないがアセットはもう使わないのでメモリから解放したいということがあると思います。そういったときは読み込んだアセットを参照しているところをすべてなくした後、Resources.UnloadUnusedAssetsを呼び使われていないアセットを解放することができます。

    • 今読み込まれているのは何か?
    • 読み込み中なのか?
    • どれくらい読み込みを行うか?(メモリの限界の問題)
    • シーン切り替え時の対処
    といった問題が出やすいので、それに気をつけて使ってください。読み込んだつもりがアセットがないとかはエラーを引き起こしたりします。ロードのしすぎでメモリが足りなくなる問題は防ぎようがないので、いくつかの実機で試すしかありません。
     

      このエントリーをはてなブックマークに追加 Clip to Evernote
    WWWクラスを使うとWebサーバーと通信して、データを取得できます。
    適当なサーバーに画像や音楽を置いておき、WWWを使ってそのURLにアクセスします。そうすると画像や音楽をダウンロードし、それをゲーム内で使うということができます。
    Unityで用意されているアセットバンドルという仕組みを使うとさらにWWWの用途は広がります。アセットバンドルはシーンやプレハブなどのUnityで使えるアセットを配信することができる仕組みのことです。Google PlayやAppStoreで配信されたときは数十MBであったアプリが、数百MBにも大きくなっていく場合はこれを使っているのでしょう。
    WWWを使ってサーバーとのやり取りもすることができます。HTTPでの通信をしているので、色々と使うことができるわけです。

    AndroidとiOSでもこの機能が使えます。AndroidとiOSではOSの機能を使っているようでキャッシュという問題が発生します。
    キャッシュはサーバーにいちいち問い合わせて通信する手間を省くために本体に一時的に保存されているデータです。同じURLでのリクエストはまずキャッシュがあるかを調べ、なければ通信を行います。キャッシュがある場合はそれをリクエストの結果として返します。この仕組みがあるためキャッシュが残っている間、同じURLでのリクエストは更新されません。
    対策としてはURLを変えるしかありません。URLのクエリパラメータに意味のないパラメータを追加し、その値を毎回変更すれば良いです。

    アクセスしたいURLがhttp://test/であったら、http://test/?nonsense=ランダムな値
    とかにすればいいのです。nonsenseというパラメータの名前にしましたが、これはサーバーが処理しないパラメータならば何でも構いません。パラメータの値が毎回変わればURL全体として、毎回変わることになります。URLが違えばキャッシュは使われないので、毎回通信して最新のデータを取得することができるようになります。

    WWWがキャッシュされるのを防ぐ」 という記事が日本語ヘルプデスクに挙がっています。この問題は発覚しにくいものであり、何がまずいのかもわかりにくいものです。WWWを使ってなんだか更新が行われないというときはキャッシュによって更新がすぐに行われていないかもしれないということを知っておくべきです。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    UnityのプログラミングはC#を使うのが一般的です。CやC++をやってきた人であればグローバルな#defineが使えないかと思うことでしょう。
    C#には#defineディレクティブがあります。ファイルに定義するものなので、他のファイルでこれを参照することができません。CやC++にある#includeディレクティブがC#にはないので、定義はファイルごとにしていかなければなりません。(C#で使えるプリプロセッサディレクティブはここにあるものです。)

    グローバルな#defineを行うには、Unity Editorにシンボル(文字列)を登録することで可能です。 

    Player Settings->Other Settings->Configurationのところに以下のようにScripting Define Symbolsという枠があります。各プラットフォームごとにありますので、定義を有効にしたいプラットフォームを選択してScripting Define Symbolsに設定を行ってください。
    定義の仕方は、
    TEST;TEST2;TEST3
    というようにセミコロンで各定義を区切って入力してください。
    scrdef
     
    Unityのマニュアルではこのページの下部に「プラットフォームのカスタム定義」と「グローバルのカスタム定義」という内容が書いてあります。上で説明したのは「プラットフォームのカスタム定義」になります。
    「グローバルのカスタム定義」は専用の定義ファイルを用意するので、使っていると結構面倒です。複数のプラットフォームで同じ定義をしたいときにこれを使います。 

      このエントリーをはてなブックマークに追加 Clip to Evernote
    C#でスクリプトを書くときは通常クラスごとにファイルを一つ作るでしょう。公式の解説にもあるようにメニューからスクリプトを作り、名前が選択されているときにリネームするのが便利な作り方です。

    Assets->Create->C# Scriptと選べば、プロジェクトブラウザー上にNewBehaviourScriptというスクリプトが追加されます。以下のように追加したタイミングではファイル名が変更できるようになっています。この瞬間だけ名前を変えることで、ファイルの中身にある文字列も一緒に変わります。
    newscript

    うっかりEnterキーなどを押してNewBehaviourScriptのままになってしまった場合は、ファイルのリネームとクラスのリネームは別々に行えば問題ありません。意外と面倒なので、ファイルを消してやりなおした方がおそらく楽です。

    Test.csというファイル名に変更した場合は以下のようなスクリプトができます。

    classのところがTestになっていて、ファイル名と一致しています。 
    ファイル名とクラス名が一致しているとゲームオブジェクトにアタッチできるということが重要です。 なぜそうなのかというとUnityのルールだからです。(C#ではファイル名とクラス名が一致しなくても良いようです。)
    ゲームオブジェクトにアタッチするというのはスクリプトを簡単に呼び出すことができ、Unityでは以下のようにドラッグ&ドロップするだけです。


    GameObjectにTestというコンポーネントが新たに設定されているのがわかると思います。



    ファイル名とクラス名が一致しない場合どうなるかというと、アタッチできません。そのようなスクリプトにはインスペクタ―上に以下のような注意書きが載ります。MonoBehaviourを継承し、かつファイル名とクラス名が一致していないという注意です。これは問題ない場合もあります。(static classとかpartial classとか)

    nomono

    この注意書きが出ているスクリプトをアタッチしてみると、警告ダイアログが出ます。

    errdi

    警告にも書いてあるようにアタッチできないスクリプトということがわかりました。ただし、GUIではできないというのが今回の本題です。

    GUIでの操作は駄目でしたが、スクリプトから
    ファイル名とクラス名が一致しないスクリプトをアタッチすることができます。やり方はAddComponentを使うことです。 

    まずファイル名とクラス名が一致しないMonoBehaviourを継承したクラスを用意します。以下の例はファイル名がNotSame.csであり、クラス名がNotSameScriptというクラスだけのスクリプトです。

    このクラスをアタッチするために次のようなTest.csを用意しました。

    Testスクリプトにはゲームオブジェクトの参照が渡せます。渡した参照のゲームオブジェクトにNotSameScriptをアタッチすることになります。

    こうしてアタッチすると正しく動きます。StartとUpdateしか試していませんが、動いていました。

    fatt

    corratt

    上のように本来アタッチできないスクリプトをアタッチしたところ、アイコンが微妙に変わりました。下の図はアタッチできるTestスクリプトの場合です。コンポーネントの名前の左のアイコンとScriptという枠が違います。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Q:コルーチンに渡すメソッドをIEnumeratorでないものにしたらどうなるのか?
    A:実はSendMessageみたいな動きをします。 


    コルーチンを開始するにはStartCoroutineを使って行うのが一般的だと思います。3種類のオーバーロードがあります。メソッドをそのまま渡すIEnumeratorを引数に取るもの、メソッド名を引数に渡すもの、メソッド名とそのメソッドに渡すパラメータを引数にするものです。
    • StartCoroutine(Func1());
    • StartCoroutine("Func2");
    • StartCoroutine("Func3", 3);
    上のように使い方が異なります。今回の記事で取り上げるのは2番目と3番目のメソッド名を渡してやる場合です。
    本来コルーチンはIEnumeratorを返すメソッドとして実装しなければなりません。StartCoroutineの第2、第3の形式では文字列の指定をするだけなので、IEnumeratorを返さないメソッドを指定することができてしまいます。
    正しい使い方ではないのですが、実はちゃんと機能するみたいです。(公式の説明はないが検証してみると問題がなかったという意味です。)
    引数に指定したメソッドの引数は0か1個でないと駄目ですが、引数に合わせた適切なパラメータを渡すようにしてやるとそのメソッドを単に呼び出すようになります。
    内部的に同じかはわかりませんが、SendMessageという機能と何ら変わらない使い方になります。(SendMessageのOptionがない場合と同等でした。)

    以下に検証したときのコードを載せておきます。この機能を使ってみようという人は、ソースをダウンロードするなり、コピーするなりして検証してみると良いと思います。


    へぇーという感じですが、まさしくそれだけです。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    C#でスクリプト上で重い処理を作ったとします。大きなリストの複雑なソートをするとか、大量の計算を行うといったことです。
    そういったときスクリプトのある特定の部分に書いてあることが、どれくらいの時間で処理されるのかということを知りたいことがあるでしょう。 こういったときはC#のStopwatchクラスを使うと簡単に実行時間が測れます。以下にコードを載せておきます。


    ストップウォッチクラスを作り、Startします。処理が終わったタイミングでStopし、ElapsedMillisecondsでかかった時間をミリ秒単位で取得しています。非常に簡単なコードなので、簡単にパフォーマンスを測れます。
    リリース版に計測用のコードが紛れ込むのが嫌なら、定義を使っておくと良いと思います。(#if DEBUGなど)

    ProfilerやFPSの計測よりも細かな範囲でのパフォーマンス計測になります。はじめは大きな計測から始めて、徐々に計測範囲を狭めていくと処理が重い原因を調べやすいと思います。
    このスクリプトのあるメソッドが重いとわかっているなら、今回の計測方法でさらに詳しく原因を探れるはずです。 

    このページのトップヘ