Table of Contents
- Climbing, gliding and movement component created with C++.
- Dialogue System and Quests are created in Blueprints.
- Created cel-shader post-process, landscape, water, sky and vegetation materials.
Climbing Mechanics
To create climbing mechanics, the class inherited from CharacterMovementComponent is extended. As the name describes, the class controls how the character should move, controlling acceleration, speed, and collision.
Header file for the character movement component. The SweepAndStoreWallHits function, a CurrentWallHits array of type FHitResult, is declared. Variables are created to indicate the size of the collision capsule CollisionCapsuleRadius and CollisonCapsuleHalfHeight.
private:
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
void SweepAndStoreWallHits();
UPROPERTY(Category = "Character Movement: Climbing", EditAnywhere)
int CollisionCapsuleRadius = 50;
UPROPERTY(Category = "Character Movement: Climbing", EditAnywhere)
int CollisionCapsuleHalfHeight = 72;
TArray<FHitResult> CurrentWallHits;
FCollisionQueryParams ClimbQueryParams;
The task of the SweepAndStoreWallHits function is to call SweepMultiplyByChannel with the appropriate parameters and store the hits it receives.
void UCustomCharacterMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
SweepAndStoreWallHits();
}
void UCustomCharacterMovementComponent::SweepAndStoreWallHits()
{
const FCollisionShape CollisionShape = FCollisionShape::MakeCapsule(CollisionCapsuleRadius, CollisionCapsuleHalfHeight);
const FVector StartOffset = UpdatedComponent->GetForwardVector() * 20;
const FVector Start = UpdatedComponent->GetComponentLocation() + StartOffset;
const FVector End = Start + UpdatedComponent->GetForwardVector();
TArray<FHitResult> Hits;
const bool HitWall = GetWorld()->SweepMultiByChannel(Hits, Start, End, FQuat::Identity,
ECC_WorldStatic, CollisionShape, ClimbQueryParams);
if (HitWall)
{
CurrentWallHits = Hits;
}
else
{
CurrentWallHits.Reset();
}
}
Depiction of wall hits
Checking if the character can start climbing to change the movement mode to climbing. CanStartClimbing function is created to iterate over the CurrentWallHits to see if the angle of impact is within range.
bool UCustomCharacterMovementComponent::CanStartClimbing()
{
UE_LOG(LogTemp, Warning, TEXT("Can start Climb?"));
for (FHitResult& Hit : CurrentWallHits)
{
UE_LOG(LogTemp, Warning, TEXT("For Can start Climb"));
const FVector HorizontalNormal = Hit.Normal.GetSafeNormal2D();
const float HorizontalDot = FVector::DotProduct(UpdatedComponent->GetForwardVector(), -HorizontalNormal);
const float VerticalDot = FVector::DotProduct(Hit.Normal, HorizontalNormal);
const float HorizontalDegrees = FMath::RadiansToDegrees(FMath::Acos(HorizontalDot));
const bool bIsCeiling = FMath::IsNearlyZero(VerticalDot);
if (HorizontalDegrees <= MinHorizontalDegreesToStartClimbing && !bIsCeiling &&
IsFacingSurface(VerticalDot))
{
UE_LOG(LogTemp, Warning, TEXT("Can climb!"));
return true;
}
}
UE_LOG(LogTemp, Warning, TEXT("Can Not climb!"));
return false;
}
To exclude situations where character climbs to the low height surfaces, function EyeHeightTrace checks if there are surface to climb in front of the character eyes
bool UCustomCharacterMovementComponent::EyeHeightTrace(const float TraceDistance) const
{
FHitResult UpperEdgeHit;
const FVector Start = UpdatedComponent->GetComponentLocation() + (UpdatedComponent->GetUpVector()
* GetCharacterOwner()->BaseEyeHeight);
const FVector End = Start + (UpdatedComponent->GetForwardVector() * TraceDistance);
return GetWorld()->LineTraceSingleByChannel(UpperEdgeHit, Start, End, ECC_WorldStatic,
ClimbQueryParams);
}
Example of the situation
In the OnMovementUpdated function, when the character is ready to climb, SetMovementMode is called, passing EMovementMode::Move_Custom as a parameter so the movement mode will be set to climbing.
void UCustomCharacterMovementComponent::OnMovementUpdated(float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity)
{
if (bWantsToClimb)
{
SetMovementMode(EMovementMode::MOVE_Custom, ECustomMovementMode::CMOVE_Climbing);
}
Super::OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);
}
If the trace line is too short, steep surfaces will not be detected. If it’s too long, it can mistakenly detect the wall that is to far from the player. To solve the problem, the length of the check line is made dependent on the steepness of the surface. The IsFacingSurface function is created, which calculates the check line and calls EyeHeightTrace. Because normalized vectors are used to calculate a vertical point (VerticalDot), the result is a scalar between 0 and 1 (assuming they are always facing the same direction).