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

в 5:50, , рубрики: c++, climbing, Movement Component, 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<UClimbingPawnMovementComponent>(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<enum EClimbingMode> 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<class USplineMeshComponent*> AddedSplineMeshComponents;

	void SetupSpline();
};

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

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

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

Автор: deema35

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js