Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Network equivalent of SendMessage?

Discussion in 'Multiplayer' started by Joe ByDesign, Feb 18, 2008.

  1. Joe ByDesign

    Joe ByDesign

    Joined:
    Oct 13, 2005
    Posts:
    841
    Hi gang,

    Is there a Network equivalent of SendMessage? I've tried using RPC in this way, but found it extremely painful trying to use RPC to sync the following configuration:

    Server / Client0:
    --Client1
    --Client2
    --Client3

    where gameplay is turn-based; each client should only be able to interact when it's their turn.

    I've understood I should use RPC in this configuration (instead of state syncs) to minimize on bandwidth usage, but I'm finding that when a spawned player performs an action, where the action hooks into a gameplay manager class, the method called on the manager class does not propagate to the other clients.

    The Manager class even has a NetworkView component with State Synchro set to Reliable Data, but alas, no sync.

    What am I missing here? Any tips for non-realtime / transform-based network game play?
     
  2. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    I guess you need to make sure that the game play manager logic is handled on the server. There's a couple of ways to do that... Network.isServer and Network.isClient are probably the most obvious ones (then, you can check who you are, and either perform the server logic, or the client logic).

    You could then use RPCMode.Server to send messages to the server (via RPC, of course), and distribute the results via RPCMode.All (since your server is also a client - if you had a standalone server, you would use RPCMode.Others).

    Ah, I almost forgot one catch (I'm having a standalone server in my game, so I don't have that problem that much): You can't use RPCMode.Server on the server itself. So, you need to call the relevant methods directly, when Network.isServer is true (instead of using an RPC with RPCMode.Server).

    Sunny regards,
    Jashan
     
  3. Joe ByDesign

    Joe ByDesign

    Joined:
    Oct 13, 2005
    Posts:
    841
    Thanks for this Jashan. The RPCMode docs are currently incomplete, so this oh-so-very-critical aspect wasn't clear to me; with your input, now it's coming together (slowly) heh!

    What about Network.Instantiated players? When I try to register each client upon Instantation (for setting player IDs and such), I can't seem to get the following RPC to propogate anything useful.

    For example;
    Code (csharp):
    1. function OnNetworkLoadedLevel () {
    2.     var somePlayer : Transform = Network.Instantiate (playerPrefab, transform.position, transform.rotation, 0);
    3.     networkView.RPC ("RegisterNetworkPlayer", RPCMode.All, somePlayer);
    4. }
    This fails because you cannot send Transform data via RPC (was going to use that for calling GetComponent for various things). But then I don't see anything that RPC sends that I *could* use, considering RPC will only send generic data (int, float, string, etc.). NetworkViewID doesn't inherit GameObject so, it's like a little island.

    It feels like there is something missing here;

    From the RPC docs;
    "A server could send an RPC to only a particular client to initialize him right after he connects, for example, to give him his player number, spawn location, team color, etc. A client could in turn send an RPC only to the server to specify his starting options, like which color he prefers or what items he has bought."

    But fails to say how... :(

    Any ideas?

    joe
     
  4. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    Whenever you do a Network.Instantiate, the object is created on all machines. So, what you can do is almost like you said:
    Code (csharp):
    1. function OnNetworkLoadedLevel() {
    2.     var somePlayer : GameObject = Network.Instantiate( playerPrefab, transform.position, transform.rotation, 0);
    3.     networkView.RPC( "RegisterNetworkPlayer", RPCMode.AllBuffered, somePlayer.networkView.viewID );
    4. }
    5.  
    One thing to note is that instead of RPCMode.All, you'll send AllBuffered, because if a new player joins, since both messages are buffered (Network.Instantiate is buffered) he will instantiate your player and register it.

    Then, to actually register, do the following:
    Code (csharp):
    1. function RegisterNetworkPlayer( viewID : NetworkViewID ){
    2.     var playerGOs = GameObject.FindGameObjectsWithTag("Player"); // assuming your playerPrefab has a Player tag
    3.     foreach( var player : GameObject in playerGOs ){
    4.         if( player.networkView.viewID == viewID ){
    5.             // do your registering stuff here...
    6.             break;
    7.         }
    8.     }
    9. }
    10.  
    Getting the idea? There may be some JS errors, I'm not really using it.

    Regards,
    Afonso
     
  5. Joe ByDesign

    Joe ByDesign

    Joined:
    Oct 13, 2005
    Posts:
    841
    Hehe, I was 1 min away from doing exactly what you suggest when I got the notification about your reply! I love synchronicity!

    FWIW, wasn't using AllBuffered because the gameplay requires that no other players enter after a play session has started.

    Thanks for the help Jashan Alfonso, it was the use of viewID that I wasn't catching on to.

    I should be up and running shortly! :)
     
  6. jeremyace

    jeremyace

    Joined:
    Oct 12, 2005
    Posts:
    1,661
    Like Jashan said it can save a lot of hassle to separate the functionality of the client code and the server code in the long run.

    One of our current projects is a 2 player online multiplayer game, and even though one player is always a player-server, the code is still separated.

    Everyone has a different way to organize this, some would like separate files for client and server, etc.

    The "pattern" I have been using successfully so far is the GameController->LittleStupidMinions method. I basically have one GameController class that handles _all_ of the main game logic, game state, GUI states, and also acts as a static blackboard for the rest of the game scripts to access to figure out what they are doing.

    This way, although it increases the size of your class (which is usually frowned on from a design standards perspective), it is a lot easier to keep all of the logic clean and organized, and I find you make fewer mistakes as all the logic is in one file.

    To keep that class organized, I specifically designate sections of my file using comment "note-blocks" or whatever you want to call them to designate sections of the file in the following structure (all these are pulled from the comment blocks in my code. It is all specified explicitly).
    Code (csharp):
    1.  
    2. - Preprocessor Symbols (for debugging, etc)
    3. - Includes (using, etc)
    4. - Member Vars
    5. - -> Client Settings
    6. - -> Master Server Settings
    7. - -> Server Settings
    8. - -> Game Settings
    9. - -> GUI settings and members
    10. - Initialization
    11. - GUI Rendering (OnGUI(), etc)
    12. - Updates and Frameloop
    13. - Window Rendering Methods
    14. - RPC Methods
    15. - -> Universal RPCs (non-peer type specific)
    16. - -> Client RPCs
    17. - -> Server RPCs
    18. - Methods
    19. - -> Universal Methods
    20. - -> Client Methods
    21. - -> Server Methods
    22.  
    And that's basically it. Other separate script files access this GameController when they need to find out what the game state is (paused, playing, etc), and other vars that need to be controlled.

    Other people will have different ways of doing this, but I hope this was useful. :)

    -Jeremy
     
  7. Joe ByDesign

    Joe ByDesign

    Joined:
    Oct 13, 2005
    Posts:
    841
    I'm sure this will help someone out there, and it's definitely interesting to see that others work the same way as you do (I do the same in regards to code structure), but I think we're getting away from the topic a bit!

    :eek:

    That said, I've managed to get most of the game working from RPC calls, but it still seems very flakey. I'm going to refigure some of the checking code to try to better keep things in sync.
     
  8. jeremyace

    jeremyace

    Joined:
    Oct 12, 2005
    Posts:
    1,661
    Haha, sry for not being more clear as to how my example applied to your problems. You have to look deeper into my eyes Joe!

    My example was to show one way to structure your main code based on the way RPC calls logically work to prevent your logic from getting "flakey" and messy, and just keeping you headache-free...for the most part. ;-)

    Keeping everything in sync, playing nice and generally behaving is quite hard if you are not very strict in how you use RPC calls and manage the various states of shared objects, and actually, by forcing a similar setup to what I showed above, the use of RPCs and RPCModes will make more sense...generally.

    If you structure your GameController like above, everything is logically broken up into client code and server code, so there are few conflicts.

    Since the separate objects/scripts in the game act as the "LittleStupidMinions" and don't contain any game or networking logic (for the most part), they are required to contact the GameController and let it figure out how to send out or handle the messages.

    So, for example, let's say player01 clicks to move his character ahead 1 unit. The script that handles that click would call a RequestMove() method on the GameController. What that method would do is check to see if we are running as a client, or as a player-server (if we have a standalone server no one is clicking anything on it anyway).

    If we are running as a client, then we use an RPC call to pass the movement request to the server using RPCMode.Server. If we are a player-server, then we just need to call the needed server method directly and we can skip the RPC. Then the server code can reply as needed either to the single player, or to everyone so they can update themselves. That's why I separate client and server methods in my pattern, as well as the RPC calls.

    Once you start doing that, the rest makes more sense.

    Most of my RPC calls are very short and structured like accessors, and pass off handling of the message data to the needed method. Like this:
    Code (csharp):
    1.  
    2. [RPC]
    3. public void RPC_SpawnEntity(EntityType entity, Vector3 pos, Quaternion rotation)
    4. {
    5.     SpawnEntity(entity, pos, rotation);
    6. }
    7.  
    As for the data you can send in an RPC call, that is the problem with the internet being so lousy. You have to figure out your own ways to get the needed info across, and one of the best ways is with good old enums. Painful yes, but that's the net for you. Transforms, GameObjects, etc are just too dang big to send over the network. Sad really...

    Hopefully that helps, :)
    -Jeremy