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

UnityでAndroidのbuild時に「Gradle build failed. See the Console for details」エラーが出た時の解決法

UnityでAndroid向けにbuildする際に「Gradle build failed. See the Console for details」というエラーメッセージがでてbuildできなかったのでその対処法を記述いたします。 環境 Windows10 Unity Hub 2.4.5 Unity 2020.3.25f1 Android Studio 2020.3.1 今回、Android SDKはAndroid Studioのものを使っています。 環境の構築方法については以下の記事をご参照下さい。 https://qiita.com/Nossa/items/4b2c5026698ed4ab7d2b ちなみに著者の環境では「JDK Installed with Unity」にチェックのままで問題ありませんでした。 Unity側の設定 Unity側の設定を確認すると以下の状態です。 Edit > Preferences > External Tools > Android Android SDK を Android Studio のものを使用するようにしています。 エラー内容 いざ build をしようと、 File > Build Settings から「Android」を選択して「Switch Platform」をクリックした後、 「Build」をクリックすると以下のようなエラーが出ました。 Gradle build failed. See the Console for details. 解決方法 上記記事に載っていました。 SDK ビルドツールが31以上の場合、 なんと、ファイル名を変える必要がある のです。 著者もこの方法でエラーが解消されました。 ファイル名を変える手順 一応手順を載せておきます。 Android Studio で install した path に移動する Projects > More Actions > SDK Manager から path を確認 SDK build ツールがインストールされた場所まで移動する 例: C:\Users\admin\AppData\Local\Android\Sdk\build-tools\32.0.0 d8.batのファイル名を以下のように修正する d8.bat > dx.bat そして、lib フォルダ内にあるd8.jarのファイル名を以下のように修正する d8.jar > dx.jar これで、もう一度 build してみます。 はい、ちゃんと .apk ファイルが作成されて無事 build が成功しました。 このエラーで困っている方に少しでも参考になれば幸いです。 以上、有難うございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Addressable Assetのホストサービス

七、Addressable Assetのホストサービス ホストサービスは、Addressable Assetsの構成データを使用して、ローカルまたはネットワークに接続されたアプリケーションビルドにパッケージ化されたコンテンツを提供するための統合ツールを提供します。ホストサービスは、パッケージ化されたコンテンツをテストする際の反復速度を向上させるように設計されており、ローカルおよびリモートネットワーク上の接続されたクライアントにコンテンツを提供するためにも使用できます。 7.1Packed Modのテストと反復 Editor Play Modeテストからプラットフォームアプリケーションビルドテストに移行すると、開発プロセスは複雑になって、時間コストも多くなります。ホスティングサービスは、Addressablesグループ構成に直接マップする拡張可能なエディター埋め込みコンテンツ配信サービスを提供します。カスタムのAddressables構成ファイルを使用すると、Unity Editor自体からすべてをロードするようにアプリケーションをすばやく構成できます。これには、開発システムへのネットワークアクセスを備えたモバイルデバイスまたはその他のプラットフォームに配置されたビルドが含まれます。 Assetホスティングサービスをサーバー環境に配置し、batch mode(headless)で実行して、イントラネットおよびエクストラネット上のUnityクライアントアプリケーションにコンテンツホスティングを提供することもできます。 7.2設定 このセクションでは、Assetホストサービスの初期設定について詳しく説明します。エディターのワークフローに重点を置いていますが、AddressableAssetSettingsクラスのHostingServicesManagerプロパティを設定することにより、APIを使用してホストサービスを構成できます。 7.2.1ホストサーバーの構成 Hosting windowウィンドウを使用して、新しいホストサーバーを追加、構成、および有効にします。 エディターで、Window>AssetManagement>Addressables>hostを選択するか、AddressablesグループウィンドウメニューのTools>HostingServicesボタンをクリックして、Hostingウィンドウにアクセスします。 新しいローカルホストサービスを追加するには、Create > Local Hostingボタンをクリックします。 注:カスタムホストサービスタイプの実装の詳細については、「カスタムサービスについて」セクションを参照してください。 新しく追加されたサービスは、Addressables HostingウィンドウのHostingServicesセクションに表示されます。 Service Nameフィールドを使用して、サービスの名前を入力します。 新しいサービスはデフォルトで無効になっています。サービスを有効にしたいなら、Enableボタンを選択します。 HTTPホストサービスは、起動時にポート番号を自動的に割り当てます。ポート番号はUnity sessions間で保存され、再利用されます。別のポートを選択するには、Portフィールドで特定のポート番号を割り当てるか、Resetボタンを使用して別のポートをランダムに割り当てます。 注:ポート番号をリセットする場合は、完全なアプリケーションビルドを実行して、正しいURLを生成して埋め込む必要があります。 HTTPホスティングサービスが有効になり、各Asset GroupのBuildPathで指定されたディレクトリからコンテンツを提供できるようになりました。 7.2.2 Profile設定 開発中にホストサービスを使用する場合、Unityは、構成ファイルを作成することをお勧めします。ホストサービスからコンテンツをロードするために、このファイルはすべてのAsset Groupsを、1つ以上の特別に作成されたディレクトリを使用するように構成する エディタで、Windows>AssetManagement>Addressables>Profilesを選択するか、AddressablesグループウィンドウメニューのTools>Profilesボタンをクリックして、Profilesウィンドウにアクセスします。これらの設定には、AddressableAssetSettings Inspectorからアクセスすることもできます。 次に、新しい構成ファイルを作成します。以下の例では、新しいプロファイルの名前はEditorHostedです。 ホストサービスからの読み込みを上書きするには、読み込みパスフィールドを変更します。HttpHostingServiceは、サービスに割り当てられたローカルIPアドレスとポートを使用するURLです。Addressablesホストウィンドウでは、PrivateIpAddressおよびHostingServicePortという名前の構成ファイル変数を使用してURLを作成できます。(たとえば、http:// [PrivateIpAddress]:[HostingServicePort]))。 また、Assets folder外の共通ディレクトリを指すように、すべてのビルドパス変数を変更する必要があります。 各グループが正しく構成されていることを確認します。BuildPathとLoadPathはそれぞれ構成ファイルに設定されることを確保します。これらの構成ファイルは、ホストサービスに用いられるために変更されました。この例では、LoadPathの構成ファイル変数を展開して、ホストサービスからロードする正しい基本URLをビルドする方法を確認できます。 最後に、Addressables Groupsウィンドウから新しい構成ファイルを選択し、ビルドを作成して、ターゲットデバイスに配置します。Unity Editorは、HttpHostingServiceサービスを介してアプリケーションからのすべてのロードリクエストを処理するようになりました。再配置せずにコンテンツを追加および変更できるようになりました。Addressableコンテンツを再生成し、配置されたアプリケーションを再起動してコンテンツを更新します。 7.2.3 Batch mode Unity Editorの下からバッチ処理モードを実行して、ホストサービスを使用することもできます。次のオプションを使用して、コマンドラインからUnityを起動します。 batchMode -executeMethod UnityEditor.AddressableAssets.HostingServicesManager.BatchMode これにより、デフォルトのAddressableAssetSettingsオブジェクトからホストサービス構成が読み込まれ、構成されているすべてのサービスが開始されます。 代替のAddressableAssetSettings構成を使用するには、独自の静的メソッド エントリポイントを作成し、 UnityEditor.AddressableAssets.HostingServicesManager.BatchMode(AddressableAssetSettings設定呼び出し)を介して再ロードします。 7.3カスタムサービス ホストサービスは拡張可能であるように設計されており、独自のカスタムロジックを実装して、Addressable Assets Systemからのコンテンツ読み込み要求を処理できます。例えば: (1)非HTTPプロトコルを使用して内容をダウンロードできることをサポートするカスタムIResourceProvider。 (2)CDNソリューションに一致するコンテンツを提供するために使用される外部プロセス(Apache HTTPサーバーなど)を管理します。 7.3.1カスタムサービスの実装 HostingServicesManagerは、IHostingServiceインターフェイスを実装する任意のクラスを管理できます(メソッドパラメーターと戻り値の詳細については、APIドキュメントを参照してください。 新しいカスタムサービスを作成するには: 1.上記の「新しいホストサービスの構成」セクションにリストされている手順に従いますが、Create>Localhostボタンを選択する代わりに、Create>CustomServiceボタンを選択します。 2.該当するスクリプトをそのフィールドにドラッグするか、オブジェクトセレクタから選択します。このダイアログは、選択したスクリプトがIHostingServiceインターフェースを実装していることを確認します。 3.サービスを追加したら、[追加]ボタンをクリックします。 次に、カスタムサービスがServiceTypeドロップダウンに表示されます。 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で続きを読む

Addressable Assets開発サイクル

六、Addressable Assets開発サイクル 6.1従来のアセット管理 Resourcesディレクトリでアセットを計画する場合、アセットはアプリケーションの初期パッケージに構築されることを意味しています。それに、Resources.Load のAPIのみを使用して、必要なコンテンツをロードするためのパスパラメーターを渡すことができます。アセットをどこでも参照するには、アセットを直接参照するか、AssetBundleとして参照する必要があります。AssetBundlesを使用している場合でも、アセットパスを渡し、ロード時に何らかの戦略に従ってそれらを組み合わせる必要があります。 AssetBundleがリモートであるか、一部の依存関係が他のAssetBundleにある場合は、それらのダウンロード、ロード、およびアンロードを管理するための独自のコードも作成する必要があります。 6.2#Addressableアセット管理 Assetにアドレスをバインドすると、その場所、ディレクトリ、またはアセットの作成方法に関係なく、そのアドレスからそのAssetを読み込むことができます。アドレス可能なアセットの名前を任意に変更しても問題ありません。ビルドまたはロードコードを変更せずに、アドレス可能なアセットをResourcesディレクトリに移動したり、ローカルビルド戦略からリモートまたは他のビルド戦略に切り替えたりすることもできます。 6.2.1 Asset group schemas Schemasはデータのセットを定義します。InspectorでSchemasをAsset Groupsにバインドできます。バインドされたSchemasグループは、その下のアセットのコンテンツをどのようにビルドするかを決定します。たとえば、BundledAssetGroupSchemaモードのグループは、Packed modeでビルドするときにAssetBundleのソースとして機能します。また、Schemasを組み合わせて、新しいGroupを定義するためのテンプレートにすることもできます。Schemasテンプレートは、AddressableAssetsSettingsのInspectorパネルから追加できます。 6.3ビルドスクリプト ビルドスクリプトは、プロジェクトでIDataBuilderインターフェイスを実装するためのScriptableObject assetsとして現れます。ユーザーは、独自のビルドスクリプトを作成し、Inspectorを介してそれらをAddressableAssetSettingsオブジェクトに追加できます。 Addressables Groupsウィンドウ(Window>AssetManagement>Addressables>group)でビルドスクリプトを適用するには、Play Mode Scriptを選択してから、ドロップダウンオプションを選択します。現在、完全なアプリケーションビルドをサポートするために3つのスクリプトが実装されており、エディターで反復するための3つの再生モードスクリプトが実装されています。 6.3.1 Play Mode Scripts Addressable Asset Packageには、3つのビルドスクリプトがあります。それはアプリケーション開発を加速させるために、Play Modeデータの作成に用いられます。 Use Asset Database(faster) Use Asset Database(BuildScriptFastMode)を使用すると、ゲームプレイ中にゲームをすばやく実行できます。Asset Databaseを介してAssetを直接ロードします。これにより、アナライザーやAssetBundleの作成を必要とせずに高速な反復が可能になります。 Simulate Groups(advanced) Simulate Groups Mode(BuildScriptVirtualMode)は、AssetBundleを作成せずにレイアウトと依存関係の内容を分析します。Assetは、パッケージを介してロードされたかのように、ResourceManagerを介してAssetDataBaseからロードされます。ゲームプレイ中にBundlesがロードまたはアンロードされるタイミングを確認するには、Addressablesイベントビューアーウィンドウ(Window>Asset Management>Addressable>Event Viewer)でAssetの使用状況を表示します。Simulate Groups Modeは、読み込み戦略をシミュレートし、コンテンツGroupsを調整して、制作とリリースの適切なバランスを見つけるのに役立ちます。 Use Existing Build(requires built groups) Use Existing Buildは、デプロイされたアプリケーションビルドに最も近いものですが、別のステップとしてデータをビルドする必要があります。このモードは、Assetが変更されていない場合に最速です。これは、Playモードに入るときにデータを処理しないためです。このモードのコBuild>New Build>Default Build Scriptを選択するか、ゲームスクリプトでAddressableAssetSettings.BuildPlayerContent()メソッドを使用して、Addressablesグループウィンドウ(Window>Asset Management>Addressable>group>group)でビルドする必要があります。 Choosing the right script Play Mode Scriptを適用するには、Addressables Groupsウィンドウメニュー(Window>AssetManagement>Addressable>group)からPlay Mode Scriptを選択してから、ドロップダウンオプションから選択します。開発および展開中、各モードには異なる時間とアセットの配置があります。次の表は、特定のモードが有効になる開発サイクルの段階を示しています。 6.4分析とデバッグ デフォルトでは、Addressable Assetsはwarnings and errorsログのみを記録します。ただし、詳細ログは、Player Settingウィンドウ(Edit > Project Settings… > Player>Player)を開いて、ログの詳細を展開します。そして、Other Settings > Configurationセクションに移動し、Scripting Define Symbolsフィールドに「ADDRESSABLES_LOG_ALL」を追加してから、 AddressableAssetSettingsオブジェクトInspectorのLogRuntimeExceptionオプションをオフにすることによって異常を無効にすることもできます。必要に応じて、独自の異常処理アプリケーションでResourceManager.ExceptionHandlerプロパティを実装できますが、これは、Addressablesがruntime initializationを完了した後に実行する必要があります(以下を参照)。 6.4.1オブジェクトの初期化 オブジェクトをAddressable Assets Settingsにバインドし、実行時に初期化アプリケーションに渡して処理します。CacheInitializationSettingsオブジェクトは、実行時にUnityのキャッシングAPIを制御します。独自の初期化オブジェクトを作成するには、IObjectInitializationDataProviderインターフェイスを実装するScriptableObjectを作成します。これは、作成・実行時にデータをシリアル化にするObjectInitializationDataシステムのEditorコンポーネントです。 6.5コンテンツ更新ワークフロー Unityは、ゲームコンテンツを次の2つのカテゴリに分類することをお勧めします。 (1)Staticコンテンツ、更新されない静的コンテンツ。 (2)Dynamicコンテンツ、更新を希望する動的コンテンツ。 この構造では、静的コンテンツはアプリケーションに付随し(またはインストール後すぐにダウンロードされ)、少数のより大きなBundlesに存在します。動的コンテンツはオンラインで存在し、更新ごとに必要なデータ量を最小限に抑えるために、より小さなパッケージであることが望ましいです。Addressable Assets Systemの目標の1つは、スクリプトを変更せずにこの構造を簡単に使用および変更できるようにすることです。 ただし、Addressable Assets Systemは、アプリのまったく新しいビルドをリリースしたくない場合など、「静的」コンテンツを変更する必要がある状況にも適用します。 また、リモートアップデートが許可されていない場合(現在の多くのビデオゲームコンソールやサーバーのないゲームなど)、毎回完全で新しいビルドを実行する必要があることに注意してください。 6.5.1仕組み Addressablesは、コンテンツディレクトリを使用して、アドレスを各Assetsにマップし、アドレスをロードする場所と方法を指定します。アプリケーションにマッピングを変更する機能を提供するには、元のアプリケーションがこのディレクトリのオンラインコピーについて知っている必要があります。この設定を行うには、AddressableAssetSettingsインスペクターで「BuildRemoteCatalog」設定を有効にします。これにより、ディレクトリのコピーが指定されたパスにビルドされ、そこからロードされます。アプリケーションが公開されると、このロードパスは変更できません。コンテンツ更新プロセスは、ディレクトリの新しいバージョン(同じファイル名)を作成し、以前に指定されたロードパスで対応するファイルを上書きします。 アプリケーションが構築されると、唯一のアプリケーションコンテンツバージョン文字列が生成されます。これらの文字列は各アプリケーションがどのようなコンテンツディレクトリをロードするかを決めます。特定のサーバーには、複数のバージョンのディレクトリを競合させることなく含めることができます。必要なデータをaddressables_content_state.binファイルに保存します。これには、バージョン文字列と、StaticContentとマークされたグループに含まれるAssetのハッシュ情報が含まれます。デフォルトでは、このファイルはAddressableAssetSettings.Assetファイルと同じフォルダーにあります。 addressables_content_state.binファイルには、Addressablesシステムの各StaticContent asset groupのハッシュおよび依存関係情報が含まれています。 StreamingAssetフォルダーに組み込まれているすべてのGroupsは、StaticContentとしてマークする必要があります。一部の大きなリモートGroupも、そのように指定できます。次のステップ(コンテンツ更新を準備する。以下の説明を参照。)中に、このハッシュ情報は、すべてのStaticContentグループに変更が必要なAssetsが含まれているかどうか、およびそれらのAssetsを別の場所に移動する必要があるかどうかを判断します。 6.5.2コンテンツ更新の準備 StaticContentグループのAssetsを変更した場合は、「Check for Content Update Restrictions」コマンドを実行する必要があります。これにより、変更されたassetが静的グループから取得され、新しいグループに移動されます。新しいAsset Groupsを生成します。 1.UnityのAddressablesGroupsウィンドウを開きます。 (Window > Asset Management > Addressables > Groups) 2.Addressables GroupsウィンドウのToolsメニューを選択し、Check for Content Update Restrictionsをクリックします。 3.開いたBuildDataFileダイアログで、addressables_content_state.binファイルを選択します。デフォルトでは、このファイルはAsset / AddressableAssetsDataProjectディレクトリにあります。 このデータは、アプリケーションが最後にビルドされてから変更されたAssetsまたは依存関係を判別するために使用されます。更新コンテンツの生成に備えて、システムはこれらのAssetsを新しいGroupに移動します。 注:すべての変更がnon-static groupsに制限されている場合、このコマンドは効きません。 重要:prepare operationを開始する前に、バージョン管理システムでブランチを作り、そこで操作することをお勧めします。prepare operationは、updating contentに適した方法でAsset Groupsを再配置します。ブランチを作ると、次に新しいplayerをリリースしたときに、カスタム設定にすばやくロールバックできるようになります。 6.5.3ビルドコンテンツの更新 コンテンツの更新を作成します。 1.UnityはAddressables Groupsウィンドウを開きます。(Window > Asset Management > Addressables > Groups) 2.Addressables Groupsウィンドウで、上部メニューのBuildを選択してから、Update a Previous Buildを選択します。 3.Build Data Fileダイアログが開いたら、既存APPによってビルドされたビルドディレクトリを選択します。このディレクトリには、addressables_content_state.binファイルが含まれている必要があります。 ビルドにより、コンテンツディレクトリ、Hashファイル、およびAsset Bundlesが生成されます。 生成されたコンテンツディレクトリは、選択されたアプリケーションによって生成されたディレクトリと同じ名前で、古いディレクトリとhashファイルを上書きします。アプリケーションはHashファイルをロードして、新しいディレクトリが使用可能かどうかを判断します。システムは、アプリに付属している、またはすでにダウンロードされている既存のAssetBundleから変更されていないAssetsトをロードします システムは、addressables_content_state.binファイルのコンテンツ、バージョン、文字列、および場所の情報を使用して、AssetBundleを作成します。更新されたコンテンツを含まないAssetBundleのファイル名は、元のファイル名と同じです。 AssetBundleに更新されたコンテンツが含まれている場合は、元のファイル名と共存する新しいファイル名を使用して、更新されたコンテンツを含む新しいAssetBundleを生成します。新しいファイル名のAssetBundleは、指定されたコンテンツホスティングの場所にコピーする必要があります。 システムは静的コンテンツのためにAssetBundleを構築することもできますが、それらを参照するAddressable Asset Entriesがないため、コンテンツホスティングのところにアップロードする必要はありません。 6.5.4コンテンツ更新の例 この例では、次のグループの概念を理解する必要があります。 バージョンがアクティブだったとき、一部のデバイスにLocal_Staticを持っていて、リモートパッケージをローカルにキャッシュしている場合もあります。 各Group(AssetA、AssetL、およびAssetX)からAssetを変更してから、Check for Content Update Restrictionsを実行すると、ローカルのAddressable設定の結果は次のようになります。 prepare operationは実際には静的グループを編集しているため、通常の理解と矛盾する可能性があることに注意してください。システムは上記のレイアウトを自動的に構築しますが、静的Groupsは結果を破棄します。したがって、プレイヤーの観点から次の結論を導き出します。 LocalStatic bundleはすでにプレーヤーのデバイスにあるため、変更できません。この古いバージョンのAssetAは参照されなくなりました。代わりに、ジャンクデータとしてプレーヤーのデバイスに残されます。 Remote_Staticbundleは同じままです。プレーヤーのデバイスにまだキャッシュされていない場合は、AssetMまたはAssetNが要求されたときにダウンロードされます。AssetAと同様に、この古いバージョンのAssetLは参照されなくなりました。 Remote_non-Staticパッケージは廃止されました。サーバーから削除することはできますが、どちらの方法でも、ここから再度ダウンロードされることはありません。すでにキャッシュされている場合は、永久にキャッシュに残ります。 AssetAやAssetLと同様に、この古いバージョンのAssetXは参照されなくなりました。 古いRemote_non-Staticbundleは、hashファイルによって区別される新しいバージョンに置き換えられます。変更されたAssetXバージョンは、この新しいBundleで更新されます。 content_update_groupのBundleは、参照され、変更されたAssetsで構成されます。 上記の例には次の意味があることに注意してください。 1.変更されたローカルAssetsは、ユーザーのデバイスに永続的に残り、未使用の状態を保持します。 2.ユーザーが非静的Bundleをキャッシュした場合、変更されていないAssets(AssetYやAssetzなど)を含め、バンドルを再ダウンロードする必要があります。理想的には、ユーザーはキャッシュされたBundleを持っていない場合、新しいRemote_non-Staticバンドルをダウンロードするだけで済みます。 3.ユーザーがStatic_Remote bundleをキャッシュしている場合は、更新されたAsset(この場合はcontent_update_groupを介したAssetLをダウンロードする)をダウンロードするだけで済みます。しかし、この状況は理想的です。ユーザーがBundleをキャッシュしていない場合は、content_update_groupを介して新しいAssetLをダウンロードし、参照されていないRemote_StaticBundleを介して現在は廃止されているAssetLをダウンロードする必要があります。初期のキャッシュ状態に関係なく、完了のある時点で、ユーザーはデバイス上で非アクティブ化されたAssetLを持ち、アクセスまたは参照されていないにもかかわらず、永続的にキャッシュされます。 リモートコンテンツの最適な設定は、それをどのように使用するかによって異なります。 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で続きを読む

Addressable Assets System

五、Addressable Assets Systemの概要 Unityは2018バージョンでAddressableAssets Systemのプレビューバージョンをリリースし、2019バージョンは公式バージョンになり、本番環境に移行できるようになりました。しかし実際には、私たちは早期採用者であり、その過程で多くの落とし穴に遭遇しました。落とし穴に入ったとき、Addressable Assets Systemはまだバージョン0.8でしたが、現在は1.3.3です。したがって、以降のコンテンツは最新バージョン1.3.3に基づいています。 Addressable Assets Systemは、文字通りにAddress可能なAssetsシステムを意味します。目的は、アセットを識別可能なAddressIDでバインドし、内部GUIDを介してアセットを管理することです。 5.X以降のアセットタグに触発されるべきであり(実際、かなり前にアセットストアに同様のBundlesプラグインがありました)、AssetBundlesは最終的に管理および生成されますが、これは実際には本質的な違いではありません。 Addressable Assets Systemは、次の2つの部分で構成されています。 (1)Address指定可能な資産ウィンドウ。 (2)スクリプト化されたビルド。 公式の観点からは、[Addressable Assets]ウィンドウがプライマリであり、スクリプト化されたビルドがセカンダリです。ただし、実際に使用した後は、多少説明がずれている場合があります。 [Addressable Assets]ウィンドウは開発中のアセットの管理と調整に使用され、スクリプト化されたビルドは自動ビルドに使用されます([Addressable Assets]ウィンドウはボタン操作も提供できます)。 5.1 Addressableをインストールする Addressableは、Package Managerからダウンロードできます。以下のように示しています。 注意:Addressable Asset Systemの実装環境はUnity2018.3以降である必要があります。PackageManagerに慣れていない場合は、公式ドキュメントを参照してください。 5.2知っておくべき用語 Addressable Assets Systemシステムを説明する前に、いくつかの用語や概念を理解する必要があります。 (1)Address:ランタイムインデックス作成用のAssetロケーション識別子。 (2)Addressable Asset Dataディレクトリ:プロジェクト内のすべてのAddressable Assetのメタデータを保存します。プロジェクトのAssetsディレクトリにあります。 (3)Asset Group:ビルド時に処理できるAddressable Assets。 (4)Asset Group Schema:生成中にGroupに割り当てて使用できるデータのグループを定義します。 (5)AssetReference:直接参照に似ていますが、初期化されるオブジェクトを遅延にします。AssetReferenceオブジェクトは、GUIDを必要に応じてロードできるAddressableなGUIDとして格納します。 (6)Asynchronous loading:ゲームコードを変更せずに、開発中にAssetの場所とその依存関係を変更できます。非同期ロードは、Addressable Assets Systemの基本的な実装です。 (7)Build script:Asset Groupプロセッサを実行してAssetsをパッケージ化し、AssetsマネージャーにAddressとAssetsの場所の間のマッピングを提供します。 (8)Label:実行時に類似項をロードするための追加のAddressable Asset識別子を提供します。たとえば(Addressables.DownloadDependenciesAsync(“spaceHazards”);)。 5.3Addressable Assetsを準備する方法 Address可能なAssetsの準備は、大きく2つの部分に分けられます。1つはAssetsの識別、もう1つはAssetsの構築です。 5.3.1Address可能な資産の特定 UnityEditorでAssetsをAddressableとしてマークする方法は2つあります。 (1)Object’s Inspector (2)Addressablesウィンドウ 5.3.1.1Inspectorウィンドウの使用 “Project”ウィンドウで、Assetを選択してそのInspectorを表示します。Inspectorで、「Address」チェックボックスをクリックして、希望の名前を入力します。 5.3.1.2Addressableウィンドウの使用 Window > Asset Management > Addressablesで、Addressablesウィンドウを開きます。識別したいassetをProjectウィンドウから、AddressablesにビルドされたAddressables Groupsにドラッグします。 5.3.2アドレスの指定 デフォルトのAddress名は、このAssetsからAssetsディレクトリへの相対パスです(例:Assets / images / myImage.png)。Assetsのアドレス名をAddressablesから変更する場合は、Assetsを右クリックしてRenameを選択します。 Addressable Assetsを初めて使用する場合、システムはプロジェクトの編集時および実行時の構成Assetsを自動的に作成します。ディレクトリはAssets / AddressableAssetsDataです。これらのディレクトリをバージョン管理に追加することをお勧めします。 5.3.3Address可能なコンテンツの構築 APPをビルドする前に、Addressables Asset Systemは、構成済みのAssetsコンテンツを実行時に使用できる形式にパッケージ化する必要があります。ただし、この手順は自動ではありません。Editorパネルまたはシステムが提供するAPIを介して操作できます。 (1)EditorがAddressableウィンドウを開き、Select Build > Build Player Contentを選択します。 (2)APIはAddressableAssetSettings.BuildPlayerContent()を使用します。 5.4Addressable Assetsの使用方法 5.4.1アドレスによるロードまたはインスタンス化 実行時にAddressable Assetをロードまたはインスタンス化できます。あるAssetが読み込まれると、そのすべての依存関係がメモリに追加されます。そのため、Assetsを通常どおりに使用できます。ただし、これによってAssetsが自動的にSceneに配置されるわけではないため、自分でインスタンス化する必要があります。 Addressablesが提供するインスタンス化インターフェースを呼び出すと、これらのAssetsが読み込まれ、すぐにSceneに追加されます。 スクリプトからAssetsにアクセスする場合は、string型のAddressを指定する必要があります。次に、UnityEngine.AddressableAssets名前空間で次のメソッドを呼び出します。 (1)Addressables.LoadAssetAsync( “AssetAddress”);指定されたアドレスにあるAssetsをロードします。 (2)Addressables.InstantiateAsync( “AssetAddress”);指定されたアドレスにあるAssetsをインスタンス化し、Sceneに追加します。 注:LoadAssetAsyncとInstantiateAsyncはどちらも非同期操作です。アセットのロードをコールするロジックに、コールバック関数を提供する必要があります。ロードが完了すると、このコールバック関数をコールします。 5.4.1.1 ###Sub-AssetsとComponents Sub-AssetsとComponentsは、Assetsロードの特殊なケースです。 (1)Components:現在、Addressableを介してGameObjectを直接ロードできないComponentsです。GameObjectをロードまたはインスタンス化する必要があります。その後、コンポーネント参照を検索します。Addressableを拡張してコンポーネントのロードをサポートする方法については、ComponentReferenceの例を参照してください。 (2)Sub-Assets:このシステムはSub-Assetsのロードをサポートしていますが、特別な文法が必要です。Sub-Assetsの例には、sprite sheetのspriteやFBXファイルのAnimation Clipsが含まれます。spritesを直接ロードする例については、spritesのロード例を参照してください。 Address:https://github.com/Unity-Technologies/Addressables-Sample/tree/master/Basic/Sprite%20Land AssetsからSub-Objectをロードするには、次のAPIを使用できます。 Addressables.LoadAssetAsync ( “MySpriteSheetAddress”); 個々のSub-Objectをロードする場合は、次のようなものを使用できます。 Addressables.LoadAssetAsync( “MySpriteSheetAddress [MySpriteName]”); Assetsで使用可能な名前は、メインのAddressablesGroupエディターウィンドウで表示できます。さらに、AssetReferenceを使用して、Assetsのサブオブジェクトにアクセスできます。 5.4.2 ## AssetReferenceクラスの使用 AssetReferenceクラスは、アドレスを知らなくてもAddressable Assetsにアクセスする方法を提供します。次の手順を実行できます。 1.SceneのHierarchyインターフェースまたはProjectインターフェースからGameObjectを選択します。 2.Inspectorインターフェイスで、Add Componentボタンをクリックし、Componentsタイプを選択します。シリアル化可能なComponentsはすべて、AssetReference変数(ゲームスクリプト、ScriptableObject、その他のシリアル化可能なクラスなど)をサポートできます。 3.ComponentsにpublicAssetReference変数を追加します(たとえば、public AssetReference explosion;)。 4.Inspectorで、オブジェクトにリンクするAddressable Assetを選択します。これは、AssetsをProjectウィンドウからpublic AssetReferenceフィールドにドラッグするか、プロジェクトで以前に定義したAddressable Assetsのドロップダウンリストから選択します(下図のように示す)。 AssetReferenceをロードまたはインスタンス化するには、対応するメソッドをコールします。例えば: AssetRefMember.LoadAssetAsync(); また AssetRefMember.InstantiateAsync(pos、rot); 注:通常のAddressable Assetsと同様に、LoadAssetAsyncとInstantiateAsyncは非同期操作です。Assetsのロードが完了したときにコールするためのコールバックを提供できます(詳細については、Async operation handlingセクションを参照してください)。 Sub-Assets(SpriteAtlasやFBXなど)を含むAssetsをAssetReferenceに追加する場合は、Assets自体またはSub-Assetsを参照することを選択できます。最初に表示されたドロップダウンリストは2つになります。1番目はAssets自体を選択し、2番目はSub-Assetsを選択します。これはメインAssetsへの参照として扱われます。 5.5ビルドの要件 5.5.1StreamingAssetsディレクトリのローカルアセット Addressable Asset Systemは、何をロードするか、どのようにロードするかを知るために、実行時にいくつかのファイルを必要とします。これらのファイルはAddressablesデータを生成し、StreamingAssetフォルダーに保存されます。このフォルダーは、前の章で説明したように、ビルド中のすべてのファイルを含むUnityの特別なフォルダーです。これらのファイルは、Addressableコンテンツが生成されるときにライブラリ内で段階的に処理されます。次に、アプリケーションがビルドされると、システムは必要なファイルをStreamingAssetにコピーし、それらを生成してソースフォルダーから削除します。このようにして、複数のプラットフォームのデータをビルドできますが、同時に、ビルドプラットフォームごとに関連データのコピーを1つだけ含めることができます。 Addressableの特定のデータに加えて、ローカルで使用するデータを作成するグループは、ライブラリプラットフォーム固有のキャッシュの場所も使用します。これが機能することを確認するには、ビルドパスとロードパスをそれぞれ構成ファイル変数として設定する必要があります。つまり、[UnityEngine.AddressableAssets.Addressables.BuildPath]と{UnityEngine.AddressableAssets.Addressables.RuntimePath}から始めます。AddressableAssettingsのInspectorでこれらの設定を指定できます(デフォルトでは、このオブジェクトはプロジェクトのAssets / AddressableAssetsDataディレクトリにあります)。 5.5.2事前ダウンロード Addressables.DownloadDependenciesAsync()メソッドをコールすると、渡されたアドレスまたはラベルの依存関係がロードされます。通常、依存関係はAssetBundleを参照します。 このコールバックによったAsyncOperationHandle構造には、PercentCompleteプロパティが含まれています。それにより、ダウンロードの進行状況を監視や表示することができます。コンテンツのロードが完了するまでアプリケーションを待機させることもできます。 ダウンロードする前にユーザーの同意を取得する場合は、Addressables.GetDownloadSize()を使用して、特定のアドレスまたはラベルからコンテンツをダウンロードするために必要なスペースを返します。ただし、UnityのAssetBundleキャッシュに残っているため、以前にダウンロードしたBundlesがチェックされることに注意してください。 ほとんどの場合、アプリのAssetを事前にダウンロードしておくと便利ですが、ダウンロードしないことを選択する場合もあります。例えば: (1)アプリケーションに多くのオンラインコンテンツがあり、通常、ユーザーがその一部のみを操作するようにしたい場合。 (2)インターネットに接続してから機能できるアプリケーションに対しては、アプリのコンテンツ全体が小さなパッケージの場合は、必要に応じてコンテンツをダウンロードすることを選択できます。 5.5.3マルチプラットフォームビルド Addressable Asset Systemは、アプリケーションコンテンツをビルドするときに、Addressable Assetを含むAssetBundlesを生成します。AssetBundleはプラットフォームに依存するため、サポートしたいプラットフォームごとに再構築する必要があります。 デフォルトでは、Addressablesアプリケーションデータをビルドする場合、特定のプラットフォームのデータは、Addressableビルドパスのプラットフォームにある特定のサブディレクトリに格納されます。ランタイムパスは、これらのプラットフォームフォルダーを記述し、該当するアプリケーションデータを指します。 注:Addressables BuildScriptPackedPlayModeスクリプトがEditor Playモードで使用されている場合、Addressableは現在アクティブなビルドターゲットからデータを読み込もうとします。したがって、現在のビルドターゲットデータが現在のエディタープラットフォームと互換性がない場合、問題が発生する可能性があります。 5.6Addressables systemへのアップグレード このシステムはとても使いやすいので、既存のプロジェクトをアップグレードできますか?既存のAssetBundlesはどのように処理しますか?方法があります。 5.6.1直接参照 この方法から移行するには、次の手順に従います。 (1)オブジェクトへの直接参照をAssetReferencesに置き換えます(たとえば、public GameObjectDirectRefMenger;はpublicAssetReference AssetRefMember;になります)。 (2)Assetsを直接参照のように、適切なコンポーネントのInspectorにドラッグします。 (3)文字列名ではなくオブジェクトに基づいてAssetsをロードする場合は、設定で作成されたAssetReferenceオブジェクトから直接インスタンス化します(例:AssetRefMember.LoadAssetAsync();またはAssetRefMember.InstantiateAsync(pos、ROT);)。注:Addressable Assets systemは、Assetsを非同期でロードします。アセット参照への直接参照を更新する場合は、非同期操作を合わせるためにコードも更新する必要があります。 5.6.2Resourceフォルダ Resourcesデータフォルダ内のAssetがAddressableとしてマークされている場合、システムはResourcesフォルダ内のAssetをプロジェクト内のResources_Moveという新しいフォルダに自動的に移動します。Assetsを移動するためのデフォルトのアドレスは、フォルダ名を省略した古いパスです。たとえば、ロードコードがResources.LoadAsync( “desert / trunk.prefab”);からAddressables.LoadAssetAsync( “desert /tank.prefab”);に変更される場合があります。 5.6.3AssetBundles AAddressables Groupウィンドウを開くと、UnityはすべてのAssetBundlesをAddressable Asset Groupsに変換します。これは、コードを移行する最も簡単な方法です。 Assetsを手動で変換する場合は、“Ignore”ボタンをクリックします。次に、前述の直接参照またはResourceフォルダーの方法を使用します。 Assetsアドレスのデフォルトのパスは、そのファイルのパスです。そのパスをAssetsのアドレスとして使用する場合は、パッケージからAssetsをロードするのと同じ方法でAssetsをロードします。Addressable Asset Systemは、パッケージとそのすべての依存関係のロードを処理します。 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】EventTrigger を拡張して、ダブルタップ・フリック・ロングタップを簡単に扱えるようにした

概要 Unity の EventTrigger でダブルタップ・フリック・ロングタップを扱えるクラス EventTriggerAdvance を作りました。 更に、それを拡張メソッドで簡潔に扱える拡張クラス EventTriggerExtension も作りました。 不具合や改善点等がありましたら、コメントをお願いします。 使い方 // ダブルクリック eventTrigger.AddPointerDoubleClickListener((pointerEventData) => {/* 処理 */}, tolerance : 0.5f); // tolerance : この値より入力が遅いと、無効になります(省略可能) // ロングタップ eventTrigger.AddPointerLongPressListener((pointerEventData) => {/* 処理 */}, tooFastToUp : 0.5f); // tooFastToUp : この値より速くボタンを離すと、無効になります(省略可能) // フリック eventTrigger.AddPointerFlickListener((flickEventData) => {/* 処理 */}, float tooShortSqrDistance : 500f, float tooLateToUp : 0.5f, float tooFastToUp : 0f) // tooShortSqrDistance : この値よりフリック距離の平方根の方が短いと、無効になります(省略可能) // tooLateToUp : この値より長くボタンを押すと、無効になります(省略可能) // tooFastToUp : この値より速くボタンを離すと、無効になります(省略可能) FlickEventData FlickEventData は PointerEventData を継承したクラスで、AddPointerFlickListener の実行時に取得できます。FlickEventData には便利な関数とプロパティが追加されています。 角度の取得.cs // -180f から 180f の範囲の角度 float angle = flickEventData.angle; // 0 : 右 // 90 : 上 // -135 : 左下 // 触れてから離すまでのベクトル Vector2 deltaPosition = flickEventData.deltaPosition; 角度の判定.cs if (flickEventData.IsRight) Debug.Log("右方向にフリックされた"); // 上記のプロパティの許容角度は±45度です // 最後に Side が付いているプロパティは、許容角度が±90度になっています if (flickEventData.IsRightSide) Debug.Log("右側にフリックされた"); // 斜め方向のプロパティ if (flickEventData.IsTopLeft) Debug.Log("左上方向にフリックされた"); // 角度を指定して判定 if (flickEventData.CompareAngle(angle : 135f, range : 22.5f) Debug.Log("北北西方向にフリックされた"); // 角度の差を取得 float diff = flickEventData.DiffAngle(angle); // 角度の差が時計回りなら負の値、反時計回りなら正の値 float signedDiff = flickEventData.SignedDiffAngle(angle); コード EventTriggerAdvance.cs EventTriggerAdvance.cs using System; namespace UnityEngine.EventSystems { public class EventTriggerAdvance { /// <summary> /// Execute when double-clicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tolerance">If the double-click time is longer than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerDoubleClickListener(EventTrigger trigger, Action<PointerEventData> action, float tolerance = 0.75f) { byte stage = 0; double time = 0, subTime = 0; EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerDown; entry.callback.AddListener((eventData) => { if (stage == 2 && Time.unscaledTimeAsDouble - time < tolerance) { stage = 3; subTime = Time.unscaledTimeAsDouble; return; } stage = 1; time = Time.unscaledTimeAsDouble; }); trigger.triggers.Add(entry); entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerUp; entry.callback.AddListener((eventData) => { if (stage == 1) { if (Time.unscaledTimeAsDouble - time < tolerance) stage = 2; else stage = 0; } else if (stage == 3) { if (Time.unscaledTimeAsDouble - time < tolerance) { stage = 0; action?.Invoke(eventData as PointerEventData); return; } stage = 2; time = subTime; } else { stage = 0; } }); trigger.triggers.Add(entry); /* If you enable this code, the double-click decision will be strict. entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerExit; entry.callback.AddListener((eventData) => { stage = 0; first = double.NegativeInfinity; second = double.NegativeInfinity; }); trigger.triggers.Add(entry); // */ return entry; } /// <summary> /// Execute when long pressed. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tooFastToUp">If the tap interval is longer than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerLongPressListener(EventTrigger trigger, Action<PointerEventData> action, float tooFastToUp = 0.5f) { double time = 0; EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerDown; entry.callback.AddListener((eventData) => { time = Time.unscaledTimeAsDouble; }); trigger.triggers.Add(entry); entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerUp; entry.callback.AddListener((eventData) => { double duration = Time.unscaledTimeAsDouble - time; if (duration >= tooFastToUp) action?.Invoke(eventData as PointerEventData); }); trigger.triggers.Add(entry); entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerExit; entry.callback.AddListener((eventData) => { time = double.MinValue; }); trigger.triggers.Add(entry); return entry; } /// <summary> /// Execute when flicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tooShortSqrDistance">If the flick sqrt distance is shorter than this value, it will be disabled.</param> /// <param name="tooLateToUp">If the flick time is slower than this value, it will be disabled.</param> /// <param name="tooFastToUp">If the flick interval is faster than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerFlickListener(EventTrigger trigger, Action<FlickEventData> action, float tooShortSqrDistance = 500f, float tooLateToUp = 0.5f, float tooFastToUp = 0f) { double time = 0; EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerDown; entry.callback.AddListener((eventData) => { time = Time.unscaledTimeAsDouble; }); trigger.triggers.Add(entry); entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerUp; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; if ((data.position - data.pressPosition).sqrMagnitude >= tooShortSqrDistance) { float duration = (float)(Time.unscaledTimeAsDouble - time); if (duration >= tooFastToUp && duration <= tooLateToUp) { action?.Invoke(new FlickEventData(null, data)); } } }); trigger.triggers.Add(entry); return entry; } } } FlickEventData.cs FlickEventData.cs namespace UnityEngine.EventSystems { public class FlickEventData : PointerEventData { public readonly Vector2 deltaPosition; public readonly float angle; public FlickEventData(EventSystem eventSystem, PointerEventData data) : base(eventSystem) { deltaPosition = data.position - data.pressPosition; angle = Vector2.SignedAngle(Vector2.right, deltaPosition); } #region Angle /// <summary> /// Compare if the angle is within range. /// </summary> /// <param name="angle">Between -180 and 180<br/>Right:0<br/>Top:90<br/>Left:180<br/>Bottom:-90</param> /// <param name="range">Between 0 and 180</param> public bool CompareAngle(float angle, float range) { return DiffAngle(angle) <= range; } /// <summary> /// Return the difference in angle. /// </summary> /// <param name="angle">Between -180 and 180</param> /// <returns>Angle difference</returns> public float DiffAngle(float angle) { float dist = Mathf.Repeat(this.angle - angle + 360f, 360f); if (dist > 180f) return 360f - dist; return dist; } /// <summary> /// Return the difference in signed angle. /// </summary> /// <param name="angle">Between -180 and 180</param> /// <returns>Signed angle difference</returns> public float SignedDiffAngle(float angle) { float dist = Mathf.Repeat(this.angle - angle + 360f, 360f); dist = dist > 180f ? 360f - dist : dist; if ((this.angle - angle >= 0f && this.angle - angle <= 180f) || (this.angle - angle <= -180f && this.angle - angle >= -360f)) { return dist; } return -dist; } public bool IsRightSide { get { return Mathf.Abs(angle) < 90f; } } public bool IsTopSide { get { return angle >= 0f; } } public bool IsLeftSide { get { return !IsRightSide; } } public bool IsBottomSide { get { return !IsTopSide; } } public bool IsTopRightSide { get { return Mathf.Abs(angle - 45f) < 90f; } } public bool IsTopLeftSide { get { return CompareAngle(135f, 90f); } } public bool IsBottomRightSide { get { return Mathf.Abs(angle + 45f) < 90f; } } public bool IsBottomLeftSide { get { return CompareAngle(-135f, 90f); } } public bool IsRight { get { return Mathf.Abs(angle) < 45f; } } public bool IsTop { get { return angle >= 45f && angle < 135f; } } public bool IsBottom { get { return angle < -45f && angle >= -135f; } } public bool IsLeft { get { return Mathf.Abs(angle) > 135f; } } public bool IsTopRight { get { return IsTopSide && IsRightSide; } } public bool IsTopLeft { get { return IsTopSide && IsLeftSide; } } public bool IsBottomRight { get { return IsBottomSide && IsRightSide; } } public bool IsBottomLeft { get { return IsBottomSide && IsLeftSide; } } #endregion public float Magnitude { get { return deltaPosition.magnitude; } } public float SqrMagnitude { get { return deltaPosition.sqrMagnitude; } } } } public bool IsTopSide { get { return angle >= 0f; } } public bool IsLeftSide { get { return !IsRightSide; } } public bool IsBottomSide { get { return !IsTopSide; } } public bool IsTopRightSide { get { return Mathf.Abs(angle - 45f) < 90f; } } public bool IsTopLeftSide { get { return CompareAngle(TopLeft, 90f); } } public bool IsBottomRightSide { get { return Mathf.Abs(angle + 45f) < 90f; } } public bool IsBottomLeftSide { get { return CompareAngle(BottomLeft, 90f); } } public bool IsRight { get { return Mathf.Abs(angle) < 45f; } } public bool IsTop { get { return angle >= 45f && angle < 135f; } } public bool IsBottom { get { return angle < -45f && angle >= -135f; } } public bool IsLeft { get { return Mathf.Abs(angle) > 135f; } } public bool IsTopRight { get { return IsTopSide && IsRightSide; } } public bool IsTopLeft { get { return IsTopSide && IsLeftSide; } } public bool IsBottomRight { get { return IsBottomSide && IsRightSide; } } public bool IsBottomLeft { get { return IsBottomSide && IsLeftSide; } } #endregion } } EventTriggerExtension.cs EventTriggerExtension.cs using System; namespace UnityEngine.EventSystems { public static class EventTriggerExtension { public static EventTrigger.Entry AddListener(this EventTrigger trigger, EventTriggerType type, Action<BaseEventData> action) { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener((eventData) => action.Invoke(eventData)); trigger.triggers.Add(entry); return entry; } public static EventTrigger.Entry AddPointerListener(this EventTrigger trigger, EventTriggerType type, Action<PointerEventData> action) { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener(eventData => action?.Invoke(eventData as PointerEventData)); trigger.triggers.Add(entry); return entry; } public static EventTrigger.Entry AddAxisListener(this EventTrigger trigger, EventTriggerType type, Action<AxisEventData> action) { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener(eventData => action?.Invoke(eventData as AxisEventData)); trigger.triggers.Add(entry); return entry; } public static EventTrigger.Entry AddListener<T>( this EventTrigger trigger, EventTriggerType type, Action<T> action) where T : BaseEventData { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener(eventData => action?.Invoke(eventData as T)); trigger.triggers.Add(entry); return entry; } public static bool RemoveListener(this EventTrigger trigger, EventTrigger.Entry entry) { if (trigger != null && entry != null && trigger.triggers.Contains(entry)) { trigger.triggers.Remove(entry); return true; } return false; } #region PointerEventData public static EventTrigger.Entry AddPointerClickListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerClick, action); public static EventTrigger.Entry AddPointerDownListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerDown, action); public static EventTrigger.Entry AddPointerUpListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerUp, action); public static EventTrigger.Entry AddPointerEnterListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerEnter, action); public static EventTrigger.Entry AddPointerExitListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerExit, action); public static EventTrigger.Entry AddBeginDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.BeginDrag, action); public static EventTrigger.Entry AddDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.Drag, action); public static EventTrigger.Entry AddEndDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.EndDrag, action); public static EventTrigger.Entry AddDropListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.Drop, action); public static EventTrigger.Entry AddScrollListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.Scroll, action); public static EventTrigger.Entry AddInitializePotentialDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.InitializePotentialDrag, action); #endregion #region BaseEventData public static EventTrigger.Entry AddUpdateSelectedListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.UpdateSelected, action); public static EventTrigger.Entry AddSelectListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Select, action); public static EventTrigger.Entry AddDeselectListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Deselect, action); public static EventTrigger.Entry AddSubmitListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Submit, action); public static EventTrigger.Entry AddCancelListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Cancel, action); #endregion #region AxisEventData public static EventTrigger.Entry AddMoveListener(this EventTrigger trigger, Action<AxisEventData> action) => AddAxisListener(trigger, EventTriggerType.Move, action); #endregion #region PointerEventData(Advance) /// <summary> /// Execute when long pressed. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="interval">If the tap interval is longer than this value, it will be disabled.</param> /// <param name="tooLateToUp">If the tap is slower than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerLongPressListener(this EventTrigger trigger, Action<PointerEventData> action, float tooFastToUp = 0.5f) { return EventTriggerAdvance.AddPointerLongPressListener(trigger, action, tooFastToUp); } /// <summary> /// Execute when double-clicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tolerance">If the tap interval is longer than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerDoubleClickListener(this EventTrigger trigger, Action<PointerEventData> action, float tolerance = 0.75f) { return EventTriggerAdvance.AddPointerDoubleClickListener(trigger, action, tolerance); } /// <summary> /// Execute when flicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tooShortSqrDistance">If the flick sqrt distance is shorter than this value, it will be disabled.</param> /// <param name="tooFastToUp">If the tap interval is longer than this value, it will be disabled.</param> /// <param name="tooLateToUp">If the tap is slower than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerFlickListener(this EventTrigger trigger, Action<FlickEventData> action, float tooShortSqrDistance = 500f, float tooLateToUp = 0.5f, float tooFastToUp = 0f) { return EventTriggerAdvance.AddPointerFlickListener(trigger, action, tooShortSqrDistance, tooLateToUp, tooFastToUp); } #endregion } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】ダブルタップ・ロングタップ・フリックを簡潔に実装できるEventTriggerの拡張メソッド

概要 Unity の EventTrigger でダブルタップ・ロングタップ・フリックを扱えるクラス EventTriggerAdvance を作りました。 更に、それを拡張メソッドで簡潔に扱える拡張クラス EventTriggerExtension も作りました。 不具合や改善点等がありましたら、コメントをお願いします。 使い方 // ダブルクリック eventTrigger.AddPointerDoubleClickListener((pointerEventData) => {/* 処理 */}, tolerance : 0.5f); // tolerance : この値より入力が遅いと、無効になります(省略可能) // ロングタップ eventTrigger.AddPointerLongPressListener((pointerEventData) => {/* 処理 */}, tooFastToUp : 0.5f); // tooFastToUp : この値より速く離すと、無効になります(省略可能) // フリック eventTrigger.AddPointerFlickListener((pointerEventData, flickAngle) => {/* 処理 */}, float tooShortSqrDistance : 500f, float tooLateToUp : 0.5f, float tooFastToUp : 0f) // tooShortSqrDistance : この値よりフリック距離の平方根の方が短いと、無効になります(省略可能) // tooLateToUp : この値より長く押すと、無効になります(省略可能) // tooFastToUp : この値より速く離すと、無効になります(省略可能) // ドラッグ移動・回転・拡縮 eventTrigger.AddDragToMoveListener(transform, (pointerEventData, pos) => transform.position = pos); eventTrigger.AddDragToRotateListener(Camera.main, transform, (pointerEventData, rot) => transform.rotation = Quaternion.Euler(Vector3.forward * rot)); eventTrigger.AddDragToScaleListener(transform, (pointerEventData, scale) => transform.localScale = Vector3.one * scale); // 引数に Camera を入れなければ UGUI 向けの処理、Camera を追加すると Sprite 向けの処理になります FlickAngle FlickAngle にはフリックした角度の情報が入っており、AddPointerFlickListener の実行時に取得できます。 // -180f から 180f の範囲の角度を取得 float angle = flickAngle.angle; // 0 : 右 // 90 : 上 // -135 : 左下 if (flickAngle.IsRight) Debug.Log("右方向にフリックされた"); // 上記のプロパティの許容角度は±45度です // 最後に Side が付いているプロパティは、許容角度が±90度になっています if (flickAngle.IsRightSide) Debug.Log("右側にフリックされた"); // 斜め方向のプロパティ if (flickAngle.IsTopLeft) Debug.Log("左上方向にフリックされた"); // 角度を指定して判定 if (flickAngle.CompareAngle(angle : 135f, range : 22.5f)) Debug.Log("北北西方向にフリックされた"); // 角度の差を取得 float diff = flickAngle.DiffAngle(angle); // 角度の差が時計回りなら負の値、反時計回りなら正の値 float signedDiff = flickAngle.SignedDiffAngle(angle); コード EventTriggerAdvance.cs EventTriggerAdvance.cs using System; using UnityEngine; namespace UnityEngine.EventSystems { public static class EventTriggerAdvance { /// <summary> /// Execute when double-clicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tolerance">If the double-click time is longer than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerDoubleClickListener(EventTrigger trigger, Action<PointerEventData> action, float tolerance = 0.75f) { byte stage = 0; double time = 0, subTime = 0; // PointerDown EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerDown; entry.callback.AddListener((eventData) => { if (stage == 2 && Time.unscaledTimeAsDouble - time < tolerance) { stage = 3; subTime = Time.unscaledTimeAsDouble; return; } stage = 1; time = Time.unscaledTimeAsDouble; }); trigger.triggers.Add(entry); //PointerUp entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerUp; entry.callback.AddListener((eventData) => { if (stage == 1) { if (Time.unscaledTimeAsDouble - time < tolerance) stage = 2; else stage = 0; } else if (stage == 3) { if (Time.unscaledTimeAsDouble - time < tolerance) { stage = 0; action?.Invoke(eventData as PointerEventData); return; } stage = 2; time = subTime; } else { stage = 0; } }); trigger.triggers.Add(entry); /* // If you enable this code, the double-click decision will be strict. // PointerExit entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerExit; entry.callback.AddListener((eventData) => { stage = 0; first = double.NegativeInfinity; second = double.NegativeInfinity; }); trigger.triggers.Add(entry); // */ return entry; } /// <summary> /// Execute when long pressed. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tooFastToUp">If the tap interval is longer than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerLongPressListener(EventTrigger trigger, Action<PointerEventData> action, float tooFastToUp = 0.5f) { double time = 0; // PointerDown EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerDown; entry.callback.AddListener((eventData) => { time = Time.unscaledTimeAsDouble; }); trigger.triggers.Add(entry); // PointerUp entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerUp; entry.callback.AddListener((eventData) => { double duration = Time.unscaledTimeAsDouble - time; if (duration >= tooFastToUp) action?.Invoke(eventData as PointerEventData); }); trigger.triggers.Add(entry); // PointerExit entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerExit; entry.callback.AddListener((eventData) => { time = double.MinValue; }); trigger.triggers.Add(entry); return entry; } /// <summary> /// Execute when flicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tooShortSqrDistance">If the flick sqrt distance is shorter than this value, it will be disabled.</param> /// <param name="tooLateToUp">If the flick time is slower than this value, it will be disabled.</param> /// <param name="tooFastToUp">If the flick interval is faster than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerFlickListener(EventTrigger trigger, Action<PointerEventData, FlickAngle> action, float tooShortSqrDistance = 500f, float tooLateToUp = 0.5f, float tooFastToUp = 0f) { double time = 0; // PointerDown EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerDown; entry.callback.AddListener((eventData) => { time = Time.unscaledTimeAsDouble; }); trigger.triggers.Add(entry); // PointerUp entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerUp; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; if ((data.position - data.pressPosition).sqrMagnitude >= tooShortSqrDistance) { float duration = (float)(Time.unscaledTimeAsDouble - time); if (duration >= tooFastToUp && duration <= tooLateToUp) { action?.Invoke(data, new FlickAngle(data.position, data.pressPosition)); } } }); trigger.triggers.Add(entry); return entry; } #region Drag to transform /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can move it by dragging. /// </summary> public static EventTrigger.Entry AddDragToMoveListenerUI(EventTrigger trigger, Transform transform, Action<PointerEventData, Vector2> action = null) { Vector2 beginPos = Vector2.zero; // BeginDrag EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.BeginDrag; entry.callback.AddListener((eventData) => { beginPos = transform.position; }); trigger.triggers.Add(entry); // Drag entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.Drag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; action?.Invoke(data, beginPos + data.position - data.pressPosition); }); trigger.triggers.Add(entry); return entry; } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can rotate it by dragging. /// </summary> public static EventTrigger.Entry AddDragToRotateListenerUI(EventTrigger trigger, Transform transform, Action<PointerEventData, float> action = null) { Vector2 beginVector = Vector2.zero; float beginAngle = 0f; // BeginDrag EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.BeginDrag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; beginAngle = transform.rotation.eulerAngles.z; beginVector = (Vector2)transform.position - data.pressPosition; }); trigger.triggers.Add(entry); // Drag entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.Drag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; Vector2 deltaPos = data.position - data.pressPosition; action?.Invoke(data, beginAngle + Vector2.SignedAngle(beginVector, beginVector - deltaPos)); }); trigger.triggers.Add(entry); return entry; } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can scale it by dragging. /// </summary> public static EventTrigger.Entry AddDragToScaleListenerUI(EventTrigger trigger, Transform transform, Action<PointerEventData, float> action = null) { float dividedBeginMagnitude = 0f; float beginScale = 1f; // BeginDrag EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.BeginDrag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; beginScale = transform.localScale.z; dividedBeginMagnitude = 1f / Mathf.Max(0.0000001f, ((Vector2)transform.position - data.pressPosition).magnitude); }); trigger.triggers.Add(entry); // Drag entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.Drag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; action?.Invoke(data, beginScale * ((Vector2)transform.position - data.position).magnitude * dividedBeginMagnitude); }); trigger.triggers.Add(entry); return entry; } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can move it by dragging. /// </summary> public static EventTrigger.Entry AddDragToMoveListenerSprite(EventTrigger trigger, Camera camera, Transform transform, Action<PointerEventData, Vector2> action = null) { Vector3 beginScreenPos = Vector2.zero; // BeginDrag EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.BeginDrag; entry.callback.AddListener((eventData) => { Vector3 tempPos = camera.WorldToScreenPoint(transform.position); tempPos.z = transform.position.z; beginScreenPos = tempPos; }); trigger.triggers.Add(entry); // Drag entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.Drag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; Vector3 deltaPos = data.position - data.pressPosition; deltaPos.z = beginScreenPos.z; action?.Invoke(data, camera.ScreenToWorldPoint(beginScreenPos + deltaPos)); }); trigger.triggers.Add(entry); return entry; } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can rotate it by dragging. /// </summary> public static EventTrigger.Entry AddDragToRotateListenerSprite(EventTrigger trigger, Camera camera, Transform transform, Action<PointerEventData, float> action = null) { Vector2 beginVector = Vector2.zero; float beginAngle = 0f; // BeginDrag EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.BeginDrag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; beginAngle = transform.rotation.eulerAngles.z; beginVector = (Vector2)camera.WorldToScreenPoint(transform.position) - data.pressPosition; }); trigger.triggers.Add(entry); // Drag entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.Drag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; Vector2 deltaPos = data.position - data.pressPosition; action?.Invoke(data, beginAngle + Vector2.SignedAngle(beginVector, beginVector - deltaPos)); }); trigger.triggers.Add(entry); return entry; } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can scale it by dragging. /// </summary> public static EventTrigger.Entry AddDragToScaleListenerSprite(EventTrigger trigger, Camera camera, Transform transform, Action<PointerEventData, float> action = null) { float dividedBeginMagnitude = 0f; float beginScale = 1f; // BeginDrag EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.BeginDrag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; beginScale = transform.localScale.z; dividedBeginMagnitude = 1f / Mathf.Max(0.0000001f, ((Vector2)camera.WorldToScreenPoint(transform.position) - data.pressPosition).magnitude); }); trigger.triggers.Add(entry); // Drag entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.Drag; entry.callback.AddListener((eventData) => { PointerEventData data = eventData as PointerEventData; action?.Invoke(data, beginScale * ((Vector2)camera.WorldToScreenPoint(transform.position) - data.position).magnitude * dividedBeginMagnitude); }); trigger.triggers.Add(entry); return entry; } #endregion } } FlickAngle.cs FlickAngle.cs namespace UnityEngine.EventSystems { public class FlickAngle { public readonly float angle; public FlickAngle(Vector2 position, Vector2 pressPosition) { angle = Vector2.SignedAngle(Vector2.right, position - pressPosition); } /// <summary> /// Compare if the angle is within range. /// </summary> /// <param name="angle">Between -180 and 180<br/>Right:0<br/>Top:90<br/>Left:180<br/>Bottom:-90</param> /// <param name="range">Between 0 and 180</param> public bool CompareAngle(float angle, float range) { return DiffAngle(angle) <= range; } /// <summary> /// Return the difference in angle. /// </summary> /// <param name="angle">Between -180 and 180</param> /// <returns>Angle difference</returns> public float DiffAngle(float angle) { float dist = Mathf.Repeat(this.angle - angle + 360f, 360f); if (dist > 180f) return 360f - dist; return dist; } /// <summary> /// Return the difference in signed angle. /// </summary> /// <param name="angle">Between -180 and 180</param> /// <returns>Signed angle difference</returns> public float SignedDiffAngle(float angle) { float dist = Mathf.Repeat(this.angle - angle + 360f, 360f); dist = dist > 180f ? 360f - dist : dist; if ((this.angle - angle >= 0f && this.angle - angle <= 180f) || (this.angle - angle <= -180f && this.angle - angle >= -360f)) { return dist; } return -dist; } public bool IsRightSide { get { return Mathf.Abs(angle) < 90f; } } public bool IsTopSide { get { return angle >= 0f; } } public bool IsLeftSide { get { return !IsRightSide; } } public bool IsBottomSide { get { return !IsTopSide; } } public bool IsTopRightSide { get { return Mathf.Abs(angle - 45f) < 90f; } } public bool IsTopLeftSide { get { return CompareAngle(135f, 90f); } } public bool IsBottomRightSide { get { return Mathf.Abs(angle + 45f) < 90f; } } public bool IsBottomLeftSide { get { return CompareAngle(-135f, 90f); } } public bool IsRight { get { return Mathf.Abs(angle) < 45f; } } public bool IsTop { get { return angle >= 45f && angle < 135f; } } public bool IsBottom { get { return angle < -45f && angle >= -135f; } } public bool IsLeft { get { return Mathf.Abs(angle) > 135f; } } public bool IsTopRight { get { return IsTopSide && IsRightSide; } } public bool IsTopLeft { get { return IsTopSide && IsLeftSide; } } public bool IsBottomRight { get { return IsBottomSide && IsRightSide; } } public bool IsBottomLeft { get { return IsBottomSide && IsLeftSide; } } } } EventTriggerExtension.cs EventTriggerExtension.cs using System; namespace UnityEngine.EventSystems { public static class EventTriggerExtension { public static EventTrigger.Entry AddListener(this EventTrigger trigger, EventTriggerType type, Action<BaseEventData> action) { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener((eventData) => action.Invoke(eventData)); trigger.triggers.Add(entry); return entry; } public static EventTrigger.Entry AddPointerListener(this EventTrigger trigger, EventTriggerType type, Action<PointerEventData> action) { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener(eventData => action?.Invoke(eventData as PointerEventData)); trigger.triggers.Add(entry); return entry; } public static EventTrigger.Entry AddAxisListener(this EventTrigger trigger, EventTriggerType type, Action<AxisEventData> action) { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener(eventData => action?.Invoke(eventData as AxisEventData)); trigger.triggers.Add(entry); return entry; } public static EventTrigger.Entry AddListener<T>( this EventTrigger trigger, EventTriggerType type, Action<T> action) where T : BaseEventData { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener(eventData => action?.Invoke(eventData as T)); trigger.triggers.Add(entry); return entry; } public static bool RemoveListener(this EventTrigger trigger, EventTrigger.Entry entry) { if (trigger != null && entry != null && trigger.triggers.Contains(entry)) { trigger.triggers.Remove(entry); return true; } return false; } #region PointerEventData public static EventTrigger.Entry AddPointerClickListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerClick, action); public static EventTrigger.Entry AddPointerDownListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerDown, action); public static EventTrigger.Entry AddPointerUpListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerUp, action); public static EventTrigger.Entry AddPointerEnterListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerEnter, action); public static EventTrigger.Entry AddPointerExitListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.PointerExit, action); public static EventTrigger.Entry AddBeginDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.BeginDrag, action); public static EventTrigger.Entry AddDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.Drag, action); public static EventTrigger.Entry AddEndDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.EndDrag, action); public static EventTrigger.Entry AddDropListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.Drop, action); public static EventTrigger.Entry AddScrollListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.Scroll, action); public static EventTrigger.Entry AddInitializePotentialDragListener(this EventTrigger trigger, Action<PointerEventData> action) => AddPointerListener(trigger, EventTriggerType.InitializePotentialDrag, action); #endregion #region BaseEventData public static EventTrigger.Entry AddUpdateSelectedListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.UpdateSelected, action); public static EventTrigger.Entry AddSelectListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Select, action); public static EventTrigger.Entry AddDeselectListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Deselect, action); public static EventTrigger.Entry AddSubmitListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Submit, action); public static EventTrigger.Entry AddCancelListener(this EventTrigger trigger, Action<BaseEventData> action) => AddListener(trigger, EventTriggerType.Cancel, action); #endregion #region AxisEventData public static EventTrigger.Entry AddMoveListener(this EventTrigger trigger, Action<AxisEventData> action) => AddAxisListener(trigger, EventTriggerType.Move, action); #endregion #region PointerEventData(Advance) /// <summary> /// Execute when long pressed. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="interval">If the tap interval is longer than this value, it will be disabled.</param> /// <param name="tooLateToUp">If the tap is slower than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerLongPressListener(this EventTrigger trigger, Action<PointerEventData> action, float tooFastToUp = 0.5f) { return EventTriggerAdvance.AddPointerLongPressListener(trigger, action, tooFastToUp); } /// <summary> /// Execute when double-clicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tolerance">If the tap interval is longer than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerDoubleClickListener(this EventTrigger trigger, Action<PointerEventData> action, float tolerance = 0.75f) { return EventTriggerAdvance.AddPointerDoubleClickListener(trigger, action, tolerance); } /// <summary> /// Execute when flicked. /// </summary> /// <param name="trigger">EventTrigger</param> /// <param name="tooShortSqrDistance">If the flick sqrt distance is shorter than this value, it will be disabled.</param> /// <param name="tooFastToUp">If the tap interval is longer than this value, it will be disabled.</param> /// <param name="tooLateToUp">If the tap is slower than this value, it will be disabled.</param> /// <returns>Entry</returns> public static EventTrigger.Entry AddPointerFlickListener(this EventTrigger trigger, Action<PointerEventData, FlickAngle> action, float tooShortSqrDistance = 500f, float tooLateToUp = 0.5f, float tooFastToUp = 0f) { return EventTriggerAdvance.AddPointerFlickListener(trigger, action, tooShortSqrDistance, tooLateToUp, tooFastToUp); } #region Drag to transform /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can move it by dragging. /// </summary> public static EventTrigger.Entry AddDragToMoveListener(this EventTrigger trigger, Transform transform, Action<PointerEventData, Vector2> action) { return EventTriggerAdvance.AddDragToMoveListenerUI(trigger, transform, action); } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can rotate it by dragging. /// </summary> public static EventTrigger.Entry AddDragToRotateListener(this EventTrigger trigger, Transform transform, Action<PointerEventData, float> action) { return EventTriggerAdvance.AddDragToRotateListenerUI(trigger, transform, action); } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can scale it by dragging. /// </summary> public static EventTrigger.Entry AddDragToScaleListener(this EventTrigger trigger, Transform transform, Action<PointerEventData, float> action) { return EventTriggerAdvance.AddDragToScaleListenerUI(trigger, transform, action); } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can move it by dragging. /// </summary> public static EventTrigger.Entry AddDragToMoveListener(this EventTrigger trigger, Camera camera, Transform transform, Action<PointerEventData, Vector2> action) { return EventTriggerAdvance.AddDragToMoveListenerSprite(trigger, camera, transform, action); } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can rotate it by dragging. /// </summary> public static EventTrigger.Entry AddDragToRotateListener(this EventTrigger trigger, Camera camera, Transform transform, Action<PointerEventData, float> action) { return EventTriggerAdvance.AddDragToRotateListenerSprite(trigger, camera, transform, action); } /// <summary> /// By assigning the return value of the Action, Vector2, to Transform, can scale it by dragging. /// </summary> public static EventTrigger.Entry AddDragToScaleListener(this EventTrigger trigger, Camera camera, Transform transform, Action<PointerEventData, float> action) { return EventTriggerAdvance.AddDragToScaleListenerSprite(trigger, camera, transform, action); } #endregion #endregion } }at tooFastToUp = 0f) { return EventTriggerAdvance.AddPointerFlickListener(trigger, action, tooShortSqrDistance, tooLateToUp, tooFastToUp); } #endregion } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでScriptableObjectからかんたんにインベントリを作る#2

ScriptableObjectを使ってみるその2 作ったデータを管理する 前回はItemというプログラムを作って、unityでたくさんアイテムを作れるようにしました。 では、そのたくさん作ったデータはどのように管理するのが効率が良いのでしょうか? データベースを作る 今回は、そのたくさん作ったアイテムをまとめあげるデータベースを作ります。 データベースとは、たくさんのデータが入っている棚のようなものだと思ってください。 次のプログラムを作ります。 今回もScriptableObjectなので、どのオブジェクトにつけなくても平気です。 using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName ="ItemDataBase",menuName ="CreateItemDataBase")] public class ItemDataBase : ScriptableObject { [SerializeField] private List<Item> itemlists; public List<Item> GetItemLists(){ return itemlists; } } 今回も一行目に注目してください。 public class ItemDataBase : ScriptableObject MonoBehaviourをScriptableObjectに書き換えられましたか? 前回にも出てきた↓これはなんでしょうか。前回はスルーしていました。 [CreateAssetMenu(fileName ="ItemDataBase",menuName ="CreateItemDataBase")] これは、Unity上のProjectやAssetメニュー欄から、新しくItemDataBaseを作れるようにしたものです。 実際にUnityでprojectの+を押してみましょう。 CreateItemDataBaseが出ていれば成功です。 選択するとAssets内にItemDataBaseが現れます。 Itemlistsの欄に作ったアイテムの数を入れてみましょう。 欄が増えるはずです。そこに実際に前に作ったアイテムを入れてみましょう。↓こんな感じ プログラムの説明をします。 [SerializeField] private List<Item> itemlists; これでUnity上で数字を入力することができるようになり、数字に応じて欄が増えるようになります。 そこに、Itemが入れられるようになります。 その下の関数はまた今度。 次回は実際にコンソールに表示できるようにしてみます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

unityでScriptableObjectからインベントリをかんたんに作る#1

ScriptableObjectを使ってみる ScriptableObjectとは ScriptableObject は、クラスインスタンスとは無関係に、大量のデータを保存するために使用できるデータコンテナです。(Unityリファレンス) と、言われてもむずかしいですね。スクリプタブルオブジェクト、と読みます。 ゲームを作る時でいえば、例えばたくさんアイテムのデータを作りたい時に使います。 たくさん作ったアイテムを一斉に管理する方法の一つ、と覚えてください。 今まで作ってきたゲームでは、プログラムは何かしらにくっつけて使うものでした。 例えば、Playerを動かすmove.csは、Player役のCubeとかにつけてたと思います。 しかし、ScriptableObjectはオブジェクトくっつけて使いません。というか、何にもくっつけません。 今回は実際に 緑のCube 青のCube 赤のCube を拾って画面にアイテムとして表示できるようにします。 ↓こんな感じ Itemを作る プログラムを書く アイテムをたくさん管理するためのもの、と言いました。 アイテムのデータをたくさん作ることができるようになるプログラムを書きます。 いつものように、project->c#Scriptからプログラムを作って以下を書きましょう。名前はItemです。 using System.Collections; using System.Collections.Generic; using UnityEngine; using System; [Serializable] [CreateAssetMenu(fileName ="Item",menuName="CreateItem")] public class Item : ScriptableObject { [SerializeField] private string itemName; public string GetItemName() { return itemName; } } まずはここに注目してください public class Item : ScriptableObject いつもと何かが違います。どこでしょうか? いつもはScriptableObjectのところがMonoBehaviorとなっていました。 ここで、このプログラムはScriptableObjectだよ〜とUnityに教えてあげていると思ってください。 つまりどこのオブジェクトにもくっつけなくてよくなります。 unityでアイテムを作れるようになる このプログラムを書くと、unityでアイテムがかんたんに作れるようになっています。 unityでprojectの+ボタンを押してみてください。一番上にCreateItemというボタンが出現したはずです。 押してみましょう。 これを押すと次のようなものが現れます。 これで大量にアイテムの情報を作ることができるようになりました。 試しにItem Nameにgreencubeと名前を入れてみましょう。 これで名前が「greencube」っていうアイテムがありますよ、というデータがunityに誕生しました。 ただこれだけではデータは名前だけでちょっと寂しいです。 画像のデータも持てるようにしましょう。 [SerializeField] private Sprite icon; public Sprite GetIcon() { return icon; } これを追加してみましょう。 そうすると、さっきのアイテムデータのところに画像を追加する項目が出現します。 このIconに好きな画像を入れてみましょう。(後で使います。) できたら、自分で複数アイテムを作ってみましょう。 今回はアイテムを大量に作れるようにしました。 次回はアイテムを管理するデータベースを作ります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む