At Mascot Engine, we build interactive character systems that connect animation, product behaviour, and application data.
In this tutorial, we will use a sample Rive file named mascotengine.riv to demonstrate how Rive Data Binding and ViewModels can control an interactive character.
The sample Mascot Engine character will be able to:
- Change emotions
- Enter listening, thinking, and talking states
- Follow pointer or touch positions
- Display dynamic text
- Change its colors at runtime
- React to success and error events
- Connect to React or Next.js application logic
Sample Mascot Engine file structure
Our sample mascotengine.riv file uses the following structure:
mascotengine.riv
├── Artboard: Mascot Engine
├── State Machine: Mascot Engine State Machine
└── ViewModel: MascotEngineViewModel
├── displayName
├── emotion
├── activity
├── isTalking
├── lookX
├── lookY
├── accentColor
├── celebrate
└── showError
This ViewModel becomes the public interface between the Mascot Engine character and the application.
The internal character rig, animation timelines, transitions, and blend states can change without requiring the developer to rewrite the integration, provided the ViewModel contract remains stable.
Add the Mascot Engine file
Place the sample file inside the application’s public directory:
public/
└── mascotengine.riv
The file can then be loaded using:
src: '/mascotengine.riv'
React implementation
'use client';
import {
useRive,
useViewModel,
useViewModelInstance,
useViewModelInstanceBoolean,
useViewModelInstanceColor,
useViewModelInstanceEnum,
useViewModelInstanceNumber,
useViewModelInstanceString,
useViewModelInstanceTrigger,
} from '@rive-app/react-webgl2';
export default function MascotEngineCharacter() {
const { rive, RiveComponent } = useRive({
src: '/mascotengine.riv',
artboard: 'Mascot Engine',
stateMachines: 'Mascot Engine State Machine',
autoplay: true,
autoBind: false,
});
const mascotViewModel = useViewModel(rive, {
name: 'MascotEngineViewModel',
});
const mascotInstance = useViewModelInstance(
mascotViewModel,
{
name: 'MascotEngineDefault',
rive,
}
);
const {
value: displayName,
setValue: setDisplayName,
} = useViewModelInstanceString(
'displayName',
mascotInstance
);
const {
value: emotion,
setValue: setEmotion,
} = useViewModelInstanceEnum(
'emotion',
mascotInstance
);
const {
setValue: setIsTalking,
} = useViewModelInstanceBoolean(
'isTalking',
mascotInstance
);
const {
setValue: setLookX,
} = useViewModelInstanceNumber(
'lookX',
mascotInstance
);
const {
setValue: setLookY,
} = useViewModelInstanceNumber(
'lookY',
mascotInstance
);
const {
setRgb: setAccentColor,
} = useViewModelInstanceColor(
'accentColor',
mascotInstance
);
const {
trigger: celebrate,
} = useViewModelInstanceTrigger(
'celebrate',
mascotInstance
);
return (
<section>
<div
style={{
width: '100%',
maxWidth: 600,
height: 500,
margin: '0 auto',
}}
>
<RiveComponent />
</div>
<p>
Character: {displayName ?? 'Mascot Engine'}
</p>
<p>
Emotion: {emotion ?? 'Loading'}
</p>
<button
type="button"
onClick={() => setDisplayName?.('Nova')}
>
Change Name
</button>
<button
type="button"
onClick={() => setEmotion?.('thinking')}
>
Think
</button>
<button
type="button"
onClick={() => setIsTalking?.(true)}
>
Start Talking
</button>
<button
type="button"
onClick={() => {
setLookX?.(0.7);
setLookY?.(-0.2);
}}
>
Look Right
</button>
<button
type="button"
onClick={() => setAccentColor?.(52, 120, 246)}
>
Change Theme
</button>
<button
type="button"
onClick={() => celebrate?.()}
>
Celebrate
</button>
</section>
);
}
How Mascot Engine structures interactive characters
At Mascot Engine, we treat the ViewModel as a stable contract between the character system and the application.
The application controls clear product-level properties such as:
activity = thinking
emotion = happy
isTalking = true
lookX = 0.5
lookY = -0.2
The developer does not need to control individual bones, timelines, mouth shapes, eye layers, or internal animation transitions.
Those details remain inside mascotengine.riv.
This makes the character easier to:
- Integrate
- Test
- Update
- Reuse
- Hand over to developers
- Connect to AI or audio systems
- Support across multiple platforms
Building an AI assistant with Mascot Engine
The sample Mascot Engine character can respond to an AI application lifecycle:
User starts recording
→ activity = listening
Application processes the request
→ activity = thinking
Audio response begins
→ activity = talking
Audio response finishes
→ activity = idle
Example:
function setMascotActivity(
activity: 'idle' | 'listening' | 'thinking' | 'talking'
) {
setActivity?.(activity);
if (activity === 'thinking') {
setEmotion?.('thinking');
}
if (activity === 'talking') {
setEmotion?.('happy');
}
if (activity === 'idle') {
setEmotion?.('neutral');
}
}
This allows mascotengine.riv to function as a complete interactive character system instead of a collection of disconnected animation clips.
Need a production-ready interactive character?
Mascot Engine creates interactive character systems for:
- AI products
- SaaS platforms
- Educational applications
- Mobile apps
- React and Next.js products
- Flutter and React Native applications
- Onboarding experiences
- Virtual assistants and support agents
Our work can include:
- Character preparation and rigging
- Emotional animation systems
- Interactive State Machines
- Data Binding and ViewModels
- Listening, thinking, and talking states
- Pointer, touch, and gaze controls
- Lip-sync and viseme systems
- Runtime customization
- Developer-ready documentation
- Web and mobile integration preparation
We do not deliver only a looping character animation.
Mascot Engine builds a reusable character system that can respond to real application logic.
Start an interactive mascot project
About the author
Praneeth Kawya Thathsara is a Rive Animator and Interactive Motion Designer specializing in production-ready character systems for AI, SaaS, web, and mobile products.
He is the founder of Mascot Engine.
Top comments (0)