Building a Reusable State Machine for Oscillating Rotation in Unity

Building a Reusable State Machine for Oscillating Rotation in Unity

Leader posted 2 min read

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)
RotatingForwardRotatingBackward MaxReached(): Checks if the current angle ≥ MaxAngle
RotatingBackwardRotatingForward 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:

  1. Reusability: The same RotationFSM can be used to drive dozens of turrets, doors, or fans.
  2. Maintainability: If you need to change the rotation logic (e.g., adding easing), you change one piece of code (OnUpdateRotatingForward), not three.
  3. 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.

If you read this far, tweet to the author to show them you care. Tweet a Thanks

1 Comment

0 votes
0

More Posts

The Grand Finale: Orchestrating Complex Motion with Multi-Context FSMs

The Singularity Workshop - Oct 15

When Unity Glitches, You Build Your Own Loop: The Four-Line App Core

The Singularity Workshop - Oct 17

Completing the Motion Trilogy: The Reusable TranslationFSM

The Singularity Workshop - Oct 14

Applying the FSM Context Pattern to Scaling: The Reusable ScalarFSM

The Singularity Workshop - Oct 14

The FSM Agent Architecture: Scaling NavMesh Agents with Decoupled Logic

The Singularity Workshop - Oct 15
chevron_left