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.