Escaping Update() Spaghetti: Reusable Oscillation with FSM & Context in Unity
If you’ve ever built a Unity object that needs to move or rotate between two points—like a swinging pendulum, a turret oscillating for patrol, or a security camera scanning an area—you’ve likely ended up with a tangle of booleans and if/else statements in your Update() loop.
// The typical mess you want to avoid
void Update()
{
if (isMovingForward)
{
// Move Forward Logic
if (transform.rotation.z > MaxAngle)
{
isMovingForward = false;
}
}
else // isMovingBackward
{
// Move Backward Logic
if (transform.rotation.z < MinAngle)
{
isMovingForward = true;
}
}
}
By leveraging the Finite State Machine (FSM) pattern with a distinct Context object (using the FSM_UnityIntegrationAdvanced package), we can create a single, clean definition for oscillating rotation that can be instantly reused across any number of objects and axes.
The Heart of the Demo: The FSM Context
The core idea is to separate the object's data (where it is, how fast it moves) from the logic (how it behaves). This is done via the RotationContext class, which acts as the data structure for each rotating object.
RotationContext.cs: Data Encapsulation
By implementing IStateContext, this class holds everything the FSM needs to operate on the specific object:
public class RotationContext : IStateContext
{
// Object and parameters for THIS instance
public Transform TransformHandle;
public Vector3 RotationAxis;
public float RotationSpeed;
public float MaxAngle;
public float MinAngle;
// ... other data
}
The Universal FSM Definition
The beauty of this approach is that the Finite State Machine itself ("RotationFSM") is defined only once inside the RotationContext constructor, making it a reusable blueprint.
| State Name | Logic (Update) |
| RotatingForward | Apply positive rotation to TransformHandle |
| RotatingBackward | Apply negative rotation to TransformHandle |
| Transition (What decides the change) | Condition (Predicate) |
RotatingForward → RotatingBackward | MaxReached(): Checks if the current angle ≥ MaxAngle |
RotatingBackward → RotatingForward | MinReached(): Checks if the current angle ≤ MinAngle |
The logic in OnUpdateRotatingForward and OnUpdateRotatingBackward simply drives the object, relying entirely on the MaxReached and MinReached predicates to decide when the object's behavior must change.
The Entry Point: Instantiating Reusable Logic
The SimpleRotationDemo.cs is a standard Unity MonoBehaviour whose sole job is to kick off the system in its Awake() or OnEnterExecuting method.
Instead of writing three separate components (one for X, one for Y, one for Z), we instantiate three unique Context objects, each feeding the same shared FSM definition ("RotationFSM") with different parameters.
private void OnEnterExecuting(IStateContext context)
{
if (context is SimpleRotationDemo srd)
{
float speed = 90.0f;
float maxAngle = 45.0f;
float minAngle = -45.0f;
// 1. X-axis Rotation: Unique context, shared FSM logic
RotationContext xAxisContext = new RotationContext(srd.Xaxis, new Vector3(1, 0, 0), speed, maxAngle, minAngle);
srd.XaxisHandle = xAxisContext.Status;
// 2. Y-axis Rotation: Same FSM logic, different object/axis
RotationContext yAxisContext = new RotationContext(srd.Yaxis, new Vector3(0, 1, 0), speed, maxAngle, minAngle);
srd.YaxisHandle = yAxisContext.Status;
// 3. Z-axis Rotation: Same FSM logic, different object/axis
RotationContext zAxisContext = new RotationContext(srd.Zaxis, new Vector3(0, 0, 1), speed, maxAngle, minAngle);
srd.ZaxisHandle = zAxisContext.Status;
}
}
The Result: Scalable Advanced Integration
With minimal setup, we achieve perfectly synchronized, bounded rotation across three distinct objects, all driven by a single, well-defined state machine. This pattern easily extends to more advanced scenarios:
- Reusability: The same
RotationFSM can be used to drive dozens of turrets, doors, or fans.
- Maintainability: If you need to change the rotation logic (e.g., adding easing), you change one piece of code (
OnUpdateRotatingForward), not three.
- Extensibility: This blueprint instantly adapts to your
Translation, Scale, or NavMeshAgentsDemo logic—simply define a new FSM and Context for those specific concerns.
This demo proves that advanced FSM integration doesn't mean complex setup, but rather intelligent architectural separation to keep your Unity code clean and highly reusable.