Здравствуйте меня зовут Дмитрий. Я занимаюсь созданием компьютерных игр на Unreal Engine в качестве хобби.
Как вы знаете недавно вышла Mirror's edge 2. Судя по отзывам критиков игра получилась очень слабая. И вы наверно уже захотели сделать свой Mirror's edge. Поэтому сегодня я расскажу как создать компонент движения, чтобы ваш персонаж двигался как героиня Mirror's edge.
Здесь будет описано создание компонента движения (дальше КД) который позволит персонажу:
1) Запрыгивать на стену.
2) Бегать по стене.
3) Перескакивать через небольшие препятствия
4) Делать ускорение при непрерывном беге
5)Делать подкат при нажатие Shift
6) Скатываться с наклонных поверхностей
7) А также мы создадим интерактивный объект веревку по которой тоже можно будет скатится
Все исходники приведены в конце статьи.
Если вы впервые слышите о компоненте движения, то вам лучше всего пройти урок Epic 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.