I have noticed that the game is actually very laggy, especially in the higher particle counts (my highest XP was ~73000. the lag actually helped me by giving me slow mo hacks lul). However, some comments regarding performance:
- Circle x circle collision detection is very similar performance to square x square collision. The circle x circle collision code would be like ( if (circle1.pos - circle2.pos).sqrMagnitude < (circle1.radius + circle2.radius) ^2 ). (Notice how we are using sqrMagnitude of the vector and the radius to avoid using the square root function). A square x square collision would be like ( if !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top) (I just stole that one from stackoverflow as I couldn't think to write it myself at the moment). It is only a little bit faster.
- I am not sure about how you have implemented the game, but if you implemented each object as an individual script, this will be very slow and you are better off making all the entities into structs and then putting into an array, so that they are all allocated next to each other in memory and it is faster to iterate them. I.E use a "BulletManager" script that has a Bullet[] array and manually iterates and updates them from it's one Update rather than having each bullet as it's own script with it's own Update. More info about this kind of optimization here:
(I have linked to 5 minutes into the video, but I remember the whole thing being a pretty good watch)
- also do object pooling. Where instead of Destroying and Instantiating each object manually, you have a list of the objects, and you just hide them when they are 'dead', and then unhide them when they are no longer dead: https://learn.unity.com/tutorial/introduction-to-object-pooling#
If you don't use unity engine then probably a lot of that specific terminology like 'instantiate' didn't make sense, but the concepts apply to game dev in general
good luck with your game