Spawn and set GeometryCollection initialization fields at runtime

Update: Fixed it! The problem was my FS_MasterField blueprint, which was assigned to the ASpawnableGeometryCollection’s FieldSystem property. I switched the FS_MasterField’s ActivationType property from Trigger to Delay and kept the DelayAmount = 0.01, and it now works!!

Thanks @wha092686 for the detailed C++ example, but it’s not quite working when I put the anchors and geometry collections into a parent actor: SpawnableGeometryCollection. It spawns OK but hits don’t damage the geometry collection.

I store soft object pointers to geometry collections in a data table, and store anchor transforms in another data table, using the structs below. I basically have a building with 4 walls, a roof and a floor, represented by 6 geometry collections. Then I have 4 anchor fields (transforms), one at each corner of the building.

//GeometryCollectionStruct.h
//---------------
#pragma once

#include "GeometryCollectionStruct.generated.h"
class UGeometryCollection;
class UGeometryCollectionDynamic;

USTRUCT(BlueprintType)
struct FGeometryCollectionStruct : public FTableRowBase
{
	GENERATED_BODY()

    FGeometryCollectionStruct() = default;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TSoftObjectPtr<UGeometryCollection> GeometryCollection;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FVector Location = FVector::ZeroVector;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FRotator Rotation = FRotator::ZeroRotator;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FVector Scale = FVector::One();
};

And:

//AnchorFieldStruct.h
//---------------
#pragma once

#include "AnchorFieldStruct.generated.h"

USTRUCT(BlueprintType)
struct FAnchorFieldStruct : public FTableRowBase
{
	GENERATED_BODY()
	
	FAnchorFieldStruct() = default;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FVector Location = FVector::ZeroVector;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FRotator Rotation = FRotator::ZeroRotator;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FVector Scale = FVector::One();
};

Here is the SpawnableGeometryCollection which loads the geometry collections and anchors. Loading in BeginPlay, PostActorCreated, or OnConstruction still doesn’t destroy the geometry collections on hit.

//SpawnableGeometryCollection.h
//---------------
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interfaces/HitInterface.h"
#include "SpawnableGeometryCollection.generated.h"

class AFieldSystemActor;
class IAssetLoaderInterface;
class UGeometryCollection;
class UMyGeometryCollectionComponent ;

UCLASS()
class WRECKING_API ASpawnableGeometryCollection : public AActor, public IHitInterface
{
	GENERATED_BODY()
	
public:	
	ASpawnableGeometryCollection();
	virtual void GetHit_Implementation(const FVector& ImpactPoint, const UPrimitiveComponent* Component, const int32 Item) override;
	
protected:
	virtual void BeginPlay() override;
	virtual void PostActorCreated() override;
	virtual void OnConstruction(const FTransform& Transform) override;
	
	UPROPERTY()
	USceneComponent* DefaultSceneRoot;

	UPROPERTY()
	USceneComponent* GeometryCollections; // UMyGeometryCollectionComponent* children

	UPROPERTY()
	USceneComponent* AnchorFields; // UChildActorComponent* children, class = AMyAnchor*

	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	TObjectPtr<UDataTable> GeometryCollectionDataTable;

	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	TObjectPtr<UDataTable> AnchorsDataTable;

	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	TSubclassOf<AFieldSystemActor> FieldSystem;
	
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	USoundBase* HitSound;
	
private:
	void InitializeAnchors();
	TArray<TSoftObjectPtr<UGeometryCollection>> SoftGeoCollPtrs;
	void InitializeGeometryCollections();
	void CreateGeometryCollectionsFromTable(UWorldAssetLoaderSubsystem* WorldSubsystem);
	void GeometryCollectionsDeferred();
	AFieldSystemActor* SpawnFieldSystem(FTransform Transform);
};

And:

//SpawnableGeometryCollection.cpp
//---------------
#include "Destructibles/SpawnableGeometryCollection.h"
#include "AnchorFieldStruct.h"
#include "GeometryCollectionStruct.h"
#include "Components/Destructibles/MyAnchor.h"
#include "Components/Destructibles/UMyGeometryCollectionComponent.h"
#include "Engine/StreamableManager.h"
#include "Interfaces/HitInterface.h"
#include "Kismet/GameplayStatics.h"
#include "Tools/WorldAssetLoaderSubsystem.h"

ASpawnableGeometryCollection::ASpawnableGeometryCollection()
{
	PrimaryActorTick.bCanEverTick = false;
	DefaultSceneRoot = CreateDefaultSubobject<USceneComponent>(FName("DefaultSceneRoot"));
	SetRootComponent(DefaultSceneRoot);
	GeometryCollections = CreateDefaultSubobject<USceneComponent>(FName("GeometryCollections"));
	GeometryCollections->AttachToComponent(DefaultSceneRoot,FAttachmentTransformRules::KeepRelativeTransform);
	AnchorFields = CreateDefaultSubobject<USceneComponent>(FName("AnchorFields"));
	AnchorFields->AttachToComponent(DefaultSceneRoot,FAttachmentTransformRules::KeepRelativeTransform);
}

void ASpawnableGeometryCollection::BeginPlay()
{
	Super::BeginPlay();
	// tried:
	// InitializeAnchors();
	// InitializeGeometryCollections();
}

void ASpawnableGeometryCollection::PostActorCreated()
{
	Super::PostActorCreated();
	InitializeAnchors();
	InitializeGeometryCollections();
}

void ASpawnableGeometryCollection::OnConstruction(const FTransform& Transform)
{
	Super::OnConstruction(Transform);
	// tried:
	// InitializeAnchors();
	// InitializeGeometryCollections();
}

void ASpawnableGeometryCollection::InitializeAnchors()
{
	if (IsValid(AnchorsDataTable))
	{
		int32 i = 0;
		for (auto AnchorMap : AnchorsDataTable->GetRowMap())
		{
			FAnchorFieldStruct* AnchorRow = (FAnchorFieldStruct*)AnchorMap.Value;
			FString AnchorName = FName("Anchor_").ToString().Append(FString::FromInt(i));
			TObjectPtr<UChildActorComponent> Child = NewObject<UChildActorComponent>(this,UChildActorComponent::StaticClass(), *AnchorName);
			Child->RegisterComponent();
			Child->SetChildActorClass(AMyAnchor::StaticClass());
			FTransform ChildTransform = FTransform(AnchorRow->Rotation, AnchorRow->Location, AnchorRow->Scale);
			Child->SetRelativeTransform(ChildTransform);
			Child->AttachToComponent(AnchorFields, FAttachmentTransformRules::KeepRelativeTransform);
			i++;
		}
	}
}

void ASpawnableGeometryCollection::InitializeGeometryCollections()
{
	if (UWorld* World = this->GetWorld())
	{
		if (UWorldAssetLoaderSubsystem* WorldSubsystem = World->GetSubsystem<UWorldAssetLoaderSubsystem>())
		{
			CreateGeometryCollectionsFromTable(WorldSubsystem);
		}
	}
}

void ASpawnableGeometryCollection::CreateGeometryCollectionsFromTable(UWorldAssetLoaderSubsystem* WorldSubsystem)
{
	if (IsValid(GeometryCollectionDataTable))
	{
		SoftGeoCollPtrs.Empty();
		TArray<FSoftObjectPath> ItemsToStream;
		int32 i = 0;
		for (auto RowMap : GeometryCollectionDataTable->GetRowMap())
		{
			FGeometryCollectionStruct* Row = (FGeometryCollectionStruct*)RowMap.Value;
			SoftGeoCollPtrs.Add(Row->GeometryCollection);
			ItemsToStream.Add(Row->GeometryCollection.ToSoftObjectPath());
			FString GeoCollName = FName("Geometry_Collection_").ToString().Append(FString::FromInt(i));
			UMyGeometryCollectionComponent* GeoCollComp = NewObject<UMyGeometryCollectionComponent>(this,UMyGeometryCollectionComponent::StaticClass(), *GeoCollName);
			GeoCollComp->RegisterComponent();
			FTransform Transform = FTransform(Row->Rotation, Row->Location, Row->Scale);
			GeoCollComp->SetRelativeTransform(Transform);
			GeoCollComp->bNotifyBreaks = true;
			GeoCollComp->bNotifyTrailing = true;
			GeoCollComp->AttachToComponent(GeometryCollections, FAttachmentTransformRules::KeepRelativeTransform);
			
			FStreamableManager& Streamable = WorldSubsystem->AssetLoader;
			Streamable.RequestAsyncLoad(ItemsToStream, FStreamableDelegate::CreateUObject(this, &ASpawnableGeometryCollection::GeometryCollectionsDeferred));
			i++;
		}
	}
}

void ASpawnableGeometryCollection::GeometryCollectionsDeferred()
{
	if (SoftGeoCollPtrs.Num() > 0)
	{
		TArray<UMyGeometryCollectionComponent*> GeometryCollectionComponents;
		this->GetComponents<UMyGeometryCollectionComponent>(GeometryCollectionComponents);
		for(int32 i = 0; i < SoftGeoCollPtrs.Num(); ++i)
		{
			if (i < GeometryCollectionComponents.Num())
			{
				if(UGeometryCollection* Item = SoftGeoCollPtrs[i].Get())
				{
					GeometryCollectionComponents[i]->SetRestCollection(Item);
				}
			}
		}
	}
}

void ASpawnableGeometryCollection::GetHit_Implementation(const FVector& ImpactPoint, const UPrimitiveComponent* Component, const int32 Item)
{
	IHitInterface::GetHit_Implementation(ImpactPoint, Component, Item);

	const FTransform SpanwPoint(GetActorRotation(), ImpactPoint, FVector::One());
	SpawnFieldSystem(SpanwPoint);
	
	if (HitSound)
	{
		const FTransform SoundPoint(FRotator::ZeroRotator, ImpactPoint, FVector::One());
		UGameplayStatics::PlaySoundAtLocation(GetWorld(), HitSound, ImpactPoint, FRotator::ZeroRotator);
	}
}

AFieldSystemActor* ASpawnableGeometryCollection::SpawnFieldSystem(FTransform Transform)
{
	AFieldSystemActor* spawned = nullptr;
	FActorSpawnParameters spawnParams;
	spawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;

	if (FieldSystem)
	{
		spawned = GetWorld()->SpawnActor<AFieldSystemActor>(FieldSystem, Transform);
		const FString Name = spawned->GetName();
		UE_LOG(LogTemp, Warning, TEXT("Spawn Field System Name: %s - Loc: (X: %f, Y: %f, Z: %f), Rot: (Yaw: %f, Pitch: %f, Roll: %f)"),
			*FText::FromString(Name).ToString(), Transform.GetLocation().X, Transform.GetLocation().Y, Transform.GetLocation().Z, Transform.GetRotation().Rotator().Yaw, Transform.GetRotation().Rotator().Pitch, Transform.GetRotation().Rotator().Roll);
	}
	return spawned;
}

I also have a IHitInterface the sends the player hit to this class and a WorldAssetLoaderSubsystem that uses the world subsystem to load geometry collections, but that’ll just make my post longer, so I won’t include that code.