Search Unity

Question Spawn different prefabs for local and remote representation of the same player - possible?

Discussion in 'Netcode for GameObjects' started by CodeSmile, Sep 25, 2022.

  1. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,892
    I have a player prefab that has a virtual camera, character controller, name tag, and so on. So far, I've followed the Netcode examples (ie ClientDriven) where you'd enable/disable the player prefab's behaviours and objects based on whether the player is owner, client or server. I find this tedious and error-prone to work with, specifically because those decisions are hidden away within scripts.

    I'd prefer to spawn two different prefabs, ie LocalPlayer for the local player controlling the character and camera whereas the remote players see an instance of RemotePlayer running around, ie with a name tag above its head.

    But I get the feeling that from a Netcode point of view those would be two different net objects, right? Is this even possible?

    I've yet to see such an example and the only forum post I found was about spawning different prefabs for player A vs B rather than local vs remote.

    The best workaround thus far would be to have the player prefab's root object contain only the stuff that both need, with two child objects "local" and "remote" which would get enabled/disabled as a whole. What are your solutions?
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,892
    I'm actually quite happy with the workaround. The game objects "LocalPlayer" and "RemotePlayer" in the player prefab change their active state based on the IsLocalPlayer property of the NetworkBehaviour:
    upload_2022-9-26_19-1-40.png

    The only other thing that I couldn't move to a child game object was the CharacterController - though long term I seek to replace that with another controller (likely the Kinetic Character Controller now that it is free).

    The relevant code fragment:
    Code (CSharp):
    1.             GetComponent<CharacterController>().enabled = IsLocalPlayer;
    2.             _localPlayerRoot.SetActive(IsLocalPlayer);
    3.             _remotePlayerRoot.SetActive(IsLocalPlayer == false);
    4.  
    PS: I prefer explicitly doing a false check for readability. Compare:
    Code (CSharp):
    1. _remotePlayerRoot.SetActive(IsLocalPlayer == false);
    2. _remotePlayerRoot.SetActive(!IsLocalPlayer);
    It's easy to overlook that innocuous ! especially when it's followed by an I or l.
     
  3. Loden_Heathen

    Loden_Heathen

    Joined:
    Sep 1, 2012
    Posts:
    480
    You have a solution but thought I would share what we do

    In our case we have a requirement that should the network be lost the game continues to play as is, if the network is resumed the game again just drives on.

    So the way we did this was that all of our "network behaviours/objects" are non-visual interfaces if you like. They do nothing but sync data between systems. All actual game objects e.g. things relevant to the game are non-networked.

    If the game should be networked then peers wait for a time out while the host spawns the network "controllers" we call them internally. The controllers on spawn create the non-network "puppets" for the "Owner" e.g. server. The puppet on the owner sets the values on the controller for all others the controller sets the values on the puppet.

    This then also means we can let each client use its own "skins" without impacting all other clients as the network objects are completely decoupled from the presentation it does mean duplication of sync data being a copy in the network objects and a copy on the puppets so to speak but this was by design for us so the game if it loses connection just lets the puppets run as if they where owner. If connection is resumed which we limit to with in a time out period the controller reassume control of their respective puppets.

    This actually proved to be a lot simpler than we had expected and as noted means the "visual" for each client isn't bound to the network prefab so we can allow client side skinning if we chose to or present different visuals to different users.
     
    Last edited: Sep 28, 2022
    CodeSmile likes this.
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,892
    Thanks for the comment, that is very insightful. Specifically:
    I'm at the very start and just getting to grasp with Netcode, so one idea I had just the other day was: why not have a central network state object, that isn't actually visually represented and just sends out messages / events to whichever game object / script needs to be informed about value changes.

    This tells me that idea is not just feasible but has the benefits I was expecting it to have, basically separating networked data from its representation, or NMVC (NetworkModel-View-Controller).

    Right now my first test simply shares the number of connected clients and displays it in the center of the level as a number, with synchronization and the label being separate objects/scripts. I liked how that made the code easier to grasp, and decoupled.

    So I'll definitely pursue this system further!
     
  5. Loden_Heathen

    Loden_Heathen

    Joined:
    Sep 1, 2012
    Posts:
    480
    Will have more than 1 networked object

    but you could get away with playerCount + 1

    That is each player will have a ... or I should say IMO should have a network object which acts as the human player's interface to the game. We use this to basically notify the server of human "requests" ... that +1 is basically the server's interface for us it holds session state info e.g. things global to the session dictated by the game not the player.

    Also of note we have AI players so for us we actually have

    playerController = player in the game could be AI or human
    userController = human interface
    networkController = server interface

    every player in the match in a networked session has a playerController that drives our playerPuppet

    every user in the match has a userController ... only the local user's userInput drives its userController the others are useful for observing other players so to show other player's health etc.

    The networkController doubles as our Spawn Pool and syncs global game session info like what wave we are on, translated NetworkMangaer.ServerTime to local time, etc.

    This by the way means we can tolerate a human player leaving and coming back without impacting the game ... infact we can make it transparent if we wanted ... e.g. you may start a session with a human ... if they rage quite AI takes over ... your non the wiser less you recognize our AIs habits
     
    CodeSmile likes this.
  6. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,892
    Yes, that's what I was thinking. One for each client, and at least one for the global or world state.

    Such a UserController system would also allow for more than just switching between human vs AI input but also for ghosting, ie playback of a previous session's or another player's recorded input to compete against.