FreelyApps

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


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

    カテゴリ: Unity

      このエントリーをはてなブックマークに追加 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
    Unity Analyticsの管理サイトでDATA EXPLORERを選ぶと、カスタムイベントの情報を見ることができます。

    下の図は、ナポレオンを1ゲームするときに呼び出しているカスタムイベントのグラフです。個のイベントを呼んだユーザー数の変化が表されています。(本来は左端にユーザー数が表示されますが、ここでは消してあります。)
    tn-analytics-1page

    これだけだとアプリを起動した人数とほぼ一致するはずです。なぜなら、このアプリを起動して遊ばずに抜けることはほぼないと思われるためです。DAUと比較してどれくらいの人が何もせずにアプリを落とすのかを知りたいときには役立つと思います。

    カスタムイベントが呼ばれた回数を知りたいことがあります。ユーザーごとではなく、各々のユーザーが呼び出したカスタムイベントの総量です。
    これの見方についての説明は載っていないのですが、おそらく以下で説明する方法で見れると思います。(正しいやり方かの保証はないのでご注意ください。)

    Add Custom Eventで見たいイベントを追加するなりして、特定のカスタムイベントを選んだら、対象となるユーザーを選び、View Parametersボタンを押します。
    そうすると2枚目のページにいけます。このページはあるカスタムイベントの詳細を見れる画面です。ここでパラメータの指定をunity.nameにします。どうもこれはカスタムイベント名を表しているようです。countというのは数字型のパラメータを指定したときの呼び出し回数です。つまりこれが発生回数になります。
    (本来は左端に回数が表示されますが、ここでは消してあります。)
    tn-analytics-2page

    何回か検証したところカスタムイベントの発生回数と一致したので、この方法で発生回数を調べられます。
     

      このエントリーをはてなブックマークに追加 Clip to Evernote
    通常はカメラは1つでゲームを作ることができます。Unityで新しいシーンを作れば、Main Cameraが作られていてこれをそのまま使うだけで物体の表示が可能です。

    複数のカメラを使うと単体のカメラではできない表現が可能になります。例えば一部のオブジェクトを常に前面に表示したり、画面を上下で分けた2P対戦といったことが可能です。
    これはCameraコンポーネントのプロパティのうち、Clear Flags、Culling Mask、Viewport Rect、Depthをいじることで実現できます。 (画面の分割についてはViewport Rectを適切に設定したカメラを複数用意しておくだけで実現が可能なのでここでは詳しく取り上げません。)

    一部のオブジェクトが常に前面に表示するということを今回は試してみたいと思います。 といってもよくわからないのでまずは結果を示します。
    uc-aftereye uc-default

    左の方は最終的に得られた結果です。右の方は1つのカメラ(通常の表示ということです。)でユニティちゃんを表示したものです。
    前者は眉毛が髪に隠れないで表示されています。(アニメ的な表現らしいです。目が見えると印象がはっきりしていますね。)
    目と眉毛が他のモデルよりも後に描かれることでこのように前面に表示することができます。とくにプログラムで制御せずに設定を変えることでこのような表示が可能です。


    ヒエラルキーの構成は以下の通りです。unitychanはユニティちゃんモデルのプレハブを置いただけで、Directional Lightも単なる光源です。Main CameraとSubCameraがあるのがポイントでSubCameraが前面に表示するオブジェクトだけを描画するカメラです。Main Cameraがすべてのオブジェクトを描画した後で、SubCameraが特定のオブジェクトを上書きすることでそのようになります。
    uc-2cam-hi

    Main CameraとSubCameraの設定は以下の通りです。
    uc-2cam-hi-main uc-2cam-hi-sub

    Main CameraとSubCameraは同じ位置にいるように設定し、Main CameraとSubCameraのCameraの設定は基本的に同じです。これは同じもの対象として描画しているためです。
    Main CameraではClear FlagsがSkyboxであり、Skyboxで以前の結果をリセットしています。これはMain CameraのDepth(カメラ同士の描画の順番のこと。Depthという名前はややこしい。)が-1であり、SubCameraよりも先に描かれるためバッファクリアの処理を指定しています。描画物の制限も特に行わず全てのものが描画されます。(最初に示した通常の絵が描かれているということ)
    SubCameraはMain Cameraの後に描画したいため、Depthの値が0で大きくしてあります。SubCameraはMain Cameraの描画結果を消してしまわないようClear FlagsにはDepth onlyが指定されています。これによりデプス(深度、Zとも)がクリアされ、SubCameraの描画結果は上書きされます。特定のゲームオブジェクトを描画するためにCulling Maskが設定されています。Subというのはレイヤーの名前です。

    ユニティちゃんモデルを構成するゲームオブジェクトのうち目と眉毛のもののレイヤーをSubにすれば、それだけがSubCameraでも描画されることになるということです。
    SubCameraの描画しているものはこんな感じです。
    subcamera-eye

    unitychanはモデルなので、複数の階層で構成されています。以下で選択されているものが目と眉毛の表示に関係するレンダラーをもつゲームオブジェクトなので、それらのレイヤーをSubにします。
    uc-2cam-hi-model

    以上の設定すると最初に載せた絵が作れます。
    uc-aftereye

    この方法はお手軽ですが、欠点もあります。言わなければ気付かないかなーと思いましたが、モデルを動かしたりするとすぐにバレるので言っておきます。
    SubCameraにとっては目と眉毛のオブジェクトしか見えないため、顔のモデル(頬とか鼻とか)に隠れて欲しい左目が表示されてしまいます。またモデルの作りによりますが、前面に表示したためにユニティちゃんの目がおかしくなります。
    この解決はかなり大変です。モデル自体がメッシュの前後関係を変えても破綻しにくい作りになっていること、描画する物体がカメラにとって表側に向いているかの判定をカスタマイズするといった対応が要ると思います。
    uc-aftereye-bug

    最後にユニティちゃんを回転させた動画を載せておきます。モーションさせたり、動かしたりするといろいろ問題が出てきます。(左の方の滲みは画像の都合なので無視してください。)
    uc-2cam-anime

    今回はモデルを使って見た目を変えてみましたが、ゲームでは重要なものを目立たせるといった使われ方があるでしょう。

    © Unity Technologies Japan/UCL

      このエントリーをはてなブックマークに追加 Clip to Evernote
    結構前の話ですが、5.2以降でも通じる話なので書いておこうと思います。

    Unity5.1においてUnity AnalyticsがUnity内部に組み込まれました。これによりアセットストアからSDKを落としてきて組み込むといった手間はなくなり、使いやすくなりました。Project IDというものをWebサイトから取得し、PlayerSettingsで設定するという方法でUnity Analyticsが有効になったと思います。(この記事を見ると、現在は5.1のProject IDはあまり推奨されていないみたいです。またしても5.1ではSDK方式でも対応できるようになっています。)

    Unity5.2以降もUnity AnalyticsがUnity内部に組み込まれているため、SDKは不要です。サービスウィンドウができたため、ここでUnity Analyticsの有効にすることができます。サービスウィンドウに色々なサービスがまとめられたため、やり方が変わったのが5.2です。つまり、使い方が変わっただけで5.1から進歩していません。(内部的には変わっているかもしれませんが……)

    サービスウィンドウを開くと下のようになっています。Unityアカウントにログインしないと何もできないということです。
    sw-start

    ログインすると、新しくProject IDを作るか既にあるIDをリンクするかを選ぶことができます。既にProject IDがあるというのは、古いバージョンのUnityでAnalyticsを使っていたとか、管理サイト上でProject IDを取得したということが考えられます。そのような場合はそのIDを設定すれば良いです。
    sw-start-after-login
     
    Project IDも新しく作る場合(新しいプロジェクトならそのはず)はUnityアカウントの所属する組織を選びます。Select organizationにリストが出るので、適当なものを選びます。そうするとCreateボタンが押せるようになるので押してください。
     
    新しくProject IDが生成され、ウィンドウにProduct Nameが表示されるようになりました。Analytics以外のサービスも設定できますが、今回はAnalyticsのボタンを押して有効にします。
    sw-projectid-created
     
    後は画面に沿ってEnable Analyticsボタンを押すか右上の有効・無効切り替えのトグルを押します。有効化するときに年齢制限の確認が聞かれます。(これはアメリカの法律でCOPPAというものに対応するためのものです。13歳未満の子供の個人情報を収集する際に制限があります。13歳未満対象かどうかをUnityが知るためアプリごととに設定することを求められているのでしょう。)詳細についてはここを見ると良いです。

    以上で設定は終了です。サービスウィンドウ上でもAnalyticsがONになっていることを確認できます。
    この設定が終わったら一度はEditor上で起動しておいてください。これによりPCから管理サイトにAnalyticsの情報を送ります。管理サイト上にValidateチェック用のページがあるので、それを見るとすぐに正しく接続できたかがわかるようになっています。ここらへんは以前と同じです。
    カスタムイベントの集積には最長6時間程度かかるそうなので、6時間待ってからチェックすれば良いです。


    最後に注意点を書いておきます。かなり重要なので、覚えておくとたぶん役に立つでしょう。

    Analytics.CustomEventメソッドはUnity Analyticsが有効にされていないと、「The name `Analytics' does not exist in the current context」というエラーが発生します。
    カスタムイベントのメソッドがエラーを起こすため、自分でカスタムイベントをラップして呼び出しておくのがお勧めです。

    Unityを閉じると、サービスウィンドウで設定したはずのProject IDのリンクが解除されています。内部的にはProjectSettings.assetにProject IDが保存されているためリンクされていますが、ウィンドウ上では新たにはじめる状態が表示されていてIDがなくなってしまったのかと勘違いします。
    ここでCreateしてしまうと新たにIDが増えていくので、「I already have a Unity Project ID」 を選んでリンクするのが通常の使い方だと思います。設定を変えなければ開くことはないかもしれませんが、これは結構驚く挙動です。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    折れ線グラフを描くための調査」でどうやって線を描くかを検討しました。複数のLineRendererを使って描くのが良さそうだったので、それを簡単に行うためのスクリプトを書きました。スクリーン座標のリストを渡すと、それらの点をつないだ線を描くという機能のものです。 

    機能自体はCameraとLayerを使うと簡単に作れます。このカメラは線を表示するために用意するものであり、メインカメラとは別のものを使います。
    スクリーン座標ではどこに線を描けばいいのかわからないので、CameraコンポーネントのScreenToWorldPointを使ってワールド座標で言うとどこなのかを求めます。このワールド座標を両端の位置とするLineRendererを用意すれば、線を表示する用のカメラのスクリーン上で正しい位置に表示されます。
    graph-scene
    LineRendererは1つにゲームオブジェクトに1つしかつけられないので、上の画像だと3つのゲームオブジェクトが使われています。

    当然、このままだとメインカメラや他のカメラからもLineRendererが見れます。そのためLineRendererが付いているゲームオブジェクトのレイヤーを適当なものに変え、CameraのCullingMaskを設定して線を表示するためのカメラのみが線を見れる状態にて余計に見えないように対処します。(レイヤーを使う方が完璧ですが、カメラの位置をx=10000とかで被らないようにできればそれでも同じ効果があります。)

    ここまでで折れ線グラフを描くことは達成できました。

    これをUnity UIで使いやすくするため、グラフを表示しているカメラの描画先をRenderTextureにしてみました。 RenderTextureはカメラにくっつけて使う特殊なテクスチャです。このテクスチャにカメラは描画結果を書きこむようになり、画面には表示をしなくなります。(内部的には出力先をレンダリング用のバッファから、レンダーテクスチャ用に確保した領域に切り替えているだけだと思います。レンダ―テクスチャがヒエラルキーに存在せず設定値を持つだけのものだということからもそう思います。)

    graph-rt
    RenderTexureに折れ線グラフを書き込んだ結果です。一度書いたら特に更新する必要はないので、描画用のカメラやLineRendererは破棄しても大丈夫かもしれません。

    これで単なるテクスチャとして使うことができます。Textureクラスなので、Spriteとしては扱いません。Unity UIで使うにはRawImageコンポーネントで表示すれば可能です。
    下のグラフの表示は灰色の下地の上に青色で表示しただけのものです。テクスチャになっていれば簡単にUIに組み込めるので、非常に使い勝手が良いです。
    graph-log

    メモリとパフォーマンスがちょっと心配です。パフォーマンスはグラフを動的に変化させるつもりがないので、レンダ―テクスチャに描画をし終えたらゲームオブジェクトを消して改善できそうです。メモリがどれくらい使われるかは良くわからないですが、解像度を下げるとかピクセルあたりのバイト数を減らすしかないでしょうね。

      このエントリーをはてなブックマークに追加 Clip to Evernote
    ゲームなどのアプリでは、SNS系のアプリ(Twitter、LINE、Facebookなど)に拡散できる仕組みを入れるのが常識になっています。文字だけの投稿もありますが、画像があった方が良いので最近は画像付きの投稿が行えるものも多くなっています。

    画像付きのツイートを送信するには当然画像がなければなりません。この画像はスクリーンショットを使うことがほとんどでしょう。画面を撮影した画像を特定のフォルダに保存して、その場所と文字列(アプリの紹介文だったり、ゲームの結果だったり)をTwitterアプリに渡すことで画像の付きのツイートができるわけです。
    画像を作ったり、Twitterアプリに適切なものを渡して連携するところもそれなりに難しいのですが、一度できてしまえばどんなアプリでも同じ仕組みで動きます。ちゃんと作るという条件付きですが、成功したら正しく動くのでわけのわからない問題は起きないでしょう。

    きちんと作って画像付きのツイートを送信することができたとしても、Android6.0では動かなくなることがあると判明しました。Android6.0に対応するように作っていないのだから、それはそうだという話ではありますが、Unityを使っているとAndroidを直接いじるのは面倒極まりないのです。なので対処法を見つけることにして、なんとかお茶を濁そうと考えています。

    原因はAndroid6.0以上のOSを使っている場合、権限の管理ができるようになったためでした。この権限のうちWRITE_EXTERNAL_STORAGEというファイルの保存に関わる許可を得られないと画像ファイルにアクセスできないため、画像付きのツイートができないのです。しかもTwitterが落ちることも結構あります。
    SS_TwitterAbort

    5.9以前ではアプリをインストールするときに必要な権限は全て許可するようになっていたので、開発者はAndroidManifestに必要なパーミッションを入れておけばそれで十分でした。
    しかしながら6.0ではユーザーが自由に権限をON/OFFできるようになってるため、それだと不十分なことがあります。「Android 6.0 以降のアプリの権限の管理」として公式ページで説明してあります。

    どのような操作をすると画像付きのツイートを送信できるようになるか
    をメモしておきます。Android6.0以前の端末ではきちんと動くのに6.0では動かないという場合、以下の対応で正常に動く可能性があります。(試した端末のAndroidバージョンは6.0.1でした。)
    まず画像付きのツイートをすると画像にアクセスできずにTwitterアプリが停止するといった症状が出る場合、設定->アプリを開き、Twitterにデータを渡している問題のアプリを開きます。アプリの情報が下のように表示されているはずです。
    許可というところをみると「権限が付与されていません」と出ています。この状態だと駄目なので、許可というところをタップして設定を変えます。許可をタップすると右のようにアプリの権限の一覧画面になります。ここではストレージという権限があり、それが無効になっているという状態です。
    SS_TN_PicTwi1 SS_TN_PicTwi2

    ストレージをタップして権限を有効化します。右の方にトグルが移動し、緑色になりました。これで権限がゆうこうになります。1つ前のアプリ情報の画面に戻ると、許可にストレージが現れています。
    SS_TN_PicTwi3 SS_TN_PicTwi4

    権限の有効・無効を設定した場合、一度アプリを終了させないと反映されないようです。意外と重要なことですが、忘れがちなことなので注意するべきです。


    Android6.0以降に対応したアプリでは権限の管理についても対応するというのが正しいあり方です。
    対応方法は開発者用の情報に載っています。当然AndroidのAPIを直接呼び出しています。
    権限があるか常にチェックするためのメソッドがあり、それにより権限が与えられているかを判定してプログラミングすれば良さそうです。また権限を要求するためのダイアログを出す機能も用意されていました。(Runtime Permissonとかで検索するとそれなりにヒットするので興味がある場合は調べてみるのもいいと思います。)

    端末ごとの対応はしたくないので、テキストだけのツイートにするかアプリの紹介ページに「Android6.0をお使いの方は~」 的な対処方法を書いておくのが現実的かなあというところです。
    アプリが落ちたりしないようなので、まあ妥協点です。 

      このエントリーをはてなブックマークに追加 Clip to Evernote
    ゲームの日々の記録などを折れ線グラフで表示したいと思いました。それで色々調べているのですが、結構大がかりな感じでした。

    スクリーン上にUnityで線を引く方法は意外と面倒臭いようです。スクリーンの位置を渡して線を描くような関数が用意されていないからです。
    uGUIを使って細い四角形を用いてもグラフは描けるし、LineRendererGLを使っても線が描けるようです。

    UI上に線を描くようにすると、UI以外では使えません。Imageに四角を描くようにしていくつも使って折れ線を表現することができます。可能ですが、元々UIをそういった目的に使うものではないのでいまいちです。
    GLは線を描くことが関数で行いやすく簡単でしたが、特定のカメラ上でのみ描画するといったことができません。GLを使うとワールド空間内に線を直接作るようなことになります。(表示位置などを思い通りにいじるためにはCGの描画における行列の知識が必要です。Unityを使って楽にゲームを作るということからは逸脱します。GLが低レベルのグラフィックスライブラリということなので、難しくなるため初心者向けではないです。)
    下の図はGLの説明のところにあったExampleClassを適当なゲームオブジェクトにアタッチして表示してみたものです。ゲームオブジェクトの座標位置を中心に放射状に線を描くというサンプルです。
    GLExample

    線を引くならやはりLineRendererが良い感じでした。LineRendererは3D空間に線を引く機能なのですが、2Dにも使おうと思えば使えます。(2D特化のLineRendererが欲しいところです。)
    なのでこれを使って線を描くようにしていきます。LineRendererは線を四角形のポリゴンで作られているようで、ねじれたり線の太さが均一にならないことがグラフを描く上では欠点です。ねじれと太さが均一でないことをなくすには1つのLineRendererで折れ線を描くのではなく、2つの点の間ごとにLineRendererを使い線分の集合で折れ線を書けば解決できます。これによって今度は線のつなぎ目に長方形同士の重なりが起こり、きれいにつながらなくなります。単一のLineRendererはポリゴンがつながっているためにそういったことは起きないのです。

    下は1つのLineRendererで3つの点を持つ折れ線を描いたものです。折れ曲がっているところがあるため、太さが均一でないのが見てとれます。
    lr3points

    下は2つのLineRendererで3つの点を持つ折れ線を描いたものです。n点の場合は(n-1)個の線分で構成されるため(n-1)個のLineRendererが必要です。太さは均一になりましたが、線同士がつながっているところできれいに表示できません。
    2lrs3points

    グラフでは線が上に表示したよりもずっと細くします。細くなった時どちらのやり方の方が良いかというと後者の方が良いです。細くなっていくと、つながっている部分も小さくなり見えにくくなるため不自然な重なりがわかりにくいのです。線が均一でないのは細くなっても結構わかります。線同士の違いが比較できるため細くなっても問題になります。
    折れ線グラフを作るには複数のLineRendererを使って行うのが良さそうです。

    Unityでグラフを作ることを調べると有料のアセットがあることもわかったのですが、アプリで儲けるということを考えると有料だと二の足を踏みます。
    ただお金で解決できることはお金で解決するのが最も良いのは間違いないです。
    私の場合は自分で試してみるということも目的であるので、自分でできそうなら自分でやります。

      このエントリーをはてなブックマークに追加 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を直接呼び出すことで非常に簡単にセーブ機能が作れますが、セーブが大きくなってくると管理クラスなどを作った方が良いでしょう。

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

    このページのトップヘ