Applying the FSM Context Pattern to Scaling: The Reusable ScalarFSM
In our previous article, we demonstrated how to eliminate Update() spaghetti by applying the FSM Context Pattern to complex Rotation logic. The result was a single, reusable blueprint ("RotationFSM") that drove three objects on different axes.
Here, we prove the true versatility of this pattern by applying it to Scale motion. The vast majority of the logic remains the same—only the Context data and the specific Unity functions change.
1. The Context: Swapping Angular for Linear Parameters
The core philosophy remains: isolate the specific data required for the motion. We move from the RotationContext to the ScaleContext.
| Rotation Context Parameters | Scale Context Parameters | Purpose |
| RotationSpeed (float) | ScaleSpeed (float) | Linear speed of scale change. |
| RotationAxis (Vector3) | ScaleAxis (Vector3) | The axis (or axes) to apply the scale along. |
| MaxAngle / MinAngle | MaxScale / MinScale | The absolute min and max bounds for the object's local scale. |
ScaleContext.cs: Data Encapsulation
The ScaleContext holds the new parameters and is what ultimately manipulates the TransformHandle.localScale.
public class ScaleContext : IStateContext
{
// The new parameters that define scaling motion
public float ScaleSpeed = 0.5f;
public float MaxScale = 3.0f;
public float MinScale = 0.5f;
public Vector3 ScaleAxis;
public Vector3 OriginalScale = Vector3.one;
public Transform TransformHandle;
// ...
}
2. The FSM Definition: Same Flow, New Names
The Finite State Machine flow is identical to the rotation demo (Move $\rightarrow$ Max $\rightarrow$ Move Back $\rightarrow$ Min) but uses domain-appropriate terminology.
The blueprint name changes from "RotationFSM" to "ScalarFSM".
| Rotation FSM | Scalar FSM (Change) |
| States: RotatingForward | States: ScalingUp |
| States: RotatingBackward | States: ScalingDown |
| Predicate: MaxReached | Predicate: Apex |
| Predicate: MinReached | Predicate: Root |
The Core Logic (The Only Update)
Instead of applying a Quaternion update using Quaternion.AngleAxis, the state update logic now directly modifies the localScale vector using the ScaleAxis vector stored in the Context.
// The OnUpdateScalingUp function within ScaleContext.cs
private void OnUpdateScalingUp(IStateContext context)
{
if (context is ScaleContext sc)
{
// Linearly increase the scale along the specified axis(es)
Vector3 scaleDelta = sc.ScaleAxis * (sc.ScaleSpeed * Time.deltaTime);
sc.TransformHandle.localScale += scaleDelta; // The only movement line!
}
}
The elegance here is that the state engine (the FSM) required zero changes. We only updated the specific motion logic inside the state functions of the new ScaleContext.
3. The Entry Point: Demonstrating Multi-Axis Motion
The SimpleScalarDemoFSM.cs acts as the entry point. This demo highlights a capability easily solved with scaling that was slightly more complex with rotation: multi-axis movement.
Notice how the third instance utilizes a ScaleAxis vector with multiple components set to 1 to drive a single FSM instance to scale along both the X and Y axes simultaneously.
private void OnEnterExecuting(IStateContext context)
{
if (context is SimpleScalarDemoFSM ssd)
{
float speed = 0.5f;
float maxScale = 3f;
float minScale = 0.5f;
// 1. X-axis Scale
ScaleContext xAxisContext = new ScaleContext(ssd.Xaxis, new Vector3(1, 0, 0), speed, maxScale, minScale);
// 2. Y-axis Scale
ScaleContext yAxisContext = new ScaleContext(ssd.Yaxis, new Vector3(0, 1, 0), speed, maxScale, minScale);
// 3. XY-axis Scale: Scales on X and Y using the *same* FSM definition
ScaleContext xyAxisContext = new ScaleContext(ssd.XYaxis, new Vector3(1, 1, 0), speed, maxScale, minScale);
// ... Instantiate all handles and transition to start state
}
}
This demo proves that the FSM Context Pattern is an engine-agnostic architectural solution. Once you define the pattern, reusing it for any kind of movement—Rotation, Scale, or Translation (coming next!)—is as simple as defining a new Context class.
See the demo in action:
YouTube Channel Video
Support the Vision and See the Code!
If this pattern has helped clean up your Update() loop, please consider supporting our development! Your support ensures we can continue to provide advanced, architectural solutions for cleaner game development.
Resources & Code:
The FSM Package (Unity Asset Store):
https://assetstore.unity.com/packages/slug/332450
NuGet Package (Non-Unity Core):
https://www.nuget.org/packages/TheSingularityWorkshop.FSM_API
GitHub Repository:
https://github.com/TrentBest/FSM_API
Support Our Work:
Patreon Page:
https://www.patreon.com/c/TheSingularityWorkshop
Support Us (PayPal Donation):
https://www.paypal.com/donate/?hosted_button_id=3Z7263LCQMV9J
We'd love to hear your thoughts! Please Like this post, Love the code, and Share your feedback in the comments.
