Experiments With Chaos Physics - Using C++
The page PhysicsUsingBlueprints describes the creation of a simple physics example implementing a trebuchet using blueprints.
This page describes implementing the same thing using c++.
Trebuchet C++ Class
Create the class using the Tools | New C++ Class
menu option. Derive the class from the Actor
base class and call it "Trebuchet". The editor will automatically prefix the
class name with "A" so the actual class name will be "ATrebuchet".
Note that this will work even if the project was created as a blueprint project - adding a c++ class will convert the project to a c++ project.
ATrebuchet Header File
This header file looks like this:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Trebuchet.generated.h"
class USphereComponent;
class UPhysicsConstraintComponent;
UCLASS()
class PHYSICS_003_API ATrebuchet : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATrebuchet();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
UPROPERTY(VisibleAnywhere) UStaticMeshComponent* Arm;
UPROPERTY(VisibleAnywhere) UStaticMeshComponent* Base;
UPROPERTY(VisibleAnywhere) UStaticMeshComponent* Ramp;
UPROPERTY(VisibleAnywhere) UStaticMeshComponent* Weight;
UPROPERTY(VisibleAnywhere) USphereComponent* Ball;
UPROPERTY(VisibleAnywhere) UPhysicsConstraintComponent* ArmBaseConstraint;
UPROPERTY(VisibleAnywhere) UPhysicsConstraintComponent* ArmWeightConstraint;
UPROPERTY(VisibleAnywhere) UPhysicsConstraintComponent* CableConstraint;
private:
bool bConstraintBroken = false;
};
In addition to the generated code we have added:
- forward declarations for USphereComponent and UPhysicsConstraintComponent classes
- member variables to hold the static mesh components which form the body of the trebuchet
- member variables for the physics constraints
- a member variable to track whether the cable constraint has been broken when the projectile is fired
ATrebuchet CPP File
The constructor for the ATrebuchet class is shown here:
ATrebuchet::ATrebuchet()
{
PrimaryActorTick.bCanEverTick = true;
static ConstructorHelpers::FObjectFinder<UStaticMesh>
ArmMeshAsset(TEXT("StaticMesh'/Game/Meshes/SM_Arm.SM_Arm'"));
static ConstructorHelpers::FObjectFinder<UStaticMesh>
BaseMeshAsset(TEXT("StaticMesh'/Game/Meshes/SM_Base.SM_Base'"));
static ConstructorHelpers::FObjectFinder<UStaticMesh>
RampMeshAsset(TEXT("StaticMesh'/Game/Meshes/SM_Ramp.SM_Ramp'"));
static ConstructorHelpers::FObjectFinder<UStaticMesh>
WeightMeshAsset(TEXT("StaticMesh'/Game/Meshes/SM_Weight.SM_Weight'"));
Base = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Base"));
Base->SetStaticMesh(BaseMeshAsset.Object);
Base->SetMobility(EComponentMobility::Static);
Base->SetCollisionProfileName(TEXT("BlockAll"));
RootComponent = Base;
Ramp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Ramp"));
Ramp->SetStaticMesh(RampMeshAsset.Object);
Ramp->SetMobility(EComponentMobility::Static);
Ramp->SetCollisionProfileName(TEXT("BlockAll"));
Ramp->SetRelativeLocation(FVector( 0.0, 410.0, 40.0 ));
Ramp->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform );
Arm = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Arm"));
Arm->SetStaticMesh(ArmMeshAsset.Object);
Arm->SetMobility(EComponentMobility::Movable);
Arm->SetRelativeLocation(FVector( 20.000000, 57.132445, 694.646682));
Arm->SetRelativeRotation(FRotator(0.000000, 0.000000, 40.000000));
Arm->SetMassOverrideInKg(NAME_None, 505);
Arm->SetSimulatePhysics(true);
Arm->SetEnableGravity(true);
Arm->SetCollisionProfileName(TEXT("PhysicsActor"));
Arm->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform);
Weight = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Weight"));
Weight->SetStaticMesh(WeightMeshAsset.Object);
Weight->SetMobility(EComponentMobility::Movable);
Weight->SetRelativeLocation(FVector( 10.000000, -165.000000, 640.000000));
Weight->SetSimulatePhysics(true);
Weight->SetMassOverrideInKg(NAME_None,4506);
Weight->SetEnableGravity(true);
Weight->SetCollisionProfileName(TEXT("PhysicsActor"));
Weight->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform);
Ball = CreateDefaultSubobject<USphereComponent>(TEXT("Ball"));
Ball->SetMobility(EComponentMobility::Movable);
Ball->SetRelativeLocation(FVector(0.000000, 190.000000, 140.000000));
Ball->SetSimulatePhysics(true);
Ball->SetMassOverrideInKg(NAME_None,15);
Ball->SetEnableGravity(true);
Ball->SetCollisionProfileName(TEXT("PhysicsActor"));
Ball->SetHiddenInGame(false);
Ball->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform);
ArmBaseConstraint = CreateDefaultSubobject< UPhysicsConstraintComponent >(TEXT("ArmBaseConstraint"));
ArmBaseConstraint->SetRelativeLocation(FVector(10.000000, 0.000000, 740.000000));
ArmBaseConstraint->SetConstrainedComponents( Base, TEXT(""), Arm, TEXT("") );
ArmBaseConstraint->SetLinearXLimit(ELinearConstraintMotion::LCM_Locked, 0);
ArmBaseConstraint->SetLinearYLimit(ELinearConstraintMotion::LCM_Locked, 0);
ArmBaseConstraint->SetLinearZLimit(ELinearConstraintMotion::LCM_Locked, 0);
ArmBaseConstraint->SetAngularSwing1Limit(EAngularConstraintMotion::ACM_Locked, 0 );
ArmBaseConstraint->SetAngularSwing2Limit(EAngularConstraintMotion::ACM_Locked, 0 );
ArmBaseConstraint->SetAngularTwistLimit(EAngularConstraintMotion::ACM_Free, 0);
ArmBaseConstraint->SetDisableCollision(true);
ArmBaseConstraint->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform);
ArmWeightConstraint = CreateDefaultSubobject< UPhysicsConstraintComponent >(TEXT("ArmWeightConstraint"));;
ArmWeightConstraint->SetRelativeLocation(FVector( 15.000000, -168.000000, 883.000000));
ArmWeightConstraint->SetConstrainedComponents(Arm, TEXT(""), Weight, TEXT(""));
ArmWeightConstraint->SetLinearXLimit(ELinearConstraintMotion::LCM_Locked, 0);
ArmWeightConstraint->SetLinearYLimit(ELinearConstraintMotion::LCM_Locked, 0);
ArmWeightConstraint->SetLinearZLimit(ELinearConstraintMotion::LCM_Locked, 0);
ArmWeightConstraint->SetAngularSwing1Limit(EAngularConstraintMotion::ACM_Locked,0);
ArmWeightConstraint->SetAngularSwing2Limit(EAngularConstraintMotion::ACM_Locked,0);
ArmWeightConstraint->SetAngularTwistLimit(EAngularConstraintMotion::ACM_Free,0);
ArmWeightConstraint->SetDisableCollision(true);
ArmWeightConstraint->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform);
CableConstraint = CreateDefaultSubobject< UPhysicsConstraintComponent >(TEXT("CableConstraint"));;
CableConstraint->SetRelativeLocation(FVector( 14.000000, 634.000000, 210.000000));
CableConstraint->SetConstrainedComponents(Arm, TEXT(""), Ball, TEXT(""));
CableConstraint->SetLinearXLimit(ELinearConstraintMotion::LCM_Locked, 0);
CableConstraint->SetLinearYLimit(ELinearConstraintMotion::LCM_Locked, 0);
CableConstraint->SetLinearZLimit(ELinearConstraintMotion::LCM_Locked, 0);
CableConstraint->SetAngularSwing1Limit(EAngularConstraintMotion::ACM_Free,0);
CableConstraint->SetAngularSwing2Limit(EAngularConstraintMotion::ACM_Free,0);
CableConstraint->SetAngularTwistLimit(EAngularConstraintMotion::ACM_Free,0);
CableConstraint->SetDisableCollision(true);
CableConstraint->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform);
}
Static Mesh Loading
For each static mesh we have an object to locate and load it:
static ConstructorHelpers::FObjectFinder<UStaticMesh>
ArmMeshAsset(TEXT("StaticMesh'/Game/Meshes/SM_Arm.SM_Arm'"));
This works for a simple example, in a larger project you would not do this because:
- it forces the mesh assets to be loaded even if they are not in use
- hard coding the asset path is brittle and hard to maintain
For most of the static mesh components we set properties like this:
Weight = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Weight"));
Weight->SetStaticMesh(WeightMeshAsset.Object);
Weight->SetMobility(EComponentMobility::Movable);
Weight->SetRelativeLocation(FVector( 10.000000, -165.000000, 640.000000));
Weight->SetSimulatePhysics(true);
Weight->SetMassOverrideInKg(NAME_None,4506);
Weight->SetEnableGravity(true);
Weight->SetCollisionProfileName(TEXT("PhysicsActor"));
Weight->AttachToComponent(Base, FAttachmentTransformRules::KeepRelativeTransform);
The location and rotation values are simply copied from the blueprint project. This is enough to show physics working from c++.
Properties and Defaults
Note that more properties required setting from c++ than when using the editor. When using the editor, changing the Mobility property to "Movable" also changes the Collision Preset value to "PhysicsActor". In c++ we need to manually set both properties like this:
Weight->SetMobility(EComponentMobility::Movable);
Weight->SetCollisionProfileName(TEXT("PhysicsActor"));
Also note the defaults for components are different depending on how they are created. A USphere component has the Hidden In Game property set to false when created in the blueprint editor and true when created from c++, so we need to explcitly set it in c++:
Ball->SetHiddenInGame(false);
Cable Release Code
This image shows the blueprint which breaks the Physics Constraint once the projectile is travelling in the right direction and at an angle of less than 45 degrees:
This is converted into the c++ code shown here:
void ATrebuchet::Tick(float DeltaTime)
{
check(Ball);
check(CableConstraint);
Super::Tick(DeltaTime);
if (!bConstraintBroken )
{
const FVector Velocity = Ball->GetComponentVelocity();
// assume we firing down X axis,
if (Velocity.X > 0)
{
const float TrajectoryDegress =
FMath::RadiansToDegrees( FMath::Atan(Velocity.Z / Velocity.X) );
if (TrajectoryDegress < 45)
{
CableConstraint->BreakConstraint();
bConstraintBroken = true;
}
}
}
}
Feedback
Please leave any feedback about this article here