20211126のUnityに関する記事は5件です。

VRMはどのバージョンを使うべき?

主にアバターを VRM フォーマットに書き出すことを目的としているユーザー向けです。 ダウンロードする VRM(UniVRM)パッケージのバージョンを選ぶ際には、近年のオープンソースソフトウェアについて知っておくべきことがあります。 オープンソースには2種類ある オープンソースのソフトウェアには大きく2つのパターンがあります。近年増えている「1」と、昔ながらの「2」です。 ※VRM は「2」に該当するオープンソースソフトウェアです。 1)プロプライエタリからオープンソースへ 企業が有償ソフトウェアや自社アプリで使用していた内製ツール等を、オープンソースとして公開し開発を続けるパターンです。これらの多くはバージョンナンバーが 1.0 以上である場合が多いです。 それ以外では、課金型サービスを提供する企業が用意する SDK 等が挙げられます。 最近の Unity 界隈だと、 UnityScreenNavigator: Library for screen transitions, tran... lilToon: Feature-rich shaders for avatars 等でしょう。 課金型サービスのSDKであれば、Google、Amazon、Microsoft 等の提供するクラウドサービスです。 特徴は、 リリース当初から製品への組み込みに耐えうる。 既に自社アプリ等に実装されており、多種多様なスマホで大多数のユーザーが問題なくプレイ出来ている。 自社の課金型サービスを利用するユーザー(=顧客)向けのSDKなので、多くのリソースを割いて開発を行っている。 基本的に最新バージョンをインストールした方が良い。 です。 これらのオープンソースソフトウェアの最新版は、古いバージョンより良くなっている場合が殆どです。 2)開発過程を公開しているソフトウェア コチラは VRM が取っている手法です。開発過程が公開されており参加することも出来る、というものです。 開発過程をすべて公開する以外にも、Unity のように「Preview」「Experimental」として正式リリース前のバージョンを公開している場合があります。 特徴は、 最新版は「最新」である、というだけの状態。 あらたなバグがあるかもしれない。 一方で特定の問題は解決しているかもしれない。 テストを行っていないわけではないが、「1)」に比べれば少ない。 というかあなたがテストをしてくれると期待されている。 ユーザーは将来実装される(かもしれない)機能を試すことが出来る。 細かな仕様は正式リリース時には変更される可能性がある。 開発側はより多くのプラットフォームやユーザーに試してもらうことで、問題の少ない枯れたソフトウェアにすることが期待できる。 等です。 この手法では、「A」を「B」にしてみた → 「B」に問題の報告があった → 「B」を「A」に戻したなど、行ってこいをすることもあります。 ※これはオープンソースに限らずソフトウェア開発では起こり得ることですが、その過程がオープンではないので外部からは見えない部分です。 「VRM(オープンソース)」ではその過程が見え、かつバージョンナンバーを振られてリリースも行われます。 これらのバージョンは「最新」であるという事を示すだけものであり、「1)オープンソース化」とは異なり、「最新版をインストールしたほうが良い」かどうかは「インストールしてみないと分からない」というものです。 ※分からないからインストールして確認してみてほしい、ということで多くのユーザーに向けてリリースされている側面もあります。 「安定版」と「開発版」 オープンソースソフトウェアの話からは若干離れますが、ソフトウェアには「安定版」と「開発版」を明確に分けているものと、そうでないものがあります。 「安定版」と「開発版」を分けているソフトウェア 明確に分けているソフトウェアの代表例はウェブブラウザでしょう。 Chrome や Microsoft Edge 等は、数億(数十億?)人が使用している安定版と、ウェブ開発者等が使用する「Dev(毎週更新」「Canary(毎日更新」が存在します。 ※ 数億人が使うソフトウェアに実験的な機能を実装して、なにか問題が起きたら大変ですからね。 開発版で問題なし、バグなし、そもそもコレいる? を乗り越えた機能が、安定版に組み込まれていきます。 VRM は「安定版」と「開発版」を分けていない VRM は「安定版」と「開発版」明確には分けていないソフトウェアです。 「v0.xx」は「v1.0(正式リリース)」に向けた「開発版」であり、「安定版」である v1.0 は存在しない、とも言えます。 ソフトウェアの問題が修正される一方で、新たな機能、実験的な機能の追加も同時に行われています。 「実験的な機能を実装して、なにか問題が起きたら大変」ということが、「アバターを書き出したい」という目的とは全く関係のないところで起こり得る、ということですね。 おわりに 現在の VRM は「1)」を目指している過程です。正式リリース(v1)が行われるまでは、問題の修正と機能の追加ほか、様々な開発が同じタイミングで行われ、適用され、最新版が最適とは限らない状態が続きます。 また、「VRMというファイルフォーマット」を扱う機能と、「VRMを読み込み、アバターに対して各種操作を行う」という機能、それらを合わせたオープンソースソフトウェアなので、状況はさらに複雑です。 今現在 VRM アバターの書き出しにおいては、VRM は「最新版」ではなく、「今まで使っていて問題のなかったバージョン」を選ぶのが最適と言えます。 -- 以上です。お疲れ様でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AvatarをセットアップするEditorを作る(Expression編)|VRC用の拡張Editorを作ってみよう(4)

この記事は前回の続きになります VRC用の拡張Editorを作ってみよう(3) 目次 1.準備 2.ExpressionMenuとParameterのメソッドを作る 3.メソッドの中にコードを書く 4.ボタンで追加できるようにする 5.動作確認 6.次回 1.準備 Expressionを入れるフォルダーを作る 最初にExpressionを入れるためのフォルダーを作っておきましょう Editorなどの隣にわかりやすく名前を付けたうえでフォルダーを作成します 今回はExpressionと付けました できました フォルダーのパスもコピーしておきましょう EditorにExpressionフォルダーの変数を作る フォルダーを作った時にコピーしたパスを変数に入れていきます 今回もOnGUIの上に書いていきます privateなString型の変数を作ります 名前は分かりやすい名前で付けます 今回はprivate string exPathにでもしましょうか exPathが宣言できると、その中に先ほどコピーしたパスを代入します 出来るとこのようになります private string exPath = "Assets/test/Expression"; これで変数ができました ExpressionParameterを扱えるようにする ExpressionParameterを扱えるようにするためにusingを使用します 以下二つを追加してください using ExpressionParameters = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters; using ExpressionParameter = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters.Parameter; これで扱えるようになりました(SDK漁ってる時に見つけたから説明できないし知らない) 2.ExpressionMenuとParameterのメソッドを作る ExpressionMenuのメソッドを作る まずはMenuの方から作っていきましょう 前回作成したSetupFXLayer()の下に作ります privateな値を返さないメソッドを作ります 名前は分かりやすいように付けます 今回はSetupExMenuにしました コードはこうなります private void SetupExMenu() { } これでExMenuのメソッドができましたね ExpressionParameterのメソッドを作る 次にParameterを作ります SetupMenuの下に書いていきます privateな値を返さないメソッドを作ります 名前は分かりやすいように付けます 今回はSetupExParamにしました コードはこうなります private void SetupExParam() { } これでExParamのメソッドもできましたね 3.メソッドの中にコードを書く SetupMenuのコードを書く Menuからコードを書いていきます まずはメニューを作成します VRCExpressionsMenu型の変数を宣言します 名前は分かりやすい名前を付けます 今回はexpressionMenuにしました 宣言ができたらCreateInstanceを使ってVRCExpressionsMenuを新しく作成します 使い方はこうです CreateInstance<"タイプ">(); タイプの中にVRCExpressionsMenuを書きます これでVRCExpressionMenuのインスタンスが新しく作成されます それを変数expressionMenuに代入します コードはこうなります VRCExpressionsMenu expressionMenu = CreateInstance<VRCExpressionsMenu>(); これで新しいVRCExpressionsMenuの代入ができました 新しいインスタンスをAssetsに.assetとして作っていきます Assetsにファイルを作成するにはAssetDatabase.CreateAssetを使います 使い方はこんな感じです AssetDatabase.CreateAsset("作るオブジェクト", "フォルダのパス"); 作るオブジェクトはVRCExpressionsMenuを代入したexpressionMenuで、 フォルダのパスは最初に作ったexPathを使います exPathのままだとファイルを作れないのでアバターの名前を付けて拡張子など指定していきましょう 言葉で説明するより書いた方が早いですね コードはこうなります AssetDatabase.CreateAsset(expressionMenu, exPath + "/" + avatar.name + "ExMenu.asset"); exPath + "/"から先は変えても大丈夫ですが[.asset]は忘れないでくださいね これでファイルの作成ができました 最後に今作ったファイルをアバターへ入れます expressionsMenuにファイルを入れるためには avatar.GetComponent()の後に.expressionsMenuと書きます その中に入れるファイルは前回使用したAssetDatabase.LoadAssetAtPathを使って取得します ファイルパスはファイルを作成した時に書いた物を使います コードはこうなりました avatar.GetComponent<VRCAvatarDescriptor>().expressionsMenu = AssetDatabase.LoadAssetAtPath(exPath + "/" + avatar.name + "ExMenu.asset", typeof(VRCExpressionsMenu)) as VRCExpressionsMenu; キャストは忘れないでくださいね これでSetupExMenuメソッドは完成しました SetupExParamのコードを書く Paramのコードを書いていきます(やっと) 今さっきと同じようにVRCExpressionParameters型の変数を宣言します 分かりやすく名前を付けます 名前はexpressionParameterにしました そのあとも今さっきと一緒でCreateInstanceを使ってVRCExpressionPatametersを新しく作って代入します ここはちょちょいと済ませてしまいましょう コードはこうなりました VRCExpressionParameters expressionParameter = CreateInstance<VRCExpressionParameters>(); これで新しいVRCExpressionParametersの代入ができました 次はExpressionParameterに初期で追加するためのパラメーターを作っていきます 最初にいくつ追加するか指定します 追加方法は変数expressionParameterに.parametersを書き加えてnew ExpressionParameter["要素数"]を代入することで追加できます 今回は3つ追加するので要素数を3にします(最初からVRCのEmoteとか入ってるから...) コードはこんな感じです expressionParameter.parameters = new ExpressionParameter[3]; これでPatameterの箱が3つ作られました ここからは追加するパラメーターなので一気に説明していきます expressionParameter.parameters["要素数"]にnew ExpressionParameter()を代入してパラメーターを追加 expressionParameter.parameters["総素数"].nameに名前を代入 expressionParameter.parameters["総素数"].valueTypeにExpressionParameters.ValueType.型を代入 今説明したものを作っていきます 要素数0 <- [name = VRCEmote] [valueType = ValueType.Int] 要素数1 <- [name = VRCFaceBlendH] [valueType = ValueType.Float] 要素数2 <- [name = VRCFaceBlendV] [valueType = ValueType.Float] コードはこうなります expressionParameter.parameters[0] = new ExpressionParameter(); expressionParameter.parameters[0].name = "VRCEmote"; expressionParameter.parameters[0].valueType = ExpressionParameters.ValueType.Int; expressionParameter.parameters[1] = new ExpressionParameter(); expressionParameter.parameters[1].name = "VRCFaceBlendH"; expressionParameter.parameters[1].valueType = ExpressionParameters.ValueType.Float; expressionParameter.parameters[2] = new ExpressionParameter(); expressionParameter.parameters[2].name = "VRCFaceBlendV"; expressionParameter.parameters[2].valueType = ExpressionParameters.ValueType.Float; これで追加するパラメーターが完成しました これをAssetDatabase.CreateAssetを使ってファイル作成しましょう 作るオブジェクトはexpressionParameterにして・・・ パスの名前もexPath + "/" + avatar.name + "ExParameter.asset"とかにしましょうか コードはこうなりました AssetDatabase.CreateAsset(expressionParameter, exPath + "/" + avatar.name + "ExParameter.asset"); これでファイルが作られるようになりましたね 次にアバターへファイルを入れましょう avatar.GetComponent()に.expressionParametersを付け足します これでexpressionParametersにファイルを入れることができます ファイルはAssetDatabase.LoadAssetAtPathを使ってロードしましょう 最後にロードしたものをアバターに入れます コードはこうなります avatar.GetComponent<VRCAvatarDescriptor>().expressionParameters = AssetDatabase.LoadAssetAtPath(exPath + "/" + avatar.name + "ExParameter.asset", typeof(VRCExpressionParameters)) as VRCExpressionParameters; Menuの時と同じくキャストを忘れないように これでSetupExParamメソッドは完成しました 4.ボタンで追加できるようにする 前回の物を修正する 最初に前回書いたSetupFXLayerのボタンを修正します avatar.GetComponent().customizeAnimationLayersを使用して カスタムアニメーションがオフになっているときだけ表示されるようにします オフの時に真にしたいので[!]を付けます 前回書いたif(GUILayout.Button("アバターのFXLayerをセットアップする"))を挟むように追加します コードはこうなります if (avatar.GetComponent<VRCAvatarDescriptor>()) { //FXボタンが表示されないように追加 if (!avatar.GetComponent<VRCAvatarDescriptor>().customizeAnimationLayers) { if (GUILayout.Button("アバターのFXLayerをセットアップする")) { SetupFXLayer(); } } } else { if (GUILayout.Button("アバターにVRCAvatarDescriptorを入れる")) { AddDescriptor(); } } これでボタンの修正ができました Expressionを追加するボタンを作る まずはif文を使用してアバターのcustomEcpressionsがオフになってるか確認するために avatar.GetComponent().customExpressionsを使用します オフの時に真にしたいので[!]を付けます その中にボタンを追加していきます if文を使用してGUILayout.Buttonでボタンを作っていきます ボタンの名前は"アバターのExpressionをセットアップする"と付けておきました ボタンまで完成したコードがこれです if (!avatar.GetComponent<VRCAvatarDescriptor>().customExpressions) { if (GUILayout.Button("アバターのExpressionをセットアップする")) { } } これでボタンが完成ですね 次はボタンに処理を書いていきましょう ボタンが押されたらcustomExpressionsをオンにするコードをif文の中に書いていきます customExpressionsをtrueにするだけなので簡単ですね コードはこうなりました avatar.GetComponent<VRCAvatarDescriptor>().customExpressions = !avatar.GetComponent<VRCAvatarDescriptor>().customExpressions; これでcustomExpressionsをオンにするコードができました 最後はこの下に作ったメソッドを追加していきましょう SetupExMenu() SetupExParam() を追加します 追加するとコードはこうなります if (avatar.GetComponent<VRCAvatarDescriptor>()) { //FXボタンが表示されないように追加 if (!avatar.GetComponent<VRCAvatarDescriptor>().customizeAnimationLayers) { if (GUILayout.Button("アバターのFXLayerをセットアップする")) { SetupFXLayer(); } } //この下に追加する if (!avatar.GetComponent<VRCAvatarDescriptor>().customExpressions) { if (GUILayout.Button("アバターのExpressionをセットアップする")) { avatar.GetComponent<VRCAvatarDescriptor>().customExpressions = !avatar.GetComponent<VRCAvatarDescriptor>().customExpressions; SetupExMenu(); SetupExParam(); } } } else { if (GUILayout.Button("アバターにVRCAvatarDescriptorを入れる")) { AddDescriptor(); } } これでExpressionを追加するEditorが完成しました!(ふぅ・・・) 5.動作確認 最初にCtrl + Sでスクリプトを保存しましょう 保存ができたらUnityに戻りEditorを開きます Editorの見た目は変わってないですね VRCAvatarDescriptorは前回の続きであればこうなってると思います 上の状態になっていたらEditorを開いてアバターを入れます アバターを入れると作ったボタンが表示されていると思います 押してみましょう ちゃんとExpressionsにMenuとParameterが追加されていますね これで今日は終了です お疲れさまでした 6.次回 次回はゲームオブジェクトを出し入れするためのアニメーションを作るEditorを作ろうと思います VRC用の拡張Editorを作ってみよう(5)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フレーム同期の最適化における難点及びソリューション

フレーム同期という部分はやや複雑なので、細部にも多くの最適化ポイントがあり、いくつかの異なる最適化の方向もあります。プロジェクトの種類、操作感への要求、オンラインプレーヤーの数などに応じて、さまざまな問題が発生します。最適化の方向性や最適化手法の違いにより、異論が生じるかもしれません。さらに、フレーム同期自体には、さまざまなニーズを満たすための多くのバリエーションがあります。したがって、この記事のすべては、作成者のプロジェクトタイプ(ACT)に基づいて解決策と最適化を行うため、フレーム同期を必要とする他のゲームには必ずしも適していません。余計な誤解を引き起こさないように事前にそれを知っておきたいのです。 フレーム同期のいくつかの難点 フレーム同期の基礎的な原理及び状態同期との違いについての記事はたくさん載っており、グーグルで簡単に検索できるため、ここではいくつかの難点だけを説明します。 クライアント独自計算の正しさを保つ、即ち一致性。 フレーム同期の基礎は、異なるクライアントが、同じ操作コマンドの順序によって、それぞれロジックを実行して同じ効果を得ることができるということです。皆が知っているように、Unityエンジンでは、異なるコールの順、タイミング、浮動小数点計算の偏差、コンテナーソートの不確実性、Coroutineでのロジックの記述によって引き起こされる不確実性、物理浮動小数点数、ランダム値による不確実性など。 ランダム値など、ランダムシードを作成することだけで簡単に解決できるものがあります。 コード仕様に注意を払う必要があるものもあります。たとえば、フレーム同期のバトルでは、ロジック部分はCoroutineを使用せず、Dictionaryなどの順序が不確実なコンテナのサイクルに依存しません。 さらに一番基本的なことは、各ロジックを単独でUpdateするのではなく、統合されたロジックのTickエントリを介して、バトルロジック全体を更新することです。各Tickが上から下まで、実行の順序が毎回同じであることを確認してください。 物理に関しては、バトルロジックに必要がないため、衝突は自らで衝突ロジックを作成するため、これを飛ばして説明を進めます。 最後に、浮動小数点数の計算では一致性を保証できないため、固定小数点数に変換する必要があります。固定小数点数の実現に関しては、元の浮動小数点数に上に1000または10000を乗算し、対応する場所を1000または10000で除算するのが最も簡単な方法です。また三角関数でテーブルを検索して、いくつかの問題を解決できて不一致の確率を減らせます。しかし、この根本から解決できない方法は、いくつかのおそれが潜んでいます(たとえば、intとfloatを乗算した場合、元の値が* 1000の場合、最終的に計算された値は非常に大きくなって、境を越えるリスクがあります。) 最善の解決策は:より正確で厳密な固定小数点数の数学ライブラリを使用することです。C#には、固定小数点数の実装があります。Photonネットワークの初期バージョンでは、Truesyncに非常に優れた固定小数点数の実装があります。 固定小数点数の実現 その中に、FPはFloatを完全に置き換えることができ、独自のロジック部分であるFloatなどをFPに変換するだけで、簡単に解決できます。さらに、Protobufシリアル化メソッド(下の図のように、コード内のAttributeに注意)とうまく統合して、配置ファイルも固定小数点になるようにすることができます。 TSVectorはVector3に対応し、FPに基づいている限り、独自のデータ構造を拡張できます。(もちろん、複雑なプラグインが使用されていて、それらがオープンソースでない場合、固定小数点数の変換ははるかに難しくなります) 三角関数はテーブルを検索する方法で実装されるため、固定小数点数の正確さが確保される 個人的には、この方法は、単純な10,000で乗算する方法、10,000で除算する方ほより優れていると思います。不利な点は、計算パフォーマンスが少し悪いことかもしれませんが、多くのテストの後、計算パフォーマンスへの影響は非常に小さく、ほとんどのプロジェクトのニーズを満たすことができるはずです。 計算の不確実性に対しても、いくつかの恐れもあります。Physics.Raycastを使用して地面と壁を検出して、キャラクターに坂を上させたり下らせたりして、階段などのでこぼこの道で歩かせて、形が規則していない壁でもあります。ここでは、不確実性が生じる可能性のある浮動小数点数の位置を取得します。ここでは、可能な限り不確実性を避けるために、数値の切り捨てなどの方法を使用しました。繰り返しテストを行った後、不一致は起こされませんでした。しかし、結局のところ、ロジックの側にこの方法はリスクがあります。より良い方法は、固定小数点数に基づいた一連のraycastメカニズムを実装することです。人員の限り、それを暫時的にしません。他の詳しい記事がありますので、ぜひ参照してください。 フレーム同期:浮動小数点精度テスト(中国語注意)https://zhuanlan.zhihu.com/p/30422277 フレーム同期ネットワークプロトコルの実現 計算の一致性の問題を解決した後、ネットワークがどのように通信するかを検討する必要があります。ここでは、p2pメソッドについては説明しません。説明したいのは、複数のclientと1つのserverのモードです。サーバーは、tickを統合し、clientの命令を転送してから他のclientへ通知することを担当します。以下の記事を参照してください。 オンラインゲームのスムーズな基盤:フレーム同期ゲームの開発 http://www.10tiao.com/html/255/201609/2650586281/4.html まず、ネットワークプロトコルの選択について話しましょう。TCPかUDPかについては、言うまでもなくUDPを選択するに違いありません。遅延を短縮できますから。UDPの選択については、インターネットでの記事を読んだことがありますが、信頼性の高い送信に基づくUDPを使用するのか、冗長な情報に基づくUDPを使用するのかという誤解が生じやすいです。 信頼性の高い送信に基づくUDPとは、UDPの上にカプセル化を加え、自らでパケット損失処理をし、メッセージシーケンス、再送信などを実装するTCPに似ているメッセージ処理方法です。従って、上位層ロジックがデータパケットを処理するときに、パケットのじょんを考慮する必要がないようにします。パケット損失など。類似な実装は、Enet、KCPなどがあります。 冗長情報UDPとは、上位層のロジックが自らパケット損失、順不同、再送信などを処理する必要があり、下位層が元のUDPを直接使用するか、Enetの Unsequencedのようなモードを使用するかのことを指します。一般的な処理方法は、両端のメッセージにフレームの確認情報が含まれていることです。たとえば、クライアント(C)がサーバー(S)に100フレームのデータを通知し、Sがデータを受信した後にCに「100フレームを受け取りました」と通知します。その知らせはずっとCに届かない場合(パケット損失、順不同などの原因で)、確認メッセージを受信するまで、100フレームのデータをSに送信し続けます。 この両者の相違を明確にしない記事はいくつかありますが、実に違いが大きいと言えます。個人的には、信頼性の高い送信のUDPはフレーム同期に相応しくないと思います。何故ならば、パケットの順序を確保し、パケットの損失や再送信などに対処するために、ネットワークが貧弱な場合、Delayが非常に大きくなるためです。これにより、パケットの送受信を、より良いと言えども、やはりTCPのように処理します。だから、良い効果を得るために、冗長情報のUDPを使用しなければなりません。それに、フレーム確認と再送信方法をサーバーとネゴシエートする限り、実装はそれほど複雑ではありません。自分で実装したら、最適化の余地がたくさんあります。たとえば、プロトコルの定義は次のようになります。  双方も、お互いにどのフレームまでに受け取ったのかを通知し、またcmdlistを介して受信されなかったコマンドを再送信する。 このような頻繁に送受信されるメッセージに対し、Protobufを使用すると、論理フレームごとにGCが発生します。これは非常に悪いことです。解決策は、ProtobufにGCなしの変換をするか、自分で簡単なbyte[]の読み取り・書き込みを実装します。実装することです。GCなしの変換は重過ぎて必要が無いと感じます。戦闘中に頻繁に送信されるメッセージはごくわずかであり、byte []の読み取り・書き込みを自分で処理するだけで十分です。 また、KCPの作者であるWeiYixiaoさんは、KCP + FECのモードは、冗長モードよりも優れた結果が得られるかもしれないと述べています。まだ深く調べたことがありませんが、一応皆さんに推します。 プロジェクトの初期に、サーバーはEnetを使用することを決定し、どうせ冗長パッケージを使用したので、EnetとKCPを考えませんでした。後はKCPに変更しようと思って、サーバーは変更したくなかったので、そのままになっています。 Enetの問題は、Enetのipv6バージョンが未成熟なPull Requestであるということです。Enetの作成者はMergeをしません(それにいくつかのipv6のPull Requestを保存しています)。安定性について確認できませんから、いくつかのCommitを確認しました。またテストして来て、大きな問題はありません。 KCP ipv6の問題をまだ評価していませんが、GithubにC#バージョンがあルため、ipv6サポートを簡単に変更できるはずです。 ロジックと表示の分離 この部分は、フレーム同期に関する多くの記事で言及されています。配置されたデータはディスプレイから分離するなら、戦闘では、戦闘のロジックもディスプレイから分離する必要があります。 たとえば、アクションスイッチングのロジックは、AnimatorでのClipの再生ではなく、独自の抽象的な論理フレームに基づいています。たとえば、攻撃アクションの場合、10フレームが表示され始め、攻撃ブロックが出現します。さらに敵の受撃ブロックとの衝突を検出し始めます。このときの10フレームは独立したロジックでなけれならなく、Animatorの再生時間またはAnimatorStateInfoのNormalizedTimeに依存することはできません。さらに、キャラクターのモデルをロードしなくても、バトルのロジックを実行できます。十分に分離されている限り、戦闘の検証プログラムとしてサーバー上で実行することもできます。「Honor of Kings」もこの方法でします。 オンラインでスムーズな戦いを実現する方法 これまでのすべての準備、最終的な目標は、戦闘をスムーズにすることです。特に、ACTゲームや格闘ゲームでは、ボタンを押した直後に操作をフィードバックする必要があり、少し遅れるとプレイヤーの操作感に影響を与え、コンボ操作が中断され、体験に悪影響を与えます。遅延に対する感度はMOBAゲームよりもさらに高く、優れた操作感とオンライン戦闘(PVP、チームPVE)を実現するために、遅延でフリーズしたまたは操作フィードバックで変化が起こったことを避け、究極のフレーム同期を実現する必要があります。 このため、Lockstepの方法は使用できません。Lockstepは、ネットワーク環境が良好なイントラネットや、操作遅延の影響を受けにくいタイプに適しています(カードゲームのフレーム同期に使用されるプロジェクトがあると聞いたことがあります)。 一部のゲームで作成された命令Bufferである操作をキャッシュサーバーで確認することはできません。「Honor of Kings」の分析記事での説明は、非常に具体的です。これはモードと呼ばれるものでもあります。このモードは、ネットワークの小さな変動や、操作に対してあまり高いフィードバックを必要としないゲームを解決できます。たとえば、攻撃する前に比較的長い前進スイングがあるゲームもあります。このタイプのゲームはこれを使用して、ほとんどの問題を解決できるはずです。ただし、この方法には依然としてリスクがあります。戦略を通じてBufferを動的に調整できたとしても、高遅延でフリーズしたことやスムーズでない問題を依然として解決し難いです。「Honor of Kings」はうまく最適化されました。Bufferの長さは0にすることができると言われました。この記事では、スムーズな補間と論理的な表示の分離による最適化についてのみ言及していて、詳細については触れませんでした。この方法でしか最適化するかどうかはまだ不明でしたが、これ以上の具体的な分析はまだないそうです。 Bufferを命令する方法も私たちのニーズを満たすことができません。言い換えれば、この方法で最適化して「Honor of Kings」のような効果が得られる方法を見つけていません。他のMOBA、ACT、ARPGゲームのテザリングもテストしましたが、高遅延、ネットワークの変動が大きい場合、「Honor of Kings」よりも優れたパフォーマンスはありません。 最後に、私たちのニーズを注意深く研究した後、自分に非常に適した有益な記事を見つけました: Understanding Fighting Game Networking http://mauve.mizuumi.net/2012/07/05/understanding-fighting-game-networking/ この記事では、さまざまな方法を詳細に紹介します。最終的なロールバックロジック(Rollback)は、究極のソリューションです。国内の記事でも言及されています。 記事の中にTime Warpと呼ばれる方法は、私的理解では、ロールバックロジックと同じようなコンセプトです。 ゲームロジックのロールバック ロールバックロジックは、即ち私たちの解決策です。クライアントの時間がサーバーより進んでいると理解できます。クライアントは、サーバーがフレームの戻りを確認してからコマンドを実行する必要はありません。代わりに、プレーヤーはそれを入力してすぐに実行してから(他のプレーヤーの入力は最新の入力に基づいて予測、または他のより最適化された予測案)、サーバーにその命令を送信します。サーバーが受け取って確認してから、前の実行と同じようなら(自分や他のプレイヤーによって予測された操作)、何も変更しません。一致していないなら(予測エラー)、ゲームの全体ロジックは最後のサーバーによって確認された正しいフレームにロールバックされ、それから現在のクライアントのフレームに追いつきます。 ここでのロジックはちょっと複雑なので、例を挙げて説明します。 現在のクライアント(A、B)は100フレームまで実行され、サーバーは97フレームまで実行されます。フレーム100で、Aが移動を実行し、Bが攻撃を実行しました。AとBの両方がサーバーに通知しました:100フレームまでに実行しました。操作は移動(A)・攻撃(B)です。サーバーは、自分のフレーム98または99でA・Bのメッセージを受信し、対応するフレームの操作データに格納されます。サーバーが100フレームまで(または事前に)実行すると、このデータをABにブロードキャストします。 次に、AとBはすぐに100フレームの実行を開始し、Aは移動を実行し、Bが操作を実行しないと予測します。 Bが攻撃を実行し、Aが攻撃を実行すると予測します(おそらくAの99フレームも攻撃です)、AとBはそれぞれ互いの操作を予測します。 予測エラーが発生して、ロジックロールバックを実行することを防ぐために、AとBが100フレームを実行した後、それぞれ100フレームの状態スナップショットとそれぞれの操作(予測操作を含む)を保存します。 数フレームを実行した後、AとBはフレーム103に到達し、サーバーはフレーム100に到達し、ABにデータをブロードキャストし始めました。一定の遅延の後、ABはサーバーによって確認された100フレームのデータを受信しました。その時、AB は既に104まで実行しました。AとBはそれぞれ、サーバーのデータが自分で予測したデータと同じであるかどうかを確認します。たとえば、Aは100フレームをチェックした後、その動作は自分の予測と同じく、何の処理も行わずに前進し続けます。それに対し、Bが100フレームをチェックした後、Aに対する予測はサーバーによって確認されたAの操作とは異なり(Bは攻撃を予測したが、実際のAの操作は移動でした)、Bは前の確認が同じフレーム(即ち99フレーム)にロールバックし、確認した100フレームの動作に従って100フレームを実行し、そして101〜103のフレームロジックをすばやく実行してから、104フレームを実行します。(101〜 104)は以前として予測されたロジックフレームです。 クライアントは現在の操作をすぐに実行するため、操作感はPVE(ネットワークに接続されていない)とまったく同じであり、Delayはありません。そのため、優れた操作感が得られます。予測が異なる場合は、論理ロールバックを実行して、現在の操作をすばやく追い戻ります。 このように、ネットワークの良いプレイヤーとネットワークの悪いプレイヤーはお互いに影響を与えません。Lockstepのように、ネットワークの良いプレイヤーはネットワークの悪いプレイヤーによってLockされます。ネットワーク遅延によってロックされることもなく、クライアントは常に前方を予測できます。 ネットワークが良いプレーヤー(A)の場合、(動的Latencyに従って)動的に調整して、クライアントをサーバーより少しの先に進めさせ、予測の量を最小限に抑え、ロールバックを最小限に抑えることができます。たとえば、ネットワークが優れている場合、クライアントは2〜3フレームだけ進んでいる可能性があります。 ネットワークが不良なプレーヤー(B)の場合は、Latencyに従ってに従って動的に調整して、サーバーより遠くの先に進めさせます(例、5フレームを優先的に行う)。 それでは、Aの予測エラーは2〜3フレームのみに残っており、ネットワークが不良なBは、5つのフレームがある可能性があります。最適化された予測技術とメッセージ通知の最適化により、AとBの予測エラー率をさらに減らすことができます。Aの場合、戦闘はスムーズで、手触りもとても良いです。少数のロールバックが最適化されており、フリーズや遅延感が起こされません。 最適化のポイントは、ネットワークが悪いプレーヤーBとその方の操作体験です。クライアントはサーバーの確認を待たずに操作を行うため、Bの操作感はAと同じですが、遅延によりBが予測するフレーム数が多くなり、予測エラーやロールバックが増える可能性があります。Bの予測によれば、100フレームでAをヒットするはずですが、Aの操作を予測し間違えるため、ロールバックが再度実行された後、Bは100フレームでAにヒットしない場合があります。 Bにとって、補間といくつかの平滑化方法を通して、Bの感じたことは大きいな区別がありません。Bは自分自身を見て、操作はタイムリーにフィードバックされ、自分側の操作はスムーズだと感じます。 このように、ネットワークの悪いBの操作感はAと同じであることが保証されます。ロールバックによって引き起こされるわずかなジッターは、Aのジッターだと思われます。最適化(補間、平滑化など)を通じて、これらをさらに減らした後、Bの操作感はとても良くなります。 200〜300ミリ秒のランダムな遅延で、Bの操作感が良好であることをテストしました。 ここで、クライアントはサーバーを前倒し、遅延が増加している場合、高速化されます。「Overwatch」はそれを同じように扱います。 ここでは、一つだけを強調したいのです。予測実行はフレーム同期に関する多くの記事で言及されている予測とは異な理、実際のロジックの予測です。一部の記事で紹介されている予測実行は、前進運動や変位などのViewレベルでの予測だけで、ロジックは事前に実行されず、サーバーは復帰を待つ必要があります。これらの2種類の予測実行(Viewの予測実行と実際のロジック予測実行)はまったく同じ概念ではないため、ここでは注意深く区別する必要があります。 ゲームロジックのスナップショット(snapshot) ロジックがロールバックできる理由は、各フレームの状態はスナップショットを処理し、各フレームの状態を保存し、任意のフレーム状態にロールバックする機能に基づいています。 Understanding Fighting Game Networking: http://mauve.mizuumi.net/2012/07/05/understanding-fighting-game-networking/ 「Overwatch」: 上記の2つの記事では、スナップショットの説明を触れました。やや違っているかもしれませんが、各フレームの状態を保存するというアイデアは同じです。スナップショットを扱う場合(『Understanding』の記事はエミュレーターゲームで、メモリスナップショットの形で簡単に行うことができます)、それは難点と言えます。 次はこの前の記事で、ECSの適用について述べました。 ECSは良い処理法で、以下の記事にオープンソースのdemoをしましたが、まだ成熟したプロジェクトと言えません。 この記事のアイデアは非常に明確であり、いくつかの実際の問題点も指摘されており、解決策のアイデアは基本的に正しいです。参照してください。 以前にやったのですが、当時は「Overwatch」の記事が公開されておらず、ECSに基づく戦いでもありませんでした。スナップショットを扱うときは、自分のロジックに従ってやらなければなりませんでした。 私の考えでは、ロールバックインターフェイスを介して、データのロールバックが必要な部分にインターフェイスを実装し、それぞれが独自の保存スナップショットとロールバックを処理します。複雑な配置をシリアル化するのと同じように、各配置は独自の部分をシリアル化し、最終的にシリアル化されたファイルにマージします。 まず、インターフェイス、およびスナップショットデータのReaderとWriterを定義します 次に、各モジュールは独自のTakeSnapshotとRollbackを単独で処理します。以下のように例を示します。 単純な数値ロールバック リストのロールバックのコピーとサブモジュールのロールバックの呼び出し アイデアが正しければ、簡単に処理できます。WriteとReadの順序に気を付け、リストの処理に注意を払うと、ほとんどの問題が解決されます。当然ながら、ロジックを実装するプロセスでは、モジュールがどのようにロールバックされるかに常に注意を払ってください(たとえば、乱数の取得もロールバックする必要があります)。 より簡単な方法があります。それは、属性にAttributeと入力してから、一般的なメソッドを作成することです。たとえば、私の初期の実装計画は次のとおりです。 属性にタグする ラベルによると、リフレクションによる一般的な読み取りと書き込みの方法を通して、各モジュールが独自の方法を実装する必要はありません。 コードの一部 この方法はほとんどの問題をうまく解決でき、前述のTruesyncにも適用します。 しかし、このメソッドには避けられない問題、つまりGCがあります。これは、リフレクションに基づいて、fieldのGetValueとSetValueを呼び出すときに、GCが避けられないためです。また、全自動であるため、特殊なロジックの処理が不便であり、最適化をデバッグするのも不便です。最後に、現在の方法に変更しました。少し面倒に見えますが、より制御しやすくなっています。その後の多くの最適化も便利になっています。 スナップショットに関しては、最適化できる点も多くあります。GCメモリの方でも実行効率の方でも最適化する必要があります。そうしないと、パフォーマンスの問題が発生する可能性があります。この最適化については、時間があれば別の記事で説明しましょう。 スナップショットがあれば、ロールバックやスキップもサポートできます。たとえば、戦闘ビデオを見たい場合、スナップショットがないと1000フレームにスキップし、保存された操作手順に従って最初のフレームから1000フレームまですばやく実行する必要があります。スナップショットを使用すると、 1000フレームに直接ジャンプできます。中間プロセスを実行する必要はありません。異なるフレームを切り替える必要がある場合は、スキップするだけで済みます。これは非常に役立ちます。 自動テスト フレーム同期には一致性をテストする必要があるため、私たちにとって、ロールバックも多くのテストを必要となります。自動テストは必要なステップです。これについて、操作、スナップショット、logを保存してから、さまざまなクライアントのデータを比較し、さまざまな場所を見つけて、エラーをチェックして修正することです。 現在は、ワンステップ操作、自動ループバトル、各バトルデータをイントラネットログサーバーにアップロードします。 戦闘データが多い場合、ツールで自動的にデータを解析して比較して、同期していないポイントを見つけます。さらに良く最適化することもできますが、今では十分に感じられます。多数の内部自動テストの後、現在の戦闘の一貫性は非常に良好です。 まとめ 要約すると、現在のフレーム同期方法は、予測、スナップショット、およびロールバックです。これらを有機的に組み合わせて最適化すると、非常に優れたフレーム同期ネットワーク効果が得られます。ネットワーク速度に関係なく、遅延が異常すぎない限り、非常に優れた操作感が保証されます。 スナップショットのロールバック方法は、すべてのゲームに適用できるわけではありません。例えば、 Skywind Insideは、オンラインゲーム同期の技術記事でこのモードの欠点(Time warpまたはRollback)について説明しています。 記事に示すように、このモードは、多数のプレーヤーがあるオンラインゲームに適応しません。例えば、MOBAに適していないかもしれません。現在、最大で3人がオンラインで、最適化やテストして、効果上の問題はありません。ただし、オンラインのユーザーが多いほど、予測操作でエラーが発生する可能性が高くなり、ロールバックが多くなります。 一つの記事で、すべてを網羅することは難しく、多くの場所が明確に説明されていない可能性があります。さらに、個人的な能力とチームメンバー(3人のクライアント)が限られているため、設計と実装が十分でない部分がたくさんあるに違い有りません。ご了承願います。 役に立つ参考記事: 1、Understanding Fighting Game Networking http://mauve.mizuumi.net/2012/07/05/understanding-fighting-game-networking/ 2、 http://www.skywind.me/blog/archives/1343#more-1343 3、 https://mp.weixin.qq.com/s/cOGn8-rHWLIxdDz-R3pXDg 4、 http://youxiputao.com/articles/11842 5、 https://zhuanlan.zhihu.com/p/30422277 6、A guide to understanding netcode https://www.gamereplays.org/overwatch/portals.php?show=page&name=overwatch-a-guide-to-understanding-netcode 7、 http://www.10tiao.com/html/255/201609/2650586281/4.html 最後に、最初に述べたように、フレームの同期にはたくさんの変種があって、実装方法や最適化の方向にもたくさんある為、記事には同じくフレーム同期と呼んでも、区別しています。ぜひ詳しく区別して理解してください。 UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析と最適化ソリューション及びコンサルティングサービスを提供している会社でございます。 今なら、UWA GOTローカルツールが15日間に無償試用できます!! よければ、ぜひ! UWA公式サイト:https://jp.uwa4d.com UWA GOT OnlineレポートDemo:https://jp.uwa4d.com/u/got/demo.html UWA公式ブログ:https://blog.jp.uwa4d.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityパフォーマンスの最適化――アニメーションモジュール

4年前、Unityの各主要なモジュールのパフォーマンス最適化知識(初心者向け版)を1つずつ説明しました。近年、エンジン自体、ハードウェアデバイス、製作基準などのアップグレードに伴って、UWAは引き続き規則や方法を更新して、各開発者に提供しつつあります。「アップグレード版」のパフォーマンス最適化マニュアルとして、【Unityパフォーマンス最適化シリーズ】はより多くの開発者が利用できるように、シンプルでわかりやすい表現を心がけています。今回、アニメーションモジュールに関連する知識を共有します。 https://lab.uwa4d.com/lab/5b442633d7f10a201faf59b4 レポートにおいて、アニメーションに関連するメイン関数は次のものがあります。 Animatorに関連する関数は2つあります:1つはDirectorUpdateAnimationBeginで、もう1つはDirectorUpdateAnimationEndです。一般的に、これら2つの関数のスタックに注意を払い、スタック関数のコール回数と時間使用の割合から原因を特定する必要があります。消費された時間。パフォーマンスと最適化の対策に影響を与える一般的な要因は次のとおりです。 1)ActiveのAnimator数を制御する キャラクター数の増加は全体時間の増加に繋がっています。キャラクターデータが増えるにつれて、各関数のCPU時間も直線的に増加します。 上図はあるプロジェクトの実機テストレポートです。あるフレームを選択してそのスタック情報を確認すると、ApplyOnAnimatorMoveのコール回数が168に達していることがわかります。これは、現在のシーンにおいて、Update状態にあるAnimatorは168個があることを意味します。一般的には、この値を30以内に保持することが勧められ、168という数値は非常に高いと言えます。 数値が高い理由として、通常、スクリーンの外の数多くのAnimatorは計算によるCPU時間を更新していることに由来します。キャラクターのAnimatorコンポーネントをキャッシュすることで、Active状態に維持することによるかもしれませんが、UIのAnimatorコンポーネントは多すぎることによって引き起こされた可能性もあります。UIのAnimatorが原因なら、簡単なアニメーションの場合、代わりにDotweenを使用して実装することをお勧めします。 2)「ApplyRootMotionをオンにする」Animatorの数を確認する Animators.Updateのスタック分析を通じて、Animator.ApplyBuiltinRootMotionが28%の割合を占めることがわかります。これは、プロジェクトでApply Root Motionのモデルアニメーションがオンになっていることに関連しています。アニメーションがディスプレイスメントを必要としない場合は、このオプションをオンにする必要はありません。 3)Optimize Game Objectsというオプションをオンにする このオプションが選択された状態では、Unityはアニメーションクリップの処理時にTransformのレベル情報を削除します。この設定は、Animators.Updateの時間コストを大きく軽減します。これにより、メインスレッドのアニメーション時間は大幅に削減され、貴重なメインスレッド時間を浮かせて、より複雑な計算ロジックに譲ります。 4)Animator.Initializeのトリガー頻度を制御する Animator.Initializeは、Animatorコンポーネントを含むGameObjectがActiveまたはInstantiateされたときにトリガーされます。これはかなり時間がかかるので、戦闘中にAnimatorを含むGameObjectに対してDeactive / ActiveGameObject操作を頻繁に実行することはお勧めしません。下の図に示されています。 頻繁にインスタンス化されるキャラクターに対し、バッファプールの方法で処理することをお勧めします。キャラクターを非表示にする必要がある場合は、キャラクターのGameObjectを直接Deactiveするのではなく、DisableAnimatorコンポーネントを使用して、GameObjectをスクリーンの外に移動することにより、Animator.Initializeのコール頻度を軽減します。 5)AlwaysAnimateモードのAnimator Controller数が多すぎる AlwaysAnimate状態では、キャラクターが画面外にあるとき、Updateコストが引き続き生成されます。このオプションをCullUpdateTransformsまたはCullCompletelyに変更することをお勧めします。CullUpdateTransformsは、アニメーションが変位を生成するAnimator Controllerに適しており、CullCompletelyは、アニメーションが変位を生成しないAnimatorControllerに適しています。 6)Animators.FireAnimationEventsAndBehaviours これは、アニメーションイベントの具体的なコストです。主にプロジェクトのロジックコードのパフォーマンスコストです。この場合、開発チームがアニメーションイベントをさらにチェックすることをお勧めします。 7)グループアニメーションをレンダリングする時にGPU Skinning+GPU を使用するInstancing UnityエンジンのネイティブなGPU Skinning操作をオフにすることをお勧めします。この操作により、余分なコストが発生し、メインスレッドまたはレンダリングスレッドで無効な待機が発生します。 同時に、同じ種類の多数のモンスターが必要な場合は、オープンソースライブラリでのGPU SkinningとGPU Instancingを使用してレンダリングすることをお勧めします。これにより、Animators.Updateの時間を短縮するだけでなく、バッチ処理の効果も実現できます。 関連するオープンソースライブラリのリンク: https://lab.uwa4d.com/lab/5bc6f85504617c5805d4eb0a https://lab.uwa4d.com/lab/5bc5511204617c5805d4e9cf 以上は、アニメーションモジュールを最適化するときの問題です。効率のために、一般的な方法ですが効果がある方法を提供します。「手作りのコインの代わりに印刷機を創造する」、パフォーマンスレポートを参照し、ctrl + fで「アニメーション」を検索して、レポートのアニメーションモジュールが正常でない場合、すぐに次の不合格な診断項目を確認できるようになります。 それに、ローカルアセットのテストのサービスでは、アニメーションアセットの関連設定の合理性をさらに包括的にチェックできるように、次の4つのテストルールも提供されております。 UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析と最適化ソリューション及びコンサルティングサービスを提供している会社でございます。 今なら、UWA GOTローカルツールが15日間に無償試用できます!! よければ、ぜひ! UWA公式サイト:https://jp.uwa4d.com UWA GOT OnlineレポートDemo:https://jp.uwa4d.com/u/got/demo.html UWA公式ブログ:https://blog.jp.uwa4d.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Assembly Definition にヘッダ的クラスを用意してコンパイルを速くしたかった

背景 Assembly-CSharp から Addressable Definition(以下、asmdef)に定義されたクラスを扱うには、asmdef の Auto Referenced を有効にして、毎回コンパイルする必要があります。 私は、多くのコードが書かれたクラスを毎回コンパイルするのは無駄だと思い、C のヘッダーのようなクラス(以下、ヘッダ的クラス)を作る事を考えました。多くのコードが書かれたクラスの Auto Referenced を無効にして、代わりにヘッダ的クラスの Auto Referenced を有効にする事で、コンパイルするコードの量を減らし、コンパイル時間を短くできないかという案です。 ヘッダ的クラスの実装が有効か判断するため、ヘッダ的クラスの有無がコンパイル時間に影響するか検証することにしました。 ヘッダ的クラスとは 他のクラスの関数を呼び出すだけのクラスを指します(このようなクラスには名前があるかもしれません)。実装がないのでコード量も少ないです。 計測方法 コンパイル時間の測定には、baba-s さんの CompileTimeMeasurer を使わせていただきました(使うにあたり、ログ表示を改造しました)。 多くのコードが書かれたクラスを再現するために、適当な関数を1000個入れたクラス Body を用意しました(Google スプレッドシートで作りました)。 コンパイルを走らせるために CompileTest というクラスを作りました。Body の関数である Function1() と Function2() を交互に書き換えて保存し、コンパイルを実行します(スクリプトが Editor に反映されるまでの時間は多少前後するので、手動で Refresh して誤差を減らしました)。 まずは、asmdef なしで20回計測しました(左から、回数,コンパイル時間,累計コンパイル時間)。 次に、Body の asmdef を作成し、Auto Referenced を有効にして20回計測しました。 Body を asmdef に入れて、変更を加えなかったので、コンパイル時間が短くなりました。 最後に、Header を作り Body の代わりに Auto Referenced を有効にして20回計測しました。 (どうでもいいですが、CompileTest の Body.Function を Header.Function に変えたり、Header.asmdef の asmdef References に Body.asmdef を追加したりもしました) 逆に遅くなってしまいました? 計測結果 左から、asmdefなし、Bodyのみ、Header利用の測定結果です。ヘッダ的クラスの利用は逆効果だと分かりました。 まとめ ヘッダ的クラスはダメでしたが、スクリプトを asmdef で定義すればコンパイルが速くなると実感できました(当然)。 私のような発想に至った人がこの記事を見て、ヘッダ的クラスは無意味だと知ってくれれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む