Wow. There are a lot of errors in this code (and possibly a code that you have not published). Here's what you need to do to increase productivity, in approximately descending order of importance / necessity:
Performance measurement. At the most basic level, a frame rate counter (or, even better, a frame counter). You want to check that you are doing better.
Do not allocate memory during the game cycle. The best way to check if you are using CLR Profiler . Although you cannot use new (to allocate class types, structs are fine), this will not surprise me if most of this LINQ allocates memory overs.
Note that ToString will allocate memory. There are non-highlighting methods (using StringBuilder ) to draw numbers if you need them.
This article provides more information.
Do not use LINQ. LINQ is a simple, convenient, and absolutely not the fastest or most memory-efficient way to manage collections.
Use a data-based approach. The main idea of ββa data-based approach is that you maintain cache consistency ( more ). That is: all of your Bullet data is stored linearly in memory. To do this, make sure Bullet is a struct and you save them in a List<Bullet> . This means that when one Bullet loaded into the CPU cache, it adds others along with it (the memory is loaded into the cache in large blocks), which reduces the time spent by the processor on waiting for the memory to load.
To quickly remove cartridges, overwrite the one you are deleting with the last bullet in the list, and then delete the last item. This allows you to delete items without copying most of the list.
Use SpriteBatch wisely for performance. Make a separate batch of sprites ( Begin()/End() block) for your bullets. Use SpriteSortMode.Deferred - this is by far the fastest mode. Sorting (as shown in your CurrentDrawDepth ) is slow! Make sure all your bullets use the same texture (use a texture atlas if necessary). Remember that batch processing is only a performance improvement if consecutive sprites use texture. ( More )
If you use SpriteBatch well, then it will probably be faster to draw all your sprites, and then let the GPU select them if they are off-screen.
(Optional) Maintain a different list for each behavior . This reduces the number of branches in your code and can potentially make the code itself (i.e. instructions, not data) more cache-coherent. Unlike the points above, this will only give a slight performance improvement, so use it if you need to.
(NOTE: Beyond this point, these changes are difficult to implement, make your code more difficult to read, and even slower. Use them only when absolutely necessary, and you measure performance.)
(Optional) Paste your code. As soon as you start getting thousands of rounds of ammo, you may need to embed your code (remove method calls) in order to squeeze even more performance. The C # compiler is not built in, and JIT only does it a bit, so you need to embed it manually. Calling methods includes things like the + and * operators that you can use on vectors - embedding these parameters will improve performance.
(Optional) Use a custom shader. If you want more performance than just using SpriteBatch , write your own shader that takes your Bullet data and calculates as much on the GPU as possible.
(Optional) Make your data even smaller and (if possible) immutable. Save the initial conditions (position, direction, time stamp) in the Bullet structure. Then use the basic equations of motion to calculate the current position / speed / etc., only if you need them. You can often get these calculations for βfreeβ - since you are probably using unused CPU time while it is waiting for memory.
If your data is immutable, you can not transfer it to the GPU in every frame! (If you add / remove markers, you will have to update it on the GPU on these frames, though).
If you completed all these elements, I think you could probably get up to 7 million bullets in a good car. Although this is likely to leave little CPU time for the rest of your game.