top of page
Search

Log 23: Single Player, But a Group Attack

  • Writer: Kassandra McCormack
    Kassandra McCormack
  • Dec 1, 2024
  • 9 min read

I'm just now realizing that following my pun-based naming scheme the last post should have been "A More PRESSING Problem"...or something along those lines.


But seriously though...


The first thing I worked on was teleporting the AI characters into position, and more specifically how to let the player know if there was a problem: Like a wall being in the way, or there being no floor under where they would teleport. The first thing I thought to try to do was teleport and rotate the player so nothing would be in the way, that way the attack would always be able to go off, but that immediately got thrown out because 1) It takes some control away from the player, 2) It would be hard to communicate why that happened, 3) It would be all kinds of difficult to program correctly and would leave a ton of problematic edge cases. But then I realized: WHY does the attack need to always go off? What if I just...didn't allow it if there was something in the way of the teleport? How would I indicate that to the player so they could intuitively understand? So I made some preview actors with special materials.


I made the material translucent so it could be obvious that they weren't physical objects, just representations of the characters, I compounded this by giving them a slightly blue tint to make it (hopefully) obvious they aren't real. As for how to indicate if the formation will work or not: I used a Fresnel node to make the previews appear outlined in green if they are okay, and I made an animating material function to pulse red instead of green if there was a problem.


Next up was making the preview actors themselves. First I made an abstract parent class actor that would hold all the logic and could be used in a single future component for the actual characters. While I was doing this, I kept trying to find better ways to control material parameters and discovered Custom Primitive Data, exposable variables in a material that I could more easily manipulate in code; All I need is a reference to the mesh object and not a dozen references to individual dynamic materials. Then I made children classes of the Preview class only having to figure out and drop in the various skeletal meshes for the Ranger suits (I decided that in addition to being different colors, each Ranger would have a different helm for better identification). I only made two previews for now as that was the bare minimum I needed to test to make sure all the characters could teleport into the right positions (technically I would only need one, but two made it easier to read for my brain).


With the previews done, it was off to making the actual Group Attack Component (or GAC from here on out).


I wanted to make the GAC a component because it was functionality that only Playable characters would need instead of the other classes it inherits from. Although for testing purposes I put it on the Targetable character so I wouldn't have to make more Playable characters quite yet.


The first thing I did was create a function that I could bind to the Linker's "Link Established Dispatcher" called "On Linker Bound". It only had to do one thing: create the preview assuming the bound linker has its own GAC attached. It then calculates an offset from the player that it will need to maintain, adding the preview to a reference map so its location can be updated as the player moves. "On Linker Unbound" was significantly easier as all it needs to do is destroy the relevant preview actor. Finally, I made an "Update Preview Location" which is called from the parent GAC's tick and accepts the player's transform to pass to the preview, which handles the actual logic. Said logic is a direct set of the actor's location and rotation by using the player's transform to turn the offset from a local space to world space and then doing a couple of line trace checks for walls or static objects between the player and the preview and then for floors.


On my initial testing everything was working smoothly... and then it all went wrong. One day I closed the project because I was done for the day (read: had to go to my day job) and then when I opened it next the GAC just...didn't work anymore. Somehow between the preview reference getting set on Linker bound and the tick event it lost the reference. The map read a length of zero and hence nothing was showing up or moving. Then on Linker Unbound it destroyed the actor and found the reference just fine. I closed and re-opened the project multiple times but nothing helped. Eventually, I just gave up: Deleted the component and started over again from scratch.


Starting over from scratch and implementing the three basic functions worked fine this time around. Well...mostly fine. I found a bug with the Linker Component in that when I entered range to link to both demo characters it would create 3 bindings instead of 2. I realized I had forgotten to clear out old bindings to the player through an intermediary when establishing a direct link to the player.


I wanted to change the spawning of the previews because spawning and destroying actors can get expensive and I don't know how often the game will have to do it once I have the AI characters moving around in addition to the player. So I had the GAC's spawn only a single preview actor on begin play and keep a permanent reference to it. Meanwhile the player GAC keeps a reference to the GACs on the NPCs and updates the previews through them.


A couple of housekeeping bugs that showed up while I was trying to implement the actual activation of the Group Attack:


I created a "Custom Play Montage Event" in the IControllable interface. This allowed me to play animation montages like normal, but I also hooked it up to a new dispatcher that called an arbitrary function on the completion of the montage. This allows me to have the character do something when the montage completes, like resetting and being able to move again after finishing a Group Attack Animation...as a random example off the top of my head.


I found a glitch where the player could infinitely press the dodge button (the same button used for Ultimates and the Group Attack) and dodge roll. This was part of why I made the montage and dispatcher combo: until the dodge roll montage is complete, the player pressing the button again (or any button) won't do anything.


I also found a bug where after doing a single-participant Ultimate, the character could no longer move, so I had to fix that. Once again using my new custom montage player and dispatcher to re-enable movement.


Back to the Group Attack proper!


Once I got the actual pressing of the button working, I focused on teleporting the linked NPC's into position around the player character and having them all play their given animation montages. It was actually fairly simple and straightforward to do. In fact while I was testing it, I made the happy discovery that if the characters teleport over another character already there they will push the already there character away, so I didn't need to worry about it despite trying to work it out last post!

ree

Next was making the particle effects for the attack. Basically what I planned was a big beam blast that charges up over the animation and then fires and creates an explosion at the end of it. To start doing this I found a pre-purchased beam effect I liked and decided to modify it.

ree
From Energy Beam VFX by Dr.Game on the Unreal Marketplace
ree

I actually split it into two parts: the first is an orb that slowly grows over time, and then a big, wavy beam that fires out of it over a couple seconds. I made them both glowing white since...well multiple colors combined make white and I won't have a White Ranger in this game.


ree

The second part is a bunch of colored effects that travel the length of the beam that I modified to be able to change the colors of based on the characters participating in the attack. Each character participating will spawn one of these colored effects with the correct Ranger color.


ree

When I started trying to incorporate the particle beams into the animation, I ran into a couple small, unrelated issues. The first is that if the player kept the Empower button held down they would keep their weapons summoned through the whole thing and the menu would stay on screen. Also, if they released the Empower button and then pressed it, the weapon would disappear and come back, the same with the menu. I had to add a new "Can Receive Input" boolean to the Basic Character that is checked between every button press call and their effect. So in this case I can set it to false when the Group Attack starts, and no matter how many times the player presses the Empower button it won't do anything.



Finally I was ready to fully incorporate the beam effects. Making them spawn was easy, just use the AnimNotify I had created before for spawning Niagara systems during animations....

I wish it was that easy. The first obstacle was the main white beam, I only wanted one of them to spawn per attack, which means it had to come from the player's GAC and not any of the other participants, so that couldn't be a notify. But that was an easy enough to add a particle spawn in before calling the bound Linkers. The colored beam effects could stay as an AnimNotify.


The next wrinkle I ran into was that I didn't want to have the beam effect go through static meshes. So the player can't hit an enemy behind a wall. If I wanted to make destroyable meshes or something I wouldn't mind it but those are expensive and beyond my scope. So I added a line trace to find if there is a wall in the way of the beam, and if there is I set a "Length" parameter on the Niagara system to make the beam shorter. But wait, how do I get that modified length to the colored beam effects? I don't want them going past the main beam or through objects themselves. Thinking about it now, I think there is a way to have particles collide with static objects and then die. But I didn't think of that when I was putting the system together. Which meant that I couldn't leave the colored beam effects as spawning from an AnimNotify. I had to figure out how to get that newly derived length from the beam spawn to all the linked characters. I also hit a new wrinkle in that because I wanted the Power cost for the attack to be deducted immediately, the characters all become unlinked, which means that the beam and beam effects all had to be spawned right at start while they were still linked and then delayed until they overlapped with the correct point in the animation (the charging particles could remain as AnimNotifies). I had to create a second dispatcher in the Linker Component that also passed along a custom struct of different parameters (specifically a starting point Vector, a Rotator, and a Float for the length). This "Linked Action with Parameters Dispatcher" would get called in the same call as the beam creation and before the Power cost deduction, that way all the Linked characters know where and how long to make their colored effects.

ree

Almost finally is how to apply damage from the attack to enemies. This one was somewhat easier: a multi-sphere trace would get all the Pawns in the beam's path as long as I saved the beam's start and end points from the "Beam Spawn Function" as variables. Which I had to go back and do (they were originally only local variables). Because I wanted the attack to be multiple hits over the course of the beam (about 10 hits over 2 seconds), I would need to either do something creative with macros (which I had done before but didn't want to recreate) or just tie an event to the GAC's Tick. So, with some clever uses of delay nodes and a gate I managed to assign the "Get Hits" event to the Tick event.

ree

I just needed to add a check for allies or enemies (and only doing damage to enemies). As well as recreate my "Parse Hits" function from my Attack Controller Component, because otherwise the Multi-Sphere trace will hit the same actor multiple times. At this point I heavily considered refactoring my Attack Controller and GAC to be part of the same inheritance tree but it would have required more advanced C++ scripting to make work and while I will do that on my next project the challenge for this one was to do it entirely within Blueprints, and even if it is to my detriment I'm no quitter.


Only two things were left for me to do at this point: 1) pull characters into the beam and launch them when done, and 2) add the explosions to the end of the beam.


Number 2 was easy, I just had to spawn my already created explosion Niagara system. I put a couple of them in between delay nodes on the "Start Get Hits" event. I have 2 because with how long the explosion lasts, it doesn't look right with just one either at the beginning or ending of the beam effect.

ree

Number 1 was ultimately just as easy, but required a bit more work. I wanted to do something like this for two reasons, the first is that normal hits work the same way and consistency is key, the second is that it looks really cool and there's a lot to be said for style points. Basically I use the map of hit actors from "Parse Hits" to uniquely add the actors to a separate array and apply a launch force to surviving actors towards the center line of the beam. For actors that die as a result of damage taken, or to all actors at the end of the attack, a random vector in a cone facing up from the beam is applied as another launch vector to send them all over the place.

ree

And here is the full thing! Yay!


 
 
 

Comments


bottom of page