Section 4: Player Combat
In this section, we learned how to perform attacks and detect when we've hit an enemy.
Section Intro - Player Combat
There are no notes for this lecture.
Grabbing Socket Locations and Rotations
In this lecture, we learned how to grab the location of a socket. You can access information of a socket via the USkeletalMeshComponent class. There are two useful functions, which are the following:
GetSocketLocation- Returns anFVectorof a socket location. Accepts the socket's name.GetSocketQuaternion- Returns anFQuatof a socket rotation. Accepts the socket's name.
Here's how we used it in our code:
FVector StartSocketLocation{ SkeletalComp->GetSocketLocation(Start) };
FVector EndSocketLocation{ SkeletalComp->GetSocketLocation(End) };
FQuat ShapeRotation{ SkeletalComp->GetSocketQuaternion(Rotation) };Adding Tracing to Our Weapon
In this lecture, we started performing tracing using a box shape. To use a box shape, we must provide half the box's size. To do that, we need the width, height, and length. The width and height can be configured via a class variable. As for the length, that will be the distance between the start and end location of our sockets.
double WeaponDistance{
FVector::Distance(StartSocketLocation, EndSocketLocation)
};
FVector BoxHalfExtent{
BoxCollisionLength, BoxCollisionLength, WeaponDistance
};
BoxHalfExtent /= 2; // BoxHalfExtent = BoxHalfExtent / 2
FCollisionShape Box{
FCollisionShape::MakeBox(BoxHalfExtent)
};We used the FCollisionShape::MakeBox() function, which accepts the dimensions as an argument.
The trace was then created with the SweepMultiByChannel() function, which allows us to detect multiple targets instead of the first target.
bool bHasFoundTargets = GetWorld()->SweepMultiByChannel(
OutResults,
StartSocketLocation,
EndSocketLocation,
ShapeRotation,
ECollisionChannel::ECC_GameTraceChannel1,
Box,
IgnoreParams
);Since we're using a multi function, we must store the results in an array with the TArray type. C++ supports arrays but Unreal provides a class that can store a collection of results. The main difference is that the Unreal version has various functions for interacting with the items in the array. When defining a TArray value, we must provide the type of values stored in the array as a generic.
TArray<FHitResult> OutResults;Resources
Drawing Debug Shapes
In this lecture, we decided to draw debug shapes, which are just visible shapes in our world that can represent anything we want. In our case, we want to be able to show what actors our sword are hitting. To do so, we included two files that come with functions to draw boxes.
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetMathLibrary.h"Next, we called the UKismetSystemLibrary::DrawDebugBox() function.
FVector CenterPoint{ UKismetMathLibrary::VLerp(
StartSocketLocation, EndSocketLocation, 0.5f
) };
UKismetSystemLibrary::DrawDebugBox(
GetWorld(),
CenterPoint,
Box.GetExtent(),
bHasFoundTargets ? FLinearColor::Green : FLinearColor::Red,
ShapeRotation.Rotator(),
1.0f,
2.0f
);The following data needs to be supplied.
The world context.
The center location of the box. We used the
UKismetMathLibrary::VLerp()function to help us calculate the location between two vectors.The box's boundaries, which can be grabbed from the collision shape's
GetExtent()function.A color from the
FLinearColortype. The color is based on if we found a target or not.The rotation in degrees. The
ShapeRotationvariable holds the rotation in quaternions. To convert them into degrees, we can use theRotator()function.How long the box should be visible in seconds.
The thickness of the box.
Resources
Storing Animation Montages
In this lecture, we're going to store animation montages in the combat component. This way, we'll be able to play them when the player is attacking. Animation montage files have the type UAnimMontage. So, if we want to store an array of montages, we can do so like this:
UPROPERTY(EditAnywhere)
TArray<UAnimMontage*> AttackAnimations;Playing Combo Attacks
In this lecture, we learned how to play animations with our code. To play an animation, we first must have a reference to an instance of the ACharacter class. The value returned by the GetOwner() function can be casted by applying a generic like so:
CharacterRef = GetOwner<ACharacter>();The ACharacter class has a function called PlayAnimMontage() which accepts a reference to animation montage file.
CharacterRef->PlayAnimMontage(AttackAnimations[ComboCounter]);To cycle through animations, we decided to store the animation count in a variable called ComboCounter, which we update and wrap the value with the UKismetMathLibrary::Wrap() function.
ComboCounter++;
int32 MaxCombo{ AttackAnimations.Num() };
ComboCounter = UKismetMathLibrary::Wrap(
ComboCounter, -1, (MaxCombo - 1)
); To grab the maximum number of attacks, the TArray class has a function called Num().
An additional step we must take is to update the animation blueprint to allow for animations to be played by code by adding the default slot node.

Animation Notifications
In this lecture, we used animation notifications to run a function when the animation reaches a certain point. To add a notification, we have to right-click on the notification track and select Add Notify > New Notify.

From there, we can add the notification as an event to call our function.

The function we created toggles a boolean variable to allow the player to attack.
Enabling Root Animations
In this lecture, we enabled root motion on our animation sequences. By doing so, the player won't be able to apply movement to the animation while they're playing. This way, the player can't move while attacking.

Overriding Virtual Functions
In this lecture, we defined a function in the IFighter interface that can be overridden by our characters. By adding the virtual keyword, we're allowed to override the function from the class that inherits this interface.
virtual float GetDamage() { return 0.0f; }In our UMainCharacter class, this is exactly what we did:
virtual float GetDamage() override;By adding the override character, we'll be allowed to replace the function with our own:
float AMainCharacter::GetDamage()
{
return 10.0f;
}Applying Damage to Opponents
In this lecture, we learned how to send damage from one actor to another. This is a common action that most developers need to perform that Unreal supplies us with a function and event to handle this step. To send damage to another actor, we must call the TakeDamage() function on that actor.
TargetActor->TakeDamage(
CharacterDamage,
TargetAttackedEvent,
GetOwner()->GetInstigatorController(),
GetOwner()
);We must provide four pieces of information: the damage to apply, an event name, the instigator controller, and the actor applying the damage.
For the event name, we must create a custom event so that the actor can identify what is causing the damage since damage can come from different sources:
FDamageEvent TargetAttackedEvent{ };Animation Notify States
In this lecture, we created animation notify states, which are similar to notifications, except we can specify when the notification starts and ends as well as run code as the notification is running. We can create a C++ class from the UAnimNotifyState parent class. In this class, we overrode two functions called NotifyBegin and NotifyEnd.
virtual void NotifyBegin(
USkeletalMeshComponent* MeshComp,
UAnimSequenceBase* Animation,
float TotalDuration,
const FAnimNotifyEventReference& EventReference
) override;
virtual void NotifyEnd(
USkeletalMeshComponent* MeshComp,
UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference
) override;For these functions, we have to match the virtual function's return type and parameter list. You can always refer to the documentation for this info. In both functions, we're grabbing the trace component and updating the bIsAttacking variable.
void UToggleTraceNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference)
{
UTraceComponent* TraceComp = MeshComp->GetOwner()
->FindComponentByClass<UTraceComponent>();
if (!IsValid(TraceComp)) { return; }
TraceComp->bIsAttacking = true;
}
void UToggleTraceNotifyState::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
UTraceComponent* TraceComp = MeshComp->GetOwner()
->FindComponentByClass<UTraceComponent>();
if (!IsValid(TraceComp)) { return; }
TraceComp->bIsAttacking = false;
}Lastly, we just added the notify state to the notification track.

Resources
Last updated