Spawn and set GeometryCollection initialization fields at runtime

I just figured it out for C++. Create your own Geometry collection component and override the OnCreatePhysicsState virtual function. Invoke the parent function (via Super) at the end of your overridden function. In the Engine code, InitializationFields activity happens in this function, so you want to add your own anchor fields to this array before it’s processed, hence invoking Super at the end of your overridden function.

//
// MyGeometryCollectionComponent.h
//
#pragma once

#include "CoreMinimal.h"
#include "GeometryCollection/GeometryCollectionComponent.h"

#include "MyGeometryCollectionComponent.generated.h"

UCLASS()
class MY_GAME_API UMyGeometryCollectionComponent : public UGeometryCollectionComponent
{
	GENERATED_BODY()

	virtual void OnCreatePhysicsState() override;
};
//
// MyGeometryCollectionComponent.ccp
//

#include "put-the-path-to-MyGeometryCollectionComponent.h here"
#include "Kismet/KismetSystemLibrary.h"

#include "put-the-path-to-MyAnchor.h here"

void UMyGeometryCollectionComponent::OnCreatePhysicsState()
{
	FVector Loc = GetComponentLocation();
	TArray<AActor*> ActorsToIgnore;
	TArray<FHitResult> HitResults;

	UKismetSystemLibrary::SphereTraceMulti(GetWorld(), Loc, Loc, 100.0f,
		UEngineTypes::ConvertToTraceType(ECC_Visibility), false, ActorsToIgnore, EDrawDebugTrace::Persistent, HitResults, true);

	InitializationFields.Empty();

	for (const FHitResult& HitResult : HitResults)
	{
		AActor* HitActor = HitResult.GetActor();
		if (HitActor->IsA<AMyAnchor>())
		{
			AMyAnchor* A = Cast<AMyAnchor>(HitActor);
			if (IsValid(A))
			{
				InitializationFields.Add(A);
			}
		}
	}

	Super::OnCreatePhysicsState();
}

The Sphere trace is just a method for your component to find the Anchor Field. The assumption in this example here is that you’re going to be spawning your actor with your MyGeometryCollectionComponent in the same location as your anchor field.

So according to this…

and the order in which the following functions are called…

it seems that the anchor field has to be fully initialized in the construction script (or prior). OnConstruction is too late, and UserConstructionScript is not a virtual function. Therefore, we’ll use PostActorCreated.

//
// MyAnchor.h
//
#include "CoreMinimal.h"
#include "Field/FieldSystemActor.h"

#include "MyAnchor.generated.h"

class UBoxComponent;
class UBoxFalloff;
class UCullingField;
class UUniformInteger;

UCLASS()
class MY_GAME_API AMyAnchor : public AFieldSystemActor
{
	GENERATED_BODY()

protected:

	UPROPERTY()
	USceneComponent* SceneComp;

	UPROPERTY(EditDefaultsOnly)
	UBoxComponent* BoxComp;

	UPROPERTY()
	UCullingField* CullingFieldComp;

	UPROPERTY()
	UBoxFalloff* BoxFalloffComp;

	UPROPERTY()
	UUniformInteger* UniformIntegerComp;

public:

	AMyAnchor();

protected:

	virtual void PostActorCreated() override;

public:

	void DynamicInit();
};
//
// MyAnchor.cpp
//
#include "put-the-path-to-MyAnchor.h here"
#include "Components/BoxComponent.h"
#include "Field/FieldSystemComponent.h"
#include "Field/FieldSystemObjects.h"

AMyAnchor::AMyAnchor()
{
	SceneComp = CreateDefaultSubobject<USceneComponent>(FName("Scene Component"));
	SetRootComponent(SceneComp);

	FieldSystemComponent->SetupAttachment(SceneComp);

	BoxComp = CreateDefaultSubobject<UBoxComponent>(FName("Box Component"));
	BoxComp->SetupAttachment(SceneComp);

	BoxFalloffComp = CreateDefaultSubobject<UBoxFalloff>(FName("Box Falloff Component"));
	
	UniformIntegerComp = CreateDefaultSubobject<UUniformInteger>(FName("Uniform Integer Component"));
	
	CullingFieldComp = CreateDefaultSubobject<UCullingField>(FName("Culling Field Component"));	
}

void AMyAnchor::PostActorCreated()
{
	Super::PostActorCreated();

	DynamicInit();
}

void AMyAnchor::DynamicInit()
{
	BoxFalloffComp->SetBoxFalloff(1.0f, 0.0f, 1.0f, 0.0f, BoxComp->K2_GetComponentToWorld(), EFieldFalloffType::Field_FallOff_None);
	UniformIntegerComp->SetUniformInteger(3);
	CullingFieldComp->SetCullingField(BoxFalloffComp, UniformIntegerComp, EFieldCullingOperationType::Field_Culling_Outside);
	FieldSystemComponent->AddFieldCommand(true, EFieldPhysicsType::Field_DynamicState, nullptr, CullingFieldComp);
	UE_LOG(LogTemp, Warning, TEXT("DynamicInit()"));
}

Now all you have to do is dynamically spawn the Anchor Field Actor FIRST and then your GeometryCollection Actor afterwards and it should work.

Cheers!

2 Likes

Thank you for this buddy, code compiled fine but at the end getting confilcts with Engine code i believe (unresolved external symbol). I did set my own API in settings, any idea? Thank you

Hmmm, did you add the following modules to your project?

image

There might be more that need to be added, but you could probably look them up in the API.

1 Like

thanks will test, did add physicsCore but nor fieldsystem, thank you buddy.

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.