2
\$\begingroup\$

Why do I need to do this? The game I'm working on fully conforms to a component style design. Every component that a game object uses is placed on the object and is disabled/enabled based on it's state machine's current state. This means if I have two components that share a base class that has an RPC method, they won't work due to this:

"RPC function names should be unique accross the scene, if two RPC functions in different scripts have the same name only one of them is called when RPC is invoked"

http://docs.unity3d.com/ScriptReference/NetworkView.RPC.html

This means an arbitrary component will be the "this" of the RPC method that actually gets called. So, to fix this I've added a unique name to every component and have to do some relatively slow searching and reflection in order to have a safe RPC call. So, now every RPC call should go through my RPC method. This method simply serialises the parameters into a string and sends them to unity's RPC method.

This works great, except one tiny thing. Serialising the entire array results in Unity doing no serialisation in NetworkView.RPC. That's fine, except as far as I can tell, Unity's NetworkView.RPC method is the only thing in the universe that knows how to serialise a NetworkViewID properly.

So, serialising the whole array of objects isn't an option. So, I now I serialise them IF they're not a NetworkViewID, then send the entire object array to Unity, which it does interpret correctly:

protected void ComponentRPC( string methodName, RPCMode mode, params object[] args )
{
    object[]                serialisedObjects       = new object[args.Length + 2];

    serialisedObjects[0] = uniqueName;
    serialisedObjects[1] = methodName;
    for( int i = 2; i < serialisedObjects.Length; ++i )
    {
        if( args[i - 2].GetType() == typeof( NetworkViewID ) )
        {
            serialisedObjects[i] = args[i - 2];
            continue;
        }
        serialisedObjects[i] = base.Serializer.Serialize<object>( args[i - 2] );
    }

    // Serialise arguments and call our RPC method through Unity.
    networkView.RPC( "ComponentRPCNetwork", mode, serialisedObjects );
}

I also put the unique component name and method name in the array. The only problem now is, Unity won't put the objects it serialises into my params object[] parameter. The RPC method:

[RPC] private void ComponentRPCNetwork( params object[] args )
{
    // First we have to deserialise the method arguments.
    string              uniqueID                    = ( string )args[0];
    string              methodName                  = ( string )args[1];
    object[]            deserialisedArgs            = new object[args.Length - 2];
    for( int i = 0; i < deserialisedArgs.Length; ++i )
    {
        if( args[i + 2].GetType() == typeof( byte[] ) )
        {
            deserialisedArgs[i] = args[i + 2];
            continue;
        }
        deserialisedArgs[i] = base.Serializer.Deserialize<object>( ( string )args[i + 2] );
    }... // More stuff.

So, my question is, can I get Unity's NetworkView.RPC method to put it's serialised objects into this "args" parameter? If not, can anyone see anyway around this?

By the way I'm using Vexe's framework, so you might see unusual things like "base.Serializer" etc.

\$\endgroup\$
  • \$\begingroup\$ Can you not just have [RPC] private void ComponentRPCNetwork(object[] args ), basically, drop the params on your [RPC] method? If you need to use it locally too you can just write a local wrapper that takes a params and passes an array to the [RPC] method. \$\endgroup\$ – MichaelHouse Dec 11 '14 at 7:54
  • \$\begingroup\$ Dropping the params still doesn't help. Unity's RPC method is looking for a method that takes every argument passed into it. So, if I pass it ( Vector3, Vector3, string ), it'll look for a method with those exact parameters and not accept anything else even though syntactically it would compile. \$\endgroup\$ – Ben Dec 11 '14 at 8:01
  • \$\begingroup\$ Ah, I understand the issue now, you'd have to cast everything in the array to an object. Well, it's undocumented, but the RPC method does allow a byte array. You can serialize your object array into a byte array. Pull out the NetworkViewID, resulting in something like: ComponentRPCNetwork(NetworkViewID viewID, byte[] objects )? \$\endgroup\$ – MichaelHouse Dec 11 '14 at 8:16
  • \$\begingroup\$ Yeah, then nothing passes through unity's serialiser, which I've found is the only way to get the serialising of a NetworkViewID to work properly. If I let Unity serialise a NetworkViewID, it works fine. If I use JSON, the allocated id won't be found by the receiver. If I use BinaryFormatter, it won't allow me to serialise it because it's missing the System.Serializable attribute. \$\endgroup\$ – Ben Dec 11 '14 at 8:20
1
\$\begingroup\$

I regularly use RPC like the following:

{
    //Send all the ship components to other clients
    NetworkViewID viewID = ship.GetComponent<NetworkView>().viewID;
    foreach(ModuleDefinition module in ship.GetAllModuleDefinitions()) {
        networkView.RPC ("AddShipModule",
                         RPCMode.Others, viewID, netOwner,
                         ModuleDefinition.WriteByteStream(module));
    }
}


[RPC]
public void AddShipModule(NetworkViewID newPlayerView,
                          NetworkPlayer netPlayer,
                          byte[] moduleInitializer) {
    if (netPlayer != Network.player) {//don't update ourselves
       //deserialize moduleInitializer into module and add it to local instance
    }
}

This is when using my own serializer to create the moduleInitializer out of an object and its components. That byte[] array could just as easily be full of JSON or any other binary data.

If your allocated ID is not being found on the other end, it could be because the object you're trying to communicate was created incorrectly, giving it a different NetworkView.viewID. You can always use Network.Instantiate or you can send a message to clients that an object needs to be instantiated and send the NetworkView.viewID along with it.

\$\endgroup\$
  • \$\begingroup\$ Yeah, I started to suspect that it's probably just my creation code. But if that's the case, how come this works perfectly using RPCs directly? \$\endgroup\$ – Ben Dec 11 '14 at 19:35
  • 1
    \$\begingroup\$ It's a good question that I don't have an answer to. Perhaps there are other factors at play when you're switching between the two methods of RPC. If you're not sure about the creation code, you can post a new question for that so we don't get too off topic here. \$\endgroup\$ – MichaelHouse Dec 11 '14 at 19:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.