noobtuts

Unity uMOBA Documentation

uMOBA

Introduction

uMOBA is a simple and easy to understand Multiplayer Online Battle Arena Demo project, similar to Dota and League of Legends. It was developed with Unity's UNET Networking system.

What we provide is a learning project that focuses on the core MOBA features like Entities, Combat, Items & Skills while avoiding the overhead around it.

This project is as simple as it gets when it comes to MOBA development because the Server and the Client are ONE, there is no more separation between them. Unity takes care of it all!

uMOBA currently contains only about 2500 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.

Download

You can Download uMOBA on the Unity Asset Store!
Feel free to take a look at our uMOBA WebGL Demo first!

Features

uMOBA contains all of the MOBA genre's core features:

Quick start guide

 

  1. Install and open Unity 5.4 or newer.
  2. Import the Project from Unity's Asset Store and open the Lobby scene file.
  3. Press Play in the Editor, select "Play and Host", press "Join".

General usage advice

If you are a developer then you can either use this project to build your own customized MOBA on top of it, you can use it as a reference to learn the best way to implement specific MOBA 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.

About the Network Game Lobby

The lobby scene was entirely developed by Unity can be found on the Asset Store.

We added a few modifications to allow hero selection. We suggest not updating the lobby asset yourself. If you notice a new lobby update, simply let us know and we will take care of updating it in uMOBA.

HLAPI-Pro

HLAPI-Pro is a drop-in replacement of UNET’s high level networking API (HLAPI). HLAPI-Pro works exactly the same, but is a lot more stable and has a lot of bugfixes that are really important for MOBA games.

We highly recommend using HLAPI-Pro along with uMOBA.

How-Tos

How to add a Monster Type

The best way to add a new Monster type is to drag an existing Monster into the scene, modify it as needed and then drag it into the Prefabs folder to create a new one. Afterwards you can select the monster spawner objects in the Hierarchy and then drag the monster prefab of your choice into it.

How to add an Npc Type

The best way to add a new Npc is to duplicate an existing Npc, modify it as needed and then drag it into the Prefabs folder to save it permanently.

How to add a Player Type

Just like Monsters and Npcs, adding a different player type isn't hard because all we have to do is duplicate the existing one. We can duplicate a Unity Prefab like this: drag the Prefab into the Hierarchy, rename it, drag it back into the Project Area to create Prefab, then remove both of them from the Hierarchy again. Afterwards we can modify the parts in the new Player Prefab that we want to modify and then select the LobbyManager in the Hierarchy and drag the new player Prefab into Game Player Prefab property.

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 items'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:

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.

uMOBA has a PlayerSpawn GameObject in the Hierarchy, which can be moved around in order to modify the character's start position.

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 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 LobbyManager 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 update your own modified MOBA to the latest uMOBA

uMOBA's code is likely to change a bit every now and then, so upgrading your modified MOBA to the latest uMOBA version could always break something in your project. We recommend to pick the latest uMOBA version and then develop your own MOBA with it without updating to the latest uMOBA version all the time.

If however uMOBA has a new feature or bugfix that you desperately need, then we recommend the following steps:

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 uMOBA 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 uMOBA 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.

Documentation

We will now give a basic overview about uMOBA's architecture and design decisions and then 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.

Technology Choices

We will begin by explaining our technology choices:

123Skill.cs

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 editing in the Inspector.

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/

AggroArea.cs

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 cause the monster to be selected.

Note that a player's collider might be on the pelvis for animation reasons, so we need to use GetComponentInParent to find the Player script.

Barrack.cs

The Barrack entity type.

Base.cs

The Base entity type, used for buildings that can be destroyed.

CameraScrolling.cs

Moves the camera around when the player's cursor is at the edge of the screen

CameraZooming.cs

Zooms in with the mouse wheel.

DefaultVelocity.cs

Sets the Rigidbody's velocity in Start().

DestroyAfter.cs

Destroys the GameObject afer a certain time.

Entity.cs

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.

EntityTargetSync.cs

[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.

Extensions.cs

This class adds functions to built-in types.

FaceCamera.cs

Useful for Text Meshes / Canvases 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.

FogOfWar.cs

The Fog of War plane in 3D should be slightly above the ground, so that trees etc. aren't darkened, only the ground. Entities will never stand above it, because no entities should be visible where there is fog of war anyway.

Note: it finds the local player, which means that it's a client sided effect and we don't have to worry about performance issues on the server.

Note: we use a Unlit/TransparentBlur shader to blur the cutout circles.

Item.cs

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.

ItemTemplate.cs

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.

An Item can be created by right clicking the Resources folder and selecting Create -> uMOBA Item. Existing items can be found in the Resources folder.

LobbyHookMOBA.cs

This component transfers data from the lobby player to the gameobject player.

Monster.cs

The Monster class has a few different features that all aim to make monsters behave as realistically as possible.

MonsterSpawner.cs

Used to spawn monsters repeatedly. The 'monsterGoals' will be passed to the monster after spawning it, so that the monster knows where to move (which lane to walk along).

MouseoverOutline.cs

Draws an outline around the entity while the mouse hovers over it. The outline strength can be set in the entity's material. Note: requires a shader with "_OutlineColor" parameter. Note: we use the outline color alpha channel for visibility, which is easier than saving the default strenghts and settings strengths to 0.

NavmeshPathGizmo.cs

Draws the agent's path as Gizmo.

NetworkMessages.cs

Contains all the network messages that we need.

NetworkName.cs

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.

NetworkNavMeshAgent.cs

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.

Notes:

NetworkProximityCheckerTeam.cs

A simple proximity check around ourselves isn't enough in games where we have multiple units in our team (MOBA, RTS etc.). There we want to see everything around us and everything around each of our team members.

We also 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. Note: visRange means 'enemies in range we should use the same visrange for everything

NetworkTime.cs

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.

Npc.cs

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.

Player.cs

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[0] and the maximum experience for level 2 can be found in expMax[1] 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[0] 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.

PlayerChat.cs

We implemented a chat system that works directly with UNET. The chat supports different channels that can be used to communicate with other players:

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.

PlayerDndHandling.cs

Takes care of Drag and Drop events for the player. Works with UI and OnGUI. Simply do: FindObjectOfType().OnDragAndDrop(...);

PlayerSpawn.cs

Used to set a spawnpoint's team, so that the correct players can be spawned here.

Projectile.cs

This class is for bullets, arrows, fireballs and so on.

SkillTemplate.cs

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.

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 -> uMOBA Skill. Existing skills can be found in the Resources folder.

StartServerIfHeadless.cs

Automatically starts a dedicated server if running in headless mode. (because we can't click the button in headless mode)

TextMeshCopyText.cs

Copy text from one text mesh to another (for shadows etc.)

TextMeshFadeAlpha.cs

Fade the text mesh color's alpha value over time.

TextMeshHealthBar.cs

HealthBar via TextMesh of "_". This is a workaround for the following Unity bug: https://issuetracker.unity3d.com/issues/destroy-gameobject-time-causes-unity-to-crash-in-sharedgfxbuffer-unshare-sharedgfxbuffer-star

Tower.cs

The Tower entity type. Automatically attacks entities from the opposite team.

Utils.cs

This class contains some helper functions.