Big day today! It’s the first day of the last weekend before my second annual presentation of the Interactive Energy Ball at the Frost Science Museum. I’ve been downplaying this event mentally because I’ve already showcased the project there once before, but really, I have a real chance to impress this time around and need to make the most of it.
Last time around, I was fixing the code up until the very last minute (literally. see 2024-08-16), and it’s my topmost goal to avoid that scenario this time around. I can’t just always rely on luck, even if I tend to consistently get lucky. With that in mind, I decided to start the workday off the right way with a classic pencil and paper planning session.
I came up with 5 tasks I need to complete before I hit MVP level. Once I get there, I’ll do a code-freeze, and continue working in a separate branch. Here they are, summarized:
- Setting up trail distortion objects between the hand and sphere
- Animating the sphere position based on hand open/close state
- Modifying throw force to work with a single hand
- Using the metaball SDF stick for force and a sphere SDF for attraction force
- Animating hand states
The astute reader might remember that #5 was the only task on my todo several weeks ago. Just look at where my ADHD takes me!
Now, let’s get to it.
I finally made Dummy player a prefab variant of the regular player prefab. This way, when I modify the original, I automatically modify the dummy.
I had to get a bit hacky to get the trail distortion transforms to interpolate positions properly between the sphere and hand without being overridden by the Brownian motion script.
for (int i = 0; i < numDistortersRight; i++)
{
float t = (i + 1f) / (numDistortersRight + 1f);
Vector3 pos = Vector3.Lerp(rightStart, rightEnd, t);
// Disable BrownianMotion temporarily to set position
if (player.rightHandTrailDistorters[i].TryGetComponent<Klak.Motion.BrownianMotion>(out var brownianMotion))
{
brownianMotion.enabled = false;
}
player.rightHandTrailDistorters[i].transform.position = pos;
// Re-enable BrownianMotion to capture new position as initial position
if (brownianMotion != null)
{
brownianMotion.enabled = true;
}
}
But at the end of the day, it works. I’ll clean that up later.
Gotteem.
So I got a bit sidetracked. I enabled Prettier in my repo and made it so my scene controller SO sends events to my scene controller script whenever certain debugging values change.
Now where was I??
Thankfully, the code responsible for the throw force is remarkable simple. I surprised myself there. It’s so simple, in fact, that I won’t even need to modify anything to get throw force to still work with one hand. This is because my throw force relies on the existing momentum of the sphere at the moment the hands close. I’ll still need to add some sort of safety net to prevent the sphere from switching directions if there’s a slight discrepancy between the frame count each hand closes at when the player attempts to close them simultaneously.
Now, I added a simple function to move the sphere from the middle toward the only open hand when, well, there’s only one open hand. All I do is change the midpoint from the halfway point between the hands to the open hand’s position. This results in a pretty drastic shift in location when a hand closes and I’d like things to be a bit more smooth.
Therefore, I want the “midpoint” to lerp from the actual midpoint to the open hand’s position over a period of time. The period of time should be relative to the distance between the sphere and hand. Or should it be relative to the distance between the hands? I wonder…
Sike! In the name of speed and avoiding mental math, I’m taking a nifty shortcut. When only one hand is open, I add a damper to the push force that guides the energy ball towards the midpoint.
isSingleHandOpen(player) ? controller.so.singleHandOpenForceDamper : 1f
I was gonna grab a clip of the target swapping in action and noticed a weird bug. It seems like my left hand’s target got stuck somehow.
First I need to see (and pray) this bug is replicatable.
Instead of tracing that bug, I made my hand tracking logic more sound. My Kinect V2 camera isn’t the greatest in low light settings, and therefore I need to be wary of hand tracking states registering values of “Unknown” or “Not Tracked”. I was setting a previous hand state every frame and using that as a buffer against untracked states, but I refactored my logic so that only states of “Open” and “Closed” actually trigger changes in logic.
I added a separate SDF sphere that I’ll use for drawing particles back to the hand. I haven’t started animating its values yet but that’s now the last thing on my MVP todo list! After tweaking the attraction values in my graph, I came up with what I consider a very satisfying visual.
With all this done, it’s finally time to animate this thing! I’m not gonna try and build out my fancy VFX Graph in-house GPU animation system—there’s no time for that. Instead, I’m gonna try out something I’ve seen Keijiro use but never thought about using myself for some strange reason: Unity’s Timeline. According to ChatGPT, the Timeline offers editor curves, per-instance bindings, and mixer tracks for fades. That’s everything I need to make my 7/17 MVP deadline!
Unfortunately, my social life binds me like a VFX Property Binder, and I have to pay respects to my friend Medet by going to his birthday party. This Timeline adventure will have to wait until tomorrow.
4/5 Daily Tasks Complete ✅
Not bad!
Tags: unity gamedev vfx animation hand-tracking scripting physics