For more information on designing and building state machines in Rive, please refer to: State Machine.
Rive’s state machines provide a way to combine a set of animations and manage the transition between them through a series of inputs that can be programmatically controlled. Once a state machine is instantiated and playing, transitioning states can be accomplished by changing boolean or double-value inputs, or firing trigger inputs. The effects of these will be dependent on how the state machine has been configured in the editor.
State machines are instantiated in much the same manner as animations: provide the state machine name to the Rive object when instantiated. Ensure that the Rive instance is set to auto-play on initialization to allow the state machine to start immediately.
In the above snippet, at instantiation time, the runtime will create a StateMachineController reference implicitly and immediately play the state machine.
If you’d like further control over the state machine in cases where you may want to prevent autoplaying the state machine, you can instead pass your own reference to a StateMachineController to the RiveAnimation widget at instantiation time with the isActive property set to false. Below is an example of what this might look like:
As you change the isActive property of the StateMachineController, you’ll see that the current state may pause advancing through the render loop. This is useful in cases where you may want to load in your Rive on screen, but delay playing until a loading sequence occurs, or some data comes back for your application.
Additionally, you can use the same APIs from animation playback (i.e play, pause, and stop) to control state machine playback, as long as you set the isStateMachine attribute to true.
Once the Rive file is loaded and instantiated, the state machine(s) can be queried for inputs, and these input values can be set, and in the case of triggers, fired, all programmatically.
Inputs can also be set on nested artboards at runtime, see Nested Inputs below.
The web runtime provides an onLoad callback that’s run when the Rive file is loaded and ready for use. We use this callback to ensure that the state machine is instantiated when we query for inputs.
<div id="button"> <canvas id="canvas" width="1000" height="500"></canvas></div><script src="https://unpkg.com/@rive-app/canvas@2.10.3"></script><script> const button = document.getElementById('button'); const r = new rive.Rive({ src: 'https://cdn.rive.app/animations/vehicles.riv', canvas: document.getElementById('canvas'), autoplay: true, stateMachines: 'bumpy', fit: rive.Fit.cover, onLoad: (_) => { // Get the inputs via the name of the state machine const inputs = r.stateMachineInputs('bumpy'); // Find the input you want to set a value for, or trigger const bumpTrigger = inputs.find(i => i.name === 'bump'); button.onclick = () => bumpTrigger.fire(); }, });</script>
We use the stateMachineInputs function on the Rive object to retrieve the inputs. Each input will have a name and type. There are three types:
StateMachineInputType.Trigger which has a fire() function
StateMachineInputType.Number which has a value number property where you can get/set the value
StateMachineInputType.Boolean which has a value boolean property where you can get/set the value
We can set a callback to determine when the state machine changes state. onStateChange provides an event parameter that gives us the string name(s) of the current state(s):
The web runtime provides an onLoad callback that’s run when the Rive file is loaded and ready for use. We use this callback to ensure that the state machine is instantiated when we query for inputs.
<div id="button"> <canvas id="canvas" width="1000" height="500"></canvas></div><script src="https://unpkg.com/@rive-app/canvas@2.10.3"></script><script> const button = document.getElementById('button'); const r = new rive.Rive({ src: 'https://cdn.rive.app/animations/vehicles.riv', canvas: document.getElementById('canvas'), autoplay: true, stateMachines: 'bumpy', fit: rive.Fit.cover, onLoad: (_) => { // Get the inputs via the name of the state machine const inputs = r.stateMachineInputs('bumpy'); // Find the input you want to set a value for, or trigger const bumpTrigger = inputs.find(i => i.name === 'bump'); button.onclick = () => bumpTrigger.fire(); }, });</script>
We use the stateMachineInputs function on the Rive object to retrieve the inputs. Each input will have a name and type. There are three types:
StateMachineInputType.Trigger which has a fire() function
StateMachineInputType.Number which has a value number property where you can get/set the value
StateMachineInputType.Boolean which has a value boolean property where you can get/set the value
We can set a callback to determine when the state machine changes state. onStateChange provides an event parameter that gives us the string name(s) of the current state(s):
The react runtime provides a useStateMachineInput hook to make the process of retrieving a state machine input much simpler than that of the basic web runtime.
With the React Native runtime, most methods/triggers are available on the ref of the Rive component, including setting input values/triggering for state machines. In this case, there is no need to acquire an instance of an input. Simply set the input state from the Rive ref or fire an input state.
export default function StateMachine() { const riveRef = React.useRef<RiveRef>(null); // Maintain the values of your state machine in React state const [selectedLevel, setSelectedLevel] = useState('2'); const setLevel = (n: number) => { setSelectedLevel(n.toString()); // No need to acquire an instance of a state machine input, just set the // input state on the `riveRef` itself riveRef.current?.setInputState("Designer's Test", 'Level', n); }; return ( <SafeAreaView style={styles.safeAreaViewContainer}> <ScrollView contentContainerStyle={styles.container}> <Rive resourceName={'skills'} ref={riveRef} autoplay={true} stateMachineName="Designer's Test" /> <RadioButton.Group onValueChange={(newValue) => setLevel(parseInt(newValue, 10))} value={selectedLevel} > <View style={styles.radioButtonsWrapper}> <View style={styles.radioButtonWrapper}> <Text>{'Beginner'}</Text> <RadioButton value={'0'} /> </View> <View style={styles.radioButtonWrapper}> <Text>{'Intermediate'}</Text> <RadioButton value={'1'} /> </View> <View style={styles.radioButtonWrapper}> <Text>{'Expert'}</Text> <RadioButton value={'2'} /> </View> </View> </RadioButton.Group> </ScrollView> </SafeAreaView> );}
See the React Native API’s to learn more about the parameters for .setInputState() and .fireState()
State machine controllers are used to retrieve a state machine’s inputs which can then be used to interact with, and drive the state of a state machine.
State machine controllers require a reference to an artboard when being instantiated. The RiveAnimation widget provides a callback onInit(Artboard artboard) that is called when the Rive file has loaded and is initialized for playback:
In the onInit callback, you can create an instance of a StateMachineController and then retrieve the inputs you’re interested in by their name. Specific inputs can be retrieved using findInput() or all inputs with the inputs property.
SMITrigger? _bump;void _onRiveInit(Artboard artboard) { // Get State Machine Controller for the state machine called "bumpy" final controller = StateMachineController.fromArtboard(artboard, 'bumpy'); artboard.addController(controller!); // Get a reference to the "bump" state machine input _bump = controller.getTriggerInput('bump');}
In the above snippet, the bump input is retrieved, which is an SMITrigger. This type of input has a fire() method to activate the trigger.
In the complete example above, every time the RiveAnimation is tapped, it fires the bump input trigger and the state machine reacts appropriately.
Note: There are two other state machine input types to be aware of as well: SMIBool and SMINumber. These both have a value property that can get and set the value.
// Example state machine input declarationsSMIBool _boolExampleInput;SMINumber _numberExampleInput;...// Extracting inputs from the StateMachineControllervoid _onRiveInit(Artboard artboard) { final controller = StateMachineController.fromArtboard(artboard, 'example'); artboard.addController(controller!); _boolExampleInput = controller.getBoolInput('exampleBool')!; _numberExampleInput = controller.getNumberInput('exampleNum')!;}...// Getting/setting state machine input valuesif (_boolExampleInput.value == false) { _boolExampleInput.value = true;}if (_numberExampleInput.value >= 100) { _numberExampleInput.value = 0;}
If you’d like to know which state a state machine is in, or when a state machine transitions to another state, you can provide a callback to StateMachineController. The callback has the name of the state machine and the name of the animation associated with the state transitioned to:
Just like with animation playback controls, setting input values for state machines goes through the RiveViewModel instantiated in the View class.
.setInput()
inputName (String) - Name of the input on a state machine to set a value for
value (Bool, Float, or Double) - value to set for the associated inputName
triggerInput()
inputName (String) - Name of the input on a state machine to trigger
// Example of a number inputstarsVM.setInput("Rating Changed", value: 5)// Example of a boolean inputtoggleVM.setInput("Switch Flipped", value: true)// Example of a trigger inputconfettiVM.triggerInput("Celebrate")
This runtime allows for delegates that can be set on the RiveViewModel. If provided, these delegate functions will be fired whenever a matching event is triggered to be able to hook into and listen for certain events in the Rive animation cycle.
Currently, there exist the following delegates:
RivePlayerDelegate - Hook into animation and state machine lifecycle events
You can create your own delegate or mix in with the RiveViewModel, implementing as many protocols as are needed. Below is an example of how to customize a RiveViewModel’s implementation of the RivePlayerDelegate:
class SimpleAnimation: RiveViewModel { init() { let model = RiveModel(fileName: "truck_v7", stateMachineName: "Drive") super.init(model) } override func setView(rview view: RiveView) { super.setView(view) rview?.playerDelegate = self rview?.stateMachineDelegate = self } override func player(playedWithModel riveModel: RiveModel?) { if let stateMachineName = riveModel?.stateMachine?.name() {...} } override func player(pausedWithModel riveModel: RiveModel?) { if let stateMachineName = riveModel?.stateMachine?.name() {...} } override func player(stoppedWithModel riveModel: RiveModel?) { if let stateMachineName = riveModel?.stateMachine?.name() {...} } @objc func stateMachine(_ stateMachine: RiveStateMachineInstance, didChangeState stateName: String) { var stateMachineNames: [String] = [] var stateMachineStates: [String] = [] stateMachineNames.append(stateMachine.name()) stateMachineStates.append(stateName) ... }}
Just like other methods within the rive-android runtime, use the view to set values on a state machine input. In this case, there is no need to grab references to state machine input instances to set values.
There are 3 different methods to set input values or trigger inputs for number, boolean, and trigger inputs respectively:
// i.e Set input state on a number inputanimationView.setNumberState("Designer's Test", "Level", 0f)// i.e Set boolean state on a boolean inputanimationView.setBooleanState("Boolean test", "foo", true)// i.e Fire a trigger inputanimationView.fireState("Trigger test", "fireInput");
To listen for state changes, when creating a Listener to register on your animation view, you can add the following callback, where you’ll receive the name of the state machine, and the state it transitions to:
val listener = object : Listener { override fun notifyStateChanged(stateMachineName: String, stateName: String) { // Do something }}animationView.registerListener(listener)
You can control the inputs of Nested Artboards at runtime. These inputs are not on the main artboard but on a nested artboard. To set a nested input, you need to know the path where the input exists at an artboard level.
If you load the Menu artboard at runtime and want to set inputs on the nested artboard with the hierarchy name Volume Molecule, the path is Volume Molecule:
var artboard = riveFile?.artboard("Menu");artboard.setBooleanInput("someBooleanInput", value: true, path: "Volume Molecule");
Use the artboard’s unique hierarchy name, not the artboard’s name.
Do not include the name of the main artboard. In the example above, the path is Volume Molecule, not Menu/Volume Molecule.
Ensure the nested artboards are marked as exported in the editor to access them at runtime:
You can go as many nested artboards deep as needed. For example, the Volume Molecule artboard shown above has two nested artboards with the following unique hierarchy names:
Volume Component
FX Component
Once you go more than one nested artboard deep the path will be a / separated string of the unique hierarchy names.
If you load in the Menu artboard at runtime, and want to get/set an input on the FX Component artboard, the path will be Volume Molecule/FX Component:
var artboard = riveFile?.artboard("Menu");artboard.setNumberInput("volume", value: 90.0, path: "Volume Molecule/FX Component");
Do not use / in the name for your components, as that will break the search functionality at runtime.
To set the Volume input for the above example:
const rive = new Rive({...});...rive?.setNumberStateAtPath("volume", 80.0, "Volume Molecule/Volume Component");
// Load the Rive filefinal file = await RiveFile.asset('assets/your_rive_file.riv');// Create a Rive Artboard instancefinal artboard = file.artboardByName('ArtboardName')!.instance();// Create a state machine controllerfinal controller = StateMachineController.fromArtboard(artboard, 'StateMachineName');// Add the controller to the artboardartboard.addController(controller!);...// Get the nested input named 'volume' from the artboardfinal volumeInput = artboard.getNumberInput('volume', 'Volume Molecule/Volume Component')!;volumeInput.value = 80.0;
If your Rive file has Rive Listeners (Listeners) and you’ve configured your Rive instance with a state machine according to the steps outlined per runtime above, there is no additional configuration or options needed to enable the pointer events to be captured on the Rive instance. The event capturing is handled internally by the Rive widget/component.
However, if you are going about constructing your own render loop and using low-level APIs to drive Rive content, (i.e. Low-level API Usage) , you may need to set up event listeners manually to capture user interaction and pass feedback down to the state machine (i.e. see setup in JS).