Tag: Ladder

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