Unityの開発でPreprocessorを使ってStaging環境を切り替える方法

# 概要

ソシャゲーやネットワークゲームを作る際、実装中にはローカルに立てたサーバーに接続して、実装がある程度できたら共通のデバッグサーバーに接続して、完成したら本番サーバーに接続して検証するといったStagingを行うことになると思います。いろいろなやり方があると思いますが、Preprocessor(#define,#ifとかのこと)を使った方法を紹介したいと思います。

# 満たしたい条件

* 各ステージの設定情報を一箇所に集約できる
* お手軽にStagingを切り替えられる
* 個人で完結し、ソースを共有した時に他の人に設定が共有されない

# 使う技術

* Preprocessor
* グローバルのカスタム Define シンボル
* Editorのメニュー拡張
* .gitignore


##　1.設定情報の集約

まず、私の場合は、設定をSetting.csというファイルに纏めて、各Stageの情報を、変数として定義しています。各Stageで同じ名前の変数を定義し、PreprocessorでdefineしたStageが有効になるようにします。コードに直書きできるので、設定ファイルをパースしたりする必要が無いので楽ちんです。

```csharp:Assets/Setting.cs

using System;

namespace MyApp
{
    public static class Setting
    {
        #if PRODUCTION
        public const string Env = "production";
        public const string APIServer = "https://prod.myapp.com";
        #elif LOCAL
        public const string Env = "local";
        public const string APIServer = "http://localhost:9000";
        #else // Stage1をデフォルトにしている
        public const string Env = "stage1";
        public const string APIServer = "http://stage1.myapp.com:9000";
        #endif

    }
}

// 使う際には以下のように書いておくだけ
APIClient client = new APIClient(Setting.APIServer);

```

## 2.Defineシンボルの設定

先ほどのSetting.csだけでは、#defineがされていないので、デフォルトのStage1しか有効になりません。そこで、外部でDefineシンボルを定義します。この際、Unityではグローバルに定義する方法が幾つかあり、今回の場合は
http://docs.unity3d.com/ja/current/Manual/PlatformDependentCompilation.html
の一番下の Assets/smcs.rsp を利用したグローバルDefineシンボルを利用します。
ファイルの中身は以下の様になります。STAGE1のところを、適宜切り替えたいStagieに変更します。

```text:Assets/smcs.rsp
-define:STAGE1
```

## 3.簡単に切り替えられるようにする

上のファイルをいちいち手で書き換えるのは面倒なので、UnityEditor拡張を使って、メニューから変更できるようにします。コードは下になります。
コメントにも書いていますが、smcs.rspの更新をしても通常では再コンパイルが行われないので、変更したdefineシンボルを使っているソースコードを再インポートして、強制的に再コンパイルをかけています。

```cs:Assets/Editor/StagingSwitcher.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
using System.Linq;

public class StagingSwitcher : EditorWindow {

    const string DEF_LOCAL = "LOCAL";
    const string DEF_STAGE1= "STAGE1";
    const string DEF_PRODUCTION = "PRODUCTION";
    static string[] STAGINGS = {DEF_LOCAL,DEF_STAGE1,DEF_STAGE2,DEF_PRODUCTION};

    /// <summary>
    /// http://docs.unity3d.com/ja/current/Manual/PlatformDependentCompilation.html
    /// </summary>
    const string SMCS_FILE = "Assets/smcs.rsp";
    /// <summary>
    /// smcs.rspを更新しても、自動でコンパイルが走らないので、
    /// smcsに追加する定義を使用するファイルを再インポートすることで、再コンパイルを行うようにする
    /// </summary>
    const string ReimportTarget = "Assets/Setting.cs";

    static string lastStaging = "";

    [MenuItem ("Window/MyApp/Staging/local", false)]
    public static void SwitchToLocal(){
        UpdateStaging (DEF_LOCAL);
    }

    [MenuItem ("Window/MyApp/Staging/stage1", false)]
    public static void SwitchToStage1(){
        UpdateStaging (DEF_STAGE1);
    }

    [MenuItem ("Window/MyApp/Staging/production", false)]
    public static void SwitchToProduction(){
        UpdateStaging (DEF_PRODUCTION);
    }


    public static void UpdateStaging(string stagingDef){
        string text = "";
        if (File.Exists (SMCS_FILE)) {
            text = File.ReadAllText (SMCS_FILE).Trim();
            var lines = text.Split ('\n').Where(line => line.Length > 0);
            text = string.Join ("\n", lines.Where (line => STAGINGS.All (staging => !line.Contains (staging))).ToArray ());
            if (text.Length > 0) {
                text += "\n";
            }
        }
        // predef定義を追加
        text += "-define:" + stagingDef;
        File.WriteAllText (SMCS_FILE, text);

        if (lastStaging != stagingDef) {
            // スクリプトをインポートして、再コンパイルを促す
            AssetDatabase.ImportAsset (ReimportTarget);
            Debug.Log ("Switch to " + stagingDef);
            lastStaging = stagingDef;
        }
    }
}
```


## 4.他の人に設定が共有されないようにする

gitなどのソース管理ツールで、Stageを切り替えた後うっかり共有してしまい、他の人がpullでソースを取り込んだ時に気づかず本番に接続してしまうなどが起こりえます。なので、できればStagingの設定は共有しないようにしたいです。そのためにsmcs.rspを共有しないように設定します。私はgitを使用しているので、以下の設定を追加してStagingの設定が他の人に反映されないようにしています。

```text:.gitignore
//この一行を追加。metaファイルもこれで共有されなくなる
Assets/smcs.rsp*
```

# 終わりに

以上が私のやっている方法の紹介になります。是非みなさんの参考になれば幸いです。