ACC 3.0
Features
Clean Code
The codebase has been cleaned up, with additional comments and logging added for better maintainability and debugging.
Moving Defaults to C++
The default ParamHandlers
, ItemParamHandlers
, and CommonScript
that were previously implemented only in Blueprint have now been migrated to C++.
Why this change?
- Better maintainability: Blueprints are harder to maintain, especially with frequent node reordering and hard-to-review diffs in version control.
- Improved performance: Blueprints have limitations when working with non-UObject references — and since parameters rely heavily on structs, C++ is a better fit.
- Still overrideable: You can still override or extend behavior in Blueprints if needed.
- Easier debugging: Centralizing logic in C++ gives better visibility when tracking down issues or regressions.
The original Blueprint ParamHandlers have not been deleted for backward compatibility, but they are deprecated, no longer used in the example project, and scheduled for removal in a future release.
DataAsset Validation
A validation system has been added for the 3 critical DataAssets
: Specie, Maturity, and Gender.
This helps catch missing or misconfigured fields during development.
For example, if a Gender
asset is missing its PreviewActorClass
, the validation will highlight it:
The validation process is recursive:
When saving a SpecieDataAsset
, it will automatically validate its associated Maturity
and Gender
assets as well:
UACCCreationDataAsset
A new wrapper around FACCCharacterCreation
has been introduced as a data asset.
This makes it reusable for presets and NPC configurations.
Create a UACCCreationDataAsset
The data asset can be created from the context menu
Once named, a popup will appear allowing you to choose default data based on your Specie configuration
Editor Preview
A new visual editor preview has been added, made possible by this encapsulation:
- On the left, you have the creation data.
- On the right, a custom outliner helps to easily inspect tested parameters.
Editor Preview (Lighting)
On the right side of the editor, you’ll also find the PreviewSettings
section.
This is purely for lighting and visual comfort while working — it has no impact on the final asset data.
This editor is meant to assist with testing your Specie and editing preset data.
Not all parameters support full automatic refresh — hence the "Reload Model"
button.
Additionally, make sure your ParamHandlers are properly written to avoid runtime dependencies.
They must remain agnostic of the GameInstance.
Example: Usage as NPC
Thanks to the UACCCreationDataAsset
, you can now directly assign a preset to a CreationComponent
and enable automatic loading by checking the AutoLoadFromCreationDataAsset
option.
Async Loading Behavior
Previously, assets were loaded synchronously, which caused noticeable freezes whenever they were accessed — clearly not ideal for polished, production-quality games.
Now, assets retrieved via parameters are still loaded synchronously by default to preserve compatibility.
However, they will be loaded asynchronously if both of the following conditions are met:
- The creation process is triggered via the async call
- The parameter handlers are correctly implemented to support async asset resolution
This is considered an advanced feature. A full guide is available here:
How to Use the Async System
Migration
Overview
During the migration, you'll mainly encounter changes like replacing parameters with const
references.
For example:
FACCParamHandlerResult OnReceiveEquip(UACCCreationComponent* CreationComponent, const FACCEquipParam EquipParam);
Becomes:
FACCParamHandlerResult OnReceiveEquip(UACCCreationComponent* CreationComponent, const FACCEquipParam& EquipParam);
Additionally, some clean-up has been performed:
- Removal of unnecessary boilerplate such as
CommonDAO
- Simplification of parameter handling
- Refactoring of mesh accessors (
GetBody()
, etc.)
Another major improvement concerns optional asynchronous loading.
Previously, many assets were stored as TSoftObjectPtr
, and we were using LoadSynchronous()
everywhere.
This was a temporary approach meant to get things working quickly, but in hindsight, it needed to be addressed for better performance and production quality.
Using LoadSynchronous()
can cause noticeable hitches, especially on consoles, and in some cases even crashes due to blocking asset loads.
We'll cover the proper asynchronous loading strategies later in this guide.
Accessing Specie Functions
Previously, retrieving a specie was done like this:
// Retrieve the specie using the DAO
if (UACCCommonDAO* CommonDao = UACCGameplayStatics::GetDAOByClass<UACCCommonDAO>(GetPrimaryWorldContextObject()))
{
// If valid specie data is found
if (CommonDao->GetSpecieData(Creation.CreationBase.Specie, CurrentSpecie))
{
// Do something
}
}
However, in hindsight, this DAO was essentially boilerplate. The GameData
object was already handling this responsibility, and it's more useful to have access to it both at runtime and in the editor. Therefore, the recommended approach now is to retrieve the GameData
and then the specie directly, like this:
UACCGameData* GameData = UACCCoreLibraryStatics::GetGameData();
if (GameData->GetSpecieData(Creation.CreationBase.Specie, CurrentSpecie))
{
// Do something
}
Additionally, since the Specie
object already has built-in functions to retrieve presets, you should also use it to get preset data like this:
// Assuming you've already retrieved the specie
UACCSpecieDataAsset* CurrentSpecie = ...;
CurrentSpecie->GetPresetCreation("my_preset", CharacterCreationData);
Param Handler
Previously, the signature for receiving parameters looked like this:
bool OnReceiveInt(UACCCreationComponent* CreationComponent, FACCIntParam IntParam);
Now, the updated signature is:
FACCParamHandlerResult OnReceiveInt(UACCCreationComponent* CreationComponent, const FACCIntParam& IntParam, bool bRemoved = false);
Why this change?
bRemoved
: Allows better handling of whether the parameter has been removed from the data.FACCParamHandlerResult
: Provides more robust error and logging support.- Parameters are now passed as
const
references for better performance and to follow best practices.
How to migrate?
First, update the function signatures in your implementations.
Then, update the return values accordingly:
-
Replace
return true;
withreturn FACCParamHandlerResult::MakeSuccess();
-
Replace
return false;
withreturn FACCParamHandlerResult::MakeEmptyError();
-
For custom error messages, use:
return FACCParamHandlerResult::MakeError(TEXT("My formatting error '%s'"), *MyString);
In Blueprint side :
Async Loading
To address the need for asynchronous loading while staying agnostic and generic, the current solution involves adding a dedicated function to parameter handlers (such as UACCItemParamHandler
and others).
For more information, you can refer to the dedicated page after reading this one:
Without diving too deep for now, the idea is that a class will recursively gather all soft assets to preload based on the parameters provided in FACCCharacterCreation
.
For example, in UACCItemParamHandler
, you’ll find the following method:
void GetSoftAssetPaths(UACCCreationComponent* CreationComponent, const FACCEquipParam& EquipParam, TArray<FSoftObjectPath>& OutPaths);
Here's a concrete use case from UACCParamHandler_HairStyle
, which gathers the hair mesh asset based on the provided hair ID:
void UACCParamHandler_HairStyle::GetSoftAssetPathsFromParam(UACCCreationComponent* CreationComponent,
const FInstancedStruct& Param, TArray<FSoftObjectPath>& OutPaths)
{
Super::GetSoftAssetPathsFromParam(CreationComponent, Param, OutPaths);
if (Param.GetScriptStruct() == TBaseStructure<FACCIntParam>::Get())
{
const FACCIntParam& IntParam = Param.Get<FACCIntParam>();
if (!IntParam.IsValid()) return;
FACCMeshData MeshData;
if (!CreationComponent->GetValidMeshData("Hair", IntParam.Value, MeshData))
{
return;
}
MeshData.AddToSoftPaths(OutPaths);
}
}
Key points:
- InstancedStruct is used to determine which parameter type is received (
FACCIntParam
,FACCFloatParam
, etc.). - The handler then calls a method to retrieve the mesh, which is stored as a
TSoftObjectPtr
. - The utility function
AddToSoftPaths()
is used to automatically append the relevant soft asset references fromMeshData
to theOutPaths
array.
This system lays the foundation for non-blocking, asynchronous asset loading, which improves runtime performance and avoids freezes or crashes—especially important on console platforms.
TryGetAsset
All previous usages of synchronous asset loading have been refactored using the TryGetAsset
wrapper. This was done to:
- Avoid breaking existing code
- Emit a warning log if the asset hasn't been preloaded
Example usage:
auto* NewMesh = UACCCoreLibraryStatics::TryGetAsset(HeadMeshData.Mesh.Mesh);
This allows you to safely access soft-loaded assets while being notified when preloading was missed:
How can I load async?
To simplify asynchronous loading on the Blueprint side, an AsyncAction is available and exposed to Blueprints:
The bAutoLoadCreation
option automatically calls LoadCreation()
on the CreationComponent
once all assets have been loaded successfully.
This allows you to seamlessly trigger the full character setup after preload, without requiring additional manual steps in your Blueprint logic.