How to optimize SpriteShapes?

Hello,

I’m working on a Great Strategy game where you can play, interact and conquer a whole country, divided by regions. I have nearly 340 regions, each containing 400 points to delimitate it (the border then).

I want to use the SpriteShapeController to allow the region creation at runtime (once a region is created, it never changes). So, at Start, I instantiate every region and bind borders points to the SpriteShapeController.spline and never touch this spline ever after.

But, doing this costs a lot when I am at the maximum dezoom level (if I see the entire country map). And I don’t know how to optimize that.

Which optimization could I do? Is the SpriteShapeController the greatest choice to achieve that so? What am I doing wrong?

NB: here is the current prefab setting for the sprite shape

Thank you for any answer :folded_hands:

I really not at all familiar with the SpriteShape system but from what little I’ve heard they really aren’t meant to be used in huge quantities and don’t scale very well. This is pretty old info though, plenty could have changed since then.

Off the top of my head though I’m thinking your best bet is to use the sprite shapes as an intermediate step to create something simpler like a regular mesh that can easily be instanced or at the very least, blasted through the SRP batcher pretty fast.

If for some reason you are really depending on the sprite shapes for some kind of interactive gameplay or UI systems then this of course won’t work. But perhaps you could meet part of the way. For example, at a certain zoom level, perhaps you could render the entire world to a render texture and display that instead of the individual sprite shapes and then simply disable any functionality that would be available at a closer zoom level? Basically it would be for scouting only and wouldn’t provide any realtime UI interaction.

^ ^ ^ Agreed…perhaps something static and cheaper like graphics imposters. Those are basically used for lots of complex things at great distance, eg, the lowest-detail LOD.

From the screenshot, it seems:

Cause: If the entire map is seen on scene load, a large number of SpriteShapes are generated at the same time.

Suggestion: To ensure CPU cost is low when generating a large number of SpriteShapes when zoomed out, try setting splineDetail of SpriteShapeController very low (this can be set all the way to 2 through scripting). When zoomed in, this can be set to high detail if its already not high to those that are visible.

SpriteShape geometry is only generated whenever there is a change in the inputs like change of properties or spline etc.. Hence if you are using a large number of SpriteShapes, set the default detail to low and only set it to high when it becomes visible (either check visible or use OnWillRenderObject). Once set to high you may want to just keep it.

You can also use SpriteShapeGeometryCache to cache generated geometry in Editor time.

Let us know if this helps.

Did you ever fix that it’s only possible to do this a) through the editor and b) only when you have a single SpriteShape selected (ie. not when you multi-select them)?

We had to fork the package, because having to by hand click 1000+ spriteshapes accross the game and clicking “cache geometry” on them one at a time was not viable.

We already switched from 3D to 2D (partly) because of the mesh generation of region which costs many resources on the client side. I though 2D would take less resources, even using sprite shape.

That was one of my other solutions if the SpriteShapeController couldn’t be optimized as well. I also thought of a Texture2D generation based on the region data (we have the borders, so we totally can spread fill the texture based on the edges). This will be done at start once and never be changed during the game lifetime.

Like you can see in my post, the splineDetail is already set to Low Quality and overriding it by script (by a value under 0) results in a bug: the shape isn’t draw at all.


Note: I need to explain why the SpriteShapeController was my first choice. Our game data are based on dynamic informations, from a file that can be modded by players. So, players could totally change the worldmap at runtime (after a game relaunch). So I needed a solution to generate regions at start. Actually, SpriteShapeController.spline is set only at start and is never changed ever during runtime. That’s why I’m questioning about the sprite shate cache system which doesn’t seems to be used for generated shapes at runtime. And that’s terrible because the spline never changes but the OnWillRenderObject() calls the BakeMesh() each frame.

It very much sounds like you should fork the package! Add a bool to make OnWillRenderObject not call BakeMesh, and call it manually from your Start function.

BakeMesh does not trigger a Geometry Generator Job. It only verifies if the Spline has changed or any parameters has changed and if there is a change calls
ScheduleBake
which schedules Generator.

If there are no changes it should never schedule a Job.

Regarding this, we will consider adding scripting support and keep this thread posted. Thanks.

Ok so the call is really heavy itself. Because taking 15ms on 340 objects to call the OnWillRenderObject() that does nothing, it’s kinda weird?

I commented the method directly from the package (to test) and it doesn’t seems to break my game.

I think I will do that and comment the entire function. Because I really don’t need it at all (calling the BakeMesh() method manually once at start)

Thanks for adding more info. Could you please submit a bug report with a simple repro? This will help track the issue. Will take a look at the earliest and update this thread.

fyi: regarding SplineDetail, the lowest valid value is 2 and anything below that may result in invalid geometry.

Report issue number: IN-100300

I tried some settings and I think that, even if the spline is not update, the spline.GetHashCode() costs really much because of the points count iteration :

 for (int i = 0; i < GetPointCount(); ++i)
{
    hashCode = hashCode * 16777619 ^ m_ControlPoints[i].GetHashCode();
}

I have 340 region (SpriteShapes) which contains ~400 points each. Retrieve the hashcode each frame seems really heavy. It could be great to allow the check manually only, or at least have a toggle/dropdown to choose which bake method we prefer (Each Frame or Manually). Maybe another boolean that turns true if both spline.SetPosition or spline.InsertPointAt is called? Instead of checking the entire hashcode of the spline?

Thanks for reporting. Agree this has to be improved. Will take a look at this asap. Will keep this thread updated.

Hi I wanted to share a small script I wrote based on this thread and another related to caching spriteshapes by script.

The intention is to enable spriteshape geometry cache only during a build to keep scene sizes small, but still get the performance benefits from the cached geo. The script also sets any found SpriteShapes to static.

This appears to work decently well but I noticed one issue: any spriteshape edges that are part of a sprite atlas do not get cached correctly (the UVs appear to not be calculated correctly) if the editor sprite atlas settings are set to “Only in builds”.
It seems to only work correctly if the editor is set to always create sprite atlases.
I also attempted to change this editor setting via script but it also does not apply correctly.

Maybe you can offer some insight into how to achieve this via script, and hopefully my script is helpful in determining what parts of the API can be opened up.
SpriteShapeBuildCache.cs (3.0 KB)

Hey @Venkify, any news ?
I still see some costly unnecessary BakeMesh calls in profiler in u6000.3.19.

@DarkRewar
Did commenting the line (~forking the package) end up working ? (if you’re not still working on it)

@samanabo
Thanks for sharing :slight_smile:
I’mn ot completely sure what your script does ?
During build it will create a new Component that will contain cached geometry within the scene … ? I don’t see deletion of the controller so I’m not sure what was the goal here ?

Thanks, good thread :slight_smile:

Forking the repository and adding a member to bake on demand worked. Then, I used the forked repo as package instead of the official one.

But I moved on Unity 6.5 since, and completely dropped SpriteShape (because game was converted to 3D).

@ABerlemont
Spriteshapes have been updated a bit since I last posted, here is an updated script.

This script will cache the SpriteShape geometry a build time, preventing it from updating at runtime. It does two things to achieve this:

  1. It creates, and serializes a SpriteShapeGeometryCache for each spriteshape in the scene. This is an internal component that spriteshape uses to store geometry data. This would normally be calculated at runtime, but this script forces it to be created at build time. In the SpriteShapeController if the cache is present then the geometry won’t be re-calculated.

  2. SpriteShapeControllers recently gained an m_UpdateGeometry serialized property. This property is currently private so to turn it off you have to go through each spriteshape manually and uncheck it in the inspector. This is obviously time consuming, so this script will grab the field using reflection and set it to false at build time.

It is a little hacky, but overall these two changes should prevent the geometry from being recalculated each frame, and it should allow the spriteshape to be rendered correctly. Because this is done at build time it should have minimal impact on the runtime performance.

I don’t delete the controller because you may still want to control it during runtime, or read into the data. I haven’t done extensive testing on this, but it seems to work for me, especially running on less performant hardware.

SpriteShapeBuildCache.cs (3.7 KB)