How can I improve this procedural damage warping effect?

I am working on an RTS game where the units are rendered client side. I am working on this effect, which will increase in intensity the lower a unit’s health gets.

9a9412b43ddc84a90ed41cc8da6103e3

it uses a really simple formula for each vertex:

editMesh:SetPosition(vertexid, 
defaultPos + Vector3.new(
   math.sin(defaultPos.Magnitude), 
   math.cos(defaultPos.Magnitude), 
   math.tan(defaultPos.Magnitude)%1
) * warpScale*1.25 )

where warpScale is the intensity of the effect (0-1) and defaultPos is the default position of that vertex. By throwing in these trigonometric functions, I create a psudo-random (but consistent) effect that gradually worsens with increasing intensity.

If any of you have any feedback of what you would change to make it better, let me know!

2 Likes

I have encountered a bit of a roadblock

When making this effect, the code uses assetService:CreateEditableMeshAsync() to make a new editable mesh for that meshpart. The problem with this is that using the Async function takes roughly a second or so to process, which would be bothersome in-game. I then made it so that the first time an editable mesh is made, it is referenced by any future units that have the same mesh, so that multiple calls to the async function are not made and it only needs to be called once per mesh. The issue with this though, is that now any unit that takes damage will now have the effect applied to itself again for every time any other unit with the same mesh takes damage. I figured “no biggie, ill just clone the preexisting editablemesh for each new unit”, but the issue with that is that editablemesh doesnt have a clone() function. There is no way to duplicate it. So my options are to either suck it up and accept that each new unit will take a second to load in, or to have this weird bug where every damaged unit in the game will have their mesh messed with each time something somewhere takes damage.

Maybe just presave the mesh as multiple damage state sets like a vfx flipbook and then set the transparency to 1?

Effect looks nice.

1 Like

You might be able to use EditableMesh:CloneFromOtherInstance() indirectly by generating a new EditableMesh and copying vertex data manually from a cached source mesh.

Since EditableMesh doesn’t support .Clone(), you could:

  • Keep one “template” EditableMesh in memory,
  • On unit creation, generate a new EditableMesh for that instance,
  • Then copy all the vertex positions and normals from the cached one into the new one.

This avoids re-calling CreateEditableMeshAsync() every time, and still gives you unique per-unit meshes that don’t conflict when damaged.

That is a lot of data to copy over. EditableMesh doesn’t have any methods of directly retrieving mesh data, nor methods for setting data wholesale. To get the vertex positions alone, you have to use getVertices() to get the ids of the verts, then loop through those ids to retrieve the positions, then create new vertices within the new mesh with those positions. That’s just to get the vertices alone, that doesn’t even cover faces, UV maps, normals, or bone weights (not that I’m using those anyway). In theory the functions they give us in the API are enough to make a cloning function, but making this is gonna take some time. If i ever do complete it, ill post it here so nobody else has to go through this mess, lol

Update: Upon attempting to make a function for cloning an EditableMesh, I appear to be running into a warning Failed to create empty EditableMesh that was requested due to reaching memory budget limits. in which AssetService returns nil instead of returning a new EditableMesh. It works once, the first time something needs to be cloned, and then it does not work again. I may need to look elsewhere for a solution to this problem.

This whole thing would be so much easier if either of these were true:

  • using meshPart:ApplyMesh() did not continue to push changes to itself and only applied the mesh once per call
  • EditableMesh had a built-in Clone() function

For now its looking like im going to have to just accept the 1 second delay upon unit insertion. I might be able to make a system that preloads models and hands them back out in an attempt to mitigate this, but that sounds like a lot of unnecessarily wasted memory. I would have to do this for every unit type, and would perhaps need to have multiple ready to go at any given time for when multitudes of units are being produced at once. None of this is ideal

Yeah, that’s the exact pain point with EditableMesh right now. The API technically gives you the tools to rebuild a mesh, but not efficiently. No bulk setters, no native cloning, and the memory budget hits way too early if you’re working with lots of units in parallel. It’s solid for experimentation, but it clearly wasn’t built for high-scale reuse like what you’re trying to do.

If you do end up going with the preload pool route, keep it tight. Just enough to handle peak bursts, then recycle. It’s annoying, yeah, but probably the cleanest tradeoff if Roblox doesn’t add proper mesh cloning or a stateless ApplyMesh() call.

And if you ever do wrap up that clone workaround, its great that you want to post it. This kind of edge-case R&D is exactly what pushes the platform forward. You’re doing the heavy lifting that the rest of us will benefit from later.

Im probably not going to complete the cloning function, given that the memory cap hits really early, rendering it pretty much useless anyway.

In the case of my game, I could just put the editableMesh creation on a separate thread so it doesnt interfere with visible unit loading, and then utilize the editablemesh later once it loads in. The game loads in the actual model first, so I could actually just have the unit load in without yielding by putting the editablemesh on a seperate thread, and then deferring any geometry warping until after the editablemesh is ready to go. This is probably the cleanest solution

That sounds like the right call. Keeps your visual flow intact and avoids hard blocking.

Deferring the deformation also gives you more control without hitting performance walls early.

Cleanest workaround given the current API limits. :+1: