A downloadable game

Git Link: https://github.com/Ananterlope1/Godliath

Google Drive Link

Gotta catch them all isn't the problem here. Getting caught is!

An action adventure where you rely on your skills and the unending rage of a berserker hot on your tail!

You work for a corporation which tasks you with capturing Alpha specimens for study. Call in these Berserkers and kite them to kill and eat aggressive fauna that gets in your way!

3rd person shooter utilizing Unreal Engine with an arcade feel.

Controls: 

WASD - Movement

Space - Jump

Left Mouse - Fire

Shift - Sprint

E - Dash

P - Pause

Code:

The main work on this project came from the Artificial Intelligence in the behaviour trees. This was because of the main gameplay loop being running and growing the Berserker enemy which chases you as well as the enemies and eats the enemies you kill.

Overall Berserker Behaviour Tree:

AI Variables:

SelfActor - Actor - The actor owning this tree. Player - Actor - The player character reference. StartLocation - FVector - Where the owner actor started. LastKnownPlayerLocation - FVector - Where the Player was last seen by this actor. ClosestEatableLocation - FVector - Location of closest actor with Eatable set to true. ClosestEatableActor - Actor - Reference to the closest actor with Eatable set to true. IsEatableCloser? - Boolean - Set on whether a eatable is closer than the Player. ClosestEnemyLocation - FVector - Location of closest actor with EnemyAI. ClosestEnemyActor - Actor - Reference to the closest actor with EnemyAI. IsEnemyCloser? - Boolean - Based on whether a enemy is closer than the Player. IsDead? - Boolean - Set on whether owner actor has Dead variable set. RoamLocation - FVector - Location of random location to roam to.

In priority order, the Berserker will always have being Dead as it's highest priority as it should overwrite all other tasks. 

IsDead? is set via this code:

This code will check the OwnerComp's (The behaviour tree owner) AI controller and then, after checking it's not a nullptr, will check if the character is dead. On true, the IsDead? variable is set to true and visa versa for false. All enemies AI inherit from the EnemyAIController class so this base is used for casting. 

There is also a check for each of the branches where once the IsDead? variable is set to true the branch will abort itself and all other tasks which are lower priority via Observer Aborts being set to both. 

In the Dead sequence being played, the behaviour will have the character play a death sound, play a death animation and then wait for a few seconds and then play the resurrect animation. For the Berserker, this is because the character can't be killed until it enters the Capture Zone and it has a True Death variable set to True while within it.

The next in the priority order is the Eating sequence:

This is entered via the IsEatableCloser? variable which is constantly checked for. The sequence itself involves moving towards the ClosestEatableActor, instigating the Eat task, playing a eating animation and then waiting for a couple of seconds. 

Eating is done via this code:

For the eat task, the code will check that the owner isn't dead by checking it's not a nullptr, if so it fails. Then take in the owner of the behaviour tree and cast it to the Berserker class (which only the berserker has this function). 

From this it then runs the Eat function: 

The eatable function works by playing a noise at the Berserkers location. From this it then uses a variable called ScalingEating which dictates how much it grows when it eats. This is used to increase the range of it's attack, the damage it does, its' speed, health and the scale of the actor.  For the purpose of this demo, there are defined tags to set which open up certain areas.

When the creature is eaten then the eaten actor has it's controller detached to stop checks to it from erroring or being responded to.  The actor's location is also stored and used to spawn a emitter for the chunks to appear when eaten. After this, the actor is hidden and finally destroyed (I found destroying it without detaching caused problems in the animation blueprint running). Finally the node reports a success. 

In priority after Eating is chasing the Player or nearest enemy: 

Whenever the owner isn't dead or an eatable is detected then, dependant on the player being seen the Berserker will move towards the Player location and when there it'll make a Melee Attack and play the attack animation.  The Melee Attack is a task that will call the Berserker's SwingWeapon function:

The SwingWeapon works by calling MeleeTrace which is a function for getting the actor aim direction, ignoring itself and the actor it's attached to to finally do a LineTraceSingleByChannel. Using the reference of HitResult and SwingDirection and based on a successful hit then a emitter is spawned at the HitResult location.  If an actor is hit then a sound for hitting them is spawned at the hit location and they take damage. 

The enemy chase works the same but is based on an enemy being closer than the Player:

This works the same as the Player chase with setting the enemy as a focus and then  moving towards the enemy actor, doing a Melee Attack against it, playing an attack animation and then waiting. This however will do a check to see if the enemy has died and has become an Eatable actor.  This can set the IsEatableCloser? to true and allow the Berserker to then eat the actor straight away. 

After these branches the investigate branch is used:

This handles checking the last place that the owner saw the Player by moving to the LastKnownPlayerLocation set when the owner is chasing the Player.  Once the owner has reached the location, it'll clear the LastKnownPlayerLocation value and wait for a few seconds (incase the Player reappears).  This has many decorators to fail the sequence as it can be overwrought by every other branch except the Roam.

Following from this is the owner roaming around: 

This works by getting a random location within a certain radius of the owner and setting the RoamLocation variable to that. The owner will then move to that location while checking for the player to appear. 

OnOverlapBegin Actors:

For this project I wanted to have narration activate when the Player enters certain areas. This was achieved using the OnOverlapBegin function within C++.

This was done with the Narration_Trigger actor. 

In the header file these were defined to create the HitBox to connect the OnOverlapBegin to (I also put this code here as I found there is a lot of misconceptions about how this is implemented so hopefully this can be picked up in searches to help):

UPROPERTY(VisibleAnywhere, Category="Components")
UBoxComponent* HitBox;
/** called when something enters the Box component */
UFUNCTION()
void OnOverlapBegin(class UPrimitiveComponent* Comp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

Within the Narration_Trigger.cpp:

The HitBox is created as a sub-object for the the actor and then the component OnComponentBeginOverlap is assigned to run the OnOverlapBegin function created here. 

For the OnOverlapBegin function, it'll check if the narration tag has been changed as this means to show a text message based on a tag an actor has set. As this is only implemented for the BerserkerCharacter it'll check if the other actor (overlapping actor) is a BerserkerCharacter and if it has a narration tag set. 
If this is passed then the WidgetTextClass (the narration text widget set for this narration trigger) is checked to be present and not null.
After this, the widget is created and dependent on it not being a nullptr, the widget is added to player viewport and a timer is started based on a set timeout variable for when the text should disappear. 
Once the timer has elapsed the function CleanUpNarration is run taking the TextWidget as an input. 
This function will remove the TextWidget from parent and remove the HitBox collision to stop the text from being shown again on another overlap. 

Leave a comment

Log in with itch.io to leave a comment.