Unity Multiplayer Concurrent Users: Own Solution vs Existing Frameworks
Leander van Grinsven, 500794728
Table of Contents
- Research Criteria
- P2P vs Client-Server
- TCP vs UDP
- Existing Frameworks
- My Own Solution
- Mirror Solution
- Performance Comparison
- A Look to the Future
In this research, I am diving into the creation of multiplayer in Unity with my very own multiplayer framework written from scratch. I will then make a comparison between my implementation and an existing framework already available to Unity developers. I will take you through the process of choosing the best contender to my own implementation that fits the following type of game:
The framework has to support a MMORPG style game where you connect to a world with other existing players doing their own thing. The clients have to communicate their position and rotation to start with and most importantly, support as many concurrent players as possible. I will then compare the performance and accuracy of both implementations to see if making your own Multiplayer framework is worth making for any developer who has thought about making a Multiplayer game with their own framework.
I have to look at different types of Multiplayer that fits best to my criteria such as making it Peer to Peer or a Client-Server connection and if the connection uses TCP or UDP.
For this research, both implementations need to match the following criteria:
- Able to support at least 50-100 concurrent users by simulating it with AI units.
- Update six (6) times per second. This is known as tickrate. Information is shared between server and clients.
- Send and receive the position and rotation of each unit or player.
When both implementations match the criteria, the performance is tested to see which is better by looking at the following:
- Position and Rotation accuracy between client and server.
- If any delay exists when both clients move around.
- Computer resources such as CPU and RAM.
- Network usage by looking at the Bytes per second that the clients and server send and receive.
The conclusion is based on the results of the performance tests and the process into creating a multiplayer framework from scratch.
P2P vs Client-Server
A P2P (peer to peer) network is a decentralized collection of computers that has been established to exchange information (such as file documents, songs, movies, software, etc) with everyone or only certain users. (Harris Andrea – Network Training)
In a P2P network, all computers on the network are considered equal, with each workstation offering access to resources and data.
This means that each node in the P2P network model can both request for services from the other peers or offer services to the other peers. Each node can be both a client and a server.
Peer-to-Peer can be huge networks in which computers may interact with each other and share what is on or linked to their machines with other people.
A popular use of a P2P network which exchanges files is a Torrent Network where users download from and upload to other users that want to have the same files.
The client-server model structure is a centralized network in which the server hosts, provides, and maintains the majority of the client’s data and services.
In this network model, a central server is a must and all the clients (computers) are connected to the central server for retrieving data or using its services.
The server acts as a middle point of the network. Servers wait for requests from clients to show up before responding.
P2P and Client-Server network each have their own pros and cons. For example P2P scales very well and can be more stable compared to Client-Server. The cons of it however are that cheating is easier and getting strong security is more difficult to achieve.
Client-Server is easier to implement, cheating is more difficult and easier for developers to prevent since the server is acting as the man in the middle making sure what the clients are doing is allowed, and there is less latency if it is properly implemented. The downside is that you have costs to run the servers which is more difficult for small scale and/or free games.
My choice is going for a Client-Server model as for this comparison I can host my own server and most of the MMO’s in existence use this model as it is easier to implement and makes cheating more difficult.
TCP vs UDP
TCP (Transmission Control Protocol) is a method to define how to establish and maintain a connection. TCP is a connection oriented protocol which means a connection is made and maintained until all connection ends have finished their exchanges. The protocol provides error detection such as transmitting packets again if the other side did not receive them and did not send a reply of confirmation. TCP also reorders packets into the correct order.
UDP (User Datagram Protocol) is a simpler and connectionless protocol. Unlike TCP, UDP does not provide error detection or packet reordering. The advantage of UDP is that it is faster and more efficient with less latency which is what is most important in games.
The best choice for this framework is UDP as we want low latency and the error detection can be done in the framework itself.
When looking at popular multiplayer frameworks, we see two major contenders which are Mirror and Photon. There are others but are much less known so more difficult to get familiar with in a short amount of time.
Mirror is a high level Networking library for Unity, compatible with different low level Transports. Mirror is for small indie games & large scale MMOs, made by the developers of uMMORPG and Cubica, used in major titles like Population: ONE. Mirror is optimized for ease of use & probability of success (Mirror Network – Unity Asset Store).
Mirror is a continuation of UNet with many improvements and bugfixes. The model is client-server based, the client and server are within one project which means the same code is used for both instead of being separate code bases. Mirror is also known to be used for large MMO based games with many concurrent users playing together. A great example of this is shown in a Youtube video uploaded by noobtuts (23 jan, 2019) where there are approximately 480 players connected at the same time:
Mirror is a great framework to compare with as it fits what the type of game I want to build. Mirror is open-source and free to use and edit based on your needs which is a big advantage.
Photon is another popular mutliplayer solution that supports both client-server and P2P connections. Currently Photon has three different implementations and they provide a comparison of each type:
The best version is Fusion which supports more users than the older versions of Photon.
A major limitation that you have to pay based on the amount of concurrent users you have. Below is an image of the pricing they have for their Fusion framework:
The server side of your game is hosted by them which could be another limitation based on what your needs are. Since I want to test at least 100 CCU, this means I would have to pay at least 95 dollars for twelve (12) months which is not at all what I need in terms of duration.
Since I have to pay for Photon while Mirror is open-source and free, the choice is simple. With Mirror, I can perform my comparison much easier and cheaper. Only when you want to deploy your game, the comparison becomes more even as with Mirror you need to rent your own server which could be costly.
My Own Solution
With my criteria and choices set, I know what my framework is going to look like. The model is as follows:
- Client-Server. There will be server which clients can connect to.
- The connection is UDP. I want the framework to be efficiënt which means UDP is the best choice.
- Client Authority. The client makes the movement and sends the information to the server. There will be client side interpolation.
- AI and weapons are server sided. Both type of objects happen on the server. The server sends the relevant information to all clients to make sure it remains consistent.
- The Server will be built on Unity and be headless to take advantage of what Unity provides while also reduce the computer resources required to run it.
I start by opening a socket using IPv4 and UDP. I define the buffer size which is the data block we send and receive. I also need an EndPoint which contains the IP Address that we either send to or receive from.
Two things are happening in ClientConnect. First I start the socket connection with _socket.Connect. I pass the IP address and port so that the socket knows how to find the server. The second is to start the socket receiver which is located in the Receive function.
What the Receive function does is listening to any messages that may come in on both a client and server. This happens asynchronously with the BeginReceiveFrom function. It receives an array of bytes that needs to be converted to a string. That string is Serialized with Json that we need to Deserialize in a separate script called the MessageProcessor in the ReceivedMessage function.
The data that is being exchanged are class objects which contain multiple properties such as what type of object it is which is an enum value, the position and rotation of the object which are Vector3’s. The objectCommand is for future player abilities to for example, fire weapons or to spawn new AI units.
This class is used for both sending and receiving to make it easier to control what is sent and received.
What is shown here is a string and a function. The string prepares the message that is sent to the client or server. I collect all the GameObjects that need their data shared. It then Serializes the string with JsonConvert.SerializeObject. The last thing it does it returns the string.
The ReceiveMessage function Deserializes the message into a list of class objects containing the data. It then calls the ObjectHandling function that updates the objects with new data.
Spawning & Movement
The ObjectHandling function finds the GameObjects it needs to update. If it has found the object and with the correct tag, it will send the new position and rotation to the object so it can interpolate between the old and new position and rotation. If it cannot find the object, it will spawn the object into the scene. So when a new AI spawns on the server, the client will spawn the new AI and start updating it every tick.
The script responsible for updating the object is ObjectMovement. What it does is interpolate the current position and rotation to the new updated values between each tick which at this point is six (6) per second. The speed of the object is calculated by looking at the distance between the current and new values divided by the tickrate. This makes sure that no matter the distance it will always reach the correct position and rotation without sending the velocity over the network which lowers the efficiency.
The last script is for the movement of the AI. This script only exists on the server as the AI on the client side uses the ObjectMovement script. What this script does is set a random Vector3 position between ten (10) and twenty (20) seconds that the AI will move towards. The AI slowly rotates towards the direction of the Vector position. The result is that the AI will fly/wander around which will be used to simulate players and units moving around.
Mirror is a pre-existing framework that is free and open-source. You can find the Asset on the Unity Asset Store. You then have to download and import it through the package manager.
One thing you would notice is how many scripts and documentation it contains. To know what everything does will take some time. Making it work however is really straightforward with plenty of documentation online.
The first step is to create an object and add the NetworkManager script to the object. This object manages everything needed to have a client-server connection. I defined the Server Tick Rate to 6 (30 by default), added a player prefab with Auto Create Player enabled. The last thing needed was adding the AIShip prefab to the Registered Spawnable Prefabs. You need to do this with any objects you wish to spawn while playing. Think about any AI units and bullets you want to shoot.
Every object I want to sync and have data shared between clients and server require the Network Transform script. I made sure that the send interval is set to 0.166 to make it equal to the six (6) per second tick rate used by my own implementation. The only negative part I see of this is that the tick rate is more easily configurable in the Network Manager which you can easily set to update six (6) times per second. In the Network Transform you need to set an annoying value of 0.166 to get the same result.
The Position and Rotation are synced. The rest of the values are left on default.
Spawn & Movement
I was able to use the same AI Movement script from my own implementation in Mirror which also shows how easy Mirror is. What I did have to make was a new AI Spawner to support the built-in functionality from Mirror.
To spawn an object such as an AI unit or bullet to shoot, you only have to Instantiate the object and then call the Spawn function in the NetworkServer class. In the script above I create ten (10) AI units by pressing the Spacebar.
This was all that was required to get it working to match what was required. Learning the basics of Mirror took less than six (6) hours with no prior knowledge.
Now that both my own implementation and Mirror are working, the next step is to see if making your own Multiplayer is worth spending time and money doing. I do this by comparing the performance of both by looking how many resources they use of my computer.
The method of testing this is by simulating a high amount of concurrent users (CCU) which we see in MMO games. Since I have no access to 100 computers each with a client and another computer that runs the server, I simulate these users by having at least 100 AI units that move and rotate around randomly and then checking the CPU, RAM, GPU, and network usage of both. There will be two (2) clients connected to the server to see how long the delay is between them when moving each client manually.
To check CPU, RAM and GPU I will use the Windows Task Manager and the Unity Profiler. For the Network usage, I will be using the Resource Monitor which tracks the amount of Bytes per Second a process sends and receives.
Both implementations have their servers running in headless mode. This means no graphics and just a console with basic information and log entries.
The difference is between my multiplayer and Mirror is vast. My framework is using a lot more CPU power than Mirror with an average of about 12% while Mirror stays around 0.1%. One thing to note is that Mirror seems to be using more RAM ever so slightly but is nothing compared to the CPU usage. Why my headless server is using so much more is definitely worth looking at in the future as something must be going wrong in at least one of my scripts.
When looking at the network usage of both, we see that my multiplayer transfers more than three times more data. Both servers send data of 100 AI Units but Mirror seems to be a lot more efficient in packing the data and sending it to the clients. Another thing worth noting is that the data being sent to the server is much smaller in Mirror which means the way I send data is lacking any form of efficiency. A large part is due to the buffer size which is 32*1024 bytes which is beyond excessive in my opinion. Exceeding the buffer size causes the client to crash and I did not expect to have to expand it to that size to support 100+ clients.
I have two (2) clients open for both implementations, and each client is running in the Unity Editor.
The clients seem to have an opposite result in terms of resource usage. Mirror requires a lot more CPU and GPU power to run while my multiplayer needs more RAM which is almost double that of what Mirror is asking for. The same models, prefabs and movement scripts were used so that rules out anything that is not related to the actual multiplayer and the connections between the server and clients. The reason Mirror uses more CPU and GPU can be that it has way more functionality and contains more scripts than my version.
In the Unity Profiler we can see that the blue line which represents the CPU usage for scripts is much thicker than my multiplayer. This means that Mirror is doing a lot more in the background. The difference in gameplay is not noticeable at all and both still have a higher FPS than refreshrate of my 120hz ultrawide monitor at 1440p and running two (2) instances at the same time with a server on top of that.
Was it worth making a multiplayer framework from scratch? In this case, absolutely not. What I had to spend weeks on, could be done on Mirror in a single day.
There are three important sections to look at:
Mirror is a clear winner on all fronts and “wipes the floor” with my version. This is largely due to the quality of my code and the time I have had for development. Mirror has had years of development with countless improvements which has given it a strong lead over my framework.
Ease of Use
Mirror is really easy to learn and you can pretty much learn most if not all you want to do in your game within a single week. There is plenty of documentation online and forums with users that have come across problems and their solutions that you will most likely run into as well.
Making your own framework from scratch can in some instances be easier because you know exactly what your code does and what you want it to do.
Time & Money
Mirror is free and open-source, so the only cost is from developing scripts to expand the gameplay of your game and to host your game on a server.
Making your own multiplayer takes way more time to develop which increases your costs, and you also have to host your game on a server. However, it does give you more freedom and not be limited by outsiders that are contributing to Mirror or waiting for certain bugs to be fixed.
If you work in a company with a limited budget and a game that Mirror can easily facilitate, then you really should not be thinking about making something from scratch. If your game has a way higher budget with functionality and gameplay that really don’t work in Mirror, first look at other existing frameworks such as Photon. Only after you have exhausted other means can you consider making multiplayer from scratch.
A Look to the Future
I still want to build my own Multiplayer game based on Star Trek. I am not sure if the Multiplayer framework is the one I built or Mirror. If I choose to go with Mirror, I won’t have to fix any major bugs and be able to focus on adding more features to my game.
If I were to continue with the one I built myself I would have to do the following:
Right now the way and amount of data it sends and receives between the server and clients is not efficient or usable at all. I will have to look at why my server is using so much more CPU power compared to Mirror. There is also a delay between each client that is unacceptably high.
Multiple optimizations like compressing the byte array before sending can save on bandwidth and making the buffer size dynamic based on the number of objects it needs to transfer can save a massive amount.
With more time, the framework can be properly optimized and I have a number of ideas on how to improve large parts of it.
Fix a lot of bugs
There are a number of bugs such as, the position and rotation interpolation of the AI and other clients is far from smooth and jitters quite a bit.
A huge bug that currently exists is that the buffer size of the data it sends and receives is way too large and makes the clients crash if the amount of data exceeds the buffer size. In order to make it possible to have 100+ players, the buffer size needs to be bigger than 16*1024 bytes. For this test I changed it to 32*1024 bytes to prevent it from crashing the clients. Hopefully I can fix this when I do a rewrite of important scripts causing these issues.
Adding more features
If the major issues have been fixed, the next step would be to add new features such as the ability to fight other players and AI with weapons.
I would also want to make it so players can choose different ships to fly and that the AI would become more diverse. As of right now, it is limited to a specific ship.
Mirror Networking – Open Source Networking for Unity. (n.d.). Mirror Networking. Retrieved April 10, 2022, from https://mirror-networking.com/
Multiplayer Game Development Made Easy | Photon Engine. (n.d.). Photon Engine. Retrieved April 10, 2022, from https://www.photonengine.com/
Json.NET – Newtonsoft. (n.d.). Json.Net. Retrieved April 10, 2022, from https://www.newtonsoft.com/json
Andrea, H. (2021, June 27). Comparison of “peer-to-peer” vs “client-server” Network Models. Networks Training. Retrieved April 10, 2022, from https://www.networkstraining.com/peer-to-peer-vs-client-server-network/
Socket Class (System.Net.Sockets). (n.d.). Microsoft Docs. Retrieved April 8, 2022, from https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket?view=net-6.0
Jiju, N. (2020, November 10). TCP/IP vs UDP: What’s the Difference? Colocation America. Retrieved April 9, 2022, from https://www.colocationamerica.com/blog/tcp-ip-vs-udp
UDP vs. TCP. (2008, October 1). Gaffer On Games. Retrieved April 10, 2022, from https://gafferongames.com/post/udp_vs_tcp/
Unity Multiplayer – What are the pros and cons of available network solutions/assets. (n.d.). Unity Forum. Retrieved April 5, 2022, from https://forum.unity.com/threads/what-are-the-pros-and-cons-of-available-network-solutions-assets.609088/
Noobtuts. (2019, January 23). uMMORPG 480 CCU *Worst Case* Stress Test – 2019–01-23. YouTube. Retrieved April 2, 2022, from https://www.youtube.com/watch?v=mDCNff1S9ZU