20220225のUnityに関する記事は8件です。

Unity+Android+OAuth認証の実装方法の紹介

1. 前置き AndroidでOAuthを実装するにあたり、専用ライブラリを使うことが最も簡単な方法です。しかし、当たり前ですが、Android向けのライブラリなので、Unityでは使えません。本稿はUnityでAndroid向けのOAuthを実装する手順を紹介するものです。また、例として、GoogleのOAuthを取り上げます。 2. 流れの概要 AndroidでGoogleのOAuthを使用する流れは次の通りです。 Custom Tabsを開く Googleの認証画面を表示する Google認証のリダイレクトを受け取る リダイレクトで得られたトークンを使い、ユーザー情報を取得する 下記のリソースを使えば、実装がより簡単になります。 2.1. リソースの紹介 2.1.1. IdentityModel.OidcClient IdentityModel.OidcClientはC#向けのOpenID Connect認定済みのOAuth SDKです。netstandard14、デスクトップ.N​​ET、UWP、.NET Core、Xamarin iOS、Androidをサポートしています。 UnityはJavaやKotlinではなくC#を使用するため、AppAuth for AndroidなどのJavaベースのOAuth SDKをそのまま使用することができません。IdentityModel.OidcClientなら使用できます。 IdentityModel.OidcClientを使えば、Google Playサービスに依存せずにUnityのAndroid向けのゲームにGoogleアカウント認証を実装できます。 リンク: https://github.com/IdentityModel/IdentityModel.OidcClient/tree/releases/2.9.x 2.1.2. Json.Net.Unity3D Json.Net.Unity3DはUnityでJSONを使うためのSDKです。IdentityModel.OidcClientではJSONを使うため、Json.Net.Unity3Dが必要です。 リンク: https://github.com/SaladLab/Json.Net.Unity3D/releases 2.1.3. unity-jar-resolver unity-jar-resolverはCustom Tabのライブラリをダウンロードするためのライブラリです。 リンク: https://github.com/googlesamples/unity-jar-resolver/tree/release/1.2.169 2.1.4. Unity3D.Authentication.Example Unity3D.Authentication.ExampleはIdentityModel.OidcClientのサンプルです。このサンプルにAndroidUnityPluginが含まれています。OAuthの認証結果は、リダイレクトを通してカスタムタブからアクティビティに戻ります。 AndroidUnityPluginはリダイレクト結果を受け取り​​Unityに返します。 リンク: https://github.com/peterhorsley/Unity3D.Authentication.Example 3. 実装方法 以下はGoogleのOAuthの実装例です。 3.1. 前準備 下記のリソースをダウンロードしておきます。 IdentityModel.OidcClient: Json.Net.Unity3D: unity-jar-resolver: Unity3D.Authentication.Example: 3.2. Custom Tabsのライブラリのインポート 3.2.1. unity-jar-resolverのexternal-dependency-manager-1.2.169.unitypackageのインポート 3.2.2. ExternalDependencies.xmlの追加 Assets\ExternalDependencyManager\EditorにExternalDependencies.xmlを追加します。 ExternalDependencies.xmlに下記のソースコードを追加します。 ExternalDependencies.xml <dependencies> <androidPackages> <repositories> <repository>https://repo.maven.apache.org/maven2</repository> </repositories> <androidPackage spec="androidx.browser:browser:1.4.0"> <androidSdkPackageIds> <androidSdkPackageId>extra-google-m2repository</androidSdkPackageId> </androidSdkPackageIds> <repositories> <repository>https://maven.google.com</repository> </repositories> </androidPackage> </androidPackages> </dependencies> androidx.browser:browserの最新バージョンはここでご確認ください。 https://developer.android.com/jetpack/androidx/releases/browser 3.2.3. Custom Tabsのライブラリのダウンロード [Asset] > [External Dependency Manager] > [Android Resolver] > [Force Resolve]を実行します。 ダウンロードが完了したら、Custom TabsのライブラリはAssets/Plugins/Andriodに保存されます。 3.3. IdentityModel.OidcClientのインポート 3.3.1. IdentityModel.OidcClientのプロジェクトのビルド 3.3.2. net452フォルダの中の全ファイルをUnityのプロジェクトのAssets\IdentityModelにコピー ただし、System.Runtime.InteropServices.RuntimeInformation.dllをコピーしません。 3.4. JsonNet.9.0.1のインポート 3.4.1. JsonNet.9.0.1.unitypackageのインポート 3.4.2. Newtonsoft.Json.dllの移動 Assets\UnityPackages\JsonNet\Newtonsoft.Json.dllをAssets\IdentityModelに移動させます。 3.4.3. link.xmlの作成 Assetsにlink.xmlを新規作成し、下記の内容を追加します。 link.xml <linker> <assembly fullname="System"> <type fullname="System.ComponentModel.TypeConverter" preserve="all"/> <type fullname="System.ComponentModel.ArrayConverter" preserve="all"/> <type fullname="System.ComponentModel.BaseNumberConverter" preserve="all"/> <type fullname="System.ComponentModel.BooleanConverter" preserve="all"/> <type fullname="System.ComponentModel.ByteConverter" preserve="all"/> <type fullname="System.ComponentModel.CharConverter" preserve="all"/> <type fullname="System.ComponentModel.CollectionConverter" preserve="all"/> <type fullname="System.ComponentModel.ComponentConverter" preserve="all"/> <type fullname="System.ComponentModel.CultureInfoConverter" preserve="all"/> <type fullname="System.ComponentModel.DateTimeConverter" preserve="all"/> <type fullname="System.ComponentModel.DecimalConverter" preserve="all"/> <type fullname="System.ComponentModel.DoubleConverter" preserve="all"/> <type fullname="System.ComponentModel.EnumConverter" preserve="all"/> <type fullname="System.ComponentModel.ExpandableObjectConverter" preserve="all"/> <type fullname="System.ComponentModel.Int16Converter" preserve="all"/> <type fullname="System.ComponentModel.Int32Converter" preserve="all"/> <type fullname="System.ComponentModel.Int64Converter" preserve="all"/> <type fullname="System.ComponentModel.NullableConverter" preserve="all"/> <type fullname="System.ComponentModel.SByteConverter" preserve="all"/> <type fullname="System.ComponentModel.SingleConverter" preserve="all"/> <type fullname="System.ComponentModel.StringConverter" preserve="all"/> <type fullname="System.ComponentModel.TimeSpanConverter" preserve="all"/> <type fullname="System.ComponentModel.UInt16Converter" preserve="all"/> <type fullname="System.ComponentModel.UInt32Converter" preserve="all"/> <type fullname="System.ComponentModel.UInt64Converter" preserve="all"/> </assembly> </linker> 3.4.4. mcs.rspの作成 Assetsにmcs.rspを新規作成し、次の内容を追加します。 -r:System.Net.Http.dll 3.5. Googleの認証画面の表示 3.5.1. 次のソースコードをUnityプロジェクトに追加 3.5.2. GoogleのOAuthの設定の追加 OAuthLoginScript.csを開き、GoogleのOAuthの設定を追加します。 Client IdはGoogle Cloud Platform > [APIとサービス] > [認証情報] > [OAuth 2.0 クライアント ID] > [クライアント ID]です。また、Client Idを作るときに、アプリケーションの種類をAndroidに設定しなければなりません。 ScopeはGoogle Cloud Platform > [APIとサービス] > [OAuth 同意画面]に設定したスコープです。 Package nameはProject Settingsに設定したパッケージ名で、Pathはなんでも大丈夫です。 3.5.3. Googleの認証画面の表示 GoogleIdLogin()を実行すれば、Googleの認証画面はCustom Tabsの中で開かれます。 3.6. AndroidUnityPluginのインポート Googleの認証結果はリダイレクトを通じて返ってくるので、Unityプロジェクトにリダイレクト結果を受け取るプラグインを追加しなければなりません。 3.6.1. AndroidUnityPluginをUnityプロジェクトにコピー Unity3D.Authentication.Example-master\AndroidUnityPluginを自分のUnityプロジェクトにコピーします。 3.6.2. Unity Editorのclasses.jarをAndroidUnityPluginにコピー C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes\classes.jarをAndroidUnityPlugin/app/libsにコピーします。 3.6.3. AuthRedirectActivity.javaの編集 AndroidUnityPluginのAuthRedirectActivity.javaを開きます。 UnityPlayer.UnitySendMessageの最初のパラメータを、OAuthLoginScriptのターゲットに書き換えます。たとえば、OAuthLoginScriptのターゲットがCanvasであれば、UnityPlayer.UnitySendMessageの最初のパラメータをCanvasに書き換えます。 3.6.4. app:exportJarの実行 gradleのapp:exportJarを実行します。実行したら、Assets\Plugins\AndroidにAndroidUnityPlugin.jarが生成されます。 3.6.5. AndroidManifest.xmlの編集 Project SettingsのPlayerにあるCustom Main Manifestをチェックします。 Assets\Plugins\AndroidにあるAndroidManifest.xmlを開き、次のソースコードを追加します。 AndroidManifest.xml <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <activity android:name="org.identitymodel.unityclient.AuthRedirectActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="{Your package name}" /> </intent-filter> {Your package name}は自分んおアプリのパッケージ名です。 3.7. 各Tokenについて OAuthLoginScriptの中で次のメソッドを実行すれば、アクセストークン、リフレッシュトークン、IDトークンが取得できます。 _authClient.GetAccessToken() _authClient.GetRefreshToken() _authClient.GetIdToken() 3.8. ユーザー情報について OAuthLoginScriptの中で _authClient.GetPayload({field}) を実行すれば、IDトークンの中のユーザー情報が取得できます。 fieldは次のものになります。 データ field Open Id sub 姓名 name 姓 family_name 名 given_name 画像URL picture メールアドレス email 有効なメールアドレスかどうか email_verified GitHub
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity Inspectorの配列変数に複数ファイル一度で登録する

初めに 配列変数で一度にスプライトを登録したい。 けど。一つずつ登録してはめんどうだ。。 って事で色々調べてみた。 Unity Editor機能がすげ。。。(結論) 元から機能がありました。 簡単に言うと、登録したいものを複数選択してドラックアンドドロップするだけでした。。 ただ、ちょっと面倒なので。。。ちょっと説明 Projectウィンドウでオブジェクトを選択すると、インスペクタが変わってしまいます。 なので、スクリプトが設定されているオブジェクトををロックします。 ロックされたオブジェクトにスプライト複数選択して、ドラックアンドドロップ! 最後に 今回は、何かプログラムも組むまでも無く、Unity Editorの機能をうまく使う方法でした。 まだ、知らない機能がたくさんありそうです。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ShaderLabの使用に関する質問

今回の話題: 1)ShaderLabの使用に関する質問 2)AndroidでのARM64とARMV7に関する質問 3)ILRuntime関連のパフォーマンス検出ツールについて 4)フォントの読み込みの問題 5)LZ4圧縮モードでのアセットパッケージ ##Shader Q:公式サイトから内蔵Shaderをダウンロードし、インターフェイスのマテルアルが内蔵Shaderを参照していないことを確認し、Standard Shaderもクリアしました。 メモリからはそのようなことはありませんが、ShaderLabの占有率はまだ少し大きく、メインシーンでは47MBになっています。 A1:バリアントが多すぎるので、キーワードが多いShaderをよく確認してください。 A2:A1の通りです。数年前に作成されたスキャンプロジェクトのShaderバリアントのコードを共有し、スキャンして、Top数個のShaderを変更して大幅に削減します。 [MenuItem("Find/GetAllShaderVariantCount", false, 20)] public static void GetAllShaderVariantCount() { #if UNITY_5_6 Assembly asm = Assembly.LoadFile(@"D:\Program Files\Unity_5.6.5f1\Editor\Data\Managed\UnityEditor.dll"); System.Type t2 = asm.GetType("UnityEditor.ShaderUtil"); MethodInfo method = t2.GetMethod("GetComboCount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); #elif UNITY_2017_1_OR_NEWER Assembly asm = Assembly.LoadFile(@"D:\Program Files\Unity_2018.3.0f2\Editor\Data\Managed\UnityEditor.dll"); System.Type t2 = asm.GetType("UnityEditor.ShaderUtil"); MethodInfo method = t2.GetMethod("GetVariantCount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); #endif var shaderList = AssetDatabase.FindAssets("t:Shader"); var output = System.Environment.GetFolderPath(System.Environment.SpecialFolder.DesktopDirectory); string pathF = string.Format("{0}/ShaderVariantCount.csv", output); FileStream fs = new FileStream(pathF, FileMode.Create, FileAccess.Write); StreamWriter sw = new StreamWriter(fs, Encoding.UTF8); EditorUtility.DisplayProgressBar("統計ファイルの書き込み", "統計ファイルを書き込み中... ", 0f); int ix = 0; sw.WriteLine("ShaderFile,VariantCount"); foreach (var i in shaderList) { EditorUtility.DisplayProgressBar("統計ファイルの書き込み", "統計ファイルを書き込み中... ", 1f * ix / shaderList.Length); var path = AssetDatabase.GUIDToAssetPath(i); Shader s = (Shader)AssetDatabase.LoadAssetAtPath(path,typeof(Shader)); var variantCount = method.Invoke(null,new System.Object[]{ s,true}); sw.WriteLine(path + ","+variantCount.ToString()); ++ix; } EditorUtility.ClearProgressBar(); //プログレスバーをクリア sw.Close(); fs.Close(); } A3:URPには一連のバリアントトリミングコードがあり、それを変更し、パッケージングプロセスに追加して、不要なバリアントをトリミングします(キーワードをあまり使用しないため、大幅に削減する必要があります)。 A4:UWAのローカルアセット検出を使用して、どのShaderにさらにバリアントがあるか、バリアントの問題であるかどうかを確認してから、Shaderに冗長性があるかどうかを確認してください。 ##Build Q:AndroidでのARM64とARMV7の問題に関して、経験から、同じUnityゲームでこれら2つのアーキテクチャの実行効率に違いはありますか?レンダリング効率はCPUアーキテクチャの影響を受けますか? A:ARM64は、メモリ割り当て中の不十分なアドレス空間とアドレスの競合によって引き起こされるメモリ割り当て障害の多くの問題を解決できます。典型的な例は、QualcommのAdrenoのビデオメモリの不足、または辞書タイプの広範な使用によって引き起こされるメモリリークの問題です。ただし、付随する問題も発生します。同じパッケージの64ビットは32ビットよりも大きいです。メモリの要件が厳しいゲームの場合、メモリの最適化はすでに面倒なことであるのに、64ビットになったらさらに面倒になってしまいました。効率の問題については、テストも比較もしていませんが、FPS、温度、エネルギー消費量などの違いを測定することをお勧めします。 ##ILRuntime Q:UWAはすでにLuaのパフォーマンス分析をリリースしています。新しいプロジェクトでは、代わりにILRuntimeを使用し、AwaitとAsyncでネットワーク通信の「同期」コードを実現します。バトルパートのロジックコードもILRuntimeでの実装を計画しています(強CPU計算は可能な限りC#で実装されます)が、パフォーマンスについては少し懸念があります。パフォーマンス分析ツール、特にC#との対話型の部分がある場合は、初期段階で技術的なフレームワークを設定することができれば安心です。 さらに、ILRuntimeによってオンラインで正常に適用されたプロジェクトはありますか?それに、安定性の観点から、Luaを引き継ぐことができますか? A1:日常使用にすれば十分だと思います。実際、基本的なメカニズムはLuaに似ており、開発の習慣と言語ははるかに快適になります。汎用モジュールの補足として使用すると便利だと思います、またはホットアップデート機能も非常に優れています。ただし、パフォーマンスの面では、ターゲットを絞ったテストはなく、現在、プロジェクトのニーズを満たしているとしか言えません。大量処理の観点から、ILRuntimeで多くのことを行うことはまだ推奨されていません。基本的な理由は、実際にはLuaの問題と非常によく似ています。 使用に関しては、Adapterの定義に注意してください。ホットアップデートに関しては、異なるコードノードがILRuntimeのホットアップデートパッケージを分離する必要がある場合があります。これはパッケージ管理の観点からは面倒ですが、統合開発言語にはまだ多くの利点があります。 A2:実際、パフォーマンスについてあまり心配する必要はありません。私の経験を共有しましょう。 1、必ずReleaseメソッドを使用してDLLをコンパイルし、DISABLE_ILRUNTIME_DEBUGマクロを開いてください。 2、解訳の実行のために、実行効率と直接実行の間には自然に20〜100倍のギャップがあり、複雑すぎる計算は間違いなく機能しません。 3、実行時も純粋なC#であるため、いくつかのパフォーマンスの問題はUWAツールで検出できます。 4、私たちのプロジェクトで使用されているネイティブDLLはLuaよりも確実に高速で、iOSでテストしたらLuaよりも低速ですが、AppleデバイスとIL2CPPのパフォーマンスは良好であるため、フルフレームで実行することもできます。 5、オブジェクト内の構造体への割り当てには必然的なGCがあります。最初にローカル変数に変換するか、V3をXYZに直接格納することをお勧めします。 6、ForeachはGCを生成します。書き込み時には使用しないでください。 A3:計算集約型はLuaを使用できます。 1、コアコードはC#を使用します。 2、バトルライブラリのロジックレイヤーは、Luaを使用します。 3、ビジネスロジックはILRuntimeを使用します。 4、Injectfixの別のレイヤーを配置し、非BurstのC#を修復します。 A4:ILRuntimeはC#で実装されているため、Profilerを見るとパフォーマンスの消費を直接確認できます。 ##Resource Q:現在、実機でテストしていますが、メモリに2つのフォントがロードされていることがわかりました。1つはログインインターフェイス、またはフォント自体を含む他のシーンでは、元のFontを参照しています。もう1つはプレハブがシーンに読み込まれると、フォントのコピーが参照されます。これを解決する方法は? A:プレハブがAssetBundleからロードされているが、シーンがAssetBundleからロードされていない場合、通常のシーンパッケージとAssetBundleは異なるパスを取り、同じフォントアセットを参照できません。1つはシーンのSharedassetsからのもので、もう1つはAssetBundleからのものです。したがって、シーンもAssetBundleによってパッケージ化されるか、フォントを使用するシーンのUI部分がAssetBundleから動的にロードされるかになっています。 ##Addressable Q:LZ4圧縮モードでは、AddressablesがBundleをロードするにはHeadersのみをメモリにロードするので、すべてのアセットをBundleにパッケージ化できますか?このようにして、アセットの粒度とアセットの繰り返しのパッケージ化の問題を回避できますか? A1:ファイルレベルのアセット更新で、しかもAssetBundleが非常に大きい場合、パッケージ全体を再ダウンロードすることと大差ありません。アセットの粒度は、主に更新アセット量を減らすことです。 A2:全体的な考え方を共有します。 AddressableですべてのアセットをBundleにパッケージ化し、固定の読み取りパスはUnityEngine.Application.persistentDataPathです。つまり、ローカルは常に読み取られます。実際の経験では、プレイ中にダウンロードするとフリーズが頻繁に発生します。 最初のパッケージは小さなパッケージをリリースします。ゲームが起動されると、アセットの更新がチェックされ、アセットBundle(後続のインクリメンタルパッケージを含む)がC#のHttpWebRequest(ダウンロードを単独で管理)を介してダウンロードされます。、persistentDataPathに一律にダウンロードされます。終了後にゲームのプロセスに入ります。 利点: 1、アセット管理は簡単です。 2、アセット分割の粒度に関係なく。 3、アセットの繰り返しのパッケージ化を考慮する必要はありません。 4、最初の起動時にダウンロードするBundleが多すぎるためにダウンロード速度が遅くなることを心配する必要はありません。 欠点: まだ考えられず、皆様の指摘をお願いします。 A3:アセットのアンインストールの観点からは不合理です。Addressableは、参照カウントによるアンロードを処理します。これは、AssetBundleの参照カウントが0になった場合にのみトリガーされます。トリガーされると、AssetBundle.Unload(true)がコールされます。したがって、この大きなAssetBundleに常駐メモリが必要なアセットの場合、AssetBundleも常駐メモリが必要であることを意味します。そうすれば、短時間の使用後にアンインストールできるテクスチャなどのような未使用のアセットは、アンインストールできません。もちろん、Addressableのロード・アンロードインターフェイスでアセットをロードおよびアンロードすることを前提にしています。 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で続きを読む

C#コードの最適化:Monoメモリの節約

C#は仮想マシン(VM)環境(Mono仮想マシンまたはIL2CPP仮想マシン)で実行され、割り当てられたMonoメモリは仮想マシンによって管理および再利用されるため、C#コードは「マネージドコード」と呼ばれ、Monoメモリも「マネージドメモリ」として知られています。「マネージドメモリ」の解放は、仮想マシンのGC(ガベージコレクション)メカニズムに依存しますが、この記事では説明していません。 C#プログラム開発で従うべき基本原則は、不要なMonoメモリの割り当てを回避することであり、Monoメモリの割り当ては主に次の結果を引き起こします。 プログラムが使用するメモリの総量が多すぎます。メモリは限られたアセットです。ゲーム(特にモバイルゲーム)の場合、メモリ使用量は貴重なものであり、解放できないメモリが多すぎると、プログラムがクラッシュする可能性があります。 割り当ての回数が多すぎると、Monoの断片化が増加し、必要な連続メモリを割り当てることができなくなり、プログラムがクラッシュする可能性があります。 メモリ割り当てはGC(ガベージコレクション)をトリガーし、GCは高価であり、遅延を引き起こします。 ##1.当クラスのメソッドには.tagへのコールがある tagはシーン内のGameObjectのタグであり、クラスGameObjectのメンバーtagは属性です。属性を取得するとき、基本的にget_tag()関数をコールし、nativeレイヤーから文字列を返します。文字列は参照型であり、この文字列が返されると、Monoメモリが割り当てられます。ただし、Unityエンジンはキャッシュによってget_tagを最適化しません。get_tagがコールされるたびに、Monoメモリが再び割り当てされます。 したがって、tagを比較する必要がある場合は、関数GameObject.CompareTag()を使用することをお勧めします。この関数は、nativeレイヤーに実装され、マネージMonoメモリの割り当てを引き起こさないため、GCの負荷を回避できます。 ##⒉当クラスのメソッドには、texture GetPixels()/ GetPixels32()のコールがある Texture2DタイプのオブジェクトでGetPixels()およびGetPixels32()をコールすることは、通常、指定されたMipmapレイヤーのすべてのピクセル情報を取得することであり、画像上のピクセル数は非常に多いことがよくあります。 メモリ割り当てに関しては、この関数はマネージMonoにメモリを割り当てて、テクスチャデータのピクセル情報を格納しますが、エンジンはそれをキャッシュしません。したがって、頻繁にコールされる関数で使用すると、永続的なMonoメモリ割り当てが発生します。 時間の面では、大規模な並列演算が得意なGPUが画像情報を処理するのは非常に簡単ですが、ピクセルごとの情報を取得する場合、CPUは少し面倒です。また、GetPixels()はCPUによって同期的に実装されるため、時間がかかり、コールのスレッドが同時にブロックされ、ラグが発生する可能性があります。したがって、必要な場合を除いて、GetPixels()を使用することはお勧めしません。 ##3.当クラスのメソッドにGetComponentsInChildrenコールがある/当クラスのメソッドにGetComponentsInParentコールがある 前回の記事「C#コードの最適化:CPU時間の節約」では、GetComponentsInChildrenとGetComponentsInParentについて簡単に説明しました。ここでは、実際のコールでは、オブジェクトのトラバーサルと結果の戻りが含まれるため、不適切に使用すると、永続的なMonoメモリの割り当てが発生することをさらに説明します。開発チームは、List型の参照をパラメータとして受け入れるバージョンを使用することをお勧めします。これにより、コールごとにMonoメモリを割り当てないようにすることができます。 ##4.当クラスのメソッドにLinq関連関数へのコールがある Linq関連の関数は、通常、データのクエリと処理に使用されます。機能的に言​​えば、一連のデータに対してさまざまなif判定とforループ処理を実行することです。 Linqが提供するAPIを使用すると、集合体データを処理するSQLステートメントスタイルのコードを記述できます。これにより、コードの単純さと読みやすさが大幅に向上し、保守も便利になり、書き込み効率が向上しますが、これらの利点はパフォーマンスが犠牲になります。 Linqは実行中にいくつかの一時変数を生成し、デリゲート(lambda式)を使用します。デリゲートを条件付き判定方法として使用すると、時間のコストが高くなり、一定量のMonoメモリが割り当てられます。したがって、一般的なUnityゲームプロジェクトの開発では、Linq関連の関数を使用することはお勧めしません。エディター機能の開発では、LinqとReflectionを一緒に使用することがよくあります。 ##5.当クラスのメソッドに、RendererにsharedMaterialsの取得がある 同様に、前回の記事「C#コードの最適化:CPU時間の節約」では、.material / materialsについて説明しました。簡単に言うと、.material(s)をコールすると、新しいシェーダーインスタンスが生成されます。sharedMaterialsは共有マテリアルであり、新しいマテリアルインスタンスは生成されません。ただし、.sharedMaterialsをコールすると、Monoメモリが割り当てられます。コールごとに、Materialのインデックスを保持する配列が割り当てられます。このアレイのメモリ使用量は比較的小さいですが、頻繁にコールすることはお勧めしません。 ##6.当クラスのメソッドにInput.touchesコールがある モバイルプロジェクトの相互作用では、タッチ操作は非常に頻繁で一般的であると言えます。タッチ操作の取得では、Input.touchesを使用して、現在のフレーム内のすべてのタッチ操作のステータスと対応するデータを取得します。しかし、.touchesの実装を見ると、コールたびにtouchesの配列がnewになり、ある程度のMonoメモリが割り当てられることがわかります。したがって、開発チームは、追加のMonoメモリの使用を防ぐために、Input.touchesを頻繁に使用しないようにする必要があります。 ##7.FindObjectsOfTypeコール 前回の記事「C#コードの最適化:CPU時間の節約」では、FindObjectsOfTypeについて簡単に紹介しました。 CPU時間は増加する上に、Monoメモリ割り当てのかなりの部分を占めるため、パフォーマンスへの影響を軽減するために、1回のコールで結果をキャッシュすることをお勧めします。 ##8.当クラスのメソッドにTextAsset / WWW.bytesコールがある このルールは、実際には2つの無関係なUnityAPI用です。 1つ目は、TextAssetのbytesプロパティです。 TextAssetはUnityのテキストアセットであり、txt、html、bytes、csvなどのさまざまな形式のファイルの変換をサポートしています。 bytesプロパティを取得するとき、Unityはnativeレイヤーからバイト配列(byte [])を取得し、それによって一定量のMonoメモリを割り当てます。 別のAPIは、WWWクラスのbytesメンバーを参照します。呼び出されるたびに、Monoメモリの割り当てが発生します。UnityがWWWクラスの関連するインターフェイスを放棄したため、UnityWebRequestを使用して関連機能を実現することをお勧めします。 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で続きを読む

Luaグローバル変数コードの仕様

今回の話題: 1)Luaグローバル変数コードの仕様 2)AssetBundleLockPersistentManagerのコスト 3)Unityの内蔵フォントは、アセット検出レポートでは冗長アセットとは見なされない 4)特定のAndroidデバイスでAdrenoがフリーズする 5)MaskとRectMaskのパフォーマンスの違い ##Lua   Q:スクリプト支援開発としてLua言語を使用することは非常に人気がありますが、Lua言語のグローバル変数は面倒なことでもあります。宣言なしで使用でき、コンパイラーは名前の変更や上書きのエラーを報告しないため、少し油断したらグローバル変数が上書きされてBugが発生し、さらにGameObjectを参照するグローバル変数がリークを引き起こす可能性があります。   開発プロセスで、Luaグローバル変数のコード仕様を作成しますか?例:グローバル変数はいつ使用できますか?宣言する方法は?上書きやその他の問題を回避する方法は? A:Lua仮想マシンの起動後、適切なタイミングでluaGlobalCheck.luaファイルを実行できます。このファイルは_Gのメタテーブルおよびメタメソッドを設定します。_newindexおよび_indexメタメソッドをオーバーライドすることにより、グローバル変数の作成と存在しないグローバル変数にアクセス時にエラの提示を禁止します。このようにして、グローバル変数の随意な作成と上書き問題が避けられます。 >setmetatable(_G, { -- グローバル変数の作成を禁止 __newindex = function(_, k) error("attempt to add a new value to global,key: " .. k, 2) end, -- グローバル変数のアクセスを禁止 __index = function(_, k) error("attempt to index a global value,key: "..k,2) end }) ##AssetBundle   Q:パフォーマンス曲線を見ると、あるフレームのAssetBundleがロードされると、LockPersistentManagerに多くの時間がかかることがわかります。これは最適化できますか? A1:これは、現在のフレームまたは前のフレームでLoadAsyncを介して大量のアセットがロードされていることを意味します。本質的には、ロードされたアセットが大きすぎるということです。独自のアセットを適切に最適化すると、Loading.LockPersistentManagerのコストを削減できます。さらに、非同期ロードを同期ロードに置き換えることにより、LockPersistentManagerは表示されませんが、合計ロードが変更されていないため、合計ロード時間は変更されません。 A2:Unity 2019.4.1バージョンでは、bundle.LoadFromFileAsyncは実際にはメインスレッドのIntegrate Assetで実行され、PreloadManagerスレッドのLoadAssetAsyncは同時に実行できないため、ロックする必要があります。そして、LockPersistentmanagerが表示されます。片側が終了するまでロックされます。 実に、それは実装が完全ではないということを意味しています。Spin Lockを使用して、Application.backgroundLoadingPriorityで片側が終了するまでロックされル必要はなく、指定された時間までロックしてから、次のフレームに移動できます。 Unity 2019.4.11および2019.4.16は、メインスレッドがBundleを読み取ってロックを待機する問題を修正しました。 Unity 2020.1.17を使用してiOSでテストしたところ、基本的に解決されました。 ただし、メインスレッドを非同期でロードしロックを待機する現象もいくつかあります。恐らくアセットが大きすぎ、コレクションの依存関係の時間が長すぎてトリガーられたと推定します。 ##AssetBundle   Q:私たちのプロジェクトでは、2つのインターフェイスのPrefabはUnity独自のArialフォントで生成されたBundleですが、アセット検出にアップロードして、レポートで内蔵のフォントが冗長アセットであることが見えませんでした。 A1:このArialはUnityの内蔵アセットに属しています。APKをパッケージ化すると、unity default resourcesに入力されました。そして、AssetBundleで使用されたArialフォントは参照関係で、対応するAssetBundleにパッケージ化されません。したがって、冗長アセットが見られないのは正常です。 AssetStudioを使用して、APKパッケージのassets / bin / Data /ディレクトリにあるunity default resourcesを開くと、表示できるようになりました。 A2:Arialフォントの使用はお勧めしません。一部のローエンドコンピュータでは中国語を表示できません。主な理由は、モデルによってデフォルトのフォントが異なるためです。 ##Rendering Q:特定のAndroidデバイスで、Adrenoに画面のフリーズ(GPUハング)の問題があります。   この現象は、画面がフリーズして更新されないということですが、音楽とUIをクリックしたときの効果音は引き続き再生できます。 Unity Profilerを使用してCPUを確認しても、例外やフラッシュバックはありません。画面がフリーズしたときにキャッチされたエラーログは、元の質問と回答をクリックして表示できます。 A1:MonoパッケージがXiaomiデバイスでも同じ状況が発生しましたが、IL2CPPに変更した後は再発しませんでした。 A2:A1の方法は確かに一部のモデルで効果的ですが、一部のMTKデバイスでも同様の問題が発生します。Unityバッチ処理、動的バッチ処理、またはGPUインスタンスの問題のように感じます。単一のバッチでGPU Instanceの数を減らしたら、少しよくなります。デバイスのGPUにマッチする数をしてください。 A3:Unityが公式に提供しているGPU Instanceソリューションを使用して実装されています。 https://github.com/Unity-Technologies/Animation-Instancing このスキームのデフォルトのShaderは、戦闘ユニットのレンダリングに使用され、奇妙なフラッシュバックとクラッシュが発生しました。デフォルトのShaderは次のとおりです。 このShaderのloadMatFromTexture関数は9回呼び出され、loadMatFromTextureの実装は次のとおりです。 ここでの問題は、サンプリング時間が長すぎることが原因である可能性があり、修正後に現在のフレームが使用され、Lerp操作は実行されず、各頂点は2つのボーンによってのみ制御され、テストしたらクラッシュしなかったことが確認できます。エフェクトロスの許容レベルについてはまだアーティストに確認されています。後のコードは次のようになります。 ##UGUI   Q:Unity UIのMaskとRectMaskのパフォーマンスに違いはありますか? A1:RectMask2D:Imageコンポーネントに依存する必要はなく、そのクリッピング領域はRectTransformのrectサイズです。 プロパティ1:RectMask2Dノードの下のすべての子ノードと外部UIノードはバッチ処理したり、複数のRectMask2D間にバッチ処理したりすることはできません。 プロパティ2:Depthを計算する場合、すべてのRectMask2Dは一般的なUIノードとして扱われますが、CanvasRendererコンポーネントがなく、UIコントロールのbottomUIと見なすことはできません。 Mask:コンポーネントはImageコンポーネントに依存する必要があり、クリッピング領域はImageのサイズです。 プロパティ1:Maskには最初と最後にさらに2つのDraw Callがあり(最初の= Maskノード、最後= Maskノードの下の子ノードがトラバースした後)、複数のMaskがバッチ条件を満たしている場合、これらの2つのDraw Callに対応的にバッチ処理できます(Mask1のヘッドとMask2のヘッドが結合されます。Mask1のテールとMask2のテールが結合され、ヘッドとテールを結合することはできません。) プロパティ2:Depthを計算するとき、Maskのヘッドにトラバースするときは、バッチ処理できないUIノードとして扱いますが、子UIノードとして使用できるbottomUIに注意してください。 プロパティ3:Mask内のUIノードとマスク外のUIノードを一緒にバッチ処理することはできませんが、バッチ条件を満たしている場合は、複数のマスク内のUIノードを一緒にバッチ処理できます。 マスク3のプロパティから、Maskをまとめてバッチ処理できるため、Maskが多いほど悪いというわけではないことがわかります。次のように結論付けられました: インターフェイスにMaskが1つしかない場合、RectMask2DはMaskよりも優れています。 2つのMaskがある場合、2つは類似しています。 マスクが3つ以上ある場合、MaskはRectMask2Dよりも優れています。 A2:複数のMaskのバッチからA2の結論を検討することができます。追加は次のとおりです。Mask でStencilを書き込むと追加のOverDrawがあり、Mask2DにはOverDrawはありませんが、各ItemはMaskRectと比較する必要があるから、一定的なCPUコストが発生します(Maskは多くのItemに適しており、Mask2Dは少数のItemに推奨されます)。 A3:UIバッチ処理の観点から、同じレベルで: Maskは優先度の高い特殊なコンポーネントであり、優先的に実行します。Maskはそれ自体とUIのMaterialを変更します。両者は異なります。 RectMask2Dは特別な権限のないクリッパーであり、そのバッチ処理ルールは他の通常のコンポーネントと同じです。 RectMask2Dは、UIのClipRectを変更します。したがって、同じRectMask2Dの下にないUIコンポーネントを一緒にバッチ処理することはできません。 UIをバッチ処理できるかどうかの理由として、次のように推測します。 Different Rect Clippingとは、RectMask2Dから通常のコンポーネントへの切り替えを指します。Different Clip Rectとは、2つのRectMask2D間の切り替えを指します。 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でコライダーの大きさをオブジェクトのscale値の影響を受けないようにする方法!!?

前口上 unityでゲームを作っていて、演出としてゲーム上のオブジェクトのscale値をアニメーションとかで変えることってよくありますよね!(あるよね?) でも、コライダーがついていると、そのオブジェクトのscale値の変動にあわせて、しっかりコライダーの大きさも変わってくれますよね~ すごく便利でありがたい機能ではあるんですけど、時としてこの機能が邪魔になってしまう事もゲームを作っていれば1回ぐらいはあるはず... 今回はそういう人に向けて、今回私がこの問題に当たった時に解決した方法を猫でも分かるように書いていきたいと思います! こんな挙動に困ってました? この動画では、下の球をアニメーションでscale値を変更しているのですが、コライダーも一緒に変っているため、上に乗っかった球が一緒に動いているのが分かると思います 今回作っているゲームでは、コライダーの接触時に反射判定をしているため、何度もコライダーに当たるような挙動になりそうで困っていました... 試したこと① 真っ先に試したこととして、まずsphrerColliderのリファレンスを確認しました 困ったときは解説記事を読む前に公式のリファレンスを読んで自分で理解する力を身に着けることも大事です!! 今回困っているのはcolliderの大きさなので、radiusの項目を読んでみると、 「ローカルサイズでのコライダーの半径」とありますね つまり、radiusの値はどれだけ弄ってもローカルの値なので、そのオブジェクトのscale値によって変わってしまうのです? もう少しわかりやすく解説すると、コライダーの大きさは、scaleのxyzいずれかのうち最大の値 * radius でコライダーの半径が求められます scale : x=2, y=1, z=1 radius: 0.5 の場合 transform.scale.x * radius = 1 = 2 * 0.5 = 1 となりますね! そして、答えの「1」という値は半径なので、直径に直すと「2」となり、「scale.x」の値と同じになりますよね だからsphireColliderのradiusはデフォルトで「0.5」になっているんですね~ 私もこうやって改めて調べてみて初めて知ることができました? ①の結果を受けて... はてさて知識を蓄えられたのはいいものの、肝心の問題は解決できませんでした これでは、結局いくらinspector上でradiusの値を変えようが意味がないという事しかわかっていないですね? しかし逆に言えば、それ以外のアプローチを試すきっかけが出来たので、さっそく他の方法を考えてみました 先にinspector上での値操作はダメと書いたので、分かった方もいるかもしれませんが、動的に変化させるというのが正解な気がしますね! 私もそう思い、動的にradiusの値を変更する方法を2つ考えました 1つめは、アニメーションで変更する方法です 私自身がアニメーションを得意とするのもあり、真っ先に思い浮かびました 計算式はもう出ているので、それに当てはめてscale値が変わるのに合わせてradiusの値も変化させればうまくいくだろうと思いました 2つめは、スクリプトから変更する方法です 別に避けてたわけではないですが、なぜか最終手段だと思って最後まで考えないようにしていました スクリプトを書けば解決するのは分かっていたんですけどね... 試したこと② 先に挙げたように、アニメーションを使えばできるだろうと私はかなり自信を持っていました しかし、結果から言えば、この方法ではできなくもないが、かなり厳しいです? アニメーションはオブジェクトのinspectorにある情報のほとんどを動的に変化させることが可能です 実際どのようにradiusの値を変化させようかと思ったときに、先の計算式を使うとすごく簡単です scale * raduis = コライダーの半径 初期サイズがscale(1,1,1)の球で、コライダーの半径は常に初期サイズの半径と同じであってほしいので、「0.5」とします 求める値はradiusです 式変換をし、 radius = コライダーの半径 / scale で求められますね ということで、 こんな感じでAnimationに入力していきます ちなみに、unityエディター上の数値入力のフィールドでは四則演算、累乗、かっこを用いた計算ならフィールド内で出来ますよ! さて、この計算式を使って、すべてのscaleアニメーションのピボットがある位置にradiusの値を入力していきました 各ピボットを見てみるとscaleが変わってもコライダーのサイズは変わってないです! これは行った!!と思いました しかし、実際に動かしてみるとダメでした なぜこうなったのか 原因を色々と考えたのですが、どう見てもピボットが打たれている場所以外の線形補完された部分で大きさに差異が出ていることには気付きました しかし、なぜそうなるのかよく分からなくて明確な答えにはたどり着けていないので、この先は考察も含めた内容です unityのアニメーションでは、ピボット間の数値の変化は直線的な補完ではなく、サインカーブのような補完がされます サインカーブのような補完というのは、最初は数値の変動が少なく、中央に近づくにつれて変動が大きくなり、終わりにつれてまた変動が少なくなるということです(語彙力) これは、アニメーションウィンドウで表示の仕方をカーブに変えるとわかると思います 分かりやすいように、それぞれの値を大きく、ピボット間の時間を長くしてあります これはピボットがある位置ではコライダーの大きさが変わっていないグラフです このように、各値がぐにゃ~ってなっているんですよね おそらくこれが原因なのは確定なのですが、どうしてこれがダメなのかはよく分かりません(本末転倒) 教えて!つよつよプログラマー! 最終的な解決方法 結局、スクリプトを書いてやるしかないみたいです? ピボットを打った位置なら正常に動くのであれば、スクリプト上から毎フレームピボットでやったことと同じことをしてあげればいいだけの話です いたって簡単なのに、なぜ最後までやらなかったのか... colliderScalling.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class colliderScalling : MonoBehaviour { [Header("このオブジェクトのSphereCollider"),SerializeField] SphereCollider col; [Header("絶対的なコライダーのサイズ(半径)"), SerializeField,Space(10)] float radiusAbsoluteSize; private void Start() { col = GetComponent<SphereCollider>(); } private void Update() { //絶対サイズ / scale.x,y,zの最大値 col.radius = radiusAbsoluteSize / Mathf.Max(this.transform.localScale.x,this.transform.localScale.y,this.transform.localScale.z); } } たったこれだけで実装できてしまいます やっている事もさっきまでやってきた事をそのままスクリプトで毎フレーム計算しただけです なんならこっちの方が球のサイズに左右されずに作れるので拡張性が高いですね! まとめ? いかがだったでしょうか? 私が普段問題解決をする際の流れも込みでいろいろ書いてみました あえてスクリプトを書こうとしなかったことで学んだことがあったので、自分的にはよかったかなって思います! こういう普段何となく使っているツールの機能も、意外と調べたり、触ってみたりすると面白い動きをしていたり、逆に不便だと思ったりすることはよくあります 特に、Unityはかなり良くできたゲームエンジンですが、それ故に頼りすぎてしまう部分多いと思うので、ただ作るだけではなく、もう少し踏み込んでみるとより面白くなるのでお勧めですよ!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【UniRx】Buffer(2, 1)よりPairwiseの方が少しお得

概要 UniRxには「流れてきたメッセージを溜めて、後でまとめて流す」ためのBufferというオペレータがあります。また、Pairwiseという「流れてきたメッセージを2つまとめて流す」オペレータもあります。 まずこれらそれぞれの動きと違いについて紹介し、Buffer(2,1)に対するPairwiseの優位性について説明します。 ついでに、せっかく内部実装を見るので少し改造したオペレータについても考えてみます。 長々書いてますが、一言で言えばBufferはIList<T>で、Pairwiseは構造体Pair<T>というだけの話なので、それで「なるほどね」となる人なら得るものは無いです。 想定する読者層 UniRxはわかる、使える オペレータ内部の実装にちょっと興味ある 説明しないこと ReactiveExtensionやUniRxについて Buffer, Pairwise以外のオペレータ 既存のBufferとPairwiseオペレータ Buffer(個数指定) 「メッセージが指定した数溜まったらまとめて流し、古いメッセージを指定した数捨てる」という動きをするオペレータです。 捨てる数を指定しない場合、流したメッセージは全て捨てます。 雑に1から4のintを流してみます。 using System; using System.Collections.Generic; using UnityEngine; using UniRx; public class BufferSample : MonoBheaviour { private void Start() { Observable.Range(1, 4) .Buffer(2, 1) // ここでメッセージが溜まる .Subscribe(bufferList => { Debug.Log(String.Join(", ", bufferList)); } } 結果 1, 2 2, 3 3, 4 4 このように、Buffer(2, 1)を指定すると、「2個溜まったら流して、古い1個を捨てる」動きをします。最後に「4」が流れているのはOnComplete時の動作で、BufferはOnCompleteで残っている中身を流す特徴があります。 Pairwise 「メッセージが2個溜まったらまとめて流す」という動きをするオペレータです。Bufferが「メッセージが指定した数溜まったらまとめて流す」オペレータなので、ほぼBuffer(2,1)と同じとなります。 using UnityEngine; using UniRx; public class PairwiseSample : MonoBheaviour { private void Start() { Observable.Range(1, 4) .Pairwise() .Subscribe(pair => { Debug.Log($"{pair.Previous}, {pair.Current}"); } } 結果 1, 2 2, 3 3, 4 Buffer(2,1)のときと違うのは、OnCompleteで「4」が流れてないところです。ペアで来ないと意味無いならPairwiseを使うのがいいでしょう。 Buffer(外部からメッセージ放流タイミングを指示) さきほどは「メッセージが2個溜まる」ことがメッセージの放流タイミングでしたが、外部から放流を指示するまでメッセージを溜め続けることもできます。例えばダブルクリック場合は以下のように書くことができます。 using UnityEngine; using UniRx; using UniRx.Triggers; public class DoubleClickSample : MonoBheaviour { private void Start() { var click = this.UpdateAsObservable() .Where(_ => Input.GetMouseButtonDown (0)); click // 0.5秒経ったら放流。流したら溜まってるバッファを全削除。 .Buffer(click.Throttle(TimeSpan.FromMilliseconds(500))) // クリックメッセージ2回以上溜まっていたらOK .Where(tap => tap.Count >= 2) .Subscribe(tap => Debug.Log ("だぶるくりっく!")); } Bufferの内部実装 Bufferがメッセージとして流す型はILIst<T>です。Bufferは内部にList<T>を持っており、OnNextではそれを流し、その後新しいListを生成しています。そのため、例えば、毎フレームメッセージが1つ流れてきて、それをBuffer(2,1)で受け止める場合を考えると、2フレームに1回Listがnewされることになります。 // Bufferの、溜める個数のみ指定した場合のObserverを一部抜粋 // 指定した個数溜まったら流して、新しいListを生成している。 // 捨てる個数も指定する場合はもうちょっと複雑(Listインスタンスが増える) public override void OnNext(T value) { list.Add(value); if (list.Count == parent.count) { observer.OnNext(list); list = new List<T>(parent.count); } } Pairwiseの内部実装 Pairwiseが流すメッセージはPair<T>という、T型を2つ持つだけ持つ構造体です。(GetHashCodeとかはちゃんと自前実装してある) Pairwiseはprev(直前のメッセージ)のみを保持して、今回のメッセージと合わせて構造体Pairを生成して流しています。その後、prevに今回のメッセージを上書きします。 // Observer一部抜粋。Pairを生成しているが、これは構造体。ヒープの確保は無い。 public override void OnNext(T value) { if (isFirst) { isFirst = false; prev = value; return; } var pair = new Pair<T>(prev, value); prev = value; observer.OnNext(pair); } つまり、状況に適しているなら、Buffer(2,1)を使うよりPairwiseを使った方がお得そうです。(OnCompleteの違いはある) スワイプ処理をPairwiseを使って実装してみる タッチデバイスでスワイプを検知するクラスを、UniRxで実装してみます。 スワイプを、「前フレームのタッチ座標と現フレームのタッチ座標の差で検知する」ように作ります。メッセージが頻繁に来るとわかっており、また前後ペアでメッセージが来ないと意味がないのでBufferではなくPairwiseを選択します。 using UnityEngine; using UniRx; using UniRx.Triggers; public class Swipe : MonoBehaviour { private IObservable<Vector2> _onSwipe; public IObservable<Vector2> OnSwipe => _onSwipe ?? (_onSwipe = CreateSwipeStream()); private IObservable<Vector2> CreateSwipeStream() { // 指の数が正しくなければ止めるためのストリーム var swipeEndStream = this.UpdateAsObservable().Where(_ => !IsCorrectFingerCount()); return this.UpdateAsObservable() .Where(_ => IsCorrectFingerCount()) // 想定したタッチ本数以外なら無視する .Select(_ => Input.GetTouch(0)) // 今のタッチ座標を取る .Pairwise() // 前回の座標とペアにする .Select(positionPair => positionPair.Current - positionPair.Previous) // 前フレームとの差分を取る .TakeUntile(swipeEndStream) // 指本数が1本でなくなったらComplete .RepeatUntileDestroy(this); // 改めてSubscribe // 想定した指本数か bool IsCorrectFingerCount() => Input.touchCount == 1; } } これで、外部からはOnSwipeをSubscribeすれば毎フレームのスワイプ距離を購読できます。 以上が、Buffer(2,1)よりPairwiseがいい、という話でした。 以降は主題から外れ、BufferやPairwiseを改造する話です。 内部のバッファを削除できる改造Pairwiseを作ってみる スワイプ処理でTakeUntil + RepeatUntileDestroyしている理由はPairwiseが保持しているバッファを削除したいからです。(スワイプが終わって、またスワイプを始めたときに、前回の終了位置と比較されては困るため。)ですが、TakeUntil + RepeatUntileDestroyだと、OnCompleteを発行して且つ改めてSubscribeすることでそれを実現しており、余計な処理が多い感じがします。処理的にも、読みやすさ的にも、素直に「Pairwiseの中にあるバッファを消す」と書けると嬉しいです。 しかしPairwiseにはそのようなオーバーロードはありません。が、UniRxはオペレータのベースクラスを公開しており、割と簡単にオペレータを自作できます。というわけで自作します。 内部のバッファを削除できる(ように見える)Pairwise using System; using UniRx; using UniRx.Operators; public static class PairwiseWithClearBuffer { public static IObservable<Pair<TSource>> Pairwise<TSource, TClearMessage>( this IObservable<TSource> source, IObservable<TCrearMessage> clearMessages) { return new PairwiseObservable<TSource, TClearMessage>(source, clearMessages); } } public class PairwiseObservable<T, TClearMessage> : OperatorObservableBase<Pair<T>> { private readonly IObservable<T> _source; private readonly IObservable<TClearMessage> _clearMessages; public PairwiseObservable(IObservable<T> source, IObservable<TClearMessage> clearMessages) : base(source.IsRequiredSubscribeOnCurrentThread()) { _source = source; _clearMessages = clearMessages; } protected override IDisposable SubscribeCore(IObserver<Pair<T>> observer, IDisposable cancel) { return _source.Subscribe(new Pairwise(this, observer, cancel)); } private class Pairwise : OperatorObserverBase<T, Pair<T>> { private T _prev; private bool _isFirst = true; public Pairwise(PairwiseObservable<T, TClearMessage> parent, IObserver<Pair<T>> observer, IDisposable cancel) : base(observer, cancel) { // バッファ削除指示が来たらisFirst扱いにする(なので実際には削除ではない) parent._clearMessages.Subscribe(_ => _isFirst = true); } public override void OnNext(T value) { if (_isFirst) { _isFirst = false; _prev = value; return; } var pair = new Pair<T>(prev, value); _prev = value; observer.OnNext(pair); } public override void OnError(Exception error) { try { observer.OnError(error); } finally { Dispose(); } } public override void OnCompleted() { try { observer.OnCompleted(); } finally { Dispose(); } } } } ほぼ既存のPairwiseのコピペで、_isFirstを書き換える指示を出せるようにしただけです。IObservebleによってその指示出しを行っている部分は、「放流タイミングを外部から指示できるBuffer」の実装に倣っています。 その他改善点 1本指スワイプだけじゃなくて、複数指スワイプも取得したい。(1本指だけなら普通にUpdateで書けばいい気がするので、なんか差別化を図りたかった) 改善したスワイプ処理 public class Swipe : MonoBehaviour { // n本指スワイプストリームの辞書(Key: 指本数 n, Value: ストリーム) private readonly Dictionary<int, IObservable<Vector2>> swipeStreamDic = new Dictionary<int, IObservable<Vector2>>(); public IObservable<Vector2> GetSwipeStream(int fingerCount) { if (swipeStreamDic.TryGetValue(fingerCount, out var swipeStream)) { return swipeStream; } swipeStream = CreateSwipeStream(fingerCount); swipeStreamDic.Add(fingerCount, swipeStream); return swipeStream; } private IObservable<Vector2> CreateSwipeStream(int fingerCount) { // 指の数が正しくなければ一旦区切るためのストリーム var swipeBoundaryStream = this.UpdateAsObservable().Where(_ => !IsCorrectFingerCount()); return this.UpdateAsObservable() .Where(_ => IsCorrectFingerCount()) // 想定した指本数以外なら無視する .Select(_ => CalculateCurrentPosition()) // 今のタッチ座標を取る .Pairwise(swipeBoundaryStream) // 前回の座標とペアにするが、指の本数が正しくなくなったらリセットする .Select(positionPair => positionPair.Current - positionPair.Previous) // 前フレームとの差分を取る .Share(); // Hot変換も一応 // 想定した指本数か bool IsCorrectFingerCount() => Input.touchCount == fingerCount; // 現座標を計算する Vector2 CalculateCurrentPosition() { // 操作中は毎フレーム走る処理なのでfor文。全指タッチ座標の重心取ってる。 var sum = Vector2.zero; for (var fingerIndex = 0; fingerIndex < fingerCount; fingerIndex++) { sum += Input.GetTouch(fingerIndex).position; } return sum / fingerCount; } } } GetSwipeStream(int fingerCount)の戻り値をSubscribeすれば、指定した指本数のスワイプを購読できます。 Bufferオペレータの改造について 既存のBufferオペレータの「毎回Listインスタンスを生成している」実装は悪いのか 全くそんなことはありません。むしろ、Bufferオペレータはこの実装でなければ困ります。 Listを改めて生成しない場合 OnNextでListをnewせず、既存のListインスタンスの中身を書き換えて使いまわせば、最初だけGCが発生して、以降は避けられます。 個数指定の場合はキャパシティも指定できるのでAddでの内部配列の再配置も起こりません。 ですが、これは問題があります。 ご存じのように、Listは参照型です。以下のように、流れてきたメッセージを溜めこんでおきたい場合を考えます。 using System; using System.Collections.Generic; using UnityEngine; using UniRx; public class BufferTest : MonoBehaviour { // 流れてきたメッセージを溜めこむ private List<IList<int>> _messageCache = new List<IList<int>>(); private void Start() { Observable.Range(1, 4) .Buffer(2, 1) .Subscribe(_messageCache.Add); foreach(var message in _messageCache) { Debug.Log(String.Join(", ", message)); } } } 既存のBufferの場合 1,2 2,3 3,4 4 ListをnewしないBufferを自作して置き換えた場合 (OnNextで流した後にインスタンスをキャッシュし使いまわす) 4 4 結果の詳細はどうでもいいです(実装に左右されるので)。大事なのは、当然ながら期待した結果にならないという点。 ライブラリの実装のあるべき形 ここで言いたいのは、既存のBufferの実装は Bufferはメッセージを溜め込む、流す数が任意 流したメッセージがどう使われるかわからない Listは参照型である ために、必要があってListを毎回生成しているということです。 「使用方法によっては期待した動作をしない」「普通に使うために内部実装を確認する必要がある」のはライブラリとしては厳しいです。よほどパフォーマンスが悪いなら考えものですが、Bufferは別にそこまでじゃないです(ダブルクリックくらいの頻度なら気にすることではない)。ちょっとしたパフォーマンス改善のために副作用が生じるのは優先順位を間違えているので、拡張はユーザーに任せるか、別の、詳細を調べるような人しか見ないメソッドを用意してあげるのが正しい姿かなと思います。 Listを毎回生成しないBufferオペレータを自作する場合 参照をキャッシュされると困るという動作上の副作用がある以上、Bufferみたいなシンプルな名前にはしない方がいいでしょう。 BufferRecycleMessageInstanceとか、副作用の部分を説明する語を追記して使用者に伝わる状態にすれば許容範囲かなと思います。(ネーミングセンスのNASA) 終わり TakeUntil + RepeatUntileDestroyが嫌なのでPairwiseを自作したり、Bufferも、使い方によっては毎回のList生成を避ける手もあると説明しましたが、どうでしょうか。良さげだと思った人もいれば、「割に合わなくね?」と思った人もいるんじゃないかと思います。 恐らく本記事でハッキリ言えるのは Pairwiseを使える状況ならBufferよりPairwiseが少しお得 くらいで、オペレータの自作の部分に関しては好みや開発の環境と相談して決めることかなと。 そもそも、UniRxが無いと綺麗な設計にならないなんてことは全く無いので、「オペレータの自作」が何らかの問題の解決手段として上位には来ないと思います。 参考 Buffer, Pairwiseの使用例はこちらの書籍のものがシンプルでわかりやすいなと思ったのでこちらからお借りしました。(他のと混ざってるのでちょっと違うけど、基本書籍です) 使う側の都合をよく考えるとかそのへんの意見は、自分の経験もありますが、こちらの影響があるかなと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRChat Unity

VRChatにアバターとUpLoadしてみる もとねた YouTube 【VRChat】超簡単!アバターをアップロードする方法【初心者必見】 Unity2019 【3Dモデル】『のん』BASIC PACK 2022/02/26 Unity起動するも VRChatがVisitorでNG... NONをDL シェーダーのDL VRChat SDK3のDL Unity 3D>新規作成 SDK3 をインポート シェーダーをインポート アバターをインポート VRChatに接続 AutoFix ダイナミックボーンの削除 VRChatのアカウントがVisitorなのでUPできず... 2022/02/25 チャレンジ開始 動画をみて Unity2019のインストール
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む