Experiments With Chaos Physics - Using C++
Introduction
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 c++ 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 <= 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