Edited at

Oculus Quest Build 7.0アップデート後に自作アプリがおかしくなった時の対策


はじめに

先日Oculus Questがアップデートされて以下の記事のようにコントローラーのトラッキング改善やガーディアンの機能強化されました。

このアップデートで自作したVRアプリを実行するとコントローラーが片手しかトラッキングされなくなったりボタンのマッピングがおかしくなってしまうので対策します。

※追記

Oculus Integration v1.39.0のOvrAvatarの挙動も変わっているので対策します。

以下のシーンはOvrAvatarが使われていて未対策だとアプリがクラッシュするので対策必須です。

/Assets/Oculus/SampleFramework/Usage/AvatarGrab.scene


環境


  • Oculus Integration for Unity v1.38.0 / v1.39.0 / v1.40.0

  • Unreal Engine 4.22.3 / 4.23.0


Unity


コントローラーの対策

公式ドキュメントに設定手順が追加されていました。

v1.40.0からOVRManagerコンポーネントのTarget Devicesの先頭をQuestに変更するだけでよくなりました。

v1.39.0とv1.38.0はOVRManagerコンポーネントのTarget Devicesの先頭をQuestに変更した上でAndroidManifest.xmlにuses-featureを追加します。


公式ドキュメントに追加される前にFix for sideloaded/homebrew apps and games with the new firmware updateで対策を見付けた方がいてAndroidManifest.xml に以下のfeatureを追加すれば両手のコントローラーが正しくトラッキングされます。これはQuestアプリをOculus Storeに提出する時に必須の設定のようです。

<uses-feature android:name="android.hardware.vr.headtracking" android:required="true" android:version="1"/>

具体的に以下のいずれかの方法でAndroidManifest.xmlを修正します。


v1.40.0以降

Oculus Integration v1.40.0から少し手間が減ったので更新をおすすめします。

v1.38.0とv1.39.0のQuest対応が中途半端なのが混乱の元凶だったんだ…


  1. OVRManagerのTarget DevicesにQuestを追加します。Quest向けアプリなら既に設定済みだと思います。


  2. Unity2018.4以下でBuild SettingsのビルドシステムがInternalならGradleに変更します。Unity2019.1以上はビルドシステムの項目がなくなりGradleのみになったので特に設定するものはありません。


以上の設定で独自に/Assets/Plugins/Android/AndroidManifest.xmlがなくてもビルド時にQuestに必要なfeatureが自動で追加されます。


v1.39.0以前


Create store-compatible AndroidManifest.xml

Oculus Store提出用のAndroidManifest.xmlを作る方法です。


  1. v1.40.0以上に更新する方法と同様にOVRManagerのTarget DevicesにQuestを追加します。

  2. 次に/Assets/Plugins/Android/AndroidManifest.xmlを削除します。メニューのOculus > Tools > Remove AndroidManifest.xmlが簡単です。

  3. 最後にメニューのOculus > Tools > Create store-compatible AndroidManifest.xmlを実行します。これで上記のfeatureが含まれたAndroidManifest.xmlが生成されます。あらかじめOVRManagerのTarget DevicesにQuestを入れておかないと必要なfeatureが含まれないので注意してください。



ビルドして実行エラー対策


  • v1.39.0までのバージョンで生成したAndroidManifest.xmlで「ビルドして実行」を行うと以下のエラーが出ます。これはビルドとインストールまで完了しているのですがUnityから実行する時に必要なandroid.intent.category.LAUNCHERが定義されていないためです。

  • v1.40.0からandroid.intent.category.LAUNCHERが定義されたAndroidManifest.xmlが生成されるようになったのでビルドして実行エラーは出なくなりました。

そこで以下のようにandroid.intent.category.LAUNCHERを追加するとビルドして実行からアプリの実行までできるようになります。


AndroidManifest.xml

<intent-filter>

<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.INFO"/>
<category android:name="android.intent.category.LAUNCHER"/> <!-- この行を追加する -->
</intent-filter>


直接記述する

UnityプロジェクトでAndroid向けにビルドすると[Project Folder]/Temp/gradleOut/src/main/AndroidManifext.xmlが生成されるので[Project Folder]/Assets/Plugins/Android/AndroidManifest.xmlにコピーして他のuses-featureの近くに上記のfeatureを追記します。


コールバックで対策する

UnityはGradleプロジェクト生成後に呼ばれるコールバックがあるので以下のスクリプトでAndroidManifest.xmlに上記のfeatureを追加します。

このコールバックを利用すると次のような事もできます。


/Assets/Editor/ModifyUnityAndroidAppManifest.cs

#if UNITY_ANDROID

using System;
using System.IO;
using System.Text;
using System.Xml;
using UnityEditor;
using UnityEditor.Android;

public class ModifyUnityAndroidAppManifest : IPostGenerateGradleAndroidProject
{
public void OnPostGenerateGradleAndroidProject(string basePath)
{
var isVr = PlayerSettings.GetVirtualRealitySupported(BuildTargetGroup.Android);
var vrSdks = PlayerSettings.GetVirtualRealitySDKs(BuildTargetGroup.Android);
var hasOculus = Array.Exists(vrSdks, s => s == OVRManager.OCULUS_UNITY_NAME_STR);

if (isVr && hasOculus)
{
if (OVRDeviceSelector.isTargetDeviceQuest)
{
var androidManifest = new AndroidManifest(GetManifestPath(basePath));
androidManifest.EnableQuestApp();
androidManifest.Save();
}
}

}

public int callbackOrder => 10;

private string _manifestFilePath;

private string GetManifestPath(string basePath)
{
if (string.IsNullOrEmpty(_manifestFilePath))
{
var pathBuilder = new StringBuilder(basePath);
pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
_manifestFilePath = pathBuilder.ToString();
}

return _manifestFilePath;
}
}

internal class AndroidXmlDocument : XmlDocument
{
private string m_Path;
protected XmlNamespaceManager nsMgr;
public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
public readonly string ToolsXmlNamespace = "http://schemas.android.com/tools";

public AndroidXmlDocument(string path)
{
m_Path = path;
using (var reader = new XmlTextReader(m_Path))
{
reader.Read();
Load(reader);
}

nsMgr = new XmlNamespaceManager(NameTable);
nsMgr.AddNamespace("android", AndroidXmlNamespace);
}

public string Save()
{
return SaveAs(m_Path);
}

public string SaveAs(string path)
{
using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
Save(writer);
}

return path;
}
}

internal class AndroidManifest : AndroidXmlDocument
{
private readonly XmlElement ApplicationElement;

public AndroidManifest(string path) : base(path)
{
ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
}

private XmlAttribute CreateAndroidAttribute(string key, string value)
{
var attr = CreateAttribute("android", key, AndroidXmlNamespace);
attr.Value = value;
return attr;
}

private XmlAttribute CreateToolsAttribute(string key, string value)
{
var attr = CreateAttribute("tools", key, ToolsXmlNamespace);
attr.Value = value;
return attr;
}

public void EnableQuestApp()
{
const string headTrackingName = "android.hardware.vr.headtracking";
var headTrackingData = SelectNodes($"/manifest/uses-feature[@android:name='{headTrackingName}']", nsMgr);
if (headTrackingData != null && headTrackingData.Count > 0)
{
return;
}

var manifest = SelectSingleNode("/manifest");
if (manifest == null)
{
return;
}

// /manifest に <uses-feature android:name="android.hardware.vr.headtracking" android:required="true" android:version="1" tools:node="merge" /> を追加する
var newUsesFeature = CreateElement("uses-feature");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", headTrackingName));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("version", "1"));
newUsesFeature.Attributes.Append(CreateToolsAttribute("node", "merge"));

manifest.AppendChild(newUsesFeature);
}
}
#endif



OvrAvatarの対策


  • v1.38.0のOvrAvatarはARMv7向けライブラリしかありません。IL2CPP/ARM64でビルドおよび実行できるのですが手が表示されません。ARMv7でビルドしましょう。

  • v1.39.0以降のOvrAvatarはOculus > Avatars > Edit Settingsから生成したOvrAvatarSettings.assetにId設定が必須になりました。未設定だとクラッシュしてしまうので自分のIdやTesting Your Integrationに用意されているテスト用Idを設定します。
    また、ARM64向けライブラリが追加されたのですがIL2CPP/ARM64でビルドして実行するとスプラッシュ画面の後にOculus Questで「アップデート中」が出た場合の対処法と同じダイアログが出て結局実行できません…

  • v1.40.0でARM64でビルドしても動くようになりました。

またv1.39.0以降のOvrAvatarでController Shaderが追加されていますが/Assets/Oculus/SampleFramework/Usage/AvatarGrab.sceneでは設定されていないのが原因でアバターのオブジェクトを無限生成して落ちてしまいます。

/Assets/Oculus/Avatar/Content/Prefabs/LocalAvatar.prefabを参考にOvrAvatar/AvatarPBRV2Simpleを設定すればよいでしょう。


Oculus Integration更新時に注意すること

Oculus Integrationはバージョンアップでファイルのパスが変わることがあるので上書き更新するとおかしな状態になる事があります。そのため/Assets/Oculus//Assets/Oculus.metaを削除した後に新規でインポートすることをお勧めします。その際にUnityから/Assets/Oculus/を削除してもUnityがdllをロックしているのでいくつか削除されないファイルがあります。

なので以下の手順が確実です。


  1. 一旦、Unityを終了する

  2. エクスプローラから/Assets/Oculus//Assets/Oculus.metaを削除する

  3. Unityを開く。この時Oculus Integrationがないのでエラーが出るが一旦無視する

  4. Asset StoreからOculus Integrationをインポートする


UE4


コントローラーの対策

公式ドキュメントに設定手順が追加されていました。



UE4.23以降


  1. プロジェクト設定 > Oculus VRプラグインのLaunch Oculus Performance WindowsからTarget PlatformMobileに変更します。

  2. プロジェクト設定 > Android > Advanced APK PackagingのPackage for Oculus Mobile devicesOculus Questを追加します。

以上を設定することでEngine\Plugins\Runtime\Oculus\OculusVR\Source\OculusHMD\OculusMobile_APL.xmlによりAndroidManifest.xmlにfeatureが追加されます。


UE4.22以前

[Project Folder]/Build/Android/ManifestRequirementsAdditions.txtに上述したfeatureを記述するとビルド時にAndroidManifest.xmlに追加されます。このフォルダはプロジェクト設定のビルドフォルダを開くボタンから開くことができます。

以下のリンクではManifestRequirementsOverride.txtが使われていますがUE4独自の設定も上書きするので追加だけのManifestRequirementsAdditions.txtの方が安全です。


原因

今後Oculus QuestにはOculus Goをエミュレートする機能が追加される予定です。その機能が一部取り込まれて今回のfeatureがあるとQuest向け、ないとGo向けとして動作しているのではないかと思われます。


参考リンク