📜 ⬆️ ⬇️

Creating a motion component for a climbing system in the Unreal Engine

image

Hello, my name is Dmitry. I create computer games on the Unreal Engine as a hobby.

As you know, Mirror's edge 2 came out recently. Judging by the reviews from critics, the game was very weak. And you probably already wanted to make your Mirror's edge. Therefore, today I will tell you how to create a motion component so that your character moves like the heroine of Mirror's edge.
')
Here will be described the creation of the motion component (further CD) that will allow the character:
1) Hop onto the wall.
2) Run along the wall.
3) jump over small obstacles
4) Do acceleration with continuous running
5) Roll up when pressing Shift
6) Roll off sloping surfaces
7) And also we will create an interactive object; a rope along which it will also be possible to roll

All sources are listed at the end of the article.


If you’re hearing about the motion component for the first time, then the best thing to do is to take a lesson from Epic Games .

As a base object for the CD, I used the UCharacterMovementComponent; this motion component already allows the character to go swimming and flying.

UCLASS() class CLIMBINGSYSTEM_API UClimbingPawnMovementComponent : public UCharacterMovementComponent { GENERATED_UCLASS_BODY() public: UFUNCTION(BlueprintCallable, Category = "ClimbingMovement") void SetClimbMode(EClimbingMode _ClimbingMode); UFUNCTION(BlueprintPure, Category = "ClimbingMovement") EClimbingMode GetClimbingMode() const; UFUNCTION(BlueprintPure, Category = "ClimbingMovement") bool CanSetClimbMode(EClimbingMode ClimbingMode); /*Offset from top of climbing surfase*/ UPROPERTY(Category = "ClimbingMovement|Climb", EditAnywhere, BlueprintReadWrite) int32 ClimbDeltaZ; /*Velocyty of climb movement*/ UPROPERTY(Category = "ClimbingMovement|Climb", EditAnywhere, BlueprintReadWrite) float ClimbVelocyty; /*Velocyty of jump from climb state*/ UPROPERTY(Category = "ClimbingMovement|Climb", EditAnywhere, BlueprintReadWrite) float ClimbJumpVelocyty; /*Angle from center when state can change in degres*/ UPROPERTY(Category = "ClimbingMovement|WallRun", EditAnywhere, BlueprintReadWrite) float WallRunLimitAngle; /*Offset from Wall when Wall Run*/ UPROPERTY(Category = "ClimbingMovement|WallRun", EditAnywhere, BlueprintReadWrite) int32 WallOffset; /*Fall Gravity Scale when charecter run on wall*/ UPROPERTY(Category = "ClimbingMovement|WallRun", EditAnywhere, BlueprintReadWrite) float WallRunFallGravityScale; /*Multiplier input vector when charecter run on wall*/ UPROPERTY(Category = "ClimbingMovement|WallRun", EditAnywhere, BlueprintReadWrite) int32 WallRunInputVelocyty; /*Velocyty of jump near wall*/ UPROPERTY(Category = "ClimbingMovement|WallRun", EditAnywhere, BlueprintReadWrite) float WallRunJumpZVelocyty; /*Velocyty of jump from wall run state*/ UPROPERTY(Category = "ClimbingMovement|WallRun", EditAnywhere, BlueprintReadWrite) float WallRunJumpVelocyty; /*Offset from rope of zip line*/ UPROPERTY(Category = "ClimbingMovement|ZipLine", EditAnywhere, BlueprintReadWrite) int32 ZipLineDeltaZ; /*Velocyty*/ UPROPERTY(Category = "ClimbingMovement|ZipLine", EditAnywhere, BlueprintReadWrite) float ZipLineVelocyty; /*Velocyty of jump from Zip Line state*/ UPROPERTY(Category = "ClimbingMovement|ZipLine", EditAnywhere, BlueprintReadWrite) float ZipLineJumpVelocyty; /*Angle of surfase when character slide*/ UPROPERTY(Category = "ClimbingMovement|InclinedSlide", EditAnywhere, BlueprintReadWrite) float InclinedSlideAngle; UPROPERTY(Category = "ClimbingMovement|InclinedSlide", EditAnywhere, BlueprintReadWrite) float InclinedSlideVelosytyForward; UPROPERTY(Category = "ClimbingMovement|InclinedSlide", EditAnywhere, BlueprintReadWrite) float InclinedSlideVelosytyRight; UPROPERTY(Category = "ClimbingMovement|InclinedSlide", EditAnywhere, BlueprintReadWrite) float InclinedJumpVelocyty; /*Velocyty of Run movement*/ UPROPERTY(Category = "ClimbingMovement", EditAnywhere, BlueprintReadWrite) float RunSpeed; /*delay before Run movement in sec*/ UPROPERTY(Category = "ClimbingMovement", EditAnywhere, BlueprintReadWrite) float RunDelay; /*Velocyty of jump near wall*/ UPROPERTY(Category = "ClimbingMovement", EditAnywhere, BlueprintReadWrite) float UnderWallJumpZVelocyty; UPROPERTY(Category = "ClimbingMovement", EditAnywhere, BlueprintReadWrite) FRuntimeFloatCurve SlideVelocytyCurve; /*UCharacterMovementComponent Interfase*/ virtual bool DoJump(bool bReplayingMoves) override; virtual float GetMaxSpeed() const override; virtual void BeginPlay() override; virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; private: EClimbingMode ClimbingMode; EClimbingMode LastClimbingMode; bool BlockClimb; bool BlockWallRun; bool BlockInclinedSlide; FTimerHandle RunTimerHandle; bool bIsRun; float MinSlideTime; float MaxSlideTime; FTimerHandle InclinedSlideTimerHandle; void SetRun(); void DefineClimbMode(); bool SetMode(EClimbingMode ClimbingMode); void UnSetMode(EClimbingMode ClimbingMode); void UnBlockInclinedSlide(); void UnblockClimbState(); void UnblockWallRunState(); bool CheckDeltaVectorInCurrentState(const FVector& InputDeltaVector, FVector& CheckDeltaVector, FRotator& CheckRotation); //Check climb is possibly from Approximate coordinate and return realy coordinate bool CheckDeltaVectorInCurrentState(FVector& CheckDeltaVector, FRotator& CheckRotation); //Check climb is possibly in current character location coordinate and return realy coordinate bool CheckDeltaVectorInCurrentState();//Check climb is possibly in current character location without return new coordinates void MoveTo(const FVector& Delta, const FRotator& NewRotation); }; 


So, as we see, a CD is a state machine that, depending on the value of the variable ClimbingMode, determines one or another behavior model. And now about the main methods:
DoJump - Called when a character is jumping.
TickComponent - The most important function is called every frame. This is where the main code works.
SetClimbMode - Toggles CD from one state to another
DefineClimbMode - Determines which state the CD should switch to.
CanSetClimbMode - determines whether the CD can switch to the desired state at the moment.
CheckDeltaVectorInCurrentState - Gets the vector that the character should move to and if in the new coordinates the character can still be in the specified state, then it returns true, the specified coordinates and the angle of rotation of the character otherwise false.
MoveTo - Moves the character to the desired position.

The UCharacterMovementComponent object works only in conjunction with an ACharacter object. Therefore, the class of our character will be derived from ACharacter, and here it is.
 UCLASS() class CLIMBINGSYSTEM_API AClimbingCharacter : public ACharacter { GENERATED_BODY() public: UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly) USpringArmComponent* CameraSpringArm; UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly) UCameraComponent* Camera; // Sets default values for this pawn's properties //AClimbingCharacter(); AClimbingCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; // Called to bind functionality to input virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override; void MoveForward(float AxisValue); void MoveRight(float AxisValue); void CameraPitch(float AxisValue); void CameraYaw(float AxisValue); UFUNCTION(BlueprintCallable, Category = "Pawn|Character") virtual void Jump() override; UFUNCTION(BlueprintCallable, Category = "ClimbingCharacter") void ChangeView(bool FistPirson); void SwitchView(); void CrouchFunk(); void UnCrouchFunk(); virtual void NotifyActorBeginOverlap(AActor* OtherActor) override; private: /** Pointer to climbing movement component*/ UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) class UClimbingPawnMovementComponent* ClimbingMovement; class AOverlapObject* OverlopObject; class AZipLine* ZipLine; bool bFistPirsonView; USkeletalMeshComponent* ClimbMesh; UCapsuleComponent* ClimbCapsule; friend class UClimbingPawnMovementComponent; }; 


And here we have the first problem. The fact is that an ACharacter object spawns a UCharacterMovementComponent object in its constructor. But we need not a UCharacterMovementComponent, but a UClimbingPawnMovementComponent. In order for an ACharacter to spawn a UClimbingPawnMovementComponent, you need to change the AClimbingCharacter's constructor with this:
 AClimbingCharacter::AClimbingCharacter(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { 

on this:
 AClimbingCharacter::AClimbingCharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass<UClimbingPawnMovementComponent>(ACharacter::CharacterMovementComponentName)) { 

Now the ACharacter will generate what we need.

This is interesting: You probably noticed the word Super, what does it mean? The word Super will replace the name of the base class for the object in which it is used. That is, if I write Super :: Tick (), this means that I called the Tick method from the base class of the object.

Then I will talk about the main methods:

You were probably attracted by the ChangeView method. Yes, by pressing the F button, it will be possible to switch from the first person view to the third view. Although it disagrees with the canon.

The MoveForward, MoveRight, CameraPitch, CameraYaw, CrouchFunk, UnCrouchFunk, Jump methods are responsible for keyboard and mouse input.

The NotifyActorBeginOverlap method works when a character crosses some other object. For all the interactive objects, I created the base class AOverlapObject here it is:
 UCLASS() class CLIMBINGSYSTEM_API AOverlapObject : public AActor { GENERATED_BODY() public: enum EClimbingMode GetObjectType() const; protected: UPROPERTY(Category = ObjectType, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) TEnumAsByte<enum EClimbingMode> ObjectType; }; 

This object contains a EClimbingMode type variable. When a character crosses this object, its Motion Component switches to this state.

So far I have done only one interactive object. AZipLine is such a rope for which the character grabs and rolls down it. If you played Mirror's edge, then you understand what I mean.
 UCLASS() class CLIMBINGSYSTEM_API AZipLine : public AOverlapObject { GENERATED_BODY() public: AZipLine(); virtual void OnConstruction(const FTransform& Transform) override; virtual void PostEditMove(bool bFinished) override; /** The main skeletal mesh associated with this Character (optional sub-object). */ UPROPERTY(Category = ZipLine, VisibleDefaultsOnly, BlueprintReadOnly) class UStaticMeshComponent* StartBase; UPROPERTY(Category = ZipLine, VisibleDefaultsOnly, BlueprintReadOnly) class UStaticMeshComponent* EndBase; UPROPERTY(Category = ZipLine, VisibleDefaultsOnly, BlueprintReadOnly) class USplineComponent* Spline; UPROPERTY(Category = ZipLine, VisibleDefaultsOnly, BlueprintReadOnly) USceneComponent* Pivot; UPROPERTY(Category = ZipLine, VisibleDefaultsOnly, BlueprintReadOnly) UBoxComponent* EndBox; UPROPERTY(Category = ZipLine, EditAnywhere, BlueprintReadOnly) UStaticMesh* RopeMesh; UPROPERTY(Category = ZipLine, EditAnywhere, BlueprintReadOnly) float SplineHeight; #if WITH_EDITORONLY_DATA UPROPERTY() class UArrowComponent* ArrowComponent; #endif protected: TArray<class USplineMeshComponent*> AddedSplineMeshComponents; void SetupSpline(); }; 


The OnConstruction method is similar to the ConstructionScript function in blueprint; it is also called when the ector is moved or changed. True, there is a slight difference, if in the blueprint all the added objects are automatically destroyed during each rebuilding, then in c ++ you have to destroy the objects yourself.

A small demonstration:

Actually this is all as promised source . By popular demand of the population, I posted them on github.

Source: https://habr.com/ru/post/303394/


All Articles