uMMORPG - The #1 Unity MMORPG
- Quick Start Guide
- Architecture Overview
- A Beginner's Exercise
- How to add or modify a Player/Monster/Npc Type
- How to add a Skill
- How to add an Item
- How to change the Start Position
- How to add more Player levels
- How to change the ground or use a different Terrain
- How to add Environment Models
- How to change the Login Screen
- How to use another Database System
- How to modify Database Characters
- How to add an Account Database
- How to use another Scene
- How to add another attribute like Dexterity
- How to make a 2D MMORPG with uMMORPG
- How to connect to the Server over the Local Network
- How to host a Server on the Internet
- How to update your own modified MMORPG to the latest uMMORPG
- How to keep track of uMMORPG changes in detail
- Development Info
- Official Addons
- Community Addons
- Technology Choices
- Script Documentation
uMMORPG is a simple and powerful Unity MMORPG that contains all the Massive Multiplayer Online Role Playing Game core features, all built with Unity's own UNET Networking system.
uMMORPG is as simple as it gets when it comes to Unity MMORPG development because the Server and the Client are ONE, there is no more separation between them. Unity takes care of it all!
uMMORPG currently contains only about 4000 lines of clean, elegant and easy to understand source code. We designed it in a way that allows you to just run it and start hacking around in it immediately.
You can Download uMMORPG on the Unity Asset Store!
Feel free to take a look at our uMMORPG WebGL Demo first!
Quick start guide
- Install and open Unity 5.5.0p3 or newer.
- Import the Project from Unity's Asset Store and open the World scene file.
- Build & Run it for your operating system via File->Build Settings
Note: you can also press Ctrl+B to immediately build and run it afterwards.
- Select "Server & Play" in the Editor to start the Server and play on it (with the account entered in the Login Mask)
Note: a Dedicated Server lets you start a Server without playing on it at the same time..
- Enter a different account (any one is accepted right now) and press "Login" in the build to see the first two players in your MMORPG.
uMMORPG was designed for you to modify. Here is a quick overview for the code and architecture:
- The NetworkManagerMMO lives in the Hierarchy. It's used for login, character selection/creation and to start/stop the server.
- The Canvas holds all UI elements. Each element has a script that pulls in the local player's state for health, mana, etc.
- The Main Camera follows the local player.
- Players, Monsters, Npcs all inherit from Entity. Entity holds common properties like health, mana, damage, defense.
- NPCs don't do much except for standing around and offering quests and items.
- Monsters use a finite state machine for IDLE/MOVE/CASTING/DEAD etc.
- Players use a finite state machine for IDLE/MOVE/CASTING/DEAD etc too. There is also a localPlayer check. Local players react to input and send commands to the server.
- There are Prefabs for all players/monsters/npcs. Use the prefabs to modify them.
- ItemTemplate/QuestTemplate/SkillTemplate are what's usually stored in a database. Thanks to Unity ScriptableObjects we can edit them in the Inspector though. Templates can be found and modified in the Resources folder.
- Item/Quest/Skill structs are the actual instances that live in the player/monster/npc. They pull their static properties from the templates. They can have dynamic properties like cooldown, amount, etc.
- There is also NetworkTime which has to be in the Hierarchy for time synchronization.
General usage advice
If you are a developer then you can either use this project to build your own customized MMORPG on top of it, you can use it as a reference to learn the best way to implement specific MMORPG features or you can even take components out of it and add them to your own game.
We used lots of comments throughout the code, so if you want to learn more, simply take a look at the implementation of the function that you are interested in.
A Beginner's Exercise
Here is a simple exercise to get your feet wet without writing any code: try adding a new 'Universal Potion' item that restores health and mana. Give it a nice description, adjust all the properties and add an icon too. When you are done, give players a way to obtain it in the game world, e.g. by adding it to a monster's drops.
Note: the Tutorial section on items could be useful.
How to add or modify a Player/Monster/Npc Type
Adding or changing entities is not very difficult, but there are a few things to keep in mind. We will go through the process that we used to create the archer from our warrior, one step after another.
First of all, make sure that your 3D model is correctly imported with all the animations:
Drag the Warrior Prefab into the Hierarchy:
Then remove the 3D Model child object in the Hierarchy:
Now drag the new mesh from your model file (FBX etc.) into it:
If the model is facing into a weird direction, then rotate the model part:
We also rename it to '3D Model' for consistency:
Now it should look properly rotated in the Scene:
Every animated model needs an animation controller. So let's duplicate the Warrior's controller (CTRL+D) and then move it into the Archer model folder:
Double click to open the Controller in the Animator:
Select the Idle state, then drag the archer model's idle animation (it's a child of the model file) into the Motion slot in the Inspector:
Replace all other state's animations as well. Note that you may have to modify the Transition (the white arrows) Duration times later if your animations stop too abruptly or too slowly.
Now select our GameObject in the Hierarchy again and then drag the archer model's Controller and Avatar into the Animator component:
Each entity also needs a Collider. We could add it to the main GameObject, but it's usually a better idea to add it to the Pelvis bone, so that it follows the animation. Imagine a standing monster and a dead monster that lays on the ground. If we would attach the Collider to the main GameObject, then it would be in the default standing position at all times. If we attach it to the pelvis then it moves around with the Animation and the Collider will stand while the monsters is standing and it will lay on the floor when the monster is laying on the floor. So let's find the pelvis in the model's armature (bone structure) in the Hierarchy:
And then add a Capsule Collider to it in the Inspector:
Adjust the Collider properties in the Inspector until the Collider fits the model:
Players have the ability to equip and unequip items. If a player equips a weapon, then the item's 3D model should be shown in the player 3D model's hand. The code for this mechanic is already written, all we have to do is find the bone that should hold the weapon. 3D Artists usually include a "weaponMount" bone somewhere in the armature:
If there is no weapon mount yet, then find the left hand bone and add an empty "weaponMount" GameObject to it:
And then add the PlayerEquipmentLocation component to the weapon mount in the Inspector. We also set the accepted category to "EquipmentWeapon":
Our archer isn't allowed to equip a shield. But if it were, then we would have to add another PlayerEquipmentLocation with the accepted category "EquipmentShield" to the other hand as well.
Now we can adjust the Archer's Player component to our needs. We will keep it simple and only adjust the Equipment Types so that the archer is not allowed to use a shield, and is only allowed to use bow weapons. We will also drag the bow into the Default Equipment list:
So far, all we did was modify the Warrior prefab in the Hierarchy. We can now either save our changes to the Warrior Prefab, or create a completely different Archer Prefab from it.
If we want to save our modifications to the Warrior prefab, then we can simply press the Apply button:
Afterwards we can remove it from the Hierarchy again.
If we want to save our modifications to a new Archer prefab, then we first rename our Warrior in the Hierarchy to "Archer":
Then drag it into the Prefabs folder:
And then we drag that Prefab into the NetworkManager's Spawnable Prefabs list:
Afterwards we can remove it from the Hierarchy again.
How to add a Skill
Adding a new Skill is very easy. At first we right click in the Project Area and select Create->Scriptable Object and press the SkillTemplate button. This creates a new ScriptableObject that we can now rename to something like "Strong Attack". Afterwards we can select it in the Project Area and the modify the skill's properties in the Inspector. It's usually a good idea to find a similar existing skill in the ScriptableObjects folder and then copy the category, cooldown, cast range etc.
Afterwards we select the player prefab and then drag our new skill into the Skill Templates list to make sure that the player can learn it.
How to add an Item
Adding a new Item is very easy. At first we right click in the Project Area and select Create->Scriptable Object and press the ItemTemplate button. This creates a new ScriptableObject that we can now rename to something like "Strong Potion". Afterwards we can select it in the Project Area and the modify the item's properties in the Inspector. It's usually a good idea to find a similar existing item in the ScriptableObjects folder and then copy the category, max stack, prices and other properties.
Afterwards we have to make sure that the item can be found in the game world somehow. There are several options, for example:
- We could add it to a Monster's Drop Chances list
- We could add it to an Npc's Sale Items list
- We could add it to a Player's Default Items list
- We could add it to a Player's Default Equipment list
How to change the Start Position
The NetworkManager will always search for a GameObject with a NetworkStartPosition component attached to it and then use it as the start position.
uMMORPG has a startpos GameObject in the Hierarchy, which can be moved around in order to modify the character's start position.
How to add more Player levels
Each player Prefab has a Levels property in the Player component. If you want 4 levels, then set the Size to 4. Afterwards you can configure each level's stats, so that a player can have 100 health in level 1, 110 health in level 2, and so on.
How to change the ground or use a different Terrain
We can use just about any mesh or terrain as the ground element. In order to allow entities to move on it, we simply have to make it Static, enable the collider and then select Window->Navigation and press the Bake button in order to refresh the navigation mesh.
How to add Environment Models
Environment models like rocks, trees and buildings can simply be dragged into the Scene. They don't need a Collider, but they should be made Static so that the Navigation system recognizes them.
Afterwards we can select Window->Navigation and press the Bake button in order to refresh the navigation mesh. This makes sure that the player can't walk through those new environment objects.
How to change the Login Screen
uMMORPG uses Unity's new UI system, so everything can be modified easily. The Unity UI Manual has a lot of information on that topic.
If you want to modify the background, you can simply move the camera to another position, for example behind a tree or in front of a camp fire.
How to use another Database System
The Database.cs class is the the only place that has to be modified in order to use another database system like SQLITE or MYSQL.
We decided against using MYSQL for our database, simply because people shouldn't have to setup a whole MYSQL database just to run our Unity MMORPG.
The database currently uses XML files for several reasons, mainly because they are incredibly easy to work with and very fast to read and write. We also tried SQLITE a while ago, but it turned out that it's just far more complicated to deal with (transactions, locks, table initialization, SQL injection, DLL files for Unity, ...) and actually much slower than XML. Saving 1000 players took about 30 seconds with SQLITE and only 2 seconds with XML.
In the end, SQLITE and XML are both just files on our hard drive, so we might as well pick the easier solution.
Note: Item, Skill and Quest templates don't really need to be stored in a Database. Right now they are just ScriptableObjects that can be referenced in the game world, which makes development very easy.
How to modify Database Characters
uMMORPG uses the XML document format for its database. The database can be found in the Project's Database folder above the Assets folder or next to the executable in builds.
The database structure is very simple, it contains folders for accounts and each folder contains XML files for the account's characters. The files can be modified with any text editor.
Characters can be moved to a different account by simply moving them out of one folder and into another folder.
A character can be deleted by simply deleting the XML file.
How to add an Account Database
In a real MMORPG, we would most likely have a Content Management System that manages the Forum, News, Accounts and Item Shop. We could then modify the NetworkManagerCustom's IsValidAccount function to verify the login credentials. The function already contains some example code for HTTP-GET and MYSQL.
How to use another Scene
First of all, we have to understand that the game server can only handle one Scene right now, so our whole game world should be in that Scene. If you want to replace the current Scene, you can either just build on top of it or duplicate it and then modify what you want to modify. Unity doesn't have a Duplicate option for Scenes, but we can open the project folder with a file manager, duplicate the scene file and then rename it.
Note: having multiple Scenes at the same time and allowing players to teleport to them makes sense too. It's very likely that UNET will get a feature like that sooner or later, which would be the best solution. Right now we can't use UNET to connect from one UNET server to another UNET server, which makes transferring players impossible without some really weird workarounds, hence why we don't want to implement that feature just yet.
How to add another attribute like Dexterity
uMMORPG already has strength and intelligence attributes which can be upgraded after each level up. To add more attributes, simply take a look at the Player.cs script, find the Attributes section, duplicate one of the current attributes rename it.
You can then modify the Player's Health, Mana, Damage, Defense properties to include your attribute in the formula.
You can show your new attribute in the UI by first adding elements to the CharacterInfo panel in the Canvas and then updating them with the UIRefresh script where attributes can be found in the UpdateCharacterInfo function.
You will notice that the UI script uses Commands when the player asks the server to increase an attribute. The final step is to add one of those commands to the Player script. This is very easy again, since you can simply copy one of the existing attribute commands and modify it to your attribute.
How to make a 2D MMORPG with uMMORPG
uMMORPG is mostly networking code, it doesn't really matter if your game is 2D or 3D. For example, you could easily make your camera orthographic and make it look down on the player to have a 2D game already (which still uses 3D models).
If you want to make a real 2D MMORPG with sprites instead of 3D models, then you will have to keep a few things in mind:
- You should be comfortable with Unity's 2D features.
- You will have to replace all 3D models with Sprites and proper 2D Colliders.
- If you want 2D top-down movement with navigation, then you need a 2D navigation system like our Navigation2D asset. You also have to modify our NetworkNavMeshAgent component to make it work with NavMeshAgent2D then - which should be very easy.
- If you want 2D WSAD or sidescroller movement, then you will have to implement that yourself and also make it synchronize with the server.
- The networking code uses Vector3 for positions, which means that an unnecessary '0' is sent around all the time. You don't really have to worry about that, unless you run into bandwidth issues some day.
Other than that, items, quests, skills etc. are all the same.
How to connect to the Server over the Local Network
If you want to connect to the game server from another computer in your local network, then all you have to do is select the NetworkManager in the Hierarchy and modify the Network Info -> Network Address property to the IP address of the server.
Note: you also have to configure both computers so that they can talk to each other without being blocked by firewalls.
How to host a Server on the Internet
You should run uMMORPG in headless mode on a Linux server (recommended) or in batchmode on a Windows server.
For a detailed step by step guide, check out our UNET Server Hosting Tutorial. It recommends a hoster and explains the whole process step-by-step.
Note: it's sometimes useful to also show the log messages in the console. Here is how to do it on Linux:
./uMMORPG.x86_64 -logfile /dev/stdout
And on Windows:
uMMORPG.exe -batchmode -nographics -logfile log.txt
powershell -noexit Get-Content log.txt -Wait
How to update your own modified MMORPG to the latest uMMORPG
uMMORPG's code is likely to change a bit every now and then, so upgrading your modified MMORPG to the latest uMMORPG version could always break something in your project. We recommend to pick the latest uMMORPG version and then develop your own MMORPG with it without updating to the latest uMMORPG version all the time.
If however uMMORPG has a new feature or bugfix that you desperately need, then we recommend the following steps:
- Make a backup of your current project in any case
- Create a new Unity project and download the latest uMMORPG version
- Then either:
- Find the bugfix/feature that you want and manually copy it into your own MMORPG
- Or add your own MMORPG's content to the new Project again
Of course, you can always try to just update your current project and hope for the best / fix occurring errors. Just be warned that this might be stressful, because uMMORPG is a source code project. Conventional software can easily be made downwards compatible because the user never modifies the source code directly. But if you modify code that we change later on, then there can always be complications.
Note: we always try to fix all known bugs before releasing a new uMMORPG version, so you really won't have to update to the latest version all the time and can work with one version for a long time instead.
How to keep track of uMMORPG changes in detail
If you want to know every single line of code that was changed between versions, there are several ways to do that:
- Look at the file modified dates to see which ones were changed lately
- Use a any text comparison tool
- Use Git or similar version control tools
If you don't know how to use git, here is how you can see version changes with just a few mouse clicks:
- Create a new Unity project, download uMMORPG from the Asset Store
Put a .gitignore file into that your project folder to ignore some files that we don't need to track:
- Download and open SmartGit
- Select Repository -> Add or Create
- Select your project folder, press OK
- Press 'Initialize' in the next window
- Now you see the list of files that are new. Select all of them and press Commit and then click Commit in the next window, use the current uMMORPG version like 'V1.1' as the commit message
- Now if a new uMMORPG update is released:
- Update to the new uMMORPG version for that project
- Open SmartGit, select your repository on the left
- Now you see all the changed files in the middle
- You can click each file to see a comparison between the old and the new content
- Once you are done, select all files and Commit with 'V1.2', so that this version is now 'default'. Next time you will see all the changed files again.
Note: please don't put uMMORPG into a public repository on the internet. Git works perfectly fine on your local machine.
A list of planned features, bugs and requests can be found in our Official uMMORPG Thread on the Unity forums.
There are several official uMMORPG addons. Some of them were created by us, some of them are services by other people that we found to be the perfect fit for uMMORPG. You can decide which ones to use, we simply share the ones that we will use for our own MMORPG some day.
A Distribution & Patching Solution
Distributing an MMORPG is challenging. There are serveral things necessary:
- A download page where people can download and play it directly without much overhead
- A patcher that keeps the game up to date all the time
- A server that contains all the game files
- Uninstall options
- Binary difference support to only upload / download the latest changes instead of the whole game every time
- Cross Platform support for Windows, Mac, Linux, possibly WebGL and more
- Paid beta / early access is useful to cover the development costs
- Security & Reliability
Taking care of all those things by ourselves would be a full time job already. What we need is some kind of tool or service so that we don't have to worry about any of it.
The Steam platform is a great solution, but it's very difficult to get into it, especially while your MMORPG is still in development.
A great alternative is itch.io. It's like an open source Steam clone that everyone can use. We highly recommend using it for your own MMORPG too.
You can get started by registering at itch.io/developers. Upload your game and you will immediately get your custom itch.io URL like yourname.itch.io/yourgame, which you can share with your players already. People that don't want to redownload your game manually every time can simply use the itch.io client, which works just like Steam:
That's it, problem solved!
The uMMORPG community has created a few useful addons that you can implement to your own project.
PHP Account Registration
Kuroato create a simple PHP Account Registration script for uMMORPG:
XML Account Registration
tvirus06 create a simple XML Account Registration plugin for uMMORPG:
uMMORPG Thread XML Account Registration
ciao00 created a Warehouse for uMMORPG:
Youtube.com uMMORPG Warehouse
Designing an MMORPG is difficult, there are a lot of technology choices to make. We will explain the most important ones for uMMORPG:
Networking: When it comes to networking, we have several different choices like UNET, TCP/IP or external solutions. UNET is a really good choice for networking. Dealing with TCP Clients & Servers means lots of manual serialization, lots of packets and opcodes and dealing with networking issues. UNET takes care of all these problems. UNET is built directly into Unity, so it's really the most simple choice too, in comparison to external solutions. When it comes to UNET, we can either use SyncVars/SyncLists or use manual Serialization via OnSerialize and OnDeserialize. Using SyncVars/SyncLists saves us a lot of work when synchronizing things like item lists, skills or gold. Doing that manually with OnSerialize and OnDeserialize would require dirty bits and lots of serialization that we don't really want to worry about. We should also keep in mind that if we were to add something to our item type, we would always have to remember to serialize it as well. Luckily for us, Unity takes care of that automatically when using SyncLists. The only downside of SyncLists is the fact that we can only use simple types like int and float, or structs. We can choose between different Qos Channels in the NetworkManager. We use Reliable Fragmented for uMMORPG because packets should be reliable (we really do need the client to receive them). Synchronizing bigger structures like a SyncList of Items will often exceed the maximum packet size, hence why we need a fragmented channel type that can split big packets into smaller fragments.
NavMeshAgents vs. real Physics: we don't really use any real physics in our MMO. Instead we completely rely on the NavMeshAgent for movement. A lot of MMORPGs have problems with gravity, falling through the level or walking through thin walls. Using only a NavMeshAgent without any physics is the solution to all of those problems because the agent can only walk on the NavMesh. There is absolutely no way to fall through the map or to walk through walls. On a side note, this also means that we only have to synchronize the agent's destination once, instead of synchronizing a player's position every few milliseconds, hence we save a LOT of computations and bandwidth.
Simplicity vs. Performance: Our #1 goal when designing uMMORPG was simplicity. We prefer simple and clean code over highly optimized and overly complex code. A 100.000 lines of code MMORPG that can handle 5000 players at once is nice if you have enough people to work on that huge amount of code. But for indie developers a 5000 lines of code Project that only supports 1000 players at once is much more valuable.
OnGUI vs. UI: Unity's old OnGUI system was great, we used it for uMMORPG in the beginning. People started requesting the new UI, so we changed over to that one later. The new UI system is more complex, but it's more mobile friendly and has easier drag and drop support. When using the new UI, we always create a new script like UISkillbar and attach it to the UI component in the Hierarchy. This is a better architecture than one big UI script, or handling UI in the player class, etc.
Template / Addon / Plugin / Mod Systems: we want everyone to be able to build their dream MMO with uMMORPG. There are a lot of different approaches to game modifications and we put a lot of research into finding the perfect solution. Here is an overview about the different options:
- Mods: we could use Unity's Resources folder and Asset Bundles to allow users to modify the game after it was released. This is very cumbersome and strongly limits the modifications that can be made. Existing scripts and algorithms could hardly be modified. Examples are games like Skyrim where the game files can be modified with a special tool.
- Addons: we could also design uMMORPG as kind of an MMORPG engine that can be extended by addon scripts. So there would be a uMMORPG core that should never be modified by anyone, and there would be an addon class that people can use for their custom behaviours like PlayerInventory, PlayerQuests, NpcTrading, etc. The benefit of this system is that all addons are small, manageable components and that the core can easily be updated to newer versions. The downside of this system is that there will always be a limit to what can be modified. Even if we were to provide all kinds of hooks like OnDeath, OnRespawn, OnCastSkill, OnMove, OnHeal, etc., there would still be parts that simply can't be extended, like the Item, Skill and Quest properties (because they are structs), or the damage algorithm, or the finite state machines. There would also be UNET related issues because commands couldn't be called by anything that is not a player, so a lot of addons would need helper addons that sit on the player and call commands for them. There would also be additional cognitive overhead because the core would have to deal with addons in a lot of places. UNET is a good example for this system, where we can inherit from NetworkBehaviour and build our custom games with it.
- Templates: in this approach, the project is just a template that is meant to be modified in it's entirety. There is no special addon type, people can just hack around in the source files and change what they like to change. The downside of this system is that people can't just download addons from someone else, put them into their folder and run the game, since the modifications are often all over the place. The benefit of this system is that people can modify 100% of it. The simplicity is also worth mentioning, since it's a less complex system and there are no special addon types, hooks or callbacks. Quake 3 mods are a good example for this system, where people would hack custom behaviour directly into the source code.
As usual, we chose the simplest solution and made uMMORPG a template for your own dream MMO.
We will now explain each Script file in detail. Note that the documentation is parsed from the project's script files, so you might as well jump right into the project and learn even more.
Catches the Aggro Sphere's OnTrigger functions and forwards them to the Entity. Make sure that the aggro area's layer is IgnoreRaycast, so that clicking on the area won't select the entity.
Note that a player's collider might be on the pelvis for animation reasons, so we need to use GetComponentInParent to find the Entity script.
The Quest struct only contains the dynamic quest properties and a name, so that the static properties can be read from the scriptable object. The benefits are low bandwidth and easy Player database saving (saves always refer to the scriptable quest, so we can change that any time).
Quests have to be structs in order to work with SyncLists.
Note: the file can't be named "Quest.cs" because of the following UNET bug: http://forum.unity3d.com/threads/bug-syncliststruct-only-works-with-some-file-names.384582/
The Skill struct only contains the dynamic skill properties and a name, so that the static properties can be read from the scriptable object. The benefits are low bandwidth and easy Player database saving (saves always refer to the scriptable skill, so we can change that any time).
Skills have to be structs in order to work with SyncLists.
We implemented the cooldowns in a non-traditional way. Instead of counting and increasing the elapsed time since the last cast, we simply set the 'end' Time variable to Time.time + cooldown after casting each time. This way we don't need an extra Update method that increases the elapsed time for each skill all the time.
Note: the file can't be named "Skill.cs" because of the following UNET bug: http://forum.unity3d.com/threads/bug-syncliststruct-only-works-with-some-file-names.384582/
We developed a simple but useful MMORPG style camera. The player can zoom in and out with the mouse wheel and rotate the camera around the hero by holding down the right mouse button.
Note: we turned off the linecast obstacle detection because it would require colliders on all environment models, which means additional physics complexity (which is not needed due to navmesh movement) and additional components on many gameobjects. Even if performance is not a problem, there is still the weird case where if a tent would have a collider, the inside would still be part of the navmesh, but it's not clickable because of to the collider. Clicking on top of that collider would move the agent into the tent though, which is not very good. Not worrying about all those things and having a fast server is a better tradeoff.
People should be able to see and report errors to the developer very easily.
Unity's Developer Console only works in development builds and it only shows errors. This class provides a console that works in all builds and also shows log and warnings in development builds.
Note: we don't include the stack trace, because that can also be grabbed from the log files if needed.
Note: there is no 'hide' button because we DO want people to see those errors and report them back to us.
This component copies a Transform's position to automatically follow it, which is useful for the camera.
Saves Character Data in XML files. The design is very simple, we store chars in a "Database/Account/Character" file. This way we can get all characters for a certain account very easily without parsing ALL the characters.
benchmarks: saving 10 chars: 0.03s saving 100 chars: 0.45s saving 1000 chars: 1.7s
Sets the Rigidbody's velocity in Start().
Destroys the GameObject after a certain time.
The Entity class is rather simple. It contains a few basic entity properties like health, mana and level (which are not public) and then offers several public functions to read and modify them.
Entities also have a target Entity that can't be synchronized with a SyncVar. Instead we created a EntityTargetSync component that takes care of that for us.
Entities use a deterministic finite state machine to handle IDLE/MOVING/DEAD/ CASTING etc. states and events. Using a deterministic FSM means that we react to every single event that can happen in every state (as opposed to just taking care of the ones that we care about right now). This means a bit more code, but it also means that we avoid all kinds of weird situations like 'the monster doesn't react to a dead target when casting' etc. The next state is always set with the return value of the UpdateServer function. It can never be set outside of it, to make sure that all events are truly handled in the state machine and not outside of it. Otherwise we may be tempted to set a state in CmdBeingTrading etc., but would likely forget of special things to do depending on the current state.
Each entity needs two colliders. First of all, the proximity checks don't work if there is no collider on that same GameObject, hence why all Entities have a very small trigger BoxCollider on them. They also need a real trigger that always matches their position, so that Raycast selection works. The real trigger is always attached to the pelvis in the bone structure, so that it automatically follows the animation. Otherwise we wouldn't be able to select dead entities because their death animation often throws them far behind.
Entities also need a kinematic Rigidbody so that OnTrigger functions can be called. Note that there is currently a Unity bug that slows down the agent when having lots of FPS(300+) if the Rigidbody's Interpolate option is enabled. So for now it's important to disable Interpolation - which is a good idea in general to increase performance.
[SyncVar] GameObject doesn't work, [SyncVar] NetworkIdentity works but can't be set to null without UNET bugs, so this class is used to serialize an Entity's target. We can't use Serialization in classes that already use SyncVars, hence why we need an extra class.
We always serialize the entity's GameObject and then use GetComponent, because we can't directly serialize the Entity type.
This class adds functions to built-in types.
Useful for Text Meshes that should face the camera.
In some cases there seems to be a Unity bug where the text meshes end up in weird positions if it's not positioned at (0,0,0). In that case simply put it into an empty GameObject and use that empty GameObject for positioning.
The Item struct only contains the dynamic item properties and a name, so that the static properties can be read from the scriptable object.
Items have to be structs in order to work with SyncLists.
The player inventory actually needs Item slots that can sometimes be empty and sometimes contain an Item. The obvious way to do this would be a InventorySlot class that can store an Item, but SyncLists only work with structs - so the Item struct needs an option to be empty to act like a slot. The simple solution to it is the valid property in the Item struct. If valid is false then this Item is to be considered empty.
Note: the alternative is to have a list of Slots that can contain Items and to serialize them manually in OnSerialize and OnDeserialize, but that would be a whole lot of work and the workaround with the valid property is much simpler.
Items can be compared with their name property, two items are the same type if their names are equal.
Defines the drop chance of an item for monster loot generation.
Saves the item info in a ScriptableObject that can be used ingame by referencing it from a MonoBehaviour. It only stores an item's static data.
We also add each one to a dictionary automatically, so that all of them can be found by name without having to put them all in a database. Note that we have to put them all into the Resources folder and use Resources.LoadAll to load them. This is important because some items may not be referenced by any entity ingame (e.g. when a special event item isn't dropped anymore after the event). But all items should still be loadable from the database, even if they are not referenced by anyone anymore. So we have to use Resources.Load. (before we added them to the dict in OnEnable, but that's only called for those that are referenced in the game. All others will be ignored be Unity.)
A Item can be created by right clicking the Resources folder and selecting Create -> Scriptable Object -> ItemTemplate. Existing items can be found in the Resources folder.
The Monster class has a few different features that all aim to make monsters behave as realistically as possible.
States: first of all, the monster has several different states like IDLE, ATTACKING, MOVING and DEATH. The monster will randomly move around in a certain movement radius and try to attack any players in its aggro range. Note: monsters use NavMeshAgents to move on the NavMesh.
Aggro: To save computations, we let Unity take care of finding players in the aggro range by simply adding a AggroArea (see AggroArea.cs) sphere to the monster's children in the Hierarchy. We then use the OnTrigger functions to find players that are in the aggro area. The monster will always move to the nearest aggro player and then attack it as long as the player is in the follow radius. If the player happens to walk out of the follow radius then the monster will walk back to the start position quickly.
Respawning: The monsters have a respawn property that can be set to true in order to make the monster respawn after it died. We developed the respawn system with simplicity in mind, there are no extra spawner objects needed. As soon as a monster dies, it will make itself invisible for a while and then go back to the starting position to respawn. This feature allows the developer to quickly drag monster Prefabs into the scene and place them anywhere, without worrying about spawners and spawn areas.
Loot: Dead monsters can also generate loot, based on the lootItems list. Each monster has a list of items with their dropchance, so that loot will always be generated randomly. Monsters can also randomly generate loot gold between a minimum and a maximum amount.
Draws the agent's path as Gizmo.
We use a custom NetworkManager that also takes care of login, character selection, character creation and more.
We don't use the playerPrefab, instead all available player classes should be dragged into the spawnable objects property.
Updates the UI to the current NetworkManager state.
Contains all the network messages that we need.
Synchronizing an entity's name is crucial for components that need the proper name in the Start function (e.g. to load the skillbar by name).
Simply using OnSerialize and OnDeserialize is the easiest way to do it. Using a SyncVar would require Start, Hooks etc.
UNET's current NetworkTransform is really laggy, so we make it smooth by simply synchronizing the agent's destination. We could also lerp between the transform positions, but this is much easier and saves lots of bandwidth.
Using a NavMeshAgent also has the benefit that no rotation has to be synced while moving.
- Teleportations have to be detected and synchronized properly
- Caching the agent won't work because serialization sometimes happens before awake/start
- We also need the stopping distance, otherwise entities move too far.
The default NetworkProximityChecker requires a collider on the same object, but in some cases we want to put the collider onto a child object (e.g. for animations).
We modify the NetworkProximityChecker source from BitBucket to support colliders on child objects by searching the NetworkIdentity in parents.
Note: requires at least Unity 5.3.5, otherwise there is IL2CPP bug #786499.
Clients need to know the server time for cooldown calculations etc. Synchronizing the server time every second or so wouldn't be very precise, so we calculate an offset that can be added to the client's time in order to calculate the server time.
The component should be attached to a NetworkTime GameObject that is always in the scene and that has no duplicates.
The Npc class is rather simple. It contains state Update functions that do nothing at the moment, because Npcs are supposed to stand around all day.
Npcs first show the welcome text and then have options for item trading and quests.
All player logic was put into this class. We could also split it into several smaller components, but this would result in many GetComponent calls and a more complex syntax.
The default Player class takes care of the basic player logic like the state machine and some properties like damage and defense.
The Player class stores the maximum experience for each level in a simple array. So the maximum experience for level 1 can be found in expMax and the maximum experience for level 2 can be found in expMax and so on. The player's health and mana are also level dependent in most MMORPGs, hence why there are hpMax and mpMax arrays too. We can find out a players's max health in level 1 by using hpMax and so on.
The class also takes care of selection handling, which detects 3D world clicks and then targets/navigates somewhere/interacts with someone.
Animations are not handled by the NetworkAnimator because it's still very buggy and because it can't really react to movement stops fast enough, which results in moonwalking. Not synchronizing animations over the network will also save us bandwidth.
We implemented a chat system that works directly with UNET. The chat supports different channels that can be used to communicate with other players:
- Local Chat: by default, all messages that don't start with a / are addressed to the local chat. If one player writes a local message, then all players around him (all observers) will be able to see the message.
- Whisper Chat: a player can write a private message to another player by using the / name message format.
- Guild Chat: we implemented guild chat support with the /g message command. Please note that the guild feature itself is still in development, so the message will not be read by anyone just yet.
- Info Chat: the info chat can be used by the server to notify all players about important news. The clients won't be able to write any info messages.
Note: the channel names, colors and commands can be edited in the Inspector by selecting the Player prefab and taking a look at the PlayerChat component.
A player can also click on a chat message in order to reply to it.
Takes care of Drag and Drop events for the player.
Used to find out where an equipped item should be shown in the 3D world. This component can be attached to the shoes, hands, shoulders or head of the player in the Hierarchy. The acceptedCategory defines the item category that is accepted in this slot.
Note: modify the equipment location's transform to mirror it if necessary.
Colors the name overlay in case of offender/murderer status.
This class is for bullets, arrows, fireballs and so on.
Saves the quest info in a ScriptableObject that can be used ingame by referencing it from a MonoBehaviour. It only stores an quest's static data.
We also add each one to a dictionary automatically, so that all of them can be found by name without having to put them all in a database. Note that we have to put them all into the Resources folder and use Resources.LoadAll to load them. This is important because some quests may not be referenced by any entity ingame (e.g. after a special event). But all quests should still be loadable from the database, even if they are not referenced by anyone anymore. So we have to use Resources.Load. (before we added them to the dict in OnEnable, but that's only called for those that are referenced in the game. All others will be ignored be Unity.)
A Quest can be created by right clicking the Resources folder and selecting Create -> Scriptable Object -> QuestTemplate. Existing quests can be found in the Resources folder.
Saves the skill info in a ScriptableObject that can be used ingame by referencing it from a MonoBehaviour. It only stores an skill's static data.
We also add each one to a dictionary automatically, so that all of them can be found by name without having to put them all in a database. Note that we have to put them all into the Resources folder and use Resources.LoadAll to load them. This is important because some skills may not be referenced by any entity ingame (e.g. after a special event). But all skills should still be loadable from the database, even if they are not referenced by anyone anymore. So we have to use Resources.Load. (before we added them to the dict in OnEnable, but that's only called for those that are referenced in the game. All others will be ignored be Unity.)
Skills can have different stats for each skill level. This is what the 'levels' list is for. If you only need one level, then only add one entry to it in the Inspector.
A Skill can be created by right clicking the Resources folder and selecting Create -> Scriptable Object -> SkillTemplate. Existing skills can be found in the Resources folder.
Drag and Drop support for UI elements. Drag and Drop actions will be sent to the GameObject with the 'handlerTag' tag.
This component can be attached to moveable windows, so that they are only moveable within the Screen boundaries.
All Player UI logic in one place. We can't attach this component to the player prefab, because prefabs can't refer to Scene objects. So we could either use GameObject.Find to find the UI elements dynamically, or we can have this component attached to the canvas and find the local Player from it.
Instantiates a tooltip while the cursor is over this UI element.
Adds window like behaviour to UI panels, so that they can be moved and closed by the user.
This class contains some helper functions.