From our thread on the Unity Forums
Continuing to develop and improve the networking code, we’ve now implemented the basics of a party system, basic network synchronised mob wandering and aggro behaviour, and scene switching for the what will be a turn-based combat system.
One of the early problems faced was with how to approach handling the mobs for each map; we needed them to be network aware because we wanted to use RPCs to control their behaviour and be synchronized between clients, which meant that we couldn’t just place prefabs in the scene. So Initially we put placeholder gameobjects in the map scene which contained a script that used InstantiateSceneObject() to create the actual mob when the map loaded.
This approach presented a number of problems, the following being the most annoying:-
- Firstly the mobs were spawned on all clients, irrespective of whether the client was on the map that the mobs belong to; Which meant that we had to add logic to hide the mobs on clients that were on a different map.
- It also meant that we were sending unnecessary instantiation messages to some clients, therefore wasting bandwidth.
- On top of this because of the way RPCs work as standard, the control RPCs for the mobs were also sent to every client, meaning we were wasting more bandwidth and also had to filter them out for clients on other maps.
- Furthermore, when a map was entered for the first time in any session there was a noticeable hitch on all clients as all the mobs were created.
- Finally, we had to have some way to remove all the mobs once there were no more players in the map.
After playing around with this for a while it became clear that it was going to be unwieldy and inefficient, so we decided to rethink our approach from the ground up.
After a bit more experimentation, we decided that we wouldn’t instantiate the mobs as scene objects, and we wouldn’t give them a PhotonView component. Then rather than having a mob spawner placeholder in the map, we would just place the actual mob gameobject itself.
This neatly dealt with problems 1,2,3 and 4 from the above list, however, we now need a way for the mobs to synchronize over the network. Which is where PhotonNetwork.RaiseEvent comes into its own. Using RaiseEvent we can specify exactly which clients get sent messages, so we can send messages only to those clients on the same map.
We also added a script to the mobs which listens for RaiseEvent messages and filters the messages by the EventCode, so they can take the appropriate action based on the message received. We also needed a way to give each mob a unique identifier (think PhotonView.viewID) that will always be the same for the corresponding mob on all clients, but this turned out to be quite straightforward, by using the build index of the map they are on (multiplied by 100,000) added to the child index of the mob. This gives us more than enough mob unique IDs for any given map and means that if we send the ID in the network message, the receiving mob can tell if the message is targeted at it or not.
So problem 5 is also now dealt with, as the mobs are created and destroyed along with the map as players leave and enter it, which has no effect on other clients.
We also created a couple of helper classes to give us handy functions to send messages to players (all players, other players, only players in the same map, etc.).
Now there was another part of the puzzle to be solved. We needed a way to decide which client should be responsible for controlling the mobs in the map. It can’t just be the current MasterClient, because the MasterClient won’t have any knowledge of mobs in maps that it isn’t itself on. So we devised a method to have each map maintain its own ‘MapMasterClient’ which is automatically updated as players enter and leave maps, and it is this client that will control the mobs for that map.
With the addition of a couple of network messages to invoke functions to synchronize the state of all the mobs when players first enter a map the puzzle was complete. We now have a system that makes it easy to control different sets of mobs for different clients, depending on which map they are on, and which also minimizes the amount of network traffic required to handle this.
As a direct result of this, we’ve taken the decision to avoid using RPCs for anything in the game, and instead implement all network communication using RaiseEvent. Also the only thing we will use PhotonNetwork.Instantiate for is the player gameobject.
With 20 clients connected, all the players on the same map and moving around constantly along with the mob wandering logic, we are still only at around 250 messages per second per room, which is within our target. In practice, most players aren’t moving constantly and the party system will reduce movement data even further for clients in a party, but we need to allow for worst case scenarios.
We’re still a way off having a playable version, but things are progressing well and as soon as we have something available for testing I’ll post an update here.