Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/effects-core/src/animation/animation-clip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { EffectsObject } from '../effects-object';
import type { ValueGetter, Vector3Curve, BezierCurve, ColorCurve } from '../math';
import { createValueGetter } from '../math';
import type { VFXItem } from '../vfx-item';
import { NotifyEvent, type AnimationEventInfo } from './animation-events';

export interface AnimationCurve {
path: string,
Expand Down Expand Up @@ -51,6 +52,8 @@ export class AnimationClip extends EffectsObject {
floatCurves: FloatAnimationCurve[] = [];
colorCurves: ColorAnimationCurve[] = [];

events: AnimationEventInfo[] = [];

sampleAnimation (vfxItem: VFXItem, time: number) {
const life = clamp(time, 0, this.duration);

Expand Down Expand Up @@ -182,6 +185,23 @@ export class AnimationClip extends EffectsObject {
} else {
this.duration = keyFramesDuration;
}

// TODO: Update spec.
//@ts-expect-error
if (data.events) {
//@ts-expect-error
for (const eventData of data.events) {
const event: AnimationEventInfo = {
name: eventData.name,
startTime: eventData.startTime,
duration: eventData.duration ?? 0,
event: new NotifyEvent(),
clip: this,
};

this.events.push(event);
}
}
}

private findTarget (vfxItem: VFXItem, path: string) {
Expand Down
39 changes: 39 additions & 0 deletions packages/effects-core/src/animation/animation-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { AnimationClip } from '.';
import type { VFXItem } from '../vfx-item';

export interface AnimationEventReference {
event: AnimationEventInfo,
currentTime: number,
deltaTime: number,
}

export interface AnimationEventInfo {
name: string,
startTime: number,
duration: number,
event: AnimationEvent,
clip: AnimationClip,
}

export interface AnimationEventData {
typeName: string,
}

export interface AnimationEventInfoData {
name: string,
startTime: number,
duration?: number,
eventData?: AnimationEventData,
}

export class AnimationEvent {
onEvent (item: VFXItem, animation: AnimationClip, eventReference: AnimationEventReference): void {
// Override
}
}

export class NotifyEvent extends AnimationEvent {
override onEvent (item: VFXItem, animation: AnimationClip, eventReference: AnimationEventReference): void {
item.emit('animationevent', eventReference);
}
}
9 changes: 9 additions & 0 deletions packages/effects-core/src/components/animator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ export class Animator extends Component {
animatedObject.target.onApplyAnimationProperties();
}
}

// Trigger animation events
//-------------------------------------------------------------------------
for (const eventReference of this.graphInstance.activeEvents) {
const eventInfo = eventReference.event;
const event = eventInfo.event;

event.onEvent(this.item, eventInfo.clip, eventReference);
}
}

override fromData (data: spec.AnimatorData): void {
Expand Down
7 changes: 6 additions & 1 deletion packages/effects-core/src/composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,11 @@ export class Composition extends EventEmitter<CompositionEvent<Composition>> imp
}
this.rootComposition = this.rootItem.getComponent(CompositionComponent);

// Bind animation event
this.rootItem.on('animationevent', eventData => {
this.emit('animationevent', eventData);
});

this.width = width;
this.height = height;
this.renderOrder = baseRenderOrder;
Expand Down Expand Up @@ -575,7 +580,7 @@ export class Composition extends EventEmitter<CompositionEvent<Composition>> imp
* @param classConstructor - 要获取的组件类型
* @returns 查询结果中符合类型的第一个组件
*/
getComponent<T extends Component> (classConstructor: Constructor<T>): T {
getComponent<T extends Component>(classConstructor: Constructor<T>): T {
return this.rootItem.getComponent(classConstructor);
}

Expand Down
9 changes: 9 additions & 0 deletions packages/effects-core/src/events/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { AnimationEventReference } from '../animation/animation-events';
import type { MessageItem } from '../composition';
import type { PointerEventData, Region } from '../plugins';

Expand Down Expand Up @@ -29,6 +30,10 @@ export type ItemEvent = PointerEvent & {
* 注意:仅对交互元素有效
*/
['message']: [message: Omit<MessageItem, 'compositionId'>],
/**
* 动画事件
*/
['animationevent']: [eventData: AnimationEventReference],
};

/**
Expand Down Expand Up @@ -74,4 +79,8 @@ export type CompositionEvent<C> = PointerEvent & {
* 用于在合成中跳转到指定时间
*/
['goto']: [gotoInfo: { time: number }],
/**
* 动画事件
*/
['animationevent']: [eventData: AnimationEventReference],
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { AnimationEventReference } from '../../animation/animation-events';
import type { GraphDataSet } from './graph-data-set';
import type { GraphNode, GraphNodeData } from './graph-node';
import type { Skeleton } from './skeleton';
Expand All @@ -13,6 +14,7 @@ export class GraphContext {
updateID = 0;
skeleton: Skeleton;
branchState = BranchState.Active;
activeEvents: AnimationEventReference[];

update (deltaTime: number) {
this.deltaTime = deltaTime;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { AnimationEventReference } from '../../animation/animation-events';
import type { VFXItem } from '../../vfx-item';
import type { AnimationGraphAsset } from './animation-graph-asset';
import { GraphContext, InstantiationContext } from './graph-context';
Expand All @@ -12,6 +13,7 @@ import { Skeleton } from './skeleton';
export class GraphInstance {
nodes: GraphNode[] = [];
skeleton: Skeleton;
activeEvents: AnimationEventReference[] = [];

private rootNode: PoseNode;
private context = new GraphContext();
Expand Down Expand Up @@ -59,6 +61,7 @@ export class GraphInstance {
// Create PoseResult
this.result = new PoseResult(this.skeleton);
this.context.skeleton = this.skeleton;
this.context.activeEvents = this.activeEvents;

// Instantiate graph nodes
const instantiationContext = new InstantiationContext();
Expand All @@ -76,6 +79,8 @@ export class GraphInstance {
}

evaluateGraph (deltaTime: number) {
this.activeEvents.length = 0;

this.context.update(deltaTime);

if (!this.rootNode.isInitialized()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { PoseResult } from '../pose-result';
import type { Skeleton } from '../skeleton';
import type { Pose } from '../pose';
import type { AnimationClip, AnimationCurve, FloatAnimationCurve, ColorAnimationCurve } from '../../../animation';
import type { AnimationEventReference } from 'packages/effects-core/src/animation/animation-events';

@nodeDataClass(spec.NodeDataType.AnimationClipNodeData)
export class AnimationClipNodeData extends GraphNodeData {
Expand Down Expand Up @@ -60,13 +61,48 @@ export class AnimationClipNode extends PoseNode {
}
}

// Sample Events
//-------------------------------------------------------------------------
this.sampleEvents(context);

const time = this.currentTime * this.duration;

this.animatable.getPose(time, result.pose);

return result;
}

private sampleEvents (context: GraphContext): void {
if (!this.animatable || this.animatable.events.length === 0) {
return;
}

const previousTimeInSeconds = this.previousTime * this.duration;
const currentTimeInSeconds = this.currentTime * this.duration;

for (const eventReference of this.animatable.events) {
const eventTime = eventReference.event.startTime;

// Check if event falls within the current time interval
let shouldTrigger = false;

// Looping: handle wrap-around at 1.0
if (this.previousTime <= this.currentTime) {
// Normal case: no wrap-around
shouldTrigger = previousTimeInSeconds < eventTime && eventTime <= currentTimeInSeconds;
} else {
// Wrap-around case: previousTime > currentTime (e.g., 0.9 -> 0.1)
shouldTrigger = eventTime > previousTimeInSeconds || eventTime <= currentTimeInSeconds;
}

if (shouldTrigger) {
eventReference.currentTime = currentTimeInSeconds;
eventReference.deltaTime = context.deltaTime;
context.activeEvents.push(eventReference);
}
}
}

protected override initializeInternal (context: GraphContext): void {
super.initializeInternal(context);
this.duration = this.animation?.duration ?? 0;
Expand Down Expand Up @@ -102,8 +138,12 @@ export interface ColorCurveInfo {
}

export class Animatable {
private transformCurveInfos: TransformCurveInfo[] = [];
/**
* @internal
*/
events: AnimationEventReference[] = [];

private transformCurveInfos: TransformCurveInfo[] = [];
private floatCurveInfos: FloatCurveInfo[] = [];
private colorCurveInfos: ColorCurveInfo[] = [];

Expand All @@ -129,6 +169,14 @@ export class Animatable {
for (const curve of animationClip.colorCurves) {
this.addColorCurveInfo(curve);
}

for (const eventInfo of this.animationClip.events) {
this.events.push({
event: eventInfo,
currentTime: 0,
deltaTime: 0,
});
}
}

getPose (time: number, outPose: Pose) {
Expand Down