Ladders

Implementation:

Setup:

  • ALadder : public AActor, public IInteractable
  • We dynamically create the ladder by setting the LadderHeight and then add an instanced mesh and fixing the LadderBox, TopPoint, and BottomPoint.
// HISM Ladder Mesh
FTransform InstanceTransform;
const FVector LadderMeshSize = HISLadder->GetStaticMesh()->GetBoundingBox().GetSize();
for (int8 i = 0; i < LadderHeight; ++i)
{
	InstanceTransform.SetLocation(FVector(0.0, 0.0, LadderMeshSize.Z * i));
	HISLadder->AddInstance(InstanceTransform);
}

// Sizing the Ladder Collision Box
if (LadderBox)
{
	LadderMeshHeight = LadderMeshSize.Z * LadderHeight;
	const double LadderBoxHeight = LadderMeshHeight / 2.0;
	LadderBox->InitBoxExtent(FVector(LadderMeshSize.X, LadderWidthBuffer, LadderBoxHeight + LadderUpperBuffer));
	LadderBox->SetRelativeLocation(FVector(0.0, LadderWidthBuffer, LadderBoxHeight + LadderUpperBuffer));

	LadderBoxes->InitBoxExtent(FVector(LadderMeshSize.X, LadderWidthBuffer, 50.0));
	LadderBoxes->SetRelativeLocation(FVector(0.0, -50.0, LadderMeshHeight + 25.0));

	// Sets the UArrowComponent TopPoint and BottomPoint locations & rotations
	TopPoint->SetRelativeLocation(FVector(0.0, -50.0, LadderMeshHeight + 15.0));
	TopPoint->SetRelativeRotation(FRotator(0.0, -90.0, 0.0));
	BottomPoint->SetRelativeLocation(FVector(0.0, 75.0, 0.0));
	BottomPoint->SetRelativeRotation(FRotator(0.0, 90.0, 0.0));
}
  • While on the ladder, we want to limit the character’s ability to look left & right (yaw). This prevents the character from rotating 360 degrees around the ladder.
if (Character && Character->GetPlayerCameraManager())
{
	Character->GetPlayerCameraManager()->ViewYawMax = MaxYaw;
	Character->GetPlayerCameraManager()->ViewYawMin = MinYaw;
}

Interacting:

  • The UInteractableComponent requires an Actor to adhere to the IInteractableInterface which then requires the Actor to implement OnInteract().
  • So, when a APawn interacts with the ALadder, we have to determine what should happen:
    • If the Pawn is already on the ladder, then we dismount.
    • I chose to dismount by “launching” the character off the ladder a bit.
if (Character)
{
	const FVector LaunchDir = BottomPoint->GetForwardVector() * 100.0;
	Character->LaunchCharacter(LaunchDir, true, false);
	StopClimbing(Character);
}
  • If the Pawn is not on the ladder, then we check if the APawn can interact with said ladder, if so we “mount” the ladder.
  • Attaching the Character to the Ladder:
const double AttachHeight = Hit.ImpactPoint.Z;
if ((AttachHeight > (BottomPoint->GetComponentLocation().Z + 25.0)) && (AttachHeight < LadderMeshHeight))
{
	const FVector AttachPoint = CharacterDistanceFromLadder->GetComponentLocation();
	const FVector TargetLoc = FVector(AttachPoint.X, AttachPoint.Y, AttachHeight);
	const FRotator TargetRot = FRotator(0.0, GetActorRotation().Yaw - 90.0, 0.0);
	Interpolate(Character, TargetLoc, TargetRot, InterpSpeed);

	Character->bUseControllerRotationYaw = false;
	return true;
}

Moving Up & Down:

  • As the character moves up & down the ladder, we want to check if the character is near the TopPoint or BottomPoint.
    • Which is simply done by calculating the distance between the character and the points.
    • If the character is near either point, then we interpolate the character from its current location to the point it’s closest to.
if (DistFromTopPoint < MAXDISTANCEFROMTOPPOINT)
{
	//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Can Dismount to Top"));

	StopClimbing(Character);
	const FVector LandingPoint = TopPoint->GetComponentLocation();
	const FVector TargetLoc = FVector(LandingPoint.X, LandingPoint.Y, LandingPoint.Z + Character->GetDefaultHalfHeight());
	const FRotator TargetRot = TopPoint->GetComponentRotation();
	// Interpolate up
	Interpolate(Character, FVector(Character->GetActorLocation().X, Character->GetActorLocation().X, LandingPoint.Z + Character->GetDefaultHalfHeight()), TargetRot, InterpSpeed / 2.0f);
	// Interpolate over
	Interpolate(Character, TargetLoc, TargetRot, InterpSpeed / 2.0f);

	return true;
}

if (DistFromBottomPoint < MAXDISTANCEFROMBOTPOINT)
{
	//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Can Dismount to Bottom"));

	StopClimbing(Character);
	const FVector LandingPoint = BottomPoint->GetComponentLocation();
	const FVector TargetLoc = FVector(LandingPoint.X, LandingPoint.Y, LandingPoint.Z + Character->GetDefaultHalfHeight());
	Interpolate(Character, TargetLoc, Character->GetActorRotation(), InterpSpeed);

	return true;
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *