Dynamic Collision Component Error

so, I’ve been working on a dynamic collision component for my game. What I mean by that, more specifically, is that my component creates a collision mesh based on an enum, both at runtime and in-editor.

the component does work, as in, I cannot pass through the pawn to which i assign it to, and the scene proxy (for debug, editor) works as well, for all bodies! BUT, whenever I enter a console command ‘show Collision’ (same goes for turning on collision view in editor), I cannot seem to make my component render the debug collision the same that conventional collision meshes do.

This is potentially a really insignificant issue, but i want to make the component as close to native components in function as possible, which includes console commands rendering proper mesh preview

here’s my full code for the custom component:

.h

// Written by Dipper/NSJaws; For purposes of project Moolah/spec{tr}.

#pragma once


 “CoreMinimal.h”

 “Components/PrimitiveComponent.h”

 “Components/ShapeComponent.h”

 “PhysicsEngine/BodySetup.h”

 “Engine/StaticMesh.h”

 “SceneView.h”


 “PrimitiveViewRelevance.h”

 “PlugMoolah.h”

 “MoolahCollisionProxyComponent.generated.h”

/**

This is a dynamic collision component that changes shape based on the value of SelectedCollisionType enum.

The enum is refereced in “PlugMoolah.h”.

Works as a root component; Attached meshes & debugs render aproperly to their specificities.
*/
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class PLUGMOOLAH_API UMoolahCollisionProxyComponent : public UShapeComponent
{
GENERATED_BODY()

public:
UMoolahCollisionProxyComponent();

// Don't need it, BodySetup is already defined in parent class.
/*UPROPERTY(Transient)
TObjectPtr<UBodySetup> BodySetup;*/

// Don't need it, BodySetup is already defined in parent class.
/*virtual UBodySetup* GetBodySetup() override;*/
virtual UBodySetup* GetBodySetup() override
{
	UpdateBodySetup();
	return ShapeBodySetup;
}

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Moolah|Collision Proxy", meta = (EditCondition = "CurrentShape == ECollisionComponentType::Sphere", EditConditionHides))
float SphereExtent = 30.0f;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Moolah|Collision Proxy", meta = (EditCondition = "CurrentShape == ECollisionComponentType::Capsule", EditConditionHides))
FVector2D CapsuleExtents = FVector2D(30.0f, 180.0f);

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Moolah|Collision Proxy", meta = (EditCondition = "CurrentShape == ECollisionComponentType::Box", EditConditionHides))
FVector BoxExtents = FVector(30.0f, 30.0f, 90.0f);

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Moolah|Collision Proxy", meta = (EditCondition = "CurrentShape == ECollisionComponentType::Mesh", EditConditionHides))
TObjectPtr<UStaticMesh> CollisionStaticMesh;

UPROPERTY(/*EditAnywhere, meta = (HideInInspector)*/)
ECollisionComponentType CurrentShape;

#if WITH_EDITORONLY_DATA
/Display When Selected:
- Display collision only when selected.
- If false, will always display collision, if debugger is set to true.
- Exists as a small performance boost check./
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = “Moolah|Collision Proxy”, meta = (AllowPrivateAccess = “true”, HideInDetailsView))
bool bDisplayCollisionOnlyWhenSelected = false;

/*Collision Debugger Color:*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Moolah|Collision Proxy", meta = (AllowPrivateAccess = "true", HideInDetailsView))
FColor DebugColor = FColor(255, 82, 44, 255);

#endif

void SetCollisionShape(ECollisionComponentType Type);

protected:
void RebuildCollision();

virtual bool ShouldCreatePhysicsState() const override
{
	return Super::ShouldCreatePhysicsState() && IsCollisionEnabled();
}

virtual void UpdateBodySetup() override;

virtual FPrimitiveSceneProxy* CreateSceneProxy() override;

virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;

virtual void OnRegister()  override;

#if WITH_EDITOR
// Editor-Only function that checks for property value changes.
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};

.cpp

// Written by Dipper/NSJaws; For purposes of project Moolah/spec{tr}.


 “MoolahCollisionProxyComponent.h”

 “PhysicsEngine/BodyInstance.h”

 “PhysicsEngine/BodySetup.h”

 “PhysicsEngine/AggregateGeom.h”

 “PhysicsEngine/SphylElem.h”

 “PhysicsEngine/SphereElem.h”

 “PhysicsEngine/BoxElem.h”

 “Engine/StaticMesh.h”

 “Components/PrimitiveComponent.h”

 “PlugMoolah.h”

/------------------------------------------------------------------------------------------------------------------------------------------------------/

class FMoolahCollisionSceneProxy final : public FPrimitiveSceneProxy
{
public:
FMoolahCollisionSceneProxy(const UMoolahCollisionProxyComponent* Component)
: FPrimitiveSceneProxy(Component)
, Proxy(Component)
{
// …
}

virtual SIZE_T GetTypeHash() const override
{
    static size_t Unique;
    return reinterpret_cast<size_t>(&Unique);
}

virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
{

#if WITH_EDITOR
if (!Proxy || !Proxy->ShapeBodySetup)
return;

    if (Proxy->bDisplayCollisionOnlyWhenSelected)
    {
        if (!IsSelected())
            return;
    }

    const FTransform TM(GetLocalToWorld());
    const UBodySetup* BS = Proxy->ShapeBodySetup;

    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        if (!(VisibilityMap & (1 << ViewIndex)))
            continue;

        FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);

        const FColor Color = Proxy->DebugColor;

        // Draw Boxes:
        for (const FKBoxElem& Box : BS->AggGeom.BoxElems)
        {
            const FTransform ElemTM = Box.GetTransform() * TM;

            DrawWireBox(
                PDI,
                ElemTM.ToMatrixWithScale(),
                FBox(FVector(-Box.X * 0.5f, -Box.Y * 0.5f, -Box.Z * 0.5f),
                     FVector(Box.X * 0.5f, Box.Y * 0.5f, Box.Z * 0.5f)),
                Color,
                SDPG_World
            );
        }

        // Draw Spheres:
        for (const FKSphereElem& Sphere : BS->AggGeom.SphereElems)
        {
            const FVector Center = TM.TransformPosition(Sphere.Center);

            DrawWireSphere(
                PDI,
                Center,
                Color,
                Sphere.Radius,
                16,
                SDPG_World
            );
        }

        // Draw Capsules:
        for (const FKSphylElem& Capsule : BS->AggGeom.SphylElems)
        {
            const FVector Center = TM.TransformPosition(Capsule.Center);

            DrawWireCapsule(
                PDI,
                Center,
                TM.GetUnitAxis(EAxis::X),
                TM.GetUnitAxis(EAxis::Y),
                TM.GetUnitAxis(EAxis::Z),
                Color,
                Capsule.Radius,
                Capsule.Length * 0.5f,
                16,
                SDPG_World
            );
        }

        // Draw Convex Hulls:
        for (const FKConvexElem& Convex : BS->AggGeom.ConvexElems)
        {
            Convex.DrawElemWire(PDI, TM, 1.0f, Color);
        }
    }

#endif
}

virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
{
    FPrimitiveViewRelevance Result;

#if WITH_EDITOR
Result.bDrawRelevance = true;
Result.bDynamicRelevance = true;
Result.bEditorPrimitiveRelevance = true;
#endif

    return Result;
}

virtual uint32 GetMemoryFootprint() const override
{
    return sizeof(*this);
}

private:
const UMoolahCollisionProxyComponent* Proxy;
};

/------------------------------------------------------------------------------------------------------------------------------------------------------/

UMoolahCollisionProxyComponent::UMoolahCollisionProxyComponent()
{
// UPDATE: We have reparented our component, which already contains ShapeBodySetup.
//BodySetup = GetBodySetup(); //CreateDefaultSubobject(TEXT(“Collision Body Setup”));   // This works correctly.
//BodySetup = NewObject(this);                                                          // This causes crash on engine startup.

SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
SetGenerateOverlapEvents(true);

// We cannot use GetBodySetup() anymore, because our parent class uses UpdateBodySetup() before returning it, potentially causing infinite recursion. 
// Instead, we directly assign to the parent class's BodySetup variable, which is what GetBodySetup() returns.
if (ShapeBodySetup)
{
    ShapeBodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex;
    ShapeBodySetup->bGenerateMirroredCollision = false;
    ShapeBodySetup->bDoubleSidedGeometry = true;
}

SetVisibility(true);
bHiddenInGame = true;
bRenderInMainPass = true;       // Optional, if you don't want to render.
bVisualizeComponent = true;     // Helps editor visualize by creating a sprite.

}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

void UMoolahCollisionProxyComponent::SetCollisionShape(ECollisionComponentType NewType)
{
CurrentShape = NewType;

RebuildCollision();

}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

void UMoolahCollisionProxyComponent::RebuildCollision()
{
// Fill AggGeom:
UpdateBodySetup();

// Refresh render state for debug draw:
RecreatePhysicsState();
MarkRenderStateDirty();

// Ensure collision properties are correct:
SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
SetGenerateOverlapEvents(true);

}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

void UMoolahCollisionProxyComponent::UpdateBodySetup()
{
// Ensure ShapeBodySetup exists:
switch (CurrentShape)
{
default:
case ECollisionComponentType::Capsule:
CreateShapeBodySetupIfNeeded();
break;
case ECollisionComponentType::Sphere:
CreateShapeBodySetupIfNeeded();
break;
case ECollisionComponentType::Box:
CreateShapeBodySetupIfNeeded();
break;
case ECollisionComponentType::Mesh:
// For mesh, create placeholder. (…will copy AggGeom below!)
CreateShapeBodySetupIfNeeded();
break;
}

// If we still don't have a valid BodySetup, log an error and exit:
if (!ShapeBodySetup)
{
    LOG_WITH_NET_CONTEXT_CONDIF(true, this, FUNCTION_NAME, EFunctionLogCategory::Error,
                                TEXT("Failed to create BodySetup for collision! Collision will not work."));
	return;
}

// Clear existing geometry:
ShapeBodySetup->AggGeom.EmptyElements();

// Fill AggGeom based on current shape:
switch (CurrentShape)
{
    default:
    case ECollisionComponentType::Capsule:
    {
        FKSphylElem Capsule;
        Capsule.Radius = CapsuleExtents.X;
        Capsule.Length = CapsuleExtents.Y;
        ShapeBodySetup->AggGeom.SphylElems.Add(Capsule);
        break;
    }

    case ECollisionComponentType::Sphere:
    {
        FKSphereElem Sphere;
        Sphere.Radius = SphereExtent;
        ShapeBodySetup->AggGeom.SphereElems.Add(Sphere);
        break;
    }

    case ECollisionComponentType::Box:
    {
        FKBoxElem Box;
        Box.X = BoxExtents.X * 2;
        Box.Y = BoxExtents.Y * 2;
        Box.Z = BoxExtents.Z * 2;
        ShapeBodySetup->AggGeom.BoxElems.Add(Box);
        break;
    }

    case ECollisionComponentType::Mesh:
    {
        if (CollisionStaticMesh && CollisionStaticMesh->GetBodySetup())
        {
            ShapeBodySetup->AggGeom = CollisionStaticMesh->GetBodySetup()->AggGeom;
        }
        else
        {
            LOG_WITH_NET_CONTEXT_CONDIF(true, this, FUNCTION_NAME, EFunctionLogCategory::Warning,
                                        TEXT("No valid 'StaticMesh' assigned for collision! Please select one, else the old collision remains."));
        }
        break;
    }
}

ShapeBodySetup->InvalidatePhysicsData();
ShapeBodySetup->CreatePhysicsMeshes();

//Super::UpdateBodySetup();
// You never really wanna call Super, since it's specifically written as a must-override function that causes a crash if not overriden.

}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

FPrimitiveSceneProxy* UMoolahCollisionProxyComponent::CreateSceneProxy()
{
//Super::CreateSceneProxy();

UE_LOG(LogTemp, Warning, TEXT("CreateSceneProxy called for MoolahCollisionProxyComponent."));

#if WITH_EDITOR
return new FMoolahCollisionSceneProxy(this);
#else
return nullptr;
#endif
}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

FBoxSphereBounds UMoolahCollisionProxyComponent::CalcBounds(const FTransform& LocalToWorld) const
{
//Super::CalcBoundingCylinder(LocalToWorld);

switch (CurrentShape)
{
    default:
    case ECollisionComponentType::Capsule:
    {
        FVector Extent(CapsuleExtents.X, CapsuleExtents.X, CapsuleExtents.Y);
        return FBoxSphereBounds(FBox(-Extent, Extent).TransformBy(LocalToWorld));
        break;
    }

    case ECollisionComponentType::Sphere:
    {
        FVector Extent(SphereExtent);
        return FBoxSphereBounds(FBox(-Extent, Extent).TransformBy(LocalToWorld));
        break;
    }

    case ECollisionComponentType::Box:
    {
        FVector Extent(BoxExtents.X, BoxExtents.Y, BoxExtents.Z);
        return FBoxSphereBounds(FBox(-Extent, Extent).TransformBy(LocalToWorld));
        break;
    }

    case ECollisionComponentType::Mesh:
    {
        if (CollisionStaticMesh)
        {
            const FBoxSphereBounds MeshBounds = CollisionStaticMesh->GetBounds();
            return MeshBounds.TransformBy(LocalToWorld);
        }
        break;
    }
}

return Super::CalcBounds(LocalToWorld);

}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

void UMoolahCollisionProxyComponent::OnRegister()
{
Super::OnRegister();

RebuildCollision();

int32 NumShapes = 0;

if (BodyInstance.IsValidBodyInstance())
{
    UBodySetup* BodySetup = BodyInstance.GetBodySetup();
    if (BodySetup)
    {
        // Sum counts of various collision primitives
        NumShapes = BodySetup->AggGeom.GetElementCount();
    }
}

UE_LOG(LogTemp, Warning, TEXT("MoolahCollision Shape Count: %d"), NumShapes);

UE_LOG(LogTemp, Warning, TEXT("Bounds: %s"),
    *CalcBounds(GetComponentTransform()).GetBox().ToString());

UE_LOG(LogTemp, Warning,
    TEXT("MoolahCollision Bounds: Origin=%s Extent=%s SphereRadius=%f"),
    *Bounds.Origin.ToString(),
    *Bounds.BoxExtent.ToString(),
    Bounds.SphereRadius
);

UE_LOG(LogTemp, Warning, TEXT("bVisualizeComponent: %d, bRenderInMainPass: %d"), bVisualizeComponent, bRenderInMainPass);

UE_LOG(LogTemp, Warning, TEXT("IsRegistered: %d, Owner: %s"), IsRegistered(), *GetOwner()->GetName());

}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

#if WITH_EDITOR
void UMoolahCollisionProxyComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);

GHOST_BOOL(bHasMadeChanges, false);
LOG_WITH_NET_CONTEXT_CONDIF(true, this, FUNCTION_NAME, EFunctionLogCategory::Warning,
                            TEXT("Attempting to change a property inside 'MoolahCollisionProxyComponent'."));

if (PropertyChangedEvent.Property)
{
    const FName PropertyName = PropertyChangedEvent.Property->GetFName();

    if (PropertyName == GET_MEMBER_NAME_CHECKED(UMoolahCollisionProxyComponent, SphereExtent) ||
        PropertyName == GET_MEMBER_NAME_CHECKED(UMoolahCollisionProxyComponent, CapsuleExtents) ||
        PropertyName == GET_MEMBER_NAME_CHECKED(UMoolahCollisionProxyComponent, BoxExtents))
    {
        RebuildCollision();

        bHasMadeChanges = true;
        LOG_WITH_NET_CONTEXT_CONDIF(true, this, FUNCTION_NAME, EFunctionLogCategory::Log,
			                        TEXT("Updated %s dimension(s)!"), *PropertyName.ToString());
    }

    if (PropertyName == GET_MEMBER_NAME_CHECKED(UMoolahCollisionProxyComponent, CollisionStaticMesh))
    {
        if (CollisionStaticMesh && CurrentShape == ECollisionComponentType::Mesh)
        {
            //SetCollisionShape(CurrentShape);  // We don't need to assign CurrentShape again, bypass with RebuildCollision().
            RebuildCollision();

            bHasMadeChanges = true;
            LOG_WITH_NET_CONTEXT_CONDIF(true, this, FUNCTION_NAME, EFunctionLogCategory::Log,
				                        TEXT("Updated static collision mesh!"));
        }

        LOG_WITH_NET_CONTEXT_CONDIF(CollisionStaticMesh && CurrentShape != ECollisionComponentType::Mesh, this, FUNCTION_NAME, EFunctionLogCategory::Error,
                                    TEXT("You attempted changing the Static Mesh without selecting the shape first; Select the Static Mesh Collision to propagate the change."));
        LOG_WITH_NET_CONTEXT_CONDIF(!CollisionStaticMesh && CurrentShape == ECollisionComponentType::Mesh, this, FUNCTION_NAME, EFunctionLogCategory::Error,
                                    TEXT("The Static Mesh you have chosen as your collision model is invalid!"));
    }
}

// Final log; Occurs if no relevant property changes were detected.	
LOG_WITH_NET_CONTEXT_CONDIF(!bHasMadeChanges, this, FUNCTION_NAME, EFunctionLogCategory::Warning,
                            TEXT("No properties were modified!"));

}
#endif

/------------------------------------------------------------------------------------------------------------------------------------------------------/

// Don’t need it, BodySetup is already defined in parent class.
//UBodySetup* UMoolahCollisionProxyComponent::GetBodySetup()
//{
//    return BodySetup;
//}

/------------------------------------------------------------------------------------------------------------------------------------------------------/

feel free to ignore custom debug macros, they don’t do anything special, though if anyone wants the code for them I’d be more thn willing to share!

images: