A good way to handle managing bullets/projectiles

Started by
9 comments, last by Khatharr 7 years, 10 months ago

I am working on a game in C++ where the various characters can fire various projectiles. After firing one, they need to take a moment to reload. Because of this restriction, any given character will never realistically have more than a few projectiles firing at once, before one goes out of range(a variable set by the firing character) or hits another object, in both cases being removed from play.

The "removed from play" is the part I'm concerned about.

My idea was to set an active boolean for each bullet, have them all in a vector, and when a character goes to fire, simply grab the first one that is set to "active == false". Inactive bullets wouldn't be drawn or updated. Since I don't at this time actually know how many bullets there would at most be, I worry that having a bunch of "inactive" bullets lying around could cause problems.

Should I be creating a new bullet when a character fires and deleting it when it hits something/goes out of range?

Advertisement

Depends on the complexity of your game. For something basic it really won't be much of an issue to have an array/vector of 10 bullets and only draw/update active ones as you describe. If you do need more you can always resize that vector. It's probably better than creating and deleting bullets each time.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

Since I don't at this time actually know how many bullets there would at most be, I worry that having a bunch of "inactive" bullets lying around could cause problems.
If this is actually going to kill your CPU time, congratulations :)

Like Nanoha already said, it's highly unlikely to cause trouble, walking over a vector doing nothing but checking a boolean takes close to no time, unless you have several zillion bullets in there.

What you could do is check the last bullet in the vector on each loop, and reduce the vector by 1 if it's not used (just reduce the length, but not the capacity of the vector). Alternatively, you could track first and last active entry. While both options would work, I would not be surprised if adding this code is actually slower than just walking over everything and don't bother about it.

What your describing is called pooling, it's a pretty common technique for games. Generally, if you foresee a lot of bullets being fired over time, it's not a bad idea. Having inactive objects shouldn't be too expensive, if you handle it right. I'd probably go with an array or vector of bullets, and queue/stack of bullet indices (that index into that other array/vector), that's your inactive bullet queue. When you disable a bullet, you put it back in that queue. It's more memory to have that second container, but not all that much, and then you don't have to iterate over your bullet list ever.

Just use a vector of structs and work on the vector. You can do this through a function, or else encapsulate the vector in a class that manages it and does the work on it. When you iterate the vector to update the bullets and you find an object that's flagged as dead then swap and pop and continue processing from that point. If a new bullet is fired, just emplace it to the vector. Vector retains its memory and you'll avoid the costs that you'd otherwise get from constant allocations and releases. The only thing you need to make sure of is that you never increase the vector during iteration. In fact, if you like, you can set up your system to log the maximum size that the vector reaches during play and when you're satisfied that you've found a good upper-bound you can use vector.reserve() to allocate all the memory up-front (increase it by 50% just for kicks).

You're going to have a hard time finding a better pattern than this, and we're talking about something that almost certainly is not a performance issue in the first place.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

Thanks for all the responses everyone. It's nice to know that I was on the right track.

You can do this through a function, or else encapsulate the vector in a class that manages it and does the work on it.

I definitely second this. This will let you completely change the underlying implementation and the classes that use the pool don't have to know about it. (And they really shouldn't)

All the class really needs is two public functions like T* GetPooledObject() and ReturnPooledObject(T* pObject) (You don't have to make this a templated thing, but you might find you want to use several different pools, depending on your game)

Assuming the order the bullets are updated doesnt matter, you can use swap&pop to reduce the array/vector size to only contain active bullets. Swap the bullet you want to disable with the last bullet in the array, then reduce the array size. No need for an active flag or iterating over a vector with mostly disabled bullets

All the class really needs is two public functions like T* GetPooledObject() and ReturnPooledObject(T* pObject) (You don't have to make this a templated thing, but you might find you want to use several different pools, depending on your game)


Why should any individual bullet ever be singled out and removed from its context? Bullets don't really get up to much in terms of decision-making or individual behaviors. Anything that you're doing to one bullet probably needs to be done to all bullets, so the encapsulating class can manage it. There's no reason to "check out" and "check in" individual bullets. All that does is defeat contiguity and remove the ability of the class to maintain its invariants.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

I have used object pools combined with linked lists for this kind of thing in the past. Provided you don't need random access to a projectile it should work just fine.

Pool allocates and stores references to the objects, it can create a bunch upfront based on and educated guess and expanded if needed at runtime, uses a vector behind the scenes and a pointer to the last unused item.

Then you just run though your linked list in the update function advancing and collision testing. It is then extremely fast to add/remove projectiles from the linked list and return/retrieve them from the pool. Most of the time there will be 0 allocations and 0 vector resizing/copying. It is not as cache friendly as blasting though a flat vector but plenty fast enough and the ease of implementation is a real plus.

There are no doubt other ways, this is just something I have used successfully many times and is both easy on the GPU and minimises memory allocation/GC.

b

This topic is closed to new replies.

Advertisement