DEV Community

Cover image for Creating a Loosely Coupled FPS Character in UE5 C++
Roman Stanuch
Roman Stanuch

Posted on

Creating a Loosely Coupled FPS Character in UE5 C++

Introduction
I am currently in the process of creating an FPS game in UE5 inspired by games such as Star Wars Battlefront II (2017). The main trooper gameplay centers around various classes with either a "build your own character" approach that allows the player to select various weapons, abilities, and appearances, or a "special character" approach that is more restrictive in customization options. The following describes how I created such a system that aims to limit tight coupling (especially 2 way dependencies) while allowing for easy creation of units, both player-built and specially defined. I have implemented this design in UE5 using C++.

Base Character
The system starts with a simple BaseCharacter class. This class derives from Unreal's ACharacter class that comes prebuilt with a mesh and movement component. The base character is sparse in implementation as it is meant to be used for both Player and AI characters. The main functionality of the class is to hold an instance of a weapon actor using a SetWeapon() public function that takes in a pointer to a weapon actor, and attaches it to the Character mesh while storing it in a protected EquippedWeapon member variable. To avoid a 2 way dependency, the weapon class does not know anything about the BaseCharacter class, as I felt that everything the weapon needs can be acquired through a "get, don't ask" type of design.

Abilities
Abilities have the opposite dependency structure of the weapon. Instead of the BaseCharacter holding abilities in member variables and accessing their public interface directly, BaseCharacter contains OnAbilityUse delegates that are activated when the controller of the character triggers an ability use. These delegates take the BaseCharacter as a parameter that will be passed to the Ability objects when they bind their OnUse() member functions to the delegates. The reasoning behind this dependency direction is because abilities need to act on the character more than the character needs to act on the abilities. The character really only needs to trigger the abilities and nothing else. An ability that gives the player health for example would need to access the character's health property, but nothing needs to be done by the character itself. I've chosen to derive the Ability class from Unreal's UActorComponent as doing so allows each character to have their own instance of an ability with unique stats and fefstates.

Bringing it Together
While the weapon, ability, and character classes have the framework for interaction with one another, they still need to be connected in some way. To accomplish this, I have decided to create a "Character Factory" inside of the GameMode. It would likely be a good idea to refactor the factory into its own component and attach that to the GameMode, but for the sake of simplicity I added the functionality to the GameMode class itself. The factory contains 2 public methods for spawning characters: SpawnCharacterFromDescription() and SpawnCharacterFromID().
SpawnCharacterFromDescription() takes as a parameter a FCharacterDescription struct that I defined to contain information such as the character's weapons, abilities, and appearance, and it also takes a controller that will possess the newly spawned character. The function spawns a new character derived from the BaseCharacter class and sets the mesh according to the character description. It then spawns a weapon derived from the Weapon class and calls SetWeapon() on the character passing in the newly spawned weapon. Finally, it creates 2 abilities derived from the Ability class by calling AddComponentOfClass() on the character, passing in the ability classes selected in the character description. The OnUse() functions of the abilities are then bound to the proper OnAbilityUse delegates within the character. Finally, the controller parameter has its pawn set to the new character. SpawnCharacterFromID differs in that it takes an FName called "ID" that is used to query a UDataTable of predefined FCharacterDescription structs. These structs define special characters (called Reinforcements and Heroes in SWBF2). Developers can easily make new special characters by defining them within the DataTable editor in the Unreal Editor application. Once querying the table for the correct struct, the function calls SpawnCharacterFromDescription() passing in found struct and the controller it received as a second parameter.

Conclusion
This provides a basic overview of my current system of character creation for my FPS. This system effectively meets my needs while being easy to work with, but I am by no means an expert and any suggestions would be appreciated. In my following posts, I plan to go more into the specifics of this system, along with how I adapt it for multiplayer use.

Top comments (0)