UnityでWindows向け個人ゲーム開発を行っている者です。
サクッと終わるミニゲームでない限り、ゲームの状態をセーブファイルに保存する必要があります。
Steamなどで販売を行う方であればオンライン保存も可能かもしれませんが、自分はホームページでの配布を予定しているため、それは使えません。
レジストリに保存するのも現実的ではないので、セーブデータはファイルとして保存する方針です。
ここで問題になるのが、Windowsのセキュリティ機能、いわゆるUAC(ユーザーアカウント制御)による保存制限です。
今回は、その対策について考えていきます。
セーブファイルに関する問題について
WindowsのUACにより、管理者権限が必要なフォルダ(例:C:\Program Files や C:\Windows など)には、通常の権限では書き込みができません。
「プログラムをPCにインストール=C:\Program Filesに配置」と考える方も多いでしょう。
この状態で、ゲームが実行フォルダにセーブデータを保存しようとすると、エラーが発生したり、保存処理がサイレントに失敗したりすることがあります。
ちなみにWindows Vistaでは、管理者権限が不要なフォルダでもUACによる制限がかかることがありました。ただし、現在Vistaを使っているユーザーは非常に少ないため、あまり気にする必要はないかもしれません。
Unityゲームでセーブ制限を回避・緩和するための対策案
UACによる保存制限に対応するにはどうすればよいか。いくつかの案を挙げて検討していきます。
Application.persistentDataPathを使う
これはUnityが推奨する方法です。Application.persistentDataPathを使用すると、以下のようなパスが取得できます。
C:\Users\ユーザー名\AppData\LocalLow\会社名\ゲーム名\
※会社名とゲーム名はBuild Settingsから設定できる
このフォルダはユーザー権限での書き込みが許可されており、保存処理が失敗することはほぼ無いと思います。
ただし、パスが非常に分かりづらいため、ユーザーがセーブファイルの場所を探したり、手動でバックアップしたりするのが難しいというデメリットがあります。
Environment.GetFolderPathを使う
Environment.SpecialFolderを指定することで、Windowsの標準フォルダ(例:MyDocuments、AppDataなど)へのパスを取得できます。
これによりMyDocumentsなどに保存できるようにもなります。
MyDocumentsに勝手に保存されるのはユーザからしたら気持ち悪すぎるので、これを使うならApplication.persistentDataPathで良い気がします。
Environment.SpecialFolder.LocalApplicationDataでC:\Users\ユーザー名\AppData\Localは取れるので、自作でApplication.persistentDataPathと同じような実装をしたい場合は使うことはあるかもしれない。
会社名\ゲーム名となるのは階層が深すぎると思ったときなどです。
READMEに特殊フォルダに置かないようにお願いする
セーブファイルは自フォルダに保存することにします。
「C:\Program Filesに置かないで」という趣旨のことをゲームの説明に書いておくという作戦です。
果たして読まれるのか?という問題があります。。。
しかしプログラム側での回避策を検討しなくてもいいというメリットが非常に大きいです。
インストール先を確認して、危険フォルダなら警告を出す
READMEが読まれないことの対策として、ゲームフォルダが危険なフォルダに有ったら警告を出すという手段があります。
例えば、以下のような関数で危険なフォルダに配置しているかどうかを検知します。
// 危険なフォルダにインストールされているかをチェックする
bool IsInRestrictedFolder()
{
string installPath = Application.dataPath;
// 一般的にUAC制限を受けやすいフォルダ
string[] restrictedPaths = new string[]
{
@"C:\Program Files",
@"C:\Program Files (x86)",
@"C:\Windows"
};
foreach (string restricted in restrictedPaths)
{
if (installPath.StartsWith(restricted, System.StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}仮保存 → 本保存の2段階処理
まず自フォルダにセーブを試み、失敗した場合にApplication.persistentDataPathなどへ保存先を切り替えるという戦略です。
これはユーザーの満足度は高そうですが、実装難易度は少し高めです。
「セーブ時にtry catchするだけだろ?」と思うかもしれませんが、ロードするときには保存処理が発生しないので、ファイルを色々な場所に探しに行かないといけないのです。
自分だったらセーブデータの保存はどうする?
色々方法はあると思うのですが、自分は「仮保存 → 本保存の2段階処理」で行きたいと思います。
まず1ゲームユーザとして、「C:\Users\ユーザー名\AppData\LocalLow\会社名\ゲーム名\」に保存されるというのはやめてほしいという気持ちがあります。シンプルにバックアップを取れないからです。
一方で、どうしても「C:\Program Files」に置きたいというユーザもいると思っています。自分がPCのフォルダ構成などはこだわりたいタイプだからタイプとしては近いです。
両方のニーズに答えるなら、「仮保存 → 本保存の2段階処理」しかないかなと。
実装案ですが、まずゲーム起動時に自フォルダに保存できるかチェックして、グローバルな変数などにチェック結果を記載します。
// プロジェクトフォルダに書き込み可能か
public static bool canWriteToAppFolder = true;
// 自ディレクトリに書き込み権限があるかチェックする
public static void CanWriteToAppFolder() {
try {
string testFile = Path.Combine(FileUtil.GetProjectPath(), "write_test.tmp");
File.WriteAllText(testFile, "test");
File.Delete(testFile);
canWriteToAppFolder = true;
} catch {
canWriteToAppFolder = false;
}
}実際にテスト用のファイルを書き込んでみるという確実な戦法です。そしてセーブとロード側で処理分岐してあげればOKとなります。
// セーブデータの保存先ディレクトリを取得する処理
string folder;
if (canWriteToAppFolder) {
folder = AppDomain.CurrentDomain.BaseDirectory;
} else {
folder = Application.persistentDataPath;
}


コメント