TransWikia.com

Mirror/Unet, RegisterSpawnHandler throws invalid asset Id error because assetId is 0 for dynamically loaded assetbundles

Game Development Asked on December 8, 2021

Its weird how documentation literally states that

For more advanced uses, such as object pools or dynamically created
Assets, you can use the ClientScene.RegisterSpawnHandler method

But seems like its broken, because dynamically loaded objects’ assetIds’ are always 0:

(This is true for all other dynamically spawned objects)

enter image description here

My code structure:

I have a "MapLoader" script that loads objects from filesystem using its config file.

MapLoader creates vehicles and assigns them unique UIDs that are same across players.

Vehicles are added into Static dictionary with UID -> vehicle key/value pairs, and to a list of GameObjects:

        VehicleNetworking vehicleNet = spawned.AddComponent<VehicleNetworking>();
        vehicleNet.vehicleScript = vehicleScript;
        vehicleNet.uid = uid;

        spawned.tag = "vehicle";
        spawned.layer = 10;

        GameManager.vehiclesToSpawn.Add(spawned);
        GameManager.AddVehicle(vehicleNet);
    }

After vehicles have been added, I call functions on the local Player object to spawn them:

  foreach (GameObject vehicle in GameManager.vehiclesToSpawn)
    {
        if (isServer)
        {
            SpawnServerVehicles(vehicle);
        }
        else
        {
            SpawnClientVehicles(vehicle);
        }
    }

Spawn scripts:

[Server]
void SpawnServerVehicles(GameObject vehicle)
{
    Debug.Log("Server should spawn: " + vehicle.name);
    NetworkServer.Spawn(vehicle);
}

[Client]
void SpawnClientVehicles(GameObject vehicle)
{
    Debug.Log("Client should spawn: " + vehicle.name);

    MD5 md5 = MD5.Create();
    string vehicleId = vehicle.GetComponent<VehicleNetworking>().uid;
    byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(vehicleId));

    Guid result = new Guid(hash);
    Debug.Log(vehicle.name + "-" + result);
    ClientScene.RegisterSpawnHandler(result, SpawnHandler, UnSpawnHandler);
}

Spawn handlers:

private static GameObject SpawnHandler(Vector3 position, Guid vehicleGuid)
{
    Debug.Log("Called Spawn Handler");
    GameObject go = new GameObject();
    return go;
}

private static void UnSpawnHandler(GameObject gameObject)
{
    Destroy(gameObject);
    Debug.Log("Called UnSpawn Handler");
}

If I am doing everything right, and Mirror library is actually broken, then I’d like to know what could the workarounds be?

My goal is to have SyncVars on vehicles working and syncing with the server(But for this, I need to have NetworkIdentity on vehicles, and for them to work, they need to be spawned using NetworkServer or ClientScene).

One Answer

Didn't really solve this particular problem, but found a much nicer working workaround.

Created a Dummy prefab with my vehicle's netcode attached to it(Note, I separated vehicle physics code and vehicle input netcode, left physics code on vehicle object where it belongs). Saved this dummy as an asset, added NetworkIdentity component to it and added it into NetworkManager.

Now all is left is to spawn vehicles based on config files locally on each client (like in singleplayer mode, without any NetworkServer interactions, just simple instantiate), and spawn this dummy prefab that I called "VehicleNetworkingController" from Player.cs code(Important, make sure you are spawning other networkidentities from local server player) like this:

//This is just a VehicleNetworkingController prefab from game Assets
public GameObject vNetControllerPrefab;

GameObject SpawnVNetPrefab(GameObject vNetController)
{
    //Could as well just use [Server] but whatever
    if (isServer)
    {
        if (isLocalPlayer)
        {
            var controller = Instantiate(vNetController);
            controller.tag = "vNetController";
            NetworkServer.Spawn(controller);

            return controller;
        }
    }
    return vNetController;
}


IEnumerator WaitForVehicles()
{
    while (!GameManager.isReady)
    {
        yield return new WaitForSeconds(0.15f);
    }
    foreach (GameObject vehicle in GameManager.vNetControllersToSpawnFor)
    {
        SpawnVNetPrefab(vNetControllerPrefab);
    }
}

Basically I am spawning VehicleNetworkingController(Dummy prefab containing vehicles netcode) using NetworkServer.Spawn(If on server), nothing special.

On client, every VehicleNetworkingController(That already will exist in clients' scenes automatically on connection because Server used NetworkServer.Spawn(instantiate) to spawn it) has this function that registers itself:

public readonly SyncListItem passengers = new SyncListItem();

public VehicleControllerDynamic vehicleScript;

[SyncVar]
public string bintVehicleId = "";

[SyncVar]
public float forward, sideways, braking;

private void Start()
{
    //Use Static classes like GameManager in my case, for storing and accessing info
    //Anytime and anywhere, very handy. 
    GameManager.AddVNetController(this);

    //Registering dummy prefab so now its networkIdentity gets synced with server. Now you can bind it to vehicle's MonoBehaviour physics handling script and sync variables from this spawned prefab to it. 

    //Basically we spawned prepared prefab to control dynamically generated assetbundle prefabs

    ClientScene.RegisterPrefab(gameObject);
    

    //I found using Coroutines with static classes very handy too. Mostly using them to wait for other scripts/objects to load and only 
    //then continue execution while not freezing a game.
    StartCoroutine(WaitForBint());

}
...

This technique resulted in more organized code I'd say, and it also rendered ClientScene.RegisterSpawnHandler function basically useless.

Answered by Nick on December 8, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP