Netcode - No more manual Network Prefab Registration @ Inspector

 

This post contains the implementation method to easily register NetworkPrefabs of Netcode at component view. It is not an official Unity guide, so caution is required when using it.

Place to register dynamic spawned prefabs

TL;DR

Netcode?

Netcode is the new server-client architecture to develop server/client logics like dedicated server in Unreal Engine. With Netcode, we can easily develop server-client games conceptually. In my case, I am struggling to use Netcode well. Read the documents if you want to know more details.

Install

I created an empty unity project NetcodeExample, and installed the following packages.

Install Netcode package

Dynamic Spawn

There are roughly two ways to spawn network objects. The first is just place objects in the scene. Then, when the scene is loaded, the objects will be spawned by Netcode. The second is instantiating an object dynamically by Instantiate method and spawn it by GetComponent<NetworkObject>().Spawn(). This post is focusing on the second way.

To use dynamic spawn, we need to register prefabs to NetworkPrefabs in NetworkManager component of Netcode.

Network Manager

The following image shows Network Manager component addition. It is included in Netcode package.

Add Network Manager Component

Network Manager component has a field named NetworkPrefabs in the following image. Prefabs to spawn dynamically have to be registered in there.

Place to register dynamic spawned prefabs

If there are just few prefabs, it is okay to register manually by drag & drop. But, if we have tens of? or hundreds of? I was very frustrated when I registered tens of prefabs. If it is okay to use APIs instead of explicit registration, AddNetworkPrefab can be used to register both server and client sides. But I wanted to show prefabs @ component view. So I started to develop the codes in NetcodeExample

I created a dog prefab to show an example.

An example prefab - dog

One-click Register

Netcode prefab has Network Manager in Netcode. I dig in to the Netcode.prefab file to reveal the structure of the file. To figure out how can I modify the file to register network prefabs by code instead of drag & drop.

Make a prefab which contains NetworkManager component

I created NetcodeRegister class to implement one-click register.

YAML - Netcode.prefab

The first thing is to understand Netcode.prefab file. Prefab files uses (Unity) yaml syntax. You can find out what is yaml in here. I recognize yaml as simplified json format. The following code shows the contens of Netcode.prefab.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
...
  NetworkConfig:
    ProtocolVersion: 0
    NetworkTransport: {fileID: 8661109935012272639}
    PlayerPrefab: {fileID: 0}
    NetworkPrefabs: []
    TickRate: 30
    ClientConnectionBufferTimeout: 10
    ConnectionApproval: 0
...
  DebugSimulator:
    PacketDelayMS: 0
    PacketJitterMS: 0
    PacketDropRate: 0

NetworkPrefabs: [] is defined @ line 8 in the above code. The list will be modified by code. For that, we have to know what happens when a network prefab is registered. When I registered the Dog prefab, NetworkPrefabs: [] changed like the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
  NetworkConfig:
    ProtocolVersion: 0
    NetworkTransport: {fileID: 8661109935012272639}
    PlayerPrefab: {fileID: 0}
    NetworkPrefabs:
    - Override: 0
      Prefab: {fileID: 979486745327824490, guid: 3077113299775460f89cbf89f0a0cb56, type: 3}
      SourcePrefabToOverride: {fileID: 0}
      SourceHashToOverride: 0
      OverridingTargetPrefab: {fileID: 0}
    TickRate: 30
    ClientConnectionBufferTimeout: 10
...

And then, I figured out that I must know how to extract fileID and guid from prefab files.

Prefab FileID & guid

In UnityEditor, find out paths of prefab files is easy. Just use Directory.EnumerateFiles. The hard thing in here is that find out FileID and guid. With some struggles, I figured out that the ids are belong to the first GameObject of each prefab like the following code.

1
2
3
4
5
var assetPath = "Assets/Test/Example.prefab";
var obj = AssetDatabase.LoadAllAssetsAtPath(assetPath).First(e => e.GetType() == typeof(GameObject));
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long fileId))
{
}

Inject into NetworkManager

Now, it is time to mix all finds out. We know prefabs’ fileIDs and guids, just write to them Netcode.prefab.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class NetcodeRegister : MonoBehaviour
{
    [SerializeField] private string RootPath;

    public void RegisterNetworkPrefabs()
    {
        var netcodePrefabPath = Path.Join(Application.dataPath, "Prefab/Netcode.prefab");
        var lines = File.ReadAllLines(netcodePrefabPath);

        var builder = new StringBuilder();

        var pos = 0;
        while (!lines[pos].Trim().StartsWith("NetworkPrefabs"))
        {
            builder.AppendLine(lines[pos]);
            pos++;
        }

        var spaces = lines[pos].Substring(0, lines[pos].IndexOf('N'));
        builder.AppendLine(string.Concat($"{spaces}NetworkPrefabs:"));
        var searchPath = Path.Join(Application.dataPath, RootPath);
        foreach (var prefabPath in Directory.EnumerateFiles(searchPath, "*.prefab", SearchOption.AllDirectories))
        {
            var assetPath = Path.Join("Assets", prefabPath.Replace(Application.dataPath, ""));
            var obj = AssetDatabase.LoadAllAssetsAtPath(assetPath).First(e => e.GetType() == typeof(GameObject));
            if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long fileId))
            {
                builder.Append(spaces);
                builder.AppendLine("- Override: 0");
                builder.Append(spaces);
                builder.AppendLine("  Prefab: {fileID: @@FILEID@@, guid: @@GUID@@, type: 3}"
                    .Replace("@@FILEID@@", fileId.ToString())
                    .Replace("@@GUID@@", guid));
                builder.Append(spaces);
                builder.AppendLine("  SourcePrefabToOverride: {fileID: 0}");
                builder.Append(spaces);
                builder.AppendLine("  SourceHashToOverride: 0");
                builder.Append(spaces);
                builder.AppendLine("  OverridingTargetPrefab: {fileID: 0}");
            }
        }

        Debug.Log(builder);

        while (++pos < lines.Length && lines[pos][spaces.Length] is ' ' or '-') ;
        while (pos < lines.Length) builder.AppendLine(lines[pos++]);

        Debug.Log(builder);

        File.WriteAllText(netcodePrefabPath, builder.ToString());
    }
}

This is all to register network object prefabs by code in UnityEditor. Now let’s add a button @ Inspector to execute RegisterNetworkPrefabs method.

One-click Button

I referenced the following video to implement add button on Inspector.

The following shows the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[CustomEditor(typeof(NetcodeRegister))]
public class NetcodeRegisterButton : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        var register = (NetcodeRegister) target;
        if (GUILayout.Button("Register"))
        {
            register.RegisterNetworkPrefabs();
        }
    }
}

The following images show before clicking the button and after clicking the button respectively.

Before clicking Register Button

After clicking Register Button

Wrap up

I was trying to figure out how can I register dynamic spawned network prefabs by code instead of drag & drop @ Inspector. During digging and digging, I could understand Unity YAML syntax and how a prefab is registered as yaml. The most difficult thing was to figure out fileID and guid.

The whole experiences helped me a lot to understand Unity.

Source Code

You can find the entire project source code in https://github.com/hyunjong-lee/NetcodeExample. If there are some tricky cases like this, I will update the repository with new blog postings.