Weapon system through components in Unreal Engine 4

  • Tutorial
Hello, in this article I want to share with my readers my views on the approach to developing on Unreal Engine 4 and using such a useful class as Actor Component.

I noticed that different tutorials for Unreal Engine 4 often use a deep and complex class inheritance hierarchy. Although the Unreal Engine 4 engine itself encourages the use of a component approach based on Actor Component.

If the reader is not familiar with what Actor, Actor Component and the Blueprints visual scripting system are, then I recommend that you first read the following materials:

Unreal Engine 4 / Gameplay Programming / Actors or material in Russian Introduction to C ++ Development in UE4 Part 2 ( section Gameplay classes: Object, Actor and Component)
Unreal Engine Tutorial. Part 2: Blueprints

Problem


As an example, consider such a thing as weapons in shooters. It is common practice to create an Actor of the BaseGun class and implement in this class the logic of shooting weapons, scatter when shooting, create a shooting effect, instantiate Projectile (bullet), etc. This class, as a rule, describes most of the functionality of different types of weapons: pistol, rifle, machine. Later, new types of weapons are implemented by inheritance from BaseGun.

But what if a shotgun or sniper rifle is to be added to the game?

Often in this case, descendant classes are created for new types of weapons. For a sniper rifle, the descendant class will implement additional logic to enable / disable the sniper scope and reduce the spread when shooting through the scope. In the case of a shotgun, it will be necessary to rewrite (override) the logic for shooting. After all, a shotgun does not shoot a bullet - it shoots a shot.

It turns out that when a new type of weapon is added to the game, the developer will perform the following set of actions:

  • creating a descendant class from one of the existing weapons or from BaseGun;
  • override or extension of the logic of the parent class.

This is where the problems appear. The deeper the class hierarchy, the more time it takes for the programmer to figure out / remember how the logic for executing the code works. And with this approach, you will have to constantly make changes to the parent classes (up to BaseGun). The reason is that at the time of designing the base classes, the developer will almost certainly not be able to calculate what types of weapons will appear in the final version of the game.

There is another way. Add logic for the shotgun and sniper rifle to the BaseGun class. But over time, we get a bold class (God object) that does “too much” and violates the principle of “divide and conquer”. Doing such classes is a vicious development strategy.

Decision


And if you transfer the logic of weapons to components? First, let's determine the list of actions that a player can do with weapons. All these actions are added to Enum E_WeaponActionType.



To create a weapon, we need two classes: BP_BaseWeapon and BP_Weapon_Action.

BP_BaseWeapon


BP_BaseWeapon is an Actor, for all types of weapons it will be the basis and base class. In BP_BaseWeapon, we will add a CustomAction to each action from E_WeaponActionType. These actions will be the interface between the player and the weapon. Also, BP_BaseWeapon will contain a collection of objects of type BP_Weapon_Action.



BP_Weapon_Action


BP_Weapon_Action is an ActorComponent, the descendants of this class will implement the behavior of the weapon: shot, reload, enable / disable sniper scope, etc. Each BP_Weapon_Action contains a link to the weapon, as well as the type of action that a particular BP_Weapon_Action performs. BP_Weapon_Action also contains a set of public functions for working with it from BP_BaseWeapon (StartUseAction, StopUseAction, IsCanUseAction), as well as several protected functions and properties.



We mark BP_BaseWeapon and BP_Weapon_Action as abstract classes so as not to accidentally instantiate them. But for their descendants we will not do this.



And now what an assault rifle with an underbarrel grenade launcher and sight will look like. This will be a descendant of the BP_BaseWeapon class, in which you need to place three components:

  • BP_Fire_Action - implements the behavior of firing ammunition, has the type Main from E_WeaponActionType;
  • BP_Scope_Action - implements enable / disable the display of a sniper scope, has the type Secondary from E_WeaponActionType;
  • BP_Scope_Action - implements firing from an under-barrel grenade launcher, has the Special type of E_WeaponActionType.

When an event is called in BP_BaseWeapon, the corresponding behavior will be searched for and StartUseAction will be called on it. For example, when calling UseWeaponAction_Main, BP_Fire_Action will be found, and our weapon will fire.



The advantage of this approach is that if a new type of weapon requires a new behavior, we do not rewrite BaseGun or some other parent class and do not override them with logic. We create a descendant of BP_BaseWeapon, and then simply add and configure components that implement the behavior of a new type of weapon. It turns out that we are collecting new classes from ready-made blocks. If there is no behavior, then we simply add a new component based on BP_Weapon_Action.

The use of the described campaign is not limited to the creation of different types of weapons. It can be extended to other gaming subsystems:

  • Gamemode for different game modes;
  • gaming abilities of characters;
  • creating a variety of mobs;
  • the creation of a variety of interactive game objects, etc.

Also popular now: