鳩の溜まり場

猫か鳩になりたい

ランタイム中にAnimatorのRuntimeAnimatorControllerを変更すると一瞬だけT-Poseになる【Unity】

症状

AnimatorのRuntimeAnimatorControllerをランタイム中に変更すると一瞬T-Poseになってから変更後のアニメーションが再生される。

↓↓ 利用させていただいたアセットはこちら ↓↓

再現用のコードです。
以下のコードでは、元のRuntimeAnimatorControllerを別のRuntimeAnimatorControllerで差し替えてますが、AnimatorOverrideControllerに差し替えても同様の現象が起きます。

public class Sample : MonoBehaviour
{
    [SerializeField] private Animator animator;
    [SerializeField] private RuntimeAnimatorController runtimeAnimatorController;

    private void Start()
    {
        Application.targetFrameRate = 10;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            animator.runtimeAnimatorController = runtimeAnimatorController;
            animator.Play("Default", 0, 0f);
        }
    }
}

解決方法

調べてみたら同じ現象に陥ったフォーラムがありました。
RuntimeAnimatorControllerを変更したタイミングでAnimator.Update()を呼び出せば良いとのこと。

forum.unity.com

Animator.Play()のあとに呼び出してるAnimator.Update()です。

public class Sample : MonoBehaviour
{
    [SerializeField] private Animator animator;
    [SerializeField] private RuntimeAnimatorController runtimeAnimatorController;

    private void Start()
    {
        Application.targetFrameRate = 10;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            animator.runtimeAnimatorController = runtimeAnimatorController;
            animator.Play("Default", 0, 0f);
            
            // この行を追加
            animator.Update(0.0f);
        }
    }
}

おまけ

あくまで憶測ではありますが、RuntimeAnimatorControllerの差し変えによってAnimator内部での更新、あるいは描画周りの更新タイミングが嚙み合わず1フレームだけT-Poseになってるのかな?と思います。
具体的な部分までは調べてないですが、必要になったらまた調べるということで・・・。

また、Animator.Update(float deltaTime)の挙動の説明が少しわかりにくかったのでコードを見ようと思ったのですが、公開されてない部分でした。
C++で実装されてる箇所とC#で実装されてる箇所の違いはなんなんだろうか。

github.com

Addressables Assets Systemでリソースのビルド用プロジェクトを用意して運用する【Unity】

はじめに

こちらはUnityゲーム開発者ギルド Advent Calendar 2022の19日目の記事です。

adventar.org

ふら~とAdventCalendarを見に来たらちょうど当日の枠が空いていたので急遽書くことにしてみました。

やること

別プロジェクトでビルドしたAddressables Assets System(AAS)のリソースをメインプロジェクトからロードする流れについてです。

対象としている読者

Addressablesの超基礎的な話は割愛します。

  • Addressablesを軽く使ったことがある
  • アドレスの登録/ロード程度しかやったことがない
  • リソースのビルド専用プロジェクトを用意したい
  • リソースをリモートに置いて利用したい

背景

AASでのビルドはプロジェクト自体に設定しているプラットフォームに向けてのみビルドするので、複数のプラットフォーム(AndroidiOS等)に向けたサービスの場合、ビルドするたびにBuild Settings -> Switch Platformをする必要があります。

その際、サービスそのものを開発しているプロジェクトでアセットをビルドするとなると、プラットフォームの切り替えにも時間がかかるため、無駄な時間が発生しがちです。

他にも、細かい懸念事項が出たりします。

  • エンジニア以外がアセットをビルドする際に関係のない個所を触ってしまうかもしれない
  • 組み込む予定のないアセットがあるのは気持ち悪い

理由はさておき、兎にも角にもリモートに配置するリソースは別プロジェクトで管理、ビルドしたい案件が起きた時の話です。

環境

  • Unity 2020.3.29f1
  • Addressables ver1.18.19

全体像

AASにはアドレスに対するリソースの情報や、リソース間の依存関係の情報等をまとめたカタログというものが存在します。
これはAASをビルドしたときに生成され、AASはリソースをロードする際にこのカタログの情報をもとにリソースのパス等を特定します。

つまり、このカタログとカタログに記されたリソースを用意すれば、あとはこのカタログの存在をAASに教えることでリソースをロードすることができるようになります。

これらを踏まえたうえで全体像を考えると、

  1. リソースのビルド用プロジェクトでリモートに配置するアセットをビルド
  2. アプリ用プロジェクトからリモートに配置してあるカタログを取得
  3. リソースのロードを行う

という流れになります。

ビルド用プロジェクトを作成

メインプロジェクトと同じバージョン、同じレンダリングパイプラインを搭載したプロジェクトを用意します。

今回は、Unityのバージョンを2020.3.29f1、レンダリングパイプラインはURPを選択しました。

プロジェクトが立ち上がったら、以下の手順で適当に準備します。

  1. PackageManagerからAddressablesをインストール
  2. Window -> Asset Managements -> Addressables -> Groupsを開く
  3. Create Addressables Settingsを押して設定回りのファイルを生成
  4. Default Local GroupはDefault Remote Group等適当な名前にしておく

グループの設定

Default Remote Groupを選択し、右クリックからInspect Group Settingsを選択します。

するとInspectorにDefault Remote Groupの設定ファイルの情報が表示されるので、各所設定します。
詳細については公式のマニュアルを参照していただき、ここでは別プロジェクトから扱う上で必要最低限の設定をします。

docs.unity3d.com

Content Update Restrictionの設定

Content Update Restrictionを設定します。
その名の通り、コンテンツを更新するときの制限の設定です。

ここではUpdate RestrictionをCan Change Post Releaseに設定します。

それぞれの違いは以下の通りです。

Can Change Post Release : ダウンロードコンテンツとして利用する際に新しいバージョンのコンテンツが存在したら自動で更新する
Cannot Change Post Release : プロジェクト自体をビルドしたタイミングでコンテンツが固定され以降更新できない

Content Packing & Loadingの設定

次にContent Packing & Loadingの設定です。
ここでは主にビルド時の出力先とAASがアセットをロードする際に参照するパスを設定します。

それぞれ以下のように設定します。

Build Path : Remote Build Path
Load Path : Remote Load Path

Build Pathはビルド時の出力先Load Pathはカタログに登録されるリソースのパスになります。

Remote Build PathやRemote Load Pathのパラメータの詳細はWindow -> Asset Managements -> Addressables -> Profilesから変更できます。
ここで指定されている[BuildTarget]等の変数は独自の静的プロパティを指定したりすることもできます。

Profiles | Addressables | 1.18.19

今回、Remote Build Pathはビルド用プロジェクトが存在する階層にserverというフォルダを用意してそこを指定し、Remote Load Pathはローカルサーバー直下を読むように指定しました。

Remote Build Path : ../server/[BuildTarget]
Remote Load Path : http://localhost:8000/[BuildTarget]

他にも高度な設定も出来ますが、ここでは必須ではないため扱いません。
AssetBundleの圧縮方法やキャッシュの利用の有無、バンドルにまとめる際の単位等を指定したりできます。
詳しくはAdvanced Optionsの公式マニュアルをご参照ください。

Group settings | Addressables | 1.18.19

Addressableシステムの設定

ここではAddressablesの基本的な設定情報が表示されるため、各所設定します。
グループの設定の時と同様、詳細については公式のマニュアルを参照していただき、ここでは別プロジェクトから扱う上で必要最低限の設定をします。

docs.unity3d.com

Addressables GroupsウィンドウのTools -> Inspect System Settingsを選択するとInspectorにAddressableシステムの設定項目が表示されます。

Catalogの設定

まず、カタログそのものに関わる設定です。

ここではPlayer Version Overrideを0.0.1にします。

通常、ビルド時にカタログが生成されると、カタログのファイル名はcatalog_{タイムスタンプ}.jsonになりますが、Player Version Overrideにパラメータを指定するとタイムスタンプの箇所が指定したものに置き換わります。
そのため、実際は0.0.1固定である必要はなく、各々好きなように設定していただいて大丈夫です。

Content Updateの設定

次に、リモートに配置するカタログ周りの設定です。

変更したパラメータは以下の通りです。

Build Remote Catalog : true
Build Path : Remote Build Path
Load Path : Remote Load Path

Build Remote Catalogをチェックを入れることでAddressablesのビルドを行ったタイミングでカタログも生成されるようになります。
(チェックを外すとプロジェクトのビルドを行うタイミングでカタログが生成されます。)

アセットを登録&ビルド&サーバーに配置

適当なオブジェクトをプレハブ化し、適当なアドレスを振ります。

ビルドしてRemote Buld Pathに設定したパスに以下の3つのファイルが存在するか確認します。

  • catalog_0.0.1.json
  • catalog_0.0.1.hash
  • defaultremotegroup_assets_all_xxxxxxxxxxx.bundle

*グループ名やカタログのバージョン名を違うものにした場合、各所自分の設定したものに置き換えて読んでください。

好きなやり方でローカルサーバーを立てます。

  1. $ cd {任意の場所}
  2. $ python -m http.server 8000

アプリ用プロジェクトでロードする

わかりやすいようにアプリ用のプロジェクトにも適当なアセットにアドレスを振っておきました。

アプリ用プロジェクトのAASにリモートのカタログを教える

アプリ用のプロジェクトは先ほどビルドしたカタログの存在は何も知らないため、明示的にカタログをAASに追加する必要があります。
新たなカタログは以下のメソッドでロードできます。

Addressables.LoadContentCatalogAsync(string catalogPath);

Addressables.LoadContentCatalogAsync | Addressables | 1.14.3

ロード

これらを踏まえたうえで簡易的なコードを用意しました。

public class AssetsLoader : MonoBehaviour
{
    // 追加するカタログ
    [SerializeField] private string catalogPath;
    // ロードするアセットのアドレス
    [SerializeField] private List<string> toLoadAssetAddressList;

    private async void Start()
    {
        // 追加のカタログをロード
        await Addressables.LoadContentCatalogAsync($"{catalogPath}").Task;

        // アセットを生成
        for (var i = 0; i < toLoadAssetAddressList.Count; i++)
        {
            var obj = await Addressables.InstantiateAsync(toLoadAssetAddressList[i]).Task;
            obj.transform.position = Vector3.right * i;
        }
    }
}

適当なシーン上にアタッチして、パラメータをよしなに設定します。

実行するとローカルとリモート両方にあるアセットをロード出来ていることがわかります。

補足1 catalog_XXX.hashについて

Addressablesのビルド時に生成されるcatalog_0.0.1.hashというファイルですが、これはカタログに差分があるか判別するためにAASが利用しているファイルになります。
中身はただのハッシュ値です。

c6375a11a7da704fe32ad078920d58a5

補足2 効率化用のエディタ拡張

マルチプラットフォームに向けて自動でAddressablesのビルドとプラットフォームの切り替えを行ってくれるエディタ拡張です。
ご自由にお使いください!

さいごに

急ぎで書いたので色々抜けてるところあるかもしれません!
もし間違い等ありましたらコメントにてご指摘いただけると幸いです。

今年1年ありがとうございました!メリクリ!🎄

プロジェクト間のファイル移植時にGUIDが重複した時の挙動について【Unity】

概要

1つのプロジェクトで開発していたリソースを分離して基盤となるプロジェクトに一部移植したいという状況が発生しました。
その際unitypackageを用いてリソースを移植した際にGUIDが一時的に重複したため、その時の挙動と対処法について具体例を用いて書き留めます。

環境

分離先である基盤のプロジェクトはローカル環境下にあるため、manifestファイルには該当プロジェクトへのパスが指定されています。
そのため基盤プロジェクトに加えた変更は元のプロジェクトを開いたときに即時ロードされます。

具体例

以降移植元のプロジェクトをAプロジェクト、移植先の基盤プロジェクトをBプロジェクトとします。

Aプロジェクトに存在するPlayerプレハブのPlayerVariantであるSuperPlayerプレハブがあるとします。

PlayerプレハブをBプロジェクトにunitypackageで移植すると勿論meta情報も同時に移植されるため、この時AプロジェクトのPlayerプレハブを消さないとGUIDが重複している状況が生まれます。

この状態で移植後にAプロジェクトのウィンドウを選択した場合、Bプロジェクトのパッケージに更新があることを発見してAプロジェクトが更新ファイルの確認作業に入ります。
GUIDは重複したままなのでAプロジェクトがGUIDの再割り当てを行います。
この場合、Aプロジェクト内に存在するファイルが優先されるため、BプロジェクトのGUIDが新しいものに置き換わります。

この時本来AプロジェクトのSuperPlayerがPrefabVariantの親として参照したいのはBプロジェクトに存在するPlayerですが、GUIDが置き換わっているため、Aプロジェクト内にあるPlayerを参照し続けることになります。

この問題を解決するには、移植作業で同じGUIDが2つ存在するという状態が発生する前に、Aプロジェクト側のPlayerを削除することでSuperPlayerを一時的にMissingReferenceな状態にしてからBプロジェクトに移植することで回避できます。

結論

つまり、移植作業を行う際の手順は以下の通りになります。

  1. Aプロジェクトで移植したいファイルをunitypackageでエクスポート
  2. エクスポートしたファイルをAプロジェクト内から削除
  3. Bプロジェクトにunitypackageをインポート
  4. Aプロジェクト側でBプロジェクトに移植したファイルの参照を取得できているか確認

#1 自動生成でいい感じの地形を作りたい【Unity】

経緯

自作ゲームでどうしてもいい感じに自動生成で地形を生成したくなったので、日記形式でゆるゆると書き進めたいと思います。

仕様

  • 滑らかな地面を作る(マイクラのようなキューブを並べたものではない)
  • 地下の概念はナシ
  • 理想は原神

引用: https://youtu.be/MUOnI40xRZk?t=7

開発環境

  • Unity 2020.3.22f1

アプローチ方法

まずは基盤となる地面を作らないことには始まらないので、地面の成形方法を考えます。

Blender等でモデルのパターンを作って繋げる

コードによる生成ではなく微調整ができるため、おしゃれな形を作り出せる点やパーツをくっつけるだけで済むため実装自体は楽で良さそうです。
ただ、パターンの量産が面倒そうだったり、パターン間のつなぎ目の違和感を消すのに苦戦しそうなので諦めます。

自動生成を諦める / マイクラのようにキューブを並べた感じにする

楽な手順の誘惑に負けそうになりました。

メッシュの頂点を操る

メッシュの頂点情報を取得して、頂点の位置を移動させる手法です。
地面も滑らかだし、適切に操ればきれいな地面ができそうな気がしました。
採用!

メッシュをいじる

まずはそもそもメッシュの頂点をいじって好きな形のメッシュに変形できるかを試してみます。

Blenderで平らなモデルを用意して頂点間を細分化します。
(この平らなモデルはUnityで実行時に生成してもOKなので必須ではないです)

エクスポートしたファイルをUnityにインポートします。
動的に頂点を操作するので、Read/Write Enabledにチェックをいれるのを忘れずに・・・。

適当にシーン上に配置し、以下のようなコードを書き、アタッチしてMeshFilterとMeshColliderの参照も渡します。

[SerializeField] private MeshFilter meshFilter;
[SerializeField] private MeshCollider meshCollider;

// ランダムな数値の範囲
[SerializeField] private float range = 0.1f;

public void Start()
{
    var vertices = meshFilter.mesh.vertices;

    for (var i = 0; i < vertices.Length; i++)
    {
        vertices[i].y += Random.Range(0f, range);
    }

    var mesh = meshFilter.mesh;
    mesh.vertices = vertices;
    mesh.RecalculateNormals();
    meshCollider.sharedMesh = mesh;
}

実行してみるとちゃんと頂点を操れることがわかりました。

MeshColliderも生成したあとのMeshの形に沿って適用されてます。

メッシュの操作は上手くできましたが、まだランダムに高さを変更してるだけなので地形っぽくします。

パーリンノイズ

パーリンノイズをキューブの座標に当てはめてマイクラ風の地形を生成する記事がいくつかあったので、同じ方針で座標の当てはめる先をメッシュの頂点にしてみます。

[SerializeField] private MeshFilter meshFilter;
[SerializeField] private MeshCollider meshCollider;

// 高低差
[SerializeField] private float height;
// ランダムな数値の範囲
[SerializeField] private float adjust;

private void Start()
{
    var vertices = meshFilter.mesh.vertices;

    for (var i = 0; i < vertices.Length; i++)
    {
        var x = vertices[i].x * adjust;
        var z = vertices[i].z * adjust;
        vertices[i].y = height * Mathf.PerlinNoise(x, z);
    }

    var mesh = meshFilter.mesh;
    mesh.vertices = vertices;
    mesh.RecalculateNormals();
    meshCollider.sharedMesh = mesh;
}

なめらかな起伏ができましたが、ループしている箇所が気になります。

調べたところ、Unityのパーリンノイズは256の周期で実装されてるようでした。
そのためメッシュが原点にある今回の場合、X軸とZ軸を軸に線対称なノイズが現れるみたいです。

地面の中心を(x, z) = (0, 0)に置きたい場合はオフセットを用意して適宜ズラしたりするのが良さそうです。

今回はズラすのも面倒&Unityのパーリンノイズでないといけない理由もないため、GithubにMIT Licenseで公開されてる良さげなのがあったので拝借しました。

github.com

早速適用してみます。

めっちゃ良さそう!

シェーダーの用意

今回はY = 0付近を水面とするので、Y < 0水の中の地面のテクスチャ0 <= Y陸地のテクスチャになるようなシェーダーを用意します。
流石シェーダーグラフ、とっても簡単。

マテリアルをアタッチして、適当なテクスチャを割り当てます。
今回は以下のテクスチャから拝借しました。

assetstore.unity.com

水も置いてみました。

assetstore.unity.com

次回

かなりのっぺりしているので次回はのっぺり感を減らせるように頑張ってみます。

次 -> 執筆予定

U1Wでよくわからないゲームを作った【U1W 22/05/02】

はじめに

遅ればせながら、unity1weekお疲れさまでした!
GWではありましたが400作品以上のゲームが開発されるイベントすごいですね・・・
今回は以前の塊ソウル以来1年以上ぶりに参加したので記録として残しておこうと思います。

https://cdn.discordapp.com/attachments/909173727919505418/982992267180671006/unknown.png

以前のやつ↓

今回できたもの

完成品 unityroom.com

リポジトリ(使用した外部アセット等は除外されてます) github.com

感想

「GWで時間もある」 x 「最近Blenderを勉強中」 x 「個人開発モチベが高い」ということで、今回はBlenderで好きに形を作りながらゲームを作るぞ!と意気込んでの参加でした。
蓋を開ければいつも通り、途中で何が面白いのかわからない時期が来て、結局途中で根本からの方向転換、結果ゲームシステムは謎、ハメ技(バグ技)あり、斬新さ無しの3コンボで色々と反省が残る作品になりました・・・🥲

とはいえ、最近は仕事でもUnity x Blenderでの作業効率が必要な環境にいるので、個人開発でその環境を体験出来たのはいい経験になりました😊

またタイミングとモチベが嚙み合えば参加したいな~~と思ってます。
次こそは頑張るど!

BlenderでリギングしたFBXのウェイトが正しく反映されない問題の対処法【Unity】

症状

Blenderでリギングして適当にアニメーションを作成したときが左、Unityに取り込んで同じ状態にすると右のように歪な形になる。

原因

Unityでは頂点が影響を受けるボーンの数を設定できるため、それの設定を適切にしてあげる必要があります。 症状で上げた画像の箇所をBlenderのウェイトペイントで見てみると、このように1つの頂点に対して複数のボーンが関与していることがわかります。

解決方法

ProjectSettings -> Quality -> SkinWeightsがデフォルトで2 Bonesになっているところを4 BonesあるいはUnlimitedにする。

FBXデータを選択し、Rig -> SkinWeightsをカスタムに変更。
Max Bones/Vertexをほどほどの値に設定でApply。

以上。

あとがき

モデル作成、リギング&アニメーション作成を行ったのが初めてだったので、どこかで操作を間違えたかと思いましたが、そんなことはなかったみたいでした。 同じ症状で沼っている方の助けになると嬉しいです。

コードを書かずにImageTypeのFilledとSlicedを共存させて綺麗なプログレスバーを作る【Unity】

ImageTypeをFilledにするとSlicedと共存できず画像の上のような状態になってしまうのを下のように綺麗にします。

画像

今回使ってる素材です。自作ですのでご自由にどうぞ。

ちなみに最初のサムネイルの画像は上記のMask用の白い画像以外の丸い画像は両方とも(TextureTypeをSpriteにして、SpriteEditorから)Borderを中心に集めています。

オブジェクト構成

Canvas以下にこのような構成で作成します。

それぞれのオブジェクトの詳細は以下の通りにします。 プログレスバーの進捗度合いを表すFilledをマスクにしてその下の階層にプログレスバーの進捗最大の時の画像をSlicedで置くことで綺麗なプログレスバーを実装してます。

おわり

きれーーい!!