20211022のUnityに関する記事は7件です。

物理シミュレーションソフトを作りたい

大まかな構成 ・右側にいろいろな条件を指定できるタブ ・実行結果を映し出す 現状 ・ゼロからスタート・・・ってわけではないけどunityかじったことはある ・物理シミュレーションソフト自体はあるのは知ってるが、自分自身物理がとても苦手なためここで克服したい気持ちがあるのだ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゲーム開発者なら覚えておきたい便利なツールやサイト

ゲームエンジン問わずいろいろ使われることの多いツール・サイト等を紹介します。 他にもいろいろ知りたいので,ここで紹介されてないけど便利だよーってものがあったら教えてください。 よく使われているもの GitHub まぁQiitaでブログ漁る層なら誰でも知ってるとは思いますが,一応…… バージョン管理ができる便利なやつです。 ゲーム開発の途中で「あ!超重要なファイルを消してしまった!」や「致命的なバグが見つかったんだけどこれいつからあるん?」的なことが起きた場合に対策できます。 こちらを利用する際はgitignoreについても検索してください(マジで超重要) gitignoreを引っ張ってくる場所。 https://www.toptal.com/developers/gitignore https://github.com/github/gitignore url: https://github.co.jp CircleCI CIとかCDとかいうやつで,GitHubと一緒に使ったりします。 どういった部分が便利かというと,GitHubにあげたファイルを自動でテストしてくれて,エラーがあった場合に開発者にフィードバックしてくれるので,開発に使う時間を超大幅に減らしてくれます。 似たサイトにJenkinsというものがあるのですが,そちらより素晴らしいです。 Jenkinsより優れている点に関しては公式ページに書いてあるので,以下のリンクから読んでください。 既存の CI/CD を Jenkins から CircleCI に移行 Sourcetree GitHubにあげたファイルの情報を,視覚的に分かり易く表示してくれます。 似たようなツールに「GitHub Desktop」ってのがあります。 この2つを比較するとこんな感じです。 Sourcetree GitHub以外でも使用できる。 複数人で開発する際に他のメンバーの状況が分かり易い。 GitHub Desktop GitHub専用に作られているので,Sourcetreeよりも使いやすい。 個人開発では滅茶苦茶便利な印象。ただ複数人での開発だと全体の流れが掴みづらいかも。 って感じです。 場合によって使い分けてください。 url: Sourcetree https://www.sourcetreeapp.com GitHub Desktop https://desktop.github.com Rider ※ Unity利用者向けです。 VSとかVSCodeとかそんな感じの開発環境なんですが,桁違いに使いやすいです。 ただ有料(学生・教員は学習目的でのみ無料)です。 しかし,有料で購入してもいいくらいの利点はあります。 以下,簡単に説明します。 足りない名前空間があった場合,自動で補完してくれる。 パフォーマンスの悪い処理の記述があった場合に,より良い方法を自動で提案してくれる。 VSCodeのように拡張機能を入れて環境構築をしなくても,最初からUnity専用の環境が用意されている。 とまぁ紹介したのは一部だけなんですが超便利です。 (特にVSCode何かアップデートする度にUnityとの間にバグが起きたりめんどくさいので) より詳細な説明だったりは公式サイトに載っていたりします。 url: https://www.jetbrains.com/ja-jp/lp/dotnet-unity/ Photon オンライン環境を簡単に構築してくれます。 クロスプラットフォームにも対応していて,東京にもサーバーがあるので軽くて速いです。 url: https://www.photonengine.com/ja/PUN Vivox Unityが提供しているVCのシステムで「VALORANT」や「PUBG」,「レインボーシックス シージ」といった超有名ゲームにも導入されています。 Unityが提供してるんですけどUnrealとかでも使えます。 同時接続5000人までは無料で使えるみたいなので,気軽に導入してみてください。 url: https://unity.com/ja/products/vivox Spine 2Dアニメーションでよく使われているものです。 公式サイトには利点として以下のことが書かれてあります。 小容量 従来のアニメーションは各フレームごとに画像を必要としましたが、Spineアニメーションは非常に小容量なボーンデータだけ保存します。このため豊富でユニークなアニメーションをゲームに搭載することができます。 スムーズ Spineのアニメーションは補間を使用しているため、フレームレートに応じて常に滑らかなアニメーションを実現できます。また、品質を落とすことなくスローモーションで再生することができます。 アタッチメント ボーンにアタッチされている画像を切り替えて、異なるアイテムやエフェクトを持つキャラクターに着せ替えることができます。アニメーションを異なる外見のキャラクターに再利用することができ、膨大な時間を節約できます。 ミキシング アニメーションはブレンドすることができます。例えば、キャラクターが「撃つ」アニメーションを再生しながら、「歩く」「走る」「泳ぐ」アニメーションも再生することができます。また、あるアニメーションから別のアニメーションへの切り替えもスムーズにクロスフェードさせることができます。 引用元: http://ja.esotericsoftware.com/spine-in-depth#Spineとは まぁQiitaを読むのはプログラマーなので殆どの方には関係なさそうですが, それでもかなり便利だと分かります。 url: http://ja.esotericsoftware.com UniTask Unityで非同期処理を行うという方には必須ともいえるライブラリです。 非同期処理の何がいいのーっていうと,処理順によるバグが防げるのと処理速度が爆上がりします。 url: https://github.com/Cysharp/UniTask/releases Docker どこでも誰でも同じ環境で開発ができる凄いやつです。 チームで開発している時なんかは特に便利で,全員が同じ環境(Windows,Mac,Linux問わず)で開発ができ,新しくチームメンバーが入ってきた場合でもこの環境を配布すれば直ぐに作業に映すことができます。 url: https://www.docker.com 個人的に推したいもの Game UI Database 様々なゲームのタイトル,セーブロード画面,ゲームオーバー画面等のUIにまつわるものが集められたサイトです。UI作る時めちゃくちゃ参考になるのでおすすめです。 url: https://www.gameuidatabase.com/index.php Codic コード書く時に変数名何にしようか迷う時あるじゃないですか,それを勝手に決めてくれる便利なサイトです。 しかもこのcodicってチームでの登録ができて,codic側から提案された変数名も編集できるので,チーム開発時の表記揺れを抑えることもできます。 例をあげると「土」という単語を変数名にする時に,「soil」にするか「dirt」にするか「ground」にするかをチーム内で決められます。 url: https://codic.jp/engine itch.io 2Dゲーム作ってる個人開発者さんにおすすめのサイトです。 他サイトと比べても結構多くの素材があります。 url: https://itch.io ArtStation Epic Gamesが運営しているデザイナーさん向けのサイトです。 ゲーム開発に使える素材も多数(というかUnityのAsset Storeよりも多い数)販売されています。 日本じゃあまり知られていないんですが海外だと有名で,世界的な超有名企業のデザイナーさんとかがポートフォリオやイラストをサイトに上げてたりして,見ていて面白いです。 url: https://www.artstation.com/?sort_by=community HackMD 個人でもチームでも使える便利なメモ帳で,マークダウン記法で書けます。 Web上にデータ保存されるのでデータが消える心配がなく,スマホでも利用できるのでとても便利です。 以下テンプレート まぁ普通にメモ書くよりは遥かに分かり易いですね。 ただ難点として,ファイルのような階層のある作りにはなりません。 その代わり,タグでジャンル分けしてあげる感じですね。 ファイルのような階層のあるメモ帳を使いたいという方は,「Notion」を利用しましょう。 機能でいうならHackMDよりNotionの方が遥かに多いので, 自分の場合は本当に短期間で消すメモのような形で使う場合はHackMD, 長期的に渡って繊細な管理をしたい場合はNotionといった感じで分けて使っています。 url: HackMD https://hackmd.io/ Notion https://www.notion.so/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

音声波形を解析しないリップシンク(Lipsync)を実装する(比較)

この記事の内容 前回の記事 音声波形を解析しないリップシンク について、備忘録を書いたものの、きちんと動作比較ができていなかったので残します。 Oculus LipSync(Oculus社) OVRLipSync ACUAH editor(自前 音声解析なしのリップシンク) 2.のソースコードも。 音声データ(WAV形式)とリップシンク用データは別途用意必要ですが、 コピペで動作すると思いますので参考まで。 1. Oculus Lipsync(Oculus社) OVRLipSync 0:08~ 2. ACUAH editor(自前 音声解析なしのリップシンク) 0:41~ ソースコード VRM の GameObject に AudioSource を AddComponent 同 AudioSource の Audio Clip に音声ファイル(WAVファイル)を設定 以下のソースコード LipSyncController.cs を VRM の GameObject に AddComponent ソースコード内の testdata は音声ファイルに合わせたデータを準備 Scene上に UI Button を配置。 OnClick で LipSyncController.OnPlayButtonClick() を実行させる using System.Collections; using System.Collections.Generic; using UnityEngine; using VRM; public class LipSyncController : MonoBehaviour { private static string locale = "ja_"; // 日本語 private Dictionary<string, float[]> lipShapeTable = new Dictionary<string, float[]>() { { "ja_a", new float[5] { 0.45f, 0f, 0.05f, 0f, 0.06f } }, { "ja_i",new float[5] { 0f, 0.28f, 0f, 0f, 0f } }, { "ja_u",new float[5] { 0.25f, 0f, 0.37f, 0f, 0.05f } }, { "ja_e",new float[5] { 0.20f, 0f, 0f, 0.12f, 0f } }, { "ja_o", new float[5] { 0.18f, 0f, 0.27f, 0f, 0.22f } }, { "ja_k", new float[5] { 0f, 0f, 0.06f, 0f, 0.28f } }, { "ja_s", new float[5] { 0.15f, 0.13f, 0.44f, 0f, 0f } }, { "ja_t", new float[5] { 0f, 0.10f, 0.39f, 0f, 0.08f } }, { "ja_n", new float[5] { 0f, 0f, 0.13f, 0f, 0f } }, { "ja_h", new float[5] { 0.12f, 0.23f, 0.21f, 0f, 0f } }, { "ja_m", new float[5] { 0f, 0f, 0.25f, 0f, 0f } }, { "ja_y", new float[5] { 0f, 0.39f, 0.19f, 0f, 0f } }, { "ja_r", new float[5] { 0f, 0f, 0.06f, 0f, 0.24f } }, { "ja_w", new float[5] { 0.19f, 0f, 0.27f, 0.05f, 0.10f } }, { "ja_p", new float[5] { 0f, 0f, 0.41f, 0f, 0f } }, { "ja_b", new float[5] { 0f, 0.01f, 0.47f, 0f, 0.04f } }, { "ja_f", new float[5] { 0f, 0f, 0.37f, 0f, 0.22f } }, { "ja_d", new float[5] { 0f, 0f, 0f, 0f, 0.21f } }, { "ja_z", new float[5] { 0f, 0.16f, 0f, 0.28f, 0f } }, { "ja_g", new float[5] { 0.12f, 0f, 0.01f, 0.05f, 0f } }, { "ja_c", new float[5] { 0.07f, 0.17f, 0f, 0.06f, 0f } } }; private AudioSource voiceSource; private int qualityType; private VRMBlendShapeProxy blendShapeProxy; void Start() { // Unity の画質設定を取得 qualityType = UnityEngine.QualitySettings.GetQualityLevel(); // VRM の BlendShapeProxy blendShapeProxy = this.GetComponent<VRMBlendShapeProxy>(); // VRM に AddComponent した AudioSource voiceSource = this.GetComponent<AudioSource>(); } void Update() { } public void OnPlayButtonClick() { // 画面上にボタンを配置して、クリック時にこれを実行させる StartCoroutine("PlayVoice"); } private IEnumerator PlayVoice() { //---------------------------- // 以下のデータを音声データに合わせて準備する // // 「おしごとおつかれさまです、なんぷんにせっとする」 音声から作成したデータ string testdata = "nn,0.14,oo,0.09,si,0.14,go,0.12,to,0.15,oo,0.10,tu,0.12,ka,0.11,re,0.13,sa,0.11,ma,0.18,de,0.13,su,0.26,nn,0.68,na,0.10,nn,0.12,pu,0.08,nn,0.12,ni,0.09,se,0.21,to,0.09,su,0.15,ru,0.21"; //---------------------------- string[] splitline = testdata.Split(','); int j = (int)(splitline.Length / 2); string[] lipSynchLetters = new string[j]; float[] lipSynchTimes = new float[j]; for (int i = 0; i < j; i++) { lipSynchLetters[i] = splitline[i * 2]; lipSynchTimes[i] = float.Parse(splitline[i * 2 + 1]) - 0.05f; // 0.05f は調整値 if (lipSynchTimes[i] < 0.01f) { lipSynchTimes[i] = 0.01f; } } StartCoroutine(PlayLipSynch(lipSynchLetters, lipSynchTimes)); voiceSource.Play(); while (voiceSource.isPlaying) { yield return null; } yield break; } //-------------------------- // LipSynch //-------------------------- IEnumerator PlayLipSynch(string[] lipSynchLetters, float[] lipSynchTimes) { // lipSyncTimes の値は PlayVoice 実行時に -0.05(秒)されている // 口の開き具合を ボリューム設定で変える。 // 日本語は音声波形データの音量でリップシンクの大きさを変えてもあまり良い効果がなかった float vv = 0.5f * voiceSource.volume + 0.5f; for (int i = 0; i < lipSynchLetters.Length; i++) { string shiin = locale + lipSynchLetters[i].Substring(0, 1); string boin = locale + lipSynchLetters[i].Substring(1, 1); // 次の子音 string nextShiin = ""; if (i == lipSynchLetters.Length - 1) { nextShiin = locale + "n"; } else { nextShiin = locale + lipSynchLetters[i + 1].Substring(0, 1); } // 画質によって変更する(Very Low, Low, Medium の場合はリミテッドっぽい表示にする) if (qualityType < 3) { // 子音 if (lipSynchTimes[i] > 0.05f) { // 子音の表示時間は0.05秒で固定 blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.A), lipShapeTable[shiin][0] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.I), lipShapeTable[shiin][1] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.U), lipShapeTable[shiin][2] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.E), lipShapeTable[shiin][3] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.O), lipShapeTable[shiin][4] * vv); blendShapeProxy.Apply(); } else { yield return new WaitForSecondsRealtime(0.05f); } // 母音 blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.A), lipShapeTable[boin][0] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.I), lipShapeTable[boin][1] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.U), lipShapeTable[boin][2] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.E), lipShapeTable[boin][3] * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.O), lipShapeTable[boin][4] * vv); blendShapeProxy.Apply(); yield return new WaitForSecondsRealtime(lipSynchTimes[i] - 0.05f); } else { // 子音 // 子音の表示時間は0.05秒で母音までの時間を見てSmoothStepで滑らかに動かす float elapsedTime = 0.0f; //経過時間 float t = 0.0f; if (lipSynchTimes[i] > 0.05) { while (elapsedTime < 0.1f) { t = elapsedTime / 0.05f; blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.A), Mathf.SmoothStep(lipShapeTable[shiin][0], lipShapeTable[boin][0], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.I), Mathf.SmoothStep(lipShapeTable[shiin][1], lipShapeTable[boin][1], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.U), Mathf.SmoothStep(lipShapeTable[shiin][2], lipShapeTable[boin][2], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.E), Mathf.SmoothStep(lipShapeTable[shiin][3], lipShapeTable[boin][3], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.O), Mathf.SmoothStep(lipShapeTable[shiin][4], lipShapeTable[boin][4], t) * vv); blendShapeProxy.Apply(); elapsedTime += Time.deltaTime; yield return null; } } else { yield return new WaitForSeconds(0.05f); } // 母音 // 母音は次の子音までの時間をSmoothStepで動かす elapsedTime = 0.0f; // 子音の時間分短くする lipSynchTimes[i] -= 0.05f; while (elapsedTime < lipSynchTimes[i]) { t = elapsedTime / lipSynchTimes[i]; blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.A), Mathf.SmoothStep(lipShapeTable[boin][0], lipShapeTable[nextShiin][0], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.I), Mathf.SmoothStep(lipShapeTable[boin][1], lipShapeTable[nextShiin][1], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.U), Mathf.SmoothStep(lipShapeTable[boin][2], lipShapeTable[nextShiin][2], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.E), Mathf.SmoothStep(lipShapeTable[boin][3], lipShapeTable[nextShiin][3], t) * vv); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.O), Mathf.SmoothStep(lipShapeTable[boin][4], lipShapeTable[nextShiin][4], t) * vv); blendShapeProxy.Apply(); elapsedTime += Time.deltaTime; yield return null; } } } // リセット blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.A), 0.0f); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.I), 0.0f); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.U), 0.0f); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.E), 0.0f); blendShapeProxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.O), 0.0f); blendShapeProxy.Apply(); yield break; } } まとめ ・OVRLipSync、自前実装 は好みの問題でしょうか。 ・自前実装はリップシンク用のデータを事前に作らないといけないので、手間ですが動作は軽量です。 (リップシンク用データは ACUAH editor で作成できます)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PCでAnimationClipの読み込みが遅い問題

問題 プロジェクトでは、アニメーションのFBXメッシュの冗長性の問題を解決するために、アニメーションを切り出す対策を講じましたが、この問題を解決すると同時に、新たな問題も発生しました。つまり、これらのアニメーションはPCでの読み込みに非常に時間がかかります。 原因 これは主にUnityのアセット管理メカニズムに関連しています。 解決 Unityについて知っておくべきいくつかのこと: 1、UnityEngine.Objectから継承されたすべてのオブジェクトは、Unityでシリアル化できます。 2、Unityのシリアル化は複数の形式をサポートします。最も一般的に使用されるのは、読みやすいYamlテキスト形式です。パッケージ化後、Unityはパフォーマンスが向上したバイナリ形式に変換されます。 Unityは、バイナリシリアル化ファイルをテキストシリアル化ファイルに変換するためのツールを提供します。 Unityのインストールディレクトリ(例:D:\Program Files\Unity2018.4.0f1\Editor\Data\Tools\binary2text.exe)にあります。 3、エディターのデフォルトのシリアル化形式はテキスト形式であり、バージョンのマージに便利です。エディターの設定で変更できます。 4、Unityのアセットインポート操作は、実際には、アセットソースファイルに従ってUnityに対応するObjectオブジェクトを生成し、それをLibraryディレクトリにシリアル化することです。Unityによって認識されるすべてのアセットがインポートされると、UnityはUnityの内部オブジェクト(Mesh、Texture、AnimationClip…)に変換されます。 5、Unityのアセットの読み込みは、実際にはこれらのシリアル化されたファイルを逆シリアル化することです。 6、UnityはFBXをPrefabとして扱います。 7、インポートされたアセットファイルごとに、UnityはGUIDを生成し、このGUIDを同じ名の.metaファイルに保存します。例: 8、このGUIDを使用して、アセットファイルに対応するUnityオブジェクトのシリアル化ファイルを見つけることができます。方法は次のとおりです。上記のように、GUIDの最初の2桁を取得します。例えば、上記のように、プロジェクトディレクトリの「Library\metadata」で「ae」と呼ばれるディレクトリを検索し、そしてGUIDと呼ばれるバイナリシリアル化ファイルが検索できます。 次に、この問題を再現するために、実験を行いましょう。 1)最初にアニメーション付きの4つのFBXファイルを準備し、次にProjectパネルでCtrl + Dを押すとFBXのアニメーションが1つずつ切り出されます。 2)2つのPrefabを作成し、それぞれfbx_animとstd_animという名を付けます。 fbx_animのAnimationコンポーネントは、4つのFBXのAnimationClipを参照します。 std_animのAnimationコンポーネントは、4つの切り出されたアニメーションファイルを参照します。 3)テストスクリプトを作成し、ゲームを起動してテストします。 4)バイナリ形式とテキスト形式が読み込みパフォーマンスに与える影響を理解するために、2つの形式で別々に実験を行い、各実験には10グループの読み込み時間データ(単位:ms)を抽出します。比較は次のとおりです。 テキスト形式の場合、切り出されたアニメーションの読み込み時間はFBXの50倍以上であるのに対し、バイナリ形式の場合、平均時間はFBXの約2倍であることがわかります。 Q:FBXからAnimationClipを切り取った後、ロードに時間がかかるのはなぜですか? 1)上記のanim_1.animを例にして見ましょう。まずUnityのLibraryには、切り出されたAnimationClipがどのようになるか確認しましょう。 そして、コマンドを実行して、anim_1.animに対応するシリアル化されたファイルをテキスト形式に変換します。 開いた後、このシリアル化されたファイルがアニメーションの曲線データを保存していないことがわかります。代わりに、アニメーションソースファイル「Assets / Animatoins /anim_1.anim」を指します。 2)次に、anim_1.fbxに対応するシリアル化されたファイルを見つけます。 ご覧のとおり、AnimationClipオブジェクトのシリアル化されたデータを格納する曲線データ、およびその他のオブジェクトのシリアル化されたデータ(Mesh、GameObject ...)が含まれています。 非AssetBundleモードを使用してPCで実行しますと、プロジェクトのアセット読み込みインターフェイスは、UnityのAssetdatabase.LoadAssetAtPathを使用しています。上記から、AnimationClipがFBXにある場合は、シリアル化されたバイナリファイルが読み込まれ、.animの独立した形式として存在する場合、Unityはシリアル化されたバイナリファイルではなくアニメーションソースファイルを直接読み込むことが分かりました。Unityのデフォルトのシリアル化形式の設定はテキスト形式であるため、非常に遅くなります。 Q:では、Unityが切り出されたアニメーションを最終形態のバイナリシリアル化形式に保存しないのはなぜですか? A:これは、FBXのAnimationCipが読み取り専用であるのに対し、独立した.animファイルは編集可能であるためです。Unityが.animファイルを最終形態のAnimationClipオブジェクトシリアル化ファイルにリアルタイムで変換する場合、編集の体験が悪くなるかもしれません。そのため、Unityはパッケージ化時にのみAnimationClipオブジェクトのシリアル化ファイルに変換します。 Q:では、この独立したアニメーションソースファイルと最終的なアニメーションファイルの違いは何ですか? A:上記の方法に従って、anim_1.fbxに対応するシリアル化されたファイルを見つけ、AnimationClipのシリアル化されたデータをコピーして別のファイルに入れ、anim_1.txtという名を付け、anim_1.animとDiff比較を実行します。 ご覧のとおり、両者にはいくつかの変数は同じですが、曲線データの構成形式は完全に異なります。アニメーションソースファイルのデータ構成形式は編集にさらに役立ち、最終リリースバージョンのAnimationClipはランタイムにさらに役立ちます。ソースファイルを直接ロードするには、データ形式の変換が必要で、これはCPUを消費します。 独立したアニメーションファイルの読み込みは遅くなるもう1つの重要な理由は、.animファイルをAnimationエディタで開いて変更して保存した後、Unityがエディタでのみ使用される大量のデータを挿入することです。 Animationエディタで編集した後、Unityは大量のデータを挿入し、ファイルサイズは元のサイズのほぼ4倍であることがわかります。これらのデータはエディターでのみ使用され、パッケージ化後に削除されますが、エディターでゲームを実行すると、これらのデータによってアニメーションファイルの読み込みが遅くなります。次の一連の実験データは、この結論を検証することができます。 小さな変更を加えた後、4つの切り出されたアニメーションを保存し、上記のテスト環境を使用して一回のテストを実行します。得たデータは次のとおりです。 実験データから、エディターで開いて保存したアニメーションファイルの読み込みが遅くなることがわかります。テキスト形式の場合、独立したアニメーションファイルのロード時間はFBXの150倍以上に達しました。バイナリ形式の場合、平均ロード時間はFBXのほぼ3倍です。 Q:では、この問題をどのように解決するのですか? A:上記の実験データから、シリアル化形式が.animアニメーションファイルの読み込み速度に最大の影響を与えることが分かりました。 アニメーションファイルをバイナリ形式で設定すると、この問題が大幅に軽減されます。ただし、プロジェクトのシリアル化形式を「Force Binary」に直接設定するだけでは不十分です。実際のプロジェクト開発では、常に複数の人が同じアセットファイルを変更する状況に置かれます。強制的にバイナリ形式に変更し、変更をマージする時に、とても面倒なことになってしまいます。また、Unityには、特定のアセットをバイナリ形式で設定するだけのオプションはありません。 Unityが提供するもう1つのオプションは「Mixed」です。このオプションは、プロジェクト内の既存のシリアル化形式を保持し、新しく作成またはインポートされたアセットはバイナリ形式を使用します。これも、この問題の良い解決策ではないようです。これは確かにジレンマであり、ちょっと解決できないようです。 一つ考えられる方法は、同じプロジェクトの2つのコピーをローカルに保存することです。一つはエディターでの開発とテストのためにバイナリ形式に設定され、もう一つのプロジェクトはバージョンライブラリと同期されます。ローカル開発が完了したら、差分ファイルを同期プロジェクトにインポートして提出します。(プロジェクトがエディターにゲームをロードする場合、それはすでに耐えられないほど遅ければ、その方法を検討することができます。) まとめ アニメーションがFBXから独立した.animファイルにカットされた後、これは「読み取り専用アニメーション」から「読み取り・書き込みアニメーション」へのプロセスです。 独立したアニメーションファイルと最終的にリリースされたアニメーションファイルのデータ形式は異なり、Unityはパッケージ化時に変換します。エディターでゲームを実行して、FBXでのアニメーションファイルを使用する場合、Unityは最終リリースしたアニメーションシリアル化データを直接ロードします。これらのデータはFBXがプロジェクトにインポートされた時に生成されるため、非常に高速です。独立した.animファイルを使用している場合、Unityはアニメーションソースファイルをロードします。これにはデータ形式の変換が必要です。テキスト形式のシリアル化されたファイルも、逆シリアル化時に非常に遅くなます。それに、Animationエディターでアニメーションを編集して保存した後、Unityはまた、このアニメーションファイルに元のデータ量の3倍を挿入します。アニメーションファイルの読み込み速度の足を大幅に引っ張ります。 プロジェクトリンク:https://pan.baidu.com/s/1CYCNWw2TZLgtKWNUY5AuAA 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で続きを読む

Instruments でMonoメモリ割り当てを確認する方法

今回の主な話題: 1)InstrumentsでMonoメモリ割り当てを確認する方法 2)Addressable v1.11.2に関する疑問 3)UV2の展開によって引き起こしたMeshの頂点数の増加 4)Unityエディターでのコードのコンパイル速度の向上 5)Renderdocのデバッグに関する疑問 Memory Q:たとえば、10MBのアレイが割り当てられると、少なくとも10MBのMonoメモリがUnity Profilerで開かれます。 では、Instrumentsで、割り当てられたメモリ情報をどのように確認しますか?Allocationsの情報は、このプロセスで割り当てられたすべてのメモリ情報ですか?100MBのメモリを割り当てようとしましたが、Allocationsの統計は増加しませんでした。 A:こちらでもテストしました: 100MBのintアレイを作成しました。実際のSizeは400MBであるはずです。 次に、Profileに移動して次のことを確認します。 ManagedHeapが400MBのスペースを正しく割り当てていることがわかります。 それに、iOSをパッケージ化し、Xcodeで実行します。実行する前に、まず、RunというSchemeのMallocStackを選択します。 RUNした後、「Memory」をクリックし、メモリグラフMemory Graphをエクスポートして次のことを確認します。 アプリのメモリはVirtualMemoryスペースに割り当てられているため、VM RegionsのVM_ALLOCATE部分を確認してください。 128X3 +16というちょうど400MBの割り当てはであることがわかります。 コールスタックも非常に簡単に判別できます。 これがテストコードです。 次に、Instrumentsについて見ていきます。まずはAllocationsの部分です。下にはいくつかのオプションがあることに注意してくだいさい。 最後のオプションに注意してください。一番目のオプションを選択する場合は、 All Heap&Anonymous VM、All HeapはAppの実際に割り当てられた物理スペースに対応します。VMが含まれません。それに対し、Anonymous VMの公式な説明は次のとおりです: interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions。 したがって、比較的大きな予約済み割り当てスペースは表示されません。 このオプションを「All VM Regions」に切り替えると、割り当てられた400Mが表示されます。 また、右側の詳細ページには、コールスタックも正しく表示されます。 さらに、VM Trackerからも観察できます。 VMTrackerのSnapshotsを開きます。 このように、400MBの詳細な割り当て情報を確認できます。 プログラムの他の部分もメモリを適用する必要があるため、Virutal Sizeは400MBよりわずかに大きいことがわかります。400MBはそれぞれResidentとSwappedに格納されます。Resident部分は基本的にDirty Sizeと同じであり、スペースのこの部分がDirtyとしてマークされ、スワップアウトできないことを示します。残りの約240MBは、使用するのに十分な物理スペースがあることを保証するために、一時的にスワップアウトできます。これは、スペースのこの部分を申し込み、特定の賦値の初期化と使用を実行しなかったためでもあります。 賦値が使用された場合はどうなりますか?コードテストを修正して、 Instrumentsを実行した後に観察します。 この400MBはすべてDirty Size内にあることがはっきりとわかります。この状況は本当に、このAppとiOSにメモリストレスをかけます。 推奨読書: 《写给Unity开发者的iOS内存调试指南》(中国語注意) 《Understanding iOS Memory (WiP)》 Addressable Q:关于Addressable v1.11.2开始编辑器在“Fast Mode”模式下运行会获取SubAsset失败的问题。 A:今日は、Addressablesシステムを使用してプロジェクトを1.8.4から1.14.2にアップグレードしました。AssetReferenceが指すアセットがインスタンス化する時、常に現れた「無効なKey」エラーに気づきました。調査の結果、1.11.2から、FastModeをさらに加速するために、公式によってプロセスの修正が行われたと発見しました。 以前のバージョンでは、たとえ「Fast Mode」であってもBuildが必要であり、Buildによって生成されたCatalogがPlay時に読み取られました。Catalogは、すべてのAssetEntryとSubAssetをシリアル化し、ResourceLocationMapオブジェクトを生成してから検索します。SubAssetを指すため、多くのAssetReferenceが使用されています。現時点では問題ありません。 バージョン1.11.2以降、Fast Modeは、Catalogファイルパスではなく、エディター環境でAddressableAssetSettingsオブジェクトのGUIDを直接提供します。したがって、FastMode専用の初期化操作は、Play後の初期化中に有効になります。この時に、ResourceLocationMapではなく、AddressableAssetSettingsLocatorが生成されました。このLocatorは、 エディターGroupウィンドウが対応する表示可能なGroup内のAssetEntryをトラバースしただけで、AssetEntry内のSubAssetはトラバースされないため、ゲームでSubAssetを使用すると、無効なKeyエラーが報告されます。 公式Changelog: Refactored Play Mode Script for "Use Asset Database" to pull data directly from the settings. This reduces the time needed to enter play mode. 提案: 1、一時的にv1.8.4バージョンのみを使用します(プロジェクトに長く使用され、比較的安定しています) 2、1.9.2から1.11.2までのバージョンの使用はお勧めしません。他のbugもあります。 3、最新の状況にアップグレードし、Simulate Groupsを使用して開発します 4、公式からの改善を待ちます Rendering UnityでUV2(Generate Lightmap UVs)を自動展開すると、個別のオブジェクトのMesh内の頂点の数が増加します。 自動的に展開した地面の場合、頂点の数が134から136に変更されました。UV2を展開しないなら問題ありません。頂点の数を増やさずにUV2を展開する方法はありますか? FBXモデルをインポートする場合、「Meshを最適化する」およびその他の頂点に影響を与えるオプションを選択されていません。 A:2つのメッシュがLightmapの2つの不連続領域に対応する場合があり、2つのメッシュに頂点が共有される可能性があるため、2つの頂点に分割する必要があります。他のデータは同じですが、UV2が異なります。これは正常です。 頂点を増加させたくないなら、アーティストが手動でUV2を設定してエクスポートする限り実現可能です。それでも、モデルが一部のメッシュは囲まれて閉じている場合、頂点が重ならないことを保証するのは難しいです。実際、現時点では、頂点はすでにMayaなどのツールに追加してエクスポートされました。Unityに変化が見えないかもしれません。 Build Q:Unityエディターでコードのコンパイル速度を向上させる方法はありますか?現在コードを変更するたびに、コンパイルの待機時間は30分近くになります。 A1:大規模なプロジェクトにとって、これは確かに頻繁に遭遇する状況です。一般的に、UnityEditorはスクリプトの依存関係に従ってコードをコンパイルします。これは主に次の4つのステップに分けられます。 1、Standard Assets、Pro Standard AssetsとPluginsフォルダーにあるRuntime Scriptをコンパイルします。 2、上記の3つのフォルダーにおけるEditorフォルダーの下にあるScriptをコンパイルします。 3、プロジェクト内の残りのすべてのRuntime Script(Editorフォルダー以外のScript)をコンパイルします。 4、残りのScript(つまり、Editorフォルダー内のScript)をコンパイルします。 Unityエディターのスクリプトコンパイル機能を理解した後、開発チームは、長期間変更する必要のないいくつかのスクリプトコード(例えば、さまざまなプラグインコードなど)をStandard Assets、Pro Standard Assets またはPluginsフォルダーに入れることをお勧めします。これなら、これらのコードは一度だけコンパイルする必要があり、その後の時間を節約できます。 あるテストでは、プロジェクトで上記の変更を行った後、元のプロジェクトのコンパイル時間が23秒から7秒に短縮されました。これはかなりチームのコンパイル時間を節約します。 A2:アセンブリ定義を追加します: https://docs.unity.cn/cn/2020.2/Manual/ScriptCompilationAssemblyDefinitionFiles.html(中国語注意) A3:プロジェクトを分割し、異なるDLLにコンパイルします。Unity2017以降、エンジン付きのツールを使用して、さまざまなプロジェクトに定義できます。 Rendering Q:Pixel Contextが灰色です: Debug Vertexも灰色です。 これはなぜでしょうか? A1:ピクセルデバッグ用のShaderに#pragmaenable_d3d11_debug_symbolsを追加します。 A2:公式ドキュメントには、D3D11またはD3D12でしかデバッグできないと記載されています。 https://renderdoc.org/docs/how/how_debug_shader.html#hlsl-debugging 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で変身エフェクトの模索

作ったモノ 今さら感満載ですが、昔作った変身エフェクトの模索を公開してみます。 環境 Unity 2019.x 処理の流れ 2つのモデルを用意します それぞれを逆方向にディゾルブさせます(クロスディゾルブ)。 黄色のオブジェクトは、表示から非表示にします(完全に覆い隠すので、今回はこちらは不要でした)。 黒いオブジェクトは、 非表示から表示にします。 私が、唯一行ったことは、ディゾルブするマップを作ったことです。 作ったテクスチャ この3番目のディゾルブさせるテクスチャですが、3分ぐらいで適当に描いた落書き画像で、なんかかっこよく見えて、お得だったので紹介します。 Unityが、すごいだけなのですが。 数時間でサクッとこんなことが無料でできる時代に生まれて、とっても幸せだと感じました。 ソースコードなど ディゾルブの処理は、高橋 啓治郎さんが動画とテキストで、かなり詳しく解説されています。(下部に列挙) 以下のgithubのURLでソースコードを公開しています。 クロスディゾルブするように変更しました。 ディゾルブ処理についての解説 高橋 啓治郎さんの動画 高橋 啓治郎さんのgist Brackeys さんの動画(英語)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity設計入門:第2章「MVXの思想」理論編その1

Unity設計入門の目次はこちら はじめに 前回、ModelとViewがそれぞれ何者であるかに加えて、それらを分離することの利点について書きました。 しかし、現実問題としてModelとViewを分離することには大きな困難を伴います。 今回は、なぜModelとViewがキレイに分離できないのか、具体的な原因の特定を目指します。 ModelとViewが分離できない状況 1. 表示制御のためのデータが増加する どのようなデータが「表示制御のための一時データ」に該当するか 複雑なゲーム画面を組み立てていくと、ModelでもViewでもないデータが増加します。 これらのデータは「サンプルコードではシンプルすぎて出てこないので解説されにくいけれど、現実的にゲームを組むとどうしても出てきて扱いに困る」という厄介な存在です。 これらのうち、具体的には以下のようなものを「表示制御のための一時データ」と呼ぶこととします。 アニメーションの秒数を管理するためのフラグ マウスを早く動かしたときにのみ動かす演出のために、マウス座標の差分をとっておくための変数 スナップするスクロールビューにおいて、現在どこのページにいるかを保存しておく変数 UIの入力モードが複数あるときに、そのどれであるのかを保存しておく変数 このようなデータは、業務用のソフトウェアでは無視できるほど少ないでしょう。ほとんどが演出用であり、省いてしまえばよいのです。しかし、ゲームは演出です。これらの変数が莫大に増加することがほとんどなのです。 さて、これらのデータをどこに記述していくのかについて考えるとなんとModel・Viewのどちらにも書けないということが分かります。 Model側に記録した場合の問題点 Modelにアニメーション状況や表示可否などの情報を記憶させる方式です。 Modelが画面に依存してしまいます。 Modelが画面のレイアウト・演出に合わせて変更に迫られるということです。 これはViewの柔軟性を損なうとともに、Modelの一部のテストにViewが要る状況を生んでしまいます。 これは前回の原則「Modelは純粋にする」に反するコードです。 View側に記録した場合の問題点 Modelから受け取った情報とViewが持っている情報を合わせて演出を出す方式です。 Viewに条件判断が増え、肥大化します。 Unityの起動回数は増え、面倒なテストを何度もする羽目になります。 これは前回の原則「Viewは極限までシンプルにする」に反するコードです。 2. シンプルなViewから受け取る入力は低レベルすぎる ユーザー入力はUnity、すなわちViewを通して受け取らざるを得ません。そしてユーザーはViewを通してModelを操作しようとするのです。小さなサンプルコードだけでは前回のように「ボタンを押して、画面に表示してハッピーだね!」というようになって終わりなのですが、ユーザーの操作は ダブルクリック判定をするために、「直前15フレーム以内にクリックがあったか」を保存しておく変数 3回のボタンクリックをすることで初めて意味を持つ入力 カードをドラッグアンドドロップして別の場所に置くときの入力 など多岐にわたります。そしてこれらの入力は、意味を持つタイミングが「複数のデータ・一次変数から複合的に判断される」という特徴を持ちます。これはボタンによる入力が「押された瞬間に意味が確定する」ものであったこととは対照的です。 さて、それでは上に挙げた例の「直前15フレームの入力の有無」「ボタンクリックが現在何回されているか」「ドラッグ中か否か」などの情報はどこに保存すればいいのでしょうか? じつはこれもModel・Viewどちらにも書けないのです。 Model側に記録した場合の問題点 Modelが「画面の(x,y)がクリックされたときに呼ぶ関数」などを用意して、低レベル情報を直接受け取る方式です。 この場合、同じくModelがViewを気にしてしまうためModelの純粋性を損ないます。 さらに、この手の関数をテストしようとするとテストケースに「(23,455)をクリックしたときの関数を呼ぶ→マウスの座標(22,444)に移動したときの関数を呼ぶ」と言ったように非常に低レベルなものになります。この(23,455)とかの座標は結局Viewを見ないとダメですし、こんなテストを書くぐらいなら、Viewを起動して動作確認するほうがよっぽど早いですね。つまりこれは、「Modelがテストを書くことを容易にする」というメリットを完全に破壊しているのです。 View側に記録した場合の問題点 Viewが「どこをクリックされたか」などの情報をためておき、ある程度たまったタイミングで、画面から離れた抽象的な意味を持つUI操作としてModelの関数を呼び出す方式です。 この場合、Viewが大きくなり、「Modelの関数がちゃんと呼ばれるか」の検証が非常に面倒です。 Viewはシンプルにするという原則に反するコードです。 3. 「ModelとViewの責務」という言葉を曖昧に使っている これは1,2とは異なり、構造的にどうしようもないもの、といえるものではありません。 Viewを見た目だ!ぐらいのぼんやりしたイメージ、Modelをデータの入出力だ!ぐらいのぼんやりしたイメージで組み立てると、往々にしてViewがModelの処理を奪い取ります。(ModelがViewを奪うことはあまりありません。というのも、Viewは最小限の状況が一番いい状況だからです) また、View・Modelの2人体制だと、ViewはModelを参照できるわけですから、Modelをpublic変数から好き勝手操作できてしまいます。 結果としてModelとViewの密結合を生みやすくなります。 ModelとViewの原則、「純粋性」と「シンプルさ」を強く意識したコーディングである程度は防げますが、根本的にModelへの参照をなくしたり、疎結合にしたりする策を講じないとView側でいじってしまうという問題はなくなりません。 おわりに 経験的には、「サンプルコードで書けてるようにキレイにならない!!なんで!!」という叫びの原因のうち、大部分はここにあると思います。また、漠然とViewとModelとX(ControllerなりPresenterなりViewModelなり)を切り分けたいというだけの思考でMVXの設計をするのではなく、「どうしようもないからXへ持っていく。ModelとViewは明確な原則に従って設計する」の方がよい設計になることが多いと思います。次回、今回上げた問題の解決策を探していきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む