This document describes the communication API between the Rendera singleton addon and Rendera-Control instance-based addons in Construct 3. The API enables Rendera-Control instances to receive animation events from their associated 3D models.
The animation event system fires callbacks for the following animation states:
enum AnimationEventType {
START = 'start', // Animation begins playing
LOOP = 'loop', // Animation loops back to beginning
COMPLETE = 'complete', // Non-looping animation finishes
FRAME = 'frame', // Every animation frame update
STOP = 'stop' // Animation stopped by user
}Each event provides detailed information about the animation state:
interface AnimationEventData {
instanceId: number; // Unique instance ID
modelId: string; // Model identifier
animationName: string; // Name of the animation
eventType: AnimationEventType; // Type of event
currentTime: number; // Current animation time in seconds
duration: number; // Total animation duration
progress: number; // Progress (0-1)
}Register a callback function to receive animation events for a specific instance:
rendera.registerAnimationCallback(instanceId, callback)Parameters:
instanceId(number): The unique ID of the Rendera-Control instancecallback(function): Function to receive animation events
Remove the callback for a specific instance:
rendera.unregisterAnimationCallback(instanceId)Parameters:
instanceId(number): The unique ID of the Rendera-Control instance
// In your Rendera-Control instance onCreate
_onCreate() {
// Get the Rendera singleton
const rendera = this.runtime.objects.Rendera.getFirstInstance();
if (!rendera) {
console.warn('Rendera singleton not found');
return;
}
// Store reference for later use
this._rendera = rendera;
// Register callback for this instance
rendera.registerAnimationCallback(this.instanceId, (eventData) => {
this._handleAnimationEvent(eventData);
});
}
// Handle animation events
_handleAnimationEvent(eventData) {
// Store event data for conditions
this.lastAnimationEvent = eventData;
// Fire appropriate Construct 3 triggers based on event type
switch(eventData.eventType) {
case 'start':
this.runtime.trigger(this.conditions.OnAnimationStart, this);
break;
case 'loop':
this.runtime.trigger(this.conditions.OnAnimationLoop, this);
break;
case 'complete':
this.runtime.trigger(this.conditions.OnAnimationComplete, this);
break;
case 'frame':
// Update progress for expressions
this.animationProgress = eventData.progress;
// Optionally trigger frame event (may be high frequency)
// this.runtime.trigger(this.conditions.OnAnimationFrame, this);
break;
case 'stop':
this.runtime.trigger(this.conditions.OnAnimationStop, this);
break;
}
}
// Clean up on destroy
_onDestroy() {
if (this._rendera) {
this._rendera.unregisterAnimationCallback(this.instanceId);
}
}// In your conditions.js
Cnds.OnAnimationStart = function() {
// Trigger already fired by callback
return true;
};
Cnds.OnAnimationLoop = function() {
return true;
};
Cnds.OnAnimationComplete = function() {
return true;
};
// Add condition to check specific animation
Cnds.IsAnimationPlaying = function(animationName) {
return this.lastAnimationEvent &&
this.lastAnimationEvent.animationName === animationName;
};// In your expressions.js
Exps.AnimationProgress = function(ret) {
ret.set_float(this.animationProgress || 0);
};
Exps.CurrentAnimationName = function(ret) {
ret.set_string(this.lastAnimationEvent?.animationName || "");
};
Exps.AnimationTime = function(ret) {
ret.set_float(this.lastAnimationEvent?.currentTime || 0);
};The FRAME event fires every animation frame and can impact performance. Consider filtering it:
_handleAnimationEvent(eventData) {
// Skip frame events if not needed
if (eventData.eventType === 'frame') {
// Only update internal state, don't fire triggers
this.animationProgress = eventData.progress;
return;
}
// Handle other events...
}If you need frame-by-frame updates, consider batching:
_handleAnimationEvent(eventData) {
if (eventData.eventType === 'frame') {
// Accumulate frame data
this._frameCount = (this._frameCount || 0) + 1;
// Process every Nth frame
if (this._frameCount % 10 === 0) {
this.runtime.trigger(this.conditions.OnAnimationUpdate, this);
}
return;
}
// Handle other events...
}Rendera-Control Instance
|
| registerAnimationCallback()
v
Rendera Singleton (InstanceManager)
|
v
AnimationController
|
| Animation plays
v
Event Detected
|
| fireAnimationEvent()
v
Callback Invoked
|
v
Rendera-Control Instance
|
| runtime.trigger()
v
C3 Event Sheet
- Always unregister callbacks in
_onDestroy()to prevent memory leaks - Filter events you don't need, especially
FRAMEevents - Store event data for use in conditions and expressions
- Check for Rendera singleton before registering callbacks
- Use instance IDs consistently between Rendera and Rendera-Control
To debug animation events, add logging to your callback:
_handleAnimationEvent(eventData) {
console.log(`[Rendera-Control ${this.instanceId}] Animation event:`, {
type: eventData.eventType,
animation: eventData.animationName,
progress: (eventData.progress * 100).toFixed(1) + '%',
time: eventData.currentTime.toFixed(2) + 's'
});
// Handle event...
}If using TypeScript in your Rendera-Control addon:
interface AnimationEventData {
instanceId: number;
modelId: string;
animationName: string;
eventType: 'start' | 'loop' | 'complete' | 'frame' | 'stop';
currentTime: number;
duration: number;
progress: number;
}
type AnimationEventCallback = (data: AnimationEventData) => void;
interface IRenderaInstance {
registerAnimationCallback(instanceId: number, callback: AnimationEventCallback): void;
unregisterAnimationCallback(instanceId: number): void;
}- Only animation events are supported (not transform or node state changes)
- One callback per instance (not per event type)
- Callbacks are not persisted across save/load
- Events are only fired for playing animations
Potential future additions to the API:
- Transform change events (position, rotation, scale)
- Node visibility events
- Model loading events
- Custom user-defined events