simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:頂点生成三角柱のインスタンスの統合による高速化

前回 15 x 15 x 15 の計3375個の三角柱を生成したとき、その時のインスタンス作成処理に 0.3秒ほど要していました。
ここから先、画面を埋め尽くすほどのブロックの数を表示しようというのに、もう既に諦めなければならないほど低速です。
この処理を高速化するため、インスタンスの数を減らして、メッシュを統合する方法をとってみます。

これも昔行った手法です。
simplestar-tech.hatenablog.com

メッシュ作成の結果、見た目は同じでも
f:id:simplestar_tech:20180922175632j:plain

インスタンス生成時の負荷は 11.84 ms (頂点バッファ確保と計算が主な仕事)
f:id:simplestar_tech:20180922175703j:plain

メッシュ作成時の負荷は 4.72 ms (一気に 5000 頂点分の設定のため多少時間を要する)
f:id:simplestar_tech:20180922175918j:plain

でした。
もともとの三角柱ごとにインスタンスを作る方法は、インスタンス生成時に 267.92 ms 要していました。
f:id:simplestar_tech:20180922180132j:plain

もともとの三角柱ごとにインスタンスを作る方法は、メッシュ作成時に 58.73 ms 要していました。
f:id:simplestar_tech:20180922180330j:plain

もともとの三角柱ごとにインスタンスを作る方法は、このチャンク(16 x 16 x 16 の三角柱の塊り)一つ作る場合でもフレームレートが維持できませんでしたが
メッシュを結合してインスタンスを統合することで、メッシュ作成処理の負荷が抑えられ、フレームレートをなんとか維持できる処理に変わりました。

以降の記事では、この 16 x 16 x 16 のチャンクをしばらく利用します。

具体的なコードは以下、よろしければ参考にしてください。(主に自分用)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class Prism : MonoBehaviour
{
    [SerializeField]
    private Material _mat;

    const float root3 = 1.732051f;

    private Mesh _mesh;
    private MeshFilter _filter;

    const int SIDE = 16;
    private Vector3[] _vertices;
    private Vector3[] _normals;
    private Vector2[] _uvs;
    private int[] _triangles;

    // Use this for initialization
    void Start()
    {
        _vertices = new Vector3[SIDE * SIDE * (SIDE / 2) * vertIndices.Length];
        _normals = new Vector3[_vertices.Length];
        _uvs = new Vector2[_vertices.Length];
        _triangles = new int[_vertices.Length];
        for (int x = 0; x < SIDE; x++)
        {
            for (int y = 0; y < SIDE; y++)
            {
                for (int z = 0; z < SIDE; z += 2)
                {
                    for (int i = 0; i < vertIndices.Length; i++)
                    {
                        int vertIndex = (x * SIDE * (SIDE / 2) + y * (SIDE / 2) + z / 2) * vertIndices.Length + i;
                        ref Vector3 vert = ref _vertices[vertIndex];
                        ref Vector3 normal = ref _normals[vertIndex];
                        ref Vector2 uv = ref _uvs[vertIndex];
                        vert = positions[vertIndices[i]];
                        vert.x += x * 2;
                        vert.y += y;
                        vert.z += z * root3;

                        normal = normals[i];
                        uv = uvs[i];
                        _triangles[vertIndex] = vertIndex;
                    }
                }
            }
        }
        _mesh = new Mesh();
        
        _filter = GetComponent<MeshFilter>();

        var renderer = GetComponent<MeshRenderer>();
        renderer.material = _mat;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            CreateMesh();
        }
    }
    private void CreateMesh()
    {
        _mesh.vertices = _vertices;
        _mesh.triangles = _triangles;
        _mesh.uv = _uvs;
        _mesh.normals = _normals;
        _filter.sharedMesh = _mesh;
    }

    static Vector3[] positions = new Vector3[] {
        // top
        new Vector3 (0f, 1f, 0f),
        new Vector3 (1f, 1f, -root3),
        new Vector3 (-1f, 1f, -root3),
        // bottom
        new Vector3 (0f, 0f, 0f),
        new Vector3 (-1f, 0f, -root3),
        new Vector3 (1f, 0f, -root3),
    };
    static int[] vertIndices = new int[]
    {
        0, 1, 2,
        3, 4, 5,
        0, 2, 4,
        4, 3, 0,
        2, 1, 5,
        5, 4, 2,
        1, 0, 3,
        3, 5, 1
    };
    static Vector3[] vertices = new Vector3[vertIndices.Length];
    static Vector2[] uvs = new Vector2[]
    {
        // top
        new Vector2 (.25f, 0.5f),
        new Vector2 (0.5f, 0.0f),
        new Vector2 (0.0f, 0.0f),

        // bottom
        new Vector2 (.75f, 0.0f),
        new Vector2 (0.5f, 0.5f),
        new Vector2 (1.0f, 0.5f),

        // side R
        new Vector2 (0.0f, 1.0f),
        new Vector2 (.33f, 1.0f),
        new Vector2 (.33f, 0.5f),

        new Vector2 (.33f, 0.5f),
        new Vector2 (0.0f, 0.5f),
        new Vector2 (0.0f, 1.0f),

        // side G
        new Vector2 (.33f, 1.0f),
        new Vector2 (.66f, 1.0f),
        new Vector2 (.66f, 0.5f),

        new Vector2 (.66f, 0.5f),
        new Vector2 (.33f, 0.5f),
        new Vector2 (.33f, 1.0f),
        
        // side B
        new Vector2 (.66f, 1.0f),
        new Vector2 (1.0f, 1.0f),
        new Vector2 (1.0f, 0.5f),

        new Vector2 (1.0f, 0.5f),
        new Vector2 (.66f, 0.5f),
        new Vector2 (.66f, 1.0f)
    };
    static Vector3[] normals = new Vector3[]
    {
        // top
        new Vector3 (0f, 1f, 0f),
        new Vector3 (0f, 1f, 0f),
        new Vector3 (0f, 1f, 0f),

        // bottom
        new Vector3 (0f,-1f, 0f),
        new Vector3 (0f,-1f, 0f),
        new Vector3 (0f,-1f, 0f),

        // side R
        new Vector3 (-0.8660254f, 0f, 0.5f),
        new Vector3 (-0.8660254f, 0f, 0.5f),
        new Vector3 (-0.8660254f, 0f, 0.5f),

        new Vector3 (-0.8660254f, 0f, 0.5f),
        new Vector3 (-0.8660254f, 0f, 0.5f),
        new Vector3 (-0.8660254f, 0f, 0.5f),

        // side G
        new Vector3 (0f, 0f,-1f),
        new Vector3 (0f, 0f,-1f),
        new Vector3 (0f, 0f,-1f),

        new Vector3 (0f, 0f,-1f),
        new Vector3 (0f, 0f,-1f),
        new Vector3 (0f, 0f,-1f),
        
        // side B
        new Vector3 (0.8660254f, 0f, 0.5f),
        new Vector3 (0.8660254f, 0f, 0.5f),
        new Vector3 (0.8660254f, 0f, 0.5f),

        new Vector3 (0.8660254f, 0f, 0.5f),
        new Vector3 (0.8660254f, 0f, 0.5f),
        new Vector3 (0.8660254f, 0f, 0.5f),
    };
}

Unity:頂点法線の計算と事前設定の速度の違い

2週間前に、Unityのパフォーマンス測定の差先端について調べた記事を書きました。
simplestar-tech.hatenablog.com

ここで調べた手法で、頂点生成系のプログラムのいくつかの負荷試験を行ってみます。
タイトルの通り、頂点法線の計算をランタイムで行うのと、事前に設定するのとでどれくらいパフォーマンスが変わるのか見ていきたいとおもいます。

負荷が計測できる程度に前回の頂点生成で作る三角柱を並べます。(15 x 15 x 15 個、計3375)
f:id:simplestar_tech:20180922081047j:plain

処理はインスタンス生成とメッシュ作成に分けましたが、それぞれで高負荷が発生しました。
まずはインスタンス生成時のタイムラインを確認してみました。
f:id:simplestar_tech:20180922081503j:plain
CreateInstance という Unity 側に予約されている部分が大部分を占め、続いて mesh オブジェクト作成と GCAlloc にも犯罪的な処理時間を要していました。

メッシュ生成のタイムラインを確認すると次の通り
f:id:simplestar_tech:20180922081616j:plain
mesh に頂点を設定、インデックスを設定、UVを設定、メッシュを割り当てといったことに、ちりも積もれば山となるという感じで、短い計算が重なって全体的な処理時間が大きくなるという結果でした。
頂点法線の計算はほとんど時間を要していないことがわかります。

こういうことがわかるのは本当、プロファイラの良いところだと思います。

タイトルの通り、RecalculateNormals を使うか、そのまま設定するかでどのように変化するかを示して終わりたいと思います。

まずは RecalculateNormals を使う場合 PlayerLoop は total 72.97 ms 要していました。
ここで頂点法線を事前に決めて与えるように修正しました。

set normals に 9ms ほど要していました。PlayerLoop は total 72.60 ms 要しています。
ほとんど変化していないですね。

次の記事で、インスタンス化する数を減らす工夫をしてみます。

Unity:頂点生成による三角柱

頂点カラーシェーダーのテスト用にメッシュオブジェクトを用意します。

ちょうど1年前に六角柱を作りましたが、やはり最も頂点数の少ない三角柱から作ろうと思います。
simplestar-tech.hatenablog.com

もう既に書式は習っているので、知っていることを利用してコードに起こしてみます。
詳しい解説が欲しい方はこちらが参考になりますよ。
qiita.com


カタカタ…

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class Prism : MonoBehaviour
{

    [SerializeField]
    private Material _mat;

    const float root3 = 1.732051f;

    // Use this for initialization
    void Start()
    {
        var mesh = new Mesh();

        Vector3[] positions = new Vector3[] {
            // top
            new Vector3 (0f, 1f, 0f),
            new Vector3 (1f, 1f, -root3),
            new Vector3 (-1f, 1f, -root3),
            // bottom
            new Vector3 (0f, 0f, 0f),
            new Vector3 (-1f, 0f, -root3),
            new Vector3 (1f, 0f, -root3),
        };
        int[] vertIndices = new int[]
        {
            0, 1, 2,
            3, 4, 5,
            0, 2, 4,
            4, 3, 0,
            2, 1, 5,
            5, 4, 2,
            1, 0, 3,
            3, 5, 1
        };
        Vector3[] vertices = new Vector3[vertIndices.Length];
        for (int i = 0; i < vertIndices.Length; i++)
        {
            vertices[i] = positions[vertIndices[i]];
        }
        mesh.vertices = vertices;

        int[] triangles = new int[mesh.vertices.Length];
        for (int i = 0; i < mesh.vertices.Length; i++)
        {
            triangles[i] = i;
        }
        mesh.triangles = triangles;

        Vector2[] uvSources = new Vector2[]
        {
            // 天
            new Vector2 (.25f, 0.5f),
            new Vector2 (0.5f, 0.0f),
            new Vector2 (0.0f, 0.0f),

            // 底
            new Vector2 (.75f, 0.0f),
            new Vector2 (0.5f, 0.5f),
            new Vector2 (1.0f, 0.5f),

            // side R
            new Vector2 (0.0f, 1.0f),
            new Vector2 (.33f, 1.0f),
            new Vector2 (.33f, 0.5f),

            new Vector2 (.33f, 0.5f),
            new Vector2 (0.0f, 0.5f),
            new Vector2 (0.0f, 1.0f),

            // side G
            new Vector2 (.33f, 1.0f),
            new Vector2 (.66f, 1.0f),
            new Vector2 (.66f, 0.5f),

            new Vector2 (.66f, 0.5f),
            new Vector2 (.33f, 0.5f),
            new Vector2 (.33f, 1.0f),
            
            // side B
            new Vector2 (.66f, 1.0f),
            new Vector2 (1.0f, 1.0f),
            new Vector2 (1.0f, 0.5f),

            new Vector2 (1.0f, 0.5f),
            new Vector2 (.66f, 0.5f),
            new Vector2 (.66f, 1.0f)
        };
        Vector2[] uvs = new Vector2[uvSources.Length];
        for (int i = 0; i < uvSources.Length; i++)
        {
            uvs[i] = uvSources[i];
        }
        mesh.uv = uvs;

        mesh.RecalculateNormals();

        var filter = GetComponent<MeshFilter>();
        filter.sharedMesh = mesh;

        var renderer = GetComponent<MeshRenderer>();
        renderer.material = _mat;

    }

    // Update is called once per frame
    void Update()
    {

    }
}

ジャン!
f:id:simplestar_tech:20180920090443j:plain

思いついていることがいくつかあるので、次の記事で試していきます。

Unity:レールに沿ったカメラを表現するCinemachine

前回、初めて Cinemachine を触ってみましたが、サクッと三人称視点のフリーカメラの動きを実現できました。
simplestar-tech.hatenablog.com

Cinemachine の見どころは多く、まだまだその機能を紹介しきれていません。
カメラワークのイメージと、それを Cinemachine で実装する手順の間のメンタルモデルの定着を目指して
今回はレールに沿って動くカメラの動きを実現してみたいと思います。

■Cinemachine の導入
Unity 2018.3 beta を使っていますが、それでもまだ Cinemachine はプレビュー版のパッケージの様子
まずは Windos > Package Manager とメニューを選んで、最初は All packages でも Cinemachine は見つけられませんが
Show preview packages を Advanced メニューから選んでインストールします。

インストールした Cinemachine のバージョンは 2.2.7 でした。

■Main Camera に Brain を追加してカメラの主導権を Cinemachine に譲渡
ゲームの絵を作り出すのは、変わらず Main Camera オブジェクトです。
その Main Camera オブジェクトを制御するわけですから、そこに Cinemachine Brain コンポーネントをアタッチします。
これで Main Camera を手動で編集できなくなりました。(強制的に Cinemachine が制御します)

■カメラの動きを決めるレールを敷く
Cinemachine Smooth Path というスクリプトコンポーネントが使えるようになっていますので、空のオブジェクトに設定します。
f:id:simplestar_tech:20180918084124j:plain

Waypoints の + ボタンをポチポチ押せば、こうしたレールが表示されるので適当な軌跡を描きます。

Cinemachine Smooth Path は他にも使い道ありそうだなぁって思ったのが、次のインタフェースを提供してくれる点です。
具体的には 0~1の値を渡して軌跡上の点の位置を返してくれるというもの

    [SerializeField]
    private Cinemachine.CinemachineSmoothPath path;

    void Update()
    {
        transform.position = path.EvaluatePositionAtUnit(0.5f, Cinemachine.CinemachinePathBase.PositionUnits.Normalized);
    }

手付けのアニメーションには必須だと思う機能なのですよ!
このオブジェクトはここでこういう風に動いて…など
レールだけにちょっと脱線しました。

■カメラの動きを強制する Virtual Camera に Path を指定する
今回は Path を指定してその Path に沿って Track & Dolly というレールに沿った動きを実現するので
インスペクターにある Body と Aim をいじります。
Body のドロップダウンメニューから Track & Dolly を選択し Path に先ほど作成した Cinemachine Smooth Path をアタッチしたオブジェクトを設定します。
Auto Dolly の Enabled にチェックを入れれば、レールに沿ってカメラが対象を追従します。(LookAt、Follow に対象となるオブジェクトを設定する必要あり)
さらに Aim にて Composer を選び、適当に Dead Zone などを設定して、映画監督のように Shot を指示します。

シーン内にこの Virtual Camera があれば、カメラは勝手にレールの上を Track & Dolly しながら撮影してくれるはずです。
次のように

f:id:simplestar_tech:20180918085640g:plain

■まとめ
レールに沿ったカメラを表現するCinemachine の機能を実践しました。
ゲームのデモムービーや、ゲーム内でのカットシーン(ゲームオブジェクトを使ったリアルタイムレンダリングムービー)の作成で活躍する技法です。
Unity のデザイナー向け機能ですが、プログラマーも覚えて損はないと思いますよ!

■参考資料
ここで紹介する 4分の動画を見れば、この記事で書いた内容がすべて映像で学習できます。最高の資料でした、ありがとうございました。
unity3d.com


■次回予告
次は頂点色を設定するメッシュ作成と頂点カラーを表示するシェーダーについて書いていきたいです。

Unity:CinemachineでTPSカメラ制御してみる

Chinemachine (シネマシーン)とは、映画のカメラワークを強力にサポートするツールセットの名称です。
映画監督ばりに、絵コンテ通りにカメラを動かしたい、そんなときに役立つ公式アセットです。(あるときから公式になった人気ツール?なのかな)

とりあえず映像に関するツールセットなので、映像でどういった機能を私たちに提供してくれるのか確認してみましょう。
unity3d.com

動画から得られた知見:
1.Package Manager を開いて Preview 版の Chinemachine をインストールする
2.知的な Main Camera にするため Main Camera オブジェクトに Chinemachine Brain コンポーネントを追加
3.Timeline Editor に2.のコンポーネント追加済みのオブジェクトをドラッグ&ドロップして、トラックの種類に Cinemachine を選択して Cinemachine Track を追加
4.Timeline の編集とは、オブジェクトに紐づくトラックを並列に追加し、そのうちの一つのトラックの中にクリップを順番に配置することだった
5.トラックの中に Cinemachine Shot Clip を作成、その Shot に関するカメラ位置姿勢と対象の追従設定などを記入する、このShotに関する設定を「 Virtual Camera 」と呼ぶことにしている
6.カメラ画像上の領域にて、対象を領域内にとらえている間はカメラは死んでいるかのように動かないとするカメラワークがある、この時のカメラの画像上の領域を「Dead Zone」と呼ぶ
7.Dead Zone の周りの領域を「Soft Zone」その外側は特に呼ぶ名前はない
8.トラックにはいくつでも Shot Clip を追加できるし、Clip 間でカメラワークを補間したり、ワープしたりをGUI操作で簡単に操作できます。

わずか 5 分で Timeline の編集の概念と、Cinemachine の基本機能と Timeline でのカメラワークの操作まで完璧に示していました。
とても良い動画だと思いました。

ここまでの基本的な知見を持ったうえで次のページを読んでみました。
gametukurikata.com

得られた知見:
1.Main Camera オブジェクトに Chinemachine Brain コンポーネントを追加、シーン内に「Virtual Camera」を追加すると、そのVirtual Camera の設定に従って Main Camera の動きが強制される
2.Virtual Camera の設定に Follow と Look At があるので、ここに移動キャラクターを設定すれば TPS (三人称視点)のカメラの動きに近づく(キャラクターを追いかけるカメラの動き)
3.Free Look タイプの Virtual Camera も用意されている、次の図に示すような三つの円で設定される楕円体の表面に沿って対象を中心に移動するフリーカメラの操作を提供してくれる
f:id:simplestar_tech:20180917164001j:plain
まさに TPS (三人称視点)のカメラの動きじゃないですか!これ

サクッとキャラクターを用意
f:id:simplestar_tech:20180917172029j:plain

Free Look タイプの Virutal Camera をシーンに配置してゲームを再生してみました。

f:id:simplestar_tech:20180917171841g:plain

これはTPSのフリーカメラですね!
歩かせてみます。

f:id:simplestar_tech:20180917172517g:plain

■まとめ
Unity 2018.3.0 beta で動かしました。
Cinemachine のプレビュー版をさわったのですが、素晴らしいカメラ操作を提供してくれるツールセットですね。
これを活用して三人称視点のゲーム作ってみたいと思いました。

ほかにもあらかじめ設定したレールの上を動くカメラなども学んだので、次に紹介したいと思います。

■追記
Free Look タイプのインスペクタービューの具体的な内容を記録します。
f:id:simplestar_tech:20181011221927j:plain

次の記事の予定

Unity に頂点カラーのマテリアルは用意されていません。
そこで、シェーダを多少はいじれるようにしておきたいと考えました。
そのほかUnityのシェーダーを編集できる UI の確認
タイムラインもそろそろ触れるようになっておきたいし
Cinemachine ってのも勉強したいです。

つまりは Unity 2017 で勉強してこなかったことを勉強していこうと思いました。

勉強の資料
nn-hokuson.hatenablog.com
こちらは読了、実践も済んでます
Unityのシェーダーを編集できる UI の Shader Graph には少しだけ慣れました。

こっちはシェーダーの基本を学ぶ資料です。ボリューム満点なので、ゆっくり読み進めます
7日間でマスターするUnityシェーダ入門 - おもちゃラボ

後で別記事でまとめますが、簡単にメモを残します。

なるほど Surface シェーダーという Unity 独特のシェーダーを使う場合に出てくる予約語はここから確認すればいいのか
docs.unity3d.com

Unity のシェーダーの設計思想が見えてきた
ライティングのシェーダーを書くのが難しく、また基本的に最先端の技術を取り入れて同じコードになるため
そこはラッピングして
ピクセルシェーダーを技術者に書かせるかわりに、ライティングの一歩手前のサーフェースシェーダーを用意し
頂点とサーフェースシェーダーだけを技術者は書けば良いように設計している。

Unity やさしい

シェーダーのプロパティ追加書式

docs.unity3d.com

ところで _BaseColor を宣言なく利用できていたけど、どこかで予約されていたりするのかな?
公式ドキュメントに一覧が書いてあるなら知りたいです。

そろそろ別記事書きます。

Unity:2018.2以降のPrefabとAssetBundleの変化を追う

ココが変わる!Unityの新しいエディターワークフロー
CEDEC2018

いつもお世話になっている「テラシュールブログ」の中の人の発表です。
これだけ Slide Share にアップされていない様子なので、記憶を頼りに
と思ったら Youtube に動画を発見した
www.youtube.com

Prefab と AssetBundle について簡単におさらい

  • Prefab

Prefab はシーン内のゲームオブジェクトをアセット化したものです。
元となるのはゲームオブジェクトなので、Prefab をもとにシーン内でゲームオブジェクトを復元するようにしてインスタンス化します。
このとき Prefab 化(アセット化)したときと同じコンポーネント構成でシーン内に登場します。

  • AssetBundle

巨大な動画、音声、画像ファイルをアセットとして外部サーバーに配置してゲームで必要になったときにダウンロードして使う仕組みで
とても一般的なゲームの実装方法ですが、共通のシステムは存在せず、各社で技術者を雇うか育てるかして独自実装してきました。
このアセットを外部サーバーに置く、必要になったらダウンロードすることを「AssetBundle」と呼びます。

今回の記事で注目するべきこと

■既存の Prefabの問題
Prefab の2 階層目以降の編集ができない
Prefab の下の階層に Prefab を配置できない
とても似ている Prefab 派生オブジェクトを一括編集できない
Prefab の編集の適用は Prefab 単位でしかできない
参照アセットは強制で一括読み込みするので、完了までゲームがフリーズする
AssetBundle に Prefab を置くと動作を確認するのに、長時間かかる(ちょっとした修正にAsset Bundle のビルドが必要)

一言でまとめると、Prefab を扱うために面倒を見るためのエンジニアやコードが必要なことが問題
もっと楽に Prefab を扱いたい

そこで新しい Prefab Workflow が登場しました。←ここが注目するべきこと

■ New Prefab Workflow
1.アイコンからゲームオブジェクトと Prefab を識別できる
2.Prefab の編集した値が太くなるだけではなく、項目の先頭が青く色付く
3.Prefab の何を編集したかを確認する UI が新しく追加される
これには項目を選択すると編集前と編集後のインスペクタを比較する機能がある
Prefab の変更の適用をフィールド単位で行えるようになった
4.Prefab を編集するシーンを開くボタンが追加された(Prefab エディター)
5.Prefab の子階層に Prefab を配置できるようになった(Nested Prefab)
値のオーバーライド(適用)では、どの階層の Prefab がオーバーライド(適用)を行うかを指定できるようになる
発表動画では詳しくデモを通して解説があり、新しく追加された3.Prefab の何を編集したかを確認する UI にて指定できる
6.Prefab の Variant (コンテキストメニューの Create > Prefab Variant を選択で作成)
Prefab なのに個別に値をオーバーライドできるようになり、つまりは、ちょっとだけ内容が違う Prefab を量産できるようになり
さらにオリジナルの変更が Variant にも反映されるので、とても似ている Prefab 派生オブジェクトを一括編集する操作が Variant を使って可能になる
7.モデルの更新でもコンポーネントを保持
今までモデルを差し替えたときにボーン構造が異なると、モデルの Prefab は初期化されてしまっていたが
Nested Prefab の仕組みが入ったので、見た目だけの更新だけで、モデルに与えていた様々なコンポーネントなどが保持されるようになった

■Addressable Asset System
これまでのアセット参照はドラッグ&ドロップだった
ただし直接参照は強制で一括読み込みするので、完了までゲームがフリーズする

既存のロード手段は三つ
・直接参照
・Resources (非推奨)
・AssetBundle (難易度高い)

新しい Unity は以下の2つに変更された
・直接参照
・Addressable Asset System

導入は Package Manager を通して Addressable System を選択して導入できる
コードは以下のように修正する

[SerializeField] GameObject player;
↓
[AssetReferanceTypeRestriction(typeof(GameObject))]
public AssetReference player;

アセットの登録は今まで通りドラッグ&ドロップ

インスタンス生成のコードが変わる

GameObject playerInstance;
playerInstance = Object.Instantiate(player);
↓
GameObject playerInstance;
player.Instantiate<GameObject>().Completed += (op) => { playerInstance = op.Result; }

インスタンス生成が非同期で行えるようになるコード例が登場
(読み込み完了後のインスタンスへの操作も、ラムダ式の中で書ける)

オブジェクトの破棄も変わったので注意

Destroy(playerInstance);
Resources.UnloadAsset(asset);
↓
Addressables.ReleaseInstance(playerInstance);
Addressables.ReleaseAsset(asset);

■Addressables ウィンドウ
AssetBundle に登録したアセットを一覧でアドレスを確認でき、検索用のタグも付けられる

var preloadOp = Addressables.PreloadDependencies("label_name", null);
yield return preloadOp;
public AssetLabelReference hazardsLabel;

var op = Addressables.LoadAssets<IResourceLocation>(hazardsLabel.labelString, null);
yield return op;
hazardLocations = new List<IResourceLocation>(op.Result);

でラベル一致のアセットを事前ロードできる書式が追加されました、便利です。

アドレスから直接オブジェクトを生成する手段もある
Addressables.Instantiate("Address");

さらにこのウィンドウではグループごとに AssetBundle を配置するパスを指定可能
ローカルパスでもサーバーのリモートパスでも可
この設定はプロファイルとして Default, Dev, Release などの名前を付けて、選択一つで切り替え可能になっています。(開発フローを意識したものです)

格納方式も選べる
1アドレスにつき1AssetBundle → pack Separately
グループを1AssetBundle → pack Together

■AssetBundle を用いた高速デバッグ
Play Mode を以下から選択できる
・Fast (AssetBundle 構築しない、AssetDatabase から直接アセット取得 なのでゲーム動作確認時に選ぶとよい)
・Virtual (AssetBundle 構築しない、しかしアセットの依存関係のみを本番と同じにできる、本番想定の関係性構築で利用する)
・Packed (AssetBundle を構築、AssetBundle からアセットを取得 なので本番想定のパフォーマンスをチェックできる)

■PM Profiler
Resource Profiler とも言う
Addressables まわりの操作で生まれたアセットのロード、開放などをプロファイリングすることができる

結論:Addressable Asset System に移行すれば、色々考えなくて良くなる