Search code examples
c#unity-game-enginenetwork-programmingspawnunity3d-unet

UNET: Correctly registering dynamic objects


I am working on a personal project of mine as some-what of an amateur at programming. My goal with this project is in summary to let players build and customize their own ships, then join a public server and fight with it.

The error I am getting is as such:

Failed to spawn server object, assetId=000000000000000000000000000000e7 netId=3 UnityEngine.Networking.NetworkIdentity:UNetStaticUpdate()

And here is the most relevant code for it:

    public void LoadShip(Vector2 pos, string path = "save.xml",Ship oldship = null){
    Debug.Log ("Loading Ship!");
    ShipObject shipObj;
    if (oldship != null) {
        shipObj = oldship.shipObjectClass;
    } else {
        GameObject newShip = new GameObject ();
        shipObj = newShip.AddComponent<ShipObject> ();
        Rigidbody2D rb = newShip.AddComponent<Rigidbody2D> ();
        rb.gravityScale = 0;
        rb.drag = 0.1f;
        rb.angularDrag = 0.1f;
    }
    shipObj.PrepareForLoad ();
    XmlSerializer serializer = new XmlSerializer (typeof(Ship));
    FileStream reader = new FileStream (Path.Combine(Application.dataPath, path), FileMode.Open);
    if (shipObj.LoadShip ((Ship)serializer.Deserialize (reader))) {
        shipObj.gameObject.transform.position = pos;
        reader.Close ();
        //Give all the children rigidbodies, as they are loaded without them
        //by default for when physics are not needed.
        for (int i = 0; i < shipObj.transform.childCount; i++) {
            GameObject child = shipObj.transform.GetChild (i).gameObject;
            Rigidbody2D rb = child.AddComponent<Rigidbody2D> ();
            rb.gravityScale = 0;
            rb.drag = 0;
            rb.angularDrag = 0;
            rb.mass = 0;
            rb.isKinematic = true;
        }
        shipObj.name = shipObj.ship.shipName;
        Debug.LogError ("Ship name = " + shipObj.name);
        ClientScene.RegisterPrefab (shipObj.gameObject, NetworkHash128.Parse (shipObj.name));
        GameObject prefab;
        ClientScene.prefabs.TryGetValue (NetworkHash128.Parse (shipObj.name), out prefab);
        if (!prefab) {
            Debug.LogError ("Prefab not registered!");
            return;
        }
        if (shipObj.GetComponent<NetworkIdentity> () != null) {
            Cmd_SpawnShip (prefab);
        } else {
            Debug.LogError ("No network identity here!");
        }
    }
}

[Command]
public void Cmd_SpawnShip(GameObject ship){
    NetworkServer.Spawn (ship);
}

As a run-down of what you're seeing: Player calls LoadShip() with the desired spawn position, the path of the ship it loads, and possibly an already existing ship they want to override. It then loads all necessary data using XML, adds physics and registers the prefab based on the ship's name, which should in all cases be unique.

Both the main ship object and all of it's child objects (which are things like walls, guns, consoles, so on so forth) are given NetworkIdentity components, and the main ship is also given a NetworkTransform component within LoadShip().

The ship spawns just fine for whichever client opted to load it, but neither the server or other clients are able to see it, and instead receive that error.

I have been trying to fix this for..Maybe the last 3 hours. It's been a long day, and I trust that you'll believe me when I say I searched both google and stackoverflow meticulously for an already-posted answer to my question. The most common problems among others were: Simply not registering the prefab, registering the prefab on the server, forgetting to give it an assetID, not calling it on a client / something with authority. I'm pretty sure I have avoided all those things, and please, don't link me to the Unity tutorials. I promise you, I've read them multiple times.

Additionally, if I just host a game with only one client and spawn in a ship, the prefab does not show under the Network Manager's 'registered prefab' list, even though I've got the network manager on 'Debug' log level and it tells me it successfully registered the prefab.

That's about all the relevant information. I'll thank you for your time in advance, hopefully whatever mistake I've made isn't too blatantly obvious.


Solution

  • Worked up the motivation to do some more tackling of this problem. After a few more hours of studiously scourging the internet for answers; Eureka! The two misconceptions I had in my code were;

    • RegisterClientPrefab() does not automatically create a prefab out of a GameObject for use by the server. I'm not sure why I even thought this in the first place, it seems pretty obvious in hindsight.
    • I assumed that when RegisterClientPrefab() was called, what it did was tell the server, 'Hey, you. I've made something. Go share it with everyone!' But in actual fact, it just tells that individual client where to look for a specific assetId.

    The way I went around fixing these misconceptions is I simply gave the server a Dictionary containing every ship's assetID and a serialized byte[] array of the xml it was loaded from, followed by a RegisterSpawnHandler that lead to correctly spawning the objects. I am not certain this is the best way to do it, and if there is any other form of encoding that's easier/faster to send over a network than a byte[] array, but it works for me.