Best way of implementing projectiles on complex path

I’m currently working on a bullet hell style projectile system and trying to figure out the best way to structure it. I want the projectiles to be able to grow in size and follow complex paths.

I’ll use position as an example to explain my naive implementation. Struct path contains two vectors (points) that define the start and end of the bullet’s path, a vector to define how long it’s been alive and how long it should live, and an ArrayList of function pointers. The functions take in the struct and return the point in the function’s curve at that time, which I combine to get the current position of the projectile.

This feels, to put it simply, dumb and wrong. I’ve thought about it more and I’m considering using a tagged union, but I’m very unsure on what methods would be best for my use case. Currently I’m running into the problem that the struct does not contain any extra information which might be of use to the functions i.e. the linear movement function doesn’t require any extra information but for the sine wave I would like to be able to define the period and amplitude.

In summary the goal is to have a series of modules that can be used in conjunction and define the path something should take from it’s starting location to it’s finishing location.

Hoping a more experienced programmer can help me not footgun myself! Please share how you would approach this problem or any data structures/patterns/etc you think would be useful.

I think I would avoid bullets taking complex paths at the beginning.

Sure some kind of boss might have spiraling or wave patterns of bullets, but I think those movement patterns don’t necessarily need to be implemented exactly the same way that you implement the movement for normal bullets.

I think I would start with simple bullets that have a position and velocity, possibly a time since spawn, that could be used to modify the velocity for different kinds of bullets (for example bullets that start out slow and then accelerate to a max speed for a kind of lingering/unfreeze effect).

For the more complex movement patterns maybe a bullet could be spawned attached to a path that then controls how the position and velocity of that bullet gets updated.

If your path has a start and end point I would worry about different paths that have different path-lengths that then result in very different bullet speeds.

I think it would be better to use rays instead (start point + normalized direction vector).

Also with a sin-wave pattern I don’t think you have a constant bullet speed along the path of the curve and instead an alternating between slow-down and speed-up, which I think would be too confusing for the player once there are many bullets on the screen. (Separating between linear and non linear movements could become overwhelming for the player)

I think it would be better to instead use more bullets with simple linear movement and then create more complex patterns out of multiple bullets shot in specific ways, for example by having an enemy rotate and shoot and thus create a spiral of bullets around it.

Using bullets with non-constant speed could easily get confusing, so I think it should be reserved for rare special bullets.

Regarding data structures, I don’t think it matters much, I would just start with a tagged union or enum if there are multiple cases and then figure out things along the way.

If you have more specific concerns it would help if you illustrate those with code.
I think starting with a tagged union is better than immediately using function pointers, that may not really be needed.

1 Like

While I appreciate you taking time out of your day to reply, I wasn’t looking for design advice but rather programming advice on how to best implement the system I envisioned.

This is intentional.

As of my naive implementation this is not the case.

To reiterate, this is not a game design question. I could elaborate on the design of the game and why this system works for it, but it is entirely irrelevant to the topic and I am not looking for that kind of advice.

Fair enough and noted, but that isn’t the only thing I wrote about.

Abstract program design decision discussions that are disconnected from actual code quickly become ramblings or go in directions that don’t actually apply to what you are doing, that is why I suggested to post some code.

Without that I don’t really know what to say except that I would start with tagged unions or enums, instead of more complex things like function pointers or interfaces.

It seems to me like you are spending too much time thinking about code architecture instead of using something simple and just figuring out what cases there are. Once you know what you want/need you can change how it is implemented.

1 Like

An ArrayList of tagged unions works. It’s sligthly better than the objected oriented approach of having pointers to methods, as at least the compiler knows that the methods that will be called are within a bounded set. However, the best option for this tends to be the data oriented approach of having a list for every type of bullet. This maximizes cache usage, as you’re sure that everything inside that list will behave the same way.
With that said, I’m with @Sze here. What he suggested is not a game design solution, but a programming solution, like you asked, you’re just not seeing the wall that you’re headed towards. If you’re going for a true bullet hell, modelling every bullet individually will become… Hell…
The vast majority of bullets will have the same properties, with only tiny deviations in speed or position. Such redundancy is the key for good performance. Like, @Sze suggested, you can code a a set of bullets as a ray. When the user starts firing, you start the ray, and, for as long as they hold the trigger, the ray keeps getting bigger. You can code thousands os bullets in a single computation. This doesn’t preclude special effects. If you want the bullet to impact something and become bigger, you can simply split the ray into two, the original and one that is larger. Or you could add a cookie in the ray marking that it has a modifier.
Even if the user fires while rotating, you can just rotate the ray or, if you want fancy elastic-like behavior, you can use bezier curves. One bezier curve will still be cheaper than a hundred individual bullets.
In case it wasn’t clear from @Sze’s post, just because you code the projectiles as a ray, doesn’t mean that the user needs to see a laser. You can trivially put a skin on the ray to make it look like bullets, or chickens, or anything you want.

While I think grouping up bullets could be done, that isn’t actually what I meant.
(I think if you have something like far away mini guns in space, modeling multiple bullets as a ray could make sense, to reduce the number of individual elements, but I wasn’t really thinking about this sort of optimization, my comment was mostly about trying to create a situation where it is simple to have explicit speed values that can be modified as data, instead of having that encoded in different path lengths (but there may be reasons to prefer having those implicit within the path))

But I do agree that taking a data-oriented approach would be a good idea, I am just not sure whether I would start with that, or just change towards that for optimization later.

I think the number of bullets wouldn’t ever be so high that grouping would be needed (unless you can zoom out very far, at which point it would make sense to have some clustering/spatial-partition algorithm + simplified rendering when zoomed out).

@trinityforesaken I am a bit lost on what the question is, what do you envision?
For example do you need something that has a comptime enumerable set of cases, or do you want something that is open ended and can be modified at run-time?

For the tagged-union case, you always can have one case that contains function pointers, that way you can have cases that are static and others that are only created at run-time. But unless you plan to compile and load dynamic libraries at runtime, static cases with dynamic data should be enough to describe everything.