FreelyApps

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


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

    カテゴリ:Unity > Script

      このエントリーをはてなブックマークに追加 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で保存するといった処理はまずい可能性があります。セーブすべきデータが更新されたら、なるべく早く保持すべきです。



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

      このエントリーをはてなブックマークに追加 Clip to Evernote
    PlayerPrefsを使うとセーブが作れます。当然プラットフォームによらないので、一度コードを書けばAndroid/iOSはもちろんPC上でもセーブがされています。私はWindowsで開発をしているので今日はその問題について書こうと思います。

    セーブがどこに保存されているかというとWindowsではレジストリになります。レジストリはプログラムが設定情報などを書き込むデータの保存場所です。PlayerPrefsを使うと、このレジストリにセーブが保存されるというのはあまり良くありません。データの保存場所なのだからそこに保存することは自然に見えるのですが、大きな問題はセーブ内容を直接見れることと、編集できてしまうことです。特に編集できてしまうのが悪影響を与えます。範囲外のデータが保存されるとプログラムがおかしくなるというのはよくあることです。
    開発中はセーブを自由にいじりやすいというのはメリットなのですが、配布するゲームのセーブがいじれてしまうのはまずいです。Unityを使ってPCゲームを一般に配布するなら、セーブ方法は自前でファイルに保存するように実装しないと駄目でしょう。

    Androidとかだともう少しセーブがいじりづらい仕組みのようなので、気にしないで使うことにしています。とはいえ気になる人は自分でファイルを作ってデータを保存するのが良いのは間違いないです。セーブの改ざんをどこまで防ぎたいかにもよって手間の掛け方が違うということです。

    Windowsでセーブ内容を見るにはregedit.exeを使います。これはレジストリエディタというものでWindowsに付属しています。名前の通り簡単にレジストリがいじれます。 プログラムを検索すれば見つかるので、regeditで検索して開いてみてください。
    PlayerPrefsの解説ページにあるようにHKCU\Software\[company name]\[product name]キーのところに保存されています。このキーはレジストリエディタの左側のフォルダみたいなものです。HKCUというのが略でHKEY_CURRENT_USERのキーを表しています。
    下の図だとcompany nameがFreelyAppsでproduct nameがFreelyAppsキーの下にいくつか並んでいます。当然これはPlayer Settingsの値によって決まっています。プロジェクトごとにproduct nameが違えばちゃんと区別されているでしょう。
    playerprefs-reg
    名前と書いてあるものがPlayerPrefsのkeyに対応し、データが保存されている値になります。このデータを編集するとセーブを編集することが可能です。また項目を削除すればセーブからデータの一部がなくなったことになります。

    プロジェクトに対応するキーを削除するとセーブがなくなるので、いちからゲームを始めた状態を作りたいときはそうすると便利かもしれません。
    ただキーが削除できない場合があります。先ほどの画像のトランプ・ナポレオンというキーの下の謎の文字列はそういったキーです。選択すると下のようなエラーが発生します。もちろんキーを消すこともできません。
    regedit_nullerror

    この原因はキーにnull文字が含まれている場合だそうで、キーが文字化けしているのでわかりやすいエラーです。null文字ってC言語のやつと同じなのかとかなんで発生するのかはよくわかりませんでしたが、RegDelNullというマイクロソフトによるこの問題の対応ソフトがあるので使うと削除できます。regeditにこの機能を入れておいてよとか思いつつ、ダウンロードして実行してみました。

    exec-regdelnull
    こんな風に-sをつけておくとHKCU/Software/FreelyAppsキーの下を再帰的に検索してnull文字を検知してくれます。見つけると消すか聞いてくるのでYes答えましょう。これでレジストリエディタで消せなかった文字化けしているキーを消すことができます。

    たぶん日本語名のアプリだから起こる問題なのだと思います。英語のタイトルのアプリでは起きてないと思うので、その可能性が高いです。

    レジストリをいじるときは注意しましょう。
    最悪パソコンが起動しなくなる恐れがあります。 

      このエントリーをはてなブックマークに追加 Clip to Evernote
    エイジングテストとは(発音的な問題でエージングとも呼ばれます)長時間製品を稼働させて動作に問題がないかをテストするものです。工業製品などでは耐久性を見るために行われていていたりします。

    ゲームの場合でもエイジングテストがあります。これは長時間ゲームを動かしっぱなしにして問題が起きないかを調べます。ある画面で何時間も放置するとゲームが停止するといったことが起きないか調べたり、自動でゲームを進行させるようにしてエラーが起きないかをチェックします。
    エイジングでは特に後者の自動でゲームプレイを行わせ、バグがないかの検査に使うことが役に立ちます。放っておくだけでゲームをPCが自動で遊んでくれると、自分で何回もテストするよりも何倍も効率的にテストが可能です。人間がやると飽きてしまうのでどうしてもテストの精度は悪くなっていきます。これが機械にやらせれば延々と正確にテストを繰り返してくれます。
    テスト結果を見るためにログに出力するようにしたり、ゲームの進行具合を確認すると良いです。 

    エイジングを行うためにはプログラムにそれ用のコードが必要になります。ただしこれらのコードは製品には不要なので、定義があるかないかでコードを抹消できるようにしておくのが重要です。ユーザーの入力が必要な時にプログラムで模倣するようにすれば良いのです。模倣といっても人間らしくする必要性はありません。可能な選択肢の中からランダムで選択するようにしておけば十分です。選択肢が多いならまだ行っていない選択を優先して選ぶというようにするのも有効です。要は可能な組み合わせを全て行えればテストとして十分なわけです。ランダムの場合は数を増やせば自動的に全ての可能性をテストできるため、意外と良い方法です。

    人間の入力がなくてもゲームが動くようになればテストが開始できます。 この時点でエイジングテストが可能な段階になりますが、いろいろな省略を実装するとより効率的にテストが行えます。動作のバグを見つけるといったときは演出関連の処理を省いたりすると短い時間でチェックが増やせます。

    以下の例はアニメーションを最低限にしているチェックです。 高速に対戦が行われているのが見て取れます。このテストではトランプゲームのルールが正しく作れているかと、AIが学習して強くなるかを試しています。
    対戦結果のUIの表示が一瞬されるため視覚的にもゲームの進行が行われているかなんとなく把握できます。

    エイジングテストは放っておいてもずっとテストをし続けてくれるということ重要です。ゲームを起動したままにしておくことができないといけないのですが、Unityのエディターは他のアプリを選択しているときには動作を停止してしまいます。つまりエイジングテスト中はUnity以外のプログラムをPCで操作することができなくなるのです。
    エイジングテスト中にはPCで他のことをしたかったりするので、これは困ったと思っていました。
    Unityのエディターを他のアプリを選択しているときでも動作しておく方法が、ハルシオンシステムの気ままBlog「【Unity】Unityが非アクティブでも動作を進めさせておく。 」にあったので試してみました。結果は期待通り、他のアプリケーションを操作しながら、Unityエディターを動かすことができました。

    Player SettingsのRun In Backgroundをチェックすればいいのですが、このチェック項目はPCやWeb Playerなどの共有設定です。Androidにはこの設定はないので、ビルドターゲットがAndroidでもいいのかと思っていましたが普通に裏で動いてくれました。本来このプロパティは実行プログラムが一時停止されないための設定だと思いますが、エディターの動作もこれで設定されているようでした。
    ps-rib

    実はこの記事を書いているときもエイジングテストを行いながらでした。何回も繰り返すとログにもエラーが出なくなり、それなりの品質になると思います。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    ゲームは一般的にセーブが存在します。セーブとは何らかの方法でメモリにある情報を保存する仕組みです。
    プログラムはメモリの情報を見て動作するので、通常は前回の実行には全く関係なく実行されます。プログラムの実行を終えたら、メモリの情報は破棄されます。メモリから情報が消されるので、再開とかは不可能なわけです。電源を落としたら最初から遊ぶようなゲームはごく普通のことなのです。

    そうはいっても今のゲームは続きから遊べるようになっています。以前遊んだステージをクリアしていたり、取得したアイテムを持っていたりするのが普通です。これは前回までのことが記憶されているということです。
    メモリに保存ができないなら、どこか別のところに保存すればいいということでセーブというものがあるのです。セーブにメモリのデータをしまっておいて、次のときにセーブからデータを読み込んでメモリ上に復元します。そうすると以前の続きから遊べるようになります。

    セーブの仕組みは端末によります。PCで言えばテキストファイルかバイナリファイルを実行ファイルのあるフォルダに作るとかになります。
    このデータをセーブ(データ)とも呼んだりするので、セーブが機能を表しているのか実体としての保存されているデータ形式を表しているのかを気をつけなくてはいけません。
    セーブデータを作る機能は扱う端末によりわかれます。ファイルの読み書きなどはOSに依存する低レベルの処理なので、かなり面倒なものです。このあたりは取り扱いが非常に面倒でありゲーム内容に依存しにくいことなので、ゲームエンジンには端末ごとの処理を書かずに何らかのAPIを呼び出すだけでセーブができるようになっています。 

    UnityではPlayerPrefsという関数でセーブが作れます。 
    PlayerPrefs.GetIntとPlayerPrefs.SetIntがセットであり、GetIntでセーブからの取得を行いSetIntでセーブへの書き込みを行います。(SetIntは厳密には書き込みではなく、書き込みのときの値を設定しているものです。しかし書き込みと考えていても基本的には問題ありません。)
    PlayerPrefsはキーバリュー型の保存方法であり、簡単に使うことができます。キーは文字列でGetInt、SetIntで同じ文字列を使うことでキーに対応する値(バリュー)を読み書きできます。
    別の値を表すのにも関わらずキーが同じものを用いてしまうと、値が上書きされます。エラーが発生することはないようです。SetFloatやSetStringで同じキーを用いても問題なく動作し、最後に呼ばれたものだけがセーブに残るみたいです。

    PlayerPrefs.SetInt("test", 100);
    PlayerPrefs.SetFloat("test", 0.5f);
    PlayerPrefs.SetString("test", "hello");
    というように同じキーで3回セットを呼んでいますが、3つ目のみがセーブされます。この後にPlayerPrefs.GetInt("test")を呼んでも、デフォルト値の0が返り100が返ることはありません。保存する型が異なってもそれらはキーによってしか区別されないということに注意が要ります。
    PlayerPrefsを使う時に最も重要なのは、意図せずキーを重複させないということにつきます。実はセーブにデータ(キーとバリューのペア)が存在してなかったみたいなことが起きても、PlayerPrefsのGet系の関数はデフォルト値を返すためにバグに気付きづらいのです。デフォルト値をいじれるので、明らかな無効値を返すようにしてしまうとかがバグを防ぐ唯一の方法な気がします。その方法も無効値が存在しないデータだと検出できないです。名前の通りキーの取り扱いにはご注意をということなら、良いネーミングセンスです。

    PlayerPrefsを直接呼び出すことで非常に簡単にセーブ機能が作れますが、セーブが大きくなってくると管理クラスなどを作った方が良いでしょう。

    オートセーブを使うゲームの増加によりユーザーにはその存在がわかりにくくなっていますが、 開発者にはセーブの存在は大きいままです。セーブに関する話題は結構あって、正解ってなんだろうかと考えさせられます。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    UIの位置を調整する場合はスクリーン座標を基準に考えるのが最も良いです。スクリーン座標とは表示するディスプレイ上でのピクセル単位の座標です。
    Unityでは左下が(0,0)、右上が(幅,高さ)となるように座標を定めています。カメラのところにちょろっと説明が載っています。
    こういったゲームビューなら右上の座標は画面のサイズになります。
    gameview

    なぜスクリーン座標で考えると良いのかというと、画面上でどこに表示されているかを直接表しているためです。画面上の位置が表示にそのまま関係するため最もわかりやすいのです。

    3DとUIを連携したい場合
    わかりやすい例で言うと3D空間上にいる物体の近くにUIを常に出したいといったことがあるでしょう。これはキャラクターにHPのバーを表示し続けるといったことが該当します。
    キャラクターが3Dだとすると、キャラクターの位置は3D空間上での位置を表しています。(モデルの中心位置に一致することでしょう。)x,y,zの3成分があるベクトルで表されますが、この数字はワールド座標であるはずです。空間上の位置から画面上のどこ(スクリーン座標)に表示されるのかを求める必要があるわけです。これはキャラクターを移しているカメラから変換行列をかけてもらい求めます。Unityではこの行列計算は、カメラコンポーネントを取得してWorldToScreenPointを位置に適用するだけなので簡単です。
    キャラクターのスクリーン座標がわかったら、UIの位置を補正するなどしてUIのスクリーン座標を決めます。頭上にHPを出したければ、キャラクターのスクリーン座標に適当な量yを増やせばいいです。このスクリーン座標をUIの座標にして、それをUIにセットすれば目的が達成されます。RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform rect, Vector2 screenPoint, Camera cam, out Vector2 localPoint)を使えば、あるRectTransform内でのローカル座標を求めることができます。rectは設定したいUIの親になることがほとんどになるでしょう。camはCanvasに使っているカメラを渡します。オーバーレイモードならnullにします。localPointには結果を取得するための変数を渡します。これで得られたlocalPointを設定したいUIのRectTransform.localPositionに設定すると完成です。

    UI同士を連携したい場合
    UIのCanvasが異なるなど単純には位置を求められない場合もスクリーン座標を介することで位置を連携することができます。
    あるUIの位置がスクリーン座標でどこになるかが分かれば良いことになります。これはRectTransformUtility.WorldToScreenPoint(Camera cam, Vector3 worldPoint)を使うと可能です。なぜかこれについてはドキュメントに載っていないようです。使い方は引数からわかるようにUIを表示しているカメラとRectTransform.position(ワールド座標)を渡します。
    スクリーン座標からUIの座標を求めることは既に述べたので、これでどちらにも変換ができます。


    UIのもつ座標→スクリーン座標とその逆であるスクリーン座標→UIのもつ座標の変換方法があれば自由に位置を調整できるということになります。
    • UIのもつ座標→スクリーン座標はRectTransformUtility.WorldToScreenPoint
    • スクリーン座標→UIのもつ座標はRectTransformUtility.ScreenPointToLocalPointInRectangle
    を使うことで可能です。
    注意点はピボットの位置がRectTransformの位置である(localPositionとかはピボットの位置を表しているということ)ということです。変換を行うときに微妙に位置がずれるときはピボットの位置がセンターだったり、トップだったりと統一がされていない可能性があります。

    意外とありそうなミスとしてはアクティブでないUIの座標を取得してしまうことです。このときの座標は正しくないようなので、起こりやすい間違いです。 これは消していたいUIの座標を取りたいという時に困るのですが、どうしようもないようです。

    以上のような変換はスクリプトで制御したい場合に必要になることが多いでしょう。Canvasの異なるUI同士の座標をそろえたりする機能が今のところなさそうなので、こういった方法が要ると思っています。
    もっと簡単な方法があると良いのですが…… 

    このページのトップヘ