FreelyApps

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


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

    カテゴリ: Unity

      このエントリーをはてなブックマークに追加 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で作業している時間がどれくらいなのか知るためには、ストップウォッチみたいなもので測ったり、時計を見たりすることで大体測れます。しかし、いちいちそういったことをするような習慣を身につけなければ測り忘れたりすることは起こるので面倒だと思います。

    UnityのEditor拡張で自動で作業時間を測ることができます。この拡張は他の使い方に応用できそうなので、シンプルなままでソースを載せておきます。
    使うものはEditorApplication.updateInitializeOnLoadMethodAttributeです。


    [InitializeOnLoadMethod]をメソッドにつけておくとエディターの起動時にそのメソッドが呼び出されます。そこにエディターで毎回行ってほしいことを登録することで定期的に計測結果を保存させるようにしています。
    EditorApplication.updateは秒間100回程度呼び出される処理を登録しておくために使います。起動してからの時間を測り、経過した時間分だけ足してEditorPrefsで値を保存しています。 
    EditorPrefsはPlayerPrefsのエディター版でPlayerPrefsとは異なる場所に保存されるデータです。WindowsではレジストリのHKCU\Software\Unity Technologies\UnityEditor 5.xというキーの中に保存されます。エディターのメジャーバージョンごとに共通の場所に保存しているのでプロジェクトごとに保存される場所を変えるということができないことに注意してください。

    制御用のウィンドウを作ったり、一定時間ごとに処理をするようにしたりと色々拡張の余地はあります。EditorApplication.timeSinceStartupで時間を計測しているのが悪いのか時間の計測がうまくできないことがあります。

      このエントリーをはてなブックマークに追加 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をどうやって用意すればいいのかがわかりませんでした。加工が禁止されているので、画像を縮小することもできないので、どこかに他のサイズを公式に提供しているのかもしれませんが……

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

      このエントリーをはてなブックマークに追加 Clip to Evernote
    以前の記事では、オンラインのゲームではマッチメイキング、同期、切断の3つの対応が必要だと書きました。
    現在、トランプ・ナポレオンをアップデートする予定でソースをちょっと見返しています。以前の記事ではあまり詳細まで解説していなかったので、良い機会だと思いマッチメイキングを解説してみようと思います。

    マッチメイキングにはサーバーが必要になりますが、Unity Multiplayerではそのサーバーが提供されています。そのため、サーバーの用意は不要で個人でもオンラインゲームが作れる環境があります。
    Unity MultiplayerはサービスウィンドウでUnityの各種サービスが使えるように登録をしておきましょう。これを行っておかないとエラーが発生して動きません。

    マッチメイキングにはスクリプトを書いて対応することになります。NetworkMatchを使って簡単に作ることができます。このインスタンスはNetworkManager.singleton.StartMatchMaker();のようにしてやると作られ、NetworkManagerのプロパティmatchMakerに参照が渡されます。(matchMakerははじめnullになっています。)
    NetworkMatchクラスのインスタンスが得られたら、後はそれに対して命令することでマッチする場所(ルームと呼ぶことにします。)の作成、ルームの削除、ルームへの参加、ルームからの退出、ルーム一覧の取得が可能です。
    それぞれ
    • CreateMatch
    • DestroyMatch
    • JoinMatch
    • DropConnection
    • ListMatches
    が対応します。いずれの処理も簡単に使えるものと詳細に設定ができるものが用意されています。
    CreateMatchで言うと、ルームの名前、ルームに入れる最大数、ルームの宣伝フラグ(たぶん検索に出すかということ?trueにしておけば良いらしいです。)、ルームのパスワードの4つの設定とコールバックです。CreateMatchなどの処理はすぐに終わらない非同期処理(コルーチン)なので、処理が終わったときに指定したコールバックが呼ばれる仕組みです。コールバックに処理の結果が渡されるので、それを見て適切に処理します。コールバックで特に何もしなくてもマッチングは行えます。
    詳細に設定する方では、matchAttributesというルームに対して属性をあたえることができます。この属性をListMatchesの方で指定することでルームのフィルタリングができる機能のようなのですが、残念ながら現時点では機能していません。質問したところバグだということなので、いずれ直るかと思いますが結構先のことだと思います。
    詳細版の方が良いのは設定を細かくできるためですが、面倒であれば簡易版でも十分です。 

      このエントリーをはてなブックマークに追加 Clip to Evernote
    ゲームのSS(スクリーンショット)を撮って、それをSNSに投稿するというのはよく行われていると思います。このSSは撮影してファイルに保存して利用します。ファイルに保存することで他のアプリからも参照できるようになり、使用することができるようになります。

    SSを保存するときファイル名は固定のものにするのが使いやすいです。保存のたびにファイル名を変えてしまうとストレージを圧迫しますし、どのファイルにアクセスすればいいのか管理が難しくなります。固定のファイル名にすることで新たに撮影し保存するときに古いファイルが上書きされていきます。古いファイルが自動で更新されていくので楽だと思います。

    SSをファイルに保存するのは必要ですが、いつまで残す必要があるでしょうか?
    消す理由はファイルを確実に新しくするためです。何らかの理由でSSが撮れなかったときは、古いファイルがそのままになります。ファイルの有無で投稿に画像をつけるか判定している場合、古いファイルが添付されることになります。(新しいSSがあるかを判定する方法であれば問題ないです。)
    私は古いファイルが残り続けるのは問題だと思っていたので、なるべく早く消していたのですが、それだとまずいこともありました。 SSをSNSにアップしている途中なのにファイルがなくなり、添付できなくなってしまうということが起きました。ファイルがないのにファイルが欲しいと要求され、アプリがエラーを起こしていました。
    古いSSを消すタイミングをファイル作成の直前にすることが最も正しいと思います。これから新しいものを保存する前に古いものを消すことで作成の成功でファイルが更新され、作成が失敗したときはファイルがなくなります。これによりファイルの有無でSSが最新かを判定して良いことになります。

    この問題を解決のため、いくつかのアプリをアップデートしました。トランプ・ゲスイットを1.2にFlashNumbersを1.1にしました。

    あまり気にしないでSS消さず残したままにした方がいろいろ楽だったと思います。

      このエントリーをはてなブックマークに追加 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
    できればやらないことにこしたことはないのですが、セーブをバージョンアップしなければならないことがあります。
    古いバージョンのセーブに含まれているパラメータを新しいバージョンのセーブでは以前と別の意味で使いたいといった特別なときのみ対応が必要になります。このような時でも容量を無駄にしても良いなら、新しくパラメータを増やして保存した方が安全です。
    デバッグ用途でなければ、バージョンは増える方向にしかいきません。バージョン1、2があったら、1から2へはいけますが逆はいけません。

    対応方法は色々あると思います。今、私が使っている方法は次のような方法です。
    セーブにはバージョン情報(単なる数字int)を保存できるようにします。またプログラム中にセーブのバージョンを定義します。プログラム中に書いたバージョンが最新バージョンになります。プログラムにあるロード処理は当然最新バージョンのデータに対応したものになっています。
    バージョンが変わるごとに1つバージョンを上げる処理を作り、それを順に通すことにより古いバージョンのセーブから最新のバージョンのセーブを作りだします。

    バージョン3が最新のものとし、セーブされているのがバージョン1とします。
    バージョン2を作ったときに、バージョン1のセーブからバージョン2のセーブを作る処理Convert1To2を作ります。Convert1To2はバージョン1をロードして、2のデータを作り、2として保存します。PlayerPrefsで作ると、バージョン1のキーで必要な情報を変数に保持しておき、バージョン2のキーで作成したデータを保存することになります。
    同じ要領でバージョン2のセーブからバージョン3のセーブを作る処理Convert2To3を作ります。これらの変換関数は現在のセーブバージョンを見て変換を行い、処理の最後でバージョンを上げます。
    1. 保存されているバージョンを調べる 
    2. バージョンが最新でなかったら、バージョンを1つずつ上げていくための処理を行う
    3. 最新バージョンのデータが保存されている状態になったら、ロードを行う
    という手順でバージョンがなんでも対応できます。この方法は古いバージョンのものがあったら、それを逐次新しいバージョンのデータに変換していくというものです。ひとつずつバージョンが上がるためにいくつものバージョンが合っても正しく変換でき、バージョンが上がるときの対応も1つ前のものから最新のバージョンへの変換処理だけ追加すれば良いという利点があります。

    ややこしく感じるのはセーブがファイル上にあるのかメモリ上にあるのかが、文章からだと区別しにくいからです。 説明していて私もちょっと混乱してきました。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    a6-permission

    Android6.0ではパーミッションをユーザーが任意に変えられる仕組みが導入されました。アプリによっては動かなくなるような致命的な影響を与える変更なので、一度は公式のドキュメントを見た方が良いです。

    パーミッションには色々ありますが、一部のパーミッションは危険だという分類にされていて、そのパーミッションを使うにはユーザーが許可する必要があります。それも動的(アプリ実行時)に変わるので、パーミッションが必要な処理をするところではその都度許可されているか調べなくてはなりません。

    どのパーミッションが危険かはこの記事のNormal and Dangerous Permissionsを見てください。
    私はSNSで画像を添付するためのファイルの読み書き
    • android.permission.READ_EXTERNAL_STORAGE
    • android.permission.WRITE_EXTERNAL_STORAGE
    の許可が必要になりました。 (実はWRITEの方があれば良いんですが……)

    許可が与えられているかを調べるにはAndroidのContextCompat.checkSelfPermissionを使えば可能です。当然、この呼び出しはJavaになります。プラグインを作ることでも可能ですが、UnityにあるJavaクラスを呼び出すためのクラスAndroidJavaClassを使うとC#のコードだけで実装が可能です。以下にコードを置いておきます。


    Check("android.permission.WRITE_EXTERNAL_STORAGE")のようにするとパーミッションが許可されているか調べられます。

    このコードを利用しても次のエラーが出るでしょう。
    AndroidJavaException: java.lang.ClassNotFoundException: android.support.v4.content.ContextCompat
    これはライブラリがないため、クラスが見つからないということです。 Plugins/Androidフォルダを作り、そこにandroid-support-v4.jarを置いておけば解決できます。
    android-support-v4.jarはAndroid SDKをインストールしたフォルダにあるはずで、
    (AndroidのSDKフォルダ)\extras\android\support\v4にjarがあります。これをUnityのプロジェクトの適切な場所にコピーすれば良いです。

      このエントリーをはてなブックマークに追加 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
    OnApplication~という名前のイベント関数が3つ用意されています。MonoBehaviourを継承したクラスに特定の名前のメソッドを作ることで自動的に呼び出してくれます。

    • OnApplicationFocus
    • OnApplicationPause
    • OnApplicationQuit
    の3つがあります。

    これらのイベントがどのような時に呼ばれるのか、いくつかのプラットフォームでどのようなときに呼ばれるのか調べてみました。
    テスト用のコードは次のようにしています。


    PC版ではログが実行中に見にくいので、適当なUIに文字列を出力していっています。ログが出る場合は、そちらを参照するだけで十分です。(PC版の場合は、exeの名前_Data/output_log.txtにログが出力されていきます。)
    ClickQuitはボタンUIにつけてApplication.Quitを呼び出すためのものです。


    Editor

    Editor上でApplication.Quitを呼び出しても何の意味もないようです。ログから呼び出しを確認できましたが、実行が停止されることもなくそのままでした。

    起動時(再生ボタンを押す)には以下のように表示されました。
    editor-awake
    OnApplicationPauseはポーズでないということでfalse、OnApplicationFocusはフォーカスされたということでtrueになっています。ポーズフォーカスの意味についてはここでは曖昧ですが、挙動を確認していくことで意味がわかってきます。

    フォーカスについてですが、これはゲームビューが操作対象になっているということです。再生ボタンを押してから他の場所をクリックしていないか、最後にクリックした場所がゲームビューであればフォーカスされているということになります。
    gameview-focusongameview-focusoff
    非常に微妙な違いですが、上のようにフォーカス時(左の図)とフォーカスされてない時(右の図)ではゲームビューのタブの色合いが違います。フォーカス時には多少明るくなっています。
    フォーカスされているかされていないかが切り替わるときに、OnApplicationFocusが呼ばれていました。

    ポーズはゲームが中断されるという意味で、実行がされなくなることです。エディターではRun In Backgroundの設定次第で挙動が異なります。
    pc-rib

    Run In Backgroundのチェックが付いてないときはエディター以外のGUIをクリックすればOnApplicationPauseでtrueとして呼ばれます。それで再びエディターをクリックして実行が開始されると、OnApplicationPauseでfalseとして呼ばれます。

    Run In Backgroundのチェックが付いている場合は、実行が継続されOnApplicationPauseは呼ばれなくなります。

    最後に再生ボタンを再び押して、ゲームの実行をやめると次のようになります。(Run In Backgroundの設定には関係しないようです。)このときはフォーカスが失われ、OnApplicationQuitが呼び出されています。
    editor-end


    PC

    プラットフォームをPCにしてビルドしたものを実行したときの挙動です。Windows上での確認になります。
    基本的な挙動はEditorとほぼ同じでした。

    異なる点はApplication.Quitが効き、このときにはOnApplicationQuitが呼ばれています。OnApplicationQuitはアプリをウィンドウ状態で開いたときに右上に出る閉じるボタンを押したときも呼ばれます。

    起動時はエディターと一緒でした。

    Run In Backgroundのチェックを付けないでビルドしたときは、OnApplicationFocusとOnApplicationPauseは引数が異なるだけで同じタイミングで呼び出されます。フォーカスされるというのは実行されていると同じだからでしょう。

    Run In Backgroundのチェックを付けてビルドしたときは、OnApplicationPauseは呼ばれなくなりました。ウィンドウ以外のところをクリックすると、フォーカスが外れてOnApplicationFocusが呼び出されるというようにエディターと同じ挙動です。

    またアプリの終了時にはOnApplicationFocusは呼ばれず、OnApplicationQuitのみが呼ばれていました。


    Android

    起動時には、OnApplicationPauseがfalseでOnApplicationFocusがtrueで呼び出されました。

    ホームボタン(ナビゲーションバーの真ん中のボタン)、リセントボタン(ナビゲーションバーの右のボタン。正式名称はよくわかりません。)を押すと、OnApplicationFocusとOnApplicationPauseが引数の値違いで呼び出されます。アプリからシステムの方に行くと中断であり、アプリに戻るのが再開のようです。
    最近のアプリ一覧の画面でアプリをフリックすれば終了させられますが、このときは何のイベントも呼ばれませんでした。つまり、OnApplicationPause、OnApplicationFocus、OnApplicationQuitのいずれも呼ばれずにアプリが終了させられるということです。

    Application.Quitを呼び出してアプリの終了させた時には、OnApplicationQuitのみが呼ばれていました。

    AndroidではOnApplicationQuitが呼ばれずに終了ができるので、重要なデータをOnApplicationQuitで保存するといった処理はまずい可能性があります。セーブすべきデータが更新されたら、なるべく早く保持すべきです。



    このイベントの呼び出し順序が決まっているのかは不明です。ログを見るとタイミングは毎回同じような気がしますが、明言されてないのでわかりません。順番がどうであっても問題なく動くようにした方がいいですね。

    このページのトップヘ