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:



