Bullet Bloom – BB – Update 2

close upBullet Bloom – BB – Update 2close up

Posted: October 05, 2024

Last Updated: October 18, 2024

Bullet Bloom – BB – Update 2

Timeframe: September 15, 2024 – October 5, 2024

!!! Jump To Visual Demos !!!

I hope you’re ready for round 2 of bullet hell updates! I think it’s been about 3 weeks since the last one. My excuse is that I had to take some time to go to the Mothman Festival in September, so cut me some slack. Besides, I never claimed they would be weekly, despite the fact that they should at least be on a fixed schedule. Nonetheless, here are the updates.

HIGHLIGHTS:

POST-PROCESSING:

At this point, I wanted to add some visual flare to some of my patterns. Transparency and bullet size modifications were already implemented, but there can always be more pizzazz. Even more importantly, I wanted to make sure that cool visual effects were at least possible, even if I wouldn’t necessarily use them when making my own bullet hell in my bullet hell-maker. Thus, I began my journey into adding some alterable effects to the software. After a little trouble setting up post-processing, I finally got a bloom (glow) effect working! Though a simple glow, it really jazzes up the place.

Similar to my implementation of bullet transparency, I had to edit the sprite shader to accept new “emission” data that would be tied as a data component to each sprite entity.

More post-processing effect options are to come, but they are not top priority. I mostly just wanted to get an idea of if I could do it.

TRIGGERS:

Here’s the big feature for this update: Triggers. What are triggers? In my head, they are just “some thing that the user wants to happen at a specific time”. That’s it. Want to change the music track when a wave starts? Trigger. Want to turn on a Vignette post-processing effect that follows the player when the player takes damage? Trigger. Want to spawn a coin every time a coin spawns and crash your game? Trigger. My goal with triggers is to make them as broad in scope as possible and add a copious number of places where triggers could be used.

With this frustratingly vague concept in mind, I went about hitting my head on walls for over a week until something that actually worked popped out. Here are some of the various design problems I ran into in that time:

  1. Way too broad (but I like it): I really wanted triggers to be able to do anything, but this huge scope means that I had to appeal to the lowest (slowest) common denominator. There are multiple aspects of the software that are implemented in the slower object-oriented framework as well as the faster DOTS setup, which means the entrypoint for running triggers would have to interface through slower managed components regardless of what kind of trigger is being run. This, to me, is an okay tradeoff. If I want to activate thousands of triggers every frame, this might be a problem depending on their needs, but I’ll cross that bridge when I get to it.
  2. Way too broad pt. 2: Because triggers have to accommodate many potential actions, the data associated with it also has to be broad in scope, which means I have to account for many kinds and varying amounts of data types being passed through triggers. For example, if one trigger is supposed to set a bloom value, I need fields/space in the trigger to specify which field to alter (bloom intensity? bloom threshold?) as well as the value to which the field should be altered (0? 5? 10?…). Without having considered every potential trigger I will ever get the desire to make, I had to just pick something. I chose 32 each of floats, ints, booleans, and entities, as well as 4 FixedString128Bytes structs.
  3. Good heavens, now there’s too much data: The very obvious problem with this very obvious solution is that now each trigger takes up a lot of space. Something like: 4*32 (ints) + 4*32 (floats) + 1*32 (bools) + 4*128(strings) + 4*32 (entities) = 928 bytes for a SINGLE TRIGGER. Way too much. Considering the maximum size for any given entity is 16kB, using nearly 1kB for ONE TRIGGER is just ridiculous, especially if I want multiple potential triggers in multiple different components on the same entity. I started going down the path of “make multiple trigger variants with smaller sizes” before I thought of a 10-billion IQ idea: just shove my problems somewhere else…
  4. Oh god, I shouldn’t have shoved my problems somewhere else: The Trigger Map. This concept made me feel pain until it was done. It’s simple – when we have trigger data, just create a mapping between an ID in the Trigger Map and the actual trigger data. This way, the components that use triggers just need to store a bunch of IDs instead of all the trigger data. Super easy. There were absolutely no problems here, as everything I make is always well thought-out ahead of time.
  5. The user: If that was all that needed to be done, then everything would be hunky-dory, but oh no things can’t be that easy. You see, I frequently fail to consider that I need people to be able to use this, which means I need to make triggers editable for anyone in a similar manner to all the other builders thus far. Unfortunately, my hubris knows no bounds, so in addition to making a new Trigger Builder where one could make and save a nice, simple trigger to be used elsewhere, I decided to add “Lambda Triggers” – a pretentious name for on-demand triggers that could be made in-place from within other builders. In short, the triggers stored on components now contain a flag (useLambda, boolean) a category ID (lambdaCategory, ushort), a category-specific ID (lambdaID, ushort), and an entity field (prefabID, Entity) to represent any trigger that has been made. This comes in at a svelt 9 bytes to represent 1 trigger! If useLambda is false, the trigger map directly uses the trigger data provided on the Entity (made in the TriggerBuilder) of prefabID. Otherwise, the map queries the relevant InGameBuilder, defined by lambdaCategory, to search their internal Lambda Triggers for one matching lambdaID. I’ve rambled long enough, so just know that this took probably 4-5 days to get working how I want it to, and there’s still more to be done.
  6. Limitations:
    1. Limited Lambda Triggers per Category – Using a ushort for lambdaID limits the total number of lambda triggers per category (a.k.a. per builder) to 65,534 triggers (0 means no lambda trigger). I can’t really imagine this ever becoming a problem, but it is a relatively fixable one if it ever presents itself.

Video Demos

Emission/Bloom differences. All 3 examples use the same red bullet with different per-bullet bloom colors/intensities.

Applying emission colors to a bullet in the Bullet Builder. Emissions vary in hue and saturation to change the effect.

Bloom in action 1.

Bloom in action 2.

Making a prefab/created trigger with the TriggerBuilder to set the post-processing bloom intensity to 10.

Applying a prefab trigger to the On Fire Trigger of a Firing Point and then changing to a Lambda Trigger with a bloom intensity of 100.

Leave a Reply