In last Gameplay Balance System post I was writing about fun. Now it’s time to actually implement patterns and manager. In this tutorial you will learn how to:
- Update enemies to communicate with patterns,
- Fast create patterns using UE4 components,
- Manage enemies – without destroying / spawning actors,
- Select which pattern should be shown to player,
This will be the first tutorial where I have done Live Streaming for you guys. Let’s move forward!
This Tutorial has been created using Unreal Engine 4.10</strong>
Make sure you are working on the same version of the engine.
Live Stream
You can watch live stream where I was developing those features here:
Hierarchy Explanation
First some explanation. I will do two classes:
Pattern
This class will be responsible for storing data about enemies in the pattern. It will activate / deactivate all enemies inside the pattern. The goal here is to not Spawn / Destroy enemies during gameplay.
USEFUL TIP: Spawning actors which have animations / complicated materials cost a lot of resources and can cause glitches. It’s always better to spawn them on level start (while in loading) and manage them without need to destroy / spawn again the same enemies.
Pattern Manager
It will be responsible for choosing which pattern should be presented to player. Manager will choose the best pattern at given time and move it from pool to player viewport.
This is really simple approach which will add a lot of freedom to designing patterns.
Updating Project
It’s not the first time when I need to update earlier created functionalities. In games you will be always getting back to things you have created earlier.
Currently my enemies are Destroyed on Die. I need to have a way to communicate to enemy and tell him to activate / deactivate. In this Tutorial I will update Drone Enemy as it have physics.
IEnemies Interface
Create new Interface named IEnemies. Open it and add those two functions:
- ActivateEnemy,
- ResetEnemy,
It will be used to communicate with Enemy from Pattern class.
Update BP_BaseEnemy
Now open BP_BaseEnemy and Implement IEnemies Interface. Add one variable as well:
- isActive (bool),
And one new Dispatcher:
- OnEnemyDead,
Open Event Graph and add ActivateEnemy and ResetEnemy Interface events:
It should be self explanatory.
Create new custom event named EnemyCanReset:
Now update Die function to call EnemyCanReset at the end:
Update BP_Enemy_FlyingDrone
I need to update the Drone to be able to reset. This won’t be so simple as it uses physics. Open BP_Enemy_FlyingDrone and add those variables:
Var Name | Var Type | Default Value | Description |
Loc_PA_Drone | Transform | ||
Loc_PA_BladeLeft | Transform | ||
Loc_PA_BladeRight | Transform | ||
Loc_PA_WingLeft | Transform | ||
Loc_PA_WingRight | Transform |
Those variables will store translation of each drone part before running physics. So we can use them later to restart the location and rotation of the parts.
Open Event Graph and add new Function named GetPartsLocationBeforePhysics:
Add another function named SetPartsLocationBeforePhysics.
Remember to first Attach the Component and then change relative transform. This way Drone will be able to move again and will be attached to default components created earlier:
Open your Event Graph again and add IEnemies Interface events (they should be visible as BaseEnemy implements it)
And that’s all here. I won’t be updating rest enemies yet. Just the drone.
Pattern Class
Create new Blueprint extending from Actor named Pattern. Open it and add those variables:
Var Name | Var Type | Default Value | Description |
bWasSpawnedEarlier | bool | false | Hold the information if this pattern was spawned earlier. |
CustomDelayBeforeStart | float | 1.0 | Delay for activating Pattern. |
Difficulty | float | 0 | What’s the difficulty of this pattern ( from o to 1) |
PlayerDeathCount | int | 0 | We will store how many times player dies on this Pattern. Will be used later. |
AllEnemies | Actor Array | Stores all enemies connected to this Pattern. | |
DeadEnemiesCount | int | 0 | How many enemies were killed. Used to determine if all enemies are dead and we can activate next pattern. |
AllEnemiesLocation | Vector Array | Stores start location of each enemy as Pattern Manager will move Patterns to and out viewport. |
Add those Dispatchers:
- OnPatternEnded,
- OnPatternStarted,
Add new function named StartPattern:
Another one named EndPattern:
Next one GetAllEnemies:
CheckIfPatternIsFinished: (Pure)
Now in your Event Graph create new Custom Event named GetEnemiesInPattern:
In the Components I have added Billboard (Hidden In Game: False) and Arrow to see the Patterns.
Pattern Manager Class
Create new Float Curve named TestDifficulty:
This will be our balance curve for the Patterns.
Create new Blueprint extending from Actor named PatternManager. Open it and add those variables:
Var Name | Var Type | Default Value | Description |
AllPatterns | Pattern Reference Array | Stores all Patterns in Level. | |
CurrentPattern | Pattern Reference | Stores current active Pattern. | |
PatternsLocations | Vector Array | Stores location of pattern before moving into viewport. | |
PatternStartLocation | Vector | Location in front of your player. | This is location where active pattern should move. |
CurrentTime | float | Holds current level time. | |
MaximumLevelTime | float | 60 | Stores maximum level time. After this time level will end. |
Difficulty | Curve Float Reference | TestDifficulty | Stores Difficulty curve for this Manager. |
NextBestPatterns | Pattern Reference Array | Stores chosen patterns for next activation. | |
CurrentTolerance | float | 0.1 | Tolerance value to find best difficulty pattern on curve. |
ManagerEnabled | bool | true | Is manager enabled? |
And one Dispatcher:
- OnLevelEndedWithTime,
Now create new function named GetAllPatterns:
Next one GetLevelProgress: (pure)
It will return value from 0 to 1.
Next function GetDifficultyFromCurve: (pure)
Next one is TryToGetBestPatterns:
- Float Input named Tolerance,
- Bool output named Success?
- One Local Bool Variable named FoundSomething?
This is basically searching for patterns that wasn’t enabled earlier and theirs difficulty is nearly equal than on curve.
Now in Event Graph add Tick function:
It will make sure we stop when level time ended.
Create new Custom Event named FindPattern:
Here’s the place where magic happens. Binded event dispatchers comes to CurrentPatternEnded:
Which will make sure current pattern will move outside the screen and start another pattern after delay.
Now add last event – Begin Play to start everything:
Creating Patterns
Now create new Blueprint extending from Pattern named P_Drone_00.
Add Child Actor Components:
Create more Patterns with Drone Child Actors and with different Difficulty (from 0 to 1)
Then place those patterns into your level somewhere behind player:
Now place the Pattern Manager to the level and set your Pattern Start Location – should be location in front of the player.
Here’s the video presenting my patterns:
Last thing you would like to to is to inform GameState that Manager finished. Open GameplayGameState add one variable:
- ManagerReference (Patter Manager Reference),
Add new custom event named OnPatternManagerAdded:
And new function named SetPatternManager:
This way your Game State will know when Pattern Manager will finish.
In PatternManager class create new function named RegisterPatternManager:
And just call it in Begin Play. That’s all! Hope you have learn how to create pattern managers!
Creating ShooterTutorial takes a lot of my free time.
If you want you can help me out! I will use your donation to buy better assets packs and you will be added to Credits /Backers page as well. Implementing game is taking time but writing about it is taking much more effort!
Pingback: Creating Gameplay Balance System – Part 3 – Supporting all enemies | Shooter Tutorial
I encountered an issue where all my patterns were starting simultaneously; the drones out of view were getting activated and consequently the patterns were all ending and I was getting the “CAN’T FIND A MATCHING PATTERN MESSAGE” a bunch. Well, I was also getting drones stuck in the walls, so that was indicative of a problem as well. Going into ghost helped to see what was happening. The solution was to go into the Event Tick inside BP_BaseEnemy_FlyingDrone, where all the flying gets done, and add a Branch that checks if “IsActive” is true in between the branch checking “IsDead” and the call to Parent:Tick. I plugged True into Parent Tick and left False blank, and this seems to have solved the issue and given me what was demo’d in the video.