Processes to manage an engine upgrade

If you have a Blueprint project, upgrading the engine should be very straightforward: there is no code to update, you simply load your project in the new engine version, see what asset errors come up, and fix what the engine is complaining about.Upgrading a code project is more involved, and is what we will cover here. The key is how heavily you’ve customized the engine. If you’ve left the engine unmodified, this should again be very straightforward to upgrade; however the effort increases quickly if you’ve made significant modifications to the engine. We fell somewhere in the middle, and the following practices helped us manage the upgrade process.

Managing engine divergences

It helps to mark up and track your changes to the engine, as this will simplify the process of merging code conflicts. For Nightingale, we follow this process:

  • All changes to the engine are reviewed by a group of approvers, for us these are the same programmers who handle the engine upgrades so they can call out changes that would be difficult to maintain.
  • We encourage as many changes as possible to be pushed back to Epic, see Contributing to the Unreal Engine. This allows us to remove code divergences when taking a future release.
  • All changes are tagged in code, and tracked with a Jira ticket. For example:

// NTGL_BEGIN - NTGL-1234 - [IMPROVEMENT] Fixing an uninitialized variable
// int IntVar;

int IntVar = 0;
// NTGL_END

 

  • Divergences are categorized as follows:
    • [IMPROVEMENT] – change is suitable to be sent to Epic
    • [TEMP] – the change is needed only temporarily, usually until the next major engine update
    • [PREPORT] – the change was pulled from a future update
    • [DIVERGENCE] – we need to make this change to ship our game, but it’s not a suitable change to be pushed back to Epic (we generally want to avoid making these changes)
  • The Jira ticket allows us to track additional context around the change, such as links to UDN, links to other tasks that required the change, and tasks required to remove the change.
  • When a divergence can’t be easily merged with an engine update, we typically drop our changes and re-implement them around the new code, or abandon our changes entirely.  
Integration process

Updating the engine can initially be as simple as obtaining the update from Epic’s Github, replacing everything under the Engine folder, and reconciling offline work through P4V; however, this will quickly become unmanageable as you make changes to engine code.

Leveraging Perforce’s stream depots, we established the following branches:

   Depot / stream    Description
//ue4/release-4.27

//ue5/release-5.0

Unmodified mirrors of the UE4 / UE5 releases

//ntgl/dev

Main development stream, where most of your team is working

//ntgl/integrate

Child stream of dev, where you will be merging engine updates

  • This stream is expected to get into a broken state until all issues are resolved

Before beginning the upgrade to UE5, you will want to establish revision history by merging from the final version of UE4 (//ue4/release-4.27) to your integrated stream. If you’re not already on 4.27, you might be thinking about jumping through multiple versions at once. In our experience it’s always easier to upgrade incrementally one version at a time.

You would then establish your release-5.0 branch as follows:

  • p4 copy //ue4/release-4.27 -> //ue5/release-5.0 (initially created as a copy of 4.27)
  • Update the contents of //ue5/release-5.0 from the official release

The update and merge steps now become:

  1. Merge all changes from your development stream to an integration stream
    • If there are ever any conflicts with content changes, always accept what comes from dev so that you’re not losing any work from your content creators
  2. Mirror the desired Unreal release into a separate Perforce depot (for example //ue5/release-5.x)
    • This mirror should remain unmodified and serves to track the engine changes in Perforce’s revision history to help with the merge step
  3. Merge/integrate from //ue5/release-5.x -> //ntgl/integrate
  4. Resolve merge conflicts and submit

These steps can all be done manually, but greatly benefit from a bit of effort to automate through scripting.

After merging the update you can break down the task of an engine upgrade into these discrete steps:

  1. Fix warnings and errors related to code project generation
  2. Fix compile and link errors
  3. Fix editor and cook errors
  4. Fix autotesting failures
  5. Fix game bugs
Automation

Investing in automation can really help to streamline the upgrade process. Ideally, you would have a repeatable build process that can be run against your integration stream, and validate that everything is working before your merge to your dev stream. Some additional areas that helped us:

  • Investing in and maintaining a comprehensive suite of automated tests can really help to catch a lot of issues before you need to even do any manual testing. In our case with our initial move to UE5, our automated tests were able to flag a lot of inconsistencies between the physics engine from UE4, and UE5’s Chaos physics.
  • Writing scripts can greatly speed up the process of mirroring releases, and the various steps for merging.
  • Adding additional automation checks to run asset validators and compile all Blueprints (-run=CompileAllBlueprints) before cooking your game will help to catch additional issues in your assets.
  • Frequently running your integration stream through the exact build process your dev stream undergoes helps make sure that any hidden edge cases are caught before your final merge.
  • Investing in systems to distribute test builds of your integration stream to allow for larger-scale playtests can help expose game issues early.
Tips and tricks

These are an assortment of tips we follow to streamline the process of going through the upgrade.

  • When taking a major engine upgrade, most files will automatically resolve but you will be left with a changelist containing tens of thousands of changes. The following p4 command will let you separate out the files that need to be manually resolved into a separate changelist:

p4 -F %localPath% resolve -n | p4 -x - reopen -c [new changelist #] 
 

  • When fixing compile and link errors, compile with -DisableUnity. This will turn off the compile optimization in UnrealBuildTool which combines many .cpp files into one. Compiling will take longer, but tracking down compile issues will be much easier, and this will also let you catch issues relating to missing include statements.
  • If you get Unreal Engine through github, you will need to decide how to populate the files obtained from GitDependencies.exe. You can either have developers update these files individually, or in our case we incorporated these files into the mirror automation scripts, submitting these files into our own Perforce mirror.
  • Pay close attention to Unreal Engine’s deprecation warnings as these often point you in the right direction of upgrading your project. Maintaining a 0 warning compile makes it much easier to keep on top of these deprecations.
  • Including all your assets in your integration stream means you have the ability to upgrade individual assets if needed. Although we didn’t need to do a lot of this, it can be cumbersome to manage if those assets are getting modified regularly in your main development stream. A few suggestions to help with this:
    • If you can cherry-pick some minimal engine changes into your dev stream, and update the assets over in dev, this will eliminate the possibility of running into conflicts.
    • If the assets can be upgraded using a commandlet, you can build a commandlet that runs after each merge to ensure all relevant assets are kept up to date. In many cases the asset simply needs to be resaved in the latest version of the editor.
  • Epic allows you to get access to the release branches even before the release enters an early preview state. This enables you to get a head start on merging an upcoming release, and to narrow the scope of how much code you merge at one time. Similarly, merging daily from your dev line enables you to intercept difficult merges right as they land so you can work with the relevant developers to address the issue.
  • If you rely on third-party plugins (from Marketplace or from other vendors), you’re likely better off to wait a bit until those plugins get updated. We often want to upgrade ahead of these updates, which means spending additional effort to update the plugins that you would otherwise get for “free” by waiting a bit longer.
  • With every new engine upgrade comes some really useful features. As tempting as it is to begin taking advantage of new features immediately, our goal is always to initially push a new engine update with as few meaningful changes to the game or developer workflows as possible. Only after we determine the upgrade to be stable in our dev stream do we start trying out new features.

 

Benefits of Unreal Engine 5

Upgrading to Lumen and Nanite was an exciting prospect. Who wouldn’t want more accurate bounced light and the ability to significantly increase the poly budget for your game?

Source: Unreal Engine Blog