I began this sprint by working on implementing shooting behavior for our enemies. Currently my enemy AI was capable of navigating to the player and punching, but not firing projectiles from a distance. To this end, I needed a couple of new things: a projectile for the enemies to fire, a blackboard key containing the range at which the enemy will shoot the player, a BT task to call an enemy’s shoot function from within the BT, and then the shooting function itself within the enemy blueprint.
Firstly, I created the BT task so that I could call the enemy’s shooting function, this was almost entirely the same as the original melee task, just with a different function to call within the base enemy class. Second, I created a decorator that checks if a target actor is within a range defined by a blackboard key. Within the AI controller, I assigned a FiringRange key inside the blackboard based on the enemy data table I created last sprint. Finally, the last thing I needed was to create a function inside the enemy base to spawn a projectile and set its data table row. Very easy!
Behold! A glowing red orb
Initially, the projectile was a white ball with a collider, but I eventually changed it to be glowing red so it’s more visible against the environment. Within this projectile, I programmed the ability to read the enemy data table from an assigned row (to get the projectile’s damage and speed based on which enemy fired it), an ApplyDamage call on contact with the player character, and finally the ability to destroy itself upon contact with something solid.
The projectile was also always firing from the enemy’s center of mass, which was fine for prototyping, but later we would probably want the projectile to be spawned at the end of some sort of gun or appendage. For that, I added a SceneComponent (basically just a transform) to the base enemy called FiringPoint, and set the projectile to spawn from there. This way when we have the actual meshes, I can parent the firing point to a socket of the mesh so that it stays attached to whatever part it needs to spawn from!
With this, I was able to make the behavior tree for the medium enemy! He will stand still and fire at the player as long as they are within range, and if they get too close, he will stop firing and throw a punch. If the player leaves his range, he will chase them!
The heavy enemy essentially had the exact same behavior, just with different stats for health, firing rate, range, and damage, so creating him took no time at all. With this we had our three basic enemy types!
I adjusted the scale of each enemy to easily tell them apart
Now with proper enemies, we needed a way to actually defeat them. Another groupmate of mine had done the work necessary for the player shooting, so we discussed at our group meeting how we wanted to make these independent systems talk to each other. We agreed that Unreal’s built in ApplyDamage() functions would be easiest, so within the enemy I extended off of the OnAnyDamage event to lower his health.
The lead designer also wanted this game to have headshots like many traditional FPS titles, so I not only needed the enemy to receive damage, I needed it to know where it was hit. After doing some research on some different approaches to this, I decided on generating a physics asset for the mesh.
The physics asset is essentially a group of colliders attached to certain bones of the mesh. Unreal will generate a pretty accurate one by default, so I only needed to adjust it slightly! This not only allowed me to differentiate between body parts, but it also meant that the shooting would feel more accurate, because shooting between the legs or in a gap in the mesh would not count as a hit.
However, here is where problems started to arise. The line trace used for the player shooting was generating multiple hits per enemy. One for the capsule collider used for the enemy’s physics, and then one or more depending on how many colliders of the physics asset were hit by the line. The first step of fixing this was somehow differentiating between the capsule and the physics asset of the enemy. For this, I created a new trace channel called “Hitscan”, which would be used for objects that should respond to any hitscan weapon shots. By default, I set this to overlap on every object (This is because line traces will stop after a blocking hit, a quirk unique to line traces I discovered!), then within the capsule I set it to ignore . After getting the OK from my fellow programmer to edit her shooting code (which was neatly isolated within an actor component and not the player blueprint, preventing a merge conflict), I changed the trace to only detect the Hitscan channel. Now the capsule collider was no longer being detected!
But the shooting wasn’t fixed yet, because if two physics asset colliders of the same mesh were detected by the trace, the damage would be applied twice. To fix this, I’d once again need to venture into the shooting component, and change the way that hits were detected.
Originally, all hits were checked for validity, and then added to an array of hit structs to be returned. The way I solved the double-hitting problem was by iterating through all hits, and keeping track of the actors that have already been hit. If a hit was on a new actor, it would be added to the array, and the actor would be added to a separate array of already hit actors. If it was not a new actor though, it would be ignored. Now the shooting was able to hit multiple targets with all of them taking the correct amount of damage!
Only one hit is counted (shown by my fellow programmer’s handy debug function)
The final thing I worked on this sprint was the enemy wave system. The designer wanted enemies to be spawned in when there were only N enemies remaining. To start off, I created a wave manager blueprint that would keep track of how many enemies were alive in the level. When enemies were spawned in, they would call a function within the wave manager to register themselves. Then upon death, they would be unregistered.
Next, when the enemy count got below a certain threshold, the wave manager needed to spawn new enemies. But which enemies would be spawned and where? To answer this question, I created a new actor blueprint called Enemy Spawnpoint.
Adjustable spawning radius
The Spawnpoint would have just a small amount of data. The radius of the area in which it can spawn enemies, a list of enemies that could be spawned there, and a cooldown min and max so that the enemies could spawn one at a time with some variance in the time between spawns (per the designer’s request). This will allow the level designer to have very fine control over which enemies can spawn and where. Currently, the wave manager will tell a spawnpoint to spawn, and the spawnpoint will pick randomly from its list of enemies. But in the future, I could change the wave manager to decide which enemy types to spawn, and those enemies will only spawn at spawnpoints that have said enemy in their list.
This spawnpoint can spawn light, medium, and heavy enemies
Next sprint, I’m excited to start working on some particle effects to enhance the player feedback of shooting, as well as further develop the wave system and implement 3D models for the enemies!
Comments
Post a Comment