This blog post shows the online multiplayer arena game that I made with Unity in my Game Engineering III class here at the Master of Entertainment Arts & Engineering program at University of Utah.
(All of the art assets and UIs are provided by the teacher or downloaded from Asset Store)
This class focuses on different aspects of gameplay engineering. I will list them all first and then dive into some of the interesting topics.
- SOLID principles.
- Core gameplay loop, which consists of a master game object and a round system.
- Player HUD.
- Character abilities.
- Abilities upgrade.
- Inventory and items with upgrades.
- Utility-based bot AI.
- Online multiplayer functionality.
Below is a short demo video of the game.
This was an incredibly fun and challenging task to tackle. Before I dived in, I spent some time going through the source codes of Epic Games Action RPG project to understand how their gameplay ability system works.
Basically, the structure that I later on created looks like below.
This structure is pretty modular and scalable, I could create a few common effects and just assemble them accordingly to generate desired ability behaviors.
The abilities allow two different types of targeting, point target (execute ability on a certain point), and directional target (for abilities like projectile that goes along a direction). I can also easily specify them in the abilities prefab.
At this point, a single ability prefab object looks something like this.
However, after the scope of the game grew bigger and we got more and more new abilities. This system proved out to be “not as scalable as I expected”. And I had to create many effects and classes just for special cases. Such as for the meteor ability that summons a meteor that damages unit on impact and then keeps rolling for a certain distance. I had to extend and create a class called AirDropAndRollAbility just for it to work!
Next, I’ll talk about our bots AI!
Since this kind of arena games (such as MOBA) have incredibly complicated and highly dynamic world states. I decided to go for utility-based AI. However, before starting to write the utility functions, I needed to solve the problem of world interfacing first. And I needed a system that is robust enough that won’t be affected by the number of players, but can still reflect them and provide sufficient information. I will talk about my approaches below.
To provide interfacing between units, abilities, and the game world, I created an influence map class to represent some world states such as threat and intensity. The abstract class declares the basic functions as shown below.
I decided to use two types of influence map, one for threats and one for units intensity. Threats can be contributed by the abilities happening in the arena and other units, while units intensity only comes from units.
The image below shows the values of the influence map when the debug flag is on, you can see that the closer it gets to each unit, the higher the values. And there is a falloff when furthering away from the units.
Despite how complicated the world states can be, the actions that AI can take are actually very few.
Below is my AI utility manager for the AI players. You can see that there are only movement utility and attack utility. I also use simple inertia and decay it with time to make sure that I don’t get back and forth bouncing behaviors.
For each ability that a player has, it will calculate its own utility and provide it to the player to calculate the attack utility. Each ability’s utility script extends from AAbilityUtility, and has custom parameters and functions. Take fireball for example. If it is in cooldown, then returns 0. Otherwise, it will try to find the closest unit, and try to predict its future position judging by its current velocity. Fireball will also take the lava into account and see if it can push the unit into the lava.
Utility functions started to get really tricky when I started taking more aspects into account. Such as shield (block all damage within a certain time period, definitely overpowered) and tether ability (tether two units together and limit their movement) since these can drastically change the tactics and strategy. The AI I created at the end of this assignment could only use three abilities for those reasons.
Online multiplayer with Photon Unity Networking (PUN)
For the online multiplayer functionality, I decided to use the Photon Unity Networking classic package from Unity Asset Store since I’ve used it before. It also has detailed documentation and a huge community.
Below is the lobby scene, players can change their names, create rooms, and join existing rooms. After a room is created, the room owner (master client) can toggle the private/public setting and start the game. The number next to player’s name is their pings.
After the game starts, there are some tricky problems that need to be solved, and decisions need to be made.
For the units and players spawning, it would make sense to check the number of players and spawn their units accordingly. However, I was wondering if I could have them all in the scene by default to bypass the process of linking each player controller with its unit (since they both hold a lot of references to other entities in the scene).
When I ended up doing was having all the units in the scene and remove them later on according to the players count, I have to make the unit prefab’s Photon view transferable instead of fixed to make it work, and let the master client handle the correct linkage between players and units. In retrospect, that probably created more trouble instead of saving, but it was an interesting experience. I also needed to manual tell the shop manager to update its target to give the abilities/items according to the local player after they’re correctly linked. For a while in my game, no matter who buys an ability, it will all go onto the same unit (master), which was probably “slightly unbalanced”.
Another problem with buying and upgrading abilities/items is binding with units. After something was purchased, it needed to be bound with its owning unit. I added an RPCBind into the IBindUnit interface for this purpose.
Damaging unit with collision was pretty tricky since it involves a lot of Unity events and custom events. I struggled with it for quite a while since GameObject information cannot be serialized (because each client has its own copy, of course). However, after realizing that I can just specify them with PhotonView ID, everything became much easier.
Despite how fun it was, the hardest part in creating the online functionalities was probably that every time I needed to test something, I have to make a build to play with the in-editor version, which accumulated up to quite a lot of time cost. Besides that, debug print has interestingly become really useful in this process compared to breakpoints since it’s hard to test the game under a multiplayer environment while constantly triggering breakpoints.