Using Engine-Version Specific Code

Making sure code runs in the correct version of Unreal

Why Track the Engine Version

This is more of a pipeline concept that film tends to worry about more, but depending on how the code is handled it is entirely possible that multiple Unreal projects could be relying on the same Python codebase.

When supporting multiple versions of Unreal with the same codebase there is the chance that the same code needs to work in multiple versions of Unreal, even if the Unreal API has changed. This page covers an implementation that will allow us to make Unreal version-specific code, allowing us to simultaneously support multiple engine versions.


How to Get the Engine Version

To see the current Unreal Engine version of an Editor session we can use get_engine_version() :

print(unreal.SystemLibrary.get_engine_version())

# Result:
    5.2.1-26001984+++UE5+Release-5.2

For our purposes, we really only need what's before the - :

5.2.1

The EngineVersion class

I wanted to make a class for this that had minimal dependencies, while it is basic in some regards it does the trick.

I'll start with the complete class and then explain the important parts below:

from functools import total_ordering
import unreal


@total_ordering
class EngineVersion:
    """
    Utility class to compare Unreal Versions

    Provide the engine version formatted as "5" , "5.0" , "5.0.0"
    or leave empty to use the current Unreal Editor session's version
    """

    def __init__(self, version: str = ""):
        version = str(version)
        
        # Get the current UE session's version if not provided
        if not version:
            version_info = str(unreal.SystemLibrary.get_engine_version())
            version = version_info.split("-", 1)[0]

        # Collect the version IDs
        version_ids = version.split(".")
        if not len(version_ids) or not all([i.isdigit() for i in version_ids]):
            raise ValueError(
                f"EngineVersion was provided '{version}' - must be in the format of '5' , '5.0' , or '5.0.0'"
            )

        # Store the version data
        version_ids = [int(i) for i in version_ids]
        version_ids.extend([0, 0])
        self.major, self.minor, self.patch = version_ids[0:3]

    @property
    def value(self) -> int:
        """The numeric value of the engine version (used for comparison)"""
        return self.major*1000000 + self.minor*1000 + self.patch

    @property
    def version(self) -> str:
        """The string value of the engine version (human-readable)"""
        version_string = f"{self.major}.{self.minor}"
        if self.patch:
            version_string += f".{self.patch}"
        return version_string

    # The @total_ordering decorator will use __lt__ and __eq__ to make
    # the rest of our comparison functions
    def __lt__(self, other: 'EngineVersion') -> bool:
        if isinstance(other, str) or isinstance(other, float):
            other = EngineVersion(other)
        if not isinstance(other, EngineVersion):
            raise ValueError(f"{other} must be of type EngineVersion!")
        return self.value < other.value

    def __eq__(self, other) -> bool:
        if isinstance(other, str) or isinstance(other, float):
            other = EngineVersion(other)
        if not isinstance(other, EngineVersion):
            raise ValueError(f"{other} must be of type EngineVersion!")
        return self.value == other.value

    # repr to provide a cleaner debug printout ~ ex: EngineVersion("5.3.0")
    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(\"{self.version}\")"

The __init__() function

This class supports a few different options to determine the Engine Version:

  • if Empty, get the current Editor Session's UE version

  • Strings, such as "5" , "5.0", or "5.0.0"

  • Numbers, such as 5.2 are also accepted

The value property

This function is what's used to compare itself with others, providing a numeric value that's easier to process

The __lt__() and __eq__() functions

These functions, along with the @total_ordering decorator on the class, define all of our comparisons. We can compare an EngineVersion class with...

  • Another EngineVersion object

  • A number, such as 5.2

  • A string, such as "5.2"

Note: While this demo class does track the patch number (the third number, 5.0.x), it's not really necessary and could be ignored in most cases


Example Uses

Testing this class in 5.2.1 I get the following results:

current_engine_version = EngineVersion()

print(current_engine_version)
print(current_engine_version > EngineVersion(5.2))
print(current_engine_version > 5.3)
print(current_engine_version == "5.2.1")

# Results:
#  EngineVersion("5.2.1")
#  True
#  False
#  True

Our code can now safely support newer features that come out:

def force_update_sequencer():
    "Force the Sequencer UI to re-evaluate itself"
    if EngineVersion() >= 5.5:
        # New method added in 5.5 to force update sequencer
        LevelSequenceEditorBlueprintLibrary.force_update()
    else:
        # The old method relies on changing the current frame to force a refresh
        current_frame = unreal.LevelSequenceEditorBlueprintLibrary().get_current_time()
        unreal.LevelSequenceEditorBlueprintLibrary().set_current_time(current_frame + 1)
        unreal.LevelSequenceEditorBlueprintLibrary().set_current_time(current_frame)

We can use the new 5.5 command while still supporting the method used for older engine versions

Last updated