Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
12 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by TheMostCuriousThing · Feb 23, 2016 at 01:13 PM · unity 5renderingspritesgameobjects

How to improve performance with 10,000s of GameObjects?

I'm in the planning stages of a (2D) bullet hell engine and looking at both Game Maker and Unity. In GM, I can process ~50,000 bullets at 60 FPS—by process, I mean change coordinates via trig, change 1-2 other parameters, run collision detection with the player, and draw to the screen. I accomplish this by keeping all bullets within a single object (therefore running all draw calls within that one object).

In Unity, I can only manage ~25,000 bullets at 60 FPS, and they're just sitting there, not being processed. ~25,000 GameObjects with only a transform component and a sprite renderer component, no physics, doing nothing but being drawn every frame. (Profiler says 64.2% Render.TransparentGeometry, 23.0% Render.Prepare, and 8.9% Culling—that's almost all of it.)

It's kinda a surprise that Game Maker blows Unity out of the water, so I'm sure I'm missing something. I dislike using Game Maker and like C# so I wanted to learn Unity, but these GameObjects are killing me. What can I do to draw 50,000+ bullets at 60 FPS? I'll look at batch call drawing next, but why should I need to?

EDIT: I see now that Unity is grouping the 25,000 bullets into only 9 batches, but Statistics says "Saved by batching: 0"? What does that mean?

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

4 Replies

· Add your reply
  • Sort: 
avatar image
3

Answer by lodendsg · Mar 13, 2018 at 12:31 AM

I know this is an old question but its a problem that is ran into offten.

The specific case we have is several hundred warships in a space game each with multiple turrets, each turret with multiple barrels and a resonably fast fire rate and projectile lifetime.

We implamented a GPU instancing solution and found it to massivly reduce performance impact. The biggest part of the gain is due to there being only 1 gameObject in the scene representing all our projectiles. The following C# code is from our test rig, it has been used in 2017.3.1 succesfuly but isn't optimized so could be further improved Im sure ... never the less its better than pooling assuming your projectiles are simple meshes with an instance enabled material.

PS: ConquestBattleConfiguration is just a ScriptableObject we use to store some common settings in this case it stores the layers we test for collision on.

 public class ProjectileRenderer : MonoBehaviour
     {
         [Header("Data")]
         public ConquestBattleConfiguration Config;
         public Mesh mesh;
         public Material material;
         public float life;
         public float speed;
         public float damage;
 
         [Header("Instances")]
         public List<ProjectileData> projectiles = new List<ProjectileData>();
         public List<GameObject> splashPool = new List<GameObject>();
 
         //Working values
         private RaycastHit[] rayHitBuffer = new RaycastHit[1];
         private Vector3 worldPoint;
         private Vector3 transPoint;
         private List<Matrix4x4[]> bufferedData = new List<Matrix4x4[]>();
 
         public void SpawnProjectile(Vector3 position, Quaternion rotation, int team, float damageScale)
         {
             ProjectileData n = new ProjectileData();
             n.pos = position;
             n.rot = rotation;
             n.scale = Vector3.one;
             n.experation = life;
             n.team = team;
             n.damage = damage;
             n.damageScale = damageScale;
 
             projectiles.Add(n);
         }
 
         private void Update()
         {
             UpdateProjectiles(Time.deltaTime);
             BatchAndRender();
         }
 
         private void BatchAndRender()
         {
             //If we dont have projectiles to render then just get out
             if (projectiles.Count <= 0)
                 return;
 
             //Clear the batch buffer
             bufferedData.Clear();
 
             //If we can fit all in 1 batch then do so
             if (projectiles.Count < 1023)
                 bufferedData.Add(projectiles.Select(p => p.renderData).ToArray());
             else
             {
                 //We need multiple batches
                 int count = projectiles.Count;
                 for (int i = 0; i < count; i += 1023)
                 {
                     if (i + 1023 < count)
                     {
                         Matrix4x4[] tBuffer = new Matrix4x4[1023];
                         for(int ii = 0; ii < 1023; ii++)
                         {
                             tBuffer[ii] = projectiles[i + ii].renderData;
                         }
                         bufferedData.Add(tBuffer);
                     }
                     else
                     {
                         //last batch
                         Matrix4x4[] tBuffer = new Matrix4x4[count - i];
                         for (int ii = 0; ii < count - i; ii++)
                         {
                             tBuffer[ii] = projectiles[i + ii].renderData;
                         }
                         bufferedData.Add(tBuffer);
                     }
                 }
             }
 
             //Draw each batch
             foreach (var batch in bufferedData)
                 Graphics.DrawMeshInstanced(mesh, 0, material, batch, batch.Length);
         }
 
         private void UpdateProjectiles(float tick)
         {
             foreach(var projectile in projectiles)
             {
                 projectile.experation -= tick;
 
                 if (projectile.experation > 0)
                 {
                     //Sort out the projectiles 'forward' direction
                     transPoint = projectile.rot * Vector3.forward;
                     //See if its going to hit something and if so handle that
                     if (Physics.RaycastNonAlloc(projectile.pos, transPoint, rayHitBuffer, speed * tick, Config.ColliderLayers) > 0)
                     {
                         projectile.experation = -1;
                         worldPoint = rayHitBuffer[0].point;
                         SpawnSplash(worldPoint);
                         ConquestShipCombatController target = rayHitBuffer[0].rigidbody.GetComponent<ConquestShipCombatController>();
                         if (target.teamId != projectile.team)
                         {
                             target.ApplyDamage(projectile.damage * projectile.damageScale, worldPoint);
                         }
                     }
                     else
                     {
                         //This project wont be hitting anything this tick so just move it forward
                         projectile.pos += transPoint * (speed * tick);
                     }
                 }
             }

             //Remove all the projectiles that have hit there experation, can happen due to time or impact
             projectiles.RemoveAll(p => p.experation <= 0);
         }
 
         private void SpawnSplash(Vector3 worlPoint)
         {
             //TODO: implament spawning of your splash effect e.g. the visual effect of a projectile hitting something
         }
     }
 
     public class ProjectileData
     {
         public Vector3 pos;
         public Quaternion rot;
         public Vector3 scale;
         public float experation;
         public int team;
         public float damage;
         public float damageScale;
         
         public Matrix4x4 renderData
         {
             get
             {
                 return Matrix4x4.TRS(pos, rot, scale);
             }
         }
     }


Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Adam_Benko · Feb 23, 2019 at 04:01 PM 0
Share

Hi, I would like to test your script. However, I have some errors when trying to run it. For example, ConquestShipCombatController and ConquestBattleConfiguration could not be found in the script. And for " bufferedData.Add(projectiles.Select " i get this error. List does not contain a definition for select

Could you please help me to run it ? Thank you very much :)

avatar image
2

Answer by phil_me_up · Feb 23, 2016 at 01:32 PM

When you say that your 25,000 bullets are sitting there, are they actually just sitting there or is there any script attached to them? Is there anything that is iterating through the list of objects (even if they are not doing anything)? These things will have an effect so your problem isn't necessarily a GPU issue (I would actually expect it to be CPU).

When dealing with such a large number of objects, small optimisations in key areas can have a dramatic effect. For example, how are you accessing your object transforms. You might find getting and caching references to those transforms is far more efficient than accessing through .transform.

Are you using GetComponent() during updates? If so then don't, it's slow and there are better ways. Same goes for anything like FindObjectInScene

Of course, the first question you should ask is if you ever actually need 25,000 bullets on screen at any one time? This seems like a huge number. If you can only ever see yourself needing say 1000 (which is still quite a few even for a bullet hell) then just have your pool at 1000 objects and properly re-use. No matter what your number though, make sure you are pooling properly, that you are only activating / deactiving as needed, that you are only performing operations on active bullets etc.

Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Dave-Carlile · Feb 23, 2016 at 02:09 PM 0
Share

25,000 bullets is only Bullet Heck. OP is going for Bullet Hell. :p

Seriously though, all valid points. In addition, often many of those bullets are probably going in the same direction. You can combine those into single objects so you're not dealing with as many individual game objects.

avatar image TheMostCuriousThing · Feb 23, 2016 at 04:34 PM 0
Share

Thanks for the responses!

When you say that your 25,000 bullets are sitting there, are they actually just sitting there or is there any script attached to them?

Literally sitting there being drawn each frame. $$anonymous$$y profiler results:

  • 64.2% Render.TransparentGeometry

  • 23.0% Render.Prepare

  • 8.9% Culling

  • 2.4% other drawing operations

  • 1.5% other operations

    Is there anything that is iterating through the list of objects (even if they are not doing anything)?

Nope, the only other object is a master that runs "if (Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.Space))" each update to instantiate 1,000 bullets. If space isn't pressed nothing happens.

Of course, the first question you should ask is if you ever actually need 25,000 bullets on screen at any one time? This seems like a huge number.

That's an excellent question. I'm not too sure myself, haha. $$anonymous$$y goal is to be able to process 100k simple bullets at 60 FPS, to allow enough overhead for a) very complex bullet processing, b) bad hardware, c) higher frame rates (for 120/144 Hz monitors and/or no VSync for less input lag), and d) just having more overhead (like most shmups, I'm using a fixed timestep and therefore can't afford to drop below 60 FPS).

I can't think of any bullet hell/danmaku that uses pool larger than 10k bullets at any one time (and even that's rare), but basically I want as much overhead as possible so I never need to limit my design.

25,000 bullets is only Bullet Heck. OP is going for Bullet Hell. :p

Yep, haha.

You can combine those into single objects so you're not dealing with as many individual game objects.

How do I render that though? I have a nested hierarchy structure that can process bullets within a single object, but how do I actually render multiple sprites within that one object?—I thought Unity was strict about one sprite renderer per GameObject?

In Game $$anonymous$$aker I put the sprite draw calls in the same nested loop structure that processes the bullets. A simplified example In pseudo-code:

 foreach (wave in pattern)
 {
     change_color();
     foreach (arc in wave)
     {
         change_angle();
         foreach (bullet in arc)
         {
             change_position();
             draw_sprite_ext(); // a Game $$anonymous$$aker function that draws to an "application surface" which then renders to the buffer once all drawing is done
         }
     }
 }

Game $$anonymous$$aker can process and draw ~50k bullets at 60 FPS using draw_sprite_ext within a single master object—so how do I accomplish something similar in Unity to unburden the CPU?

avatar image
1

Answer by Riderfan · Feb 23, 2016 at 05:01 PM

Sometime in March Unity is scheduled to release version 5.4 and this is supposed to have GPU Mesh Instancing included as a feature. While I don't have any of the specifics of how Unity will implement it, it's going to be critical for my probject (I need to have about 15,000 3D spectators in my stadiums). GPU Mesh Intancing is how pretty much any modern game now handles very large numbers of objects that are all (more or less) the same.

I'm not sure if GPU Mesh instancing works with moving objects (like bullets) but maybe there's a solution on the way.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
0

Answer by HonoraryBob · Jan 22, 2017 at 10:15 PM

@TheMostCuriousThing - I've repeatedly run into big problems if there are even a moderate number of GameObjects at the same time, which I've confirmed slows it down more than having a larger number of polygons in only a few GameObjects. Batching helps, but often cannot be done enough to get decent frame rates. I'm not sure why this is such a huge issue. Let me know if you've found any solution.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

10 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

4.6 -> 5.0 Dramatic Performance Loss - No batching? 1 Answer

Is it possible to render in Unity without the editor/scene/player being visible? 0 Answers

Unity extract data before rendering 0 Answers

Objects are not visible until the camera gets closer. 1 Answer

Unity2D strange green line under sprites 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges