【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」は動かないような印象です)

nformation

■Android Studioがインストールされていない環境で ADB コマンドを動かす方法

Android Studio SDK がインストールされていなくても、SDKに含まれる「platform-tools」をプロジェクトに含め、adb.exeのパスを指定すると ADBコマンドが実行可能となります。

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 での開発のお役に立てれば幸いです。

\ 最新情報をチェック /