One of the biggest technical upgrades we made to the WoW Companion App for Shadowlands is the ability to support multiple expansions. We know it’s a feature many players have been asking for, and today we wanted to share some of the challenges and decisions involved in making this kind of large-scale tech change.
A Little History
The Companion App was originally designed to support one expansion: Legion. Leading up to Battle for Azeroth, we made the decision to prioritize several major back-end improvements, including the ability for the app to share code with the PC client, which allows us to take advantage of many of the features and tools that are available to the main game’s UI (user interface) that the app previously did not have access to. The tradeoff was that we weren’t able to build out the kind of framework that would be necessary to support more than one expansion at a time—so we swapped the single expansion that the app supported from Legion to the latest expansion, Battle for Azeroth.
We know that players were disappointed to lose access to Legion content within the app when we made the switch, so we made a commitment with Shadowlands to support more than one expansion at a time. In addition, the mobile team was eager to give the community what they wanted and bring back Legion content.
We briefly discussed the option of supporting only partial functionality from previous expansions to maximize our ability to work on Shadowlands content, but we quickly decided that we wanted to restore as much functionality as possible. We also knew that with the arrival of Shadowlands, players would have the option to level their alts through Legion via Timewalking Campaigns, and we wanted to make sure those players would have full mobile support for their level-up experience.
In what turned out to be a bit of a mixed blessing, most of the Legion-specific code was still present in the app, and we still had the original Legion assets in our source control, which made re-adding Legion content the perfect test for our new multi-expansion framework. And while there was not a lot left to implement, it did mean we had some work to do to cleanly separate Legion and Battle for Azeroth from each other.
Creating the multi-expansion framework required three major pieces:
The app needed to clear out any data for your current expansion content and request data for a new expansion from the server.
We needed to split expansion-specific UI code apart to keep functionality separate.
We needed to break up expansion-specific assets to minimize the app’s resource usage.
When the Legion Companion App was originally developed, most of the server code that handled requests from the app was purpose-built to only return Legion data. When the time came to switch over to Battle for Azeroth, we changed those functions to return Battle for Azeroth data instead. This time, however, we needed the ability to fetch data for any expansion selected, which meant going through all of the server functions that respond to mobile requests and changing them to accept parameters specifying which expansion to fetch data for. For example, the request for Follower data needed to be able to specify a Follower type (e.g. Legion follower or Battle for Azeroth follower), and the request for World Quests needed to include which zones it wanted quest data for.
The app itself also had a number of places that were extended to specifically support Battle for Azeroth. In Legion, the app specifically requested your Order Hall Resources; for Battle for Azeroth, we simply added War Resources to that message. Rather than continue to add more currencies to the same message for Shadowlands, we chose to port the entire currency system in the PC client to the new shared-code framework that was developed for Battle for Azeroth. That means that every expansion is now able to automatically request its own currency types instead of having to manually specify which currencies we want.
Much of the code that was written to support the Battle for Azeroth expansion was written on top of the existing Legion code, which made it challenging to understand what code was actively in use. For example, the Battle for Azeroth Followers list still had the ability to show Armaments, except the Armaments button was hidden so that code was never called. Expansion functionality for Legion and Battle for Azeroth were similar enough that this wasn’t a huge problem, but we knew that with the new features we were adding for Shadowlands, it wasn’t going to be feasible to maintain that kind of architecture. If code that was being shared with Legion and Battle for Azeroth suddenly needed to support things like Soulbinds and Covenants, it was only going to become more complex than it already was.
Instead, we went through each script, figuring out which functionality was shared and which was specific to one expansion. We split expansion-specific code into subclasses or completely new components, keeping only the shared, base functionality in the parent classes, and made new game objects for each expansion using those new subclasses. Now, even though they’re backed by the same system and share the majority of their functionality, only the Legion Missions list needs to have a Combat Ally button, and the Shadowlands Adventures list is able do things like get rid of the two-tab design that Legion and Battle for Azeroth used and display enemy portraits instead of mission type icons.
Another significant behind-the-scenes change we made to the app for Shadowlands is how we generate database files; the Companion App now builds its database code and data files using the same pipeline as PC. One place where that came in handy is in the code that determines which map each World Quest gets displayed on. The app originally made that decision using manual logic for each map ID, which wasn’t a problem when it only needed to support Argus and the Broken Isles. As we added more zones for Battle for Azeroth, however, it quickly became cumbersome. Rather than continue maintaining that code for Shadowlands, we were able to easily pull in the UI Map database tables we needed to make the system completely dynamic, converting a large chunk of code that used to have to handle zones for all three expansions into a single, slim function that can automatically handle any World Quest without any expansion-specific logic.
One big difference between mobile and PC development is that on mobile, we have to keep in mind that players may be using the app on a cellular connection with a data cap or poor connectivity. We need to ensure that players have a fun experience without encountering frustration while using the app regardless of their internet connection, and one of the ways we do that is by limiting how much data players need to download in order to use the app.
Each expansion has a lot of asset files that can add up very quickly, and going from one expansion in the app for Legion and Battle for Azeroth to three expansion support for Shadowlands meant we would be tripling the amount of data needed by the app. However, many assets are only used by one expansion, such as hi-res world map textures, mission location backgrounds, and follower and enemy portraits, so we made the decision to separate those assets into different bundles for each expansion. By doing that, the app only needs to load assets into memory that are used by the currently active expansion, reducing the amount of memory the app uses.
We also decided early on to split each expansion into its own game scene, which let us maintain a clean separation of functionality between expansions. The Legion game scene doesn’t need a Covenant Callings page, and the Shadowlands game scene doesn’t have a Troop Shipments window. Just like the changes we made to the code, this makes it much easier to grasp what’s happening in the app at any given time. Splitting both the assets and the game scenes like this means that each expansion is self-contained, which also paved the way for some more user-friendly download options.
We wanted to give players more control over when they need to download assets for the app. You can choose to download everything all at once when you’re on a stable connection, or you can choose to download assets for other expansions later so you can get into the app faster. And if you choose not to interact with a specific expansion, you’re never forced to download assets for it. At 30-40 MB per expansion, that’s a significant amount of data to be able to skip downloading!
Splitting our assets per expansion required changes to two pieces of our pipeline: our asset curation tool needed to know which assets belonged to which expansion, and our asset bundle creation tool needed to be able to group those assets together when it built the final bundles. The asset curation tool was another aspect that was heavily specialized to support Legion and then Battle for Azeroth, so we had to rewrite it to be more expansion-agnostic. The new version of the tool goes through the list of expansions with content the Companion App might want (by iterating over the GarrisonType enumeration) and outputs assets for each expansion into separate folders. Modifying the curation tool to use the full list of expansions also means that it will automatically pick up any values added to that list in the future, making it easier to add new content to the Companion App without having to continually make updates to our tools.
Our asset file creation and runtime loading make heavy use of Unity’s AssetBundle variants feature, which lets the app treat different versions of a single bundle file as if they were the same bundle. The bundle creation pipeline makes a different variant of each bundle for each expansion, based on the expansion-specific folders output by the curation tool. Those asset bundles are swapped out at runtime whenever you switch expansions in the app. Because all variants of a bundle can be treated the same, any code that needs to fetch Follower portraits, for example, can do so without worrying about which specific set of Follower portraits is currently loaded. This insulates our feature code from worrying about the nuts and bolts of asset loading and unloading; each expansion can pretend like it’s the only expansion in the app.
Into the Shadowlands
Whether you’re playing the new Adventures content in Shadowlands or leveling a fresh alt through Legion, we’re incredibly excited to be able to give you more World of Warcraft content on the go! We hope this has given everyone an interesting peek into the kind of work we do and some of the decisions that go into designing frameworks that can support not only current content but also whatever World of Warcraft may have in store for the future.