Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Tips for mobile games that make heavy use out of triggers/colliders

Discussion in 'Scripting' started by DasSchwarz, Jan 29, 2012.

  1. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    I am developing a strategy game that has many units that must be able to attack each other. Each unit has a rigidbody (kinematic) and sphere collider (trigger) attached. This sphere collider allows them to "see" enemies, which will make them chase said enemy. The units work in such a fashion due to the mechanics of the game.

    At any one time the amount of units will grow to a few hundred (let's say 400-500 max). This slows down mobile devices a lot, so I decided to use layers so that units don't "see" (or do collisions) against their friends. Also, I disable the trigger colliders when units are occupied with a certain task. I realize that several hundred units may seem strange to some. Keep in mind that each unit is essentially expendable and quite simple. Still, I have to give of them some sort of behavior.

    Anyway, it is still difficult to optimize my game for iOS despite my efforts. The game becomes even slower when units actually move with the triggers enabled. I was wondering if anyone had ideas to share in regards to optimizing physics to physics-heavy games, which would go beyond ignoring collisions using layers? i.e. are the sizes of the game objects important as well? This would relate to whatever acceleration structure that Unity uses for physics. Thanks...
     
  2. abbc

    abbc

    Joined:
    Mar 12, 2011
    Posts:
    52
    Hmmmm, mobile game...a few hundred units...i wonder why your device slows down.
     
  3. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    Don't post anything if you're going to be sarcastic. I'm looking for ideas here.

    As I said in my post, I know that this type of idea would be strange. But that's type of game we are going for; there are a swarm of many units that you could control as an army. Each unit is quite simple, and essentially expendable. Like an army of ants. While mobile devices are becoming more powerful, putting a sphere collider around each unit so that it could "see" enemies is no the best idea. Slowdowns typically occur for non-static objects, probably because some acceleration structure is involved.
     
    YagoRG likes this.
  4. Chris-Clark

    Chris-Clark

    Joined:
    Jan 16, 2012
    Posts:
    130
    Not really sure exactly how your game works, but it sounds like once a bunch of units get near each other, every unit's sphere would be colliding with every other one all the time. Crazy strain on the system.

    Is your game 2D or 3D?

    Either way, I'd say to get rid of the sphere colliders completely. In your unit update, just check by distance to other objects, and only check periodically, like once per second. Then change their state to attacking or lost target, etc.

    Basically, do it all in scripting, forget the collision system and triggers. Manage your own object states, and then optimise it. For instance, if ont unit sees another and attacks, the other unit must also be able to see the first and want to attack.

    So loop through one unit finding another unit close enough, then change both states of both units to attacking each other.

    Since you are using triggers and the collision spheres I bet you don't keep track of units in an array. Don't use GameObject.Find to do this, actually don't use it ever.

    All setup and management of units and unit states should be pure scripting, you might want one master object with a script that can own and iterate through all the units on the screen.

    Basically, to recap. Forget about sphere colliders, do it yourself, keep references to every object, do your own distance checks and communicate directly between units. In the end, write it like games have always been written before the 'simplicity' of the Unity 3D editor came around, your game is more of that style.

    P.S. I'm sure you know it, but also never use transform, gameobject, rigidbody, variables directly. Always cache them in the object if you are going to need to reference it more than once, those are actually not variables, but slow functions. At Start() do something like _transform = transform and then use _transform from then on. Basically there are these helpers in Unity, but they are always slower than doing it the correct way yourself.
     
    Last edited: Jan 29, 2012
    DarthDisembowel likes this.
  5. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    The game has 3D objects but it's effectively 2D (objects move on a plane only).

    Thank you for the suggestions. I thought about the square magnitude test before, however I went with the physics solution because I thought that it would be optimized more (and I thought physx would scale better). Right now the units do the change state thing as you suggested, but it uses physics.

    But I think back now, and it appears that checking distsqr between each object would be best. If I had all the units in one huge array, and did a check distance square thing once a frame (or periodically as you indicated), then that would be fastest I think. It's kind of like the Unity boids project they made for the performance optimization presentation a few years back.

    In regards to Find functions, I fortunately only use those in Awake/Start as they are quite expensive. I did know about the transform, gameobject, rigidbody, etc thing but it helps to check that I didn't do anything stupid.
     
  6. Chris-Clark

    Chris-Clark

    Joined:
    Jan 16, 2012
    Posts:
    130
    Glad to help. I hope that these improvements help. It sucks to get so far into a design and then hit a dead end.

    Yeah, even though I constantly try to cache the component variables too, things always slip through. Once in a while I will just step through all my code looking for stuff like that. It seems I can be lazy when trying something new, and then I don't remember to fix it. :)

    Before completely converting everything over the just a plain distance check, you might want to write some form of test to make sure it will really be faster. Like you said, you thought the physics engine would be optimized better, and it is probably very optimized. It would suck to find out your new solution ends up slower.

    But I think the best improvement with your own check would be the fact you don't have to check each frame. If you find every frame is too slow, every 3rd frame would triple the speed. You might want to stagger the objects though, a third on one frame, a third on the next, etc. Or however you split it up, in thirds or tenths, etc.
     
  7. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    but physics probably calculates a bunch of other stuff you dont need.

    if your units act together and are organized in groups of fleets you can also use a distance test for all of them in one group so some kind of tree structure where you first check group against group and only when they are close together you check the single units. and you can have several test distances like see (move towards), in shooting range (shoot) in melee range (fight) etc. so several conditions can all be checked in one loop iteration. thats quite faster than having 3 colliders per object in physics system.

    you should also consider checking if a unit is visible. make a sphere in the center of the fov and check this against each unit. one that is inside the sphere is updated more frequently than one outside.

    a problem which might occur is the visibility test as you cant use raycasts without collider/rigidbody. so with distance test your units will see through obstacles but this can explained with recon/satellites etc in game design. you cant have everything ;).
     
    Last edited: Jan 30, 2012
  8. SteveJ

    SteveJ

    Joined:
    Mar 26, 2010
    Posts:
    3,066
    So what is each unit represented by? Is it a 3D object? If so, how detailed is the model - in terms of poly count? If you have 400-500 units on screen, each represented by even a 500 poly object, then that might be your problem in itself. Nothing to do with the physics side of things.

    ?
     
  9. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    I thought that physics would be fast enough, but it isn't unfortunately. So I'm going to try this distance test as an alternative. I know it seems strange, but the physics calculations seem to slow things down for me. Especially when objects move.

    I guess one could make an acceleration structure to support the distance tests as you indicated. Keep in mind though, all the units need to do is "see" enemies and chase them. There is no shooting. On another note, my comment about the comparison with the Unity boids demo was inaccurate.

    Each unit right now is represented by a box, which is eight vertices but since Unity splits things up into triangles, each boxi s a 24 verts. I'm only using a box as a placeholder; they will likely be triangular in the future as each unit is really simple. So I don't think it has anything to do with rendering as I encourage unity to batch stuff and I keep vertex counts low in general (I know this based on the profiler in Unity and in xcode).

    For one scene, the number of vertices goes up to 10k near the end of the game, which is the end of world for my 2nd gen ipod touch but is no problem for 3gs/4g/4gs. Once I have a simpler model for each unit the vertex count will be even lower.

    I did notice an improvement in performance if all triggers are turned off.
     
  10. Lokken

    Lokken

    Joined:
    Apr 23, 2009
    Posts:
    436
    I unfortunately don't have any additional suggestions but I did want to say thanks to TheClarkster for this

    I did not know this!
     
    DarthDisembowel likes this.
  11. abbc

    abbc

    Joined:
    Mar 12, 2011
    Posts:
    52
    Do you use OnTriggerStay ? If so, I would advice to use OnTriggerEnter and OnTriggerExit instead. First make an empty list of the type "GameObject". If OnTriggerEnter detects something, add that object to the list. If OnTriggerExit detects something, remove that object from the list. Loop through the list every frame, if an object's HP drops below zero, remove that object from the list. Also you should start with fewer units, then gradually increases the number until device slows down noticeably.
     
  12. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21

    The game does start with few units and the number grows gradually.

    I do use OnTriggerEnter/Exit. In my case I use a dictionary to keep track of objects entering/exiting the trigger (which means I add/remove objects from said dictionary, respectively). The first object that had entered the trigger (or added to the dictionary) will be the candidate for the chase. If that object exits the trigger, the unit will then see if there are any other candidates in the dictionary.

    I guess I could use a List<>, but I think it's quite fast to remove objects from a dictionary as I use the object's name for the key. I appreciate the comment though. When I have time I will let you guys know what happens when I implement the distance thing...just almost finished with this one bug.
     
  13. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Have you done any profiling to see where the performance is taking a hit?

    That would be my first suggestion... otherwise without seeing any code its hard to say exactly whats causing it.
     
  14. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    The profiling I've done is the "printing to the console" option in xcode where it prints vert count, frame time, et cetera. I don't have access to the professional version of unity. Which profiler are you referring to?

    By the way, the slowdowns typically occur BEFORE I hit somewhere around 400 units (I'll have check when...). The only objects I have right now are a handful of bases (which are represented by unity's cubes as placeholders) and units which are each represented as a cube. Since unity renders each cube using 24 verts, then 400 units be around 9600 vertices/second. That's within the graphical capabilities of most iOS devices. I'm not using fancy lighting or anything as the one light in my scene is not important. I should probably check my lightmapping settings too...
     
    Last edited: Jan 30, 2012
  15. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    yeah im referring to unity profiling.

    Have you used your pro trial?

    I would consider doing this just to be sure the slow down is occurring where expected, otherwise you may have to do some clever coding to minimise the problem. I cant suggest anything, because without seeing your code its hard to know.
     
  16. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    I didn't even know that there was a Pro Trial available. We just paid for the iOS license. I'll look into it.

    We're trying to keep this an internal project for now. If I were to share code, it would be bits and pieces...I'll look into doing that.
     
  17. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,039
    First use distance not physics for sight as suggested. There are a lot of heuristics you can use to save computational time. For example if one unit sees something it can tell other units that they see it too. You don't have to run the "sight" algortihm on every unit.

    i.e. (PSUEDO CODE)

    Code (csharp):
    1.  
    2. class Unit() {
    3.  
    4. list friendlyUnits;
    5.  
    6. enum State {SEARCHING, TARGET_FOUND, FIGHTING};
    7.  
    8. Search() {
    9.    if (state == State.SEARCHING ) {
    10.       LookForTarget();
    11.       yield return new WaitForSeconds(TIME_BETWEEN_SCANS);
    12.   }
    13. }
    14.  
    15. LookForTarget() {
    16.   target = FindTarget()
    17.   if (target != null) {
    18.     foreach (unit in friendlyList) {
    19.      friendly.ReportTarget()
    20.    }
    21.   }
    22. }
    23.  
    24. ReportTarget(target) {
    25.   if (state == State.SEARCHING  targetWithinSightRange(target)) {
    26.     state = State.TARGET_FOUND;
    27.     myTarget = target;
    28.   }
    29. }
    30.  
    31. }
    32.  
    You can extend:
    Periodically update the friendlyList (no need to report to people far away).
    Ensure not too many in friendlyList (you don't want all units to target same enemy).
    Use the friednly list of enemies to quickly find groups of targets, etc.

    I guess the key is create the behaviour you want, don't try to simulate the "reality" of the situation.
     
    Last edited: Jan 31, 2012
  18. abbc

    abbc

    Joined:
    Mar 12, 2011
    Posts:
    52
    How about the idea of unit group. One group contains many units, which share a single sensor. Just treat the whole group as a single unit.
     
  19. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
    I didn't read the whole thread, but this is what I was going to suggest. Instead of having hundreds of units making independent decisions, choose a few "generals" and have the others do whatever the generals are doing. Sort of like flocking behavior.

    But as others mentioned, before you start trying to optimize, you need to make sure you know exactly what is slowing you down. If you start messing with the behavior and your bottleneck is poly count or draw calls, then you are wasting your time.
     
  20. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
    Last edited: Feb 1, 2012
  21. vdek

    vdek

    Joined:
    Sep 2, 2011
    Posts:
    368
  22. bajeo

    bajeo

    Joined:
    Dec 5, 2011
    Posts:
    75
    A simple improvement could be reducing the number of times the physics code is run per second... by increasing the physics time step in the Time Manager from 0.02 -> 0.05 you will reduce the number of times the physics code is calculated which over that many units should result in a noticeable difference.
    Ref: http://unity3d.com/support/documentation/Components/class-TimeManager.html
     
  23. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    Thank you for all of the suggestions thus far. I've been busy with other stuff as this project is not my day job, but I have to address a few things...

    --Doing a simple distance test doesn't seem to help. When I have a 80 units on the screen, the frame time goes to 100 ms and beyond. It starts out at around 30. I'm using a second generation iPod Touch. Having said that, if there is anything that I should optimize, it should be my use of triggers. When I use those, the frame time climbs to the 100s when I approach close to 100 units on the screen.

    Don't get me wrong; I could optimize either. But I get the idea that I should use whatever black boxes that are available to me. Especially one that works with the GPU.

    I have to say though, I thought that even a few hundred sphere colliders would be smooth. You could do distance square calculation to see if they are close enough, which doesn't require a square root...

    --I guess a hierarchical approach to detect enemies is one option. The thing is, you can never predict how the user will group their ant-like units together. This would mean that I need some scheme to group units together in real time and assign a general to each group, which would require some developing.

    --I had a feeling that draw and verts counts were less of a problem. I've done some work to enabled automatic batching. Here's an example where I disable all of the physics:
    Code (csharp):
    1.  
    2. ----------------------------------------
    3. iPhone Unity internal profiler stats:
    4. cpu-player>    min: 75.6   max: 87.0   avg: 79.2
    5. cpu-ogles-drv> min:  1.4   max:  2.6   avg:  1.7
    6. frametime>     min: 84.6   max: 128.0   avg: 90.4
    7. draw-call #>   min:   9    max:   9    avg:   9     | batched:   220
    8. tris #>        min:  2426  max:  2786  avg:  2736   | batched:  7938
    9. verts #>       min:  4772  max:  5492  avg:  5392   | batched:  5300
    10. player-detail> physx: 26.6 animation:  0.0 culling  0.0 skinning:  0.0 batching:  1.7 render:  8.6 fixed-update-count: 4 .. 7
    11. mono-scripts>  update: 41.1   fixedUpdate:  0.0 coroutines:  0.1
    12. mono-memory>   used heap: 602112 allocated heap: 643072  max number of collections: 0 collection total duration:  0.0
    13.  
    The frame time here is comparable to when slowdowns occur when enemy detection is enabled (via physics or distance tests), BUT the vertex counts are higher here. I should note that the objects on the screen are always moving. The movements can be a bit expensive, since units essentially float and must accelerate and decelerate. They also patrol around certain areas, which require calculations of cos and sin. I realize that these things are expensive; I wanted to trim the fat starting with physics.

    Here's a snapshot of what happens when the enemy detector/trigger stuff is enabled:
    Code (csharp):
    1.  
    2. ----------------------------------------
    3. iPhone Unity internal profiler stats:
    4. cpu-player>    min: 223.7   max: 259.0   avg: 236.5
    5. cpu-ogles-drv> min:  1.5   max:  3.5   avg:  1.8
    6. frametime>     min: 239.4   max: 273.7   avg: 251.3
    7. draw-call #>   min:   9    max:   9    avg:   9     | batched:   243
    8. tris #>        min:  2942  max:  3074  avg:  3005   | batched:  8746
    9. verts #>       min:  5804  max:  6068  avg:  5931   | batched:  5839
    10. player-detail> physx: 108.9 animation:  0.1 culling  0.0 skinning:  0.0 batching:  2.0 render:  8.8 fixed-update-count: 12 .. 14
    11. mono-scripts>  update: 112.8   fixedUpdate:  0.0 coroutines:  0.1
    12. mono-memory>   used heap: 593920 allocated heap: 643072  max number of collections: 0 collection total duration:  0.0
    13.  
    This is worse. The vert count here is higher than the previous example but one gets the idea that the frame time increases at a much faster pace.

    Finally, here's an example where physics is disabled and units stay still (NO idling or patrolling movements) unless they AI sends them to fight (also having the physics disabled means I can't select units to do anything, so the AI does all of the movements):
    Code (csharp):
    1.  
    2. ----------------------------------------
    3. iPhone Unity internal profiler stats:
    4. cpu-player>    min: 41.1   max: 61.6   avg: 49.0
    5. cpu-ogles-drv> min:  2.0   max:  4.5   avg:  2.5
    6. frametime>     min: 51.5   max: 71.5   avg: 58.8
    7. draw-call #>   min:   8    max:   8    avg:   8     | batched:   364
    8. tris #>        min:  4430  max:  4466  avg:  4451   | batched: 13120
    9. verts #>       min:  8780  max:  8852  avg:  8823   | batched:  8755
    10. player-detail> physx: 11.0 animation:  0.0 culling  0.0 skinning:  0.0 batching:  3.1 render: 11.4 fixed-update-count: 2 .. 4
    11. mono-scripts>  update: 19.6   fixedUpdate:  0.0 coroutines:  0.0
    12. mono-memory>   used heap: 790528 allocated heap: 860160  max number of collections: 1 collection total duration: 16.2
    13.  
    Which makes the units themselves look dead unless they are moved. So there is a lot of fat here. To summarize here's ordering of the slowest version of the game to fastest:
    3. Moving (idling) and patrolling units, enemy detection enabled.
    2. Moving (idling) and patrolling units, enemy detection disabled.
    1. Moving when requested only, enemy detection disabled.

    Tomorrow I guess I can describe how each unit chases enemies using triggers (it's actually fairly simple) but it's getting late right.

    --I will try testing with the physics time step stuff. I guess it would help to optimize it as soon as possible and use the time step stuff as a next step?
     
    Last edited: Feb 3, 2012
  24. DasSchwarz

    DasSchwarz

    Joined:
    Jan 29, 2012
    Posts:
    21
    So I've realized that this post is not in the iOS forum even though I'm talking about that topic. Should it be moved?

    Anyway, I wanted to let you know how I handle the collisions. Each unit has two colliders: a small one that is close to the size of the fighter (sphere collider with radius of 0.5), and a bigger one that is much bigger and allows it to detect enemies (sphere collider with radius of 2.5).

    The smaller collider is with the parent object, along with a kinematic rigid body. The larger one is in a child object along with kinematic rigid body. The larger one, which is the detector, is marked as a trigger and has a script with OnTriggerEnter and OnTriggerExit functions defined. I have a dictionary that keeps track of enemy objects that enter and exit the trigger.

    The reason for the dictionary is this; if an enemy leaves the trigger zone, but there are other enemies still around, the unit should chase the next available enemy.

    In this script's update function (called from another script, which I use since apparently the MonoBehaviour inherited Update() function is expensive), I check to see if the unit is not chasing something and if there are items to chase in our dictionary. If so, the item chases the first object in the dictionary that is available.

    Anyway, I realize that having a dictionary per object is expensive but each object should have some kind of autonomy.
     
    Last edited: Feb 4, 2012
  25. David_29

    David_29

    Joined:
    Jan 22, 2014
    Posts:
    12
    Are you looking for a way to maintain the memory efficiency regardless to the number of colliders used or not?
     
  26. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723