Persistence

技術メモなど

Unityをいじってみた(その3〜データのダウンロードと保存方法〜)

続きです。実際にゲームを作ることでGameObjectやScriptに関する知識はたくさん溜まりましたが、すべてを書くのは果てしないので、このゲーム独特の内容を書いていきたいと思います。

データの配置と読み込みについて

このゲームは、UIイメージの他に、音源や譜面にBMSデータ(※)を使用しています。主に使うデータとしては、「イメージ画像」「音源(WAV)」「譜面(テキスト)」の3種類になります。※フリーのBeatmaniaクローンゲームのデータ。音源等には著作権があるため、あくまで個人利用。

「イメージ画像」「音源(WAV)」といった素材はUnityのProjectに配置することで、Unityに最適化された状態に変換されるようです。またこの時、ProjectのResourcesフォルダに配置することで、Script側からオブジェクトとして簡単に呼び出すことが可能になります。

一方で「譜面(テキスト)」の場合はUnityで利用する素材ではないため、テキストファイルとして読み込む必要があります。読み込みには.NetライブラリのFileInfoクラスなどを使います。また、Unityはビルド時にアセットを統合してしまうため、実機で実行するとファイルをロードすることができなくなります。統合されたくないデータはProjectのStreamingAssetsというフォルダに配置することで、そのままの状態でファイルをロードできます。

Unity - マニュアル: ストリーミングアセット

データの保存領域について

スマホアプリとしてゲームを配布する場合、データ容量に注意する必要があります。AndroidiPhoneともに、アプリのサイズは最大50MBに制限されています。BMSファイルは1曲で数MB〜数十MBになるため、とてもアプリに含めて配布することができません。そこで、BMSファイルをダウンロードしてアプリ領域以外にデータを保存する領域が必要になります。

1.キャッシュ領域

キャッシュ領域はデータ容量の上限はありませんが、その代わり永続性はありませんので、ゲームデータを保存するのには向きません。

Unity - スクリプティング API: Application.temporaryCachePath

2.永続領域

キャッシュではなく永続的にデータを保存できる領域です。ゲームデータを保存するのにも良さそうです。しかし、iPhoneアプリでここをたくさん使う場合、リジェクト対象となるそうです。

Unity - スクリプティング API: Application.persistentDataPath

3.Asset Bundle

Asset Bundleはアプリ領域にデータを追加保存するようです。ソーシャルゲームなどで一般的に使われているのはこれだと思います。BMSファイルを配布するにはこれが一番良いとおもいきや、Pro版のみの機能ということであえなく断念しました。

参考:【Unity】AssetBundleを使わずに動的にデータを取得するゲームを作る | バイナリ覚書

Asset Bundleもどきを作る

Asset BundleはPro版でしか使えないので、永続領域にBMSファイルをダウンロードして保存し、そこからデータを読み込むことにしました。この場合、iPhoneではリジェクト対象となるのでAndroidだけをターゲットにすることにしました。実装までには次の3つの機能を作成します。

1.ダウンロード

データのダウンロードは、.NetのWWWクラスを使うことで実現できます。ファイルがローカルに存在するか確認し、なければダウンロードします。

     string filename = "bms.zip";
        string path = "file:///" + Application.persistentDataPath + "/";
        string url = "http://localhost/bmsdata/";
        if (File.Exists (Application.persistentDataPath + "/" + filename)) {
            Debug.Log ("Find local file." + Application.persistentDataPath + "/" + filename);
        } else {
            Debug.Log ("Can't find local file, Downloading..");
            WWW www = new WWW (url + filename);
            while (!www.isDone) {
            }
            File.WriteAllBytes (Application.persistentDataPath + "/" + filename, www.bytes);
        }

2.Zip解凍

BMSデータはzip圧縮しているので、ゲームをプレイする前に解凍します。最初は.NetのライブラリのZipFileクラスを使おうとしたのですが、Unityが利用するMonoは.Netの3.5までしか対応しておらず、ZipFileクラスは4.5で追加されたライブラリのため使うことができませんでした。そこで、CodePlexにあったDotNetZipライブラリを使用しました。

DotNetZip Library - Home

外部ライブラリを追加する場合はUnityのProjectに追加するだけでScriptからusingできるようになります。

f:id:bisque3311:20150305223842j:plain

以下はzipファイルから特定の拡張子のファイルだけを解凍するサンプルコードです。ExtractExistingFileAction.OverwriteSilentlyを指定することで、既にファイルが存在する場合も上書きします。

using Ionic.Zip;

        using (ZipFile zip = ZipFile.Read (Application.persistentDataPath + "/" + filename)) {
            zip.ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently;
            foreach (ZipEntry entry in zip) {
                if (Regex.IsMatch (entry.FileName, @"\.(wav|WAV|bme|BME)$")) {
                    Debug.Log ("decompress: " + entry.FileName);
                    entry.Extract (Application.persistentDataPath, ExtractExistingFileAction.OverwriteSilently);
                }
            }
        }

しかしこのライブラリは、Windowsにしか対応していないため、与えられたパスの'\'を'/'に置換するという処理が入っていました。そのため、Macなどの環境ではパス指定ができず読み込みエラーとなりました。(.NetのライブラリなのでWIndowsしか想定していないのは当たり前といえば当たり前ですね)。これをモンキーパッチ的にLinux用に修正されたモジュールがあり、それを使用することでこの問題はひとまず回避出来ました。

DotNetZip Library - View Issue #15236: "Path is empty" when extracting in a linux system.

3.AudioClipの作成

Unityでは音源をAudioClipのインスタンスにして再生します。ダウンロードした音源はWAVファイルなので、これをAudioClipに変換する必要があるため、以下を参考にして実装しました。正直、このレベルになってくると自分の力では相当難しいので、Githubにソースが公開されていて助かりました。

Post Position 【Unity】 WAVファイルからAudioClipを自前で生成するサンプルを作ってみた

以上で、Asset Bundleもどきができました。

できた後で知りましたが、Androidアプリの場合、ダウンロードなどはせずにSDカードに直接データを入れる方法を取るほうが一般的みたいですね。

次に続きます。