C++ programming language, just like a regular English language, has concept of “genericity”. For instance, the word “bank“, in English, is generic in the sense that the word can mean an institution (noun) or an activity (verb). The meaning is inferred from the context of the word usage.
Genericity introduces certain degree of flexibility in the language. The flexibility covers etymologically related or even, in some cases, unrelated meanings (or links?). For instance, “bank” may refer to financial institution as a noun or, related activity as a verb, or edge of a river as an unrelated (to the institution) noun. Basically, genericity can be viewed as minimizing the vocabulary and maximizing the utility or means to control the complexity of a language.
Similarly, in C++ language, there is a notion of generic programming which is implemented by using templates. Consider the following C++ code
template<typename T>
void Exchange(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
This function, Exchange, can be used for floats or ints alike to exchange the value like so
int a = 10;
int b = 20;
float c = 100;
float d = 200;
Exchange(a, b); // a becomes 20 and b becomes 10
Exchange(c, d); // c becomes 200 and d becomes 100
Note how the function Exchange is used for both ints and floats type. Based on the context, int and float calls, compiler generates two copies of Exchange function, one each for int and float type respectively, using which the values of variables are exchanged.
I am sure there must be a mnemonic-al way to Unreal’s collision configuration, independent from C++ or blueprint. Maybe Stephen’s explanation can demystify parts of the collision. In this blog-post I shall attempt to create an appropriate mnemonic device using abstraction(s).
In Unreal, UPrimitiveComponent is the scene component class with collision specific data and render-able geometry. Any specific object, that you see placed or spawned in the level, having certain shape, probably has a scene component derived from UPrimitiveComponent. The collision settings are configured in the details panel of the appropriate blueprint
The picture above shows collision configurable for capsule component. Similarly there is one set of configurables for Mesh component. Rest of the components have no such collision configurable.
To get an overview of all the collision configurables, one can click on the parent of Capsule Component (BP_FortniteCrystal here) to get the following in Details panel
We note that collision configurables for both the mesh and capsule component are shown which pretty much sums up the collision configurable for the blueprint.
Next, we have the following configurables
Simulation Generates Hit Events
Generate Overlap Events
Can Character Step Up On
Collision Presets
Not sure what point 1 does. Point 2 makes sure that the component is involved in generating overlap events. If you want the ability for a character (a pawn with ability to walk) to step on, check point 3. Finally we come to Collision Presets
The picture above shows some default collision presets (except ZombieTouch and Ragdoll presets which are custom presets, which I may explain later). You may select the preset that best fits the component. For instance in C++ code we have the following equivalent
Mesh3P->SetCollisionProfileName(TEXT("Ragdoll"));// Go to Project Settings -> Collision -> Preset -> Ragdoll
}
...
}
The SetCollisionProfileName selects the Collision Preset, when using C++, during runtime, when zombie character is killed and becomes a mere ragdoll.
The idea is that each Collision Preset (or profile) makes the component sensitive to raycast or spherecasts or some sort of cast (by a channel), sent from external source, appropriately (corresponding to trace).
Channels decide the nature of sensitivity, in a sense, to the trace being done. For instance, in the picture of collision presets below, there are three categories, one custom (WeaponNoCharacter) and two default (Visibility and Camera). BTW, here is Epic’s blog-post on the subject. Also objects can decide the nature of sensitivity to the trace being done.
Traces offer a method for reaching out to your levels and getting feedback on what is present along a line segment, LineTraceMultiByChannel, or sphere tube and all that (SweepTestByChannel).
Consider the following custom collision presets
for, say, skeletal mesh component. This implies that the component registers blocking hit only for WeaponNoCharacter channel trace and ignores Visibility and Camera channel traces.
Similarly the component is sensitive only to the objects of WorldStatic, WorldDynamic, PhysicsBody, and Destructibles category.
A custom trace channel is created in Project Settings
and to use in C++, open Config/DefaultEngine.ini and look for the channel name (here WeaponNoCharacter). You may find something like
SOLID principles for object oriented programming are demonstrated in action here. Whilst coding my FPS game, I observed couple of violations (SO) the second time I modified a section of code. This is the classic example of how context helps in catching such violations. Consider the following hierarchical class arrangement
class SUNOVATECHZOMBIEKILL_API ASTPickupInventory : public ASTPickup{
public:
virtual void GiveTo(Pawn* Target) override;
...
}
class SUNOVATECHZOMBIEKILL_API ASTPickupWeapon : public ASTPickupInventory
{
...
}
and finally implementation of GiveTo() function like so
void ASTPickupInventory::GiveTo(Pawn* Target)
{
if(Target == nullptr)
{
return;
}
Super::GiveTo(Target);
ASTInventory* Existing = Target->FindInventoryType(InventoryType, true);
if (Existing == nullptr)
{
ASTInventory* Inv = nullptr;
// Spawn the inventory type class object and attach to pawn
UWorld* const World = GetWorld();
if (World != nullptr)
{
...
}
else
{
UE_LOG(LogSunovatechZombieKill, Log, TEXT("No world found, can't spawn actor of class %s"), *InventoryType->GetName());
}
// Add inventory to target and equip
if(Target->SwitchToRecentPickup())
{
Target->AddInventory(Inv, true);
}
else // or not
{
Target->AddInventory(Inv, false);
}
// This looks out of place
ASTWeapon* MyWeapon = Cast<ASTWeapon>(Inv);
// Generate circular doubly linked list for switching weapons by rotation
if(MyWeapon)
{
Target->GenerateWeaponCDL();
}
}
else // ok this pickup already exists in inventory
{
// This looks out of place as well
// if weapon, increase the ammo
ASTWeapon* MyWeapon = Cast<ASTWeapon>(Existing);
if(MyWeapon)
{
MyWeapon->AddAmmo(10); // to do: move this code to STPickupWeapon maybe
}
}
}
The “out of place” code may require data member (or member function) of ASTPickupWeapon class, for instance AddAmmo() may require amount belonging to the class.
The SRP (Single Responsibility Principle) says that class should have only one function, here, involving general inventory only. Specializing to ASTWeapon seems responsibility of ASTPickupWeapon. Open/Close principle says that class should be open for extension and closed for modification which also seems like a violation here. This implies violation when weapon specific modifications are applied. Hence if SRP is violated, so is Open/Close principle.
A simple solution is to write overridable methods like so
class SUNOVATECHZOMBIEKILL_API ASTPickupInventory : public ASTPickup{
protected:
/**
* @brief Function for dealing with non-existant and post inventory spawn procedures
*/
virtual void DealWithNonExistentInventory(ASTInventory* NonExistingInventory, Pawn* Target);
/**
* @brief Function for dealing with existing inventory procedure
*/
virtual void DealWithExistingInventory(ASTInventory* ExistingInventory, Pawn* Target);
...
}
by doing this, we have opened passage way for child class ASTPickupWeapon to do ASTWeapon specific computing.
void ASTPickupInventory::GiveTo(Pawn* Target)
{
if(Target == nullptr)
{
return;
}
Super::GiveTo(Target);
ASTInventory* Existing = Target->FindInventoryType(InventoryType, true);
if (Existing == nullptr)
{
ASTInventory* Inv = nullptr;
// Spawn the inventory type class object and attach to pawn
UWorld* const World = GetWorld();
if (World != nullptr)
{
...
}
else
{
UE_LOG(LogSunovatechZombieKill, Log, TEXT("No world found, can't spawn actor of class %s"), *InventoryType->GetName());
}
// Add inventory to target and equip
if(Target->SwitchToRecentPickup())
{
Target->AddInventory(Inv, true);
}
else // or not
{
Target->AddInventory(Inv, false);
}
DealWithNonExistentInventory(Inv, Target);
}
else // ok this pickup already exists in inventory
{
DealWithExistingInventory(Existing, nullptr);
}
}
Recently, whilst making an FPS game, I happen to implement a feature involving blood. The idea is, when you butcher zombies, their post-coagulated(?) blood spills on the floor they are standing or rather creeping on. Clearly we need to use something “other than” UParticleSystem for the purpose because we are not looking those particle effects which look like spray in air
The gif shows two kinds of blood effects. One is the blood-sprayed-in-air effect (particle effect) and other is splat on ground (decal).
For particle system we have the following Unreal C++ declaration
Making an Unreal Engine game solo is quite introspecting experience in a way. Not only you can take independent decisions but also you have to take the responsibility of those decisions and ability to convince others (while presenting the work). Thus there is an element of democracy in the process and if done right, could be a secularist process, in the sense that you constantly get to challenge the dogma. Then this process becomes necessary for proper evolution of everything involved in making of games.
For instance Epic’s global illumination way of making photorealistic graphics (Lumen) can be reasonably questioned and some, if not many, studios have chosen to do without getting into the complexity which is not measured by checking a particular field in the details panel.
For my game with the theme of shooting and killing zombies, I decided to use FSM for different states of weapon for instance firing and just holding. A very apt article on FSM for games is in game programming patterns. A well known game Unreal Tournament also uses FSM for weapons. So I took some code from there and used that in my game.
"These (FSMs) came out of a branch of computer science called automata theory whose family of data structures also includes the famous Turing machine"
A minor glimpse of the design can be seen here. The FSM states that I consider are
USTWeaponStateFiring – When the weapon is firing
USTWeaponStateActive – When weapon is not firing but being held by player
USTWeaponStateInActive – When weapon is not held by player (dropped weapon etc)
Zooming – When player is using zoom feature
They are all derived from a single base class USTWeaponState defined like so
UCLASS(DefaultToInstanced, EditInlineNew, CustomConstructor, Within=STWeapon)
class SUNOVATECHZOMBIEKILL_API USTWeaponState : public UObject
{
...
}
The variable for caching the current state is declared like so
/**
* @brief The present state of the weapon. Basically cache for weapon's
* finite state machine
*
* @note UT uses UUTWeaponState
*/
UPROPERTY(BlueprintReadOnly, Category = "Weapon")
USTWeaponState* CurrentState;
Finally, the code for transitioning of weapon’s state is like so
/**
* @brief FSM's routine for state transition
*
* @param NewState The weapon state to transition to
*/
virtual void GotoState(class USTWeaponState* NewState);
All the possible FSM states are instantiated and initialized in the constructor like so
ActiveState = ObjectInitializer.CreateDefaultSubobject<USTWeaponStateActive>(this,
TEXT("StateActive"));
for (int32 i = 0; i < 2; i++)
{
USTWeaponStateFiring* NewState = ObjectInitializer.CreateDefaultSubobject<USTWeaponStateFiring, USTWeaponStateFiring>(this, FName(*FString::Printf(TEXT("FiringState%i"), i)), false);
}