Создание компонента движения для системы лазания в Unreal Engine

image

Здравствуйте меня зовут Дмитрий. Я занимаюсь созданием компьютерных игр на Unreal Engine в качестве хобби.

Как вы знаете недавно вышла Mirror’s edge 2. Судя по отзывам критиков игра получилась очень слабая. И вы наверно уже захотели сделать свой Mirror’s edge. Поэтому сегодня я расскажу как создать компонент движения, чтобы ваш персонаж двигался как героиня Mirror’s edge.

Здесь будет описано создание компонента движения (дальше КД) который позволит персонажу:
1) Запрыгивать на стену.
2) Бегать по стене.
3) Перескакивать через небольшие препятствия
4) Делать ускорение при непрерывном беге
5)Делать подкат при нажатие Shift
6) Скатываться с наклонных поверхностей
7) А также мы создадим интерактивный объект веревку по которой тоже можно будет скатится

Все исходники приведены в конце статьи.

Если вы впервые слышите о компоненте движения, то вам лучше всего пройти урок Epick Games там все очень подробно расписано.

В качестве базового объекта для КД я использовал UCharacterMovementComponent это компонент движения уже позволяет персонажу ходить плавать и летать.

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);
        
};

Итак как мы видим КД является конечным автоматом который в зависимости от значения переменной ClimbingMode определяет ту или иную модель поведения. А теперь про основные методы:
DoJump — Вызывается если персонаж прыгает.
TickComponent — Самая важная функция, вызывается каждый кадр. Здесь работает основной код.
SetClimbMode — Переключает КД из одного состояния в другое
DefineClimbMode — Определяет в какое состояние должен переключится КД.
CanSetClimbMode — определяет сможет ли КД переключится в нужное состояние в данный момент.
CheckDeltaVectorInCurrentState — Получает вектор на который должен переместится персонаж и если в новых координатах персонаж все ещё может находится в заданном состояние то возвращает true, уточненные координаты и углом поворота персонажа в противном случае false.
MoveTo — Перемещает персонажа в нужное положение.

Объект UCharacterMovementComponent работает только в паре с объектом ACharacter. Поэтому класс нашего персонажа будет производным от ACharacter вот собственно и он.

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;
};

И тут у нас возникает первая проблема. Дело в том что объект ACharacter порождает объект UCharacterMovementComponent в своем конструкторе. Но нам то нужен не UCharacterMovementComponent, а UClimbingPawnMovementComponent. Чтобы ACharacter породил UClimbingPawnMovementComponent нужно конструктор AClimbingCharacter изменить с такова:

AClimbingCharacter::AClimbingCharacter(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{


на такой:

AClimbingCharacter::AClimbingCharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName))
{


Теперь ACharacter будет порождать то что нам нужно.

Это интересно: Вы наверно заметили слово Super, что оно означает? Слово Super заменят собой имя базового класса для объекта в котором его используют. То есть если я напишу Super: Tick () то это значит что я вызвал метод Tick из базового класса объекта.

Дальше я расскажу про основные методы:

Вас наверно привлек метод ChangeView. Да по нажатию кнопки F можно будет переключатся с вида от первого лица в вид от третьего. Хоть это и расходится с каноном.

Методы MoveForward, MoveRight, CameraPitch, CameraYaw, CrouchFunk, UnCrouchFunk, Jump отвечают за ввод с клавиатуры и мыши.

Метод NotifyActorBeginOverlap срабатывает когда персонаж пересекает какой то другой объект. Для всех интерактивных объектов я создал базовый класс AOverlapObject вот собственно и он:

UCLASS()
class CLIMBINGSYSTEM_API AOverlapObject : public AActor
{
        GENERATED_BODY()
public:
        enum EClimbingMode GetObjectType() const;

protected:
        UPROPERTY(Category = ObjectType, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
        TEnumAsByte ObjectType;
        
};


Этот объект содержит в себе переменную типа EClimbingMode когда персонаж пересекает этот объект, его Компонент движения переключается в данное состояние.

Пока что я сделал только один интерактивный объект AZipLine это такая веревка за которую персонаж хватается и скатывается по ней вниз. Если вы играли в Mirror’s edge, то вы поняли о чем я говорю.

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 AddedSplineMeshComponents;

        void SetupSpline();
};

Метод OnConstruction аналогичен функции ConstructionScript в blueprint он также вызывается при перемещении эктора или его изменении. Правда есть небольшое отличие, если в блюпринте все добавленные объекты автоматически уничтожаются при каждом перестроении, то в c++ нужно самому уничтожать объекты.

Небольшая демонстрация:

Собственно на этом все как и обещал исходники. По многочисленным просьбам населения я разместил их на github.

© Habrahabr.ru