Save and Load¶
Modular Building System does not require projects to use a specific save framework.
The plugin provides building data collection and world rebuilding APIs through UBuildingWorldSubsystem. The project is responsible for writing this data to its own USaveGame, database, or server-side save system.
The save workflow primarily uses the following two APIs:
| API | Description |
|---|---|
CollectSaveRecords() |
Collects the building records that need to be saved from the current world. |
RebuildAll() |
Rebuilds the building world from saved records. |
Basic Workflow¶
Saving the building world:
Get UBuildingWorldSubsystem
→ Call CollectSaveRecords
→ Write the records to the project save data
→ Save locally or on the server
Restoring the building world:
Enter the target world
→ Wait for Building World Subsystem to become available
→ Read the project save data
→ Get the saved building records
→ Call RebuildAll
→ The plugin rebuilds the building visuals and runtime data
UBuildingWorldSubsystem¶
UBuildingWorldSubsystem is the primary access point for building world data and save APIs.
It can be obtained from the current UWorld:
Always check that the returned value is valid before using it:
CollectSaveRecords¶
Function declaration:
CollectSaveRecords() collects the data that needs to be persisted from the current building world.
Example:
const TArray<FUpdateBuildingEntityInfo> BuildingRecords =
BuildingWorldSubsystem->CollectSaveRecords();
The project should save the returned FUpdateBuildingEntityInfo array directly. It does not need to access HISM Components, Chunk Actors, or building Actors manually.
Note
FUpdateBuildingEntityInfo is the building record structure provided by the plugin.
The exact saved data depends on the structure definition in the current plugin version. Projects generally do not need to split or reorganize its contents.
RebuildAll¶
Function declaration:
RebuildAll() rebuilds the building world from the saved building records.
Example:
The rebuilding process restores the runtime visuals and mappings required by the building system.
The project does not need to create the following manually:
- HISM Components
- HISM Instances
- Render Chunk Actors
- Actor-mode buildings
- Runtime mappings between Entities and rendering objects
These runtime objects should be rebuilt by the plugin.
Creating a SaveGame Class¶
You can create a class derived from USaveGame to store the building records returned by the plugin.
The following is a minimal example.
BuildingWorldSaveGame.h¶
// Copyright 2026 Zhiying Li. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/SaveGame.h"
#include "Types/UpdateBuildingEntityInfo.h"
#include "BuildingWorldSaveGame.generated.h"
/**
* Stores building records collected from Modular Building System.
*/
UCLASS()
class UBuildingWorldSaveGame : public USaveGame
{
GENERATED_BODY()
public:
/** Records used to rebuild the building world. */
UPROPERTY(SaveGame)
TArray<FUpdateBuildingEntityInfo> BuildingRecords;
};
Note
Types/UpdateBuildingEntityInfo.h is provided only as an example.
Adjust the #include path according to the actual header location of FUpdateBuildingEntityInfo in the plugin.
Saving the Building World¶
The following example creates a UBuildingWorldSaveGame, collects the current building records, and saves them to the specified Slot.
// Copyright 2026 Zhiying Li. All Rights Reserved.
#include "BuildingWorldSaveGame.h"
#include "Kismet/GameplayStatics.h"
#include "System/BuildingWorldSubsystem.h"
bool SaveBuildingWorld(
UObject* WorldContextObject,
const FString& SlotName,
int32 UserIndex
)
{
if (!IsValid(WorldContextObject))
{
return false;
}
UWorld* World = WorldContextObject->GetWorld();
if (!IsValid(World))
{
return false;
}
UBuildingWorldSubsystem* BuildingWorldSubsystem =
World->GetSubsystem<UBuildingWorldSubsystem>();
if (!IsValid(BuildingWorldSubsystem))
{
return false;
}
UBuildingWorldSaveGame* SaveGame =
Cast<UBuildingWorldSaveGame>(
UGameplayStatics::CreateSaveGameObject(
UBuildingWorldSaveGame::StaticClass()
)
);
if (!IsValid(SaveGame))
{
return false;
}
SaveGame->BuildingRecords =
BuildingWorldSubsystem->CollectSaveRecords();
return UGameplayStatics::SaveGameToSlot(
SaveGame,
SlotName,
UserIndex
);
}
Example call:
Restoring the Building World¶
The following example loads the building records from a Slot and calls RebuildAll().
// Copyright 2026 Zhiying Li. All Rights Reserved.
#include "BuildingWorldSaveGame.h"
#include "Kismet/GameplayStatics.h"
#include "System/BuildingWorldSubsystem.h"
bool LoadBuildingWorld(
UObject* WorldContextObject,
const FString& SlotName,
int32 UserIndex
)
{
if (!IsValid(WorldContextObject))
{
return false;
}
UWorld* World = WorldContextObject->GetWorld();
if (!IsValid(World))
{
return false;
}
UBuildingWorldSubsystem* BuildingWorldSubsystem =
World->GetSubsystem<UBuildingWorldSubsystem>();
if (!IsValid(BuildingWorldSubsystem))
{
return false;
}
USaveGame* LoadedObject =
UGameplayStatics::LoadGameFromSlot(
SlotName,
UserIndex
);
UBuildingWorldSaveGame* SaveGame =
Cast<UBuildingWorldSaveGame>(LoadedObject);
if (!IsValid(SaveGame))
{
return false;
}
BuildingWorldSubsystem->RebuildAll(
SaveGame->BuildingRecords
);
return true;
}
Example call:
When to Call RebuildAll¶
Do not call RebuildAll() immediately before the target world has finished initializing.
Before restoring buildings, make sure that:
- The target world has been loaded.
UBuildingWorldSubsystemhas been created.- The plugin runtime systems have finished initializing.
- The
Building Data Assetreferences in the save data can be found by Asset Manager. - No other logic is creating the same buildings at the same time.
Recommended workflow:
Open the target level
→ Wait for world initialization to complete
→ Read the save data
→ Get UBuildingWorldSubsystem
→ Call RebuildAll
If the project uses asynchronous loading, wait for the related building data assets to finish loading before rebuilding the world.
Building Data Assets¶
Building save records usually reference their corresponding Building Data Asset through an asset identifier.
When restoring save data, the building assets referenced by the save records must still exist and must be discoverable by Unreal Engine's Asset Manager.
When modifying building assets:
- Do not casually delete building assets that are already referenced by save data.
- Do not change a Primary Asset ID without a migration plan.
- After renaming an asset, check whether older save data can still resolve it.
- Make sure that building data assets are included in packaged builds.
For building asset configuration, see Building Data Assets.
Data the Project Should Not Save¶
The project should not save temporary runtime objects generated by the plugin.
Do not save the following directly:
UHierarchicalInstancedStaticMeshComponent*- HISM Instance Index
ARenderChunkActor*ABuildingEntityActor*- Actor pointers
- Component pointers
- Chunk Actor references
- Runtime mappings between Entities and Instances
- Client-side local caches
- UObject references from the current world
This data is valid only in the current runtime world and should be regenerated by RebuildAll().
Warning
An HISM Instance Index may change after deletion, rebuilding, or reordering.
Do not store an Instance Index as a permanent building identifier in project save data.
Chunk Data¶
Chunks organize building rendering and network data, but projects generally do not need to save Chunk Actors or their internal mappings directly.
When buildings are restored, the plugin should rebuild the corresponding Chunks and rendering data from the saved records.
Project code should not depend on:
- A specific Render Chunk Actor
- A specific HISM Component address
- A permanently stable Instance Index
If the project changes Chunk Size, test whether older save data can still be rebuilt correctly.
For more information, see Chunk System.
Multiplayer¶
In multiplayer games, building save and load operations should be handled by the server.
Recommended workflow:
The server reads the building save data
→ The server gets UBuildingWorldSubsystem
→ The server calls RebuildAll
→ The plugin synchronizes the Building Entities
→ Clients create the corresponding HISM or Actor visuals
Clients should not independently read the same building world save data and call RebuildAll().
Doing so may cause:
- Duplicate buildings
- Entity ID conflicts
- Data inconsistencies between the server and clients
- Duplicate Actor creation
- Incorrect HISM Instance counts
When saving the building world, it is also recommended that the server call:
The server can store the returned data in:
USaveGame- Dedicated Server save data
- A database
- Cloud storage
- A project-specific world save structure
Handling Missing Save Data¶
A building save may not exist when the world is entered for the first time.
Check before loading:
If the save does not exist, the project can:
- Keep the building world empty
- Load preset buildings from the level
- Create a new building save
- Skip
RebuildAll()
Whether an empty array should be passed to RebuildAll() depends on whether the project intends to preserve or clear the current building world.
Repeated Calls¶
Do not call RebuildAll() multiple times with the same data unless there is a clear requirement to do so.
For example, do not restore the same building world from multiple locations such as:
- Game Mode
- Game State
- Level Blueprint
- Game Instance
- Player Controller
The project should define a single world restoration entry point.
It is recommended to use one of the following:
- Game Mode
- A dedicated world save manager
- Game Instance Subsystem
- A project-specific server save system
Save Versions¶
The building record structure may change after updating the plugin or project.
It is recommended to add a version number to the project save data:
Older data can then be migrated during loading:
Pay particular attention to the following changes:
- Building assets are deleted or renamed
- The Entity data structure changes
- Gameplay Tags are replaced
- Building attribute names change
Chunk Sizechanges- Cost or effect assets change
- An Actor Class is replaced
Troubleshooting¶
| Issue | What to Check |
|---|---|
CollectSaveRecords() returns an empty array |
Check whether it is being called in the correct world and on the server. |
UBuildingWorldSubsystem cannot be obtained |
Check whether the World is valid and whether the call is being made too early. |
| Buildings are not created after loading | Check the save records, building data assets, and Asset Manager configuration. |
| Buildings are duplicated after loading | Check whether RebuildAll() is being called from multiple locations. |
| HISM buildings are not restored | Check the corresponding Building Data Asset and Module Definition. |
| Actor buildings are not restored | Check whether the corresponding Actor Class is valid. |
| Older save data cannot be loaded | Check whether the save version or building asset identifiers have changed. |
| Building positions are incorrect after loading | Check the world transforms in the saved records and the project's world-origin rules. |
| Buildings cannot be restored in a packaged build | Check whether the building data assets are included in the packaged content. |