littlewing

人間とコンピューターとメディアの接点をデザインするために考えたこと

Unityでビジュアル要素 (ヘッドレス) 無しのCLIアプリを作成する

Unityでアプリケーションを作成する際に、コマンドラインでも操作したい事があると思います。

Server Buildのオプションと、Mono.Options を利用することで、簡単に作成することができたのでその方法をメモします。

サンプルコードも最後にあります。

何に使えるのか?

ツール系アプリケーションをUnityで開発していると

  1. 起動時にコマンドラインでオプションを指定したい
  2. GUI無しでUnityで作成した処理を利用したい。
  3. マルチプレイ用のネットワークサーバとしてUI無しのヘッドレスモードで起動したい.
  4. アプリケーション実行中に標準入力を受け取りたい
  5. UbuntuCLI上で、ゴニョゴニョしたい。

ということがあり、活用できると思います。


CLIアプリ作成の前提知識

1. Server Build オプション

PC, Mac & Linux Standalone のPlatformには BuildSettings にServer Buildというオプションがあります。

f:id:pigshape:20210417212148p:plain

マニュアルは以下のように記述されています。

コマンドラインのオプションを全く使わずに、ビジュアル要素 (ヘッドレス) なしのサーバー用のプレイヤーをビルドします。これを有効にすると、UNITY_SERVER 定義を持つマネージスクリプトがビルドされます。これにより、アプリケーションにサーバー固有のコードを書くことができます。また、stdin と stdout にアクセスできるようにコンソールアプリケーションとして Windows バージョンにビルドすることもできます (Unity ログはデフォルトで stdout に保存されます)。

  • ServerBuildするとコンソールに以下のようなメッセージが表示されます。
NullGfxDevice:
    Version:  NULL 1.0 [1.0]
    Renderer: Null Device
    Vendor:   Unity Technologies
Begin MonoManager ReloadAssembly
- Completed reload, in  0.071 seconds
Microsoft Media Foundation video decoding to texture disabled: graphics device is Null, only Direct3D 11 and Direct3D 12 (only on desktop) are supported.

2. -batchmode オプション

  • Server Build オプションを有効にしなくても、通常ビルドで -batchmod のオプションを付けるとHeadlessで起動することもできます。
  • その場合 標準出力を受け取るには合わせて、 -logFile -のオプションも付与します。
./hoge.exe -batchmode -logFIle -

3. #if UNITY_SERVER ~ #endif

  • Server Build を有効にすると UNITY_SERVER のシンボルが有効になります。
  • これにより、引数や stdin/stdout の利用など、Server Build 時のみ有効なコードを記述する事ができます。
  • ただし、-batchmode を利用した場合は、UNITY_SERVER は有効にはなりません。

4. コマンドライン引数の解析

コマンドライン引数を利用するには入力オプションや値を解析する必要があります。

C# における、コマンドライン引数の解析は、いろいろライブラリがあるのですが、Mono.Options が追加ライブラリ不要で、そのままUnity内で利用できたので、それを今回は利用しています。

導入が容易で実装もシンプルなのが、良いですが正常系以外の処理などは充実していないので、高度なことをしたい場合は、他のライブラリも選定しても良いかもしれません。


Mono.Options について

  • Mono.Optionsに関してはこちらのサイト を見てもらうのがわかりやすいです。
  • UnityのC# でも、 using Mono.Options; を書くことで使うことができます。
  • 以下のサンプルコードにあるように、OptionSetを定義する事で簡単にコマンドラインの解析を行うことができます。

値を取りえるオプションの場合、以下のいずれの方法でも値の指定ができます。

-n value
-n=value
-name value
-name=value
--name value
--name=value
/n value
/n=value
/name value
/name=value

また、スイッチ系のオプションの場合、以下のような指定ができます。

# オンにする
-s
-s+
# オフにする
-s-

実際にコードを書く

  • Unityのシーンに、以下のコードを空のGameObjectにAttachして、ProjectSettingsでServerBuildをONすることで挙動を確認することができます。

  • 具体的には以下の処理を行うことができます。

    1. 起動時にコマンドライン引数を受け取る 例: xx.exe -v 10 —number=200 -x hoge

    2. 起動後の画面で標準入力としてオプションを与えることで、処理を制御することができる。 例 -v HOGE —number=200 -x FUGA

  • gistにもあります。

using Mono.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;

public class StdinReader : MonoBehaviour
{
    private string stringValue = "";
    private int number = 0;
    private bool help = false;

    private OptionSet options;

    void Start()
    {

#if !UNITY_SERVER
        return;//Server Build 以外は無視する
#endif
        //CLIアプリケーションの場合、UIの描画はされないので、
        //処理内容によってはフレームレートを抑えることで
        //CPUの負荷も減らすことができる。
        Application.targetFrameRate = 10;

        options = new OptionSet()
        {
            // string 型の引数を取るオプション
            { "v|value=", "specify a value.", v => stringValue = v },
            // string 型以外の引数を取るオプション
            { "n|number=", "specify a number.", (int v) => number = v },
            // 引数を取らないオプション
            { "h|help", "show help.", v => help = v != null },
            // 引数を持たないメソッドをコール
            { "t|test", "call test method", v => TestMethod()},
            // 引数を持つメソッドをコール
            { "x|xmethod=", "call test method2", v => TestMethod2(v) },
        };

        string[] args = System.Environment.GetCommandLineArgs();
        ParseCommandLineArgs(System.Environment.GetCommandLineArgs());

        Task.Run(() =>
        {
            string line;
            Console.WriteLine("CLI task start");
            while (true)
            {
                if ((line = Console.ReadLine()) != null)
                {
                    args = line.Split(' ');
                    ParseCommandLineArgs(args);
                }
                Task.Delay(100);
            }
        });
    }

    void ParseCommandLineArgs(string[] args)
    {
        List<string> extra;

        try
        {
            extra = options.Parse(args);
            Console.WriteLine($"value={stringValue}");
            Console.WriteLine($"number={number}");

            if (help) options.WriteOptionDescriptions(Console.Out);

        }
        catch (OptionException e)
        {
            Console.WriteLine(e.Message);
        }

        Console.WriteLine("GetLine:" + string.Join(" ", args));
    }

    void TestMethod()
    {
        Console.WriteLine("call TestMethod");
    }

    void TestMethod2(string _v)
    {
        Console.WriteLine("call TestMethod2:" + _v);
    }
}

参考サイト

Unity Editor Preferences の設定をC#から変更する

Unity Cloud Build に限った事では無いのですが、 自動ビルド環境を構築する際、Unity Editorの Edit > Preferences の設定をC# scriptから変更したい場合があると思います。

普段触らないので毎回忘れるのでメモ。

UnityEditor.EditorPrefs で設定できる。

結論としては、UnityEditor.EditorPrefs で設定できます。

docs.unity3d.com

ただ、どんな設定項目(key)があるのかの一覧を見つけることができなかったので、 手元の設定を開いてキーと型を見つけ出す必要があります。

上記、ドキュメントにもあるのですが、

  • Windowsの場合はレジストリ (Win + R > regedit)
    • HKCU\Software\Unity Technologies\UnityEditor 5.x
  • mac の場合は、
    • ~/Library/Preferences/com.unity3d.UnityEditor5.x.plist

を開いて見つけ出さないといけないようです。

Gradleの設定を切り替える

具体例として、Android ビルド時に利用するgradleの切り替えを行います。

デフォルト状態では、インストール時の設定にもよりますが、Unity組み込みのgradleが利用されます。

f:id:pigshape:20210327132019p:plain

これを変更するには、GUI上では Edit > Preferences > External toolsを開いて

  1. Gradle Installed with Unity recommended のチェックを外す
  2. Gradleのパスを browse を押して任意のパスに変更する

という手順を踏む必要があるのですが、これをスクリプトでやったのが以下のコードとなります。

gradle-5.6.4 が、Application.dataPath/../../gradle-5.6.4 に配置されている想定です。

public static class BuildAndroid
{
    private static void PreBuild()
    {
        var defaultGradle = EditorPrefs.GetString("GradlePath", "");

        var newGradle = Path.Combine(
            Application.dataPath,
            "..",
            "..",
            "gradle-5.6.4");

        EditorPrefs.SetBool("GradleUseEmbedded", false);
        EditorPrefs.SetString("GradlePath", newGradle);

        Debug.Log($"ChangeGradlePath: {defaultGradle} to {newGradle}");
    }
}

Unity Cloud Buildでも、gitリポジトリ内にgradleライブラリを配置して、

Config > Advanced Options > Pre-Export Method Name に上記 BuildAndroid.PreBuild を設定してやれば動作する事は確認できました。

(ARCore関連で必要に迫られることがあったのです)

EditorPrefs の設定は全プロジェクト共通なので注意

  • EditorPrefs の設定は、Editor側の設定なので、同一バージョンのエディターを利用している場合は、全UnityProjectで共通の設定として扱われます。
  • 特定のProjectのみgradleのバージョンを切り替える場合は、ビルド完了後に、gradleのバージョンを元に戻す設定を入れておいた方が良いかもしれません。

参考

Unity Bolt Tips6. BoltでJson.netを使う

Bolt Tips 連続記事です

前回の続き

Bolt でサーバ上のJsonを取得してParseしたのでその方法のメモ。

Jsonリアライザの選定

UnityでJsonフォーマットを読み込む場合、標準では UnityEngine.JsonUtility が用意されています。

ただし、JsonUtilityは

  1. root要素がリスト型の構造は取り扱えない。
  2. シリアライズ/デシリアライズのためには事前にC#でクラス定義しておく必要がある。

など、制約があります。

今回は、上記の制約がない、Json.Net(Newtonsoft.json)を利用します。

Newtonsoft.jsonであれば、C#のクラス定義が不要で、Boltのノードだけで完結することができます。

ただ、デメリットとしては処理速度が遅いようで、大量のデータを読み取る場合や頻繁にデータを更新する場合は JsonUtilityを使ったほうが良いかもしれません。

こちらの比較記事が参考になります。


Newtonsoft.jsonを扱うための準備

1. Newtonsoft.json のインポート

Newtonsoft.json は標準ではUnityには付属していません。そのため外部ライブラリとして取り込んでおく必要があります。

こちらの記事を参考にDLLをインポートします。

また、IL2CPPビルドの際にランタイム上でエラーが出る場合もあるので、link.xmlの調整が必要になる場合もあります。

2. Boltのセットアップ

また、Bolt環境で、Newtonsoft.jsonを利用しようとすると、初期設定のままでは利用できません。(右クリックでノードが出てこない)

Tools > Bolt > Setup Wizard でNewtonsoft.jsonが利用できるように設定を行う必要があります。

Assembly Options Type Options
f:id:pigshape:20210228131512p:plain f:id:pigshape:20210228131648p:plain
追加項目
Newtonsoft.json
追加項目
JsonCovert
Jobject
JArray
System.DateTime (日付を扱う場合)

実際にJsonを読み込んでみる

1. ローカルのテキストファイルを読み込む

ここまで準備できたら、テキストファイルとして保存されたJsonファイルを、Boltを利用して読み込んでみます。

Test.json

[
  {
    "key1": "value1",
    "key2": 0.1,
    "key3": 1,
    "datetime": "2021-02-18T02:31:57.000000Z"
  },
  {
    "key1": "value2",
    "key2": 0.2,
    "key3": 2,
    "datetime": "2021-02-18T02:31:57.000000Z"
  }
]

Resourcesフォルダに、Test.jsonファイルを配置してそれをstringとして読み込みます。

f:id:pigshape:20210228134442p:plain

2. Jsonを読み込む

その出力を JsonCovertでデシリアライズすることで値を取り出すことができます。 f:id:pigshape:20210228134807p:plain

  • Consoleの出力 f:id:pigshape:20210228134917p:plain

3. UnityWebRequestを利用してサーバからJsonを受け取る

UnityWebRequestを利用してサーバ上のJsonを取得する場合は以下のように記述すれば利用可能です。

  • Getでリクエストしています。
  • わかりやすいように、エラーハンドリング等かなり簡略化しています。

f:id:pigshape:20210228135534p:plain

以上。

Unity Cloud Build でsubmodule + symbolic linkを使う

Unity Cloud Buildシンボリックリンクを使うための方法を調べたのでメモ。

マルチデバイス向けのUnity開発プロジェクトや、メインプロジェクト内で、他のリポジトリの特定のフォルダ/ライブラリのみを利用したい場合、submodule と symbolic linkをよく利用します。

ローカル環境ではClone時に symbolic link 生成用のバッチ/shellを手動で実行すれば良いですが、Unity Cloud Build で利用する場合、そこも自動化する必要があります。

1. リポジトリ構成例

[Repository root]
├───Assets
├───Packages
├───ProjectSettings
├───_external
│   └───SUB_REPO1
│       ├───Assets
│       │   │   Scripts.meta
│       │   │
│       │   └───Scripts
│       ├───Packages
│       └───ProjectSettings
└───_tools
        ProjectGenerate.sh
        ProjectGenerate.bat
  1. _external/SUB_REPO1/ に 別のProjectをgit submoduleとして登録している
  2. _external/SUB_REPO1/Assets/Scripts/フォルダを メインの root/Assets 内でシンボリックリンクで利用したい
  3. シンボリックリンク作成用のShellは _tools/ProjectGenerate.shとして作成している

2. シンボリックリンク作成用のシェルスクリプトを作成

シンボリックリンク作成用のShellは以下のように作成します。 これは手元のShell環境(Macなど)でもそのまま動作するものです。 普段Windows環境では .bat のバッチファイルで書いています。

# _tools/ProjectGenerate.sh
# CWDにshell scriptのカレントディレクトリを代入する
CWD=$(cd $(dirname $0); pwd)
echo ${CWD}

# 対象のサブモジュールディレクトリ
SUBMODULE_DIR="${CWD}/../_external/SUB_REPO1/Assets/"

# メインのUnity Project
MAIN_DIR="${CWD}/../Assets/"

# Assets/Scripts/フォルダのシンボリックリンクを作成
ln -s ${SUBMODULE_DIR}Scripts ${MAIN_DIR}Scripts
ln -s ${SUBMODULE_DIR}Scripts.meta ${MAIN_DIR}Scripts.meta

3. Unity Cloud Buildの設定

一般的な設定内容に加えて、Pre-Build Script を設定することで、ビルド開始前にシンボリックリンクを作成します。

  1. Unity Cloud Build の該当プロジェクトのConfigメニューを開く
  2. advanced options > Pre-Build Script Path に shellを設定 _tools/ProjectGenerate.sh

これで、おしまいです。

また、この状態の時に、ビルドログに

[Unity] Error (Not a directory) occured whilst enumerating Assets/Scripts.meta

のエラーが表示されますが、ビルド自体は成功(Success)しているので、問題は無さそうです。


Subfolderを指定している場合の注意事項

ただし、Basic Info の Project Subfolder を設定している場合は、そのフォルダを起点としてパスを指定する必要があります。

Subfolder を指定しているにもかかわらず、リポジトリルートからのパスでスクリプトを指定すると

! Pre-Build Script configured, but not found at {Pre-Build Script Path}  Skipping.

のメッセージが表示され実行されません。


余談: Windows .bat の場合

シンボリックリンク作成用のShellはWIndowsの場合バッチファイルで作成しますが、上記と同じ内容をWindowsで作成する場合は以下のように書いています。 mklink と ln で シンボリックリンクの書き順が逆なので、よく間違える

  • _tools/ProjectGenerate.bat
@echo off
rem  _tools/ProjectGenerate.bat

set CWD=%~dp0
set SUBMODULE_DIR=%~dp0..\_external\SUBMODULE_REPO1\Assets\
set MAIN_DIR=%~dp0..\Assets\

cd %CWD%
echo %CWD%

mklink /d %MAIN_DIR%Scripts %SUBMODULE_DIR%Scripts
mklink %MAIN_DIR%Scripts.meta %SUBMODULE_DIR%Scripts.meta

おしまい。

Azure Static Web AppsとGitHubを使ってSSL+SNS認証付きWebサイトを手軽に構築する

Azure Static Web Apps とは?

Azure Static Web Apps を使用すると、GitHub の特定のbranchと連動して、自動更新されるWeb アプリケーションを簡単に構築できます。

docs.microsoft.com

同じような事はGitHub Pagesでもできるのですが、

  1. master 以外の特定のbranchを指定できる (運用環境とステージング環境とか)
  2. 認証機能を組み込める(json書くだけ)
  3. 地理的分散 による高速化
  4. 他のAzureリソースと組み合わせやすい
  5. プレビュー版なので無料

というところが、良さそうです。

現在、Azure Static Web Apps はパブリック プレビュー段階であり、無料で運用が可能です。

  • 1アプリ250MB以内/認証は25人までなど、制限が書かれています。 docs.microsoft.com

個人的には

  • UnityアプリをWebGLビルドして、人に見てもらう
  • iOSアプリのAdhoc/Enterpriseビルドの配布ページとして
  • ビルドしたアプリケーションやドキュメントの配布ページとして利用する

という使い方ができるんじゃないかなと思っています。


静的ページを公開してみる

公式ドキュメントにチュートリアルがあり、これにそって進めれば、簡単に試すことができます。 アカウントが一通り用意されていれば、15分ぐらいでも試せるのではないでしょうか?

docs.microsoft.com


本題、認証付きページを作成する

ここからがこの記事の本題なのですが、 Unityアプリの配布などを行う場合、不特定多数の人には見られたくないケースもあります。 そういった場合に、標準で組み込まれている認証機能が、便利そうなので試してみました。

現時点でドキュメントを見ると

のアカウントを利用したアクセス制限の設定が可能です。

docs.microsoft.com

サイト構成

簡単なテストなので以下の構成とします。/index.html は誰でも見れるページ /member/フォルダは承認された人しか見れないページとします。

「静的ページを公開してみる」のチュートリアルにそってHTMLを配置すれば、認証機能以外は簡単に準備できると思います。

/index.html (誰でも見れる)
/member/index.html (認証が必要)
/routes.json (route設定のjson)

routes.jsonが出てきましたが、ここで、認証の設定を行います。

{
    "routes": [
        {
            "route": "/login",
            "serve": "/.auth/login/github"
        },
        {
            "route": "/.auth/login/facebook",
            "statusCode": "401"
        },
        {
            "route": "/member/*",
            "serve": "/member/index.html",
            "allowedRoles": [
                "reader",
                "contributor"
            ]
        },
        {
            "route": "/",
            "allowedRoles": [
                "anonymous"
            ]
        }
    ],
    "platformErrorOverrides": [
        {
            "errorType": "Unauthenticated",
            "statusCode": "302",
            "serve": "/index.html"
        }
    ]
}

各項目で以下のような設定を行っています。

  1. /login はGitHubアカウント認証へのエイリアス(必要ないけど例として)
  2. facebookアカウントを用いた認証は status 401で利用できないようにしている(必要ないけど例として)
  3. /member/フォルダへのアクセスは Roleが readercontributorのユーザのみがアクセスできるように制限をかけている
  4. /(ルートフォルダ)は誰でもアクセスできる
  5. 未認証ユーザーは status:302 で /index.htmlへリダイレクトしている

という設定になっています。

また、公開している/index.htmlには、認証ページへのリンクを設置します。

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <title>Hello</title>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <link rel='stylesheet' type='text/css' media='screen' href='main.css'>
    <script src='main.js'></script>
</head>
<body>
    <h1>Hello (public page)</h1>

    <a href="/member/index.html">ダウンロードページ</a>へのアクセスは認証が必要です。
    
    <li><a href="/.auth/login/google?post_login_redirect_uri=/member/">Googleでログイン</a></li>
    <li><a href="/.auth/login/aad?post_login_redirect_uri=/member/">Azure Active Directoryでログイン</a></li>
    <li><a href="/.auth/login/github?post_login_redirect_uri=/member/">GitHubでログイン</a></li>
    <li><a href="/login?post_login_redirect_uri=/member/">GitHubでログイン(Alias利用)</a></li>
    <li><a href="/.auth/login/twitter?post_login_redirect_uri=/member/">twitterでログイン</a></li>
    <li><a href="/.auth/logout">Log out</a></li>
</body>
</html>

このような形で、jsonの設定だけで認証機能を設けることができます。

実際の承認対象ユーザを追加する。

  • ユーザの追加は Azure Portal 上で行います。

  • 対象のAzure Static Web Apps (静的Webアプリ) を開いて、

    設定 > ロール管理 >招待 からユーザを追加できます。

f:id:pigshape:20210111203217p:plain

プレビュー版の制限で最大25ユーザまでらしいですが、少人数への配布のために 手間なく、SSL +認証付きのページを作成できるのは、結構便利です。

iOSのUDID確認方法

UDID (Unique Device Identifer) は以下のような文字列です。

12345678-123456789012345E (ハイフン無しで24文字)

少し古めの機種だと

abcdefghijklmnopqrstuvwxyz12345678901234 (30文字)

の場合もあります。

MacOS を使ったUDID確認方法

  1. Xcodeを起動し、端末を接続
  2. Window –> Organizer –> Devicesタブ
  3. 左側のDEVICESから該当端末を選択
  4. Identifierの横の文字列がUDID

Windows を使ったUDID確認方法

以下を参考に

Unity Bolt Tips.5 日本語チュートリアルの動画・資料

Bolt Tips 連続記事です

前回の続き

Unity Technologies Japan から、Boltの使い方を説明する日本語のチュートリアルが出ていたので貼っておきます。

で公開されています。


YouTube

ビジュアルスクリプティングシステムBoltを使ってみよう 1回目

ビジュアルスクリプティングシステムBoltを使ってみよう 2回目


Slide Share

ビジュアルスクリプティングシステムBoltを使ってみよう 1回目

ビジュアルスクリプティングシステムBoltを使ってみよう 2回目