This is an activity-driven sandbox game where I took ownership of the Zone Traversal System. This system enables players to travel seamlessly across playable areas by integrating multiple gameplay aspects including NavMesh, Animation, Timeline, VFX, player input, and camera movement.
The game features various types of traversals, each with unique characteristics. I designed a unified script to manage all traversal scenarios, which ensures consistency and maintainability throughout the system.
The system is fully data-driven. Since some traversals are bidirectional, I exposed origin and destination data directly in the editor. When a player interacts with either the starting or ending point, the system receives the appropriate data and executes the traversal.
I leveraged Unity's Timeline system to maintain complete control over traversal sequences. This approach delivers:
Each Timeline contains:
Using animation triggers in Timeline introduces a one-frame delay due to the animation state machine architecture. This causes desynchronization with other animation clips. I evaluated both approaches and made the following comparison:
Animation Clips (Selected Approach):
After thorough evaluation, I selected Timeline animation clips over Animator triggers to achieve smooth integration and minimize memory overhead.
Initially, I recorded positions for each traversal directly into animation clips. This approach created problems when adjusting traversal positions in the scene. Any repositioning required re-recording and re-baking data into the clips, resulting in an inefficient workflow.
I implemented a constraint-based system with the following features:
This solution removes the need to bake movement positions into animation clips. When traversal devices are repositioned in the editor, Timeline anchors update automatically, which significantly streamlines the integration workflow.
To provide context for the Root Motion issues, let me first explain the prefab hierarchy
Root Cause: In a standard rigged character hierarchy, gameplay logic (collision and movement) depends on the outermost Character Root, while the animation's root motion affects the innermost Skeleton Root (Root_Skin).
When the animation moves the visual component but the Character Root remains stationary, the gameplay collider stays behind. Simply moving the Character Root by the animation delta creates double movement because the visuals (Skin Root) are already being translated forward by the animation system.
I developed a custom RootMotionController script to manage this hierarchy with the following approach:
This achieves the following result: Character Root Position += Delta and Visual Root Position -= Delta. This method stabilizes the visuals while advancing the gameplay root forward.
public void LateUpdate()
{
if (!_isTimelineRootMotionEnabled)
{
return;
}
// Update animator localposition
Transform animatorTransform = _animator.gameObject.transform;
animatorTransform.localPosition = _startAnimatorRootLocalPosition;
// Calculate delta
Vector3 deltaWorld = _skeletonRoot.position - _lastSkinRootWorldPosition;
Vector3 deltaLocal = _characterRoot.InverseTransformDirection(deltaWorld);
Vector3 horizontalDeltaLocal = new Vector3(deltaLocal.x, 0, deltaLocal.z);
// Update character root localposition
_characterRoot.localPosition += horizontalDeltaLocal;
_visualRoot.localPosition -= horizontalDeltaLocal;
_lastSkinRootWorldPosition = _skeletonRoot.position;
}
Root Cause: When chaining multiple animation clips (e.g., transitioning from the first jump to the second jump in an 8-stone traversal), a rewind occurs.
The RootMotionController relies on the previous frame's position for delta calculation. When Timeline transitions between clips:
To maintain continuity, I implemented the UpdateRootMotionPosition signal to transfer accumulated distance at transition points: