はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】任意のプラットフォームでコンソール・簡易プロファイラー利用・パラメータ調整ができる「SRDebugger」の利用方法

はじめに

今回はSRDebuggerというビルドできる任意のプラットフォームにてコンソールと簡易プロファイラー、パラメータ調整ができるアセットを紹介したいと思います。

f:id:hanaaaaaachiru:20220304212934g:plain
利用している様子

一応iOSAndroidなどの他のデバイス上で動作しているものを、Unityエディタ上でプロファイリングできる機能がUnityには標準で実装されています。

リモートプロファイリング
他のデバイスや、別のコンピューター上の Unity プレイヤーで実行しているゲームのプロファイリングを行うには、Unity エディターを他のデバイスまたはコンピューターに接続します。 ...

Profiler ウィンドウ - Unity マニュアル
アプリケーションのプロファイル - Unity マニュアル

ただリモートプロファイリングはUnityエディタが起動していなければならないのに対し、SR Debuggerはアプリケーション上で動作します。


またオプションタブにてパラメーターを調整できるようにしたり、メソッドを実行できたりするボタンを追加したりできる拡張を行うことによって、よりデバッグをしやすくなるはずです。

インストール

AssetStoreで購入した後、PackageManagerからSRDebuggerをインストールします。

f:id:hanaaaaaachiru:20220228151608p:plain
SRDebuggerをインストールする

デバッグパネルを表示する

SR Debuggerはインストールするだけで全てのシーンにて利用可能になります。

デフォルトでは左上を三回クリックすることで立ち上がります。

f:id:hanaaaaaachiru:20220304213606g:plain
起動

デフォルトのショートカット

キー入力 動作
Ctrl-Shift-F1 Systemタブを開く
Ctrl-Shift-F2 Consoleタブを開く
Ctrl-Shift-F3 Optionsタブを開く
Ctrl-Shift-F3 Profilerタブを開く
Escape デバッグパネルを閉じる
f:id:hanaaaaaachiru:20220304215220p:plain
Window -> SR Debugger -> Setting Windowから変更できる

起動に関する設定

メニューバーよりWindow -> SR Debugger -> Setting Windowを選択し、Generalタブよりパネルを起動する(した)際の設定ができます。

f:id:hanaaaaaachiru:20220305000644p:plain
SRDebugger SettingのGeneralタブ
名前 意味
Loading ロードするタイミング。 Disable: SRDebug.Init()をコード上で呼ぶまで、SRDebuggerのロードをしない,Automatic: ゲーム起動時に自動でロード
Trigger Mode パネルを表示する環境。 Enableed: 常にパネルを起動するトリガー(例. 3回タップ)が有効, Mobile Only: モバイルプラットフォームのみトリガーが有効, Off: 常にトリガーが無効
Trigger Behaviour パネルを表示する手法。 Triple-Tap: 三回タップ, Tap-and-Hold: 長押し, Double-Tap: 2回タップ
Default Tab パネルを表示したときに最初に表示するタブ。
Require Entry Code パネルを表示するときにパスコードを要求するか。

Require Entry Codeを有効にすると、スマホのロック画面みたいなのが出てきます。

f:id:hanaaaaaachiru:20220305001925p:plain
Require Entry Codeを有効にする

コードからデバッグパネルを開く

SR Debuggerは3回タップのようなTriggerを利用する以外に、コードからもパネルを開くことができます。

// デバッグパネルのConsoleタブを開く
SRDebug.Instance.ShowDebugPanel(DefaultTabs.Console);

// デバッグパネルが表示されていたら閉じる
if(SRDebug.Instance.IsDebugPanelVisible)
    SRDebug.Instance.HideDebugPanel();

基本機能

SR Debuggerで利用できる機能は以下になります。

System Infomation

動作しているデバイスの情報を表示します。

f:id:hanaaaaaachiru:20220305013403p:plainf:id:hanaaaaaachiru:20220305013407p:plainf:id:hanaaaaaachiru:20220305013411p:plain
System Infomation

Options

デバイス上でパラメータの変更・メソッドの実行をすることができるUIを表示することができます。
利用の詳細は後述。

f:id:hanaaaaaachiru:20220305162450p:plain
Optionsタブ

Console

Debug.Log, Debug.LogWarning, Debug.LogErrorで出力されるログを表示します。

f:id:hanaaaaaachiru:20220305004613p:plain
Consoleタブ

しっかりとスタックトレースも表示されていますね。

Profiler

各フレームでどれくらい処理時間がかかったかを表示します。ただUnityEditorと比較してかなり雑で、CPU・GPUのどちらがボトルネックになっているか程度の利用方法がメインになると思います。

また割り当てられたメモリと利用されているメモリを確認することができます。

f:id:hanaaaaaachiru:20220305005319p:plain
プロファイラータブ

Bug Reporter

ユーザーからのバグレポートを指定したメールアドレスに転送してくれるサービスです。

f:id:hanaaaaaachiru:20220305012101p:plain
Bug Reporter

またユーザーがデバッグパネルへアクセスしなくても、APIを使用してバグレポート送信することが可能になります。

// BagReportの送信画面を開く
SRDebug.Instance.ShowBugReportSheet(); 

// 成功したときのコールバックをつける
SRDebug.Instance.ShowBugReportSheet(success => Debug.Log("Bug Report Complete, Success: " + success));

// Descriptoinにあらかじめ文字を打ち込んでおく
SRDebug.Instance.ShowBugReportSheet(descriptionContent: "This will be placed in the description field.");
セットアップ

メニューバーからWindow -> SR Debugger -> Settings Windowを選択し、Bug Reportタブを開きます。

f:id:hanaaaaaachiru:20220305011329p:plain
API Keyを取得する

※申し訳ないのですが、組織にて購入したアセットを割り当てして利用させていただいているので、注文番号が分からず検証できていません。
詳細が分かり次第追記できたらと思います。

Optionsタブの拡張

Optionの追加

optionを追加するにはSROptionspartial classを定義します。

public partial class SROptions
{

}

ファイルの命名に関しては、SROptions.Gameplay.cs, SROptions.Debug.cs, SROptions.Cheats.csのようにカテゴリごとにファイルを作成すると良いでしょう。

また後述の設計の箇所で記述しますが、Assembly-CSharpの中にファイルを入れなければならないことに注意してください。

プロパティを追加
public partial class SROptions
{
    // プロパティ
    private int _value = 0;

    [Category("My Category")]
    public int Value
    {
        get => _value;
        set
        {
            _value = value;
            OnPropertyChanged(nameof(Value));       // プロパティの更新をする(デバッグパネルが更新されたり、SROptions.PropertyChangedが発火されたり)
        }
    }

    // readonlyなプロパティ
    [NumberRange(0, 10)]
    public float Value2 { get; }
}
// 利用する側
SROptions.Current.Value = 10;
var a = SROptions.Current.Value;
f:id:hanaaaaaachiru:20220305194856p:plain
表示される様子

サポートしている型はBoolean, Enum, int, uint, short, ushort, byte, sbyte, float, double, stringです。

メソッドを追加
public partial class SROptions
{
    // メソッド
    // 返り値はvoid,引数はなしでないとオプションタブに表示されない
    public void Hoge()
    {
        Debug.Log("Click Button");
    }
}
f:id:hanaaaaaachiru:20220305195403p:plain
表示する様子
Attributeの一覧
Attribute 意味
NumberRange 指定された範囲のみ値を制限
Increment オプションタブでの,一度での数値の変更量を指定
DisplayName オプションタブで使用する名前を設定
Sort オプションの表示順を設定
Category オプションタブでのカテゴリーを設定

Dynamic Options

動的にオプションを追加します。

// Optionsタブにプロパティ追加
var propertyOptionDefinition = OptionDefinition.Create(name: "Name", getter: () => _value, setter: value => _value = value,
    category: "Category", sortPriority: 0);
SRDebug.Instance.AddOption(propertyOptionDefinition);
        
// Optionsタブにメソッド追加
var methodOptionDefinition = OptionDefinition.FromMethod(name: "Name", callback: Hoge, category: "Category",  sortPriority: 0);
SRDebug.Instance.AddOption(methodOptionDefinition);
        
// Attributeをつけたい場合は、PropertyReferenceを自分で生成する必要がある
var myAttributes = new Attribute[]
{
    new NumberRangeAttribute(0, 10),                // 0 ~ 10の範囲に制限する
    new IncrementAttribute(1),                      // ボタンを押したときに1ずつインクリメントするようにする
    new SROptions.DisplayNameAttribute("MyName"),   // Optionsタブで表示する名前
    new SortAttribute(0),                           // Optionsタブでの表示順
    new CategoryAttribute("MyCategory"),            // Optionsタブでどのカテゴリーに属させるか
};
var propertyReference = PropertyReference.FromLambda(getter: () => _value, setter: value => _value = value,
    attributes: myAttributes);
SRDebug.Instance.AddOption(new OptionDefinition(name: "MyName", category: "MyCategory", sortPriority: 0, propertyReference));
        
        
// 削除
SRDebug.Instance.RemoveOption(propertyOptionDefinition);
SRDebug.Instance.RemoveOption(methodOptionDefinition);

DynamicOptionContainer

DynamicOptionContainerを利用して一度に複数のオプションを追加することができます。

private int _value;
private int _value2;
private int _value3;
    
private void Start()
{
    var container = new DynamicOptionContainer();
    SRDebug.Instance.AddOptionContainer(container);
        
    // _valueをValueという名前でreadonlyに追加
    container.AddOption(OptionDefinition.Create("Value", () => _value));
    // _value2をValue2という名前で追加
    container.AddOption(OptionDefinition.Create("Value2", () => _value2, value => _value2 = value));

    var optionDefinition = OptionDefinition.Create("Value3", () => _value3);
    container.AddOption(optionDefinition);
        
    // オプションの削除
    container.RemoveOption(optionDefinition);
        
    // オプションのコンテナの削除
    SRDebug.Instance.RemoveOptionContainer(container);
}

設計について

partial classの定義する場所

partial class同一アセンブリに定義しなければなりません
クラスの機能拡張 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

SROptionsがデフォルトで入っているアセンブリはAssembly-CSharp(つまりAssembly Definitionが定義されていない)になります。

ですので新しく追加するpartial classAssembly-CSharpに入れる手法がまず考えられます。

f:id:hanaaaaaachiru:20220305183956p:plain
依存関係

Assembly-CSharpに含まれるファイルは、他アセンブリを全て参照可能なので、変数・メソッドにアクセス・変更を加えられます。

おそらくSR Debuggerの作者もそのためにAssembly Definitionを定義していない(SR Debuggerの本体にはアセンブリが定義されている)と思うので、こちらが正しい使い方だと思います。

SROptionsにAssembly Definitionを定義する

(私はあまり推奨しませんが)SROptionsが利用される側にしたい場合は以下のような関係にすることも考えられます。

f:id:hanaaaaaachiru:20220305184829p:plain
Assembly Definitionを定義する

コードの一例はこんな感じ。

// 利用者側
public class SomeClient : MonoBehaviour
{
    private int _value;
    private void Start()
    {
        // SROptionsのValueが変更されたら、値を更新する
        SROptions.Current.PropertyChanged += (sender, propertyName) =>
        {
            if (propertyName == nameof(SROptions.Current.Value))
            {
                _value = SROptions.Current.Value;
            }
        };
        
        // ボタンが押された時のメソッドを登録する
        SROptions.Current.OnClickButton += () => Debug.Log("Click Button");
    }
}

// SROptions(利用される側)
public partial class SROptions
{
    // プロパティ
    private int _value;
    public int Value
    {
        get => _value;
        set
        {
            _value = value;
            OnPropertyChanged(nameof(Value));
        }
    }

    // メソッド
    public event Action OnClickButton;
    
    public void ClickButton()
    {
        OnClickButton?.Invoke();
    }
}

まあ書いていてあれですが、あんまり現実的ではないのかなぁと思います。特に列挙型を利用したい場合はSROptions側か共通に参照しているアセンブリに定義しないといけないので面倒です。

なんならDynamic Optionをメインに使って記述する方が良いかもしれません。