CAGD 470: Video Game Production

Sprint 6

November 7 - 20, 2025

    This sprint I was finally able to focus purely on UI! Which I was and still am excited about because that is what I really wanted to learn in Unreal Engine 5.

    First thing I did this sprint was making a damage vignette, which flashes every time you take damage. I made a quick and rough vignette in Photoshop, using black masks and a white background to achieve the vignette effect. I then basically made a fade animation in a widget that shows the vignette, creating a new vignette widget animation every time the player takes damage which shows up here.

White vignette I made for the damage flash effect

Sceen flashes red whenever damage is taken


    I then worked on the text pop up system, where when a player walks through a trigger zone a typewriter text effect pops up towards the bottom of the screen that gives them instructions or tells them what to do. 

The setup for the dialogue zones, where you can add a new element in the string array for the text


    Since this is a fast paced game, I wanted to add a way to interrupt the dialogue mid-animation so that the player can skip through dialogue if they want, or just skip to the end if they are fast readers. To make it interruptible without using multi-threading techniques (which blueprints don't support), I basically did a check every time the typewriter effect revealed a new letter in the full text to see if the player called to cancel the typewriting or not. And if so, reveal the entire full text immediately. Additionally, I added a little text at the bottom to tell the player to punch if they wanted to skip / hide the dialogue since the text does not disappear on its own.

Player punching to skip / continue the dialogue typewriting animation


    I also had to make these interruptible when running into a new dialogue zone, as previously text would overlap each other and cause one text pop up to exist indefinitely. To fix this, I simply added an additional check when the player receives the event to play a dialogue, checking if text is currently already playing before starting a new dialogue animation.

Text overlapping each other when a different zone is triggered while a text animation is playing.

After the bug fix, which closes the previous dialogue immediately to show the next one


    I then worked on the pause and menu menu for the game, just adding the main functionality that we will need when playing the game. There is nothing special about them yet and I plan to add some animations and art to them slowly over Thanksgiving break. For the main menu, only the play and quit button works, while the pause menu has the main, resume, and quit buttons functioning properly.

Main menu allowing the player to quit the game and go to the first level in the game

Pause menu which freezes the game and allows the player to either unfreeze or quit the game


    I think this is the first time I've had burnout really effect me this semester, which I am glad happened before Thanksgiving break. I started off the sprint working on an internship presentation and then transitioned to adding / polishing a ton of features in 495, leaving me with little energy to invest into this project. I communicated to my teammates that I would not be getting much work done within the first half of the sprint due to those circumstances, but I did not realize that it would carry over the second half as well due to burning out so hard. I think what happened differently this sprint compared to the last sprints could be because both games are reaching the end of their development which requires a bit of crunching due to the little time we have for these projects. But I also think that my mentality played a role in this burnout.

    Since I've always wanted to do the UI for the game, I thought it would be an easy and fun thing since I've already done some of features I was planning to work on for a game jam I did not too long ago. Because of this, I sort of put it off towards the end of the sprint to spend it as "dessert time" and end this sprint with something I think I would enjoy. However when the time came to do a lot more UI work at the end of the sprint, I was already burnt out and pretty much unwilling to do anything even if it was fun work. I'm not 100% sure on how to avoid this other than "just don't do it again", but I am thinking that I should flip the tasks next time. So do the easy work to get the stuff out of the way before getting into the hard tasks and burning out afterwards.

    Thanks for checking my blog post as always!

Sprint 5

October 24 - November 6, 2025

    This sprint I worked a lot with the breakable object system, making the breaking more consistent as well as adding a whole new explosive system to the game.


    First thing I did in this sprint was improving the melee interactivity with the breakable objects. Through testing, I found that the geometry collections, the component that handles the fracturing of meshes, were being destroyed by nearby master fields (the hitbox that fractures geometry collections on touch) even when disabling and hiding the component / actor itself. This meant that even if a sliver of the punch's master field, the breakable actor would freeze because the geometry collection would just not be turned on. The result of this were static meshes that were permanently turned on and floating in place, even trapping players in rooms if the room required the player to break a wall to move on to the next section of a level.

Showcasing a bug where the breakables don't break due to it's geometry collection already being broken from touching a nearby master field


    It took a lot of personal testing and googling, but I eventually found a workaround to this by basically separating the geometry collection and static meshes into two different actors. The static mesh would be the one to initially spawn into the world, but would spawn the geometry collection actor and delete itself as soon as it was being called to fracture. The only problem with this is that breaking more than 10-20 objects at the same time may cause lag for some lower pc's since this essential spawns 10-20 fracturing objects at the time of the breaking.

Punch successfully detecting and breaking ~100 wooden boards


    I also changed the hit detection of the punch from an overlapping actors node to a multi-box trace. This made detecting objects in the punch range much more accurate since the player will be moving a lot, which makes it hard to detect if actors are within a constantly moving box.

Punching and breaking now works 100% of the time, even if objects are close to each other.


    After improving the breakable system, I added the health system to them. Basically, if an object has 5 health, it would need to be hit 5 times in order to break. After quickly implementing that, I moved to work on an explosive system. This explosive system would include damage, damage falloff from center of explosion, knockback of enemies, and impulse to nearby breakable objects. 

Wall has 3 health, so it needs to be shot / punched 3 times to break.


    First thing I worked on was detecting the damage within a radius of the explosive actor. I did a multi-sphere trace at the target location, applying the radius and types of objects it will hit. After taking all valid actors / instances in range, I then applied damage to each individual actor, utilizing our damage system we custom made in our blueprints. After getting the damage application to work, I then applied a linear damage falloff, where the further the target is from the center of the explosion, the less damage they would take compared to an actor that is closer to it. 
    
Enemy taking more damage from explosive barrels then the player, as shown from the enemy dying and the player still having more than half of their health


    Next up is probably my favorite part in this entire project, and that is applying knockback to actors in range. I utilized my designer's existing code for soul explosions which apply knockback to enemies in range and added it to the current explosion system which got the knockback to work right away with pawns and other character actors. I then worked on getting the explosives to break nearby breakables and fly in other directions. I basically damaged the breakable actors in range to switch them to their geometry collection, and then applied a field that broke the actors and sent them flying away from the center of the explosion. Testing it out was so much fun, especially when I was stress testing the performance of this system and lined up a bunch of breakables everywhere like a chain reaction : )


Transient field that fractures the geometry collections and sends them flying off from the center of the explosion

Explosive barrels breaking everything! Including each other!


    I then added a blacklist for damage types for breakables in case we wanted to make strong objects like metal barrels or something that wouldn't break from just bullets or punches. It actually can work with all other things with health like players and enemies so we could technically have enemies that don't take damage from bullets or things like that.

Adding damage immunity to melee punches to explosive barrels


    Lastly, I worked on fixing up the card ricochet ability and adding a player hit marker. The card ricochet was not working because our other programmer recently overhauled the projectile system, so I just had to adapt it to the new system. 

Hit marker (X symbol) shows up at the player symbol when something is damaged with a bullet


    This sprint was more focused on polishing and adding some smaller features, and I think we all did pretty well as a team. I'm getting more excited as more art is added to the game because we can finally see the game closer to what we all imagined in our heads : )

Sprint 4

October 10 - 23, 2025

    For this sprint, I finished up the mechanics of the Deck of Fate ability, improved the breakable object system, and improved the pathing of the AI when running alongside other AI units.



    The first thing I completed this sprint was the ricochet bullet bouncing between multiple enemy units when in between two cards. For the sake of time, I had to scrap the bubble sorting function that I had because it simply wasn't working. Swapping elements in arrays in blueprints is a huge pain in the butt because of how hard it is to reference temporary elements, and if I am to come back to this I will definitely use C++.
    
    I also had to slightly rework the base projectile class which the ricochet bullet was based off of. Previously, the the hit scan functionality of the class would only work with the player character's position, so I had to add custom variables to represent custom start and end points of the sphere casting of the hit scan. From this experience, I can see why programmers generally prefer to do C++ by itself or through hybrid programming because it was way more tedious to add this functionality than it should have been. I had to add these variables, make sure they were public, and then go to each of the child blueprints effected and make sure that they were able to calculate the start and end points of the hit scanning.

Setup the hit scan system to support both projectiles coming from the player as well as from other custom positions


     I also took all the ricochet logic and calculated it all on the card projectile itself rather than on each individual ricochet bullet. Previously, the ricochet bullets that spawned from each hit would perform its calculations and trajectory before shooting to the next target, but it would bug out a lot because the target actor it was aiming towards would become null a lot of the time. I also figured that it would be easier to have all the ricochet pathing be calculated at the start of the sequence rather than have it calculate on the go. This got the ricochet system to work finally, and all I really had to do was slap on a Niagara beam effect to show the player the fun trajectory of the bullets!

Ricochet beams from the Deck of Fate ability


    Next up, I worked on getting the Deck of Fate cards to stick on to breakable objects. In Unreal Engine 5.6.1, geometry collections do not update their world position when moved. This was a huge problem for the cards, because even if the cards were sticking to the blueprint actor, the geometry collection would continue to move without telling the actor that it would move. This resulted in scenarios where the cards would attach onto snowmen and stay frozen in the air while the snowmen would roll away.
    
    I tried looking around on the internet to see if there was a boolean I could turn on in order to update it's world position, but couldn't find anything about that. I decided to do the next best thing I could think of, which was to have the static mesh and geometry collection be two different things. So when the actor is being called to shatter, the static mesh, which has the most up to date world position, would swap out with the geometry collection which would shatter when enabled. Thus, I was able to get the best of both worlds!

Cards sticking onto movable objects, keeping their relative position on them as they move


    As for enemies AI improvements, I worked on improving the pathing of the enemies when they are moving alongside each other. Previously, enemies, especially the melee ones, were often getting stuck on each other when they were clumped up in a group. The first thing that came to mind was adding EQS to the system, which could be time consuming and also taxing when it comes to performance. So I opted for simpler methods, and the first thing I did was up the speed of the melee characters by a lot. It actually solved the issue of melee enemies getting stuck on each other because I guess they just never had the time to actually stop each other from moving. However, the issue of them getting stuck was still there when ranged enemies, who weren't as fast, were added into the mix. 

Melee enemies sprinting towards the player


    I looked around on the internet and actually found that they had an avoidance section in the character movement component. The avoidance is basically adding a radius around the given pawn, and tells other pawns to maintain a certain distance from that specific pawn when calculating their pathfinding. So I turned it on, adjusted the parameters to fit the size of the enemies, added avoidance to their character movement component. and it actually worked most of the time! We may have to actually implement EQS into the mix into the future to fix this issue once and for all, but the new improvements to the movement should allow most enemies to work properly in narrower hallway / passages.

All enemies are now able to run around other enemies


    Last thing I did was add a quick level transition trigger, which allowed my level designers to have the player switch levels whenever they reached the end of a level. Very simple to setup, as all you have to do is set which level the trigger would send the player to.

Touching the pink level trigger transitions the player to a different level


    Overall, I would consider this sprint a successful learning sprint. I am a lot more comfortable with using blueprints now, and am starting to figure out a lot of the limits and workarounds of this language. I was able to pick up on a lot of patterns and good practices from seeing my teammate's blueprints so I am lot more confident in setting up new blueprints going forward. I also just learned the process of adding C++ to a team project, so am excited to hopefully implement some C++ programming into this project soon!

Sprint 3

September 26 - October 9, 2025

    In this sprint, I fixed an issue with the ranged enemy attack and worked on getting most of the mechanics of the deck of fate ability up and running. 

    So first off, the issue with the ranged enemy's attacks. I didn't realize this during the time I was working on the attack but the attack did not take elevation into account. The enemy projectile that it spawned would only travel on the same Y-axis as the ranged enemy itself. So if the player was on a different elevation, the bullet would just fly in a straight line above / below the player which defeats the purpose of the ranged enemy in the first place. To fix this, I would keep the x and y values the same of its rotation values, but change the z value (roll) to reflect the direction between the enemy and player. 




    In summary the Deck of Fate ability is where you can throw a card and have it stick onto a visible surface. Player can shoot said card to reflect bullets to nearby enemies. In cases where multiple cards are placed within a radius of each other, ricochet bullets can bounce between cards, prioritizing the cards with most enemies within the pathing of the bullet.

    The first thing I did was allowing the player to pick up the deck of fate ability using my designer's pre-existing ability pickup system. Technically, the ability is just a gun but you are able to use it while it isn't equipped.

Picking up the ability (green spinning cube) and shooting projectiles


    Next up was getting the cards to stick onto the wall. First, I set the projectile to be a really thin cube to represent the card being thrown. Then, I just had to learn how the projectile system my designer set up in the game worked so that I could control the lifetime of the projectiles as well as the event where they get stuck onto the walls. I also made the cards spin while they fly!

"Card" projectile which is a cube that is scaled to be a card.

Adding spin to the card, which updates its rotation every frame before it 


    After getting the cards to spin, I realized that the cards were set to destroy as soon as the cards were spawned into the world. This is a problem because the cards might be traveling through the air for a while before the player could have a chance for the card to land and shoot it. I made an override function over the existing cleanup code, so that children of the base projectile class could customize how the bullet cleaned themselves up if needed. I then created a temporary float variable to keep track of the remaining lifetime of the card projectile. The lifetime would only start ticking down once the card has landed on a surface, and could also be added onto during runtime to extend or set the lifetime dynamically. I used this when setting the remaining lifetime to 1 when a breakable object with a card is destroyed.

Creating a custom event to run the cleanup code so the children of this projectile class can customize the cleanup as they need

Lifetime of projectile starts to tick down after the card is settled (when spinning is set to false)


    Next up, I worked on getting the cards to stick onto the surface properly as well as rotate to make it seem like they are sticking flat onto the surface they land on. I got stuck on this task for a couple days because I could not get the card mesh to rotate properly to the hit position, but that was because I was rotating the entire object as a whole rather than the mesh itself. 
    I was able to implement the cards sticking onto the walls, floors, and enemies, but not breakable objects because geometry collections aren't welded and cannot be welded to their parent actors. I plan to address this in the upcoming sprint after I finish the ricochet functionality of the deck of fates. Moving on from that, I then made a little animation of the card spinning and floating upwards after the object it is attached to gets fractured. I also set the lifetime to the duration of the animations so it deletes itself after finishing.

Cards sticking on to surfaces, and playing a spinning animation if the object is fractured


    

Next up, I got to work on the ricochet of the cards, which is the most fun feature I have worked on so far in this project. I got to work on the cards detecting each other first, where a card that has been shot spawns a projectile that goes towards the closest card in range if it exists. It was sort of hard to see the trail of the ricocheting going on, so I added a temporary ribbon trail in the Niagara system for the bullet tracer to help players see the pathing of the bullet.







    

    Afterwards, I got to work on getting cards to hit a single enemy. For this one, I made it so that the cards would prioritize hitting other cards before hitting an enemy, which added a fancy ricochet effect of the bullet bouncing around the enemy before hitting it.


    Then I tried moving to one of the main and final features of the ricochet, which was to get bullets to ricochet between multiple enemies. This was quite the challenge for me because there is no default sort function for arrays or dictionaries and I needed one for sorting an array of actors from closest to farthest from the starting card. I searched up some sorting algorithms online, and came up with a 'simple' solution of going through each element, comparing that element with every other element to see whether it is closer or not and swapping positions accordingly. This is called 'bubble sort', which is inefficient when it comes to larger arrays of data but worked for me because there will probably not be more than 100 elements whenever the sorting is called.

Bubble sort running through each element twice to sort array by closest to farthest actor


    So after getting the ricochet logic and sorting figured out on paper, all that was left to do was to was to add that sorted array to the ricochet bullets so that they would know which targets to aim for during its traveling. That was until I ran into physics issues, where the ricochet bullets that spawn from hitting the enemy characters are colliding with them and changing their trajectory. I tried to tweak many settings, setting the ignore list of the projectiles and all numerous times but nothing would work. This meant that I now have to switch the entire system over from projectile-based to hit scan.

    Well now comes the question of why I chose to use projectile physics rather than sphere-casted hit scanning in the first place. I chose to use projectile physics so that I could have an easier time implementing the bullet trails instead of having to calculate the distance and then playing the bullet trail effect. I think that worrying about the visuals was something I shouldn't have worried about so early on in the development of the feature because now I have to redo a lot of programming of the system I was developing. But that is for next sprint. Thank you for reading!

Sprint 2

September 11 - 25, 2025

    For this sprint, I worked on breakable objects, enemy spawner improvements, and added a ranged enemy to the game. I also ran into some GitHub issues which delayed my work, which I will try my best to explain how I "worked around" it.

Breaking walls : )


    The first thing I completed this sprint was the breakable object system in the game. For the player to break objects, the object must first be fractured using the Fracture Mode in the scene. But before I got the geometry collection from the fracturing, I made a dataflow graph which randomizes the fracturing of the breakable object. After hooking up the dataflow graph and getting the geometry collection, I set the damage threshold of the collection to an extremely high number, so that the object would only break from the punch and not from other sources like projectiles, fast moving enemies, and so forth. I also edited the lifetime in the geometry collection of these objects so that when the fractured pieces stop moving, they eventually shrink out of existence. 

Adding the amount of fractures to a prop (each progressing level adds more total fractures)

Dataflow graph which randomizes the fracturing of the object so objects breaks differently every time


    After setting up the objects to fracture, I now needed to make it so that the player punches would trigger them to break. To do this, I created a Master Field object, which applies an almost infinite amount of damage to any geometry collection that touches it to guarantee that it breaks. I tweaked some values to make the pieces fly off in a direction as well as delete itself after a small duration. However, I could only get them to fly towards the up vector of the master field actor, so I rotated it to face the correct orientation when I spawned it in front of the player camera. After taking a day or two to figure out the correct outcome to break objects, I was able to simply spawn one in front of the player when they punch which breaks any breakable object they are facing!

The three arrows pointing upwards represent the direction of the velocity applied to fragmented pieces

Rotation code of the master field, which I incorporated alongside the same code I used to calculate the orientation of the punching hitbox

Punching a prop, which breaks it into pieces!


    Next up, I worked on improving the enemy spawner code. I made it so that multiple different types of enemies can spawn in the same batch, and added an enemy wave system. So when a wave of enemies are defeated, a new wave of enemies spawn if there are more enemies set in the individual spawners. 

New wave of enemy spawns as soon as no enemies of the previous wave remain


    To make this system, I used blueprint interfaces for communication between the blueprints and separated the spawner setup into three parts: the master spawner (green cube), the individual spawners themselves (yellow hole spheres), and the trigger to spawn them (see-through blue section). The master spawner is the one that sends the individual spawners the signal of which wave to spawn, and based on the wave number given, the individuals spawn their batch of enemies for that specific wave. Also, every time an enemy dies, it checks to see if any other enemies in the wave are still alive. If there are none, it sends a signal to spawn a new wave, and the cycle repeats until there are no more waves to spawn.

Master spawner (green), individual spawners (yellow), trigger zone (blue)

Individual spawner with settings to change what enemies it spawns as well as the amount in that specific wave


    Lastly, I started to work on ranged enemy AI. The goal was to have an enemy that shoots a projectile at a player, and only move to the player when it couldn't see the player. However I ran into GitHub desktop issues.

    During the middle of the sprint, our team added GitLFS 2 to our project, which made it so that I couldn't open the project at all because I had to recompile the project. I also couldn't recompile the project because I was missing Visual Studio files apparently, and took me about a day alongside my designer to fix it by changing the compiler version somewhere in the Unreal Engine files (shown in the image below).



    But even after fixing the compiler issues, I ran into more GitHub desktop shenanigans, where I couldn't pull changes or switch branches because there would be changes that could not get discarded from my change list. I was too scared to do it at the time, but it turns out that those were files that were checked out by me or others, and that I had to push before I could do anything.

Un-discardable changes


    So finally, after figuring all that out over the span of a weekend, I was able to work on the ranged enemies. I scrapped the behavior tree I made for the melee enemy, rearranging some of the same tasks and adding a ranged attack task to the enemy. I then had to set priorities on the attack side of the behavior tree while setting the chasing of the enemy to a lower priority. So when I updated a blackboard boolean value which represented if the enemy could see the player or not, the enemy would stop in place and prepare to shoot at the player. To update the blackboard value while the enemy was in the middle of chasing the player, I made and attached a service which checked if the enemy could see the player two times per second.

Behavior tree of the ranged enemy, showing the high priority attacking side, and the low priority chasing side of the tree.

Ranged enemy peeking out and shooting the player


    I also almost forgot to mention, but I attached Unreal Engine's AI perception component to the ranged enemy to detect if it had line of sight to the player or not.

Enemy perception, which shows it sees the player with the green sphere in its radius


    And that was all for this sprint. It was pretty rough during the middle of the sprint, but I'm glad those issues won't be slowing me down for the future sprints (I hope). Thank you for reading this blog post.

Sprint 1

August 28 - September 10, 2025

    I am working on a team of 5 as a programmer / flex on a game called Escape The Devil's Saloon. It is a boomer shooter inspired by titles such as Doom and Ultrakill, where you use a 6-shooter and double barrel shotgun to gun down enemies in a setting from the wild west. There will also be abilities you can use for style points, such as throwing cards which you can shoot to bounce to other cards or enemies. It is being developed with Unreal Engine 5.61.

    As for the work for the first sprint, I spent the first chunk of it learning how to use Unreal Engine 5. I have a bit of background knowledge from using Unreal Engine for Fortnite development as well as some from schoolwork, but I have never experimented with it on my own. With the help of my lead designer, I was able to get the basics of blueprints down.

    The first notable feature that I worked on was the enemy AI. It seems like a crazy thing to try to create while learning Unreal Engine, but it actually fit quite well with what I was already familiar with. I had previously made an AI system myself on Roblox, so all I had to learn was how to convert that to Unreal Engine. Thankfully, there are many more tools provided on here than on Roblox, and I was able to get the enemies to chase the player pretty fast.



    I also was able to implement AI behavior trees into it, which is going to be an insane feature to use in the future because I had to create it from scratch in my personal projects. The sequence nodes for the behavior tree blueprints were insanely helpful and intuitive compared to what I had to do on Roblox (which was make my own sequences myself). It also made coding the tasks within the behavior trees super organized since they all had their own separate tasks, or in other words, a function the AI runs through for a specific task. For example, the 'BTT_FocusTarget' task made the enemy character turn towards the player.


    I also creating my own punch animation for the enemy AI which I had a lot of fun with. I have some experience with animating in both Unity and Roblox, but never on a character with joints rotated in all sorts of ways. After making it, I quickly hooked it up to the enemies and had them punch the player whenever they got close.

I also made enemy spawners which could spawn enemies. For now, it spawns enemies when the game starts. In the future, I plan to add multiple enemy types that can spawn, waves, and spawning when an invisible trigger is touched.


    I also created some UI sketches with my designer for future reference when I start on the UI programming. For now, we decided to sketch the main menu, in game UI, and how a skill will look like during its use.

Main menu screen as well as the in-game view

High noon ability where time slows down and allows you to click on enemy heads during that time. When the time runs out, all enemies who were clicked on get killed.

    The last thing I was able to get working was the punch hitbox, which takes a box collider, moves the box in front of the player character, and gets all overlapping actors within the hitbox. The damage is customizable, utilizing a custom damage system created by my lead designer. I was in the middle of trying to get it to break walls, but I was not able to figure it out in time.


Overall, a fast and solid start for this project given that I am a beginner, and I am looking forward to working more on this project!


Comments