top of page

Star Trek Elite Force Remake Game Development Blog #1 - Movement

adamtedders

Disclaimer: This game will not be released publicly, I do not own the rights to Star Trek or to Star Trek Elite Force. This is just a fun fan project that I'm doing in my spare time.


Star Trek Elite Force Fan Remake For Unreal Engine 5

Project Breakdown

I've always been a huge fan of Star Trek, especially Voyager which spawned Elite Force! One of the best Star Trek games ever produced. It was a first person shooter built on the Quake 3 engine developed by Raven Software. The game was released in 2000. Timeline wise the game is taking place sometime during Season 6 of Voyager between episodes "Live Fast and Prosper" and "Muse" (according to Memory Alpha) .


In the game, you play Ensign (Alexander/Alexandria) Munro as part of the hazard team to deal with all of the alien foes that Voyager dealt with week to week. Logically, it does make sense to have the hazard team due to Voyager being 70,000 light years away from home with no backup. It was a pretty great show to watch, with some amazing two parters like "Year of Hell" and "The Killing Game", plus some great characters like Janeway, The Doctor and 7 of 9.


For me, Elite Force was one of the rare Star Trek games where it felt as though you were part of a multi-episode arc of the show due to it's strong gameplay, writing, recreation of sets and getting all of the actors from the show to voice their lines. Not only that, you also get to fight against the Borg, Species 8472 (these guys need to return, especially with modern CGI), Terrans, Hirogen and more. I also loved that each mission was unique with some missions being more stealthy, others actiony, a few horror (Borg levels of course) and some down time to explore the ship.


It's a game that I must have played through loads of times over the years.


Over the last few months, I've always wonder what Elite Force would look like if it was built now in modern engine like Unreal Engine 5. So for a bit of fun and as an awesome side project I want to try and attempt to rebuild Elite Force in Unreal 5. I'm also using this game development project to further progress my C++ abilities by building most stuff in C++ instead of blueprints.


Researching the Movement

The first part of the game that I re-made was the movement. So I loaded up the OG game and played around for a bit to get a feel of what the controls are, and how the player navigates around the world. Below is my breakdown of the controls:


  • Character can move around fast, even walking is fairly quick. On start up, you do need to re-map as movement controls are not set to the standard buttons.

  • You can sprint for as long as you like, there really isn't a stamina

  • Can lean from side to side, but the way they do it in this game is really awkward. You have to hold down E then press A and D to lean.

  • Climb Ladders by moving towards them, then looking up and down to either move up or down the ladder.

  • Can crouch as well which is useful to get into Jeffries tubes and sneak around.

  • You take damage when you fall from above a certain height.

  • You can swim in the game.

With this breakdown done and my notes collected. I was ready to start building out the controls in Unreal.


Remaking the Basics - Sprinting

To help me get started, I used Unreal's built in First Person C++ Template so that I can have basic movement controls implemented in and something that I could build off of.


To ease myself into the project, I decided that I will firstly implement the sprinting mechanics. As the project is using Unreal 5 new input system, I created an input action (IA) for the sprint and added it to the input mapping context that my character uses.


Input Action for Sprinting. It's value type is a digital bool.
Input Action for Sprinting

With the Input Action implemented, I was able to build out the character code to implemented sprinting. Below is the snippet of the code that I wrote for the sprinting functionality.



void AStarTrekEFCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{

	// Sprinting
	EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Triggered, this, &AStarTrekEFCharacter::Sprint);
	EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AStarTrekEFCharacter::Sprint);

}

void AStarTrekEFCharacter::Sprint(const FInputActionValue& Value)
{
	// If Crouching then don't update movement speeds
	if (bCrouching) {
		return;
	}

	GetCharacterMovement()->MaxWalkSpeed = (Value.Get<bool>() ? sprintSpeed : walkSpeed);
	GetCharacterMovement()->MaxFlySpeed = (Value.Get<bool>() ? sprintSpeed : walkSpeed);
}

Remaking the Basics - Crouching

With the sprinting implemented in, the next step for me was to implement the crouching mechanic. This mechanic went through several iterations as at first I was using the built in Character Movement Component crouching but I didn't really like it as the transition from a standing to a crouch state was instant and not smooth.


This lead to me developing my own crouching system that would allow for smooth transition between standing and crouching. To have more control over the speed of the transition, I used a timeline with a float curve. I also added in a check that would stop the user from standing up if something is above their head. This is important to implement as it stops the user from clipping through and getting stuck in bits of the environment. Below is a snippet of code that I wrote for the crouching functionality.



// Toggling the player to crouch or not
void AStarTrekEFCharacter::Crouching()
{
	// If cannot stand then don't try and stand
	if (!CanStand()) {
		return;
	}


	if (bCrouching) {
		bCrouching = false;
		crouchingTimeline.ReverseFromEnd();

		// Setting walking speed back to normal
		GetCharacterMovement()->MaxWalkSpeed = walkSpeed;
		GetCharacterMovement()->MaxFlySpeed = walkSpeed;

	}
	else if(!bCrouching) {
		bCrouching = true;
		crouchingTimeline.PlayFromStart();

		// Setting walking speed to crouching speed
		GetCharacterMovement()->MaxWalkSpeed = crouchingSpeed;
		GetCharacterMovement()->MaxFlySpeed = crouchingSpeed;
		
	}
	
	GetWorld()->GetTimerManager().SetTimer(crouchingHandle, this, &AStarTrekEFCharacter::CrouchTimeline, DELTATIME, true, 0.0f);

}

void AStarTrekEFCharacter::CrouchTimeline() {
	if (crouchingTimeline.IsPlaying()) {
		crouchingTimeline.TickTimeline(DELTATIME);
	}
	else {
		GetWorld()->GetTimerManager().ClearTimer(crouchingHandle);
	}
}

void AStarTrekEFCharacter::CrouchProgress(float Value) {

	float newHeight = FMath::Lerp(96.0f, crouchingHeight, Value);

	// Updating Capsule Height
	GetCapsuleComponent()->SetCapsuleHalfHeight(newHeight);
	
	// Updating Camera Position
	FVector currentCameraHeight = GetFirstPersonCameraComponent()->GetRelativeLocation();
	FVector newCameraHeight = FVector(currentCameraHeight.X, currentCameraHeight.Y, newHeight * 0.67f);
	GetFirstPersonCameraComponent()->SetRelativeLocation(newCameraHeight);

}

// Checking if the user can stand up or not
bool AStarTrekEFCharacter::CanStand() {
	FVector CapsuleLocation = GetActorLocation();
	FVector startingLocation = CapsuleLocation + FVector(0.0f, 0.0f, 30.0f);
	FVector endLocation = CapsuleLocation + FVector(0.0f, 0.0f, 90.0f);

	FHitResult hit;

	bool bHit = UKismetSystemLibrary::SphereTraceSingle(GetWorld(), startingLocation, endLocation, 10, ETraceTypeQuery::TraceTypeQuery1,
		false, TArray<AActor*>(), EDrawDebugTrace::ForDuration, hit, true, FLinearColor::Red, FLinearColor::Green, 1.0f);

	return !bHit;
}

Below is a video of the crouching in action



With the movement basic implemented, I was able to move on to the more complex elements of the movement system.


Complex Movement - Leaning

In the original game you were able to lean. This helped you to look around corners which was especially helpful in firefights and the stealth section later in the game. However, the way it's been implemented is really clunky as mentioned earlier you have to hold down a key then press movement buttons to lean left or right. For me, this made me not really not want to use unless I had to. So in my remake, I decided that it would be better to have leaning to be controller with the Q and E keys which is much simpler for the player.


The code took some time to write as I wanted to get it right and for it to feel good from a player's perspective. For instance, I added in a check that would stop the user from leaning and clipping through a wall. I also implemented smooth transitions for leaning and back to standing up which made it feel better as you were not suddenly teleporting to a new position and rotation which could cause motion sickness. Below is a code snippet for leaning functionality.


// Leaning the player from left to right
void AStarTrekEFCharacter::Lean_Implementation(const FInputActionValue& Value)
{
	bool leanRight = Value.Get<float>() == 1;

	// Reseting Return Lean Handle
	GetWorld()->GetTimerManager().ClearTimer(returnLanHandle);

	float newMaxLeanRot = MAX_LEAN_ROT;
	float newMaxLeanLoc = MAX_LEAN_LOC;

	CalculateMaxLean(newMaxLeanRot, newMaxLeanLoc, leanRight);

	// Setting Lean Rotation
	leanRotation = FRotator(GetControlRotation().Pitch, GetControlRotation().Yaw, leanRight ? newMaxLeanRot : -newMaxLeanRot);
	FRotator newControlRotation = UKismetMathLibrary::RInterpTo(GetControlRotation(), leanRotation, GetWorld()->GetDeltaSeconds(), LEAN_SPEED);
	GetController()->SetControlRotation(newControlRotation);


	// Setting Lean Location 
	FVector cameraLocation = FirstPersonCameraComponent->GetRelativeLocation();
	leanLocation = FVector(cameraLocation.X, leanRight ? newMaxLeanLoc : -newMaxLeanLoc, cameraLocation.Z);
	FVector newCameraLocation = UKismetMathLibrary::VInterpTo(cameraLocation, leanLocation, GetWorld()->GetDeltaSeconds(), LEAN_SPEED);
	FirstPersonCameraComponent->SetRelativeLocation(newCameraLocation);


}

// Calculating the max lean angle
void AStarTrekEFCharacter::CalculateMaxLean(float& newMaxRotation, float& newMaxLocation, bool leanRight)
{

	FVector camareLocation = GetActorLocation() + (GetActorRightVector() * (leanRight ? (MAX_LEAN_LOC + MAX_LEAN_ROT) : -(MAX_LEAN_LOC + MAX_LEAN_ROT)));
	FHitResult hit;

	bool bHit = UKismetSystemLibrary::SphereTraceSingle(GetWorld(), camareLocation, camareLocation, 10, ETraceTypeQuery::TraceTypeQuery1,
		false, TArray<AActor*>(), EDrawDebugTrace::None, hit, true, FLinearColor::Red, FLinearColor::Green, 1.0f);

	if (hit.GetActor()) {
		newMaxRotation = MAX_LEAN_ROT * 0.1;
		newMaxLocation = MAX_LEAN_LOC * 0.1;
		return;
	}
	else {
		newMaxRotation = MAX_LEAN_ROT;
		newMaxLocation = MAX_LEAN_LOC;
		return;
	}

}

// Reseting Leaning to Standing up Straight position
void AStarTrekEFCharacter::ResetLeaning()
{
	// Reset Control Rotation
	leanRotation = FRotator(GetControlRotation().Pitch, GetControlRotation().Yaw, 0.0f);
	FRotator newControlRotation = UKismetMathLibrary::RInterpTo(GetControlRotation(), leanRotation, GetWorld()->GetDeltaSeconds(), LEAN_SPEED);
	GetController()->SetControlRotation(newControlRotation);

	// Reset Camera Location
	FVector cameraLocation = FirstPersonCameraComponent->GetRelativeLocation();
	leanLocation = FVector(cameraLocation.X, 0.0f, cameraLocation.Z);
	FVector newCameraLocation = UKismetMathLibrary::VInterpTo(cameraLocation, leanLocation, GetWorld()->GetDeltaSeconds(), LEAN_SPEED);
	FirstPersonCameraComponent->SetRelativeLocation(newCameraLocation);

	if (newCameraLocation.Equals(leanLocation, 1.0f)) {
		GetWorld()->GetTimerManager().ClearTimer(returnLanHandle);
	}
}

Below is a video of the leaning in action.



Complex Movement - Climbing Ladders

The final complex movement that I had to implement was the ability to the user to climb ladders. In the original game, you climbed ladders by walking up to them and looking up and down to control the direction you would want to go in. Unlike leaning, I really like this way of climbing ladders as it's quick and easy to get to grips with.


This mechanic took sometime to implement, and lead to me looking online for solutions. I found a really great video from Ryan Laley which helped me make the ladder climbing mechanic. From watching this video I was able to implement my own version of climbing a ladder, it also helped implement a check so that the user would only climb the ladder when they were actually facing it. I did also make some custom adjustments because at first the it was a bit floaty as it was using the flying part of the character movement mechanic, so I increased braking deceleration flying to make it less floaty.


Braking Deceleration Flying Increased to 600 from 0. The current max fly speed is
Braking Deceleration Flying Increased to 600 from 0

Below is a snippet of code that I wrote for implement the climbing ladder functionality.


// Called every frame
void AClimbableLadder::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	FVector cameraLocation = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0)->GetCameraLocation();
	FVector cameraForwardDirection = cameraLocation + (UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0)->GetActorForwardVector() * 250.0f);
	FHitResult hit;

	UKismetSystemLibrary::LineTraceSingle(GetWorld(), cameraLocation, cameraForwardDirection, ETraceTypeQuery::TraceTypeQuery1, 
		false, TArray<AActor*>(), EDrawDebugTrace::ForDuration, hit, false, FLinearColor::Red, FLinearColor::Green, 1.0f);

	if (hit.GetActor() == this) {
		if (player) {
			player->SetOnLadder(true);
		}
	}

}

void AClimbableLadder::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor && OtherActor != this) {

		if (!player) {
			player = Cast<AStarTrekEFCharacter>(OtherActor);
		}
		
		if (player) {
			SetActorTickEnabled(true);
		}
	}
}

void AClimbableLadder::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OtherActor && OtherActor != this) {

		player = Cast<AStarTrekEFCharacter>(OtherActor);

		if (player) {
			player->SetOnLadder(false);
			SetActorTickEnabled(false);
			player = NULL;
		}
	}
}

Below is a video of the climbing ladder in action



What's next?

I'm not too sure what could possibly be next to be honest. I've implemented most of the movement mechanics, so for instance I could move on from this and move on to remaking the shooting mechanics. This would be pretty fun, as the game did have overall 9 weapons, with three different types of weapon and each weapon had an alternative fire mode. Also each weapon was designed for specific situations, so it's going to be fun breaking down the weapon mechanics.


However, I've got some ideas to make movement more fluid such as adding in a slide if you're sprinting and then go into crouch. This could make combat situations and level traversal quite fun.


Overall though, I'm having fun so far as I love breaking down and studying one of my most favourite games. I'm also proud that I've done most of the coding through C++ even if I start out in Blueprints first before converting to C++.


Comments


2021 Adam Tedder 

  • Twitter
  • LinkedIn
  • YouTube
  • Grey Instagram Icon
bottom of page
Mastodon