Behind League's New Missile System

Greetings, skill-shot savvy summoners, Brian "Penrif" Bossé here to talk to you about some new technical underpinnings behind missiles. We've been rolling out a new implementation of missiles in the past few months (as mentioned in recent patch notes). If we did our jobs right, you didn't notice the change while playing. Even so, we're very excited about what this unlocks for the game, and I wanted to share some deeper context behind it.

The old system was a tangled mess and almost impossible to work with, while the new one lets us do silly things like this easily:

Ashe Heart

Not that we're making Ashe's Volley do that, mind you. It's just something I threw together in a couple hours while on prescription painkillers to show what can be done without much effort. Or ability to focus. Or pain.

 

Missiles: Surprisingly Complex

To give you an idea of what we were dealing with, let me show you a diagram that represents the per-frame execution flow for missiles in the former system:

(I'd let you zoom & enhance but just like in electronic music - the words don't matter)

While not as complex as real missile guidance systems, this complicated flow makes changes risky just by its nature. Briefly, what we're looking at here is a hierarchy of missile types, each expressing a different set of missiles. Functionality from these different types is sometimes shared, sometimes duplicated—and frequently confusing. This diagram breaks out those missile types horizontally, with similar functionality color coded. For example, if you wanted to know how missiles handle collision tracking, you'd have to examine all of the pink code—conveniently located all over the place.

The top box in the diagram comprises what we call "base missile," the root class of the hierarchy. These are the simplest missiles: they fly towards a position or a unit and activate only when that movement is done. Auto-attacks and turret shots are in this set, as are the missiles for abilities like Corki's Q and Ziggs' R. Their flow is quite straight-forward in that they track their target, then evaluate where the missile should move, then see if they've hit the target.  Easy-peasey; lemon-squeezy.

The next big box in the diagram comprises "line missiles," encompassing most of what players would call a "skill shot" missile - things like Lux's Light Binding, Ashe's Volley, or almost everything Ezreal does. There are some surprises in this category as well. Like Yasuo's windwall: it's a line missile. The irony should hurt a little.

Line missiles have significantly more complicated behavior than base missiles. For example, they can hit things as they travel, return to their caster (like Draven’s ult), or hug the ground (like Zyra’s Grasping Roots). Lumping all of those different behaviors into one entity is enough sin straight out the gate, but line missiles are further compounded by being a child of base missiles. As such, line missiles inherit all of base missile's functionality by default, but use it differently. This put us in the frustrating situation where changes to base missiles brought the risk of breaking line missiles in complicated ways.

The small groups at the bottom are for our three circle missiles: Diana's Q and W, and Ahri's W.  They behave very similarly to line missiles, but travel in a circle. They derive from base missile and bear a striking resemblance to line missiles in everything other than movement. In the course of making this diagram I found some bug fixes in line missile that hadn't made their way over to circle. Duplicating code duplicates its bugs too; such is the pain brought by the copy-pasta demon.

A viable alternative for visualizing the flow of this missile system

 

In short, the missile system in League of Legends was unnecessarily complex, tangled inward on itself, and rather fragile. As missiles represent a large design space for the game, we clearly had to make them easy to work with.

The method we chose to accomplish this was to nuke the system and rewrite it from scratch. It's the only way to be sure.

 

Clean Slate

The rewrite had four core principles:

  • Like functionality should be co-located in code to ease understanding.
  • Dependencies between different functional groups should be minimal and explicit.
  • Reasoning about what missiles do in general should be easy.
  • Special case behavior should be cleanly separated from the main execution path.

The team started out by identifying the core concerns of a missile, and planned around writing components to address those concerns independently. Apparently I love lists of things, so here's a list of what missiles do:

  • Movement : missiles go places.
  • Collision : missiles hit things.
  • Target tracking : missiles go towards things that can move.
  • Script interfacing : missiles need designers to tell them what to do.
  • Visibility : missiles get seen by some things but not others.
  • VFX and sound : y'all gotta see and hear what's going on.

We started the rewrite with that list, having a component for each item that we could construct on the fly or even swap mid-flight. As we got further into working within that setup, we continually re-evaluated these splits and made adjustments when the abstractions no longer served a purpose. For example, we rolled target tracking logic into the movement code as we found their concerns closely tied, and we rolled the scripting interface into the common Missile base class as we found no need to differentiate it between different missiles.

In the end, we have a system that looks like this:

We call the first diagram "Rainbow Vomit," and this one's just made of rainbows.

Rainbows make an excellent building material

What this means for League

So when we get down to brass tacks this work provides two primary benefits. First, missile code should no longer scare our programmers, with defects being much easier to chase down and fix. That's already paid some dividends with the resolution of some defects considered unfixable for a long time.

For example, we've been scratching our heads at some videos showing missiles sailing right through their targets. While a very rare event, this defect impacted the game deeply when it happened. With no way to reproduce the problem and no real leads on where to go with it, the only thing to do was to put it in the back of our heads and carry on. However, having all of missile collision brought together to one place and cleaned up, a couple scenarios that would cause collision to fail became easy to spot. After cleaning those up, we've got a high level of confidence that missiles will hit what they're supposed to.

To save myself some flame, some situations where missiles fly through a unit probably still exist. But I suspect those come from errors in the synchronization of the position of the units rather than the collision detection of the missile.

While having happier programmers and fewer defects is good and all, the second and bigger benefit here is that we're able to do new things with missiles much more quickly. Without having to set up a whole new missile class in order to have a missile that just moves differently, we're free to make them do all sorts of crazy stuff. I leave you with some examples we put together to demonstrate. These do not represent production ideas: they’re just the outcome of having engineers with a little free time and a touch of the sillies.

Draven loop-de-loop

We had some fun putting a loop-de-loop into auto-attacks, and it looked pretty rad on Draven

 

And with a slight modification to the center spline control point, we add some love to Varus

 

Hexagons are the bestagons

Hexagons are the bestagons

Go forth and shoot all the things \o/

  • Brian “Riot Penrif” Bossé
  • Anoop “Noopmoney” Kamboj
  • Kevin “kbox” Borer
  • Vasiliki “vLemon” Siakka
  • Abe “Riot Meyea” Nguyen
  • Jessica “Safelocked” Nam
  • Richard “Riot Asyrite” Henkel

 

Mixing salt dough by Jimmie is licensed under CC BY 2.0
Spaghetti alla chitarra by Jessica Spengler is licensed under CC BY 2.0
Rainbow by Steve Snodgrass is licensed under CC BY 2.0

Posted by Brian Bossé