- Home /
A way to optimize moving a huge group of colliders at once
I'm making a 2D top down game where the player runs around and random segments will spawn so that they are constantly surrounded by a 3x3 group of segments giving the illusion of an endless map. These segments have a box collider and a huge amount of primitive colliders as child objects. (100-300 colliders per segment give or take) and a bunch of sprites. Here is an example of a segment:
I'm using an object pooling system with a total of 24 segments, 9 being used at a time and the rest off screen (disabling them caused a bigger spike than just moving them). No matter what method I try I can't escape the massive physics.simulate spike. I've considered trying a mesh collider instead of primitives but that would require an enormous amount of work with all the segments I've already made and may not even help. Does anyone know a way to remove or reduce the spike caused by this? Note that I've tried having a kinematic ridgedbody on all colliders, just the parent colliders, and on none of the colliders and it doesn't seem to make much of a difference.
Here's the code that 'generates' the segments:
IEnumerator GenSegments() {
//Move all segments outside of the 3x3 box back into the segment pool (objects at y -1000 are in this pool)
foreach (Transform OtherSeg in UseableSegments)
{
if (Vector3.Distance(CurrentSegment.position, OtherSeg.position) > 80 && OtherSeg.position.y > -500)
{
//Move the segment
OtherSeg.position = new Vector3(0, -1000, 0);
}
//Stagnate the spike to avoid a frameskip
yield return new WaitForFixedUpdate();
}
//CheckPos is used to check for existing segments before moving in a new one
Vector3 CheckPos = new Vector3 (CurrentSegment.position.x + 50, 25, CurrentSegment.position.z);
//Checks all 9 spots where a segment can be
for (int A = 0; A < 8; A++)
{
if (!Physics.Raycast (CheckPos, Vector3.down, out SegmentRay, 30f, SegmentLayerMask))
{
//If there is no segment, grab a random one from the pool and move it to the empty spot
Transform CurrentSeg = null;
int RandomInt = Random.Range(0, 11);
while (!CurrentSeg)
{
if (UseableSegments[RandomInt].position.y < -500) CurrentSeg = UseableSegments[RandomInt];
else {
RandomInt++;
if (RandomInt > 11) RandomInt = 0;
}
}
//Move the segment
CurrentSeg.position = new Vector3(CheckPos.x, 0, CheckPos.z);
}
//Used to move CheckPos to the next segment spot
if (A == 1 || A == 2){
CheckPos.x -= 50;
} else if (A == 3 || A == 4){
CheckPos.z -= 50;
} else if (A == 5 || A == 6){
CheckPos.x += 50;
} else if (A == 0){
CheckPos.z += 50;
}
//Stagnate the spike to avoid a frameskip
yield return new WaitForFixedUpdate();
}
}
I am using 3D colliders as I found that most of the 2D features did not have top down games in mind and that made things difficult. All movement is based on raycasting so there is no actual physics. I would greatly appreciate any help you gives can give.
you might want to add a collision mask so that the colliders aren't registering collision with other colliders of the same type
http://docs.unity3d.com/$$anonymous$$anual/LayerBasedCollision.html
Already got that set up. All those colliders don't register collisions with anything and are only used by raycasts.
2D physic is always a better alternative if you don't use 3d. I don't know where do you get "2D features did not have top down games in $$anonymous$$d".
In general case, the problems are caused by us developers,and not the engine. Using many colliders with move/enable/disable should not cause any problem.
The "foreach (Transform OtherSeg in UseableSegments)" and "while (!CurrentSeg)" are very dangerous code, but you take care and I don't see real problems. I can see some $$anonymous$$or improvements like remove while (!CurrentSeg) with a list of availables, but nothing to solve.
Unfortunatelly I think that you need to profile more. Did you have unity pro to check code/physic spikes? You could use http://wiki.unity3d.com/index.php/Profiler to understand what time take each look.
Problem is you have to have the 2D colldiers side on and that made a lot of things like LookAt not work and it also made it just a whole lot more confusing as almost all examples around the web are expecting top down games to be looking from top down. There would probably have been a way to do it but I wasn't really confidant enough to try. Also I originally planed to use navmeshs and they don't work side on.
I've been using the profiler from unity pro to check everything. Here's one of the spikes that occurs when I move the segments:
The GPU spikes are caused by RenderTexture.SetActive so I assume they are to do with me still enabling and disabling my zombies when I return them to the their pool. The CPU Physics.Simulate are the spikes caused by the segments.
Sisso I think what he means by "2D features did not have top down games in $$anonymous$$d" is that it's not very obvious how to set up a top down 2d game with what unity gives you. A blank 2D project starts with every object's transform.forward facing the same direction as the camera and gravity is down on the Y axis. This is only useful for sidescrollers really.
While it is the responsibility of the developer I can't help but shame Unity for kind of ignoring the top-down style. Any beginner is going to have a tough time.
Answer by _dns_ · Oct 10, 2014 at 04:07 PM
Hi, a physics engine uses some optimizations to reduce the number of collisions tests it makes. Those optimizations rely on structures that are fast to do some things, but slow to do some other things. You can read more about this here : http://jitter-physics.com/wordpress/?tag=sweep-and-prune.
In your case, I believe when you move a segment, the physics internal structs must be updated for lots of colliders in the same frame so it causes a spike. Enabeling + disabeling colliders usually causes the same structure update. You can read Unity's blog about physics in Unity 5.0 here: http://blogs.unity3d.com/2014/07/08/high-performance-physics-in-unity-5/ it explains some of this stuff. Hopefully, Unity 5 could fix your problem, you may be able to ask for a beta to check if it does, and then maybe you can live with the spikes until 5.0 is released.
Another way to fix this would be to reduce the number of active colliders. Your map has some kind of islands of those colliders, maybe you can regroup each island under a bigger sphere that would be a trigger (this sphere object would have childs containing colliders). When your player enters that trigger, it enables all the childs colliders it contains (and disable them when exiting). This way, you would force the physics system to update its internal structs very often, but only with a limited amount of colliders and this should not "spike". Also, as all the colliders from other segments would be disabled (except the few triggers), I think the spike of moving them should disappear. This is the same kind of "divide & conquer" thing physics engine broadphase does, but made specific to your game.
This solution may not work if you have lots of other objects that must raycast on those spheres, like enemies. They would also have to trigger enabeling/disabeling lots of colliders and this could lead to other spikes. If this is the case, I would switch to the 2D colliders system as it is faster (I'm sure you can write an editor script that could "convert" all your 3D objects in 2D).
Thank you for your links! Unity 5 looks promising. There are up to 100 zombies spawned at a time so all the colliders would always be active, that is a very cool idea that I may use in the future though :D
I've decided that the combination of a huge amount of colliders, needing to move them, and needing a large number of AI to navigate them all at once is a terrible idea for android. It was my own inexperience that lead to this but I have learned a lot from it and that's what counts! A good resigned of the gameplay shouldn't be too hard with all I've learned along the way.
Answer by drudiverse · Oct 10, 2014 at 05:06 PM
When you are moving, you will have to move 1 object every frame or 2, so put a yield wait for fixed update line or wait for seconds in the loop that finds what to move. actually, find what to move, and every time the loop finds a moveable, add 1 to a var called moveable delay, multiply the process time that actually moves the object by that incremental number, so you can run teh compare move loop in 1 frame but actually move teh objects in 100 frames say.
so you never move more than 1 object ever 0.02 seconds for example.
I've done this with a the segments themselves but the sheer amount of colliders would take a fair amount of time to move using yield and the player would reach them before they got moved. I could probably try doing it on groups of colliders though but that would cause constant low FPS rather than a lag spike. I'll give it a go though!
Your answer
Follow this Question
Related Questions
enemy pooling 1 Answer
How do I get my game to run faster? 4 Answers
Merging together all GameObjects of an Array? 1 Answer
Windows Phone performance 0 Answers
Sprite Alpha Performance 0 Answers