20210907のUnityに関する記事は12件です。

[Unity]uGUIのデフォルトシェーダーをカスタムしたが、マスク対象のuGUIに反映されない問題

問題  Canvas.GetDefaultCanvasMaterialで取得できるマテリアルのシェーダーを置き換えることで、uGUIにカスタムしたシェーダーを使うことができます。 ChangeUIShader.cs Canvas.GetDefaultCanvasMaterial().shader = customShader;  一部シーンでSRPを使っている関係で、ゲーム中に動的にGetDefaultCanvasMaterialのシェーダーを置き換えていたのですが、MaskコンポーネントでマスクされたuGUIのシェーダーが置き換え前の初期uGUIシェーダーになってしまい、表示が破綻してしまいました。 解決方法 上記のコードでシェーダーを置き換えた後、下記処理を入れることで解決しました。 ChangeUIShader.cs StencilMaterial.ClearAll(); foreach (var canvas in FindObjectsOfType<Canvas>().Where(x => x.isActiveAndEnabled)) { canvas.enabled = false; canvas.enabled = true; } 解説 マスクできるuGUIは全てMaskableGraphicを継承しています。 MaskableGraphicは、マスクされたときにGetModifiedMaterialメソッドが呼ばれ、その中で現在のuGUIのマテリアルを基にして、マスクされた時用のマテリアルをStencilMaterial.Addで生成しています。 GetDefaultCanvasMaterialを置き換える前にマスク用のマテリアルが作られてしまうと、GetDefaultCanvasMaterialを置き換えた後もそのマテリアルが使われるため、これが原因で表題のようなことが起きていました。 対策として入れたコードは、UIのデフォルトマテリアルのシェーダー置き換え前のものが残ってしまっているところを、クリア&全部Graphic.SetMaterialDirty呼ぶことで解決しているものです。 社内の有識者に教えてもらったんですが、コンナノワカラナイヨ…! 余談:Unityの標準コンポーネントのコードってどこで読むの? 今回だと、MaskableGraphic.csを読むことで原因が分かったんですが、標準コンポーネントのソースコードってどこで読むのが正道なんですかね…? URPをインポートしてるプロジェクトでMaskableGraphic.csを検索にかけたら Librarly フォルダ内の下記の場所に格納されてて読めたんですが、こういう調べ方がセオリーだとは思わないので、次似たようなことが起きたときのために参照すべき場所を知っておきたい…。 Libraly/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/ 参考:MaskableGraphic.GetModifiedMaterial MaskableGraphic.cs /// <summary> /// See IMaterialModifier.GetModifiedMaterial /// </summary> public virtual Material GetModifiedMaterial(Material baseMaterial) { var toUse = baseMaterial; if (m_ShouldRecalculateStencil) { if (maskable) { var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); m_StencilValue = MaskUtilities.GetStencilDepth(transform, rootCanvas); } else m_StencilValue = 0; m_ShouldRecalculateStencil = false; } // if we have a enabled Mask component then it will // generate the mask material. This is an optimization // it adds some coupling between components though :( if (m_StencilValue > 0 && !isMaskingGraphic) { var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0); StencilMaterial.Remove(m_MaskMaterial); m_MaskMaterial = maskMat; toUse = m_MaskMaterial; } return toUse; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VContainer入門(5) - EntryPoint

この記事について 前回に引き続きVContainerの説明をしていきます。 今回はEntryPointについて説明します。 EntryPointを使うとMonoBehaviourを継承しないクラスでもUpdateなどのイベントを利用できます。 目次ページ: VContainer入門 EntryPointを使ってみる EntryPointとして使うクラスでは用意されたインターフェースを実装します。 次の例ではITickableインターフェースを実装したTickableSampleクラスを用意しています。 TickableSample.cs public sealed class TickableSample : ITickable { // このメソッドがUpdateのタイミングで呼び出される public void Tick() { Debug.Log("Tick : " + Time.time); } } ITickableはTickメソッドが定義されているだけのインターフェースです。 このクラスをRegisterEntryPointで登録すると使えます。 LifetimeScope protected override void Configure(IContainerBuilder builder) { builder.RegisterEntryPoint<TickableSample>(); } これでTickableSampleのTickがUpdateのタイミングで呼び出されるようになります。 エントリーポイントの登録(RegisterEntryPoint) RegisterEntryPointで登録されたクラスは、IContainerBuilderの生成直後にインスタンス化されます。 Registerで登録されたクラスは、他クラスからの依存関係を解決するタイミングでインスタンス化される点で異なります。 つまり、RegisterEntryPointで登録すれば他から依存されていなくても必ずインスタンス化されるエントリーポイントとして利用できます。 登録できるインターフェースの一覧 ITickableはUpdateのタイミングで呼び出されますが、Update以外で呼び出されるインターフェースも使えます。 以下のインターフェースが使えます。 インターフェース メソッド 呼び出されるタイミング IInitializable Initialize() クラスが生成された直後 IPostInitializable PostInitialize() IInitializableが呼び出された直後 IStartable Start() MonoBehaviourのStartの直前 IPostStartable PostStart() MonoBehaviourのStartの直後 ITickable Tick() MonoBehaviourのUpdateの直前 IPostTickable PostTick() MonoBehaviourのUpdateの直後 IFixedTickable FixedTick() MonoBehaviourのUpdateの直前 IPostFixedTickable PostFixedTick() MonoBehaviourのUpdateの直後 ILateTickable LateTick() MonoBehaviourのLateUpdateの直前 IPostLateTickable PostLateTick() MonoBehaviourのLateUpdateの直後 RegisterEntryPointに渡すクラスは上記のインターフェースを1つ以上実装する必要があります。 どのインターフェースも実装されていないクラスを渡しても、そのクラスはインスタンス化されません。 また、そのクラスにIDisposableが実装されていればIObjectResolverが破棄されるタイミングでDisposeが呼び出されます。 エントリーポイントの生成 前述の通り、RegisterEntryPointで登録してインスタンス化してもらうにはいずれかのインターフェースが必要です。 インターフェースで提供されるイベントが必要ないクラスでも、VContainerにインスタンス化してほしい時には次の手段があります。 実際には使わないIInitializableを実装しておく RegisterBuildCallbackを利用する 以下でそれぞれ説明します。 使わないIInitializableを実装しておく こちらは単純に、とりあえずIInitializableを実装しておくだけです。 EntryPointSample.cs public sealed class EntryPointSample : IInitializable { public void Initialize() { // 何もしない } } このクラスをRegisterEntryPointで登録すれば、不要なInitialize呼び出しは発生しますがインスタンスは生成されます。 RegisterBuildCallbackを利用する こちらではVContainerのEntryPointの機能に頼らず、RegisterBuildCallbackで強制的にインスタンスを生成してしまいます。 EntryPointSample.cs public sealed class EntryPointSample { } LifetimeScope protected override void Configure(IContainerBuilder builder) { builder.Register<EntryPointSample>(Lifetime.Singleton); builder.RegisterBuildCallback(objectResolver => { objectResolver.Resolve<EntryPointSample>(); }); } RegisterBuildCallbackを使うと、IObjectResolverの生成直後に呼び出されるコールバックを登録できます。 そのコールバックの中でobjectResolver.Resolveを呼び出してインスタンスを生成させています。 こちらの方法を多用する際は、RegisterとRegisterBuildCalbackを行う拡張メソッドを用意しておくと便利です。 ObjectResolverExtensions.cs public static class ObjectResolverExtensions { public static RegistrationBuilder RegisterPlainEntryPoint<T>(this IContainerBuilder builder, Lifetime lifetime = Lifetime.Singleton) { builder.RegisterBuildCallback(objectResolver => objectResolver.Resolve<T>()); return builder.Register<T>(lifetime); } } この拡張メソッドを用意しておけば、builder.RegisterPlainEntryPoint<EntryPointSample>();の一行で先ほどと同じRegisterとRegisterBuildCallbackを呼び出せます。 例外処理(RegisterEntryPointExceptionHandler) RegisterEntryPointExceptionHandlerで、EntryPoint内で発生した例外を処理できます。 TickableSample.cs public sealed class TickableSample : ITickable { public void Tick() { throw new System.NotImplementedException(); } } LifetimeScope protected override void Configure(IContainerBuilder builder) { // 例外が発生したときに呼び出されるコールバックを登録できる builder.RegisterEntryPointExceptionHandler(e => { Debug.LogException(e); }); builder.RegisterEntryPoint<TickableSample>(); } RegisterEntryPointExceptionHandlerを使わない場合、発生した例外はDebug.LogExceptionで出力されます。 なので上記のサンプルコードのRegisterEntryPointExceptionHandlerはデフォルトの動作と同じになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]Blitで渡したカメラに映る画面テクスチャがシェーダーで空になる問題

先に結論 CameraTarget(画面)はテクスチャじゃ無いため、_MainTexとして渡せないことが原因でした。 sourceとdestのみのBlit呼び出しで一時的なレンダテクスチャに画面の表示を渡し、その後にシェーダーをかけることで解決できました。 経緯 GameRender.cs commandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, dest, material); みたいなコードで画面にポストエフェクトをかけるシェーダーを書いていたのですが、シェーダー側の_MainTexにCameraTargetが持ってるはずのテクスチャが渡されませんでした。 ドキュメントを読んでも ソーステクスチャやレンダーターゲットは "_MainTex" プロパティーとしてマテリアルに渡されます としか書いておらず、原因が分からず… いろいろ調べても情報が無く、ヤケクソでTwitterで検索かけたら下記のツイートがヒットしました。 画面はテクスチャじゃ無いかららしい という言葉のソースになるドキュメントは見つかりませんでしたが、起きてる状態に完全に合致したので、一時的なレンダテクスチャを用意してBlitを2段階に分けたところ、見事解決できました。ツイートに感謝…! GameRender.cs static int TempID = Shader.PropertyToID("_Temp"); // 中略 // 一時的なレンダテクスチャを作成 commandBuffer.GetTemporaryRT(TempID, -1, -1, 0, FilterMode.Bilinear); // CameraTargetを一時的なレンダテクスチャに移して、それに対してシェーダーをかける commandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, TempID); commandBuffer.Blit(TempID, dest, material); // 一時的なレンダテクスチャを解放 commandBuffer.ReleaseTemporaryRT(TempID); このようにコードを修正することで解決しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]Blitで上下反転して描画される問題

原因 GPUによって描画する際のy座標の0の基準が異なるらしいです。 Direct3D系:座標の一番 上が0。Direct3D、Metalなど OpenGL 類:座標の一番 下が0。OpenGL、OpenGL ESなど で、基本的にこれはUnityは勝手に吸収してくれるようなのですが、レンダテクスチャに描くときにその吸収する仕組みが悪さすることがあるようです。 ▼参考:公式ドキュメント 対策 この仕組みが原因で上下反転する場合は、シェーダ書いて解決してね、と上記の公式ドキュメントに書いてありました。 具体的には DirectX系/Metalのときのみ、vertexシェーダーでUVを反転をさせるようです。 ReverseResolve.shader #if UNITY_UV_STARTS_AT_TOP // DirectX系, Metalの場合はY軸反転. o.uv.y = 1 - o.uv.y; #endif UNITY_UV_STARTS_AT_TOPを使って、座標の一番上が0のGPUであるかどうかを分岐させ、trueの場合のみ反転させています。 これで解決できました。 余談 今回の件でハマったときいろいろ調べてたら、「CommandBufferでレンダリング結果を利用する場合には、Camera.forceIntoRenderTextureを有効にすると良い」という記事も見かけました。 自分のケースには当てはまりませんでしたが、そういうこともあるみたいです。 また、CommandBuffer.Blitの内部で行われるいろいろな処理分岐によるハマりポイント的な記事も見かけたので、こちらも備忘録として記載しておきます。 これ↓とかよく分からないけど何らかのハマりポイントになりそう…。 AfterEverything開始時の、CurrentActive, CameraTargetをsrcとしたBlit命令は、内部でBlit Copyではなく、Back BufferをsrcとしたGrab RenderTextureが発生する シェーダー全文 ReverseResolve.shader Shader "Camera/ReverseResolve" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } 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_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); #if UNITY_UV_STARTS_AT_TOP // DirectX系, Metalの場合はY軸反転. o.uv.y = 1 - o.uv.y; #endif return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UniTaskのChannelを使う

Channel UniTask には Channel という機能があります。名前空間は Cysharp.Threading.Tasks です System.Threading.Channels を元に作られていて、 async/await と親和性の高いデータの受け渡しが手軽にできます データを発行する生産者 と それを読み取る消費者 という2人の登場人物が出てきます UniTaskのChannel と System.Threading.Channels.Channel (読み飛ばしても良き) Channel は 生産者と消費者が 何 : 何 なのかをしっかり意識してプログラムを書く必要があります System.Threading.Channels は Channel<int> channel = Channel.CreateUnbounded<int>( new UnboundedChannelOptions { SingleWriter = true, SingleReader = true, }); こんな感じで Channel に Writer(生産者), Reader(消費者)が単一なのか複数なのかをオプションで指定できます 対して UniTask の Channel は 単一消費者 専用 です Channel<int> channel = Channel.CreateSingleConsumerUnbounded<int>(); ファクトリメソッドの名前からして単一消費者臭がぷんぷんしますね! 実際 Channel<T> は abstract なので今後 Channel.CreateUnbounded が来てもおかしくない! Writer と Reader Channel は Writer(生産者), Reader(消費者) に分かれてデータのやりとりをします。 データ構造は内部でキューが使われていて、 Writer が エンキュー、 Reader がデキューをするイメージです。 UniTask の Channel は単一消費者なので注意が必要です Writer Writerはこんな感じの手順です Channel から ChannelWriter を取得する 値を書き込む(Enqueueする) チャネルを閉じる // ChannelWriterを取得する ChannelWriter<int> writer = channel.Writer; // 値を書き込む writer.TryWrite(1); writer.TryWrite(2); writer.TryWrite(3); // チャネルを閉じる writer.TryComplete(); writer.TryWrite キューに値を追加します。チャネルが既に閉じている場合は false が返ってきます writer.TryComplete / void writer.Complete 後片付け的な処理です。チャネルを閉じ、これ以降は値を書き込めなくなります 既にチャネルが閉じている場合、TryComplete は false を返し、Complete は ChannelClosedException がスローされます Reader Channel から ChannelReader を取得する 読み込む(Dequeueする) (チャネルの完了を待つ) // ChannelReaderを取得する ChannelReader<int> reader => channel.Reader; // 値が書き込まれるまで待ってtrueを返す(クローズしていてキューに何もなくなるとfalseを返す) // 既に値がキューされていたら true を返す if(await reader.WaitToReadAsync(token)) { // キューから値を1つ取り出す。キューに何もなかったらfalseを返す if(!reader.TryRead(out var i)) return; Debug.Log(i); // 値が取れたときだけログを吐く } // 既に値がキューされていたらデキューして返す // なかったら WaitToReadAsync + TryRead の挙動をする var value = await reader.ReadAsync(); // 全ての書き込みを IUniTaskAsyncEnumerable<T> で値を取りに行く await reader.ReadAllAsync() .ForEachAsync(i => { Debug.Log(i); }); // クローズされていてキューに何もないときに完了するUniTask await reader.Completion; Reader はいくつか読み込み方法があるので使い分ける必要があります。 1個だけ値が欲しいとき await reader.WaitToReadAsync + reader.TryRead WaitToReadAsync は値が書き込まれるまで待ちます 両方とも例外を吐かず、キューに何もない場合は false が返ってきます そのとき、reader.TryRead には default値が渡されるので注意しましょう await reader.ReadAsync 既に値がキューされていたらデキューして返し、なければ WaitToReadAsync + TryRead の挙動をします クローズされていてキューになにもない場合は ChannelClosedException がスローされます ログを出したくないときは try-catch で例外を握りつぶせばOK! 複数の値が欲しいとき while + await reader.WaitToReadAsync + reader.TryRead while と WaitToReadAsync を組み合わせることで複数待機が可能です // チャネルが閉じるまで繰り返す while(await reader.WaitToReadAsync()) { if(!reader.TryRead(out var i)) return; Debug.Log(i); } while + await reader.ReadAsync // 例外が吐かれるので無限ループでも大丈夫 while(true) { var i = await reader.ReadAsync(); Debug.Log(i); } reader.ReadAllAsync writer.TryWrite を IUniTaskAsyncEnumerable<T> で待機できます。個人的にこれが一番好きですね Forget すればブロッキングせずに読み込み処理ができる、とか 値の取り方(ForEachAwaitAsyncとか)オペレータを組わせたりとか色々な工夫ができます チャネルが閉じすべての値を読み込み終わったら foreach の await が完了します reader.Completion Writer が TryComplete / Complete を呼んでいて(クローズ)、かつ Reader の読み込みが終了した(キューが空になった)ときに完了する UniTask です TryComplete / Complete の引数に例外を指定していたら、reader.Completion は失敗状態になり例外を吐きます 注意点 やはり注意すべきは単一消費者という点です 内部では1つのチャネルに1つのキューが使われていて Writer も Reader もそれを共有しています。多方面から Reader が デキューをするとそりゃ意図しない挙動になるよねっていう話です。狭いスコープで使うのが良いと思います。 Reader を public にしてデータのやり取りをする UniRx みたいな使い方はできないでしょう。 もし値を外部にも通知したい場合は reader.ReadAsync().Publish() を使うことで安全に IUniTaskAsyncEnumerable として公開できます IUniTaskAsyncEnumerableとして外部に公開する ということで、例として Channel で動くタイマーを作ってみました public class AsyncTimer : IDisposable { private readonly int limitTime; // 制限時間 private readonly Channel<int> channel; private readonly IConnectableUniTaskAsyncEnumerable<int> publish; private readonly IDisposable connection; private CancellationTokenSource cancellationTokenSource; private CancellationToken cancellationToken; // IUniTaskAsyncEnumerable<T>として公開する public IUniTaskAsyncEnumerable<int> CountdownAsyncEnumerable => publish; public AsyncTimer(int limitTime) { // チャネルを生成 channel = Channel.CreateSingleConsumerUnbounded<int>(); // IConnectableUniTaskAsyncEnumerable<T>に変換 publish = channel.Reader.ReadAllAsync().Publish(); // .Connect()で起動する connection = publish.Connect(); this.limitTime = limitTime; cancellationTokenSource = new CancellationTokenSource(); cancellationToken = cancellationTokenSource.Token; } public async UniTask CountdownAsync() { // カウントダウン処理 foreach(var i in Enumerable.Range(0,limitTime + 1).Reverse()) { // 値を書き込む channel.Writer.TryWrite(i); if(i == 0) break; await UniTask.Delay(1000, cancellationToken : cancellationToken); } // チャネルをクローズする channel.Writer.TryComplete(); } public void Dispose() { channel.Writer.TryComplete(); connection.Dispose(); cancellationTokenSource.Cancel(); cancellationTokenSource.Dispose(); } } 注目すべきはココです // チャネルを生成 channel = Channel.CreateSingleConsumerUnbounded<int>(); // IConnectableUniTaskAsyncEnumerable<T>に変換 publish = channel.Reader.ReadAllAsync().Publish(); // .Connect()で起動する connection = publish.Connect(); Publish() と Connect() で外部に公開してもちゃんと動く IUniTaskAsyncEnumerable ができました .Connect()を呼ばないと writer.TryWrite しても 値を取りに行けないので注意が必要です まとめ チャネルは非同期キューイングな処理ができる Writer と Reader に分かれて処理を行う 基本は System.Threading.Channels と同じなのでそっちを調べても良き 単一消費者専用なので注意 消費者を複数にする場合は Publish() と Connect() を使う 参考 UniTask v2 – Unityのためのゼロアロケーションasync/awaitと非同期LINQ System.Threading.Channelsを使う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity] Android NativePluginの実装の仕方

はじめに Unityで使用するNativePluginのタスクがあったのでとりあえずテストでサンプルを作成してみました。 ここでは、UnityでAndroidのNativeアラートを表示させるまでの忘却録です。 準備 macOS Catalina: 10.15.7 Unity 2018.4.19f Android Studio 4.0.1 Android Studioでプロジェクトを作成 今回は「AndroidStudio4.0.1」で作成していきます。 「Select a Project Template」で「No Activity」を選択 Projectの概要を決めていきます。「Package name」はサンプルなので適当です。言語は「Java」を選択しました。 ライブラリ用モジュールを作成する 「File」→「New」→「NewModule」を選択 「Select a Module Type」で「Android Library」を選択 「Module name」もサンプルなので適当にそのままにしました。 左上にあるプルダウンから「Android」を選択しておきます。 「mylibrary」が出来てます。 Plugin開発には不必要なAppディレクトリを削除する 「File」→「Project Structure」を選択 左サイドバーの「Modules」を選択して「app」を選択。赤丸の「-」で削除します。 「Remove Module」で削除確認されますが、「Yes」です。 Unity機能をAndroidから使用する準備 Android側からUnityのクラスを認識するために「jar」ファイルをインポートする必要があります。 Android 用に開発されたプラグインを使用して、Unity の外部で作成された Java や C++ コードを C# スクリプトから呼び出しすることができます。これにより、他の方法では Unity で使用できない OS の呼び出しやサードパーティのコードライブラリなどの機能にアクセスできます。Unity によるプラグインの使用方法の詳細については、プラグイン のドキュメントを参照してください。 以下のページでは、Android プロジェクトで使用する独自のプラグインを作成する手順を説明します。これらのページの情報は、Unity のネイティブプラグインの作成方法をすでに理解していることを前提としています。ネイティブプラグインとその使用の詳細については、ネイティブプラグイン のドキュメントを参照してください。 https://docs.unity3d.com/ja/current/Manual/PluginsForAndroid.html 場所はUnityがインストールされてるディレクトリにあります。 ファイル名: classes.jar 下記は、UnityHubを使用していた場合の格納場所です。 /Applications/Unity/Hub/Editor/2018.4.19f1/PlaybackEngines/AndroidPlayer/Variations/il2cpp/Release/Classes/classes.jar Androidプロジェクトの「libs」ディレクトリへ「classes.jar」をコピーします。 Gradleの設定からclasses.jarを外す Gradleの設定からclasses.jarを外さないと、出力されるaarファイルに含まれてしまいUnityのビルド時にエラーが出てしまうので外さないといけません。ということで、「build.gradle」を編集します。 下記、箇所を変更。また、追記します。 build.gradle // 26行目 NG: implementation fileTree(dir: "libs", include: ["*.jar"]) ↓ OK: compileOnly fileTree(dir: 'libs', include: ['classes.jar']) // 追記箇所 android.libraryVariants.all{ variant-> variant.outputs.each{output-> output.packageLibrary.exclude('libs/classes.jar') } } 完成形は下記。右上に出ている「SyncNow」を押下して完了 Unityで呼び出すNativeAlertDialogのサンプル 該当箇所からClassを作成します。 該当コード AndroidNativeDialog.java package jp.co.test.mylibrary; import android.app.AlertDialog; import android.content.Context; public class AndroidNativeDialog { static public void showNativeDialog(Context context, String title, String message) { new AlertDialog.Builder(context) .setTitle(title) .setMessage(message) .setPositiveButton("はい", null) .setNegativeButton("いいえ", null) .show(); } } ビルド 画面右側に「Gradle」のアイコンがありますので、設定画面を表示します。 「mylibrary」→「Tasks」→「build」→「assemble」をダブルクリックでビルドが始まります。 成功すると「BUILD SUCCESSFUL in 時間」が表示されます。 aarの場所 左上から「Project」を選択し、「mylibrary」→「build」→「outputs」→「aar」の中にあります。 該当のファイルから「Reveal in Finder」でFinderが開いてくれます。 aarをUnityプロジェクトへ配置する UnityプロジェクトをAndroidにSwitchPlatformします。 該当のaarは「Assets」→「Plugins」→「Android」のディレクトリを無ければ作成して配置します。 Unity側の対応(呼び出し方法)サンプル 飛び出す際に「AndroidJavaClass」、「AndroidJavaObject」を使います。 サンプルでは、ShowNativeDialog()をButtonEventで使用しました。 ButtonView.cs using UnityEngine; public class ButtonView : MonoBehaviour { public void ShowNativeDialog() { #if UNITY_ANDROID AndroidJavaClass nativeDialog = new AndroidJavaClass ("jp.co.test.mylibrary.AndroidNativeDialog"); AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject context = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); context.Call ("runOnUiThread", new AndroidJavaRunnable(() => { nativeDialog.CallStatic ( "showNativeDialog", context, "タイトル", "本文テキスト" ); })); #elif UNITY_EDITOR UnityEngine.Debug.Log("On Click!"); #endif } } サンプルフォルダ構成 Unity配置 ボタンを押下するとアラートダイヤログを表示 ビルドして実機で確認します。 成果物
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】AndroidアプリをGooglePlayに公開する

Unity 下記項目を設定 会社名 プロダクト名 アイコン パッケージ名 ヴァージョン ビルド番号 ARM64にチェック キーストアを登録 Build App Bundleにチェックを入れてビルド GooglePlayConsole アカウントを作成 右上のボタンからアプリを作成 ダッシュボードの指示に従い必要な情報を登録 審査に提出
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UniRxの使い方を間違い、フリーズを発生させてしまった

はじめに 初歩的なミスをしてしまったので、再び発生させないようにするためにも備忘録を残します。 制作しているアプリで一定時間メインスレッドが止まり表示が更新されないフリーズが特定条件で発生していました。 原因を調査するためにプロファイラーを見ていたところ、GarbageCollectionで大きな山が? GCが発生した箇所 プロファイラーの詳細を見ると、ObservableDestroyTrigger.OnDestroyで発生していました。 コードを調べてみると、IObservableのSubscribe時に呼び出すAddTo()が原因でした。 問題となったコード 実際のコードは載せられないので、近い挙動をするコードになります。 using System; using UniRx; using UnityEngine; public class Sample : MonoBehaviour { private Subject<Unit> _apiSubject = new Subject<Unit>(); private Subject<Unit> _subject = new Subject<Unit>(); void Start() { // ApiReceive時の想定 _apiSubject.StartWith(Unit.Default).Subscribe(_ => { UpdateSubject(); }).AddTo(this); // Apiの複数回投げたときの想定 for (int i = 0; i < 26000; i++) { _apiSubject.OnNext(Unit.Default); } } void UpdateSubject() { _subject.Subscribe(_ => { Debug.Log("DoSomething"); }).AddTo(this); } void Update() { if (Input.GetKeyDown(KeyCode.D)) { Destroy(gameObject); } } } 実際のコードが、Apiであるデータのレスポンスが返ってきたときに対象の処理を再購読するというものでしたので、上記のようなコードになっています。 (実際のコードではこれほど大量にNextを発行していませんが、実際起った挙動を再現するためにこのようにしています) ちなみにこちらのコードでGameObjectがDestroyされたときに、2.52GBのGC Allocが働いています。 なぜAddToが必要か 購読とオブジェクトを紐付けないと、オブジェクトが破棄されても購読が残り処理が走ってしまいます。 今回のコードは同じクラス内で生成したSubjectに対して購読しているので、AddTo()がなくても問題ありませんが、 クラスを跨いだときに問題が出るのでAddToを呼び出す癖をつけておいた方が良いです。 実際の挙動 フリーズが分かり易いように、アニメーションしているキューブを表示しています。 Dキーを押した時に、GameObjectがDestroyされるときにフリーズが発生しています。 何が問題だったか こちらの処理に問題があります。 void UpdateSubject() { _subject.Subscribe(_ => { Debug.Log("DoSomething"); }).AddTo(this); } 再び購読する際に、前回購読していたものを破棄していないのでどんどん購読が積まれて行ってしまいます。 正しいコード private IDisposable _disposable = null; void UpdateSubject() { _disposable?.Dispose(); _disposable = _subject.Subscribe(_ => { Debug.Log("DoSomething"); }).AddTo(this); } 再購読時、上記のように前回のものを破棄してから購読させます。 ?はnull条件演算子で、オブジェクトがnullの場合は関数が呼ばれません。 以下と同じ処理になります。 if (_disposable != null) { _disposable.Dispose(); } 最後に 普段、再購読するときは前回購読したものを破棄すると気をつけてはいましたが、今回はミスで正しくないコードになっていました。 ちょっとしたミスが分かりづらい不具合を生んでしまいます。 今回のミスのおかげで、GCが大量に走ってしまうパターンを発見することができました。 今回紹介した内容は初歩的なミスですが、これからはAddToに同じオブジェクトを大量に指定しないように気をつけなければいけませんね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

asset化しない生のファイル読み込み

var a = await Re.Get<T>(name);//T: Texture2D or string using UnityUtil; //... async void Start() { Re.basepath=Application.streamingAssetsPath; //.../Assets/StreamingAssets //quad var tex =await Re.Get<Texture2D>("stand.png"); gameObject.GetComponent<Renderer>().material = tex.ToMat(); //rawimage var tex = await Re.Get<Texture2D>("stand.png"); gameObject.GetComponent<RawImage>().texture = tex;// as Texture; gameObject.GetComponent<RectTransform>().sizeDelta=new Vector2(tex.width,tex.height); //image var tex = await Re.Get<Texture2D>("stand.png"); gameObject.GetComponent<Image>().sprite = tex.ToSprite(); //text var str= await Re.Get<string>("test.txt"); Debug.Log(str); Debug.Log("end"); } namespace UnityUtil { using System.Collections.Generic; using UnityEngine; using System.IO; using System.Threading.Tasks; public static class Re { /*usage using UnityUtil; Re.basepath=Application.streamingAssetsPath; var a =await Re.Get<string>("license.txt"); var b= await Re.Get<Texture2D>("zed1.jpg"); */ public static Dictionary<string, object> cash = new Dictionary<string, object>(); public static string basepath = ".\\"; public static async Task<T> Get<T>(string name) { name = name.Trim(); if (!cash.ContainsKey(name)) { var o = await Load(name); cash.Add(name, o); } // return (T)cash[name]; } static async Task<object> Load(string name) { var ret = new System.Object(); var path = Path.Combine(basepath, name); if (isText(name)) { var str = File.ReadAllText(path); ret = str; } else { var tex = new Texture2D(1, 1); var bytes = File.ReadAllBytes(path); tex.LoadImage(bytes); ret = tex; } return ret; // bool isText(string name) => name.IndexOf(".txt") != -1; } public static Material ToMat(this Texture2D tex,string shadername="Transparent/Diffuse"){ Material mat = new Material(Shader.Find(shadername)); mat.SetTexture("_MainTex", tex); return mat; } public static Sprite ToSprite(this Texture2D tex) { return Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), Vector2.zero); } } }//namespace
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10 で Unity Android buildに失敗したこと。(Gradle failed、原因 license agreement 不足をsdkmanagerで解決(Windows)

発生した問題 UnityHub より AndroidSDK のインストールも行った。 次に、実際にAndroidビルド実行時にGradlebuild失敗となった。 エラーログ等をキャプチャしてなかったがとりあえず、以下のような雰囲気のエラーが出た。 Android SDKをインストールしてパスが通ってる状態です。 build-tools;29.0.2 license agreement 解決 license同意を行えば良いという情報を得て実行したところ解決した。 cmd から行いました。(PowerShell等もお使いください。この場合.bat拡張子まで書く必要があるらしい。) 注意点: cmdは管理者権限で実行する必要がある。 (コマンドを実行すると最初に進捗5%まで進んだりするし、エラーログ呼んでも権限の問題だと認識できなかった。) <AndroidSDK>\tools\bin\sdkmanager.bat が存在するフォルダ(bin\)へ移動します。 sdkmanager "build-tools;29.0.2" を実行します。 sdkmanager --licenses を実行して全て yes にします。 Macに慣れた後、windows開発を始めて慣れておらず罠に陥った感ある。 実際はログを読んでググるを繰り返せば大抵は何とかなる。(頭は良くならないが)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity 2018 3D表示を2D表示にする方法

0.0 はじめに Unityにて2Dゲーム設定だけどなぜかSceneビューが3D風になった時の対処方法です。結構悩んだので備忘録です。 1.0 変更方法 Windowメニュー → Rendering → Lighting Settingsを選ぶ Skybox MaterialをNoneに変える 2.0 変更前 3.0 変更後
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】シングルトンを使わずにシーン間でデータを共有したい

RPGゲームを開発中のUnity初心者です。シーン間のデータ共有の方法について検索をかけると「DontDestroyOnLoad」や「シングルトン」が多く結果に上がってきますが、正直あまり使いたくないのが本音です。 ます「シングルトン」はあまり多用したくないですよね・・・。 アイテムの所持状態やキャラステータス等シーン間で共有したいデータはたくさんあるのですが、その数のシングルトンを用意するのはオブジェクト指向に反していると感じます。「static」も同じです。 「DontDestroyOnLoad」に関しては個人的な感想なのですが、寿命が長いオブジェクトってどうなのっていう(;´・ω・) シーンを切り替えても切り替えてもヒエラルキーに「DontDestroyOnLoad」が残り続けるのがなんか気持ち悪くて嫌なんです・・・。 ということでシーン切替時にデータを受け渡す方法を探したら神サイト様がありました! SceneManager.sceneLoaded こういう神サイト様の存在は本当にありがたいです? 要はシーン切替時に呼び出すイベントの中で、シーン切替後のスクリプトを呼び出すことが可能だとのことです。その時にデータを渡せますね、便利です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む