While our game may be running without any issues in the editor or even in…
Inputs with Parameters
Imagine the following case: You’re building your awesome FPS game using UE4 and you’ve built a handgun, an assault rifle and a knife. Now, you want to give the players the ability to equip each weapon by pressing a specific key (let’s say button 1 is the bound input for the handgun equip functionality, button 2 is for the assault rifle and so on). That’s easy, right? I will just implement the following logic and bind it to my inputs:
1 2 3 4 |
void AMyAwesomeCharacter::Equip(AWeapon* WeaponToEquip) { //Logic goes here } |
In order to specify my inputs and hook up the corresponding bindings and logic I will just follow the related post from the official UE4 documentation. When you complete the said tutorial, you may notice that in the binding phase of your game actions you can’t hook up a function with parameters, thus the implemented function for our weapon equipment has gone to waste.
A simple workaround to achieve the functionality we need, is to create three new functions, one for each weapon and use copy-paste to transfer our logic from the Equip function into our newly created functions (but modifying it a bit in order to work for one weapon at a time). After doing the above steps our code will end up like the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void AMyAwesomeCharacter::EquipKnife() { //Equip logic for our weapon (knife in this case) } void AMyAwesomeCharacter::EquipRifle() { //Equip logic for our weapon (assault rifle in this case) } void AMyAwesomeCharacter::EquipHandgun() { //Equip logic for our weapon (handgun in this case) } |
When you find yourself copy and pasting your code logic, chances are there is a better way to implement the functionality you need without replicating bugs everywhere.
After declaring three new functions without parameters we can hook up our logic for our action bindings and be done with it. However, imagine that you’ve made a bug in the first version of your code (which was later copy-pasted into the three functions). This means that each time you find a bug you have to modify three version of the same code, isn’t that a same?
In this post, I will show you a better workaround to overcome the above situation using templates. This will be a lengthy tutorial, so grab your coffee and take a deep breath.
This post is divided into the following parts:
- Templates Explanation (non UE4 Project)
- Binding Inputs with parameters (UE4 Project)
So, let’s get started!
Understanding Templates
In object oriented programming, inheritance, overloading and interfaces provide a way to write flexible code. However, in some cases, this is not enough. Templates allow a function or a class to work with many different data types without the need to re-write the function.
Let’s say that we want to find the max number based on two integer inputs. To do that, we write the following code:
1 2 3 4 |
int max(int a,int b) { return a > b ? a : b; } |
However, later on, we decide that we need to find the max number based on two double inputs. To do so, we overload the above function like so:
1 2 3 4 |
double max(double a,double b) { return a > b ? a : b; } |
Later on, you may need to find the max between two different types which are not covered with the above blocks of code. Notice that in order to find the max between the given inputs, we execute exactly the same logic but with different types. That’s where templates come in!
Function templates provide a way to write a function without knowing the data types we need. You might be thinking, how is this possible? Well, everything will be clear in a bit!
In order to write a function template, we write the following code:
1 2 3 4 5 |
template<typename T> T max(T t1,T t2) { return t1 > t2 ? t1 : t2; } |
Let’s break down the above code in order to better understand what’s happening here!
In the first line, we declared that this is a template function, and inside the brackets (<>) we declared a typename of T. This means that we declared a variable, named T, of type “typename”. This means that we don’t know what is the type of T. It could be int, double or really anything you want it to be (please take into consideration the note at the end of this example).
Then, we declared a function which has a return type of T (again, we don’t know what is the type just yet) which takes two parameters of type T.
Having written the code above, the following lines are valid and will execute without any problem:
1 2 3 |
int int_result=max(5,10); double double_result=max(5.0,10.0); |
When you call a function template, the compiler will determine the data types based on what you provided. In the above example, the compiler can determine that 5 and 10 are integers, so the max(5,10) will return an int type. The same applies for the max(5.0,10.0), the compiler determines that 5.0 and 10.0 are double and will return a double type as a result.
Important note: So…does this mean that the max function can be executed with really anything? The answer is yes….and no. In order for the templated max function to work, the types we use as arguments must overload the > operator. Otherwise, the compiler will give you an error.
Binding Inputs with Parameters
Let’s recall for a moment the first example of this post. We have 3 weapons and we need to provide a function with parameters for our action inputs. To do so, create the following enum in order to determine what type of weapon our character will equip:
1 2 3 4 5 6 |
enum class EWeaponType : uint8 { Knife, HandGun, Rifle }; |
Then, inside your character’s header file, type in the following functions:
1 2 3 4 5 6 |
/*This function handles the weapon equipment*/ void EquipWeapon(EWeaponType EWeaponToEquip); /*Templated version of EquipWeapon which calls the above function*/ template<EWeaponType EWeaponToEquip> void EquipWeapon() { EquipWeapon(EWeaponToEquip); } |
Switch to your character’s source file and type in the following implementation of the EquipWeapon:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void AMyCharacter::EquipWeapon(EWeaponType EWeaponToEquip) { switch (EWeaponToEquip) { case EWeaponType::Knife: { GLog->Log("Equipped knife"); //more logic here break; } case EWeaponType::Rifle: { GLog->Log("Equipped rifle"); //more logic here break; } case EWeaponType::HandGun: { GLog->Log("Equipped handgun"); //more logic here break; } } } |
Then, inside the SetupPlayerInputComponent, add the following logic:
1 2 3 4 5 6 |
//We tell the compiler that we pick the EWeaponType::Knife explicitly InputComponent->BindAction("Knife_Equip", IE_Pressed, this, &AMyCharacter::EquipWeapon<EWeaponType::Knife>); //We tell the compiler that we pick the EWeaponType::Rifle explicitly InputComponent->BindAction("Rifle_Equip", IE_Pressed, this, &AMyCharacter::EquipWeapon<EWeaponType::Rifle>); //We tell the compiler that we pick the EWeaponType::Handgun explicitly InputComponent->BindAction("Handgun_Equip", IE_Pressed, this, &AMyCharacter::EquipWeapon<EWeaponType::HandGun>); |
And we’re done! Even though we have 3 different inputs, we handle them with the same function, but we change the input in every case!
It looks like the documentation tutorial you linked is 404. Any chance you can remake that tutorial or find it again?
It looks like the original ue4 tutorial has been 404’d