ScriptableObjectを設定ファイルとして扱う(時の注意点)

# はじめに

自分のScriptableObject活用法について定期的に書いています。
今回はScriptableObjectを設定ファイルとして扱う場合とその注意点について書きます。

【もくじ】
[その1 ScriptTemplateでScripableObjectのための環境構築](https://qiita.com/keidroid/items/40db91531c32a13c257f)
その2 ScriptableObjectを設定ファイルとして扱う ←今回

# 2. ScriptableObjectを設定ファイルとして扱う

アプリ内で条件によって定数群を変更したい場合があります。
以下の例のような場合、ソースコードを直接編集する方法もありますが、
ScriptableObjectを活用することが出来ます。

例1)
オンラインゲームを開発していて、本番環境と開発環境の接続先を変更したい。

例2)
スマートフォンゲームの開発で、Androidのみの不具合があったので
実行環境によってクライアントが持つバージョンを変更したい。
iOSのバージョンは3、Androidはバージョン4にしたい。

例3)
Editor実行時のみログを出力したい。

コードで編集するのと比較するとScriptableObjectを使用するメリットとしては以下があります。

- ソースコードに影響がない
- 新規作成や複製が容易
- 開発者以外の編集が容易 (SDKなどコード提供者と設定の編集者が異なる場合も同様)

↓設定に使用する定数群をソースコードで管理する例

```
public class Config
{
	//Android
/*
	public static readonly int VERSION = 4;
	public static readonly string HOST = "Android接続先";
*/
	//iOS
	public static readonly int VERSION = 3;
	public static readonly string HOST = "iOS接続先";

	//Android(開発環境)
/*
:
}
```

## 2.1 実装例

設定ファイルのScriptableObjectは以下のように作成できます。
(バージョン, ホスト, デバッグの有無を設定ファイルとして持つ場合)

```
public class ConfigModel : ScriptableObject
{
	public int ClientVersion;
	public string Host;
	public bool IsDebug;
}
```

各定数はInspector上で編集できます。

<img width="240" alt="スクリーンショット 2017-11-16 15.42.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/1025cf9e-cb8a-7987-24ff-9afadc081306.png">


## 2.2 設定ファイルに最適な読み込み方法を考える

2.1のScriptableObjectを作成し、適切に以下を行えば設定ファイルとして扱うことが出来ます。

1. ドラッグ&ドロップでMonoBehaviourにアタッチ
2. Resouces.Loadによって読み込み

### 2.2.1 Resourcesフォルダを使用する時の注意点(1)

上記のScriptableObjectの読み込み方法としてResouces.Loadで読み込む方法があります。
UnityのチュートリアルページによるとResourcesシステムには下記のような注意点があります。

https://unity3d.com/jp/learn/tutorials/temas/best-practices/resources-folder

> 2.1. Resourcesシステムの推奨される使用方法
> 使用しないでください。
> 
> 使用をお勧めできない理由はいくつかあります。
> 
> (中略)
> 
> 2.2. Resourcesシステムの適切な使用
つぎのふたつの場合は、開発に支障をきたすことなく Resources システムを有効に活用できる可能性があります。
> 
>- 速いペースでプロトタイプの制作や試作を行なうには、シンプルで使いやすい Resourcesシステムがとても便利です。ただし、プロジェクトが製品版を制作する段階に入ったら、Resourcesフォルダーの使用を止めることを強くお勧めします。
> 
>- Resourcesフォルダーは、以下のすべての条件がそろっており、重要度の低いケースを扱う場合に便利です。
> 
>  - Resourcesフォルダーに含まれるコンテンツのメモリ負荷が低い。
>  - プロジェクトの存続期間中、コンテンツが最後まで一貫して必要である。
>  - パッチの適用をほとんど必要としないコンテンツである
>  - プラットフォームやデバイスによって変わることがないコンテンツである。
>
> 上記の 2 番目のケースには、シングルトンの MonoBehaviour のホストになっていてプロジェクト全体に使用されているプレハブや、サードパーティの設定データのホストになっている Asset（Facebook App ID など）が含まれます。

ScriptableObjectで作成した設定ファイルに関して2.2に当てはめてみると..

|項目|判定|
|:--|:--|
|Resourcesフォルダーに含まれるコンテンツのメモリ負荷が低い。|✓|
|プロジェクトの存続期間中、コンテンツが最後まで一貫して必要である。|✓|
|パッチの適用をほとんど必要としないコンテンツである|✓|
|プラットフォームやデバイスによって変わることがないコンテンツである。|✓|

上記から一般的に設定ファイルに関して問題なさそうです。

### 2.2.2 Resourcesフォルダを使用する時の注意点(2)

https://docs.unity3d.com/jp/current/Manual/SpecialFolders.html

ResourcesフォルダとAssetsの関係は以下の仕様となります。
(※ScriptableObjectはAssetの一種です)

- Resourcesフォルダ下の有効なAssetsは【全て】ビルドに含まれる
- Resourcesフォルダ外のAssetsは必要分ビルドに含まれる
- ResourcesフォルダがEditorのサブフォルダの場合ビルドに含まれない

上記仕様から、隠蔽したい情報の設定ファイル(開発環境のURLなど..)を
Resourcesフォルダに置くとビルドに含まれてしまうことがわかります。

例えば以下のようにプラットフォームごとにScriptableObjectを分けた場合でも、
Resourcesフォルダ下にある限り全てビルドに含まれます。

↓Resourcesフォルダ下にあるので赤枠内のファイルは全てビルドに含まれる
<img width="640" alt="スクリーンショット 2017-11-16 16.09.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/89fdf8cb-19bd-9b4f-b088-6019e2ce5235.png">

## 2.3 自分の設定ファイル読み込み方法

2.2から、自分の場合は以下のように設定ファイルを読み込むようにしています。

1. 設定ファイル本体はResourcesフォルダ外に置く
2. プラットフォーム切替時など実行前に、使用するScriptableObjectをResourcesフォルダに移動する
3. 実行時にResources.Loadで設定ファイルを読み込む

2はビルド直前など任意のタイミングでokです。
プラットフォーム切り替えはUnity2017.1からIActiveBuildTargetChangedを使用することで取得できます。
(Unity5.6以前はactiveBuildTargetChangedで対応)


``` Assets/Editor/ConfigModelUpdater.cs
using UnityEditor;
using UnityEditor.Build;

public class ConfigModelUpdater : IActiveBuildTargetChanged
{
	public int callbackOrder
	{
		get	{ return 0; }
	}

	public void OnActiveBuildTargetChanged(BuildTarget previousTarget, BuildTarget newTarget)
	{
		//Resourcesフォルダ外に設定ファイルを置いて必要分をコピー
		string source = null;
		if (newTarget == BuildTarget.Android) {
			source = "Assets/Templates/ConfigModel/android.asset";
		} else if (newTarget == BuildTarget.iOS) {
			source = "Assets/Templates/ConfigModel/ios.asset";
		}

		if (source == null) {
			//例外か終了処理
		}
		string dest = "Assets/Resources/ConfigModel/config.asset";
		
		//更新前ファイルをmetaファイル含め削除
		FileUtil.DeleteFileOrDirectory(dest + ".meta");
		FileUtil.DeleteFileOrDirectory(dest);
		//ファイルをコピー
		FileUtil.CopyFileOrDirectory(source, dest);
	}
}
```

``` Assets/ConfigModel.cs
using UnityEngine;

[CreateAssetMenu(menuName="ScriptableObject/ConfigModel")]
public class ConfigModel : ScriptableObject
{
	public int ClientVersion;
	public string Host;
	public bool IsDebug;

	public static ConfigModel Load()
	{
		return Resources.Load<ConfigModel>("ConfigModel/config");
	}
}
```

↓複数Resourcesフォルダ外に置いて必要分のみResourcesフォルダにコピーする
<img width="640" alt="スクリーンショット 2017-11-19 15.59.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/3c4e122d-5caf-cb1d-17d2-14c8a447f888.png">


## つづく
設定ファイルをScriptableObjectで使用する際は、
Resourcesフォルダの仕様に注意点がいくつかあるという話でした。

マスターデータ編につづく