It turns out that of all the SDFs, the ones most radically affecting the velocity of the particles out of the hands were the trail distorters. I had their attraction speed set to 200 and attraction force set to 10, meaning that as soon as particles found themselves within the force radius of these attractors, they’d get drawn toward them at ridiculously high speeds.
I can understand why I designed things this way. I was using the strong attractiveness to shape my particles so the overall trail expanded from the hands toward the ball.
However, it’s pretty obvious to me now that having such a strong secondary attraction field in between the hands and the energy ball is a recipe for weird glitches. I know that I can increase initial particle spread by adjusting the radius of the sphere the particles spawn within, spawnSphereRadius, and the initial YZ velocity spread, spawnVeloSpread. This, however, can’t give me my desired shape of a gradually-expanding funnel from the hands to the center sphere because, even with the trail distorters disabled, the pull of the main SDF causes particles to move at a constant acceleration almost immediately. The solution, it would seem then, is not weakening the trail distorters and thereby sacrificing the nice particles trail shape. Instead, I should disable the distorters entirely during situations that are likely to result in glitches. Currently, I lerp their attractiveness to 0 over the course of the hand close animation, disabling them entirely once the animation is fully complete. I just added an additional lerp for their attraction speed over the animation progress because, why not.
I’m able to trigger this glitch by sequentially opening and closing the hands which causes the center sphere to move in an irregular pattern. And despite everything I just wrote about the trail distorters, the above footage was recorded in spite of them being disabled. Clearly, they’re a red herring.
I found the source of the glitch! It should have been obvious, but to be fair, when you work on a project off-and-on for so long, you forget about certain things.
The metaballs are generated with a marching cubes algorithm, which means they only ever exist inside the confines of the grid where the cubes go marching. I used to have a gizmo for visualizing this grid, but it was easy enough to remake it using a simple cube collider. Look at what happens once the SDF disappears entirely…
The particles seem to flee to the origin when there’s no longer a valid SDF attraction field. So how should I go about fixing this?
The simplest solution is to make the grid larger. However, that’s too expensive to realistically implement. I think that whatever the proper solution is involves creating a trigger to detect when the sphere travels outside the grid boundary.
What if I turned the grid bounds into a solid box collider? This could even be gamified if I added a bit of bounciness to the grid walls. Now, while that would be fun, it’d also impair the ability of users to throw energy balls forward into the camera, which is one of my favorite things to do.
Let’s go back to the general trigger idea. When should the trigger trigger? If I wait until the sphere is completely outside the bounds, it’s probably too late, because the particles near the sphere when it disappears completely will go bezerk. I think what I should do is trigger an automatic hand-close override when the sphere crosses the grid boundary by more than 50%. That means I can simply compare the position of the sphere to the grid limits on each axis since the sphere’s center (i.e. its transform position) is 50% from its edge.
I implemented the above proposed solution, but it’s still liable to awkward particle motion when the sphere exits the grid at a high speed and returns shortly after.
I also notice a second related bug which occurs when the hands are closed shortly before the sphere leaves the grid.
Luckily, I know exactly what’s causing it; I use logic in the VFX graph to make it so particles that have collided with the sphere already don’t get drawn back to the hands when they’re closed. Unfortunately, those particles that don’t get drawn back suddenly move toward the origin when the SDF attraction disappears. To fix it, I simply need to incorporate the bounds check into that switch in the VFX graph.

Since this fix still doesn’t address the awkward motion bug, I need to take things one step further and nip the problem at the root—the SDF itself. In my MetaballsToSDF component, I update the SDF every frame. My theory is that if I stop updating the SDF right before the metaball completely leaves the boundary of the grid, it will prevent the particles from having nowhere to go and being attracted to a random point.
And honestly, now that I say that, I realize the particles definitely still have something attracting them even when the hands are closed—the secondary attractors! I just added a guard disabling them once the sphere goes out of bounds.
The whole stopping-the-baking-of-the-sdf-on-out-of-bounds theory sounded good until I remembered there’s a single SDF shared between all players. I’m getting closer to a true solution though. I’m now trying out disabling a player’s attraction to that main SDF when they go out of bounds. This fixes most of the awkwardness, but there’s still a weird visual when particles being drawn to a nearby metaball suddenly get released when the SDF attraction is disabled.
I think a true solution, then, is conditional upon how many players are in the scene. Simply disabling the metaball mesh SDF attractiveness when only one player is in the scene suffices. However, a more involved fix will be required when multiple players are active. I think I’ll try to freeze the position of a metaball on the outer edge of the grid boundary when a player’s sphere exits the grid entirely. I’ll shoot a ray from the center of the player’s hands to the position of the sphere, and position the metaball based on where that ray intersects the grid. Before I do this though, it’s time to commit my changes so far and log off. And with my prompt already written out nicely, I’ll be ready to go with Claude once I start working on this again.