Tuesday, June 11, 2024

Unreal Build Tool Command Line Arguments

We've been exploring some of the ways we can use command line parameters to allow different types of builds on the same platform in Unreal.  If you're building from the command line or using the Project Launcher, you can specify extra build arguments with the -ubtargs switch.  There's a very good explanation of how to set things up here:

https://greenforestgames.eu/article/Building-Unreal-projects-with-UAT-1605886728

In brief, you can create a class member in your game's Target.cs build file and give it the [CommandLine] attribute.  If you run the build with -ubtargs=-customarg, that class member will be set appropriately, and you can read it and respond to it as desired.

using UnrealBuildTool;

// Required for CommandLine attribute
// (if UE4.27, using Tools.DotNETCommon;)
using EpicGames.Core;

public class UBTExampleTarget : TargetRules
{
    [CommandLine("-targetsetting")]
    public bool bTargetSetting = false;

    public UBTExampleTarget(TargetInfo Target) : base(Target)
    {
        // ...
        // Existing code
        // ...

        if (bTargetSetting)
            // Do something if command line arg is present
    }
}

Then run the command line with -ubtargs (the argument must match the value in the [CommandLine] attribute):

<Unreal Engine location>\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=<project path> -platform=Win64 -clientconfig=Development -build -cook -pak -stage -ubtargs=-targetsetting -archive -archivedirectory=<archive path>


The limitation here is that you can only get the command line arguments in a Target.cs file.  You have to use other means to communicate that information to individual module Build.cs files where it may actually be useful for setting preprocessor defines or including specific libraries.  However, you can actually access the command line arguments in a module Build.cs file by using .NET and Unreal Build Tool systems.  Specifically, you must create a CommandLineArguments object and pass it System.Environment.GetCommandLineArgs(), then call .ApplyTo() on the module.

using UnrealBuildTool;

// Required for CommandLine attribute and CommandLineArguments
// (if UE4.27, using Tools.DotNETCommon;)
using EpicGames.Core;

public class UBTExample : ModuleRules
{
    [CommandLine("-modulesetting")]
    public bool bModuleSetting = false;

    public UBTExample(ReadOnlyTargetRules Target) : base(Target)
    {
        CommandLineArguments commandLineArguments =
            new CommandLineArguments(
System.Environment.GetCommandLineArgs());
       
commandLineArguments.ApplyTo(this);


        // ...
        // Existing code
        // ...

        if (bModuleSetting)
            // Do something if command line arg is present
    }
}

This technique will work in module Build.cs files that are in the main game, and it will also work for modules in plugins, in case you want your plugin to read the command line and respond to it without having to do extra work in each project you add it to.

You also do not have to apply the command line arguments to a target or module.  You can create a custom object and apply values directly to it.

using UnrealBuildTool;

// Required for CommandLine attribute and CommandLineArguments
// (if UE4.27, using Tools.DotNETCommon;)
using EpicGames.Core;

public class UBTExample : ModuleRules
{
    private class UBTExampleCommandLineData
    {
        [CommandLine("-examplesetting")]

        public bool bExampleSetting = false;
    }

    public UBTExample(ReadOnlyTargetRules Target) : base(Target)
    {
        UBTExampleCommandLineData ubtExampleCommandLineData =
            new UBTExampleCommandLineData();

        CommandLineArguments commandLineArguments =
            new CommandLineArguments(System.Environment.GetCommandLineArgs());
        commandLineArguments.ApplyTo(ubtExampleCommandLineData);

        // ...
        // Existing code
        // ...

        if (ubtExampleCommandLineData.bExampleSetting)
            // Do something if command line arg is present
    }
}

Examples of all of this are provided for both Unreal 4.27 and Unreal 5.4.

https://github.com/JBurkeKF/UBTExample

One extra item: if you want to set up a Project Launcher profile that uses build arguments, you can do a bit of a hack.  In the "Additional Command Line Parameters" field in the Build section, use double quotes to escape the entry and add -ubtargs for building.  For example (this time including quotes!) " -ubtargs=-targetsetting".



Thursday, May 30, 2024

Astor: Blade of the Monolith!

 Astor is a great action-adventure title featuring tons of fun environments to explore!  As the heroic Astor, seek the Monolith that's the source of all your world's evil, and put an end to it once and for all.

We handled all the platform work on this Unreal engine based title, working alongside the original developer C2, and did lots of work on performance and memory optimization.  The result is a game that runs well on everything from a Switch to a PS5 or PC.


Now available on Xbox, PlayStation, Switch, and PC!


Wednesday, April 10, 2024

Broken Roads!

A post-apocalyptic role-playing adventure through Australia, we've been co-developing Broken Roads with the folks at Drop Bear bytes for the last year!  Follow a long, complex narrative where your choices matter and your morality affects your gameplay! 


Now available on Steam, Xbox, and PlayStation platforms.  Switch will follow pretty soon!


Wednesday, January 10, 2024

Unity Addressables Content Catalog Refresh

 We're using Unity's Addressables to implement DLC for a multi-platform game, and on certain platforms, we don't actually know the path to the DLC until after it's loaded, which could be some time after the game has started.  One method is to use a multiple catalog system, like this one developed by LuGus-Jan and pdinklag.  In our case, though, we wanted to stick to a single catalog system if at all possible, and we were able to come up with a way to refresh the Addressables content catalog at runtime, though normally Unity does it once when the game starts up.  The code is written against Unity Addressables 1.19.19.

Refresh Addressables Content Catalog

The Addressables system allows you to specify code to dynamically construct load paths at runtime, but those paths are resolved to strings during the initial content catalog load, and take whatever value the code returns at that point.  Using this refresh operation, we can update the return value for the DLC path later while the game is running, and then force the Addressables system to re-resolve the paths to their correct values.  Addressables can then be accessed from the DLC as expected.

The public method for loading an Addressables content catalog is made assuming that data is being downloaded from the internet, and so includes an automatic caching feature to avoid redundant downloads.  However, the feature involves writing to Application.persistentDataPath, which is not allowed on some platforms.  For those cases, there is an alternate version that uses .NET reflection to access the internal methods and duplicate the same process, but avoid the step of reading and writing to the cache.  This may need to be revised with future versions of the Unity Addressables package.

Unity Addressables Per-Platform Group Settings

While developing a project for multiple platforms, we've encountered issues where the recommended packing settings for Addressable Groups differs, particularly when it comes to compression.  Some platforms don't natively compress the final package, so it's useful to compress the Addressable packed assets to keep the overall file size down.  Other platforms have recommended packing settings to help with patching.  In some cases, having uncompressed Addressable packed assets is ideal for patching.  There's currently no way of specifying different packing settings per platform (as of Unity Addressables 1.19.19).  Unity is aware of the need for this, but we have not seen any public progress on it.

In the mean time, we've come up with a relatively straightforward way to handle this situation so that we don't have to manually change settings when switching platforms.  In short, we have a new Addressable Group schema to apply where we can specify overrides for particular packing settings per platform, and a new Addressables build script that looks for this schema and replaces the actual packed schema with the overrides, if appropriate.

The code is available here.

In addition to the code, there are several more manual steps that need to be taken before things become fully automated.
  1. Create a new PlatformBuildScriptPackedMode Addressable Data Builder and (ideally) place it in Assets/AddressableAssetsData/DataBuilders.
  2. Register this new Addressable Data Builder with the Addressables Settings.  In your Addressable Asset Settings, add the new PlatformBuildScriptPackedMode asset to the list of "Build and Play Mode Scripts".

  3. Now go to individual Addressable Groups and add the new PlatformBundledAssetGroupSchema to each Group that needs an override.

  4. To actually build correctly, you need to select the new Platform Build Script Packed Mode builder from the Addressables build menu.

This should cover all the basics for using the platform overrides.  If you want to automate builds and select the proper builder, we recommend following this example from Unity.

You can add more platforms as needed when you use the code, as well as more overrides for your specific use case.  Right now, this schema does not support multi-editing, though that would be a very useful feature for future development.