🔥

【Unity】プラットフォーム依存の処理の書き方について

2023/12/03に公開

はじめに

この記事は Panda株式会社 Advent Calendar 2023 3日目の記事です。

Panda株式会社は東京大学松尾研究室・香川高専発のスタートアップで、AR技術とAI技術を駆使したシステム開発と研究に取り組んでいます。
このアドベントカレンダーでは、スタートアップとしての知見、AI・AR技術、バックエンドなど、さまざまな領域の記事を公開していきます。

自己紹介

Panda株式会社Unityエンジニアの雨海陸です。大学入学してすぐにUnityでゲーム開発を始め、研究室や長期インターン等では画像処理や機械学習について学んでいます。
現在はPanda株式会社でMR[1]を活用したアプリケーションの開発をしています。

本記事について

Unityではプラットフォーム依存の処理の書き方として、Unityのプラットフォーム依存コンパイル(条件付きコンパイル)機能を利用する方法と、Application.platform の値をif文(switch文)により判定する方法の2つがよく用いられています。
今回の記事ではそれぞれの手法の違いについて調べたので、その結果をお伝えするとともに、皆さんがこれらの処理を使い分ける際の参考になれば幸いです。

環境

  • Unity 2022.2.9f1
  • Visual Studio Version 17.6.1

プラットフォーム依存コンパイルとはなにか

プラットフォーム依存コンパイルについて、Unityの公式リファレンスでは以下のように解説されています。

Unityの プラットフォーム依存コンパイル 機能は、いくつかのプリプロセッサーディレクティブで構成されており、スクリプトを分割して、サポートされているプラットフォームの 1 つのために専用にコードのセクションをコンパイルして実行することができます。

プリプロセッサディレクティブとは、#で始まる、コンパイル前に実行する命令のことを指します。
つまり、プラットフォーム依存コンパイルは、プリプロセッサディレクティブを用いてプラットフォームの判定を行う方法になります。
これにより、コンパイル前に条件分岐を行い、条件に合致した部分のコードのみを残してその他のコードを除外することで、プラットフォームによって異なるコードをコンパイラに読み込ませるようにしています。

プラットフォーム依存コンパイルとif文による条件分岐との違い

プラットフォーム依存コンパイルとif文による条件分岐では、コンパイラに読み込ませるコードが異なります。このことは"分岐するタイミング"と"エディタ上でのインテリセンスの適用範囲"に影響を与えます。また、コンパイラに読み込ませるコードを変えることで、通常コンパイルエラーになるような記述も可能になります。

分岐するタイミング

プラットフォーム依存コンパイルではコンパイル前に条件分岐を行いますが、if文を用いるとコンパイル後、すなわち実行時に条件分岐を行います。
従って、プラットフォーム依存コンパイルを行うことで、実行時に条件分岐が発生しません。
実際に、以下のコードで

  1. 条件分岐なし
  2. プラットフォーム依存コンパイル
  3. if文による条件分岐

の3パターンで10^8回条件分岐を行った場合の実行時間を比較しました。

void PlatformTest()
{
    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    int count = 0;

    //1.条件分岐なし
    sw.Start();
    for (int i = 0; i < Mathf.Pow(10, 8); i++) { count++; }
    Debug.Log($"1:{sw.ElapsedMilliseconds}ms");
    sw.Reset();

    count = 0;
    //2.プラットフォーム依存コンパイル
    sw.Start();
    for (int i = 0; i < Mathf.Pow(10, 8); i++)
    {
#if UNITY_EDITOR_OSX
        count++;
#endif
    }
    Debug.Log($"2:{sw.ElapsedMilliseconds}ms");
    sw.Reset();
	
    count = 0;
    //3.if文による条件分岐
    sw.Start();
    for (int i = 0; i < Mathf.Pow(10, 8); i++)
    {
        if (Application.platform == RuntimePlatform.OSXEditor) { count++; }
    }
    Debug.Log($"3:{sw.ElapsedMilliseconds}ms");
    sw.Stop();
}

このコードを実行した結果、それぞれの場合での実行時間は以下のようになりました。

1.条件分岐なし 2.プラットフォーム依存コンパイル 3.if文による条件分岐
2195ms 2188ms 5982ms

このことにより、プラットフォーム依存コンパイルを行った場合は条件分岐なしの場合とほとんど同じ実行時間であることがわかります。
この調査では実行時の処理の違いをわかりやすくするために実行回数を非常に大きくしており、4秒近くの差が生じていますが、基本的にはif文による条件分岐でもパフォーマンスに大きな影響はないと思います。

プラットフォーム依存コンパイルを利用したイレギュラーな記述

プラットフォーム依存コンパイルは読み込ませるコード自体を変えるため、コード内のどこでも分岐させることができます。
if文やswitch文はメソッド内にしか記述することができませんが、プラットフォーム依存コンパイルではコード内のどこでも分岐させることができます。

//フィールド変数の分岐処理
#if UNITY_EDITOR_OSX
    string hoge = "hoge";
#else
    int hoge = 0; //問題なし
#endif
    int hoge = 0; //コンパイルエラー
    
    void Start
    {
        ・・・
    }

これにより、不要な変数の定義を回避したり、プラットフォームによってメソッドのオーバーロードが必要になることを防ぎ、同じ処理を何度も記述することを避けたりすることができるようになります。
さらに、プラットフォーム依存コンパイルではプラットフォームによる条件分岐により実行されないコードはコンパイルの対象にならないため、同じスコープ内で同名の変数を宣言してもコンパイルエラーになりません。
これを応用すると、Unityで提供されているクラスを他のプラットフォームのために新しく定義したい場合などにも用いることができます。
このように、コンパイルの対象にならないことで、通常とは異なる記述ができるようになります。

エディタ上でのインテリセンスの適用範囲

プラットフォーム依存コンパイル機能を利用すると、エディタの動作環境以外の範囲にはインテリセンスが適用されなくなります。
Visual Studioのドキュメントでは、インテリセンス機能についてこのように説明されています。

Visual Studio では、専用の C++ コンパイラを使用して、すべての IntelliSense 機能を実現するデータベースを作成および維持します。

インテリセンスは専用のコンパイラが用いられていることから、インテリセンスの際にもプラットフォーム依存コンパイルが実行されていると考えられます。
インテリセンスが適用されないと作業効率が格段に悪化する恐れがあるのですが、この問題には解決策があり、以下の記事で紹介されています。
https://zenn.dev/fumiyahr/articles/3fa7013e43b5b5
大まかに説明すると、
Unityの Edit -> Setting -> Preferences -> Generate .csproj files for: -> Player projectsをONにする -> Regenerate project files
のようにしてインテリセンスの際にコンパイラが認識するプラットフォームを切り替えることができるようです。
ただし、この方法ではエディタを動かしているプラットフォームとその他のプラットフォーム(おそらくBuild Settingsで指定しているもの)に適用させることはできますが、分岐先に含まれるプラットフォームのうち複数を同時に適用させることはできません。
このようにコンパイラが認識するプラットフォームが切り替わるため、コンパイルエラーがVisual Studio側で検出されなくなる点には注意が必要です。(保存するとUnity側で検出されます)

まとめ

ここまでプラットフォーム依存コンパイルとif文による条件分岐によるプラットフォーム依存処理の違いについて紹介しましたが、結局は分岐するタイミングが違うということです。
これにより、パフォーマンスが少し向上したり、普通はできないような記述ができるようになったりします。また、インテリセンス機能については少し注意が必要になります。
ただ、基本的にはどちらを使っても大差ないと思いますので、どちらを使うかどうかは好みで良いかもしれません。

おわりに

今回は「プラットフォーム依存の処理の書き方について」というテーマでPanda株式会社 Advent Calendar 2023 3日目を執筆させていただきました。
本記事では、主にプラットフォーム依存コンパイルについて取り上げましたが、プリプロセッサディレクティブには他にもデバッグに便利な機能があるので、ぜひ活用してみてください。

明日は、弊社代表の田貝による「OpenAIのAPIとUnityで音声会話チャットボットを作る ~ チャット機能編 ~」です。Unityを始めたばかりの方でもチャットボットが作れるようになります、お楽しみに!

脚注
  1. Mixed Realityの略。現実世界と仮想世界の相互作用をもたらし、直感的な認識や操作を可能にします。 ↩︎

Panda株式会社

Discussion