マルチプレイの確認に便利なUnity Multiplayer Play Modeパッケージ!にて、ソースコードを編集してもVirtual Playerに反映されないバグの対処法

Unity6から使えるMultiplayer Play Modeという公式パッケージがあります。

Editor上のプレイボタンを押すと最大3つのプレイヤーが自動で立ち上がり(Editor本体を入れて4つ)、
マルチプレイゲームのデバッグが簡単にできる!という非常に便利なパッケージです。
この時、Editor以外のプレイヤーのことをVirtual Playerと呼びます。


(公式ブログより引用)

今まではParrelSyncを使っていたのですが、
Editorを丸ごと複製するためアセットのリロード作業は個別に行わないといけなかったりと少し面倒でした。

Multiplayer Play Modeパッケージを使うと、プレイヤー部分だけが複製されるため非常に楽です。
コードを読む限りUnity MultiplayだとかUnity Gaming Serviceと呼ばれているものとの依存関係はなく
ただプレイヤー部分を複製するだけのようです。


これだけで終わればハッピーだったのですが、
1.3.0-pre.1で発生している、ソースコードを編集してもVirtual Playerに反映されないバグの対処法を紹介します。
致命的すぎて、このために導入を見送っていた人も少なくないはず。自分もその一人でした。

フォーラムには1年前から書き込みがありますが未だに治っていません。
どうやらRiderを使っていると発生するそうなのですが、
JetBrains側でも"Third Party Problem"ということでCloseされてしまっています。

以下のEditorScriptを配置しておくだけでオッケーです。
リフレクションで無理矢理呼び出してるのでバージョンアップで使えなくなる可能性は高いです。

ForceSyncVirtualPlayer.cs
// https://discussions.unity.com/t/multiplayer-playmode-does-not-update-assembly-properly/922976

using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;

internal class ForceSyncVirtualPlayer : AssetPostprocessor
{
    private const string SessionStateKey = "ForceSyncVirtualPlayer.CachedTotalCount";

    private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
    {
        var totalCount = importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length;

        if (!didDomainReload)
        {
            SessionState.SetInt(SessionStateKey, totalCount);
            return;
        }

        var cachedTotalCount = SessionState.GetInt(SessionStateKey, 0);
        if (cachedTotalCount == 0) return;

        _targetType ??= Type.GetType("Unity.Multiplayer.Playmode.VirtualProjects.Editor.AssetDatabaseCallbacks, Unity.Multiplayer.Playmode.VirtualProjects.Editor");
        if (_targetType == null)
        {
            Debug.LogError("Target type not found.");
            return;
        }

        _eventField ??= _targetType.GetField("OnPostprocessAllAssetsCallback", BindingFlags.Static | BindingFlags.NonPublic);
        if (_eventField == null)
        {
            Debug.LogError("Event field not found.");
            return;
        }

        _eventDelegate = (Delegate) _eventField.GetValue(null);
        if (_eventDelegate == null)
        {
            Debug.LogError("Event delegate not found.");
            return;
        }

        _eventDelegate.DynamicInvoke(true, cachedTotalCount);
        SessionState.EraseInt(SessionStateKey);
    }

    private static Type _targetType;
    private static FieldInfo _eventField;
    private static Delegate _eventDelegate;
}