UGUILateBindingの使用注意事項

今回の主な話題:UGUILateBindingの使用注意事項、UpdateRendererBoundingVolumes関数の役割、ToArrayが生成するヒープメモリ割り当ての回避。


アセット管理

Q1:UGUIとSpriteAtlasが生成するAssetBundle依存関係に関する一つの問題を聞きたいです。以下のテストをしました、環境はUnity2018.1です。

一つのUI Prefabは:

その中の三つのImageは次のようになります。

三つのImageは異なるテクスチャを引用していますが、これら3つのテクスチャは同じSpriteAtlasに手動で配置し、Include in buildオプションを削除します。

UI PrefabとSpriteAtlasをそれぞれ2つの異なるAssetBundleに入れ、この時、前者は後者に依存関係はないことがわかりました。

ただし、AssetBundleのサイズから、ImageAssetは二つのAssetBundleに繰り返し入力されていないことが簡単にわかります。

AssetBundleの方法でこのUI Prefabを表示したい時に、事前にSpriteAtlasをロードせず、SpriteAtlasManager.atlasRequestedコールバックを書かない場合、確かにエラーが発生します。

これから推測できますのは、このPrefabは実際にある方式で自分が使うべきアトラスを分かります。問題は、AssetBundleManifestを介してアトラスへの依存関係を取得できないため、どうせればこの依存関係を適切に処理できますか?つまり、どうすればこのUI Prefabをロードする前にどのSpriteAtlasまたはSpriteAtlasをロードする必要があるかを知れますか?

まず、Late Bindingは本当に要るかどうかを確認することをお勧めします(Include in buildをチェックしません)。Late Bindingをオンにすれば、UnityがPrefabとAtlasの依存関係を解けることは納得できます。以前に、Prefabをロードまたはインスタンス化する前に依存アセットを必ず準備する必要がありましたから。しかし、Late Bindingには不要です。したがって、実際にLate Bindingが必要ない場合は、「Include in build」をオンにすればいいです。ただ一つのBugにあう可能性があり、Atlasの冗長性を導きますが、それでも避けられます。

Bugはこちらに:https://answer.uwa4d.com/question/5a822325847802258a06509e(中国語注意)

Late Bindingが本当に必要な場合は、SpriteAtlasManager.atlasRequestedのコールバックで現在RequestされたAtlasのTagを取得できます。そしてこのTagに基づいて対応するAtlasをロードできます。(Late Binding規則のため、ここでのロードは同期的に完了する必要はありません)


Monoメモリ

Q2:プロジェクトでLineRendererを使用しました。線を引くときにSetPositions(Vector3 [] position)をコールし、パスはListに保存されます。何かToArrayの割り当てを回避する方法はありますか?

Listをトラバースし、LineRenderer.SetPosition(int index, Vector3 position)をコールします。

プールを使用して、配列のサイズを見積もり、そして直接に渡します。テーリングを行うと、ポイントが長期間存在しないため、アセットを節約できし、list変換のコストも節約できます。これらの空きポイントは1つの位置にグループ化できます。これはあまり消費せず、安定しています。

私はこのような仕事をよくやりました。C#がエンジンインターフェイスにコールされた時、データはコピーされるため、ある方法で渡されたデータを少し処理することは必要です。

たとえば、渡した配列の実際のサイズは10ですが、長さは非セキュアコードによって一時的に10未満の値に変更され、APIコールの完了後に元に戻されます。これは、メモリ変更でゲームデータを変更するに少し似ています。前提は、コールするAPIがエンジンからエクスポートされることを確認する必要があります。デコンパイラツールを使用してこのAPIの声明を確認して、InternalCallは、このAPIがエンジンによってエクスポートされることを示しているため、このhack方法を使用できます。

// UnityEngine.LineRenderer
[GeneratedByOldBindingsGenerator]
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void SetPositions(Vector3[] positions);

最初にコードを投稿します。値タイプの配列の場合、これを行うことができます。

この例では、Byte配列をVector3配列に置き換えでも問題はありません。

[StructLayout( LayoutKind.Sequential )]
struct ArrayHeader {
    internal IntPtr type;
    internal ArraySize_t length;
}

public unsafe static T HackArraySizeCall<T>( Byte[] array, int size, Func<Byte[], T> func ) {
    if ( array != null && func != null ) {
        if ( size < array.Length ) {
            fixed ( void* p = array ) {
                ArrayHeader* header = ( ( ArrayHeader* )p ) - 1;
                var oriLength = header->length;
                header->length = ( ArraySize_t )size;
                try {
                    return func( array );
                } finally {
                    header->length = oriLength;
                }
            }
        } else {
            return func( array );
        }
    }
    return default( T );
}

例のコードを使ってわずかに変更できます。最初にパブリックVector3配列を使用して手動でキャッシュし、listを置き換え、有効なデータを自分で記録します。APIを渡す必要がある場合は、このhack関数を使用してメモリコストのコールを1回騙します。

直接にVector3 []を使用してパスを保存するのが一番いいですが、その他は二番目良いソリューションです。

Hackまで言及したら、リフレクションによってListの_itemsを取得することは問題ではないはずです。ただし、この_itemsの長さはcapacityであることに注意してください。


レンダリング

Q3: 妙な問題に遭遇しました。線を引くシェーダーを実現したいのですが、DrawCallをマージするために、線の速度Speedと線のスケールYScaleを法線Normal.xとnormal.yに入れてシェーダーに渡し、C#でmesh.normalを使って法線を直接に指定します。

しかし、変なことが起こりました。シェーダーで取得されたnoraml.xの値は正常であり、normal.yの値は異常でした。これにより、線の速度も異常になります。タンジェントを使っても同じに、xの値は正常でありますが、他の値は異常です。

理解しにくいのは、Sceneシーンで線の速度が正常であります。つまり、SceneにShaderが取得する法線は正しいでしたが、Gameシーン内の速度は間違いました。これは、GameシーンでShaderが取得する法線の値が異常であることを説明しました(xの値以外、x値は両方のシーンで正しかった)。

Dynamic Batchingの問題かもしれません。Batchingをやりたいから、頂点に対して、モデルスペースが変更されたため、NormalもTangentも従って変更します。

関する問題:https://forum.unity.com/threads/dynamic-batching-seems-to-modify-preset-vertex-normals-and-tangent-is-this-fixable.240882/

UV3またはUV4を使ってカスタムデータを保存することをお勧めします。


制作

Q4: Unityで内部可動車両機能を実現するにはどうすればよいですか? たとえば、World of Warcraftのエレベーターと飛行船。

現在目にある問題:

1)CharacterControllerを使います。キャラクターは車両にバインドされず、自分で重力をシミュレートしますが、キャラクターは揺れ、または車両は水平に移動するときにキャラクターを運転できません。

2)CharacterControllerを使います。キャラクターを車両とバインドしますが、CCには局部変位を行う方法がないようです。

3)CharacterControllerを使用せず、rayで完全に書き換えます。仕事の量は大きすぎです。

同じような機能を行った人がいませんか?経験を教えてください。

デザインの筋を推測します。

キャラクターの水平座標=車両の水平座標+キャラクターの相対変位;

キャラクターの向き=車両の向き+キャラクターの相対的な向き;

キャラクターの高さ=キャラクターの水平座標に対応する車両の上面。

1)CCを変更して、キャラクター移動操作のロジックを再利用しますが、操作の結果は、キャラクターの相対的な変位とキャラクターの相対的な向きになります。

2) キャラクターが車両にバインドされているかどうかは問題ありませんが、バインドしない方が柔軟です。

3) キャラクターの高さは射線を使って車両メッシュに垂直に衝突し、重力は必要ありません。

CharacterControllerは、移動時に単位係数があって、車両が移動するときにも係数を持ちます。この2つを足して最終係数を取れます。したがって、車両の上に立って動かなくても、主人公は車両を追跡して移動します。

最終案はCCを使用することでした。重要な点は、すべての変位操作をFixed Updateに入れる必要があることです。そうしないと、ジッターが発生します。バインドは不要です。車両の各フレームの相対変位を記録し、ジャンプなどの操作に慣性を追加すれば大丈夫です。


レンダリング

Q5: Unityエンジンが5.4から5.6.6にアップグレードされた後、UWAパフォーマンスレポートの「CPU占用の高い関数」リストにUpdateRendererBoundingVolumesという関数が追加されました。この関数は何に使用されますか?

UWAから見ると、これはUnityエンジンがレンダリング前、LateUpdate後のオブジェクトに対するバウンディングボックスの更新です。シーン内の動的オブジェクトが多いほど、コストが高くなります。下図のように。

UWAが最適化したプロジェクトでは、コストは主にパーティクルシステムとSkinned Meshの数に関連します。この値が大きい場合、一般的にActiveのパーティクルシステムとスキンメッシュの多い大きいシーンに存在します。MOBA、超大シーンMMOやPUBGみたいなゲームによく見られます。もちろん、妙な問題も発生しました。たとえば、Unity 2017以降の一部のプロジェクトでは、NGUIとパーティクルシステムの組み合わせによって異常なコストが発生しましたが、これらは特殊なケースです。

上記は、この関数の主なパフォーマンスコストです。これを理解した後、最適化のターゲットも明確できます。つまり、シーン内のActiveパーティクルシステムとスキンメッシュの数を可能な限り制御し、1〜2画面の外でそのようなゲームオブジェクトにDeactiveを行うことなどです。


UWA公式サイト:https://jp.uwa4d.com

UWA公式ブログ:https://blog.jp.uwa4d.com

UWA公式Q&Aコミュニティ(中国語注意):https://answer.uwa4d.com