本日はQuest3の小ネタ枠です。
LCC Unity SDKを使ってQuest3でLCCモデルを表示する方法です。
Documentsフォルダにアクセスする
QuestのDocumentsフォルダにアクセスするには以下の作業が必要になります。
- AndroidManifestにMANAGE_EXTERNAL_STORAGE権限を付与する
- アクセス権限の許可を取得する
- /sdcard/Documents/フォルダにアクセスする
AndroidManifest.xmlへの権限設定
初めにAndroidManifestの設定を有効化します。
[Edit -> Project Settings..]を開き、[Player]タブの[Publishing Settings]パネルを開きます。

[Build -> Custom Main Manifest]のチェックを入れます。
これで編集可能なAndroidManifestが生成されます。

プロジェクトのアセットフォルダにあるAndroidManifest.xmlを開きます。
Assets/Plugins/Android/AndroidManifest.xml

AndroidManifest.xmlに以下の機能権限を追加します。
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

フォルダアクセスの許可を求める
Documentsフォルダにアクセスするにはアプリ内でフォルダアクセスの許可を得る必要があります。
以下のコードを実行するとアプリ内でアクセス許可のダイアログが表示されます。
<summary>
</summary>
private void AskForManageStoragePermission()
{
try
{
using var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
using AndroidJavaObject currentActivityObject = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
string packageName = currentActivityObject.Call<string>("getPackageName");
using var uriClass = new AndroidJavaClass("android.net.Uri");
using AndroidJavaObject uriObject =
uriClass.CallStatic<AndroidJavaObject>("fromParts", "package", packageName, null);
using var intentObject = new AndroidJavaObject("android.content.Intent",
"android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION", uriObject);
intentObject.Call<AndroidJavaObject>("addCategory", "android.intent.category.DEFAULT");
currentActivityObject.Call("startActivity", intentObject);
}
catch (AndroidJavaException e)
{
m_FolderPermissionOverride = true;
Debug.LogError("Java Exception caught and ignored: " + e.Message);
Debug.LogError("Assuming this means we don't need android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION (e.g., Android SDK < 30)");
}
}
フォルダアクセスの許可が得られれば以下のフォルダパスでDocumentsフォルダにアクセスできます。
/sdcard/Documents/
前回記事で作成したLCCRendererを以下の通りドキュメントフォルダを参照する形に修正しました。
・LCCRenderer.cs
using UnityEngine;
using LCCCore;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
public class LCCRenderer : MonoBehaviour
{
public LCCManager m_manager;
private LCCCore.Renderer m_renderer;
async void Start()
{
string documentFolderPath = string.Empty;
#if UNITY_ANDROID && !UNITY_EDITOR
await GetStoragePermissionAsync();
documentFolderPath = GetDocumentFolderPathPerAndroid29();
#else
documentFolderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
#endif
string searchPath = System.IO.Path.Combine(documentFolderPath, "LCC");
string[] files = Directory.GetFiles(searchPath, "*.lcc", SearchOption.TopDirectoryOnly);
if (files.Length == 0)
{
Debug.LogError($"[LCCRenderer] No .lcc files found in: {searchPath}");
return;
}
string path = files[0];
Debug.Log($"[LCCRenderer] Loading file: {Path.GetFileName(path)}");
m_renderer = m_manager.GetRender(this.transform);
m_renderer.Load(path, PlatformType.Quest, onLoadCallback);
}
private void onLoadCallback()
{
Debug.Log("data loaded");
}
#if UNITY_ANDROID
<summary>
</summary>
<returns></returns>
private string GetDocumentFolderPathPerAndroid29()
{
string documentPath = "/sdcard/Documents/";
return documentPath;
}
<summary>
</summary>
<returns></returns>
private async Task<bool> GetStoragePermissionAsync()
{
if (!UserHasManageExternalStoragePermission())
{
AskForManageStoragePermission();
while (!UserHasManageExternalStoragePermission())
{
await Task.Delay(100);
}
}
return true;
}
private bool m_FolderPermissionOverride = false;
<summary>
</summary>
private bool UserHasManageExternalStoragePermission()
{
bool isExternalStorageManager = false;
try
{
AndroidJavaClass environmentClass = new AndroidJavaClass("android.os.Environment");
isExternalStorageManager = environmentClass.CallStatic<bool>("isExternalStorageManager");
}
catch (AndroidJavaException e)
{
Debug.LogError("Java Exception caught and ignored: " + e.Message);
Debug.LogError("Assuming this means this device doesn't support isExternalStorageManager.");
}
return m_FolderPermissionOverride || isExternalStorageManager;
}
<summary>
</summary>
private void AskForManageStoragePermission()
{
try
{
using var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
using AndroidJavaObject currentActivityObject = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
string packageName = currentActivityObject.Call<string>("getPackageName");
using var uriClass = new AndroidJavaClass("android.net.Uri");
using AndroidJavaObject uriObject =
uriClass.CallStatic<AndroidJavaObject>("fromParts", "package", packageName, null);
using var intentObject = new AndroidJavaObject("android.content.Intent",
"android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION", uriObject);
intentObject.Call<AndroidJavaObject>("addCategory", "android.intent.category.DEFAULT");
currentActivityObject.Call("startActivity", intentObject);
}
catch (AndroidJavaException e)
{
m_FolderPermissionOverride = true;
Debug.LogError("Java Exception caught and ignored: " + e.Message);
Debug.LogError("Assuming this means we don't need android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION (e.g., Android SDK < 30)");
}
}
#endif
}
これでプロジェクトの変更は完了です。
Quest3へのデプロイとサンプルモデルのダウンロード
Quest3へのデプロイとサンプルモデルのダウンロード手順については以下の記事を参照ください。
bluebirdofoz.hatenablog.com
ドキュメントフォルダを参照する場合のファイルの配置箇所について以下に記述します。
Editor上での動作確認
一時フォルダからドキュメントフォルダにLCCフォルダを移動します。
C:\Users\(ユーザ名)\Documents\LCC

シーンを再生してLCCモデルが表示されることを確認します。

Quest3上の動作確認
Quest3のドキュメントフォルダにLCCファイルをアップロードします。
Quest3をPCにUSB接続してストレージを開き、以下のフォルダにLCCファイル内の全ファイルをコピーします。
/Documents/LCC

アプリを起動すると、以下の通りLCCモデルが描画されました。

ただし筆者環境では画面の右半分のみモデルの解像度が下がっているように表示される事象を確認しました。
本事象については現状、原因と解決策は不明です。