Apple Vision Proのアプリについて、結局公式ドキュメントを見て勉強中。思ったより充実してて、これでいいじゃんとなった。結局のところBlenderで作ったとてReality Composer Proをしっかり使いこなす必要がありそう。

今日はSwiftのエラーメッセージがわかりにくいなーというので消耗してしまった。普通に間違っていたのでコンパイル通らないのは当たり前なのだけど、それが型が曖昧で推論できないエラーに丸め込まれて、エラーが出る箇所が本来間違っている部分と違うところなので大変わかりづらかった。

というのと別に、前に触った全然関係ないだいぶつらいフレームワークというかプラットフォームのことを思い出し、ゾワッとし、それに比べればなんていい環境なんだと元気になった。 そういうのを経験しておくとこうなれるのでおすすめ。

Apple Vision Pro開発のメモ

知っておいた方が良さげなことをメモる。 出典: Designing RealityKit content with Reality Composer Pro

entity, scene, asset, componentの関係など

  • https://developer.apple.com/documentation/visionos/designing-realitykit-content-with-reality-composer-pro
    • scene は Reality Composer Proにおいてentityの階層のことで、.usdaファイルに保存される
    • Reality Composer Proのscene == RealiyKitのsceneであり、Reality Composer Proプロジェクトは複数のscenewを持つことができる。
    • 1 scene = 1 RealityViewとなる
    • 1 sceneは表現したいものを構成するデータを全部保持する。飛行機のモデルのためにシーンを作りたいならそのsceneに飛行機の3Dモデル、煙を表現するパーティクルエフェクト、エンジン音のための音声ファイルを含むsceneを作って、combinedなassetとしてアプリ内で使うことができる
      • スクショを見ると、実際にはその飛行機Entityの子にパーティクルエフェクトを持たせてそう
    • デフォルトで空っぽのScene.usdaがつくられる
    • assetをプロジェクトにインポートする→シーンに追加するの流れ
      • ** どのsceneにも追加してないassetもprojectに含めたままにできる、実行時に読み込むことができる **
        • 動的なゲームとかだとこの仕組みをよく使いそうに思われる
    • USDZモデルはsceneに入れたらentityやentityの階層構造になる
    • 画像ファイルはShader Graphにおけるテクスチャなどようにしか参照できず、entitiyにはならない。entityにならないファイルをsceneにドラッグして追加しようとしても何も起こらない
    • プロジェクト内にはフォルダを作って整理できる
    • アセットライブラリがあって+ボタンから追加できる
    • 読み込んだassetはread onlyになる
      • scene内でassetに変更を加えてもそのscene内でしか変更されない
    • scene内はすぐ散らかるので使わないentityはcontrol-clickしてDeactivateを選ぶと良い、deactivatedなentityはグレーアウトしてコンパイル後のバンドルにも含まれない
    • RealityKitはEntity-Component-System(ECS)というデザインパターンで作られてるのでentityにcomponentを追加して挙動を変更できる←Unityとかでもはや当たり前なやつかな
    • デフォルトのcomponentもあればカスタムなやつを書いて、Reality Composer Pro Swift packageのSources folderに追加することができる(Reality Composer Proで追加してXcodeで編集することもできる)
    • ECSの詳細←あとで読む
    • インスペクタからAdd ComponentするとSources folderにcomponent classが作られる。system class(←?)も作られる場合もある
    • sceneはentity(複数)の階層であり、自由に変更できるが、.usdzファイルとしてインポートした中身は、インポートしたassetがread onlyなファイル(上の方参照)なので変えられない。

マテリアル関係

  • USDZモデルをインポートすると、そのassetが含む全てのPBRマテリアルごとにマテリアルが作られる。マテリアルはヒエラルキービューに表示される。3D Viewには表示されない。
    • BlenderとかでPBRマテリアルでエクスポートすれば普通に表示されるしそれごとにマテリアル的な概念がReality Composer Proでもつくられるということかな
  • ヒエラルキービューでマテリアルを選ぶと色とか編集できる。新しいマテリアルを作ることもできる
  • PBRはリアルっぽく描画するにはいいけど、アニメ調などの表現やロジックを含むもの(アプリからの入力に応じて変化するとか)はできないのでカスタムマテリアルという種類のマテリアルをサポートしてて、要するにShader Graphが使える。UnityとかBlenderとかにあるやつ。Metalでシェーダー書かなくて良い(Metalで書くこともできるっぽい
  • Reality Composer ProのカスタムマテリアルはRealityKitではShaderGraphMaterialになり、CustomMaterialというのは別物らしいので注意。
  • Shader Graphで出力をCustom Surfaceに繋ぐと見た目を変えられる(フラグメントシェーダー的)、Custom Geometry Modifierに繋ぐと形を変えら得れる(バーテックスシェーダー的)
  • ノードの入力はPromoteすることでSwiftコードから実行時に値をセットできるようになる。定数ノードをcontrol-clickすることで出力をPromote/Demoteできる。
  • ShaderGraphMaterialインスタンスのsetParameter(name:value:)でセットすることができる。

RealityKitで読み込む

  • Reality Composer ProのsceneをRealityKitで読み込むのはApp Bundleに追加したUSDZファイルを表示するのとほぼ同じやり方でできる、ただReality Composer Pro package bundleを指定する必要がある。大抵RealityViewのイニシャライザでやる。
  • ${プロジェクト名}Bundleという名前のグローバル定数が定義されるのでそれを使う。デフォルトでReality Composer ProのプロジェクトはRealityKitContentと呼ばれるので、グローバル定数の名前はrealityKitContentBundleになる
RealityView { content in }
    if let scene = try? await Entity.load(named: "Biplane" in: realityKitContentBundle) {
        myDataModel.add(scene)
        content.add(scene)
    }
} update: { content in 
    // ...
}
  • ↑のコードでmyDataModel に追加してるのは、ReailtyView はiOSやmacOSのARViewと違ってすぐにscene contentへ参照を持ってない(you don't have ready access to the scene content) から追加しておくと便利よということらしい。まだよくわかってないけどなるほど
  • RealityKitがsceneの読み込みを終えると、scene変数にはsceneのroot entityが入るので、それをcontent変数に追加することで、RealityKitがユーザーにそれを表示する

その他

  • 複数sceneでassetを共有した時にはreferenceを作ることができる。referencesのセクションはinsupekutaで初期は非表示なので、Reality Composer Proの設定からHide Empty Referencesをuncheckすることで表示させられる。
  • 実機があればMacに繋いでsceneをプレビューできる

ここまで来たのでRealityKitについても

  • 出典: Understanding RealityKit’s modular architecture
  • いわゆるオブジェクト指向言語で書かれてるけど、いうてもcompositionベースではなくECSベースでEntityとComponentを使ってやるぞということが書いてある
  • EntityはEntityクラスを継承している。
  • Entityは3Dモデル、プリミティブな形状、ライト、見えないsound emitterやtrigger volumeもそう。
  • Entity自体はあまり多くのプロパティを持たず、Entityに追加されたComponentがたくさん持ってる
  • いろんなEntityがビルトインされて提供されている
    • ModelEntity→インポートした3Dモデル(.usdz or .reality file)など
    • これもEntityクラスにModelComponentを追加したのとおんなじとのこと
  • Component自体はロジックを持つが、そのプロパティや初期状態に対するロジックのみを含む。複数のentityの振る舞いやプロパティの状態をマイフレーム変更するなどはSystemの役割になる。一つのEntityが持てるComponentは、同じ種類のComponentであれば一つだけになる。
  • SystemはRealityKitが毎フレーム呼び出すようなEntityの振る舞いや特定の種類のEntityの城田を更新するコードを書くのに使う。SystemはComponentを通じて情報を保存したり、複数のComponentの組み合わせを参照しながらEntityを制御する。
  • 例えばゲームにおけるダメージシステムなどを実装するのに使う。毎フレーム、全てのEntityにダメージが与えられるか、あるいはもうライフがゼロで消えるかなどをモニタリングして、状態を更新する。
  • そのために各EntityにはそのEntityのライフを保持するHealthComponentみたいなのがあるだろうし、防御力を上げるArmmorComponentみたいなのがあれば、それも加味してダメージ計算をする。
  • 毎フレームそのDamageSystemは各EntityのHealthComponentに問い合わせて、もうライフがゼロになっていれば破壊のアニメーションを再生したりentityをsceneから消す。
  • Systemにそういうロジックを書くのは重複を防ぐためで、今までのOOPではそういったロジックはentityクラスに実装されていて、似たような計算が全てのentityインスタンスで行われてしまう。Systemであればどんだけentityがいても計算は一回で済む。
    • なるほどー、というか、普通のプログラミングに慣れたプログラマがゲームプログラミングするときに、参考書通りにPlayerクラスとEnemyクラスを作って、ダメージはどっちが処理を?みたいな気持ちになるあるあるみたいなのがこれで解消されてて良い。というか、それこそ自分はそういうモヤモヤを感じていたのだけど、それに解決策をフレームワークの方から提示してくれてて嬉しい。