I have some singletons in my project that I would like to replace with a better, decoupled pattern. To this end, I watched Ryan Hipple’s talk on Game Architecture with Scriptable Objects. In it, he suggests replacing some functions of a singleton with SOs. I really like the approach and have most of the refactoring figured out already, but some of my singletons are derived from Networkbehavior and use rpcs, which of course are not usable in an SO.
I can think of two solutions that could solve the problem: Custom messages and a custom “RpcManager” that processes all Rpc and can be called globally. The first solution seems a bit overkill and the second contradictory.
Are there any other solutions that I have overlooked and which one should I use?
You don’t need to make any (!) *Behaviour script a singleton. All you really need is a central lookup registry. This is my (initial) solution. I have since made this a generic class.
In my project there is no singleton other than the registry, and the ones I’m forced upon (NetworkManager).
Custom (named) messages aren’t all that difficult to use. In fact I gained a liking because they help decoupling a lot. They do however make it more complicated to filter recipients since they do not support the Rpc keywords like SendTo.NotOwners
and such.
1 Like
The problem is not the access, but the rpc. Even if I had a registry similar to yours, I would need a NetworkObject to handle the specific rpcs of each component. And that NetworkObject needs to be spawned for it to work. I also plan to inject the SO directly into any Behavior that needs it, so a registry is not necessary in this case.
As I understand it, I only have two options: a global NetworkObject that handles all rpc and is called by every SO, or use custom messages. Or am I missing something here? If not, I’d like to know which solution is best for production.
Sure, the RPCs need to be moved outside the singleton to the components that are responsible for handling the RPCs.
You can still use the registry to avoid the spawned NetworkObject instance having to be a singleton because other scripts can get its reference through the registry.
That’s about correct. Best for production, long term and assuming something more complex than chess or Pacman I’d say go for custom messages. This provides you with better optimization (combining messages, packing and/or compressing data) and debugging/logging opportunities down the road as you’ll pass all traffic through a central handling script of your own.
Personally I came to conclude that RPCs inside NetworkBehaviour are nothing but a crutch to serve a) fast prototyping and b) those who don’t know any better. It’s kind of akin to doing the Update in a script per enemy rather than a central “EnemyManager” iterating over each instance and updating it. The overhead is going to be highly inefficient as you scale up, in the latter case it hurts your CPU, and in the networking case it’ll generate a lot more traffic.
I was able to optimize per-enemy RPCs for 100 instances from 200 KiB/s down to 40 KiB/s and with Brotli compressing the resulting buffer even cutting the latter in half, so it’s a 5-10 fold decrease in traffic! Imagine paying 5-10 times less for your gaming service (Hosting, Relay) bandwidth surcharges. 
1 Like