FreelyApps

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


    ブログを
    https://freelyapps.net/
    に移転する予定です。
    リンク切れがある記事はこちらに移動した可能性もあります。

    タグ: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
    アプリを作ったら、自然に広まっていくようにするためSNSとの連携ができるようにしたいと思うかもしれません。ゲームであれば、何らかのタイミングで結果が出ます。その瞬間にSNSに投稿できるようにするというのは自然だと思います。

    Unityを使ってSNSと連携するときに最も簡単なのはTwitterのTweet Web Intentを使うことだと思います。 特定のURLにアクセスすることでウェブサーバーがツイートを行うようにしてくれます。Web APIという呼ばれ方をされている利用方法でしょう。ものによっては認証が必要なことがありますが、ツイートすることについてはブラウザでアクセスするだけで可能です。

    https://twitter.com/intent/tweet?text=test

    のようなURLをApplication.OpenURLに渡してやれば良いです。このサイトを見ているブラウザで試してみるとわかりやすくて良いでしょう。私の場合、赤字になっているURLをブラウザでコピペして開いた結果が以下のようになりました。
    tw-wintent

    単なる文字列を投稿するだけならtextパラメータに値を設定すれば良いですが、渡す文字列はURLの書式に対応したものでなければなりません。具体的に言うと、文字列データをWWW.EscapeURLで変換したあとの文字列を渡す必要があります。
    var webintent = "https://twitter.com/intent/tweet?text=" + WWW.EscapeURL("送りたい文字列");
    というようにすれば良いということです。

    クエリパラメータにはいくつか種類が用意されているので、他にもできることがあります。クエリパラメータを使わなくても特殊な文字列(#、@など)を使っても問題なく動きます。

    このTweet Web Intentを使うことの利点はUnityの機能をあまり使っていないということです。単にブラウザにアクセスする機能さえあれば、あとはURLを適切に作成するだけなので、どの端末でも間違いなく動きます。AndroidでもiOSでもWindowsでもMacでも関係がありません。
    欠点はURLに埋め込めるのは文字列だけということとSNSによっては対応していない方法だということです。いくつかのSNSはこういった機能を持っているので、同じようなことができます(どれに投稿するか選ぶ画面を作らなくてはなりませんが)。

    簡単にSNSに連携してみたいという場合は、Tweet Web Intentを使ってみるのが最も良いと思います。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    Unityではイベント関数と呼ばれる特殊な関数があります。これはゲームオブジェクトにアタッチされたスクリプトの中で特別な名前のメソッドがUnityから呼ばれるという機能です。

    よく使うものを挙げると、 
    • Awake()
    • Start()
    • Update()
    • OnDestroy()
    などです。

    イベント関数を使う上で問題になることが多いのが、名前を間違うことです。名前を間違えただけなので修正は容易なのですが、問題が発覚しにくいことが厄介なところです。
    名前を間違っていても特にコンパイルエラーは発生しません。そのためプログラム的な問題はないと判断されて、後になって実は問題があるということになります。Unityから呼び出されなくても文法的には問題ないため、検出できないバグになるのです。

    OnCollisionEnter(Collision)みたいに特定のクラスを引数に取る場合も注意が必要です。イベント関数の名前が合っていても引数の型が間違っているということがあります。このときどのようになるかを例を挙げて調べてみました。
    void OnCollisionStay(int n)のように定義すると、以下のようにエラーが発生しました。
    event-argerror
    しかしながら、void OnCollisionStay()というように引数を空にしてみるとイベントが発生したタイミングに正しく呼ばれていました。
    つまり、イベント関数が引数を取る場合、引数があっているか空であれば呼ばれるということです。(もちろん名前が合っているとして)

    イベント関数の名前を間違ったり、引数の型を間違っていてもエラーが発生しないことがあります。
    正しくないイベント関数を書かないようにすることは絶対には防げないので、起こりにくくする方法を紹介することにします。


    VSTUを使っている場合は便利機能でイベント関数を自動で実装することができます。これを使うのが最もミスの起こりにくい方法だと思います。あまり詳しくないイベントを使う時などはこの機能を使っておくのが良いです。

    Visual Studioでイベント関数を追加したいソースファイルを開いて、ソース上で右クリックをすると下のメニューが現れます。
    vs-event

    Implement MonoBehavioursとQuick MonoBehavioursとが用意されているので、どちらかをクリックします。

    Implement MonoBehavioursの場合は、次のウィンドウが現れます。
    vs-implement
    チェックしたイベント関数が一度に追加でき、メソッドの説明をコメントできたりするのでお勧めです。ソースに自動的にプログラムが書かれるので間違うことがなく、積極的に使うべき機能だと思います。

    Quick MonoBehavioursの場合は、次の小さなウィンドウが現れます。
    vs-quick
    入力欄にイベント名を一部でも入れると候補が検索して現れます。候補を選べばメソッドが追加されます。
    こちらの機能はイベントの名前を知っている場合、素早く追加することができるという熟練者向けの機能と言えます。一度に追加できるのは1つまでです。

    VSTUを使って自動的に追加されたイベント関数にはpublic void Awake()のようにpublicが付きますが、publicはなくても問題ありません。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    ゲームをした日ごとのデータを保存して、どのように変化しているかを表示しようと思っています。最終的にはグラフで勝率を出したいのですが、データの蓄積部分が必要です。

    日付と保存したいデータの組み合わせをクラスにして、それをリストで持てばやりたいことは可能そうでした。日ごとのデータクラスをDaily、それを保持するリストを持つクラスをLogとして話を続けましょう。
    Dailyクラスは日付として、年、月、日の3つを持っていても良いし、DateTime構造体を持っていても良いです。DateTime構造体は日付の計算を楽にしてくれるので、計算のために使います。年、月、日の3つのパラメータからDateTime構造体が作れるので、どちらで保持しても情報は同じです。Dailyクラスは例えばスコアといったゲームの情報も持たせます。この情報は1日ごとに集計するものになります。
    Logクラスの中にいくつものDailyが保持されているわけですが、日が進むにつれてDailyクラスは増えていきます。ゲームの開始からずっと保持していくとどれだけのデータが必要が決められないので、日数を区切って保存することにします。例えば30日分とかです。このように決めると、リストの要素は30が限界になります。 セーブする領域も30個のDailyクラスを保持できるようにしておけばいいです。(要素数が確定すると配列でも良いような気はします。Listを使う方が便利なので、Listを想定していますけど。)

    日が進むと要らないDailyデータがリストに現れます。それは現在時刻から判断して、30日以前のものはいらないということです。
    保持しているリストにあるDailyデータの日付と現在の日付の差をとって、何日間の差があるか調べればいいです。DateTime構造体同士の差を取ると、TimeSpan構造という間隔を表す構造体が得られます。この構造体はDaysで何日間の差があるか取得できるのでこれを使います。0であれば今日であり、正であれば未来になり、負の場合は過去です。今日を含めた過去30日分のデータを取るなら、0から-29までのデータを残します。
    このようにして必要な期間のデータをリストに残すことができます。このリストの更新は今日のデータがあるかLogクラスに問い合わせて、新しく今日のデータを追加するときにリストの更新をすると効率的だと思います。

    時間をごまかすチートの対策は記録程度では要らないですが、必要であればセーブに最終更新した日付を残しておけば良いです。この最終更新日時は常に過去であるはずなので、もし現在日時が最終更新日時よりも過去であったらチートしているということになります。
    サーバーとか外部のものが使えるのであれば、そこの時刻と端末の時刻が正しいかを判定すれば良いでしょう。何かセーブするときに時刻を調べておかしくないか判定すれば、不正はできないと思います。

      このエントリーをはてなブックマークに追加 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
    ヒエラルキーにスクリプトをドラッグアンドドロップしたら、そのスクリプトがアタッチされたゲームオブジェクトがヒエラルキーにできると思っていたことがありました。それはプレハブをドラッグアンドドロップするとそのコピーができることからそんな勘違いをしていたのです。
    そうなっていてほしいことがそうなっていないので、Unityを使っていて長い間不満でした。それでどうにかできないかと思っていたので、今回なんとかしてみました。スクリプトはC#のみ対象です。BooやJavaScriptの方は型の情報が取れなかったので、やめました。といってもC#がメインでしょうからあまり関係ありません。


    以上のようにMonoBehaviourを継承していようが、継承していなくても関係なくスクリプトをヒエラルキーにドラッグアンドドロップしても何も起こりません。これはUnityの通常の挙動です。
    MonoBehaviourを継承したクラスはゲームオブジェクトにアタッチできますから、MonoBehaviourを継承したクラスのスクリプトをヒエラルキーにドラッグアンドドロップしたら、新しいゲームオブジェクトをヒエラルキーに作成しそのスクリプトをアタッチするというように変更したいと思います。


    できたものの動作は以下のようになりました。
    MonoBehaviourを継承したスクリプトのみ反応して、ゲームオブジェクトが作られています。(partialを使った場合は、クラス名と一致するスクリプトのときだけ反応します。)



    これをどうやって作ったかは結構難しい話になると思います。まず参考にしたサイトがいくつかあるのでそれを挙げておきます。
    Unity5 HierarchyにC#スクリプトをドラッグ&ドロップできるようにするエディター拡張
    Create GameObjects/Components by Dragging Assets to Hierarchy/Inspector
    の2つです。特に前者の方の目的はやりたかったことそのままと言ってもいいです。じゃあわざわざ作る必要があったのかとなりますが、うまく動かなかったので自分で作ることにしました。

    以下に作ったコードを載せておきます。現在、私が使っているUnityのバージョンは5.3.1f1 Personalです。これで動くことは確認しています。使い方は、UnityのプロジェクトにEditorフォルダを作りそこにこのスクリプトDropScript.csを置けば使えます。
    ログに”DropScript AutoStart”と出れば読み込みが完了しています。

    #if~#endifの部分は手動でこの拡張機能をON/OFFするためのものです。使いたい場合は適当なdefineを入れてください。
    重要なのはリフレクションを使って型の情報を取っている部分です。(Unity4だったら、名前でAddComponentできるので、それにclassNameを渡すだけで良いです。)Types.GetTypeという部分がドキュメントには載っていないので、使い方が正しいか微妙です。
    ドラッグのイベントは何度も呼ばれてしまうので、boolフラグで一度だけ呼び出されるように対処しています。

    完成したものは十分に要件を満たしていますが、いくつかの欠点があります。もう少し改善すると注意せずに誰でも使えるものになりますが、ちょっと無理そうなのでそれはしません。
    • ヒエラルキーにゲームオブジェクトがひとつもないと反応しない
    • スクリプトをドラッグしたままヒエラルキーを横切るとゲームオブジェクトができてしまう
    • スクリプトをドラッグしてゲームオブジェクトにアタッチすると、ついでにゲームオブジェクトができてしまう
    1つ目はEditorApplication.hierarchyWindowItemOnGUIがヒエラルキーにあるゲームオブジェクトから呼ばれているようなので、他の判定方法がないと改善できません。よって、これはどうしようもないと……
    2つ目はドラッグ中にヒエラルキーのウィンドウ外に出ることをドロップと判定されているようなので、おそらくどうよもないです。
    3つ目は個人的に結構大きな問題と考えています。ドラッグでのアタッチをやめてボタンからコンポーネントをつければ競合しないですが、気をつけないと知らずに不要なものができてしまうので問題です。
    まあ何らかの方法で対応できるかもしれませんが、これを使うとこんな感じのこともできるのでこのままの方が使えると思っています。ヒエラルキーをまたぐように行ったり来たりして大量のゲームオブジェクトを作っています。


    これすごく便利だと思うので、Unityの公式の機能としていずれ追加してほしいです。

    このページのトップヘ