← Back to projects
Major Project · 2019

Niloc

For my final year university project, our team built Niloc: a 2–4 player local multiplayer game with elements similar to the MOBA genre (think DOTA 2). Rather than picking a character with a fixed kit, players choose their abilities from four selectable sets and mix and match to whatever playstyle suits them.

  • C#
  • Unity 2018.3 (HDRP)
  • Odin
  • DOTween
  • Rewired
  • FMOD
  • Cinemachine
Niloc gameplay: two players fighting with ability effects in the arena

The Team

Core team

Outsourced

I led as project manager and programmer, working in C# alongside Odin, DOTween, Rewired, FMOD, Cinemachine, and a handful of minor plugins. The game was built on Unity 2018.3's High Definition Render Pipeline.


The Ability Framework

This was the single most important task on the project. It needed to be extensible (supporting a growing roster of new abilities), flexible, so abilities weren't restricted in unforeseen ways, and easily balanceable, since any multiplayer game lives or dies on how quickly designers can tune numbers.

After some research, I found an approach structured around Scriptable Objects aimed at MOBA-style games (a close match for what we needed) and adopted it as the base of our ability system.

Code screenshot of the abstract Ability ScriptableObject base class

The class is abstract, forcing every ability to derive from it, with properties used to enforce better encapsulation.

Player implements IPlayerProperty IPlayerProperty interface Ability references IPlayerProperty 12 Concrete Abilities RockBarrage, Heal, Dash + 9 more
Player implements IPlayerProperty; Ability only ever holds a reference to that interface, never to Player directly, holding to the interface segregation principle and making it far easier to debug and control exactly what data passed between the two. The twelve concrete abilities all inherit from Ability.

The Abilities

Twelve abilities shipped in the final game, covering damage, crowd control, buffs/debuffs, and support.

Rock Barrage ability

Rock Barrage

Cast a barrage of rocks towards the enemy.

Ice Shard ability

Ice Shard

Cast an ice shard that damages and slows the enemy on impact.

Fire Breath ability

Fire Breath

Cast a damaging cone in front of you while speeding you up.

Dash ability

Dash

A generic dash ability: what more do you want?

Heal ability

Heal

Cast a burst heal that heals both you and your teammates.

Shield ability

Shield

Cast an aura around yourself that protects you from all damage.

Lightning Beam ability

Lightning Beam

A shot of concentrated lightning for massive damage at great distance.

Electric Stream ability

Electric Stream

A deadly burst of electricity in a cone at medium range.

Ice Wall ability

Ice Wall

Spawns a wall of ice that deals damage on spawn.

Charge ability

Charge

Charge forward, dealing damage and knocking back enemies.

Fire Heart ability

Fire Heart

Cast an aura of fire around yourself, dealing massive damage.

Siege ability

Siege

Channel arcane power, then cast a mighty strike upon your foes.


The Object Pooling Framework

Abilities needed a pooling system, both for performance and for a consistent, aesthetically clean way to spawn and despawn effects, for example, waiting for a particle effect to finish before disabling it, and correctly resetting state when it's re-enabled.

PoolManager MonoBehaviour singleton ObjectPool one per poolable type Stack<PoolObject> keyed by identifier PoolObject returned & activated
PoolManager owns every ObjectPool asset, and each one keeps its inactive instances in a stack keyed by identifier, so GetObjectFromPool can hand back an existing instance instead of instantiating a new one, and ForceReturnToPool pushes it back onto the stack once it's done.
Code screenshot of the pool object inheritance structure (PoolObject, TimedPoolObject, PoolParticle, Projectile)

Projectiles and particle effects shared functionality and both returned to the pool automatically after their lifetime, callable from anywhere via the PoolManager.

Code screenshot of an ability requesting an object from the PoolManager

This made for a strong framework: abilities could be prototyped and iterated on very quickly, which made balancing far easier.

Unity inspector showing the Fire Breath ability's tunable variables

Every variable was exposed in the inspector, letting us rapidly test and balance changes on the fly.

The ability system turned out flexible enough to switch render pipelines and swap the player for physics-based vehicles, as shown above, though some physics-heavy abilities like Charge needed extra tuning to behave.


Ability Framework: Evaluation

I'm happy with how the framework turned out overall, though one change would have made it significantly better. Right now, each player instances their own copy of an ability on spawn, so edits to the ability asset don't immediately affect players at runtime. A better approach would look up the shared ability asset directly, separating its logic and enabling real-time balancing, a simple-sounding refactor that would have reset most of the tuned inspector variables, so it stayed on the "next time" list.


Tool Development: Complex Collider

I built a tool called Complex Collider to make the asset pipeline less tedious for the team, inspired by Unreal Engine's FBX static mesh pipeline, where a mesh can be turned into a primitive/convex collider automatically.

Three-part comparison: manual collider mesh, the generated collider mesh, and the final in-game result

Colliders were originally built by hand, slow and tedious for the designers. The tool takes a mesh and generates all of its colliders automatically, named identically to Unreal's convention, straight from the mesh's own name.


We'd originally hoped to release on Steam but decided to stay on itch.io for now: you can play Niloc here. A code showcase is also up on GitHub, though the full project isn't public since it depends on commercially licensed third-party plugins.

← Back to projects