今日だけ頑張るんだ

主に軽めの開発記録

自作アプリでUniRxからR3に乗り換えるときにコンパイルが通るまでにやったこと

世間ではUniRxがarchiveされR3への移行が行われ始めているそうです。

github.com

I have started distributing an evolved version of UniRx in Cysharp/R3, please use it instead of UniRx.

github.com

自分は仕事でも個人開発でもUniRxにはお世話になっていました。自作アプリの開発をしていたのでR3に関する情報は傍目に見るだけでしたが、リリースしてひと段落したタイミングでR3への移行を行いました。

naninunenoy.hatenablog.com

所要時間

自作アプリでも System.IObservable<T>UniRx.IReadonlyProperty<T> は結構使ってますし、MessagePipe も使ってます。 そんなプロジェクトですが、4時間程の作業でUniRxからR3へ移行できました。

しかし、とりあえずコンパイルエラーが通ってEditor実行で動いている風なのを確認しただけで、ちゃんと動作確認するとまだ修正点があるかもしれません。

やったこと

やったことを順序だてて解説します。説明の都合で実際の手順と異なる場合があります。

作業当時の環境は以下でした

  • Unity: 2022.3.17f1
  • UniRx: 7.1.0
  • R3: 1.1.7
  • MessagePiep: 1.7.4

1. R3のインストール

こちらの記事を参考にインストールします。 zenn.dev

そしてインストールが自分的に一番の落とし穴だったのですが、ちゃんと記事の通りにインストールしましょう。 上記記事には

以下のスクショのようにUnity NuGet内の R3 (NuGet) とOpenUPM内の R3 からインストールします。

とあります。自分はUnity NuGetからだけR3をインストールして満足しており、後の作業で using R3.Triggers; ができずに30分ぐらい悩んでました。 R3.Triggers はUnity依存の実装であり、OpenUPMの方のR3から取得する必要があるようです。また、プロジェクトでasmdefを切っている場合は R3.Unity への参照を追加する必要があります。 なお、R3本体の方はdllなのでasmdefに参照を追加する必要はなく、勝手に参照できるようになります。

また、このときMessagePipeをUnity NuGetの方に乗り換えようとしたのですが、VContainerとの参照でコンパイルエラーが出たので止めました。 (GitHub経由のインストールのままとしました)

2. UniRxをR3に置換する

"using UniRx" を一気に "using R3" に置換してしまいます。当然コンパイルエラーが出ますがこれを一つずつ直していくことになります。

3. UniRxを消す

UniRxをpackage manager からremoveします。UniRxとR3は同名の拡張メソッドを多数持つので早めにかぶりを無くしてしまった方が良いでしょう。 プロジェクトのasmdefに追加したUniRxの参照もMissingになるので削除しておきましょう。 同時に代わりではないですがasmdefの参照に R3.Unity を追加しておきましょう。 これでいくらかコンパイルエラーが減るかと思います。

4. IReadOnlyReactiveProperty を変える

筆者は UniRx.IReadOnlyReactiveProperty を公開用に多用してました。R3ではinterfaceは消えてしまったので R3.ReadOnlyReactiveProperty を使います。 これも "IReadOnlyReactiveProperty" を "ReadOnlyReactiveProperty" に置換してしまっていいでしょう。

ただ、UniRx.IReadOnlyReactiveProperty.ValueR3.ReadOnlyReactiveProperty.CurrentValue となります。

5. IObservable を変える

System.IObservable のinterfaceを使うことが多かったと思いますが、R3ではクラスの Observable を使います。 変数名が置換対象ならないよう、型名のみ置換されるように "IObservable<" を "Observable<" に置換しました。

6. TakeUntillDestroy を消す

オブジェクトの破棄時に購読を止める際、有名なのは .AddTo(this) ですが、プロジェクトによっては .TakeUntillDestroy(this) を推奨している場合もあると思います。 R3では TakeUntillDestroy はないようなので、AddToRegisterTo に移行します。 筆者は RegisterTo を使いましたが、渡すのが thisgameObject でなく destroyCancellationToken になります。

7. ToUniTaskFirstAsync に変える

筆者のプロジェクトでは IObservable<T> の最初の値を待ち受けるために

await observable.ToUniTask(true, cancellationToken: ct);

のように書いていました。同様の処理は FirstAsync で書けるようです

await observable.FirstAsync(cancellationToken: ct).AsUniTask();

8. EveryFixedUpdate を変える

Observable.EveryFixedUpdate() は直接呼べなくなっています。Observable.EveryUpdateUnityFrameProvider.FixedUpdate を渡してやることで FixedUpdate を購読できます。また、フレーム数をとるには Index() を購読する必要があるようでした。

-Observable.EveryFixedUpdate()
+Observable.EveryUpdate(UnityFrameProvider.FixedUpdate)
+  .Index()
   .Subscribe(i => {

9. ObserveEveryValueChanged を変える

文章では説明しずらいですが ObserveEveryValueChanged の書き方が変わりました。 これはとり先生の説明を見てもらえればわかるかと思います

qiita.com

10. MessagePipe.ISubscriber<T> の変換

先ほどのとり先生の記事には

(この記事の執筆時点では MessagePipe -> R3.Observable の変換メソッドは存在していませんでした。将来的に実装される可能性があります)

とありましたが、下記のように .AsObservable()ToObservable() を挟むと一応 MessagePipe.ISubscriber に関しては R3.Observable に変換でき、Select などを挟むことが出来るようになりました。

using MessagePipe;
using R3;
using UnityEngine;
using VContainer;

public class SubscriberToR3 : MonoBehaviour
{
    [Inject] ISubscriber<int> _subscriber = default;

    void Start()
    {
        _subscriber
            .AsObservable()
            .ToObservable()
            .Select(x => x * 2)
            .Subscribe(x => Debug.Log(x))
            .RegisterTo(destroyCancellationToken);
    }
}

あとやること

自分のプロジェクトでは上記でコンパイルエラーはなくなりました。

ただ、Prefabやシーンのオブジェクトに UniRx.ObservableEventTrigger をアタッチしていた場合は先のUniRxの削除により Missing になっているはずなので、R3.ObservableEventTrigger に差し替えてやりましょう。

後は折角R3にアップデートしたので新しい機能を使いましょう。とりあえず SubscribeAwait を積極的に使いたいと思います

-.Subscribe(_ => UniTask.Void(async () =>
+.SubscribeAwait(async (_, ct) =>

KenzenViewerを作ったときに考えていたこと

この度、VR(Windows/MetaQuest)で画像を見られるアプリをリリースしました。

naninunenoy.booth.pm

経緯

元々画像ファイルの収集癖があり、好きなアニメのDVDのジャケット画像を集めて保存したりしてました。 PCに保存したから後で何かに使うわけでもないのに保存だけしてました。 たまに眺めるとかはしたかもしれません。

「好みの画像だったから保存した」みたいなミーム?があるので他にも同じ人がいるんだろうと思います。

dic.nicovideo.jp

ただ、画像を見る際PCやスマホのディスプレイ以上に大きく表示はできません。 画像を大きく表示したくても当然ディスプレイ外が描画されるわけもありません。

ある日、OculusQuest2を買って遊んでいるとOculusLinkで簡単にデスクトップをミラーリングできることを知りました。 そしてそのミラーリングしたディスプレイは自由に移動・拡縮でき好きな大きさで画像を見られることが出来て感動しました。

そしてUnityの画面を割と簡単にVRビューで表示できることを知り自分用に画像を見られるアプリを作ってみるかというアイデアが生まれました。

Windowsのデスクトップがミラーリングできるのでブラウザや標準のフォトアプリで同様のこともできます。最初はそれで満足してましたが、ミラーリングは自分の用途ではTooMuchに感じましたし、フォトアプリもページ送りの挙動や拡大の操作などは当然VRのコントローラは想定してないので使いづらい面も感じました。

仕事ではUnity使った開発をしていますし、個人開発というものをオレもやってみるかーと開発を始めました。

そしてリリース

開発開始から1~2年1ありましたが、ひとまず他人にも使えるレベルになったと判断したのでリリースとしました。 開発でこだわった点は以下です

  • 画像の読み込みと表示を可能な限り早くする
  • 左右両手のコントローラで同じ操作をできるようにする

です。

画像の読み込みと表示を可能な限り早くする

別スレッドのファイル読み込みのよりメインスレッド上で UnityWebRequest 経由で読む方がなんだかんだ早い2みたいな発見を経ましたが、OSSで用途に合ったものがあったのでそれを使っています。

github.com

また、一度読み込んだ画像の縮小画像をwebpに変換して保存し、次回の表示時に利用するなどしています。大量の画像を表示することを想定したうえでの仕様です。3

webpはpngよりファイルサイズが小さいくらいの認識でしたが、以下の記事でUnityでも使えることを知り採用しました。

note.com

また、2度ほど抜本的な作り直しをしましたが上記の詳細も併せて別の機会にまとめようと思います。

左右両手のコントローラで同じ操作をできるようにする

ナニとは言いませんが、片手だけで操作したい場面があるのです。

アプデの構想

MetaQuest以外でも使えるように

QuestLink(OculusLink)頼りなのでMetaQuestでしか動きません。SteamVRを経由すれば他のVR機器でも動くらしいと教えてもらったので試したいと思っています。 ただ、現状ユーザが自分だけなので優先度は低めかも。

動画再生

Unityの VideoPlayer がPC上のローカルの動画を再生するのに使えそうなので動画の再生の対応はしたいです。しかし時間送りとかUIが面倒そうなのでひとまずは再生と停止だけかな。

音声再生

音声もUnityの AudioClipAudioSource で行けそうですが、現状のアプリの都合上画像を表示しながらの音声再生はやっかいなので後回しになりそう。

複数画像表示

ほんとは画像を複数枚表示して動かしたりしたかったのですが、これをやろうとするといつまで経ってもリリースできないと思ったので、一枚表示の時点でリリースしました。

Apple Vision Pro の Safari では標準機能のようです。まさにこんなことがしたいですねー。自分としてもここまで出来るようになれば完成といっていいかなと思っています。

今後

アプデだったり、人目に付くように記事を書いたり、LTなどの機会を作っていきたいなと思っています。


  1. Gitの最初のコミットが2022年の6月でした
  2. 結局 Texture2D の生成はメインスレッド実行でボトルネックになるので
  3. 自分が収集した画像は1万とか超えているのでそれらを表示することを視野に入れました