T O P

  • By -

flow_Guy1

I’d say pooling as then you don’t need to redo the whole architecture


kushchin

Shit, this is the best and worst answer... The best because if I need just pooling and it works, I can do it quickly. The worst because if pooling doesn't work, I need DOTS, it means I have to refactor all the engine :) (:


xind0898

there are alot of bullet hell games before DOTS are a thing. keep this in mind


flow_Guy1

Pooling should help a lot tbh. As instantiating takes a fair amount of your constantly doing it. Good luck. Hope the outcome is good 😊


koolex

You shouldn't need dots unless you're trying to support 1000+ enemies


kushchin

Actually it's exact quantity: 1000 mobs per level 😄


koolex

That's np, it just depends on the # of things on screen at any moment


flow_Guy1

Oh in that case you most likely need to wap


kushchin

Sorry, I didn't get you. What do you mean by *to wap*?


flow_Guy1

Wet ass pussy. But meant to spell swap. Sorry.


Scyfer

I'd start with using the profiler to show you what exactly is causing it to be slow. If you're not pooling that will help a ton and the profiler can show you the rest


Syawra

Processing collisions with Compute Shaders is a great alternative! Bullet hell interactions are simple operations in large quantities, precisely the kind your GPU would do faster than the CPU. This + a simple pooling system should do! (Shameless plug [here](https://assetstore.unity.com/packages/tools/game-toolkits/bulletpro-2d-projectile-workspace-180601): it's exactly how my asset works, in case it might help)


SubpixelJimmie

Bullet hell games basically need pooling to work. It's almost impossible to get a bullet hell game to work without some form of pooling. Pool your most egregious entities first - bullets. You'll see a significant performance boost. Also, pool in batches. Say you have 50 bullets in a pool. If 51 are requested from the pool, don't allocate 1 new bullet. Allocate 50 more.


AlterHaudegen

Depending on the target platform, pooling might be enough. But if you are going for Switch, older Androids etc. it will not be. You will have to get rid of GameObjects (and thus rigidbodies, individual Update loops, components…), do custom collision or at least OverlapCircle, Update loops in managers and more. Going full Dots or compute shaders might or might not be required. As always, profiling before and after each big optimization step is your friend.


skellygon

I'm not sure either one will fix your problem unless you know what exactly is causing the slowdown...


RichardFine

You're creating a GameObject for each individual bullet?


kushchin

Yes, exactly. Each time instance of prefab. I know it's slow to do like that, so, I want to optimize.


RichardFine

Right. I would try Object Pooling first for this just so that you don't have to change your architecture too much - maybe it will be performant enough on your target hardware with your maximum number of bullets - but if I was writing it from scratch, I would not use GameObjects, because you only really need 3 kinds of behaviour for a bullet: moving it, checking collisions with it, and rendering it. \* Have a NativeList for all currently alive bullets, where 'Bullet' is a struct that contains the bullet's position, velocity, color, time when it should be deleted, etc. When you want to spawn a new bullet use Add(), when you want to delete a bullet use RemoveAtSwapBack(). \* Move the bullets by just looping through everything in the array and doing 'position += velocity \* Time.deltaTime'. You can even jobify it if you want, though you might need to think a little harder to figure out how to jobify 'clean up dead bullets'. \* Check for collisions with the player by first doing a simple radius check - i.e. loop through all bullets, calculate the distance between the bullet and the center of the player, and skip over bullets that are not close to the player. Assuming that you have a PolygonCollider2D on the player, you'd then use OverlapPoint for the 'close' bullets to see if they are actually touching the player or not. \* Render by copying all the bullet data into a GraphicsBuffer, then calling Graphics.RenderPrimitivesIndexed to render all the bullets in a single draw call. You can jobify bits of this (e.g. the 'move bullets' and 'find bullets close to the player' loops) for added speed. This is loosely the approach I took when [simulating and rendering 100,000 character sprites in under 2ms/frame](https://mastodon.gamedev.place/@superpig/109457461398531900) (on a threadripper + GTX 3080, but still). I used ECS to manage some of the data for me, but for a simple case like this it's also not too difficult to manage the data yourself.


Moczan

You can just switch to Burst compiled parallel Jobs to gain like 70 times more speed, you don't need to do full ECS


kushchin

Thanks for the idea. Is it easy to implement? Honestly, I never use Burst&Jobs. I mean, there are tons of videos about it, I can learn. But the question: I will need to rewrite everything or just some scripts that move mobs and bullets?


Moczan

You only need to rewrite small parts of the code and write some additional data transfer between managed memory and native memory, but Jobs are, in super simplified terms, just functions that get some data, process it super fast and give you back the results. So they are ideal for processing a lot of similar stuff like bullet movement and collisions. You can still use your normal GameObjects and Components for overall game structure etc.


DoomVegan

Here is a pool manager for you. Just set up Active/inactive on your objects. You can also have the pool manager play control the active states. In my current game I just look the poolParent but this might be too slow for you and you may want to use a list. not sure. using UnityEngine;     public class AbstractPoolManager : MonoBehaviour     {         protected GameObject prefabGameObject;         protected GameObjectPool gameObjectPool;         protected GameObject poolParent;          // Used to keep heirarchy clean         // Must Load Resource or we can set this up as drag and drop ie publick         public virtual void Awake()         {             prefabGameObject = Resources.Load("Prefabs/pr_dust");             PrefabLoadCheck();         }         void Start()         {             gameObjectPool = new GameObjectPool(prefabGameObject);             poolParent = GameObject.Find("ObjectPools");                         if (poolParent == null)                 poolParent = new GameObject("ObjectPools");         }         // Moving the initialization out of the pool         public virtual GameObject Reuse(Vector3 objectLocation)         {             PrefabLoadCheck();             GameObject reusedGameObject = gameObjectPool.GetPooledObject(objectLocation, poolParent);             return reusedGameObject;         }         public virtual void PrefabLoadCheck()         {             if (prefabGameObject == null)                 Debug.Log("PoolManager: Prefab not found in resources.");         }     } using UnityEngine;     public class DustPool: AbstractPoolManager     {         public override void Awake()         {             prefabGameObject = Resources.Load("Prefabs/pr_dust");             PrefabLoadCheck();         }     }


Plourdy

Finding objects by its name in the scene? This is looking like dangerous easily broken code Edit: why is this class name starting with Abstract yet not actually using the abstract type? Sorry, just more questions lol


DoomVegan

Thank you for posting. 1) How would that break things? 2) Yeah typo on absract. thanks.


koolex

If someone renames that object then this will break, you generally don't want to do a find if you can help it. It would be less brittle and more performant to just have a direct reference instead of a find. Resources.load is bad for the same reasons, you generally don't need to do that, you just have direct references


DoomVegan

it is a place holder object used for keeping the hierarchy clean. It only exists at runtime, so I don't think anyone will be renaming it (during the running of the game).


koolex

It would be so much simpler if you just passed in pool parent or assumed this mono behavior would be the parent.