Search Unity

How should I create the following structure properly without a NetworkBehaviours hierarchy?

Discussion in 'Multiplayer' started by TobiasW, Aug 18, 2016.

  1. TobiasW

    TobiasW

    Joined:
    Jun 18, 2011
    Posts:
    91
    Hello,

    After some trouble implementing a hierarchy of NetworkBehaviours in a prefab, I found out here that it's not possible to spawn something like that.

    Simplified, I have the following prefab:
    • Puzzle
      • Wheel
        • LeapGrabbableWheelHandle
      • Wheel
        • LeapGrabbableWheelHandle
      • Display
      • Display
    Right now, all of these are NetworkBehaviours to keep it highly flexible: To add something to the Puzzle, I just need to add a new child with the proper component.

    Here are the reasons while all of these are NetworkBehaviours:
    • The Puzzle is a NetworkBehaviour because it needs to synchronize over the network what its current state is and whether it is solved, and it needs to play sounds on all clients.
    • Wheel and Display are NetworkBehaviours because they should manage themselves (e.g. synchronize their visual appearance). Right now they are part of a Puzzle, but they should be able to exist without it, e.g. as part of a command console or in some other sort of machine.
    • LeapGrabbableWheelHandle is a NetworkBehaviour because it is derived from LeapGrabbable - an object that can be grabbed by a Leap Motion hand and automatically synchronizes its state (is it grabbed, by which hand, etc).
    This structure works if...
    • ...I just drop it in the scene...
    • ...without it being a prefab and..
    • ...if I give every child a NetworkIdentity.
    A prefab with child NetworkIdentities just dropped into the scene doesn't work - the NetworkIdentities get assigned wrong IDs.

    A spawned prefab doesn't work according to this thread.

    How would I properly implement this then?

    All the best,
    Tobias
     
  2. TobiasW

    TobiasW

    Joined:
    Jun 18, 2011
    Posts:
    91
    I now pack all of the scripts on the prefab root and link its targets via reference in the editor, but damn, that's hardly a good solution. Now I have over a dozen scripts on one game object - and I needed to interconnect references between the scripts, which now all look like "SomePuzzle (Wheel)". I don't know of any way to even find out which Wheel scripts they reference in particular because they are all on the same GameObject. It's a maintenance nighmare.
     
  3. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    What's wrong with using a single networked script on the root object which exclusively controls all network synchronization tasks for related child objects as a central manager script?
    Addition of multiple child-specific scripts to the root seems cumbersome indeed and I don't think that you need to do that. You can still have individual scripts on your child objects which are usual MonoBehaviors or whatever else. These scripts just need to call appropriate functions on the manager script.

    If you want to create childs dynamically (e.g. during runtime), you can add methods like myManager.AddChild() or RegisterChild(). Then, on the server, you could call a ClientRPC so that the childs are added on the clients as well. Also, you could use unique identifiers to identify child objects across server and clients.

    I think this is a problem which can be solved by some changes to the code architecture.
     
    Last edited: Aug 25, 2016
  4. TobiasW

    TobiasW

    Joined:
    Jun 18, 2011
    Posts:
    91
    I like that definitely more than having every child on the root.

    Downsides that I see:
    • I can't use SyncVars in the children anymore. Updates could be handled via RPC calls, but SyncVars are especially useful for providing the correct initial values. How would you handle those?
    • How would you do the unique identifiers? Setting them manually seems cumbersome and a potential source of human error. Previously, I used SyncVars for something like that, but here I can't.
    • The manager class needs a lot of methods that are just routing calls along, but I guess I can live with that.
     
  5. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    Allocation of identifiers should be automatic. For example, you could just use an incremental int value. Whenever you register a child at the manager on the server, you increment that value. In addition, you could add the registered identifier to a SyncList. So, the number of your child objects and their unique id's are automatically synced to the clients by the list. You can implement similar functionality with RPCs though.
     
    Last edited: Aug 25, 2016
  6. TobiasW

    TobiasW

    Joined:
    Jun 18, 2011
    Posts:
    91
    Maybe I am misunderstanding something here. If the identifiers are the only way of identifying a child and they are assigned on the server at runtime, how does the client match the identifier list values to its respective childs?
     
  7. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    Valid question.

    If you use RPCs, you can simply pass the id from the server. Then, if the RPC is executed on the client, you can create the reference for the object with the very same id and do something like RpcCreateChildOnClient(int id).

    With sync list, this might be a bit more tricky, especially if multiple objects are added/removed at a time. However, if you can make sure that the child objects are always in the same order on both server and clients, you could simply iterate over all childs and assign IDs by their order. You would want do this everytime when a child is added/remove and the results should always be the same for server and client.

    Also, if you make sure that child objects are created and removed in the same order on server and clients, the incremental id assignment should be deterministic and usable as well.

    I guess there are probably more approaches for this.
     
    Last edited: Aug 25, 2016
  8. TobiasW

    TobiasW

    Joined:
    Jun 18, 2011
    Posts:
    91
    But I cannot call RPCs directly in the children because they aren't NetworkBehaviours. I would need to make an RPC in the central manager script - and then on the client, I wouldn't know from which child the RPC call came because IDs are not assigned yet.

    Maybe the best way would be to just use child GameObject names as a preliminary identifier.
     
  9. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Since you want your Wheel/Handle objects to be able to exist on their own, I think keeping your 12 components on the root object seems like the best solution.

    Because if you go with the "manager" approach, and you need to have a standalone handle at some point in your game, you'll have to write a NetBehaviour manager just for that handle. There's probably a way to do this and not duplicate code all over the place, but I still like the 'lots of components' approach more. It's a very small price to pay.

    Remember that not being able to have netBehaviours on children means better network efficiency (probably)
     
    Last edited: Aug 25, 2016
  10. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    You do not need NetworkBehaviours on the childs.
    I think I need to elaborate a little more what I meant..

    So, we have the basic puzzle object with the networked mananger. And we have prefabs for childs.

    1. On server, you create your child objects based on prefabs and assign ids as you like for each child. You store the childs in a data structure (array, list, etc.) so that they can be accessed by it's id. Now you can access your child objects by id on the server.

    2. Next, again on server, you send a ClientRPC from your manager script for each child you have. For each RPC, you pass the particular child's id and optional other arguments as you like. Then, on the clients, when a particular RPC is received, a child is created with the received ID. You then store this object in the same data structure as on the server and you are able to access your objects by id on the client as well. You would not need to do this on a host of course.

    At the end of the day, you have the same childs with synced id's on server and client, and childs do not need to be networked objects themselves.

    This is one possible solution among others.
     
    Last edited: Aug 25, 2016
  11. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Actually, I have a related question and a possible alternative solution. Consider this:



    Am I allowed to not put any NetworkIdentity or NetworkBehaviours on "Puzzle", but to put them on each child individually instead? Will this spawn and work without any problems?

    If so, the problem is solved. Just add a regular monobehaviour on your Handles to make them follow a point on the wheel using matrix transforms instead of using parenting

    Edit: one more thing.... I don't know if calling RPCs on Update() was part your plan, but that's really not recommended. RPCs take up too much bandwidth for this sort of usage (according to Unity manual)
     
    Last edited: Aug 25, 2016