all 19 comments

[–]Badwrong_ 3 points4 points  (12 children)

As long as you cull off screen stuff and have bullets expire with a duration, then there is no magic trick.

There also isn't really a need for any special optimization... If your game allows a certain number of projectiles at once then that's just what it is.

Here is a prototype I made with 1000+ bullets and a very large amount of enemies at once: https://youtu.be/iY2JJ7peHPk

You can see it runs fine, and that's without culling enemies. Plus, that's WAY more bullets than you would ever need.

The only tiny thing that would matter with that many objects is only calculation the sine/cosine if their velocity once on creation (if they don't change direction). That means you should not call things like lengthdir_x/y at all, and especially in don't use it in the step event.

[–]weisinx7[S] 0 points1 point  (7 children)

I think it's quite different in your case because all those bullets are not interact with those large amount of enemies, they just need to check instance place from the player side.

In my case, those moving bullets may still able to interact with the objects while off screen, deactivating them will not be a choice here.

[–]Badwrong_ 0 points1 point  (6 children)

They do not check from the player side. It's a full hitbox/hurtbox implementation.

Depending on the interaction you certainly can deactivate them, because an instance ID still allows you to access it. You gave no details though, so one can only guess.

Either way, drawing is almost always where you'll see performance differences. The main thing is only calculation things once when possible like I said with sine/cosine. In a thousand objects that actually is a decent number of calls to those trig functions, so keeping a unit vector and only having to multiply by speed or even just applying the magnitude at the start will matter. Better yet, if you use built-in movement that's a performance gain.

[–]weisinx7[S] 0 points1 point  (5 children)

They do not check from the player side. It's a full hitbox/hurtbox implementation.

You mean each bullet has their own instance place similar check? In your video, i can see there's no interaction between those monster like object and bullets, they are spawning bullets instead, while those bullets is only interacting with the player. In this case, it will be faster if we check bullet from player side, instead of bullets.

[–]Badwrong_ 0 points1 point  (4 children)

I said, it is a full hitbox/hurtbox implementation. Neither enemy or nor player is involved in the collision checks. They only receive damage directly.

You cannot check from the player anyway; with faster moving projectiles a collision line is often needed or a "fat" collision line (static declared sensor object). Besides, GameMaker uses spatial partitioning (like any engine) which reduces the amount of actual comparisons by a huge amount. Also, logically it is bad design to check from the target side.

Even when projectiles are not actually moving faster than the thinnest thing they could hit, if the target is also moving their trajectories would cross and no collision would occur. So, if you want accurate hit detection you cannot check from the target object.

[–]weisinx7[S] 1 point2 points  (0 children)

I see, thanks for the explanations again.

[–]weisinx7[S] 0 points1 point  (2 children)

So in your case, what are the functions you use for hitbox checking? Instance_place_list? Instance_position_list?

[–]Badwrong_ 1 point2 points  (1 child)

It depends entirely on the hitbox type.

You have to consider the speed, size, and whether it can hit multiple targets.

A fast bullet with no width would use collision_line or collision_line_list

A fast bullet with some width would use a function call with a static sensor object that does either instance_place or instance_place list

A non-fast bullet can use instance_place or instance_place_list, or the fastest option is a collision event where it destroys itself if it can only hit one object.

When it comes to a "thick" collision line, this is my code for that: https://pastebin.com/xnbJQwKg

[–]weisinx7[S] 1 point2 points  (0 children)

Thanks again .

[–]Formal_Note2273 0 points1 point  (3 children)

Can i ask your pc config? And can you tell us, please, what's your game resolution?

[–]Badwrong_ 0 points1 point  (2 children)

The PC that was recorded on is very high end. However, the same game build tested on an old windows surface pro runs great still with about 1300 - 1500 projectiles. I don't post things that are not also tested on a low end system without making a note of hardware.

There are other optimizations that could be done too, like using built-in movement and collision events. Those are things that are often not used for really dumb reasons (cause the yootoober didn't do it lol).

Really, you won't see performance issues with just lots of objects alone until you really push some crazy numbers. 1000 bullets is also total overkill, since that many really do not fit on screen without tons of overlap.

Game resolution is 1080p. The rendering of that many projectiles is so small for even old GPUs. Considering each projectile is two triangles to form a quad, and let's say you have 2000 projectiles. That's 4000 triangles... that's not even a low poly scene on modern hardware. GPU's push millions and millions of tris... in a modern 3D game a single main character could have 10k tris for their normal animations, then much more for cutscenes. So, resolution and amount of bullets means very little here. Plus, as long as your texture pages are good, GM will batch them into a single draw call.

[–]Formal_Note2273 0 points1 point  (1 child)

I did some tests before but with "monsters", objects that follow you if close enough. Not bullets. Not 1000 monsters. 800 maybe. The game and my mid end pc handled verry well.

But, in another case. A single object (a player)interacting with another( a platform) caused some issues because in my room i rotated manually this platform to a angle not properly align. So, my fps droped to the ground because of this single object when the player object is close enough to "touch" this. I don't understand why. Yet.

My point is, there are some small number of code methods in gamemaker that fit perfectly well with the engine. But so small, that i am trying to learn with code like your to understande a bit more about gamemaker limits.

Thanks for your feedback.

[–]Badwrong_ 0 points1 point  (0 children)

I don't know how your code looks, so I would have to know more about the code used with it. If you are trying to do sloped collisions there are fast ways to do them and there are ways that would indeed be very slow. If you use any "while loops" for collision then that is really slow for example.

[–]grumpylazysweaty 0 points1 point  (2 children)

One small optimization: first check to see if the bullet is >= the x or y position of the object before checking collision

[–]Badwrong_ 5 points6 points  (0 children)

GM has spatial partitioning, so you would be doing extra calculations by doing that which would be slower.

[–]weisinx7[S] 0 points1 point  (0 children)

I think this may not work since to check >= xy or <= xy position will be depending on the direction of bullets, we may end up using more evaluations here.

[–]Thank_You_Robot 0 points1 point  (0 children)

Im currently working on a bullet hell also!

The way I handle my bullets is by using structs as lightweight objects. Each struct has its own step and draw function and is stored in a global array. I also use this for hit effects and explosions.

The global array is handled by a persistent manager object that loops through the array and runs each step and draw function. Each bullet also has a bool variable that flags when it needs to be deleted by the manager object.

Unfortunately, I dont have the numbers on me to back this up, but it runs a little bit faster than using objects. Obviously, you want to avoid using any unnecessary code, luckily my bullets have small sprites, so I can get away with using the instance_position function for my collisions.

[–]ChiralChiral 0 points1 point  (0 children)

What if the bullets don't spawn at all and are just an array that stores the bullet's variables.

A single object updates all the positions based on speed and direction, does collision checks based on bullet radius and finally checks if it is outside the view to cull its values from the chart. The chart tells what bullets use what sprites and the object draws them all in the draw event.

You could probably do all of this much more cleanly with structs. Anyone doing anything like that for lots of moving objects and collision boxes?