kumak1’s blog

kumak1のイラストや技術ログ

UnityのWebGLビルドをレンサバに設置する際やっとく設定のメモ

WebGL の Build and Run

Unity の WebGL プラットフォームの Build の際、 Build and Run をボタンを押せば Unity Editor はローカルサーバーを建てて動作確認することができます。 簡単便利。

f:id:kumak1:20210912224331p:plain

サーバーにアップロード

とわいえ「ローカルで動いたから」といって何も考えずにそのままレンタルサーバーにアップロードしても、そのままでは動作してくれない。 (いや、レンタルサーバーによっては動くかもしれんが)

Brotli のエラー

Unable to parse Build/temp.framework.js.br!
If using custom web server, verify that web server is sending .br files with HTTP Response Header "Content-Encoding: br". Brotli compression may not be supported over HTTP connections. Migrate your server to use HTTPS.

Gzip のエラー

Unable to parse Build/temp.framework.js.gz! This can happen if build compression was enabled but web server hosting the content was misconfigured to not serve the file with HTTP Response Header "Content-Encoding: gzip" present. Check browser Console and Devtools Network tab to debug.

なるほど、webサーバーの設定がうまくできてないのだな、と .htaccessgzip や brotli をなんやかんやしよう・・て試行錯誤したがなんやらうまくいかない。と踠いていたらもっとシンプルに対処できる方法を見つけた・・というより公式に書いてた・・。(わかりづらいけど)

WebGL: Compressed builds and server configuration - Unity マニュアル

サーバーで動作させるために設定すること

1. Decompression Fallback にチェックを入れて Build (ここではBrotliの例を記載する。チェックを入れると br ファイルではなく unityweb という独自の拡張子で書き出し、 loader.js でよしなにしてくれるらしい) f:id:kumak1:20210913000140p:plain

2. 下記記述の .htaccessBuild ディレクトリ直下に配置

<IfModule mod_mime.c>
  AddEncoding br .unityweb
</IfModule>

3. サーバーにアクセスしたら見れる!

やったー。 これで安易にゲームを公開できるようになったぞ。

UnityでShaderを書くときはなるべく #pragma vertex vert_img を使わないようにする

Unityでもビジュアルスクリプティングが叫ばれる昨今ですが、 レビューしたり、後からの修正のしやすさを考えると、スクリプトでゴリゴリ書きたいわたしです。

Render Texture を操作する Shader を書きたくなった時、 下記のように記述して、vertex shader を UnityCG.cginc に宣言済みの vert_img 関数を指定すると、 fragment shader の記述に集中できるので楽チンですね。

#include "UnityCG.cginc"
#pragma vertex vert_img

思わぬ落とし穴

私は PostProcess をモリモリ自作していて、そこで Render Texture を操作する Shader をよく記述します。 のだけども、iOS 用にビルドしてiPhoneの実機で表示確認してみると、Mac の Unity Editor の表示と全く違う表示がされた・・

f:id:kumak1:20210704014222p:plain
上はMac,下はiPhoneでの実際の画像

なんで・・

原因は先ほど利用した vertex shader の v2f_img です。 UnityCG.cginc で定義されている記述はこちら。

struct appdata_img
{
    float4 vertex : POSITION;
    half2 texcoord : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f_img
{
    float4 pos : SV_POSITION;
    half2 uv : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
    UNITY_VERTEX_OUTPUT_STEREO
};

v2f_img vert_img( appdata_img v )
{
    v2f_img o;
    UNITY_INITIALIZE_OUTPUT(v2f_img, o);
    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    o.pos = UnityObjectToClipPos (v.vertex);
    o.uv = v.texcoord;
    return o;
}

そう、 half が悪さをしていました。 PC(Mac)とMobile(iOS)で表示が異なる場合は、だいたい精度の問題ですね・・。

struct appdata_imghalf2 texcoord : TEXCOORD0;struct v2f_imghalf2 uv : TEXCOORD0; となっているため、Render Texture の座標精度が悪くて崩れてしまうのだ。

Unity公式だってテクスチャ座標を扱うときは float使え って言ってる。 (また iPhone12 の画面の解像度は 2,532 x 1,170仮数が10bitしかない half では、2532は表現しきれない。)

対処法

残念だが v2f_img の利用はやめよう。 便利だったけれど。お世話になったけれど。 無理せず float2 で宣言し、 appdata_base を受け取るようにしよう。 ( appdata_basetexcoordfloat4 で宣言されている )

struct v2f {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
    UNITY_VERTEX_OUTPUT_STEREO
};

v2f vert(appdata_base v)
{
    v2f o;
    UNITY_INITIALIZE_OUTPUT(v2f, o);
    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    o.pos = UnityObjectToClipPos (v.vertex);
    o.uv = v.texcoord;
    return o;
}

まとめ

アイコンなど、少ない低解像度の画像を扱うときは v2f_img を使っても良いが、 Post Process などで高解像度の画像を扱うときは v2f_img を使ってはけない。

Unity の Reorderable のエディタ拡張を作る

unity.com

Unity 2020.2 の Inspector では、配列は並び替えが可能な Reorderable なものに変わりました。

自作クラスのデフォルト表示

下記のように [Serializable] を指定した自作クラスについても、手軽に ReorderableGUIを Inspector で確認できます。便利ですね。

f:id:kumak1:20201223135307p:plain

using System;
using UnityEngine;

[Serializable]
public class TestObject
{
    public GameObject Prefab;
    public Vector3 Position;
    public Vector3 Rotate;
}

public class TestComponent : MonoBehaviour
{
    public TestObject[] TestObjects = {new TestObject(),};
}

けれど、ちょっと見づらいですね。イマイチポイントを挙げると以下のとおり。

  • 一覧性に欠けている
    • Foldoutを閉じた状態 : Element 1 といった連番のラベル情報しか表示されない
      • → 配列の中身を識別できる固有の情報を表示したい
    • Foldoutを開いた状態 :
      • → インデントを用いて、データのまとまりを表現したい

自作クラスのエディタ拡張

ということで、エディタ拡張を書いて解決をしてみます。 EditorGUILayout は使えないので、 EditorGUI で Rect をしっかり指定します。

f:id:kumak1:20210104010656p:plain

using System;
using UnityEngine;
using UnityEditor;

[Serializable]
public class TestObject
{
    public GameObject Prefab;
    public Vector3 Position;
    public Vector3 Rotate;
}

public class TestComponent : MonoBehaviour
{
    public TestObject[] TestObjects = {new TestObject(),};
}

[CustomPropertyDrawer(typeof(TestObject))]
public class TestObjectDrawer : PropertyDrawer
{
    private static float GetPropertyHeight(SerializedProperty property = null)
    {
        var height = property == null
            ? EditorGUIUtility.singleLineHeight
            : EditorGUI.GetPropertyHeight(property, true);

        return height + EditorGUIUtility.standardVerticalSpacing;
    }

    private static bool FoldoutField(ref Rect rect, SerializedProperty property, string label, string propertyName)
    {
        var prop = property.FindPropertyRelative(propertyName);
        prop.isExpanded = EditorGUI.Foldout(rect, prop.isExpanded, GUIContent.none);
        EditorGUI.PropertyField(rect, prop, new GUIContent(label));
        rect.y += GetPropertyHeight(prop);

        return prop.isExpanded;
    }

    private static void Field(ref Rect rect, SerializedProperty property, string label, string propertyName)
    {
        var prop = property.FindPropertyRelative(propertyName);
        EditorGUI.PropertyField(rect, prop, new GUIContent(label), true);
        rect.y += GetPropertyHeight(prop);
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        position.height = EditorGUIUtility.singleLineHeight;

        if (FoldoutField(ref position, property, "Prefab", "Prefab"))
        {
            EditorGUI.indentLevel++;
            Field(ref position, property, "Position", "Position");
            Field(ref position, property, "Rotate", "Rotate");
            EditorGUI.indentLevel--;
        }
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        var enableSkinName = property.FindPropertyRelative("Prefab");
        var height = GetPropertyHeight(enableSkinName);

        if (enableSkinName.isExpanded)
        {
            height += GetPropertyHeight(property.FindPropertyRelative("Position"));
            height += GetPropertyHeight(property.FindPropertyRelative("Rotate"));
        }

        return height;
    }
}

これでようやく使い物になりそうな見た目になりましたね。 ちょっとした気遣いで、UnityEditor上の作業はすごく楽になるので、「ゲームの内容には関係ないし・・」とか思わず、ちょいちょいっと書いていきましょい

M1 搭載 Mac mini を購入したので Unity のビルド時間とか測ってみた

総評

ワットパフォーマンス・コストパフォーマンス・静音性(ほぼ無音)がとんでもなく良い。 (比較用2016, 2019 のMacBook Pro はそれぞれ30万クラス。自作PCも20万弱程度で組んだハズ。一方、今回購入したMac mini は11万円)

このため、メイン開発環境はもちろん、常時起動のビルドサーバ用にとりあえず買う、のも大いにアリだと思う。 (Webエンジニアとしては、Docker対応されるまでintelさんにはお世話になるけども)

計測

手元にあるモノで色々とベンチマークとってみた。

端末 自作PC MacBook Pro 2016 MacBook Pro 2019 Mac mini 2020
CPU Intel Core i5 4670
4core
Intel Core i7 6700HQ
4core
Intel Core i9 9880H
8core
M1
4core + 4core
GPU GeForce GTX 1060 Radeon Pro 460 Radeon Pro 560X M1
RAM 32GB 16GB 16GB 16GB
消費電力
(idle)
83W 8W 17W 2.5W
消費電力
(Cinebench Multi)
117W 61W 91W 22W
Cinebench R23
( Single Core )
881 821 1113 1519
Cinebench R23
( Multi Core )
2796 3962 7468 7815
Unity 2020.2.0b12
iOS (IL2CPP) Build Time
( 3D Game Kit )
- 00:09:16 00:05:17 00:05:16
Unity 2020.2.0b12
Win (Mono) Build Time
( 3D Game Kit )
- 00:08:59 00:05:12 00:04:46
Unity 2020.2.0b12
Win (IL2CPP) Build Time
( 3D Game Kit )
00:12:14 - - -
Unity 2020.2.0b12
Linux (IL2CPP) Build Time
( 3D Game Kit )
00:29:36 00:24:42 00:13:37 00:13:09

Unityのビルド時間は、大方 Cinebench スコア比の通りの差になっている。 Unity 2020.2.0b12 ではまだ Apple Silicon にネイティブ対応したアプリケーションではないため、対応版だともうちょっと短縮されるのかもしれない。

余談

CPU と高速ストレージが上手いこと噛み合っているからか、アプリケーションのインストールや、アセットのインポートなどがガッツリ早くなっている。 Jetbrains Rider や Intellij IDEA の挙動も良い。Karabiner, 1Password, Alfred, Magnet といったユーティリティの動作も問題ないので、ゲーム開発する端末はもう移行しちゃおうと思う

Unity の Package Manager で GitHub の Private Repository を使う

2020/11/12 追記

manifest.json にtokenを直書きしたものをcommit・運用するのもよくないので、もんりぃ先生のいう通り git+ssh プロトコルで通信しよう!

コピペ用 リポジジトリ直下にasmdefファイルがある場合

git+ssh://git@github.com/[orgs]/[repo].git

asmdefの格納されているディレクトリを指定したい場合

git+ssh://git@github.com/[orgs]/[repo].git?path=[path/to/dir] 

結論

Personal access tokens を使った認証をしたらつかえる!

こばなし

Unity2020 にバージョンアップ Unity エディタ上から AssetStore は見れなくなり「Package Manager を使ってね」という圧がかけられるようになりました。なるほど。 購入アセットが100を超えてるので、一覧性がキビしい事を除けば、色々とできることが増えてて良さそうですね。

Package Manager 用の自作ライブラリの作り方

どうやら AssetStore で購入したライブラリだけでなく、自作ライブラリもお作法に則って作成・公開すれば、Package Manager で取り込みできるようになるみたい。大変便利だ。

PackageManagerで自作ライブラリを作成する方法 - Qiita

Package Manager で使えるライブラリのホストについて

GitHub の Public Repository が使えるのは上のQiitaの記事でも確認し、実践済み。 が、Private だとかで認証使いたい場合、他の人は npm サーバーを自分で建てたりしている。

UnityのPackageManagerプライベートリポジトリの調査 - 渋谷ほととぎす通信

Verdaccioでローカルのnpmサーバーを立ててUnityPackageManagerにプライベートリポジトリを使う - 渋谷ほととぎす通信

Unity Package Manager が認証に対応しました! - もんりぃ is undefined.

しかし、個人プロダクトのセキュリティを自分自身で面倒みるのは、正直コスパが悪すぎる。 このため、GitHub Packages を利用するのが適切・・なのだが、Private な Packages 0$運用するにはストレージ容量とデータ転送量が限られる。 パーソナルアクセストークンを使用するアクセスは無料なので、開発メンバーが1人だけで500MB以下の運用なら積極的に使っていこう。 スケールしてもお金で殴れば解決するし。

GitHub Packagesについて - GitHub Docs

GitHubパッケージの支払いについて - GitHub Docs

Package Manager で GitHub の Private Repository を使う設定

「Packages にするほどでもないけど、金かけたくないし、なんか雑に試したいし、認証付きで公開したいんじゃい!」という事もあると思う。 この場合 Personal access tokens を使った認証をしたら、すんなり利用できた。

自身のUnity プロジェクトの manifest.json に以下のフォーマットで読み込みたいリポジトリを指定するだけ。

{
  "dependencies": {
    [中略]
    "{package_name}": "https://{token}:x-oauth-basic@github.com/{owner}/{repo}.git"
  }
}

{package_name} {token} {owner} {repo} はよしなに置き換えてください。 ちなみにアクセストークンは以下リンクから発行できる。

https://github.com/settings/tokens

開発環境をうまく使いこなして、快適にすごそう

Unity の Command Buffer 用の Shader を雑に試せるスクリプト書いた

会社でお昼ご飯食べながら Amazon.co.jp: ディジタル画像処理 [改訂第二版] eBook: ディジタル画像処理編集委員会: Kindleストア を読む会をすることになった。

内容をパラっとめくってみるとどうやら、理論・数式・画像処理前後のサンプル画像は載っているが、実コードには落とし込まれていないようだ。 (汎用性を持たせるために、この構成は正しいと思う)

「より理解を深めるために実際にコードを書きたい!できればShaderで!」 「処理対象の画像は手軽に作り出したい!」

と思ったので、Unity のカメラへ手軽にShader(Material)を重ね掛けできるコンポーネントを作った。

GitHub - kumak1/CommandBufferTester

利用方法は、Readmeの通りに Unity Package Manager で Package を追加し、Camera オブジェクトにコンポーネントを追加するだけ。 Materials パラメータに自作の Shader(Material)をポイポイ登録すればOK。 (Shader の作成は https://github.com/kumak1/CommandBufferTester/blob/master/shader/Grab.shader を見るとお約束がわかると思う)

f:id:kumak1:20200920000609p:plain

捗る準備はできたので、勉強すすめよ〜

Unity の Assembly Definition の依存関係を PlantUML で出力する雑スクリプト書いた

Unity - Manual: Assembly definitions 使ってますか?

qiita.com

上記の記事のような完全に理解したツワモノのおかげで、わたしも色々恩恵をいただいております。 肥大化した自作コードを分割できたり、エディタービルド時間を削減できたりして便利ですね。

f:id:kumak1:20200913234032p:plain

コードで上で縛るのはもちろん、このように単体の依存関係を目で確認しやすいのも良い所ですね。

ここまでできると「プロジェクト全体など任意のスコープで俯瞰した図をみたい」「Docusaurusなどのドキュメントでも俯瞰図みたい」と欲が深くなったので、指定ディレクトリ配下を検索して PlantUML を出力できるようなスクリプトを書いてみました。

jqyq を利用してるので注意、と、よしなに改変してご自由に利用ください。

#!/usr/bin/env bash

JQ=/usr/local/bin/jq
YQ=/usr/local/bin/yq

TARGET_FILE=\*.asmdef
WORK_DIR=$1
UP_ARROW="-up->"

# オプションがなければカレントディレクトリが処理対象。
# 空白のあるディレクトリ名・ファイル名を考慮してないので注意
if [ -z "${WORK_DIR}" ]; then
  WORK_DIR="."
fi

ITEMS=$(find "${WORK_DIR}" -name "${TARGET_FILE}")
ROOT="\"Assembly-CSharp\""
TEXTS=()

echo "@startuml"
echo "component ${ROOT}"

for ITEM in ${ITEMS}; do
  NAME=$(cat $ITEM | $JQ '.name')

  # コンポーネントを宣言
  echo "component ${NAME}"

  # Assembly-CSharp への依存関係を明示
  if [ $(cat $ITEM | $JQ -r '.autoReferenced') = "true" ]; then
    echo "[${NAME}] ${UP_ARROW} [${ROOT}]"
  fi

  # その他ライブラリへの依存関係をいったん控える
  for GUID in $(cat $ITEM | $JQ -r 'select(.references != null) | .references[]' | sed s/GUID://); do
    TEXTS+=("[${NAME}] ${UP_ARROW} [${GUID}]")
  done
done

# bash 3.2 利用想定なので非常に非効率(ハッシュが使えない・・)
# 利用しているシェルに合わせて、GUID -> Assembly Definition File Name への置換処理をチューニングしましょう
for ((i = 0; i < ${#TEXTS[@]}; i++)) {
  SHOW=false

  for ITEM in ${ITEMS}; do
    NAME=$(cat $ITEM | $JQ '.name')
    GUID=$($YQ read $ITEM.meta 'guid')

    if [ "`echo ${TEXTS[$i]} | grep ${GUID}`" ]; then
      echo ${TEXTS[$i]} | sed s/${GUID}/${NAME}/
      SHOW=true
      break
    fi
  done
  
  if ! "${SHOW}"; then
    echo ${TEXTS[$i]}
  fi
}

echo "@enduml"

これを実行すると PlantUML が標準出力されるので、クリップボードにコピーしてよしなに利用しましょう。 JetBrains IDE 利用者は PlantUML integration - Plugins | JetBrains を使うと scratch で作成できるし、プレビューも画像保存も手軽で良い。

で、実際に私が出力してみたものがこんな感じ。

f:id:kumak1:20200913235612p:plain

どう依存してるかは俯瞰してみれるようになったが、なんだか大変煩雑な図ができあがってしまった。。コードをもっと整理しないとな・・ (図内でGUIDが直接出力されているのは、対象ディレクトリ外に Assembly Definition File があるため。Rewired や Behavior Designer などの asset だ)

なにはともあれ、こういった問題点も可視化できて便利だな?