🚀 Spring Sale is live! 🚀 Don't miss your chance to get all my products at 30% OFF!  

Get it!

Combat(Attack/Defense) System

Overview

This article briefly introduces the attack and defense process in the GCS combat system to help you better utilize GCS.

I dislike writing this part because it feels more like a design document, and clever programmers can easily reverse-engineer and create their own versions.

Initiating the Attack Process

Any object, including the environment/traps, can initiate an attack. The attack process for humanoid characters in the game generally follows these steps:

战斗(攻防)系统.001

Process Steps

  1. Activate ability: The attacker (player/AI) activates different abilities, such as melee abilities or spell abilities.
  2. Initiate Attack Request: The ability plays different combat animations. You place AttackTrace/BulletTrace animation notification states in the animation and configure the relevant attack request parameters.
  3. Ability Handles Attack Request: The start and end of the AttackTrace notification state will pass the attack request to the Ability through gameplay events (attack information). The Ability will also include some of its own information in the attack information, such as the gameplay effects to be applied.
  4. Melee: If it is a melee request, the character or weapon's collision trace instance (HitBox) will be activated, and the attack information will be passed to the collision trace instance. Once valid targets are detected, the attack information will be applied to those targets.
  5. Bullet: If it is a bullet request, a bullet instance will be created through the bullet subsystem, and the attack information will be passed to the bullet instance. Each bullet instance has its own collision trace instance, and when a valid target is detected, the bullet will apply the attack information to the target.

In the game, other environments and traps can also initiate attacks; they simply need the required attack information.

Before the attack information is applied to the target, you can modify the gameplay effect instances (GameplayEffectSpec/GameplayEffectContainerSpec) generated during the process, such as:

  1. Passing numerical parameters from the attack definition to the gameplay effect.
  2. Adding different tags to the gameplay effect based on conditions for later use (e.g., using tags to indicate whether it is fire damage or magic damage).
  3. Diminishing damage based on the bullet's flight distance.
  4. Adding new targets to the gameplay effect during bullet flight.

The ultimate goal of the attack process is to generate and modify gameplay effect instances and then apply them to the target.

Damage Calculation

The ultimate purpose of initiating an attack is to apply gameplay effects to the target. During the execution of the gameplay effect, specific attributes of the target will be modified. Gameplay effects can modify target attributes in various ways, and the GCS reference content provides a wealth of different effects for your reference, among which:

  • GCS_Execution_Damage offers a default reference implementation of damage calculation logic found in soul-like games.

About the Default Implementation

This implementation is highly general and achieves multi-type damage and damage mitigation (absorption) logic through a minimal number of gameplay attributes. It is not hard-coded, allowing you to add more damage types (like holy damage, holy damage mitigation, etc.). You can use it directly or as a reference to create your own version.

战斗(攻防)系统.002

Thanks to Generic Gameplay Abilities, you can fully leverage the extensive features of GAS through Blueprints without writing C++ code.

Handling the Attack Process

战斗(攻防)系统.003

Combat Flow

CombatFlow is a UObject, and each combat system component can specify a CombatFlow. You should inherit the built-in GCS_BaseCombatFlow and override the relevant functions to implement your custom logic, or reference existing CombatFlow and create a new one.

Handling Attribute Changes

As mentioned in the user guide, the attribute system component forwards all attribute change callbacks to the CombatFlow for processing. Therefore, you only need to override HandleGameplayEffectExecute.

战斗(攻防)系统.004

How to handle attributes and what kind of attack results to produce is entirely determined by your own game mechanics, while the reference implementation provides common implementations found in soul-like games.

Generating Attack Results

After processing attribute changes in HandleGameplayEffectExecute, you should convert the information from that process into attack results based on your game logic and register them with the combat system component.

战斗(攻防)系统.005

For example, after handling IncomingDamage, you should construct the relevant information into an AttackResult structure and register it with the combat system. TaggedValues includes some information generated during this process, such as "actual changed health." You can also customize this here.

Customizing Combat Flow

Attackers can initiate different attack requests, but each target may have a different "handling" for each attack request. You can implement different attack handling processes by customizing GCS_CombatFlow and configuring different CombatFlow classes on the CombatSystemComponent.

For example, if an arrow is shot at an enemy, it should typically trigger a hit reaction. However, if your enemy is very large and you do not want them to react, you can specify a different CombatFlow without needing extensive if/else checks at the attacker level.

When to Use Completely Different Combat Flows?

  1. You don't want a dog to deflect your attack.
  2. You don't want a gigantic boss to fall over just because you scratched its foot.
  3. You don't want a building to dodge your attack.

Attack Request

If you want to initiate any form of attack, you need to use an attack request. GCS has different types of attack requests, such as melee or bullet. Attack requests are generally triggered through animation notification states.

This section explains how to initiate different attack requests. Additionally, all Gameplay Abilities that inherit from GA_GCS_Attack will automatically handle attack requests.

Melee Attacks

When a melee attack ability plays a montage, you can add ANS_GCS_AttackTrace (animation notification state) within the appropriate attack frame range to configure an attack request.

战斗(攻防)系统.006
  • TraceToControls: Defines which collision trace instances need to be activated during the activation of the animation notification state.
  • AttackDefinitionHandle: Points to a specific attack definition in the attack definition table, containing all static data related to "this hit" (such as attack power, cost, etc.).

In the above action, a right-hand weapon is used for the attack, so the detection instance named "RightHandWeapon" (which is bound to the weapon's mesh) should be activated. If the action involves a punch or a kick, you should also activate the corresponding detection instances (like RightHand, LeftFoot, which are bound to the character's mesh).

Ranged Attacks

In montages where bullets need to be fired (e.g., archery), you define the attack request by adding ANS_GCS_BulletTrace in the attack frame.

战斗(攻防)系统.007
  • BulletDefinitionHandle: Points to a specific bullet definition in the bullet definition table, containing all static data related to "this bullet" (such as how many projectiles are actually generated, its attack power, etc.).
  • TargetingSourceType: Determines how the bullet's spawn transform is calculated.
战斗(攻防)系统.008

Custom Attack Request Types

You can create a subclass of GCS_AttackRequest in Blueprints/C++ to add more information and use it in subsequent processes. For example, when you select Custom for TargetingSourceType, you should inherit from GCS_AttackRequest_Bullet to create a new version and implement GetTargetingTransform.

战斗(攻防)系统.009

Using Attack Requests in the Combat Flow

The attack request itself is passed through the GameplayEffectContext in different processes, meaning you can access various information configured in the attack request at any time. For instance, you can retrieve the attack request object via the context and access its associated attack definition.

战斗(攻防)系统.010

Good to Know

  • The attack request is an instantiated UObject, meaning it is not created at runtime but is created in the editor and stored on disk, which is the secret to its efficient network transmission.
  • The attack request is of type Const, meaning it can only store static data for game logic use, and you should not modify it during gameplay.
  • If you define a variable of type AttackRequest in Blueprints, you will see that it allows you to inline create an instance and save it with the outer asset.
战斗(攻防)系统.011
战斗(攻防)系统.012

Attack Definition

Concept

An attack definition is a structure. For every type of attack in the game, there should be a corresponding attack definition. It contains static information for "each hit" in the combat game. A melee ability may play an attack animation but can produce multiple different types of attacks.

Each type of attack/weapon swing/projectile has specific properties associated with it, such as damage type, numerical bonuses, swing direction, etc.

In games like Elden Ring, there are thousands of different attack definitions. Since GCS manages attack definitions using a DataTable and uses soft class/object references, it will not slow down your project's performance.

Creating Attack Definition Tables

As mentioned earlier, when initiating an attack request, you need to specify an attack definition. You can create a new DataTable of type FGCS_AttackDefinition and define all the attack definitions you will use in that table.

Typically, the number of attack definitions depends on the complexity of your game mechanics and how many attack variants exist. For example, the ability "Waterfowl Dance" in Elden Ring has as many as 20 attack definitions, each with different damage types, resilience damage, and associated attributes.

战斗(攻防)系统.013

There are numerous fields to configure, with the most important properties being as follows:

  • AttackTags: You describe the characteristics of the attack by adding game tags. These tags will be added to the attack information, allowing you to use this information for further logic checks when processing attack information.
    • Example: When processing the attack result, you can check if the attack from the attacker has fire attributes and then check if the target is weak to fire. If both conditions are met, you can apply additional effects to the target.
  • SetByCallerMagnitudes: Essentially a key-value pair of GameplayTag -> float, where you can fill in any information that will be passed into the GameplayEffectSpec, allowing for further operations in calculations.
    • Example: If you have a series of combo attacks, the final segment may have a significant damage boost, and you can specify how much here, as long as your calculation accounts for it.

Managing Attack Definition Tables

From my understanding, Elden Ring categorizes different attack definition tables based on weapon types. Therefore, you can organize your attack definition tables as follows: DT_Atk_Sword_A, DT_Atk_Sword_B, DT_Atk_GreatSword_A, DT_Atk_GreatSword_B, etc.

Attack Results

Whenever CombatFlow processes an attack, it generates an attack result and records it in the combat system component, synchronizing it with the client over the network.

When a combat result is generated, GCS triggers different combat feedback based on its content, such as playing hit reactions or generating visual effects through gameplay cues.

Handling Attack Results

In CombatFlow, you can configure different AttackResultProcessors (attack result processors) in a data-driven manner to handle different attack results accordingly.

战斗(攻防)系统.014
战斗(攻防)系统.015

You can refer to the GCS_BaseCombatFlow Blueprint to understand the specific usage.

Built-in Attack Result Processors

Send Gampelay Event

This one allows you to send different gameplay events to attacker/victims based on attack results.

战斗(攻防)系统.016

You can also use TagQuery to route different logic.

For example: only trigger BlockHit when the attack results's source tags have Blocktag. Only trigger hit reaction when attack results's source tags have Hittag.

Custom Attack Result Processors

For example: You can write a "Dmage Statistics Processor" which keeps track of received damage to other log systems.