アニメーションボーンノードのバッチ処理

今回の主な話題：アニメーションボーンノードのバッチ処理、パーティクルシステムのクラッシュ、iOSでカスタムShaderが異常する、エディターで100フレームに達する可能の場合に実機で常に30フレーム未満状況、多言語でのテキストコンポーネントのサイズとテキストの長さの適応。

---
###アニメーション
Q1: アニメーションシステムでOptimize GameObjectオプションをオンにすると、不要なボーンが消えます。ボーンの下に多くの特殊効果のハンギングポイントをぶら下げました。また、 Extra Transforms to Exposeを介してハンギングポイントを公開します。しかし、モデルほどに手動的に選択することはめんどくさいすぎで、何かバッチ処理方法はありますか？

①
>これは、Asset Postprocessorを介して、インポートする時にモデルをトラバースすることができ、特定プレフィックスに一致するノードパスをModel ImporterのExtra Exposed Transform Paths属性に追加して、Optimize Transform Hierarchyを引用してノード最適化効果を実現します。そう理解していますが、正しいかどうかがわかりません。

②
>補足します。

>ノード名がもう知っている場合、直接割り当てることができます。つまり、常にOnPreprocessModelを使用します。具体的なルールはプロジェクトチーム内で合意したら大丈夫です。

>```ruby:
public static string[] NodeNames = new string[]
{
    "node_head",
    "node_arm",  
    "node_leg", 
};
void OnPreprocessModel()
{
    ModelImporter importer = assetImporter as ModelImporter;
    importer.optimizeGameObjects = true;
    //以下のノードが実際のボーンにない場合、エラーが報告される場合がありますが、使用には影響しません。
    importer.extraExposedTransformPaths = EWEquipmentsBase.nodeNames;
}
```

③
>上記の両方の答えは有効ですが、もっと簡単な方法があります。

>最初に、Extra Transforms to Expose大体の原理について話しましょう。（絶対的に正しいではありません）UnityEditor.dll関連の実現を観察したら、以下のことはわかりやすいです。

>1）開発者がExtraTransformstoExposeを変更した後、UnityはfbxのmetaファイルのextraExposedTransformPaths属性に情報を保存します。

>2）Unityがfbxファイルに基づいてPrefabを生成すると、metaファイルのextraExposedTransformPathsの各Stringに基づいて対応する名前の空のGameObjectを作成します。親子関係を考慮する必要はありません。

>3）実行時に、（推測）PrefabがInstantiateされた後、AnimatorコンポーネントがAwakeする時にAnimator.Rebind方法をトリガーします。この方法はTransformの子ノードをトラバースし、子ノードのnameをAvatarにあるボーン情報と比較してから、自動的にノードをバインドします。

>これも、Optimizedした後にAnimatorコンポーネント上にAvatarが必ずある原因であります（OptimizedしていないGameObjectはAnimatorコンポーネントのAvatarを空にさせらあれます）。Avatarをさらに調査したい場合は、InspectorをDebugモードに変更してチェックすることはできます。

>上記の分析に基づいて、このプロセスを模倣し、直接にOptimizedした後のPrefabの下に空のGameObjectを作成し、空のGameObjectの名前を合意されたボーンハンギングポイント名に変更したら、ExtraExposedTransformPathsと同じ機能を実現できます。

>さらに、Animator.Rebindメソッドを使用して、実行時にハンギングポイントを動的に追加および削除機能を実現できます。 これの利点は、特殊効果担当と計画担当がPrefabを繰り返し変更する必要がないことです（もちろん、オフラインにすることもできます）。

>二つ目の利点は、Prefabに全てのハンギングポイントをバインドする必要はないことです。配置テーブルで実際に使用されるハンギングポイントのみを作成し、ハンギングポイントGameObjectをオブジェクトプールに入れてリサイクルしたら、キャラクターが多い、またはデフォルトでハンギングポイントが多い場合には、メモリを最適化することもできます。欠点は、Animator.Rebindに時間がかかるため、Instantiateでコールすることを考慮できることです。

>動的にハンギングポイントをバインドする例、

>```ruby:
private static readonly string[] s_BindPoints = new string[]
 {
     "node_head",
     "node_arm",
     "node_leg",
 };
 private Animator m_Animator = null;
 private void Awake()
{
    m_Animator = GetComponent<Animator>();
    Assert.IsNotNull(m_Animator);
    foreach (var bindPoint in s_BindPoints)
    {
        GameObject bindPointObj = new GameObject(bindPoint);
        bindPointObj.layer = gameObject.layer;
        bindPointObj.transform.SetParent(transform);
    }
   m_Animator.Rebind();    // important!
}
```

---
###クラッシュ
Q2: 低確率のランダムクラッシュ問題を皆さんに聞きたいです。クラッシュのスタック情報は次のとおりです。

インターネットで「Abortmessage： ‘* Assertion at mini-arm.c：2634、condition `pdata.found == 1’」を検索しました。誰かがDLLに関連していると言われており、Androidプラットフォームではローカルnative codeに関するはずです（現在のプロジェクトでliblua53.soと他のluaに関する.soライブラリを使用しています）。mini-arm.cのソースコードを見ました、確かにmono_code_managerに関するものを処理しています。誰か経験のある友達はいませんか？経験を教えてください。

目前、さまざまなパラメータを使用して.soを再コンパイルしてテストしようとしています…同時に、IL2CPPを採用してポジショニングを試す予定もあります。
![1 .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/a21f9d11-46d4-d0cf-5fec-666452533ecc.png)

>先週、私たちのプロジェクトはインターネットで7日間の小規模（約2000人のプレイヤー）クローズドテストを実施しました。この期間中、クラッシュ問題の処理過程と原因は依然として非常に深刻でした。

>以前、「libmono.so “Abort message: Assertion at mini-arm.c:2634, condition `pdata.found == 1’ not met” 」の低確率のランダムクラッシュ問題は私をよく困らせました。主に、この問題はmonoでクラッシュしますから、クラッシュのスタック情報はlibunity.soやlibmain.soと完全に関係ありません。Unityに基づくロジックレイヤーコードももちろんです。クローズドテストの終わり（11月30日）に迫って、28日にmonoに簡単な処理を一回しました。monoコードmini-arm.c：2634のアサーションg_assert（pdata.found == 1）;をコメントアウトし、一般的なエラーメッセージに変えて出力し、コンパイル後にプロジェクトに直接送信します（この変更は無害であります）。そしてプロジェクトにあるスタックポイントの最適化を開始しましたが、この変更を追跡し続けませんでした。その時はそれをただの小さな確率の出来事と考えました。

>11月29日にリリース版を提出した後、夕方にフィードバックされた、このバージョンのクラッシュ率は少し高いと感じたが、実際の統計はなかったとのことでした。このフィードバックをもらった後、注意を払わず、他の問題に対処し続けました（本当にその時にbuglyを見に行かなかったことを後悔しています）。

>クローズドテストは11月30日の正午に正式に開始されました。午後4時から5時以降、同僚から「クラッシュ率が少し高い。平日のテストにはクラッシュしないけど今日はもう2〜3回クラッシュしました。」というフィードバックがくれました。中の一人が「15分自動戦闘すると必ずクラッシュする。」と言いました。この問題について聞いたとき、緊張してbuglyを急いで調べたところ、クラッシュ率が7.x％に達し、通常のテストの2.x％の統計よりもはるかに高いことがわかりました。見てみると、すべてがパーティクルシステムのクラッシュに集中していることがわかりました。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/8d946d39-9f50-2203-bdd7-5298ee3c1d21.png)
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/6ccf69b4-0f62-2a96-c0b6-22b50aa4efec.png)
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/e84852a7-1ef9-4686-832c-89e7aa80a803.png)
これらのクラッシュを確認した後、過去2日間の最適化を思い出しました。ジャム問題の最適化したところ、パーティクルシステムコンポーネントParticleSystemにローカルキャッシュ引用を行いました。スキル特殊効果を再生するたびにGetGetComponentsInChildren（true）を実行する必要はありません。同時に、スキル特殊効果パーティクルシステムが停止しているときにstop（）をコールし、pause（）に変更します（スキル特殊効果は再生後にpoolにリサイクルされるので、pause（）をコールしても残差はありません。この時、データを解放する必要はありません、シーンを切り替えると実際に特殊効果のデータを解放します）。このとき、クラッシュスタックからの情報を見て、最初に考えた変更は、stop（）がpauseを変更したことによって引き起こされたクラッシュであります。プログラマーにiOSで自動戦闘してこの問題を再現することを頼みました。やっぱり2回続けて再現されました。8〜12分以内に必ずクラッシュし、クラッシュスタックはbuglyでの統計と同じです。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/a0623ada-acf2-16dc-d2ad-d66457315462.png)
このとき、すぐにiOSで直接コンパイルテストを変更し、pause（）をstop（）に復元して、テストを続行しました…予期しないことは、問題がまだ存在することです。自動戦闘すると15分内に必ずクラッシュします。さらに考えると、stop()は当時のパーティクル更新とパーティクル放出だけを停止しますが、データは残っています。コードと変更し続き、stop()がコールされた後、すぐにclear()をコールしてテストします。気のめいるのは、まだ自動戦闘すると15分内に必ずクラッシュします。

>特殊効果の再生コードの上下ロジック処理を詳しく確認しましたが、問題は見つかりませんでした。通常のクラッシュ率はそれほど高くありません。思い出して、この2日間で残っている変更は、ParticleSystemローカル引用キャッシュを追加することだけなので、直接にiOSで「再生した後にすぐローカル引用を空に置く」に変更し、コンパイルして実機テストに同期します。30分、または40分以上テストしていて、再びクラッシュすることはありません。pause()コールに戻しても、クラッシュしませんでした。

>この結果には少し驚きました。考えもしなかったので、すぐに外部ネットワークのホットアップデートに修正しました…やっぱり翌日に外部ネットワークのクラッシュ率は低下しましたが、まだ存在しています。しかし、心の中で問題はどこにあるのはもうわかっています。UI特殊効果の再生はまだ処理していませんから（スキル特殊効果とUI特殊効果は分離しており、スキルには高配置と低配置の設定があり、初期化解析は違います）、同じようにUI特殊効果を変更した後、クラッシュ率は1-2%まで低下し続きます。残ったクラッシュは依然としてUIパーティクルシステムに関連しており、主に一部のUI機能ビジネスレイヤーが特殊効果のリサイクルのインターフェイスをアクティブにコールしなかったことが原因です。

>今回のクローズドテストが終わった後、クラッシュ情報を振り返ると、最も厄介なmonoクラッシュ問題は28日に終了しました。つまり、28日にmonoコードを変更したから、「libmono.so “Abort message: ‘* Assertion at mini-arm.c:2634, condition `pdata.found == 1’ not met” 」というスタックデータでもうクラッシュしなくなりました。顧みると、以前の低確率のクラッシュ問題は、UIパーティクルシステムによって引き起こされました。一部のビジネスレイヤロジックは、リリースインターフェイスをアクティブにコールせず、mono-arm.cのクラッシュラインg_assert（pdata.found == 1）でスタック情報が上位層への転送を中断します。

>Unityのバージョンは5.6.5P4であります。キャッシュパーティクルシステムコンポーネントがすぐにリリースされない原因は不明です。Unityの根本的なバグであるか、パーティクルシステムコンポーネントParticleSystemロジックへの上下コールに問題がある可能性があります。

---
###Shader
Q3: カスタムShaderはAndroidスマートフォンでは正常ですが、iOSでは異常であり、多くのエラーが表示され、アセットがすべて黒くなります。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/2c30be35-f5bc-a96e-22b7-8464910abea9.png)

①
>1行目は、不完全を示しています。頂点シェーダーがないことと言っているはずです。
2行目は、Passが削除されたことを示しています。
3行目は、使用可能なSubshadersまたはFallbacksがないことを示しています。

>まとめすると、Shaderは空のShaderであり、使用できません。そのため、取り替えを行いました。その表現は、全て黒くなり、マテリアルが失ってではありません。

>ShaderForgeを使用したことはありませんが、アーティストさんがShaderForgeからエクスポートしたShaderを見ることはあり、プラットフォームのコンパイルオプションが含まれています。このコンパイルオプションが間違っている可能性があり、ShaderForgeのコンパイルオプション設置を確認しなければなりません。

②
>まずはiOSプラットフォームに切り替え、間違ったShaderを選択すると、「Shader is not supported on this GPU」を表示可能性があります。「Compile and Show Code」をクリックして、「Compile errors generating this shader.」は表示するはずです。

>この場合は、GraphicsAPIがサポートしていないFeatureを使用していることを意味します。

---
###フレームレート
Q4: 最近、あるプロジェクトを実行しました。時間コストを確認したところ、ただ十何秒かかりましたが、実機では30フレームを超えることはできませんでした。コードにはApplication.targetframerateを制限することはありませんが、原因は何ですか？
>30フレームの垂直同期制限がオンになっているかもしれません。60フレームに変更すること、垂直同期をオフにすること、または自分でApplication.targetframerateでフレームを制限することができます。

---
###UI
Q5: 多言語で、Textコンポーネントサイズとテキスト長さの適応はどうやって行いますか？同じテキスト、同じフォントサイズ、異なる言語では、長さ（文字数）とテキストサイズ（ピクセルの幅と高さ）が異なります。テキストがTextコンポーネントの表示範囲を超えないようにするにはどうすればよいですか？視覚効果が合理的であることを確認する方法（テキストが小さすぎたり大きすぎたりせず、位置が偏りすぎないようにする）？ 自動化させる方法とは何ですか？
>まず、1種の言語に応じてテキストボックスの大きさを決めます。英語に基づいて計画することをお勧めします。漢字が2文字である場合があり、他の言語に翻訳すると長い文字列になります。 英語はもっと似合うものです。

>1つの方法は、テキストボックスのサイズとBestfitを固定して、最小フォントと最大フォントを設定することです。Bestfitを展開しないと、このやり方は少し乱暴です。私たちの前のプロジェクトはそのような乱暴でありました。

>もう1つの方法は、ContentSizeFitterを介して、1文字のサイズを計算し、自分が設置した最小と最大フォントに応じて、拡張スクリプトを使用して水平方向と垂直方向に動的に拡張することです。

>一般的に、極端な状況から逃れることはできないので、このような非常に特殊な処理を行うためにスクリプトを拡張します。

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com