【UE5】Meta Quest 3でコンソールコマンドを実行する方法とGUIツール作成
プログラマーの尾関です。
今回はMeta Quest3 の実機でUnreal Engineアプリのデバッグやパフォーマンス計測を行う際にコンソールコマンドを使う方法について調べたので、それについて書きます。
実機転送時のコンソールコマンドの実行
最近まで知らなかったのですが、APKファイルとして Meta Quest3 に転送した実行ファイルに対して、ADB (Android Debug Bridge) 経由でコンソールコマンドを実行することが可能です。
Android Studioがインストールされた環境であればコマンドラインから、コンソールコマンドが使用可能です。
> adb shell "am broadcast -a android.intent.action.RUN -e cmd 'stat fps'"
上記のコマンドは 'stat fps' を実行して FPS を表示することができます。「stat fps」だけでなく、他にも「stat SceneRendering」でレンダリング統計情報を表示したり、「show collision」でコリジョンを表示することもできます。
ただ一部のコマンドは利用できないので、そのあたりは実際に実行してみて試す必要があります。(例えば「stat gpu」は動かないような印象です)
PythonでGUIツールを自作
毎回コマンドを手打ちするのは手間なので、PythonとTkinterでGUIツールを作成しました。
import tkinter as tk
from tkinter import COMMAND, ttk, messagebox
import subprocess
# 送信するコマンド
COMMANDS = {
# stat系.
"stat fps": "FPSを画面上に表示",
"stat unit": "Frame/Game/Draw/GPU、各スレッドの1フレームあたりの所要時間を表示",
# "stat gpu": "GPUの使用率を表示", # 実機では使えない様子…
"stat Engine": "レンダリング統計情報 (全体)",
"stat SceneRendering": "シーンレンダリングの詳細な統計を表示",
# show系.
"show collision": "コリジョン(当たり判定)の表示・非表示を切り替え",
"show particles": "パーティクルの表示・非表示を切り替え",
"show bounds": "オブジェクトのバウンディングボックス表示切替",
# レンダリング系. (変化がなかったので使えないのかもしれない)
#"r.ScreenPercentage": "描画解像度のスケーリング。(例: r.ScreenPercentage 100)",
#"r.DynamicRes.OperationMode": "動的解像度の操作モードを設定。(例: r.DynamicRes.OperationMode 1)",
#"r.ViewDistanceScale": "視界距離のスケールを設定。(例: r.ViewDistanceScale 1.0)",
#"r.FinishCurrentFrame": "レンダリングの同期設定 (例: r.FinishCurrentFrame 1)",
# ViewMode系. (実機ではほとんど使えない様子)
#"ViewMode lit": "ViewModeをデフォルトに戻します",
#"ViewMode wireframe": "ワイヤーフレーム表示",
#"ViewMode ShaderComplexity": "Viewport上でShader負荷がかかっているところを赤く表示する",
#"ViewMode collisionpawn": "プレイヤーコリジョンの可視化",
#"ViewMode scenedepth": "シーンの深度情報をグラデーションで表示",
# VR固有.
"vr.TrackingOrigin": "トラッキング原点を床または目に設定する",
"vr.HeadTracking.Reset": "VRヘッドトラッキングをリセット",
# レベル開く系.
"open Lv_Boot": "ブートレベルに遷移",
"open Lv_Main": "メインレベルを開く",
# サウンド系.
"au.debug": "サウンドデバッグ情報を表示",
# その他.
"open": "レベルを開く (例: open Lv_Boot)",
"pause": "ゲームを一時停止",
"resume": "ゲームを再開",
"quit": "ゲームを終了",
}
def send_command():
cmd = command_var.get() if dropdown_var.get() == "ドロップダウン" else entry.get()
if not cmd.strip():
messagebox.showwarning("警告", "コマンドを入力してください。")
return
param = param_entry.get().strip()
adb_cmd = f'adb shell "am broadcast -a android.intent.action.RUN -e cmd \'{cmd} {param}\'"'
try:
result = subprocess.run(adb_cmd, shell=True, capture_output=True, text=True, timeout=10)
output_text.delete(1.0, tk.END)
output_text.insert(tk.END, f"コマンド: {adb_cmd}\n")
output_text.insert(tk.END, result.stdout if result.stdout else result.stderr)
except Exception as e:
output_text.delete(1.0, tk.END)
output_text.insert(tk.END, f"コマンド: {adb_cmd}\n")
output_text.insert(tk.END, f"エラー: {e}")
def switch_input(*args):
if dropdown_var.get() == "ドロップダウン":
dropdown["state"] = "readonly"
entry["state"] = "disabled"
else:
dropdown["state"] = "disabled"
entry["state"] = "normal"
def update_description(*args):
if dropdown_var.get() == "ドロップダウン":
desc = COMMANDS.get(command_var.get(), "")
else:
desc = ""
description_label.config(text=desc)
root = tk.Tk()
root.title("ADBコンソールコマンド送信ツール")
root.geometry("600x400")
dropdown_var = tk.StringVar(value="ドロップダウン")
command_var = tk.StringVar(value=list(COMMANDS.keys())[0] if COMMANDS else "")
# 入力方法選択
input_frame = tk.Frame(root)
input_frame.pack(pady=10)
tk.Label(input_frame, text="入力方法:").pack(side=tk.LEFT)
input_method = ttk.Combobox(input_frame, textvariable=dropdown_var, values=["ドロップダウン", "テキストボックス"], state="readonly", width=15)
input_method.pack(side=tk.LEFT, padx=5)
input_method.bind("<<ComboboxSelected>>", switch_input)
# コマンド入力欄
command_frame = tk.Frame(root)
command_frame.pack(pady=10, fill=tk.X, padx=10)
dropdown = ttk.Combobox(command_frame, textvariable=command_var, values=list(COMMANDS.keys()), state="readonly", width=60)
dropdown.pack(side=tk.LEFT, padx=5)
entry = tk.Entry(command_frame, width=63, state="disabled")
entry.pack(side=tk.LEFT, padx=5)
# 説明ラベル(コマンド入力欄の下に表示)
description_label = tk.Label(root, text="")
description_label.pack(padx=10, anchor="w")
# 追加パラメータ(小さめサイズ)
param_frame = tk.Frame(root)
param_frame.pack(pady=2, fill=tk.X, padx=10)
tk.Label(param_frame, text="追加パラメータ:", font=("", 9)).pack(side=tk.LEFT)
param_entry = tk.Entry(param_frame, width=30, font=("", 9))
param_entry.pack(side=tk.LEFT, padx=3)
command_var.trace_add("write", update_description)
dropdown_var.trace_add("write", update_description)
# 送信ボタン
send_btn = tk.Button(root, text="送信", command=send_command, width=10)
send_btn.pack(pady=10)
# 出力欄
output_text = tk.Text(root, height=12, width=70)
output_text.pack(padx=10, pady=10)
switch_input()
root.mainloop()
このツールを使えば、よく使うコンソールコマンドをドロップダウンから選択するだけで、Questに直接送信できます。

カスタムコマンドやコマンドのパラメータの入力にも対応しています
おしまい
ADB経由でコンソールコマンドを実行できることを知ってからは、デバッグ効率が少し上がりました。
もっと早く知っておけば良かった…ということで今回ご紹介をさせていただきました。
以上、Unreal Engineでの Meta Quest での開発のお役に立てれば幸いです。