Tuesday, April 2, 2019

Independent Study - UE4 Combat Systems

I was tasked with creating a combo attack system in Unreal Engine 4 as part of my UE4-oriented independent study, and here are the steps I followed to do so:

Step 1: Setting Up Variables

Original Size: https://i.imgur.com/JvALzCO.png

Go into the SideScrollerCharacter blueprint, and then go to its Construction Script.

Create a float named meleeHitboxOffset.
- Set it to 75 for now.
Create a float named comboLength.Create a float named hitboxDelay.
- Note: Not currently shown; set to 0.5.
Create a float named hitboxDelayDefault.
Create a Boolean named hitboxActive.
- Note: Not currently shown.
Create a float named compassYaw.
- Note: Not currently shown.

In the variables list, click the “eye” next to compassYaw, meleeHitboxOffset, comboLength, hitboxActive, and hitboxDelay to make them public.


Step 2: Setting Up Inputs

Original Size: https://i.imgur.com/aJeEkwR.png

In the main UE4 window, navigate to: Edit > Project Settings > Engine > Input
Under Bindings, create and set the following key binding:

- Attack, bound to the Left Mouse Button.

Optionally, you may alter the other key bindings as necessary.
For example, I differentiated between MoveLeft and MoveRight, unlike the default.

Step 3: Creating the Hitbox Actor

Original Size: https://i.imgur.com/kzDgTsQ.png
Original Size: https://i.imgur.com/6XP5XQc.png
In the Content Browser, navigate to: Content > SidescrollerBP > Blueprints.
Right-click, and then create a new Blueprint Class.
Name this new class PlayerMeleeHitbox.

Now, open up PlayerMeleeHitbox in the Blueprint Editor.
Add a Cube static mesh component to it, and then a Box collision component.
Move both to 0x, 0y, 0z if they aren’t already there.
- Their scale will need to be fiddled with, but ideally the Box collision will be just barely bigger than the static mesh.
Later, the static mesh will be removed, but it serves as a visualization for now.
Make sure that the Cube static mesh has no collision at all, and that the Box collision overlaps all dynamic objects.

Step 4: Adding an Input Check

Original Size: https://i.imgur.com/wT8M77q.png

First, create a function for SideScrollerCharacter called Melee Attack.

In SideScrollerCharacter’s Event Graph, add an InputAction Attack node, and then have it run Melee Attack.


Step 5a: The Melee Attack Function

Original Size: https://i.imgur.com/HbN7u7Q.png

The Melee Attack function will house quite a bit of code, the first of which being a series of Branch nodes to check if you can attack.
Of the ones you see, the relevant ones are:

A Branch checking how long comboLength is.
- Only continue if less than or equal to 2.
A Branch checking if hitboxActive is true.
- Only continue if false.

Step 5b: The Melee Attack Function (cont.d)

Original Size: https://i.imgur.com/STWI9NW.png
Next, set hitboxDelay to hitboxDelayDefault, or 0.5.
Get a reference to Self, GetActorRotation from it, Break the resulting Rotator, and then set compassYaw to the rotator’s Z.
Split the path into two separate paths using a Sequence.
Meanwhile, before continuing, Get compassYaw and then plug it into two inRange (float)’s. In the first, check between -90 to -0.1, and in the second check between 0.1 to 90.

Step 5c: The Melee Attack Function (cont.d)

Original Size: https://i.imgur.com/BaqDMRM.png

Add a Branch to each path of the Sequence you just made, and plug the -90 to -0.1 inRange into the first one and the 0.1 to 90 inRange into the second.
Below, GetActorLocation of the SideScrollerCharacter, and then Break the Vector.
First, subtract the resulting Y-value by meleeHitboxOffset, or 75. After that, Make the new Y-value along with the old X and Z into a new Vector.
Next, add the old Y-value to meleeHitboxOffset, or 75. Make another Vector just like before.
Plug these new vectors into their own Make Transform nodes.
For the True path of the aforementioned Branches, make a SpawnActor Player Melee Hitbox node and plug the FIRST Make Transform into it. Do the same for the SECOND Make Transform.

Step 6: Creating an Enemy

Original Size: https://i.imgur.com/RM8N2nP.png

Before we get into the proper melee hitbox, we are going to need an enemy to hit with it.
Do what you did before with making PlayerMeleeHitbox, step for step, but this time call it Enemy.
In Enemy’s Construction Script, make a PUBLIC float called enemyHealth, and set it to 10.
In Enemy’s Event Graph, drag off of Event Tick and make a Branch. Have it check for when enemyHealth is Less Than or Equal To 0. When this is True, Print a String before Destroying the Actor.

Step 7a: The PlayerMeleeHitbox BP

Original Size: https://i.imgur.com/C0VAw8I.png

Create the following functions for PlayerMeleeHitbox:

- Toggle Hitbox Variables.
- Check Combo Length.

In PlayerMeleeHitbox’s Construction Script, run Toggle Hitbox Variables first, and then Check Combo Length.

In Toggle Hitbox Variables, Cast to SideScrollerCharacter (after using Get Player Character), and then set Hitbox Active to true.

Step 7b: The PlayerMeleeHitbox BP (cont.d)

Original Size: https://i.imgur.com/VVxWSIF.png
Original Size: https://i.imgur.com/ARBUdJn.png

In PlayerMeleeHitbox’s Event Graph, drag off of Event Tick and Cast to SideScrollerCharacter. Plug Get Player Character into this.
Off of the cast, Get hitboxDelay and plug it into a Less Than or Equals with the other value being 0.
Plug the result into a Branch that, if True, does the following: Set hitboxActive to False, runs Check Combo Length, and then Destroys the Actor.

Step 7c: The PlayerMeleeHitbox BP (cont.d)

Original Size: https://i.imgur.com/BJtBThm.png

Now is where things start getting a little complicated.
In PlayerMeleeHitbox’s Check Combo Length function, Cast to SideScrollerCharacter and then Get hitboxActive from it. Plug it into a Branch, and, while you’re at it, Get comboLength.
For the next two sub-steps, remember that every Get or Set comboLength must be linked back to Cast to SideScrollerCharacter.

Step 7d: The PlayerMeleeHitbox BP (cont.d)

Original Size: https://i.imgur.com/VflRkad.png

First, we are going to increment comboLength from 0 to 1, 1 to 2, and 2 to 3.
Dragging off of the earlier Branch’s True path, nest a series of three more Branches.
Below the first Branch, plug the earlier Get comboLength into a Nearly Equal (float) node as the A value. For the B value, enter 2.0. Plug this into the first Branch. If it is true, Set comboLength to 3.
If the first Branch was false, do the same as above but enter 1.0 as the B value. If this is true, Set comboLength to 2.
If the second Branch was false, do the same as above but enter 0.0 as the B value. If this is true, Set comboLength to 1.
For debugging purposes, also have each of these Print comboLength as a String.

Step 7e: The PlayerMeleeHitbox BP (cont.d)

Original Size: https://i.imgur.com/KhaUQja.png

Next, we are going to decrement comboLength from 1 to 0, 2 to 1, and 3 to 2.
Dragging off of the earlier Branch’s False path, nest a series of three more Branches.
Below the first Branch, plug the earlier Get comboLength into a Nearly Equal (float) node as the A value. For the B value, enter 1.0. Plug this into the first Branch. If it is true, Set comboLength to 0.
If the first Branch was false, do the same as above but enter 2.0 as the B value. If this is true, Set comboLength to 1.
If the second Branch was false, do the same as above but enter 3.0 as the B value. If this is true, Set comboLength to 2.
For debugging purposes, also have each of these Print comboLength as a String.

Step 7f: The PlayerMeleeHitbox BP (cont.d)

Original Size: https://i.imgur.com/yVA3Ikh.png

Going back to PlayerMeleeHitbox’s Event Graph, we are going to have the hitbox detect an overlap with any Enemy actor.
Create an OnComponent Begin Overlap node, and Cast to Enemy from it using the Other Actor pin as its Object reference. Immediately afterwards, Cast to SideScrollerCharacter and pin the GetPlayerCharacter node from earlier to it.
From SideScrollerCharacter, Get comboLength and plug it into three separate Nearly Equal (float) nodes as the A value. For the B value, input 1, 2, and 3 respectively.

Step 7g: The PlayerMeleeHitbox BP (cont.d)

Original Size: https://i.imgur.com/PuleUPr.png

Off of the main path, create a nest of three Branches, plugging the uppermost Nearly Equals node into the uppermost Branch and so on for the others.
For each, if True, Print a String and then:
- If comboLength equaled 1, Set enemyHealth to enemyHealth minus 1.
- If comboLength equaled 2, Set enemyHealth to enemyHealth minus 2.
- If comboLength equaled 3, Set enemyHealth to enemyHealth minus 3.

Step 7h: The PlayerMeleeHitbox BP (cont.d)

Original Size: https://i.imgur.com/qhbxJeh.png

Finally, have all of the three sub-paths converge back into a Set hitboxActive node, setting the Boolean to False.
After that, Destroy the Actor.

Step 8: Revisiting SideScrollerCharacter

Original Size: https://i.imgur.com/FSxa43a.png

We are almost finished with the blueprints; just a little more!
In SideScrollerCharacter’s Event Graph, drag off of Event Tick and make a Branch. Plug into it a Less Than or Equals To, and then compare hitboxDelay and 0.
If True, Set hitboxDelay to 0 and Set comboLength to 0.
If False, Set hitboxDelay to hitboxDelay minus Delta Seconds, a pin dragged off of Event Tick.

Step 9: Setting Up Your Scene

Original Size: https://i.imgur.com/BxCuSx4.png

At last, place a couple Enemy actors into your scene, and then play.
If everything works as intended, you will make a hitbox in the direction you are facing (either left or right).
With this setup, you will only be able to swing once… but if you hit an enemy, you will be able to swing up to three times!
Once the enemy is hit with two full combo attacks or a mixture of hits, they will be destroyed.

Of course, there are several ways to improve it from here:
- Making it so you can’t move while in the middle of a combo.
- Tweaking the position and size of the hitbox.
- Adding alternate hitboxes for mid-air attacks.
- Making the hitboxes move along with the player.

Bonus: A Brief Look into the Importance of Combat

The Importance of Combat

- First and foremost, combat makes a game more engaging.
- Games can and have pulled off combat-less experiences well, but several genres are practically built around the concept.
- Other times, combat adds to the overall experience like spice would a well-prepared meal.
- As game designers, it is our job to make the player feel emotions, and combat in particular should excite them.
- To give you an idea, I have a couple examples of good video games with equally good and engaging combat systems.

Example: Dark Souls

- Dark Souls’s combat system requires the player to make smart plays and punishes them severely for mistiming risky techniques like parrying.
- One of the core mechanics of the game, healing yourself with your Estus Flask, also leaves the player helpless for several precious seconds.
- In terms of “combo attacks” in particular, each weapon family features similar strings of light and heavy attacks.
- For example, a dagger will have a string of very fast, low-damaging slashes, while the zweihander features slow, wide-arcing slices that easily stagger opponents.

Example: Ori and the Blind Forest

- Unlike Dark Souls, Ori and the Blind Forest’s combat takes on a somewhat lesser focus, the game instead intermixing it with platforming.
- Aside from a couple purely offensive skills like the standard homing spark, many abilities are just as effective in fighting enemies as they are in maneuvering around the environment.
- A strong aerial ground-slam can smash through weak floors and armored enemies alike. A grenade-like ball of light can be used to hit distant enemies or be bashed off of to travel a fair distance.
- The bashing mechanic in particular. Redirecting enemy fire, using the enemies themselves to dodge, hurling enemies into hazards…

No comments:

Post a Comment