top of page

Bugnauts!

Third-Person Educational Bullet Hell
Gameplay Engineer
June 2023 - May 2024
Made in Unreal 5

About

Bugnauts! is a game developed in Unreal Engine 5 being backed by the AGP class at USC. Players play as Chloe in an Educational Third-Person Bullet Hell freeing bugs that are trapped in their phantom forms and learning about ecology along the way. For my part of this project I worked on developing the systems used to create enemies and projectiles. I did everything from tools engineering with Slate in Unreal to doing technical procedural animations for the bee.

Highlights

  • Complex Projectile System which included custom editors/tooling, customizable collision manager, object pooling, homing, flight paths and a variety of other features

  • Created flight movement inspired by the real life movements of a bee

  • Worked with designers to create 20+ node and systems for designers to create enemies

Projectile Systems

Projectile gameplay was split up into different sections. Projectiles themselves, which when formed together was considered a Wave, and when you combine multiple waves together you get a Pattern.

Projectile patterns were basically a list of waves and it would be inputted into a component onto an enemy to actually run the pattern. From there the component handled delays between waves as well as sending off individual waves to a different component that would handle actually spawning projectiles and doing all the math required. This separation of responsibilities was great if we needed in special scenarios only use one or the other!

Expand the dropdowns for specifics about projectiles such as tooling or gameplay

  • Showcasing the creation of a projectile pattern

    • Block-Based Drag & Drop Editor

    • Support for designers to create multitudes of projectile patterns quickly and efficiently

    • Preview window for quick testing without having to go into game

    • Projectile Patterns were basically defined as a collection of waves and delays inbetween waves. To support this I created a drag and drop interface which overall is fairly boring engineering wise in my opinion as I took Unreal's current drag and drop support and extended it. Basically taking wave nodes and making them movable and locking delay nodes and making them unable to move. With some further logic I added I basically made delay nodes reorientate themselves whenever a wave node was moved so that every wave node was always separated by a delay node. 

      The more interesting part of this is the preview window as this was the biggest challenge. The biggest theology with testing(which is what a preview window is IMO) that I have is that testing should run as much of the code that's going into the build as possible. You shouldn't have a projectile spawning logic that's different for the preview window compared to the actual game. 

      Unreal solved a lot of this for me as it's preview worlds were the exact same as the actual game world(go figures, that's good engineering) which allowed me to create the components I needed in the preview world for it work. You might be like where's the interesting part well that's where object pooling comes in. I had object pooling implemented in the game for obvious reasons(crap ton of bullets :p) and had it connected to Unreal's subsystem architecture(singleton architecture basically). The problem was that the object pool was only getting created when the game starts due to how Unreal handles the specific type of subsystem I was using. Turns out previewing a pattern isn't exactly starting a game so I made a mini Depedency Injection-lite system. I say lite as it was hardcoded to only work for the object pooling interface as it wasn't needed in other parts of the project but it worked wonders as I was able to create an object pool when an editor was opened up and inject it into the preview world. So instead of pooling projectiles it would instantly create and delete projectiles which helped designers who were on their own personal laptops trying to run a bulky engine like Unreal.

      There were also problems with serialization when it came to patterns but I already wrote too much and those problems are more related to Unreal and not really interesting for most engineers.

  • waveeditor.png

    Full Screenshot of Wave Editor

    • Improvement to old wave creator system that was only able to create very basic cylindrical patterns.

    • Allows free placements of projectiles in any sort of pattern that designers want and also allows them to set the direction they are fired from

    • Uses the concepts of shapes that designers can place into the editor and adjust settings

    • Allows the deconstruction of shapes into single projectiles that can be further edited

    crazywave1.png
    crazywave2.png
    crazywave3.png

    You can create some pretty funky patterns using the shapes!

    • Waves defined in Bugnauts are just collections of projectiles that are fired at the same time. At first, we just used a simple data asset to define the parameters of a wave. Mainly the radius, the number of projectiles, and the min and max angles. This meant we were only able to make waves that were circular in nature and that was about it. This slowly became obsolete so a better system had to be put in place which is what you see above this.

      The system relies heavily on math to create all patterns as it considers patterns just a collection of shapes which can be edited. Under the hood, there is basically a base shape class that can be extended to from new shape that defines how a shape should be drawn and the settings that should be displayed as well. It would be nice to do this through interfaces but game engine support for interfaces is very bare bones it's not evey funny. Each shape that is part of a wave is then serialized and can be loaded into the game (usually at the beginning). Furthermore, shapes can be broken down into single projectiles in the hierarcy through just some simple math that is also used when spawning projectiles. 

      Overall not the most complicated system ever but in the future I would like to make shapes able to be parented to eachother and then use matrices to create local and world space distinctions that could be used to edit projectiles more intuitively.

  • flightpath.png
    • Bezier-Based editor that allows designers to create 3D curves that projectiles would follow as a "flight path"

    • Flight paths would repeat until the projectile was destroyed so designers didn't have to draw redundant curves

    • Visual indicator of how a projectile would repeat inside of the editor

    • This is in my opinion the coolest and the most fun editor to create. It was probably the easiest pure tooling wise as I didn't have to deal with a 3D world(you can edit beziers in 3D hahahaha and it was done for this game as well but it wasn't as much as a challenge as the other editors) or complicated logic that I had to deal with in the previous 2 editors. Instead this posed a challenge gameplay wise due to flight paths themselves being able to be very complicated beziers instead of just a math function or a straight line. Well it wasn't just a challenge it was actually 2. One was determining how to keep a projectile moving at a constant speed and the other challenge was how to repeat a flight path until the projectile eventually was destroyed.

      Moving the projectile at a constant speed turned out to be surprisingly fun. I looked towards a previous implementation I had done before in another project which just lerped objects between two points at a constant speed. For that I needed to know the distance between the points and basically I would just calculate the distance that the object would need to move in a specific frame and took the current distance already traveled and add them up together to get the target distance. With the target distance I divided by the total distance of the segment which gave me a target percentage on the segment that the object would need to move to. From there doing a simple equation with the percentage to find the location I needed was very easy.

      Implementing this into beziers was a bit harder though as in beziers "t" isn't constant with the distance and also running the math to calculate the distance every time I needed to move a projectile(which was a lot) is super ineffecient so instead I cached the result into a lookup table. When projectiles actually were spawned in game they could now look at this lookup table which basically attached a distance to "t"(the percentage of the line). So I could use the same calculations I did with my example that just used straight lines and now use it with a more complicated bezier curve. Obviously a lookup table can only contain so many values and more often than not values would land inbetween actual values so we can just a lerp between the two neighboring entries to find the best value so the projectile would move smoothly! One additional problem I did encounter was that projectiles would drift further and further off the flight path as time went on which happened because I kept updating the reference position

      So that was problem number 1 solved. If you still aren't bored here's problem number 2 which is how to repeat projectile patterns properly. Before we get started here's a funny image of 100 projectiles going in a congo line when it wasn't working. Notice how some projectiles are really inaccurate, we'll talk about why that's happening later.

      congoline.png

      Basically repeating patterns had two solutions that were both actually implemented. The first solution was that I took the direction that the bezier ended and created a transform matrix with it that was then applied to the points of the new bezier, problem with this is that projectiles would eventually end up repeating on themselves for a lot of patterns which is what happened above. I realized later after implementing solution #2 that I could have added an additional transform for scale which could create interesting spiral patterns that were able to repeat properly.

      For solution #2 it was actually very basic where a direction was picked by designers and the bezier was just translated on that line with no additional rotation data which worked a lot better in preventing projectiles from repeating and was also easier to implement and created repeating behaviors that designers preferred.

      So remember how I said pay attention to how projectiles were drifting in the above picture. What happened was that I when transformed the bezier into it's new position to continue repeating, I would position the start of that bezier on the projectile. Because a game runs at an indiscriminate amount of time between each frame different origins can be set for different projectiles and when a projectile repeats a pattern multiple times the margin of error would build and build. This was quite a cool example of setting the right reference and was a very simple case of updating the new bezier position to be on the old bezier position which eliminated this problem entirely.

      repeatingflightpath.png

      Flight Path Editor showing how a pattern would repeat with solution #2 implemented

  • Projectile collisions in Bugnauts! go through a specific system as opposed to the usual suspects. This was made because projectiles was hardcoded to destroy when they collided with something. And due to execution order for collision events being random we would get frequent but not consistent bugs because the projectile had been reset back to it's original data when returned to the object pool.

     

    The projectile collision manager added another important benefit in allowing projectile collisions with specific components to trigger different logic which was really important for certain aspects of the game.

    collisionmanagerexample.png

    The above example shows an ant enemy with a shield. If a specific projectile type collided with the shield it would destroy the shield and if any projectile collided with the ant mesh it would deal damage. There are multiple similar situations for this as well which is the other reason why the projectile collision system was needed. 

     

    For this specifically, I created a receiver component that can be attached to any actors that need logic attached to projectile collisions. Inside the receiver component it stores a "map"(key values weren't unique). Keys were the components that could be collided and then the values were whatever implemented the correct interface. All the receiver had to do was get the correct values out of the map and pass the collision data along which created a very scalable system.

    • Homing functionality using a data curve so designers can tweak homing strength depending on the lifetime of the projectile.

    • Object Pooling that was stress tested to handle hundreds of projectiles

    • Object Pooling was future friendly in allowing designers to build new types of projectiles and not have to change the object pool to support new types of projectiles

    • Event System for projectile state for designers, audio engineers, tech artists, and others to connect behavior to changes in projectiles

    SplineFollowingProjectiles.gif
    • Shown above is a different type of ways projectiles can move using Unreal's spline component. ​This made it really easy to generate arcing projectile patterns at runtime

Enemy AI & Gameplay

  • BeeFlyDown.gif
    BeeDash.gif

    Above you can see examples of the flight of the bee enemy which is inspired by the dashes of a bee in real life. Though we have the speed of the dashes way lower in game due to design reasons. However, the dash movement is really cool because we take flight navmesh pathfinding data and instead of doing smoothing on it we instead take the rough line segments and input them into a flight movement component which holds the segment information in a queue. From there we can just pop the segments off and run movement logic on that information.

    The specific movement logic is also pretty cool because I use a float curve that designers can edit to tweak the acceleration of the bee as it goes into dash and even cooler comes to the end of the dash it will run the float curve in reverse. Starting the dash was easy as I just ran the float curve, however, ending the dash was hard as I had to figure out when to start evaluating the float curve so that the bee ended up in the right position. To do this I basically calculated and cached a riemann sum(integral) for the curve. Technically, I did this twice and then cached the final result as a total distance the float curve would travel and would just run a simple distance check to determine when to start deaccelerating.

    This sounds all good and all but if the segment is on the smaller side there would be scenarios where the bee would have to start deaccelerating before it even finished accelerating. This was actually really easy as I would compare the cached distance(multipled by 2) to the total length of the segment. If it was smaller all that meant was that once the bee reached halfway on the segment I would just have to run the float curve in reverse from the position I last evaluated!

    For actual attack gameplay there was also a few really cool things that I had the opportunity to do. For example, take the below bombing run type of attack. 

    BeeBombingRun.gif

    This attack will basically find the position across from the player in the air and move to that position in a sort of bombing run. This was actually really easy to optimize as it was just a simple raycast that overshot the player. If for whatever reason the raycast hit something it just wouldn't run the attack and the Behavior Tree would move on. In most cases though the raycast never hit anything so we could just move the bee to this location and run projectile patterns during. All of movement and querying positions existed outside of the flight navmesh system as it was more performant for it to exist outside and also made more sense as it was a bombing run and the flight navmesh system could generate paths that were not straight.

    Another very cool gameplay feature that I implemented for the bee enemy was it's ability to find out the best random location near the player. Looking back on this project it would have been a lot easier to use EQS as I hardcoded a lot of these systems with their own custo algorithms but you live and you learn. I will not get into too much detail about this feature because of that. Think about how you would implement this using EQS and my algorithm is just a hardcoded version of that.

  • The repositioning system calculates positions around a target that are within a certain range and also in Line of Sight. It uses a simple EQS-like algorithm that checks points in a radius around the player. No further performance improvements were needed as there were 5 enemies tops that would be running this algorithm and the algorithm would only run when enemies queried for the information.

    In the future I would like to add Navmesh distance as a parameter that controls this query to the repositioning system so that enemies don't see high ground as a viable option unless it's actually a close walking distance. This on it's own would be a very expensive algorithm so I would check the z offset between the enemy and the current checking target and if it was above some sort of threshold I would then run the more expensive algorithm that would check the distance. Maybe another time I'll try this out on my own time!

  • image.png

    The alert system calculates a hidden alertness stat that enemies use to determine which behavior they should use. It supports different enemies having different values for being alerted by having the alert calculator take in a data asset that can be switched out. For example ants are harder to alert than beetles and this is represented by changing the alert rate increase in the data for each data asset.

  • Some notable stand out behavior tree nodes

    • Generic/Template Blackboard Key Setter Node that can set any type of blackboard value straight from the behavior tree instead of creating a task for each new type of value

    • Special Query nodes to query special types of data such as finding random locations within a target or querying air positions for flying enemies

    • Movement nodes with heavy customization for special types of movement

    I created over 20+ nodes for designers and myself to use when creating behavior trees ranging from being specific to enemy types to being useful for all enemy types.

  • Other enemy gameplay stuff I did on this project!

    • Numerous features for the Ant Queen including Ant Mounds, Attack Selector, and other features

    • Support for designers primarily concentrated on enemies and creating features that allow for gameplay creation by them

    • Melee attacks for ants that eventually ended up getting cut so animators could concentrate on more polished projectile attack animations

    • Semi-Complex Charge attack for beetles that ran into walls and stunned itself when missing the player

Tech Art

So I'm not too sure how but I got roped into doing some tech art for this project which is an area of expertise that I'm not super familiar with. However, it was actually really fun and allowed me to put my math skills to the test.

BeeAnimation.gif

The bee had completley custom procedural animations that I made using Unreal's Control Rig tool. Basically learned this tool from scratch near the end of the project to make the bee less static. All I did for the bee was store the last position of bones that needed to react to movement which I could use to calculate velocity. Putting it through a spring interp and actually updating the bone positions to that helped sell the effect of legs moving behind the bee. This was definitely hard to do but really rewarding and fun and I'd argue that the result looks very professional.

BlastPlantAnimation.gif

Another area I worked on was adding more feedback to grabbing projectiles. This went more into shaders instead of the technical animation I did with the bee. It also happened to be a lot easier than doing the procedural animations for the bee as all it required was calculating the dot product between the player and the plant and then applying that as a wind force to one of Unreal's Material Nodes.

Not pictured here but another fun tech art thing I did was creating an animation for explosive barrels. This was a lot harder than it sounds, obviously doing a squash & stretch was very easy, however, tipping over the barrel realistically was a bit harder. Calculating the direction to tip was very very easy, however, you can't just rotate the barrel because you need to rotate from a reference point. If you have a water bottle on your desk right now it's very easy to show, though preferably empty or capped. Try tipping it over and you will see that it rotates on the bottom edge at the point that is furthest away from you. This is really easy to understand, however, actually implementing was a bit more difficult. What I ended up doing was using matrices to transform the barrel into a local space that was relative to that point and then rotate the barrel based upon that which worked super well!

bottom of page