Confused by Console Variables

I’m writing a simple plug-in and have some values that I want to drive from Console Variables that can be set in the config files for the class.

The console variables themselves seem to work as expected. If I open up the console and change the variables, the behavior of the class accurately reflects what changed.

Where I’m running into issues is with the saving and loading of the variables.

I’ve set up a UDeveloperSettings class, and when I go to Project Settings and change a value, it properly saves the new value to the config. But when I restart the editor, instead of loading the console variable value from the config, it sets the console variable back to its default value, and when I enter the console variable in the console, it says ‘Last Set By Constructor’.

I’ve tried setting some different ECVF flags, but I get the same result every time, unless I use ECVF_CreatedFromIni, which causes a crash in DeveloperSettings on startup because it can’t find the console variable. I get the same crash if I try to create the console variables in the constructor of my actor class instead of globally in the actor class.

I’m not sure what I’m missing here.

Code snippets:
Global Console Variable Declarations in the CPP of my actor class:

static TAutoConsoleVariable<int32> CVarSmoothTime(
	TEXT("sdn.SmoothTime"),
	1,
	TEXT("Sets whether to use a smooth or stepped sun update")
);

static TAutoConsoleVariable<float> CVarDayLength(
	TEXT("sdn.DayLength"),
	10.0f,
	TEXT("Sets how many real-time minutes equal 24 in-game hours")
);

static TAutoConsoleVariable<float> CVarSeasonLength(
	TEXT("sdn.SeasonLength"),
	4.0f,
	TEXT("Number of days for each season")
);

static TAutoConsoleVariable<float> CVarStepRate(
	TEXT("sdn.TimeStep"),
	1.0f,
	TEXT("Number of seconds between updates when not using Smoothed sun update")
);

Header for settings class:

UCLASS(Config=SimpleDayNight, DefaultConfig, DisplayName = "Simple Day/Night Cycle")
class SIMPLE_DAYNIGHT_API USimpleDayNightSettings : public UDeveloperSettings
{
	GENERATED_BODY()

public:
	virtual FName GetContainerName() const override;
	virtual FName GetCategoryName() const override;
	virtual FName GetSectionName() const override;
	
	virtual void PostInitProperties() override;
#if WITH_EDITOR
	virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif

public:

	UPROPERTY(EditAnywhere, Config, Category = "Optimization", meta=(ConsoleVariable = "sdn.SmoothTime", DisplayName = "Smooth Sun Movement"))
	int SmoothTime = 1;

	UPROPERTY(EditAnywhere, Config, meta = (ConsoleVariable = "sdn.DayLength"))
	float LengthOfDay = 10;

	UPROPERTY(EditAnywhere, Config, meta = (ConsoleVariable = "sdn.SeasonLength"))
	float SeasonLength = 4;

	UPROPERTY(EditAnywhere, Config, meta = (ConsoleVariable = "sdn.TimeStep"))
	float SteppedTimeRate = 1;
};

In the CPP for the settings I’m just doing an ImportConsoleVariableValues in PostInitValues, and ExportValuesToConsoleVariables in PostEditChangeProperty.

I can get it to work if I iterate through the FProperties and ExportValuesToConsoleVariables in PostInitValues instead, but that feels like the wrong approach. Surely the variables in the config should be overriding the defaults in the constructor automatically?

The other issue with using ExportValuesToConsoleVariables in PostInitValues is that ExportValuestoConsoleVariables is an Editor Only function. So when I build a build, the saved config values are ignored again.

Trying to do this fully manually, calling Set() on a CVar in PostInitProperties() in the actor class works fine in editor and standalone, but gives me an assert in a packaged build.

Calling Set() in BeginPlay() in the packaged build works, but a packaged build isn’t loading the property values that were set in the Default Config. Yes, I am calling LoadConfig(). It WILL load the values from a saved config, but that doesn’t do me any good when the values are all wrong on first time startup. This is driving me insane.

If my knowledge is right, UDeveloperSettings is for the editor only. If you want to do something in a packaged build, you must avoid using it.

Why don’t you try just UObject with UCLASS(config=SomeName)?

If your package means packaging of the plugin itself, for example to the marketplace or just distribution of the plugin, not a packaged game, then it should work. I forgot the possibility of this.

And, there are other things that came to mind:

Thanks, I think I’ve got it figured out now. I gave up on using a DeveloperSettings class, but things still weren’t working quite right.

My issues turned out to be:

  • Apparently CVars don’t automatically load. At least not for plug-ins. You have to explicitly call ApplyCvarSettingsFromIni() in StartupModule() to set them on startup.
  • My properties weren’t loading in a build because I was using the ConsoleVariable meta tag (which is apparently new and not very well documented). Turns out it only works in editor/standalone, and gets compiled out in packaged builds. So while running in editor or in standalone mode those properties were loading and saving their values into the config as the CVar name, in a build they were loading and saving as the property name. Which meant that the packaged Default Config didn’t have the property names for them to load. I managed to get around that by setting their values from the CVars in the constructor before calling LoadConfig() (this way I get the default values from the CVar, and anything that’s been saved differently overrides that default). This also required adding some extra stuff in my Console Variable sink function to only update and save if the last thing that changed the CVar wasn’t ProjectSettings.

The code still feels a bit messier than I’d like, but at least it’s all working the way it should be now.

1 Like

Well, dang. New wrinkle in this that kind of blows everything up again: blueprint sub-classes of the main actor class save any changes to a different section of the default config.

So if I just drop the main class into a level and tweak some settings they will get saved to [/Script/Simple_DayNight.DayNightController], but if I make a blueprint sub-class and tweak settings in the blueprint, they will get saved to [/Game/NameOfBPClass.NameOfBPClass_C].

And the BP sub-class is still saving the variables using the ConsoleVariable meta. But since I have to specify the section of the config to load the console variables from in StartupModule I can’t load them from that section, and since the linkage of the property name to the CVar breaks in a build this means the blueprint sub-class can’t load its defaults in a build.

So I guess I just plain can’t use the ConsoleVariable meta tag in the actor class, and the best approach is to use a DeveloperSettings class to let users define the defaults for the CVars for their project, and then overrides of the variables save using the property names, and I can have the actor update the CVars in its BeginPlay.

I found something today: 5.4 Config not saving correctly