Star Trek Elite Force Remake Game Development Blog #2 - Building Out the Weapon System
- adamtedders
- Nov 29, 2024
- 9 min read
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.

Breaking Down Elite Force's Weapon System
Before building out the weapon system for my remake, I needed to breakdown how the weapon system worked in the OG game and get a feel for the weapons and combat. Also, it's a good way to figure out what worked in the OG weapon system, and what can be improved upon. Below is my breakdown of the weapon system in Elite Force:
Feature | Description | Can be Improved Upon? |
Weapon Ammo Type - Phaser/Beam | Your basic re-chargeable ammo type that the player falls back to when they're out of other ammo types | Not really |
Weapon Ammo Type - Energy | Ammo Type used on federation class of weapons. Has a finite amount of ammo that the player has to find ammo for throughout the level either from pickups or recharge points | Not really, I like that federation has it's own ammo type |
Weapon Ammo Type - Crystal | Ammo type used by alien class of weapons. Finite amount of ammo so player has to find more ammo throughout the levels from pickups or recharging points | Not really, just like with energy weapons separating it out adds to it's uniqueness. |
Reloading | There is no reloading in the game at all as each weapon takes ammo from an ammo pool | I really like this as it makes sense in universe and for the gameplay style that they're going for. However, I might add in the option to toggle in reloading, allowing me as a developer to have that option if I want |
Weapon Collision | Done either through raycasting (line trace) or projectiles but this will depend on the weapon | I really like this implementation already so no need to change. |
Weapon Specialisations | Some weapons work better against certain enemies. For instance, the I-MOD is the only weapon that can be used against the Borg as they cannot adapt to it! | Won't be changing this feature at all, it adds so much depth to the game. |
Fire Modes | Each weapon has a primary and alt fire modes, with the alt being the more powerful but more costly. | I really like this so won't be changing it as these different fire modes allow for different tactics. For example, a weapon later in the game allows for bouncing bullets which is very useful for hitting enemies behind cover! |
Weapon Limit | You have no weapon limit in game, as in-universe you have a teleporter buffer on your toolbelt holding your weapons. This is quite a cool way to explaining it away, instead of the player having to suspend disbelief that somehow you're holding an entire arsenal of weapons on your back or in your pockets. | For the type of game it's going for this really works but it might be a nice option to implement this as a toggle. |
No Aiming Down Sights but has Zoom | No Aim Down Sights as this is a game from the early 2000s way before Call of Duty. It does have a zoom which is useful for shooting enemies at a distance | This is something that I'm debating on changing/updating. I don't really like it at all as you never really use as your weapons are already pretty accurate. Also, it's just very clunky and makes movement impossible when using it. |
With the breakdown of the weapon system complete, the next step is to rebuild and update in my remake.
Building the Weapon Component
Following good design principles and making the code more reusable/maintainable, I decided to go with a weapon component, which can be attached to the player, any other character or actor if needed. The weapon component is responsible for these aspects:
Primary Fire
Alt Fire (Secondary Fire)
Updating ammo, and keeping track of ammo pools
Switching Weapons, and updating what the current weapon is
Projectile Weapon or Raycasting Weapon
Weapon Sounds
Primary Fire & Alt Fire (Secondary Fire)
Primary Fire and Secondary Fire were the first elements of the weapon component that I worked on, as I wanted to get these working before weapon switching, ammo tracking and so on. I also wanted the primary and alt fires to feel responsive to player input and fun to play around with. Below is a video of primary and alt fire in action!
To get the primary and alt fire feeling right took a lot of tweaking, which was made easy with the weapon data asset I made (will talk about this later). Below is a small snippet of code for the primary and alt fire modes
void UWeaponComponent::PrimaryFire(const FInputActionValue& Value)
{
UE_LOG(LogTemp, Warning, TEXT("Primary Fire Called!"));
//character->PlayWeaponSound(currentWeapon->PrimaryFireSound);
// Abort if the player has no ammo
if (!HasAmmo()) {
return;
}
// Abort if current fire state is secondary
if (currentFireState == EFireState::ESecondaryFire) {
return;
}
// Play Fire Animation
if (currentWeapon->WeaponFire() != nullptr) {
UAnimInstance* AnimInstance = GetAnimInstance();
if (AnimInstance != nullptr) {
character->GetWeaponAudioComponent()->SetBoolParameter("AltFire", false);
AnimInstance->Montage_Play(currentWeapon->WeaponFire(), 1.f);
// Play Primary Fire Sound
UGameplayStatics::PlaySound2D(GetWorld(), currentWeapon->PrimaryFireSound());
EFireType currentFireType = currentWeapon->FireType();
if (currentFireType == EFireType::E_Ray) {
FireRay(currentWeapon->BaseWeaponDamage());
FireParticleEffect(currentWeapon->PrimaryFireParticleEffect());
}
else if (currentFireType == EFireType::E_Projectile) {
FirePrimaryProjectile();
}
DecreaseAmmo(currentWeapon->WeaponType(), currentWeapon->PrimaryAmmoUsage());
}
}
currentFireState = EFireState::EPrimaryFire;
}void UWeaponComponent::SecondaryFire(const FInputActionValue& Value)
{
UE_LOG(LogTemp, Warning, TEXT("Secondary Fire Called!"));
// Abort if the player has no ammo
if (!HasAmmo()) {
return;
}
// Abort if current fire state is primary
if (currentFireState == EFireState::EPrimaryFire) {
return;
}
// Play Fire Animation
if (currentWeapon->WeaponFire() != nullptr) {
UAnimInstance* AnimInstance = GetAnimInstance();
if (AnimInstance != nullptr) {
character->GetWeaponAudioComponent()->SetBoolParameter("AltFire", true);
AnimInstance->Montage_Play(currentWeapon->WeaponFire(), 1.f);
// Playing the Alt Fire Sound
UGameplayStatics::PlaySound2D(GetWorld(), currentWeapon->AltFireSound());
EFireType currentFireType = currentWeapon->FireType();
if (currentFireType == EFireType::E_Ray) {
FireRay(currentWeapon->AltWeaponDamage());
FireParticleEffect(currentWeapon->AltFireParticleEffect());
}
else if (currentFireType == EFireType::E_Projectile) {
FireAltProjectile();
}
DecreaseAmmo(currentWeapon->WeaponType(), currentWeapon->AltAmmoUsage());
}
}
currentFireState = EFireState::ESecondaryFire;
}With the primary and alt fire functionality implemented, the next step was to start building upon it. For instance, some weapons in Elite Force fire out projectiles such as grenades or bouncing bullets. So the next step is to implement these projectiles into the project. This lead to me creating a Projectile Base Actor Class that will contain all of the basic functionality for the projectile to work but can be expanded upon in child classes. Below is a small snippet of the initialisation code for a projectile.
// Initlializing the Projectile and its settings
void AProjectile::Initialize(float _initialSpeed, float _maxSpeed, bool _bounce,
bool _gravity, float _damage, FVector _velocity, UNiagaraSystem* particleEffect,
UNiagaraSystem* _particleEffectImpact, USoundBase* _impactSound)
{
// Setting the Projectile Movement
if (ProjectileMovement) {
ProjectileMovement->InitialSpeed = _initialSpeed;
ProjectileMovement->MaxSpeed = _maxSpeed;
ProjectileMovement->bShouldBounce = _bounce;
ProjectileMovement->ProjectileGravityScale = _gravity ? 1.0f : 0.0f;
ProjectileMovement->Velocity = _velocity;
}
// Setting Niagara Component
if (NiagaraComponent) {
NiagaraComponent->SetAsset(particleEffect);
NiagaraComponent->Activate(true);
}
// Setting Impact Particle Effect
if (_particleEffectImpact) {
particleEffectImpact = _particleEffectImpact;
}
// Setting up Collision
if (CollisionComp) {
CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &AProjectile::OnHit);
}
// Setting Projectile Damage
damage = _damage;
// Setting Impact Sound
if (_impactSound) {
impactSound = _impactSound;
}
}With the base projectile class implemented, I wanted to make the grenade projectile for scavenger gun which was it's alt fire. To do this I created a the projectile explosive sub-class of the projectile actor allowing to expand and change it's hit behaviour. Below is what it current looks like in game (particle effects are placeholders for the moment)
Updating Ammo and Keeping Track
With weapon firing all done for the moment, the next step was to add in the functionality to increase, decrease and return ammo. The original game contained three ammo types, so I had to implement this into my project, which lead to me creating 3 ammo pools which are: beam, energy and crystal. I also created functions for increasing, decreasing and returning ammo, which can be seen below here.
// Decrease the ammo from the current weapon type ammo pool
void UWeaponComponent::DecreaseAmmo(EWeaponType weaponType, int changeAmount)
{
if (weaponType == EWeaponType::E_Energy) {
energyAmmoPool -= changeAmount;
energyAmmoPool = FMath::Clamp(energyAmmoPool, 0, MAX_ENERGY_AMMO_POOL);
return;
}
else if (weaponType == EWeaponType::E_Crystal) {
crystalAmmoPool -= changeAmount;
crystalAmmoPool = FMath::Clamp(crystalAmmoPool, 0, MAX_CRYSTAL_AMMO_POOL);
return;
}
}
void UWeaponComponent::IncreaseAmmo(EWeaponType weaponType, int changeAmount)
{
if (weaponType == EWeaponType::E_Energy) {
energyAmmoPool += changeAmount;
energyAmmoPool = FMath::Clamp(energyAmmoPool, 0, MAX_ENERGY_AMMO_POOL);
return;
}
else if (weaponType == EWeaponType::E_Crystal) {
crystalAmmoPool += changeAmount;
crystalAmmoPool = FMath::Clamp(crystalAmmoPool, 0, MAX_CRYSTAL_AMMO_POOL);
return;
}
}
// Getting the current ammo pool of the selected weapon
int UWeaponComponent::GetCurrentAmmoPool()
{
switch (currentWeapon->WeaponType()) {
case EWeaponType::E_Energy:
return GetEnergyAmmoPool();
break;
case EWeaponType::E_Crystal:
return GetCrystalAmmoPool();
break;
case EWeaponType::E_Beam:
return GetBeamAmmoPool();
break;
}
return 0;
}Overall, this mechanic was quite easy to implement and as you can see in the videos the ammo updates the correct ammo pool when you use the weapon. It also increase when you interact with ammo points or pick up ammo when walking over weapons or ammo objects (this will be talked about further in a later blog post).
Weapon Data Asset
The creation of the weapon data asset was a very important code and design choice as by using a data asset, I can have all the weapon information in a single data structure. Also, data assets allows me as the developer to create weapons very quickly without having to write any code. Below is what a data asset looks like in engine.

The data asset is tied into the weapon component, which will get all of the weapon info from the current weapon data asset. This is a very good design choice, since when I switch weapons I only have to update the current weapon data asset being used. The weapon component has been coded to deal with the change, as it will run an initialisation function updating the model, weapon damage, current pool ammo pool being used, projectiles and so on.
Damage Interface & Health Component
When firing weapons, the most fun and crucial thing is to destroy enemies or objects in the environment. With all of the previous elements implemented, I can now move on to implementing damaging and health aspect into the project.
Health Component
As a developer, I love using components as it follows good design principles and makes it much more reusable! Like with the weapon component, I created a health component that can be attached to any actor, character or object to allow it to have health and be damaged. Unlike, the weapon component though, this is a much simpler component as it's just keeping track of health and firing out an onKilled event dispatcher when health hits 0.
// Updating the health of what this component is attached to
void UHealthComponent::UpdateHealth(float changeAmount)
{
health += changeAmount;
health = FMath::Clamp(health, 0.0f, MAX_HEALTH);
if (health == 0 && !bInvincible) {
onKilled.Broadcast();
}
}Damage Interface
Interfaces are an awesome data type and abstraction of a class as it allows you to implement a feature across multiple different classes, that usually don't relate to each other. Also, when calling an interface function you only have to check if it implements that interface, and if so then call the function you want. Overall, this is so much cleaner because you could possibly repeat your code and implement that function over multiple classes, but this would just be so messy and unmaintainable especially when dealing with large code bases.
This is why I decided to create a damage interface, as the damage interface will not just be used on enemies, allies, the player but it will be used on barrels, box and other items in the environment. Instead of having a million if and cast functions, I can just check to see if weapon a weapon hits an object just check if it implements a damage interface. If so, then call the damage function but if not then don't do anything. Below is an example of what the code look likes
// Check explosive hits array
if (explosiveHits.Num() != 0) {
for (FHitResult explosiveHit : explosiveHits) {
// Check if the explosive hit actor has a damage interface, if so apply damage
if (explosiveHit.GetActor()->GetClass()->ImplementsInterface(UDamageInterface::StaticClass())) {
IDamageInterface::Execute_ApplyDamage(explosiveHit.GetActor(), damage);
}
// Apply Radial Impluse to the component
if (explosiveHit.GetComponent() != NULL) {
explosiveHit.GetComponent()->AddRadialImpulse(GetActorLocation(), 250.0f, 3000.0f, ERadialImpulseFalloff::RIF_Constant, true);
}
}
} With the health component and damage interfaces implemented, I had the functionality to take away health or destroy items if needs be. Overall, the code is very reusable and maintainable; which can be expanded upon. For me, this is very important as I want to make development easy as possible and stop technical debt from building up early on.
What's next?
So far I'm quite happy with my approach to the weapon implementation but I'm sure it will change as I find bugs, build on and improve upon my implementation. Also, I'm sure you've noticed in the videos that I've started to bring over assets from the game into the unreal engine! I will talk about more about this in another blog post, as it took me a while to get these in engine. Probably will be a bit more of a tutorial that you can follow!
However, I'm not too sure what the next feature I'll work on, as I can either look into bringing over some levels or playing around with enemy AI. Will have to go through my Trello board and decide.
I think though I will write a third blog post soon, as I have implemented the user picking up and interacting with items in the world but didn't want to include it this blog post so it's not too large. I've also started to implement a basic HUD into the game, but it's still very early on but I will talk about that at a later date.


Comments