- 投稿日:2021-03-14T22:38:15+09:00
ROS講座123 UnityでROSと通信する
環境
この記事は以下の環境で動いています。
項目 値 CPU Core i5-8250U Ubuntu 18.04 ROS Melodic Gazebo 9.0.0 python 2.7.17 Unityの動作環境は以下です。
項目 値 ホストマシン Windows10 Unity 2019.4 インストールについてはROS講座02 インストールを参照してください。
またこの記事のプログラムはgithubにアップロードされています。ROS講座11 gitリポジトリを参照してください。概要
これまでQtやブラウザでUIを作ってきましたが、今回はスマホで操作することを目標にしてUnityで作ってみます。
Unityはゲーム作成の統合開発環境で、ゲームで必要な3DCGや物理演算などの処理がを簡単に扱うことが出来ます。UnityはWindows/Mac/CentOSをサポートしていますが、今回はWindows上で開発します。
Unity本体だけでも基本的なゲームを作成することが出来ますが、アセットという追加のソフトウェアパッケージで処理や画像などの素材を追加することが出来ます。アセットはUnityのアセットストアで有償/無償で配布しています。無償の物でもクオリティーが高く便利なものがたくさんあります。今回はROSと通信ができる「ROS#」というアセットを使用します。
またUnityはマルチプラットフォームで実行できることが特徴です。Windowsの統合開発環境上でその場でプレビューすることができて、もちろんそれをWindowsアプリとしてリリースすることもがきます。それ以外にもAndroidやiPoneアプリとしてリリースすることもできます(ただしiPnone用のアプリ作成はMac上でのみ可能)。
Unityでは基本的にC#で開発を行います。UnityではC#プログラムを「スクリプト」と呼びます。
Unity自体に関する説明は長くなってしまうので、入門書にありそうな部分は説明を省きます。ROS#について
UnityとROSの間を通信するためのUnityのアセットとしてROS#があります。これはC#で書かれたRosBridgeのクライアントでUnityでです。このアセットはUnityのアセットストアで配布しているものではなくgithubのページからダウンロードしてUnityでインポートします。
ROS#はrosbridge_serverがjson形式にしたrostopicをwebsocket通信でやり取りをすることでUnityとROSを接続します。インストール(on Windows)
Unity本体のインストール
Unityのダウンロードページからダウンロードします。UnityHubはUnityのバージョンなどの管理ツールで最初は「UnityHub」をダウンロードします。「UnityHub」から「Unity本体」をダウンロードします。最新のROS#の推奨バージョンの2019.4を入れます。
後々必要なのでUnity本体のダウンロードの時に「Android Build Support」にチェックを入れてください。プロジェクトの作成
UnityHubの「新規作成」から新しいプロジェクトを作成します。名前は何でも構いません。テンプレートは「3D」を選びます。
ROS#アセットのプロジェクトへのインストール
- github上のリリースページから「RosSharp.unitypackage」をダウンロードしてローカルに保存します。
メニューバーの「Assets」->「Import Package」->「Custom Package...」を選んで出てくるウィンドウで「RosSharp.unitypackage」を選択します。
Unity上の操作(on Windows)
今回はROSからのimageのsubscribeとjoyのpublishを目標にします。
RosConnectorの設置
「Hierarchy」ウィンドウの「+マーク」->「Create Empty」で空のGameObjectを作成して、RosConnectorと名前を変えます。
「Project」ウィンドウのAssets/RosSharp/Scripts/RosBridgeClient/RosCommuncation/RosConnector.csをRosConnectorにアタッチします。
Imageのsubscribe
まず画像を表示するためのplaneを追加します。「Hierarchy」ウィンドウの「+マーク」->「3D Object」->「Plane」を選択します
「Project」ウィンドウのAssets/RosSharp/Scripts/RosBridgeClient/RosCommuncation/ImageSubscriber.csをRosConnectorにアタッチします。
Joyのpublish
- 「Project」ウィンドウのAssets/RosSharp/Scripts/RosBridgeClient/RosCommuncation/JoyPublisher.csをRosConnectorにアタッチします。
- 「Topic」を/unity/joyとします。
- 「Project」ウィンドウのAssets/RosSharp/Scripts/RosBridgeClient/MessageHandling/JoyAxisReader.csをRosConnectorにアタッチします。
- 「Name」を「Horizontal」とします。
- もう1つ「Project」ウィンドウのAssets/RosSharp/Scripts/RosBridgeClient/MessageHandling/JoyAxisReader.csをRosConnectorにアタッチします。
実行
ネットワーク構成
ROSを実行するUbuntuPCとUnityを実行するWindowsPCが同一セグメント上にいるとします。
以下UbuntuPCのIPアドレスを「192.168.2.105」としますROSの実行(on Ubuntu)
シミュレーションを起動します。
ターミナル1(gazeboの起動)roslaunch sim3_lecture base_world.launchターミナル1(rosbridgeの起動)roslaunch rosbridge_server rosbridge_websocket.launchUnityの実行(on Windows)
UnityEditorのplay(上部中央の三角形のボタン)を押します。
設置したPlaneにカメラの画像が写っています。またROSで
rostopic echo /unity/joy
とすると、Unity側画面でのキーボードの上下左右ボタンの入力が反映されます。コメント
このサンプルではpublish側でで不具合があるために動かない可能性があります。これの場合は以下の修正が必要です。
Assets/RosSharp/Scripts/RosBridgeClient/RosCommuncation/JoyPublisher.csの修正protected override void Start() { System.Threading.Thread.Sleep(3000); // 追加 base.Start(); InitializeGameObject(); InitializeMessage(); }参考
- (書籍)Unity 3D/2Dゲーム開発実践入門 Unity 2019対応版
- C#スクリプトの内容は軽く、Unity特有の内容について詳しく説明しているので1冊でUnityの基本的な開発が大体理解できます。
- ROS#リポジトリ
- ROS#使い方
目次ページへのリンク
- 投稿日:2021-03-14T21:14:18+09:00
自分用 XR リンク集
随時更新予定
Mixed Reality Toolkit (MRTK)
- MRTK-Unity Developer Documentation | Microsoft Docs
- 公式ドキュメント
- 日本語
- MRTK チュートリアルの概要 - Mixed Reality | Microsoft Docs
- OculusQuestMRTK | Microsoft Docs
- UsingARFoundation | Microsoft Docs
- GitHub - microsoft/MixedRealityToolkit-Unity
- Azure Spatial Anchors チュートリアルの概要 - Mixed Reality | Microsoft Docs
- MRTK x Azure Spatial Anchors
- Android と iOS 用の Azure Spatial Anchors - Mixed Reality | Microsoft Docs
- MRTK x Azure Spatial Anchors x Android
Azure Spatial Anchors
- Azure Spatial Anchors のドキュメント - Azure Spatial Anchors | Microsoft Docs
- Azure Spatial Anchors Unity の概要 - Azure Spatial Anchors | Microsoft Docs
- Unity 向けの Azure Spatial Anchors をインストールする - Azure Spatial Anchors | Microsoft Docs
- Azure Spatial Anchors のインストール方法
- Azure Spatial Anchors チュートリアルの概要 - Mixed Reality | Microsoft Docs
- MRTK x Azure Spatial Anchors
- Android と iOS 用の Azure Spatial Anchors - Mixed Reality | Microsoft Docs
- MRTK x Azure Spatial Anchors x Android
ARCore
- Quickstart for ARCore Extensions for Android | Google Developers
- AR Foundation を用いた ARCore のインストール方法
- Build for Android 11 with Unity | ARCore | Google Developers
- Android 11 のデバイスにビルドするときの注意
AR Foundation
- 投稿日:2021-03-14T19:01:15+09:00
【Unity】アセットバンドルのビルドと読み込み時のハッシュ管理について
確認環境
Unity 2020.2.0f1
内容
自分自身アセットバンドルは使ってきましたが、サーバなどからダウンロードしてアセットバンドルの読み込みをしたことがありませんでした。
streamingassetsから読み込んでいるとハッシュを気にしなくてよかったのですが、キャッシュを聞かせるとなるとAssetBundleManifest(アセットバンドルをビルドしたときに勝手に作られるフォルダー名と同じアセットバンドル)のGetAssetBundleHashを使用してハッシュの管理をしてもよいですが、ビルドしたマシンの影響でハッシュが変わったり不安定で、jenkinsなど1か所でビルドしていてもjenkinsPC自体が変わってしまった場合にすべてハッシュが変わってしまうのではと思ったり、ダウンロードサイズを表示しないといけないなどがあったので独自にアセットバンドル一覧の管理アセットを用意する必要があったので備忘録的に残しました。もしかしたらAddressableだとこの辺りもしっかりしているのかもしれませんがAddressableのことは何も調べていません。
準備
アセットバンドル名も独自に付加してもよかったのですが今回はAsset Bundle Browserを使用しています。
unity 2020.1からAsset Bundle Browserなどの一部のパッケージがPackage Manager(パッケージマネージャー)からインストールできなくなっているらしくこちらの記事を参考にインストールしました。com.unity.assetbundlebrowserを入力してAdd
Window>AssetBundle Browserを開きアセットバンドルにしたアセットをドラッグ&ドロップでアセットバンドル名が付与されます。
ビルド
Asset Bundle Browserを使用してそのままビルドもできますが。
今回はアセットのサイズ、ハッシュを管理できるアセット(ScriptableObject)も生成するので独自でビルドすることにしました。ビルドの手順は次の通りです。
1. アセットバンドルをビルド
2. ビルド後該当のアセットバンドルのサイズ、ハッシュを計算して管理用のScriptableObjectを生成
3. 2で生成した管理用のScriptableObjectをアセットバンドル化アセットバンドル管理用のハッシュについて
アセットバンドルのサイズはFileInfoのLengthでとれると思いますがハッシュをAssetBundleManifestのGetAssetBundleHashで取得した場合マシンによって変わり不安定なので自分で作る必要がありました。
調べるとCRCという元のアセットが変わった場合のみ変更される一貫性のある値が存在したので
アセットバンドル名+CRCをハッシュにすればいい感じになると思いこちらでハッシュを作ることにしました
*結局CRCもOSによって変わってしまっているPrefabが存在しましたが・・・ 1CRC
CRCなのですがこちらはアセットバンドルをダウンロードする場合に引数で渡さないといけないものです。
(よくわからないので0にしてスキップさせますが)これはマニュアルを見てみるとインテグリティチェックのためにダウンロードしたデータと比較するためのチェックサムです。
正常なデータのチェックに使われているみたいですが今回はこちらをハッシュに使用します。
取得自体は
BuildPipeline.GetCRCForAssetBundle(assetsPath, out uint crc)で取得します。
ハッシュ生成
アセットバンドルのダウンロード時のキャッシュはUnityEngine.Hash128で管理しています。
namespace UnityEngine { public struct Hash128 : IComparable, IComparable<Hash128>, IEquatable<Hash128> { public Hash128(ulong u64_0, ulong u64_1); public Hash128(uint u32_0, uint u32_1, uint u32_2, uint u32_3); } }生成にはuintやulongで生成する必要があったのでcrcはuintでそのまま取得できましたがアセットバンドル名(string)をuintにする必要がありました。
アプローチとしては
string→byte[]→uint
に変換するのですが
byte[]→uint
これをそのまま変換してしまうとuint.Maxを超える場合があったのでSha1に変換してからuintにしました。SHA1Managed sha1 = new SHA1Managed(); byte[] textBytes = Encoding.UTF8.GetBytes("assetName"); byte[] sha1Bytes = sha1.ComputeHash(textBytes); uint nameInt = BitConverter.ToUInt32(sha1Bytes, 0);Hash128 hash128 = new Hash128(crc, nameInt);使用例(UniTask)
public static async UniTask<T> LoadAssetBundle<T>(AssetBundleLoadData assetBundleLoadData) where T : UnityEngine.Object { string assetName = assetBundleLoadData.AssetName; CachedAssetBundle cachedAssetBundle = new CachedAssetBundle(assetName, Hash128.Compute(assetBundleLoadData.Hash128)); string uri = "ここはURL"; using (var request = UnityWebRequestAssetBundle.GetAssetBundle(uri)) { request.downloadHandler = new DownloadHandlerAssetBundle(uri, cachedAssetBundle, 0); await request.SendWebRequest(); var assetBundle = DownloadHandlerAssetBundle.GetContent(request); T obj = assetBundle.LoadAsset<T>(assetName); assetBundle.Unload(false); return obj as T; } }今回のサンプルコード
アセットバンドルのサンプルビルドコード
AssetsBundleBuilder.csusing System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using UnityEditor; using UnityEngine; public class AssetsBundleBuilder { [MenuItem("Sample/AssetBundleBuild")] private static void AssetBundleBuild() { BuildTarget buildTarget = BuildTarget.Android; BuildAssetBundleOptions buildAssetBundleOptions = BuildAssetBundleOptions.DeterministicAssetBundle; string platformName = buildTarget.ToString().ToLower(); string assetbundledatalistName = "assetbundledatalist" + platformName; string outputFolderPath = Path.Combine(Application.dataPath, "Scenes/AssetBundleScene/Output"); // 出力フォルダ string outputAssetbundlesFolderPath = Path.Combine(outputFolderPath, platformName); // アセットバンドルビルド出力フォルダ CheckDirectory(outputFolderPath); CheckDirectory(outputAssetbundlesFolderPath); string[] assetbundleNames = AssetDatabase.GetAllAssetBundleNames(); List<AssetBundleBuild> assetBundleBuildList = new List<AssetBundleBuild>(); foreach (var item in assetbundleNames) { // アセットバンドル管理用のアセットバンドルは別でビルドする if (item.Contains(assetbundledatalistName)) continue; string[] assetsPaths = AssetDatabase.GetAssetPathsFromAssetBundle(item); AssetBundleBuild assetBundleBuild = new AssetBundleBuild(); assetBundleBuild.assetBundleName = item; assetBundleBuild.assetNames = assetsPaths; assetBundleBuildList.Add(assetBundleBuild); } AssetBundleManifest assetBundleManifest = BuildPipeline.BuildAssetBundles(outputAssetbundlesFolderPath, assetBundleBuildList.ToArray(), buildAssetBundleOptions, buildTarget); // コンバート先のフォルダーからハッシュタグとbyteサイズを計算して。 AssetBundleLoadDataList assetBundleLoadDataList = ScriptableObject.CreateInstance<AssetBundleLoadDataList>(); foreach (var item in assetBundleManifest.GetAllAssetBundles()) { FileInfo file = new FileInfo(Path.Combine(outputAssetbundlesFolderPath, item)); long size = file.Length; Hash128 hash128 = GenerateHash128AssetNameCRC(outputAssetbundlesFolderPath, item, assetBundleManifest); assetBundleLoadDataList.SetAssetBundleLoadData(new AssetBundleLoadData(item, hash128, (uint)size)); } // ScriptableObjectの作成はAssetsからのパスなので注意 string filePath = Path.Combine(@"Assets/Scenes/AssetBundleScene/Output", assetbundledatalistName + ".asset"); CheckDirectory(Path.GetDirectoryName(filePath)); AssetDatabase.DeleteAsset(filePath); AssetDatabase.CreateAsset(assetBundleLoadDataList, filePath); AssetImporter importer = AssetImporter.GetAtPath(filePath); importer.assetBundleName = assetbundledatalistName; importer.SaveAndReimport(); EditorUtility.SetDirty(assetBundleLoadDataList); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); AssetBundleBuild[] assetBundleBuildArray = new AssetBundleBuild[1]; assetBundleBuildArray[0].assetBundleName = assetbundledatalistName; assetBundleBuildArray[0].assetNames = new string[] { filePath }; BuildPipeline.BuildAssetBundles(outputAssetbundlesFolderPath, assetBundleBuildArray, buildAssetBundleOptions, buildTarget); } /// <summary> /// アセットバンドル名とCRCからアセットバンドルのハッシュを生成 /// </summary> /// <param name="outputFolder"></param> /// <param name="assetName"></param> /// <param name="assetBundleManifest"></param> /// <returns></returns> private static Hash128 GenerateHash128AssetNameCRC(string outputFolder, string assetName, AssetBundleManifest assetBundleManifest) { string assetsPath = Path.Combine(outputFolder, assetName); if (BuildPipeline.GetCRCForAssetBundle(assetsPath, out uint crc)) { SHA1Managed sha1 = new SHA1Managed(); byte[] textBytes = Encoding.UTF8.GetBytes(assetName); byte[] sha1Bytes = sha1.ComputeHash(textBytes); uint nameInt = BitConverter.ToUInt32(sha1Bytes, 0); Hash128 hash128 = new Hash128(crc, nameInt); return hash128; } else { return assetBundleManifest.GetAssetBundleHash(assetName); } } private static void CheckDirectory(string outputFolder) { if (Directory.Exists(outputFolder)) return; Directory.CreateDirectory(outputFolder); } }
AssetBundleLoadDataList.cs(管理用のScriptableObjectクラス)
AssetBundleLoadDataList.csusing System; using System.Collections.Generic; using UnityEngine; [Serializable] public class AssetBundleLoadDataList : ScriptableObject { [SerializeField] private List<AssetBundleLoadData> m_AssetBundleLoadDataList; public AssetBundleLoadDataList() { m_AssetBundleLoadDataList = new List<AssetBundleLoadData>(); } public void SetAssetBundleLoadData(AssetBundleLoadData assetBundleLoadData) { m_AssetBundleLoadDataList.Add(assetBundleLoadData); } public List<AssetBundleLoadData> GetAssetBundleLoadDataList() { return m_AssetBundleLoadDataList; } } [Serializable] public class AssetBundleLoadData { public string AssetName; public string Hash128; public uint Bytes; public AssetBundleLoadData(string assetName, Hash128 hash128, uint bytes) { AssetName = assetName; Hash128 = hash128.ToString(); Bytes = bytes; } public override string ToString() { return $"<color=red>{AssetName}</color>\nHash128[ {Hash128} ]\nBytes[ {Bytes} ]"; } }おわり
様々な管理方法があるかと思います、今回初めてだったこともありもっと良い方法などもあるかと思いますが。
これからアセットバンドルのロードなどを実装する方たちに何かしらの参考になればと思います。
詳しくは調べられませんでしたがSpineなどのアセットはwindowsとmacでアセットバンドルをビルドした場合でもcrcが変更しました。同じOS内なら変更されなかったのでビルドするOSは統一させないといけなかったです。 ↩
- 投稿日:2021-03-14T15:23:30+09:00
【Unity】RendererFeatureの値をランタイムで変更したい
URPの場合は以下のようにRendererFeatureにアクセスできます。
var pipeline = ((UniversalRenderPipelineAsset)GraphicsSettings.renderPipelineAsset); var propertyInfo = pipeline.GetType().GetField("m_RendererDataList", BindingFlags.Instance | BindingFlags.NonPublic); var scriptableRendererData = ((ScriptableRendererData[])propertyInfo.GetValue(pipeline))[0]; var renderObjects = (RenderObjects)scriptableRendererData.rendererFeatures[index]; renderObjects.settings.depthCompareFunction = CompareFunction.Greater; scriptableRendererData.SetDirty();値を変更した場合、ScriptableRendererData.SetDirty() を呼び出すことで変更が反映されます。
自作RendererFeatureを実装している場合は以下のようにアクセスできます。
var myRendererFeature = scriptableRendererData.rendererFeatures.OfType<MyRendererFeature>().FirstOrDefault();
Unity 2020.1.17f1
Universal RP 8.3.1
- 投稿日:2021-03-14T14:45:04+09:00
【Unity】カスタムポストエフェクトでURP入門してみる(ぼかし処理)
はじめに
最近のUnityの傾向として、かつてからある伝統的なBuilt-in Render Pipelineから、Universal Render Pipeline(URP)に移行せよ...という圧力を感じます。Built-in Render Pipelineを選択するのもアリだよとも聞くので、悩みどころとなります。
一方自分は実務ではここ1年程Unity 2019/Built-in Redner Pipelineを開発で使ってきました。
Unity2018の頃はLWRPと呼ばれていましたが採用せず(正直に言えばこれは正解だったと思う)、後にURPと改名されたことを知りまだまだ安定してないのでは等の不安感が強く、実務での利用を控えてきました。
なによりただでさえ遅れがちなゲーム開発の進捗が遅れることを恐れたためです。しかしながらShader Graph使いたい、というデザイナーさんからの要望も高まっており(現に使っていてデザイナーさん凄いなと思うのですが)、これがURPでないと利用できないためShader Graphを使いたいがために「各社頑張って対応してそうだな」などと想像しています。
また、先日Unity 2020がLTSとなったこともあり、このままではゲームプログラマとして不味いという強迫観念もあり、いよいよ自分もBuilt-in Render Pipelineを捨ててURPに乗り換える気になってきた、というのが最近の状況です。(つまりURP完全初心者!)
調査がてらポストエフェクトないしブラー処理から入門してみることにしました。ブラー処理は手軽に実装できかつ比較的よくある要望の1つであるためです。なによりポストエフェクトにはロマンがある。
なお、ここでいうブラー処理とは移動ベクトルを使うモーションブラーのことではなく、画像処理でいうところのガウシアンフィルタのようなぼかし処理です。
手始めに先人の知恵を借りる
まずどこから手をつけたら良いか分からなかったので、ググったところ下記の2つが大変参考になりました。
Unity HubからUniversal Render Pipelineテンプレートから新規プロジェクトを作成して上記の2つのRenderFeatureを組み込んでみたのが下記。
Unity URPを探り始めた。とりあえずポストプロセスとしてグレースケール変換&ズームブラーを試す。楽しい
— 山下 貴史 (@kishi_yama) March 7, 2021
参考:
グレースケール変換 ?https://t.co/zsJ3mNS2iX
ズームブラー
? https://t.co/T2F8RIqJ67 pic.twitter.com/BpWScXwtY1ちょっとバトルシーンに入る前のエフェクトみたいになって楽しいです。やっぱりポストエフェクトにはロマンがある!
これはこれで素晴らしいのですが当初やりたかったのは、ぼかし処理なのだ、ということを思い出す。UnityのPost Processing Stackについて
Unity 2018を触っていた頃Post Processing Stack v2というものがあることを知り、これを使えば比較的簡単にポストエフェクトを実装できる凄いやつ、程度の認識をしていました(元々はPostFx v2というものだったそうです)。
さらに遡るとUnity 5.xの頃のUnityマニュアルを見ると、この頃はそもそもデフォルトでBlurがあったようです。
当時はRenderTextureベースでスクリーンに対するImage Effectという位置付けで実装されていたようです。一方Post Processing Stack v2のリファレンスのEffectsを見るとでは、カメラの動きによるモーションブラーは実装されているものの、ガウシアンフィルタのようなものはありません。
Post Processing Stack v2でこれを自前実装することなしに実現したい場合はUnityToolsetを導入する必要があるとのことでした。なるほど、一体何故かつて提供していたにも関わらずデフォルトで実装されていないのか。
それは自分で実装してくださいよ、というメッセージなのだろうか。やりましょう。さらにURPの進展からかPost Processing Stack v2はレガシーとのこと。僅か2年程でレガシーというのが何ともアレなのですが、Post Processing Stack v3に引き継がれる流れのようです。消えなくて良かった。
では、Post Processing Stack v3ではどうなのかというと、Unity Forumでも同様の質問があり、特に将来的に実装予定でもなさそうです。代わりにURPのScriptableRenderFeatureで実装したデモがこのフォーラムで紹介されており、参考になりそうです。
やりたかったのはコレですよコレ!勉強になります!(新しいオモチャを見つけた子供のような心で)
デモの内容を見る
デモを見ると分かるのですが、このデモでは
blur
マテリアルにCustom/RenderFeature/KawaseBlur
シェーダ、glass
マテリアルにShaderGraphs/BathroomGlass
シェーダが付けれており、グラス表現を適用したPlaneに対して川瀬ブラーを施す、というものです。初見どこを弄ればブラー効果が確認できるのか分からなかったのですが、
ForwardRenderer
でプロパティが露出していました。
手始めに先人の知恵を借りる
では共にVolumeComponent
を継承したクラスでプロパティが実装されておりVolume
コンポーネントのインスペクターにプロパティが露出していたため多少混乱しました。一方こちらのデモでは
Volume framework
(URP 10.1.0以前ではVolume system
と書かれていた)は関係なく、KawaseBlurSettings
がScriptableRendererFeature
内で定義されており、これが設定項目としてFowardRenderer
に露出しているようです。各設定項目を見てみましょう。
Render Pass Event
Rendear Passを差し込むタイミングの指定です。
どのタイミングが良いかはRenderPassEvent.BeforeRenderingPostProcessing
もしくはRenderPassEvent.AfterRenderingTransparents
あたりかな、と思いました。Blur Material
KawaseBlurに紐づくマテリアルを指定します。
Blur Passes
ブラーの強さです。値を大きくすればするほど強いブラーとなりますが、負荷が気になるところです。
Downsample
負荷低減のためダウンサンプリングする際のテクスチャ縮小具合です。
下記の計算でテクスチャを縮小しているようです。つまり2を指定すればテクスチャサイズは1/4となりその分負荷が軽くなりそうです。var width = cameraTextureDescriptor.width / downsample; var height = cameraTextureDescriptor.height / downsample;ある程度負荷を抑える制御ができる点が玄人的で素晴らしいです。
Copy To Framebuffer
このデモは
BathroomGlass.shadergraph
で参照しているテクスチャに対してブラーをかけるだけかと思いきやこのオプションを有効にするとPlaneだけでなくスクリーン全体にブラーが効きました(これが本当にやりたかったこと)。Target Name
_blurTexture
という文字列が指定されています。
これはBathroomGlass.shadergraph
で参照されるプロパティのようです。
この辺りはまだShaderGraphを調査していないため雰囲気で言っています。
BathroomGlass.shadergraph
のインスペクターで「Edit」を押すとShaderGraphのエディタが開き、中身を確認できました。なお
KawaseShader.shader
については何故かmulti_compile_fog
というpragmaがあり本当にコレが必要なのかどうなのかは良く分かりませんでした(フォーラムでも質問が上がっているが特に返答は得られていない様子)。川瀬ブラーのみ取り出してVolumeComponentで扱えるようにする
上記のデモがあれば十分なのですが、このままだと
Volume framework
に適応していません。
Volume framework
に則るとweight
で「ポストエフェクトがどの程度影響するか」を調整できるため、便利そうです。
また、volume.profile.TryGet<MyVolumeComponent>(out var volumeComponent)
のように自作したVolumeComponent
にアクセスしてプロパティをプログラムから制御できるため、都合が良さそうです。そこで
手始めに先人の知恵を借りる
で紹介されているような方法で書き直してみます。
今回スクリーン全体に適用されたら十分だったため必要なさそうな箇所は適当に調整して簡略化しました。KawaseBlur.csusing UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class KawaseBlur : VolumeComponent, IPostProcessComponent { // RangeではなくClampedIntParameterを使うことでインスペクター上でスライダーとなることに注意。 public ClampedIntParameter passes = new ClampedIntParameter(2, 2, 20); public IntParameter downsample = new ClampedIntParameter(1, 1, 10); public bool IsActive() => passes.value >= 2; public bool IsTileCompatible() => false; }KawaseBlurRenderFeature.csusing UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class KawaseBlurRenderFeature : ScriptableRendererFeature { class CustomRenderPass : ScriptableRenderPass { static readonly string RenderTag = "Kawase Blur Effect"; KawaseBlur kawaseBlur = default; Material material = default; RenderTargetIdentifier source = default; RenderTargetIdentifier tmpRT1 = default; RenderTargetIdentifier tmpRT2 = default; int tmpId1 = default; int tmpId2 = default; public CustomRenderPass(RenderPassEvent evt) { renderPassEvent = evt; var shader = Shader.Find("Custom/RenderFeature/KawaseBlur"); if (shader == default) { return; } material = CoreUtils.CreateEngineMaterial(shader); } public void Setup(RenderTargetIdentifier source) { this.source = source; } public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { if (kawaseBlur == default) { var stack = VolumeManager.instance.stack; kawaseBlur = stack.GetComponent<KawaseBlur>(); } if (!IsValid()) return; var downsample = kawaseBlur.downsample.value; var width = cameraTextureDescriptor.width / downsample; var height = cameraTextureDescriptor.height / downsample; tmpId1 = Shader.PropertyToID("tmpBlurRT1"); tmpId2 = Shader.PropertyToID("tmpBlurRT2"); cmd.GetTemporaryRT(tmpId1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32); cmd.GetTemporaryRT(tmpId2, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32); tmpRT1 = new RenderTargetIdentifier(tmpId1); tmpRT2 = new RenderTargetIdentifier(tmpId2); ConfigureTarget(tmpRT1); ConfigureTarget(tmpRT2); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (!renderingData.cameraData.postProcessEnabled) return; if (!IsValid()) return; var passes = kawaseBlur.passes.value; CommandBuffer cmd = CommandBufferPool.Get(RenderTag); RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor; opaqueDesc.depthBufferBits = 0; // first pass cmd.SetGlobalFloat("_offset", 1.5f); cmd.Blit(source, tmpRT1, material); for (var i = 1; i < passes - 1; i++) { cmd.SetGlobalFloat("_offset", 0.5f + i); cmd.Blit(tmpRT1, tmpRT2, material); // pingpong var rttmp = tmpRT1; tmpRT1 = tmpRT2; tmpRT2 = rttmp; } // final pass cmd.SetGlobalFloat("_offset", 0.5f + passes - 1f); cmd.Blit(tmpRT1, source, material); context.ExecuteCommandBuffer(cmd); cmd.Clear(); CommandBufferPool.Release(cmd); } bool IsValid() { if (material == default) { Debug.LogError("material is not found."); return false; } if (kawaseBlur == default) { Debug.LogError("kawaseBlur is not found."); return false; } return kawaseBlur.IsActive(); } } CustomRenderPass scriptablePass = default; public override void Create() { scriptablePass = new CustomRenderPass(RenderPassEvent.AfterRenderingTransparents); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { scriptablePass.Setup(renderer.cameraColorTarget); renderer.EnqueuePass(scriptablePass); } }KawaseBlur.shaderShader "Custom/RenderFeature/KawaseBlur" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderPipeline" = "UniversalPipeline" } Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_TexelSize; float4 _MainTex_ST; float _offset; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f input) : SV_Target { float2 res = _MainTex_TexelSize.xy; float i = _offset; fixed4 col = float4(0, 0, 0, 1); col.rgb = tex2D(_MainTex, input.uv ).rgb; col.rgb += tex2D(_MainTex, input.uv + float2( i, i) * res).rgb; col.rgb += tex2D(_MainTex, input.uv + float2( i, -i) * res).rgb; col.rgb += tex2D(_MainTex, input.uv + float2(-i, i) * res).rgb; col.rgb += tex2D(_MainTex, input.uv + float2(-i, -i) * res).rgb; col.rgb /= 5.0f; return col; } ENDCG } } }Editor上では下記のようなことまでできました。(解像度的に見辛いかもしれませんが...)
ふぅ...できた。 pic.twitter.com/3888qsvsJe
— 山下 貴史 (@kishi_yama) March 14, 2021実機検証
普段はiOS/Androidで開発していますが、AndroidではVukkan/OpenGL ESの差でハマりがちなので、警戒が必要です。
手元のiPhone 12, Google Pixel XL(Vulkan)では無事動作しましたが、Open GL ES 3.0にフォールバックされた場合は手元に検証端末がなく、確認していません。
まとめ
簡単なポストエフェクト実装からURPに入門してみました。
Scriptable Rendering Pipelineまわりは開発が活発で次から次へと状況が変わっており、ただでさえ敷居の高いGraphicsに拍車をかけて追いかけるのが難しいです。
また作業中何かの拍子にGameViewに何も映らなくなりUnity再起動で直ったり、
VolumeComponent
で公開するClampedIntParameter
のmin,maxの値を変更して再コンパイルしたときに正常に反映されず一度VolumeComponent
を外して付け直すと直る、見慣れない警告が出たりといったような事が発生して少し驚きます。とはいえ最近はモバイルゲームといえど、ハイクオリティなタイトルも珍しくなくなってきているため、今後もURPへの期待は業界内で高まっていくのが自然な流れかなと思っています。
ある程度手応えを得ることができたので、これを皮切りに今後もリファレンスやサンプルを読み漁りつつ追いかけてみようと思います。
- 投稿日:2021-03-14T14:45:04+09:00
【Unity】カスタムポストエフェクトでURPに入門してみる(ぼかし処理)
はじめに
最近のUnityの傾向として、かつてからある伝統的なBuilt-in Render Pipelineから、Universal Render Pipeline(URP)に移行せよ...という圧力を感じます。Built-in Render Pipelineを選択するのもアリだよとも聞くので、悩みどころとなります。
一方自分は実務ではここ1年程Unity 2019/Built-in Redner Pipelineを開発で使ってきました。
Unity2018の頃はLWRPと呼ばれていましたが採用せず(正直に言えばこれは正解だったと思う)、後にURPと改名されたことを知りまだまだ安定してないのでは等の不安感が強く、実務での利用を控えてきました。なによりただでさえ遅れがちなゲーム開発の進捗が遅れることを恐れたためです。しかしながらShader Graph使いたい、というデザイナーさんからの要望も高まっており(現に使っていてデザイナーさん凄いなと思うのですが)、これがURPでないと利用できないためShader Graphを使いたいがために「各社頑張って対応してそうだな」などと想像しています。
また、先日Unity 2020がLTSとなったことを受け「このままではゲームプログラマとして不味い」という強迫観念を感じ、いよいよ自分もBuilt-in Render Pipelineを捨ててURPに乗り換える気になってきた、というのが最近の状況です。(つまりURP完全初心者!)
調査がてらポストエフェクトないしブラー処理から入門してみることにしました。ブラー処理は手軽に実装できかつ比較的よくある要望の1つであるためです。なによりポストエフェクトにはロマンがある。
なお、ここでいうブラー処理とは移動ベクトルを使うモーションブラーのことではなく、画像処理でいうところのガウシアンフィルタのようなぼかし処理です。
実行環境
Unity: 2020.3.0f1
Core RP Library: 10.3.2
Universal RP: 10.3.2手始めに先人の知恵を借りる
まずどこから手をつけたら良いか分からなかったので、ググったところ下記の2つが大変参考になりました。
Unity HubからUniversal Render Pipelineテンプレートから新規プロジェクトを作成して上記の2つのRenderFeatureを組み込んでみたのが下記。
Unity URPを探り始めた。とりあえずポストプロセスとしてグレースケール変換&ズームブラーを試す。楽しい
— 山下 貴史 (@kishi_yama) March 7, 2021
参考:
グレースケール変換 ?https://t.co/zsJ3mNS2iX
ズームブラー
? https://t.co/T2F8RIqJ67 pic.twitter.com/BpWScXwtY1ちょっとバトルシーンに入る前のエフェクトみたいになって楽しいです。やっぱりポストエフェクトにはロマンがある!
これはこれで素晴らしいのですが当初やりたかったのは、ぼかし処理なのだ、ということを思い出す。UnityのPost Processing Stackについて
Unity 2018を触っていた頃Post Processing Stack v2というものがあることを知り、これを使えば比較的簡単にポストエフェクトを実装できる凄いやつ、程度の認識をしていました(元々はPostFx v2というものだったそうです)。
さらに遡るとUnity 5.xの頃のUnityマニュアルを見ると、この頃はそもそもデフォルトでBlurがあったようです。当時はRenderTextureベースでスクリーンに対するImage Effectという位置付けで実装されていたようです。
一方Post Processing Stack v2のリファレンスのEffectsを見るとでは、カメラの動きによるモーションブラーは実装されているものの、ガウシアンフィルタのようなものはありません。
Post Processing Stack v2でこれを自前実装することなしに実現したい場合はUnityToolsetを導入する必要があるとのことでした。なるほど、一体何故かつて提供していたにも関わらずデフォルトで実装されていないのか。
それは自分で実装してくださいよ、というメッセージなのだろうか。やりましょう。さらにURPの進展からかPost Processing Stack v2はレガシーとのこと。僅か2年程でレガシーというのが何ともアレなのですが、Post Processing Stack v3に引き継がれる流れのようです。消えなくて良かった。
では、Post Processing Stack v3ではどうなのかというと、Unity Forumでも同様の質問があり、特に将来的に実装予定でもなさそうです。代わりにURPのScriptableRenderFeatureで実装したデモがこのフォーラムで紹介されており、参考になりそうです。
やりたかったのはコレですよコレ!勉強になります!(新しいオモチャを見つけた子供のような心で)
デモの内容を見る
デモを見ると分かるのですが、このデモでは
blur
マテリアルにCustom/RenderFeature/KawaseBlur
シェーダ、glass
マテリアルにShaderGraphs/BathroomGlass
シェーダが付けれており、グラス表現を適用したPlaneに対して川瀬ブラーを施す、というものです。初見どこを弄ればブラー効果が確認できるのか分からなかったのですが、
ForwardRenderer
でプロパティが露出していました。
手始めに先人の知恵を借りる
では共にVolumeComponent
を継承したクラスでプロパティが実装されておりVolume
コンポーネントのインスペクターにプロパティが露出していたため多少混乱しました。一方こちらのデモでは
Volume framework
(URP 10.1.0以前ではVolume system
と書かれていた)は関係なく、KawaseBlurSettings
がScriptableRendererFeature
内で定義されており、これが設定項目としてFowardRenderer
に露出しているようです。各設定項目を見てみましょう。
Render Pass Event
Render Passを差し込むタイミングの指定です。
どのタイミングが良いかはRenderPassEvent.BeforeRenderingPostProcessing
もしくはRenderPassEvent.AfterRenderingTransparents
あたりかな、と思いました。Blur Material
KawaseBlurに紐づくマテリアルを指定します。
Blur Passes
ブラーの強さです。値を大きくすればするほど強いブラーとなりますが、負荷が気になるところです。
Downsample
負荷低減のためダウンサンプリングする際のテクスチャ縮小具合です。
下記の計算でテクスチャを縮小しているようです。つまり2を指定すればテクスチャサイズは1/4となりその分負荷が軽くなりそうです。var width = cameraTextureDescriptor.width / downsample; var height = cameraTextureDescriptor.height / downsample;ある程度負荷を抑える制御ができる点が玄人的で素晴らしいです。
Copy To Framebuffer
このデモは
BathroomGlass.shadergraph
で参照しているテクスチャに対してブラーをかけるだけかと思いきやこのオプションを有効にするとPlaneだけでなくスクリーン全体にブラーが効きました(これが本当にやりたかったこと)。Target Name
_blurTexture
という文字列が指定されています。
これはBathroomGlass.shadergraph
で参照されるプロパティのようです。
この辺りはまだShaderGraphを調査していないため雰囲気で言っています。
BathroomGlass.shadergraph
のインスペクターで「Edit」を押すとShaderGraphのエディタが開き、中身を確認できました。なお
KawaseShader.shader
については何故かmulti_compile_fog
というpragmaがあり本当にコレが必要なのかどうなのかは良く分かりませんでした(フォーラムでも質問が上がっているが特に返答は得られていない様子)。川瀬ブラーのみ取り出してVolumeComponentで扱えるようにする
上記のデモがあれば十分なのですが、このままだと
Volume framework
に適応していません。
Volume framework
に則るとweight
で「ポストエフェクトがどの程度影響するか」を調整できるため、便利そうです。
また、volume.profile.TryGet<MyVolumeComponent>(out var volumeComponent)
のように自作したVolumeComponent
にアクセスしてプロパティをプログラムから制御できるため、都合が良さそうです。そこで
手始めに先人の知恵を借りる
で紹介されているような方法で書き直してみます。
今回スクリーン全体に適用されたら十分だったため必要なさそうな箇所は適当に調整して簡略化しました。KawaseBlur.csusing UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class KawaseBlur : VolumeComponent, IPostProcessComponent { // RangeではなくClampedIntParameterを使うことでインスペクター上でスライダーとなることに注意。 public ClampedIntParameter passes = new ClampedIntParameter(2, 2, 20); public IntParameter downsample = new ClampedIntParameter(1, 1, 10); public bool IsActive() => passes.value >= 2; public bool IsTileCompatible() => false; }KawaseBlurRenderFeature.csusing UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class KawaseBlurRenderFeature : ScriptableRendererFeature { class CustomRenderPass : ScriptableRenderPass { static readonly string RenderTag = "Kawase Blur Effect"; KawaseBlur kawaseBlur = default; Material material = default; RenderTargetIdentifier source = default; RenderTargetIdentifier tmpRT1 = default; RenderTargetIdentifier tmpRT2 = default; int tmpId1 = default; int tmpId2 = default; public CustomRenderPass(RenderPassEvent evt) { renderPassEvent = evt; var shader = Shader.Find("Custom/RenderFeature/KawaseBlur"); if (shader == default) { return; } material = CoreUtils.CreateEngineMaterial(shader); } public void Setup(RenderTargetIdentifier source) { this.source = source; } public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { if (kawaseBlur == default) { var stack = VolumeManager.instance.stack; kawaseBlur = stack.GetComponent<KawaseBlur>(); } if (!IsValid()) return; var downsample = kawaseBlur.downsample.value; var width = cameraTextureDescriptor.width / downsample; var height = cameraTextureDescriptor.height / downsample; tmpId1 = Shader.PropertyToID("tmpBlurRT1"); tmpId2 = Shader.PropertyToID("tmpBlurRT2"); cmd.GetTemporaryRT(tmpId1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32); cmd.GetTemporaryRT(tmpId2, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32); tmpRT1 = new RenderTargetIdentifier(tmpId1); tmpRT2 = new RenderTargetIdentifier(tmpId2); ConfigureTarget(tmpRT1); ConfigureTarget(tmpRT2); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (!renderingData.cameraData.postProcessEnabled) return; if (!IsValid()) return; var passes = kawaseBlur.passes.value; CommandBuffer cmd = CommandBufferPool.Get(RenderTag); RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor; opaqueDesc.depthBufferBits = 0; // first pass cmd.SetGlobalFloat("_offset", 1.5f); cmd.Blit(source, tmpRT1, material); for (var i = 1; i < passes - 1; i++) { cmd.SetGlobalFloat("_offset", 0.5f + i); cmd.Blit(tmpRT1, tmpRT2, material); // pingpong var rttmp = tmpRT1; tmpRT1 = tmpRT2; tmpRT2 = rttmp; } // final pass cmd.SetGlobalFloat("_offset", 0.5f + passes - 1f); cmd.Blit(tmpRT1, source, material); context.ExecuteCommandBuffer(cmd); cmd.Clear(); CommandBufferPool.Release(cmd); } bool IsValid() { if (material == default) { Debug.LogError("material is not found."); return false; } if (kawaseBlur == default) { Debug.LogError("kawaseBlur is not found."); return false; } return kawaseBlur.IsActive(); } } CustomRenderPass scriptablePass = default; public override void Create() { scriptablePass = new CustomRenderPass(RenderPassEvent.AfterRenderingTransparents); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { scriptablePass.Setup(renderer.cameraColorTarget); renderer.EnqueuePass(scriptablePass); } }KawaseBlur.shaderShader "Custom/RenderFeature/KawaseBlur" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderPipeline" = "UniversalPipeline" } Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_TexelSize; float4 _MainTex_ST; float _offset; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f input) : SV_Target { float2 res = _MainTex_TexelSize.xy; float i = _offset; fixed4 col = float4(0, 0, 0, 1); col.rgb = tex2D(_MainTex, input.uv ).rgb; col.rgb += tex2D(_MainTex, input.uv + float2( i, i) * res).rgb; col.rgb += tex2D(_MainTex, input.uv + float2( i, -i) * res).rgb; col.rgb += tex2D(_MainTex, input.uv + float2(-i, i) * res).rgb; col.rgb += tex2D(_MainTex, input.uv + float2(-i, -i) * res).rgb; col.rgb /= 5.0f; return col; } ENDCG } } }Editor上では下記のようなことまでできました。(解像度的に見辛いかもしれませんが...)
ふぅ...できた。 pic.twitter.com/3888qsvsJe
— 山下 貴史 (@kishi_yama) March 14, 2021実機検証
普段はiOS/Androidで開発していますが、AndroidではVukkan/OpenGL ESの差でハマりがちなので、警戒が必要です。
手元のiPhone 12, Google Pixel XL(Vulkan)では無事動作しましたが、Open GL ES 3.0にフォールバックされた場合は手元に検証端末がなく、確認していません。
まとめ
簡単なポストエフェクト実装からURPに入門してみました。
Scriptable Rendering Pipelineまわりは開発が活発で次から次へと状況が変わっており、ただでさえ敷居の高いGraphicsに拍車をかけて追いかけるのが難しいです。
また作業中何かの拍子にGameViewに何も映らなくなりUnity再起動で直ったり、
VolumeComponent
で公開するClampedIntParameter
のmin,maxの値を変更して再コンパイルしたときに正常に反映されず一度VolumeComponent
を外して付け直すと直る、見慣れない警告が出たりといったような事が発生して少し驚きます。とはいえ最近はモバイルゲームといえど、ハイクオリティなタイトルも珍しくなくなってきているため、今後もURPへの期待は業界内で高まっていくのが自然な流れかなと思っています。
ある程度手応えを得ることができたので、これを皮切りに今後もリファレンスやサンプルを読み漁りつつ追いかけてみようと思います。
- 投稿日:2021-03-14T03:33:57+09:00
Caching.ClearCacheでキャッシュの削除に失敗してfalseが返ってくる
事象
・Caching.ClearCacheでキャッシュの削除に失敗してfalseが返ってくる
・アセットバンドルのダウンロード後すべて失敗再現方法
- アセットバンドルダウンロード
- Caching.ClearCache()実行
- アセットバンドルダウンロード
- Caching.ClearCache()実行(失敗!!!!)
ClearCacheする直前に AssetBundle.UnloadAllAssetBundles(false); を呼んであげると解決
- 投稿日:2021-03-14T00:25:37+09:00
【Unity】スマホアプリでカメラロールへの保存・SNSにシェアができるアセット NatShareのメモ
はじめに
【Unity】簡単にビデオ録画ができるアセット NatCorderのメモという記事の続きになります。
https://qiita.com/Formi/items/4b1ea2c53e25033436eaスマホアプリ内で作成した画像や動画ファイルをカメラロールへ保存・SNSへシェアしたかったので利用しました。
NatCorderと違い無料のAssetになのがありがたいですね!NatShare
NatShare は、軽量で使いやすい Unity Engine 用のソーシャル共有 API です。NatShare を使用すると、わずか 3 つのステップでクロスプラットフォームのテキスト、画像、メディアの共有が可能になります。
公式API
https://docs.natsuite.io/natshare/
こちらからインポートできます。
https://assetstore.unity.com/packages/tools/integration/natshare-mobile-sharing-api-117705Share方法
SharePayloadクラス
SharePayloadクラスを生成します。
名前の通りこのオブジェクトを使ってシェアをすることになります。var sharePayload = new SharePayload();SharePayload.AddText
シェアするときに利用するテキストを設定する関数です。
引数にはstring型で渡してあげます。sharePayload.AddText(stringText);SharePayload.AddMedia
シェアしたい「音声・画像・ビデオ」ファイルを設定する関数です。
引数にはstring型でpathを渡してあげます。sharePayload.AddMedia(mediaPath);SharePayload.AddImage
シェアしたい「画像」ファイルを設定する関数です。
引数にはTexture2D型で直接ファイルを指定します。(NatCorderと合わせて使う場合はpathが吐き出されるのでAddMediaでいい気がします)sharePayload.AddMedia(mediaPath);SharePayload.Commit
上記関数で設定した情報をもとにShereをします。
プラットフォーム判別はいい感じにやってくれるので気にしなくても良いです。sharePayload.Commit();カメラロールへの保存方法
SavePayloadクラス
SharePayloadクラスを生成します。
オブジェクト生成時に保存するフォルダー名を指定します。
用意されている関数がSherePayLoadと同じなので簡単に使えます!var savePayload = new SavePayload(FOLDER_NAME);SavePayload.AddMedia
シェアしたい「音声・画像・ビデオ」ファイルを設定する関数です。
引数にはstring型でpathを渡してあげます。savePayload.AddMedia(mediaPath);SavePayload.AddImage
シェアしたい「画像」ファイルを設定する関数です。
引数にはTexture2D型で直接ファイルを指定します。savePayload.AddMedia(mediaPath);SavePayload.Commit
上記関数で設定した情報をもとにカメラロールでフォルダの作成・保存をします。
プラットフォーム判別はこいつがいい感じにやってくれるので気にしなくても良いです。savePayload.Commit();使い方
使いたいクラスのオブジェクトにAddしてCommitするだけで使えます。
Commit関数はTaskになっていて成功したかどうかをreturnしてくれるので、ちゃんと使いたい方は公式APIをのぞいてみてください。まとめ
NatCorderと合わせて使うことでMediaを扱う幅がグッと広がりました!
APIもしっかり用意されていて神Assetだと思います。
- 投稿日:2021-03-14T00:00:23+09:00
【Unity】ステートごとにapplyRootMotionを設定する方法
こんな困りごとありませんか?
移動中は rigidbody で座標を操作したいけど、
この攻撃モーション中は applyRootMotion で座標制御しないと不自然…よし、キャラに付けてるスクリプトの Update で
ステート名が「SpecialAttack」なら applyRootMotion を true にして
ステート名が「Locomotion」なら false にしよう!あ、そうや、「Jump」ステートも applyRootMotion = true で「Fall」は false やな…
あれもこれも分岐に追加や…あーもうぐちゃぐちゃだよ(´・ω・`)
その悩み、解決します!
これでスッキリ!
事前準備として、applyRootMotion の初期値は false にしておきましょう。
つづいて applyRootMotion = true にしたいステートを選択して~
Inspector の一番下「Add Behaviour」から「New Script > ApplyRootMotion」を選択します。
中身はこんなかんじ
ApplyRootMotion.csusing UnityEngine; public class ApplyRootMotion : StateMachineBehaviour { //OnStateEnter is called when a transition starts and the state machine starts to evaluate this state override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { animator.applyRootMotion = true; } // OnStateExit is called when a transition ends and the state machine finishes evaluating this state override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { animator.applyRootMotion = false; } }あとは applyRootMotion を true に設定したいステートごとに取り付ければOK!
結果
キャラに付けてるスクリプトの変更は不要で、こんな感じに制御できます。
余談ですが、攻撃後など基本アクション(移動等)に遷移したいときは
上記のように遷移先を Exit に設定しとくとごちゃごちゃしづらいです。他にもStateMachineBehaviourは応用の幅が広いので、ぜひ活用してくださいね。
何か改善点やトラブルがあったら教えてください!追記
切り替えた瞬間に重力での加速がリセットされるような挙動は、
Animation側に BakeIntoPose(Y) を設定してあげれば解消できます。
※上下動のルートモーションが不要な場合