The Future of League's Engine
Hiya folks, Brian "Penrif" Bossé, your local friendly Tech Lead of League here. I'm taking some time in between matches of TFT to wax philosophic about game engines and how we on League make decisions around what direction to take our custom game engine. Join me on a moderately long look at one dimension of game engine design, where League currently exists on that dimension, and where we're taking the game from there.
There are many different axes one could choose to evaluate a game engine against - performance, platform support, and graphics fidelity are all useful metrics. The particular dimension I want to look at in this article is how an engine captures the complexity of the games implemented on it.
Flavors of Complexity
In the long, long ago, there wasn't much complexity to capture in games. They were very simple, executed by small teams - often just one person - and over a small period of time. As machines got more capable and player's expectations rose, complexity of implementation rose as well. The industry obeyed the guidance set forth by Daft Punk and made harder goals, better tools, faster iteration speeds, and stronger... something. Anyway, our industry has responded to that complexity in many ways, but few have been as effective as introducing higher level programming languages - scripting of various shapes and sizes.
Where an engine draws the line between its high-performance core and the high-expression script dictates where its complexity is captured. You can go engine-heavy and have loads of complexity in the high-performance core, or have a light core and push a bulk of the complexity out to script, or really anywhere in between. Let's talk briefly about the extremes, then go on to how this relates to League.
In the engine-heavy case, the purpose of higher-level language scripting looks more like configuration than programming. Objects and methods that are exposed to the script side are highly abstracted and their interactions are well-controlled. If the engine's concepts map well to the game's needs, this setup can work wonderfully - programmers manage the complexity of objects and their interactions, and game designers get a playground of robust toys to configure to their whims. If the constructs don't match well though, you get into trouble - either your game is hobbled in important ways or the complexity leaks out into script.
At the engine-light extreme, you have a super small central core that exposes very basic objects to scripting. The complexity of how to use them is left up to the higher-level language, and most things you'd recognize as gameplay systems are implemented mostly - if not fully - in the scripting language. Only the parts that are performance-critical get pulled into the core, like physics integration and rendering. This extreme can work well too with clean engineering principles applied to the script codebase. The game's complexity can be tamed better with higher level programming constructs, while not sacrificing too much performance in the process.
Having established the extremes of engine-heavy and engine-light, and that they both work well and make for happy game-makers, here's the bad news - League's solidly between the two.
This is not a happy place. Our core engine exposes both high-level constructs and low-level primitives to the scripting engine. The scripting language itself is simple, providing the very basics, which is what you'd expect from a language supporting an engine-heavy setup, but we've built complex systems on top of it anyway. The origin of exactly how we got here is lost history from before my time here at Riot. Best I can tell, the engine started out very close to the engine-light world, but in the pursuit of shipping many things quickly, enough compromises were made to no longer classify that way.
As it stands today, there's high complexity everywhere, and we'd do better to move towards either extreme, so let's have a look at what those transitions could look like.
League Goes Engine-Light
Were League to go engine-light, there's a couple big transitions that would need to happen.
First, we'd have to make the scripting language far more robust. Continuing with a domain-specific language when you're looking to wrangle the complexity of general game development is a bit silly; the domain of "game development" is large enough that going specific isn't valuable. We'd be much better served by adopting a proven, robust language like C# or Python. Translating mechanically from the existing language to one of those wouldn't be hard - their functionality is a superset of the existing language - but that code would look awful goofy not taking advantage of having basic language constructs like generalized for loops. A significant amount of rewriting would be prudent to make the codebase look like it belongs in the new language.
The other major transition that would need to occur is that we would need to pull many of the game systems currently in C++ out to the higher level language, leaving only the high-performance core. Simple actions that happen at high frequency in League would stay behind, while functions that carry larger complexity at a lower frequency would benefit from moving out of the engine core. For example, minions recalculate their path often, and there are many of them. It's a simple calculation that the game does a lot, so it should stay behind. On the other hand, champion spell casting is a complicated setup - lots of different routes around instant-cast or cast-while-moving or channeling - there's significant complexity there. That happens infrequently since most abilities have substantial cooldowns, so the cost of moving that complexity out is low. Off it goes up to higher level language land.
These transitions would leave us with a game implementation that's much quicker to work on - code can be cleanly hot-reloaded like many of our assets currently are, increasing programmer iteration time substantially. Having a fully-featured programming language would reduce the pain in finding "creative" solutions to expressing gameplay that doesn't quite fit the engine's constructs. For the price of a bit of execution speed, we'd gain development velocity, a well supported development environment for all aspects of programming, and a majority of our complexity contained in one ecosystem instead of being split. That's a trade-off well worth making.
League Goes Engine-Heavy
Today, League has a substantial amount of complexity implemented in script. Spell queuing implementations, manual animation control, and VFX control are some common examples. Object classification logic is often implemented on top of the classifications already present in the engine - for example, checking if a minion is actually a Zyra plant. Walking the engine-heavy path means pulling a bulk of this complexity into the engine. In turn, the engine would expose rich, high-level nouns and verbs with simple but sufficient logical flow constructs to tie them together. This setup would allow non-technical implementers to have mastery over the output of the engine without having to swim in much complexity. Engineers would have more complexity to manage in the engine, but it would all be in our development environment instead of requiring developers to constantly swap between code and script to understand an implementation fully.
Going engine-heavy should see a drastic reduction in the size and scope of scripts. Our scripting language would be likely to contract, with low-level constructs being replaced with higher order abilities. For example, when we added a construct for triggering an event when a unit enters a geometric area, the need for the ForEachUnitInTargetArea construct went down significantly. Taken to full fruition, many of those low-level queries and low-level actions would become redundant and should be removed from production output. They'll still have a place in prototyping, where waiting for a new construct to be built in engine is prohibitively slow.
What would be left in script is a distilled expression of the unique gameplay being realized. Game engineers would spend most of their programming time on the C++ side, tending the garden and creating new nouns and verbs to be orchestrated by script. This shift of focus would allow designers to worry much less about implementation details while maintaining their ability to fine-tune experiences. It would take considerable effort to move to this world, but it too would create valuable trade-offs.
Which Way Do We Go?
Both of these routes have strong benefits over our existing situation, so which should we aim at? If I could wave a magic wand and have everything happen instantly and perfectly, I'd wave it at engine-light. Having a clearly separated home for "needs to be fast" and "needs to be expressive" is very powerful, and since magic is involved we'd have perfect discipline to move systems across that boundary exactly when performance becomes more important than expressiveness and visa-versa. It'd be great.
Unfortunately, I have no such wand. If you do, please let me know. I would like to buy it from you.
Back in reality, our primary consideration should be the skillset of our developers and how well they map to the two alternatives. We have a group of strong C++ engineers, some of whom have solid experience in higher level language development, but not all do. We have a lot of non-technical designers and artists who have learned how to manipulate the current scripting solution, but who would be totally lost in even an immaculately curated Python environment.
Both of these point towards going engine-heavy. We could choose to work against that and retrain everyone to be successful in an engine-light world, but that's extremely costly in time and effort. It would also likely cause some very talented developers to head for more comfortable ground instead. Since both outcomes are considerably better than what we have now, there's no reason to swim up current. We'll play to our strengths and head towards an engine-heavy world.
So, as it happens, I don't communicate technical direction first via the public blog. The reasoning described in this article has been the direction that the Gameplay group on League has been walking for a couple years now. This has lead to some shifts on how we approach projects, for example how the Champions team encapsulated the complexity of Sylas into higher-level constructs and dramatically simplified the script implementation involved.
The movement towards engine-heavy and explicitly away from engine-light will provide us with a more secure footing for the increasing complexity of League. If wrangling this kind of complexity shift sounds like your cup of tea, and you're good at slinging the C++ around in pursuit of fun, we look forward to hearing from you; check out available positions here.
Anyway, that's all I got on the game engine philosophy front for today. If you have questions, war stories, or funny cat pictures, please post them below. See you on the Rift, or on whatever we're calling the TFT board.