Using mesh (instead of sharedMesh) in edit-mode

I’m working on a revision to SpriteManager that takes advantage of batching, but adds sprite animation features. I also want sprites to be able to be seen in-editor. To accomplish this, I’m having it execute the necessary routines in-editor. That includes building the mesh data for the GO’s mesh filter. However, I get an error message in the log that what I’m doing will “leak meshes” and that I should use sharedMesh instead. Problem with that is, sharedMesh will alter the mesh for ALL sprites, which is clearly not what I want.

Is this an error worth worrying about (it draws the sprites just fine in-editor), and if so, is there a work-around?

I don’t think this error is a problem for what you are doing. The warning would make sense if you were using a script to build mesh geometry that you wanted to retain - you are just using this as a visualisation aid.

Yeah, I didn’t think it was a problem either, but unfortunately, I’m doing this for others to use, and it creates this big, red, scary error message in the console. I wish there were a way to suppress such a message.

Can you clarify which meshes you’re changing exactly?

I would guess that you are constructing a brand new GO with a mesh that combines all meshes of its target sprites. Why would modifying the shared mesh for this new object modify anything else?

You can simply create a new mesh, assign it to the sharedMesh of your sprite manager’s meshFilter and it should work without harm to anything else. The mesh will even be saved into the scene for you.

But perhaps I just don’t understand what you’re doing ;).

Actually, now with batching, it’s advantageous not to combine the meshes into a single mesh. Instead, each GO with a Sprite script attached gets its own mesh (4 verts forming a quad). I can only presume that since this mesh is getting created from scratch rather than loaded from an asset, it is wanting to share all similarly created meshes. But perhaps I’m missing a way to tell Unity that “this mesh” is different from “that mesh” in another sprite, thereby making it so that “sharedMesh” isn’t, well, shared, between all the sprites.

Assuming you are creating these quads yourself, have you tried something like this:

foreach (Sprite s in mySprites)
{
    // find mesh filter:
    MeshFilter f = (MeshFilter) s.GetComponentInChildren(typeof(MeshFilter));

    // delete old mesh (to avoid leaking):
    DestroyImmediate(f.sharedMesh);

    // add new mesh:
    f.sharedMesh = new Mesh();
    
    // set the mesh contents:
    CreatQuadMesh(f.sharedMesh);
}

After this, each sprite should have its own unique shared mesh. It would be even better if you could manage to run the last two lines of the loop only at creation of a new sprite. Since these shared meshes are persistent in the scene, you need to create one only once per sprite.

I don’t have access to Unity atm, so I can’t test it for you. You’ll have to test yourself to see if I haven’t overlooked something ;).

1 Like

Hmmm, I’m doing basically the same thing, minus the two lines in the middle. Also, I’m not “managing” the sprites anymore (to take advantage of batching), I’m building the mesh contents in each separate GO in its own “Awake()”.

But those two lines in the middle might be the key. Up to now, I’ve just been accessing the mesh object member of the mesh filter, then assigning vertices to it, etc. But explicitly instantiating a new mesh might be the trick. I’ll give it a try, thanks!

Okay, that got rid of the error message and appears not to affect runtime functionality, but unfortunately it makes it so that the meshes don’t always appear in-editor, which is the entire goal. I’m not sure why this is though… The strange thing is that sometimes it will show up in-editor after running the game, and other times it won’t.

That is odd. I’m using a similar trick for my road mesh generation scripts. Basically, once you press the ‘generate road mesh’ button, it deletes the old mesh, creates a new sharedMesh and fills it with geometric data. This mesh is then permanent: quitting/restarting and even sharing through the asset server will preserve the mesh as generated.

Are your sprite objects destroyed/recreated at some point? Anyway, I’m willing to look at your code if you want. I have my share of experience in sharedMesh madness ;).

Well, perhaps it’s because I’m doing it on Awake()?

Here’s what I tried:

Awake()
{
   meshFilter = (MeshFilter) gameObject.GetComponent(typeof(MeshFilter));
   meshRenderer = (MeshRenderer)gameObject.GetComponent(typeof(MeshRenderer));
 
   DestroyImmediate(meshFilter.sharedMesh);
   meshFilter.sharedMesh = new Mesh();

   mesh = meshFilter.sharedMesh;
}

Then in Start(), I populate the mesh with all the necessary values (verts, uvs, etc).

I think Awake() gets called whenever Unity transitions to/from edit mode. So when you stop the game, it’ll call Awake() again because it’s going back to edit mode. That could be it, but I don’t know any way to get around that since I don’t want the developer using the Sprite script to have to explicitly invoke the mesh creation from code themselves.

I’m not convinced Awake is called after shutdown. It’s probably the fact that Unity restores the scene back to the state it was in when pressing play.

So, how do your sprites work exactly? If you create the geometry during game start, do the sprite GameObjects exist when in edit mode? If so, how does the user add new sprites?

Yes, they exist in edit mode. To create a sprite, you just drag a sprite script onto a GO.

And I’m pretty sure when you go back to edit mode, the scene is reset much the same way it is when you press “Play”. In fact, I think it has to for my scripts to work at all (since the code is executed in Awake()). (Yep, I just put a Debug.Log() in there, and it gets called when you go back to edit mode.)

Sadly, Unity does not provide a OnEditorInstantiate() callback, but it should be possible to get similar behavior using a hack:

void OnDrawGizmos()
{
    MeshFilter filter = (MeshFilter) gameObject.GetComponent(typeof(MeshFilter));
    if (filter != null  filter.sharedMesh == null)
    {
        filter.sharedMesh = new Mesh();
        SetQuad(filter.sharedMesh);
    }
}

This ensures that the mesh is generated in editor and will be created only once. Obviously it’s not pretty and it adds editor overhead, but it does appear to work (I tested it). The only other option I see, is adding an editor menu option ‘generate sprite models’ that will generate sharedMeshes for any sprites that don’t have one yet, but that would require the user to press a button to make them visible.

Hmmm… I think as far as Unity is concerned, when you hit “stop” (okay, so there’s no “stop” button, but I mean when you hit “Play” to stop playing), it basically re-instantiates all the GOs. So I’m not sure if it is actually carrying anything over from run-time.

I’ll see if adding that logic helps, but I’m thinking its going to wind up being the same. I’ll give it a shot and let you know. It’ll be a bit, however, as I’ve got to get to bed now. Thanks so much for helping me walk through this!

I specifically tested stop and go and it appeared to work. You just need to remove the new Mesh part from Awake, as I’m not sure that will work well with the OnGizmos hack ;).

You could also reduce editor overhead by cashing the GetComponent:

private MeshFilter _filter = null;
public MeshFilter filter
{
    get
    {
        if (_filter == null)
            _filter = (MeshFilter) gameObject.GetComponent(typeof(MeshFilter));
        return _filter;
    }
}

void OnDrawGizmos()
{
    if (filter != null  filter.sharedMesh == null)
    {
        filter.sharedMesh = new Mesh();
        SetQuad(filter.sharedMesh);
    }
}

That should prevent large numbers of sprites from calling GetComponent every single editor frame.

Interesting, it looks like this solved the disappearing sprite issue. Then I thought, “why do I need to check for a null sharedMesh each frame?” And maybe I don’t fully understand the reasons, but, perhaps foolishly (we’ll see), I tried removing OnDrawGizmos() and just having the new Mesh allocation in Start() where everything gets setup instead of Awake(). That seemed to fix it also. I guess something happens between Awake() and Start() somehow. I don’t fully understand it myself.

Are there reasons you foresee that I would need the continual check in OnGizmoCreate()?

The only difference I can think of is that in OnDrawGizmos, the graphic will appear instantly upon dragging a sprite into the hierarchy. In Start it will not appear until after pressing play once (unless you’re telling me that Start is also called in editor mode :P).

The null check is simply there to avoid creating a sharedMesh when there is already one there. If you leave it out, you wil start getting occasional warnings where Unity ‘cleans up leaked objects’. Whenever you assign a new mesh to sharedMesh, a link to the old one is lost. Since the old one is the same mesh anyway, that should not be necessary. This obviously assumes that the initial meshFilter of your sprite prefab has its mesh set to ‘none’.

Okay, sounds good then. OnDrawGizmos might nice to be able to see the sprite instantly, except the values have to get assigned as well, so I guess I could put in a flag to check if the mesh has been initialized yet or not…

Anyway, thanks a ton for all the help! I think I’ve got it just about where I want it now. That error message was extremely annoying. :slight_smile:

Thank you very muchBrady, for bringing up this issue, and tomvds for solving!

1 Like

Thanks for the OnDrawGizmos() hack, cleared up those crappy errors in my editor.