Dev Log #3: Adding Health and Death

Now that the Player can fire bullets and move around, it is time to add some functionality to the bullets and test them out! This will mean making a Destructible Tile, a Health Component to attach to Characters, and making the bullets work. 

So first, I need to make a Tile Class that can handle being destroyed. For now, this will be an empty class that I will build a Blueprint from. Later, I plan to add functionality to handle dropping items after it is destroyed, animations, and sounds. 

Next, I need to make a Health Component. This will attach to Actors in their Blueprints for those meant to have health. This Component will handle the Health, taking Damage, and Death. 

After that, I will be able to build out the bullets. The plan with the bullets is to have them destroy themselves after impacting any actor. When they then collide with an actor that has a Health Component attached, damage should then be applied. To spawn bullets, for now I will attach a spawn point onto the character and write a function to spawn them. 

Finally, I need to build a function to handle Death. When Health falls to zero, the actor should be destroyed and call any required functions for handling death. For example, dropping items. 

So first, I will build the Health Component. It will have a BeginPlay function and a MaxHealth Property. A Float will also be created for holding the current Health. Functions for handling Taking Damage and Death are created as well. 

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

// Health Protected Variables *********************************************************************

	// Max Health of the attached actor
	UPROPERTY(EditAnywhere)
	float MaxHealth = 200; 

	// The Current Health of the attached actor
	float Health = 0; 

// Health Protected Functions *********************************************************************

	// Called when the attached actor takes damage. 
	UFUNCTION()
	void DamageTaken(AActor* DamagedActor, float Damage, const UDamageType* DamageType, class AController* Instigator, AActor* DamageCauser);

	// Called when attached actor dies
	void Death(AActor* DamagedActor);

Code language: C++ (cpp)

The big item here in the Health Component is the DamageTaken function. It takes in the Damaged Actor, the amount of Damage, the Damage Type, the Controller responsible for causing the damage, and the Actor that caused the damage. For now, the function only uses the Damaged Actor and the Damage amount.

When the DamageTaken function is called, it will first check if the damage is less than zero. If it is, it will early return to prevent the rest of the code from running. If damage is not less than zero, Health is decremented by Damage. Then, when Health falls below zero, the Death Function is called. 

// Called when the attached actor takes damage. 
void UHealthComponent::DamageTaken(AActor* DamagedActor, float Damage, const UDamageType* DamageType, class AController* Instigator, AActor* DamageCauser)
{
	// Check if Damage is less than zero. Early Return if true. 
	if (Damage <= 0)
	{
		return; 
	}

	// Subtract Damage from Health
	Health -= Damage; 

	// If Health is less than or equal to zero
		// Call Damaged Actor's Death
	if (Health <= 0)
	{
		Death(DamagedActor);
	}
}
Code language: C++ (cpp)

The DamageTaken function is then setup in BeginPlay. GetOwner is called, which returns the Actor that is attached to the Health Component. Here, a function from the AActor class is called off of GetOwner, the OnTakeAnyDamage function. The DamageTaken function from the Health Component is then connected to OnTakeAnyDamage. While it is not necessarily obvious why I am doing it this way, it will make sense here shortly.  

void UHealthComponent::BeginPlay()
{
	Super::BeginPlay();

	// Set Health to Max Health
	Health = MaxHealth; 

	// Setup the DamageTaken Function so that the attached actor can call it when taking damage. 
	GetOwner()->OnTakeAnyDamage.AddDynamic(this, &UHealthComponent::DamageTaken);
	
}
Code language: C++ (cpp)

With the Health Component setup, I will now build out the Bullet Class. The Bullet Class inherits from the MoverTile Class (See Dev Log #2: Making a Cube Move). For now, it will have a Float for damage, a function called OnHit for handling collisions, and a UProjectileMovementComponent. 

First, here is the constructor for the Bullet Class. Of note, a Projectile Movement Component is created for the bullet to handle movement instead of using the Translation functions from the Moving Tile class that the Bullet Class inherits from. The reason for this is simple, the code that follows for handling damage requires the bullet to have some velocity to register an impact. The functions from the Moving Tile Class, while they make an object appear to move, do not give the object a velocity. This is because they are resetting the location of the object every frame versus giving it actual movement. The difference is telling the computer the object is moving and the object now exists at a new set of coordinates. For now, I coded this Movement Component into the Bullet Class. Later on though, I will be fixing the Moving Tile Class so that everything that inherits from it will have a proper Movement Component. 

ABullet::ABullet()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

    // Setup Movement Component
    BulletMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Bullet Movement"));
	BulletMovement->MaxSpeed = 100;
	BulletMovement->InitialSpeed = 100; 
}
Code language: C++ (cpp)

First up among the functions in the Bullet Class, the OnHit function. It takes in the component doing the hitting (the bullet), the actor being hit and its component, an impulse from the impact, and a FHitResult. From here, the function gets the owner of the bullet. If there is no owner, the bullet is destroyed. The controller is collected from the owner of the bullet and a Static DamageTypeClass is created. These are then funneled into an ApplyDamage function from the GameplayStatics Class, but only if all of the following conditions are met: That the actor being hit exists, that the actor being hit is not the bullet, and the actor being hit is not the owner of the bullet. After all of this, the bullet is then destroyed with a simple Destroy() call. 

// Delegate Function for when the bullet impacts another object. 
void ABullet::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
    // Get Owning Actor
    AActor* MyOwner = GetOwner();
	if (MyOwner == nullptr) 
	{
		Destroy();
		return;
	}

    // Get Controller for Owning Actor
    AController* MyOwnerInstigator = MyOwner->GetInstigatorController();
    // Create Damage Type
	UClass* DamageTypeClass = UDamageType::StaticClass();

    // Check if OtherActor exists, that the Bullet is not OtherActor, and that the owner of the Bullet is not OtherActor
	if (OtherActor && OtherActor != this && OtherActor != MyOwner)
	{
        // Apply Damage to OtherActor
		UGameplayStatics::ApplyDamage(OtherActor, Damage, MyOwnerInstigator, this, DamageTypeClass);
	}

    // Destroy Bullet for all impacts. 
    Destroy();
}
Code language: C++ (cpp)

This function is then setup in BeginPlay and connected to a function in Unreal called OnComponentHit. This sets up the function so that whenever the bullet hits something, OnHit is called. 

// Called when the game starts or when spawned
void ABullet::BeginPlay()
{
    Super::BeginPlay();

    // Setup OnHit Delegate Function so that bullet impacts are registered. 
    BaseMesh->OnComponentHit.AddDynamic(this, &ABullet::OnHit);

}
Code language: C++ (cpp)

Now, remember back when I mentioned OnTakeAnyDamage? The way that OnTakeAnyDamage works is that it is waiting for the ApplyDamage function to be called from OnHit. When ApplyDamage is called, it will broadcast to all functions connected to taking damage to then be called. This is a very useful feature in Unreal that makes it easier to handle the dealing of damage. So, for my code here, when OnHit is called, it will then call DamageTaken in the Health Component without hard coding this connection. This is simpler and more robust.

Next, I need to spawn the bullets from the BaseCharacter Class. Here, I created a USceneComponent that I will call ItemSpawnPoint that I attached to the Root Component of the BaseCharacter Class. From here I will be able to spawn the bullets. To fire the bullets, I will do this from a function called FireBullet. The location and rotation of the spawn point are retrieved and then used to spawn the bullet. Once the bullet is spawned, the owner of the bullet is set to the character that spawned it. 

// Call to fire a bullet from the character
void ABaseCharacter::FireBullet()
{
	FVector Location = ItemSpawnPoint->GetComponentLocation();
	FRotator Rotation = ItemSpawnPoint->GetComponentRotation();

	ABullet* Bullet = GetWorld()->SpawnActor<ABullet>(BulletClass, Location, Rotation);
	Bullet->SetOwner(this);
}
Code language: C++ (cpp)

Now with the Health Component and the Bullet Class setup and ready to use, I can write the Death Function. For now, it will write to the Log saying, “I AM DEAD!!!” and then calling for the actor to destroy itself. 

[HealthComponent Lines 61 to 66]

// Called when attached actor dies
void UHealthComponent::Death(AActor* DamagedActor)
{
	UE_LOG(LogTemp, Warning, TEXT("I AM DEAD!!!!"));
	DamagedActor->Destroy();
}
Code language: C++ (cpp)

The result? The player can now fire bullets around and destroy a cube. 

Player fires a quantity of orange bullets at a cube and makes the cube disappear.

While the results do not appear to be much, a lot of work was put into making a rather small, yet critical feature work. This is very much the base on which I can begin building out a far more fleshed out bullet class that can start looking more like something from Touhou with beautiful bullet patterns galore. For now, that will have to wait. Next up for me to work on is building out a Player Character Class and starting work on a Player Controller.