本文将为您详细介绍在AI实时互动Demo中,智能体形象动画的实现方式。
智能体形象动画
在语音通话场景中,您可以定制智能体形象,使其根据情绪标签实现情感切换。相比普通智能体,具备情绪输出能力的智能体能更自然地与用户互动,通过语气、表情和动作增强情感共鸣,提升交互的趣味性与真实感。它还能精准感知用户情绪,提供更贴合需求的服务,适用于客服、教育、娱乐等多种场景。
官方智能体形象动画实现介绍
实现介绍
在AI实时互动官网Demo中,使用After Effects来设计智能体形象动画并导出为Lottie格式的资源文件,客户端集成Lottie组件库进行动画的播放与控制,Lottie详情,请参见LottieFiles。
官方Demo在After Effects中设计动画后,按照头部、手部、眼睛、捂眼睛进行模块拆分,并导出对应模块的动画资源,本方案使用到的所有的Lottie资源如下:
├── Avatar // 形象动画资源根目录
│ ├── bg.png // 背景图片
│ ├── Enter // 开场动画(通话接通中)
│ ├── Head // 头部动画
│ ├── Hand // 手部动画
│ ├── CoveringEyes // 手部捂眼睛动画(智能体讲话被打断效果)
│ ├── EyeEmotions // 眼睛动画
│ ├── Interrupting // 眼睛-被打断动画
│ ├── Listening // 眼睛-聆听中动画
│ ├── Thinking // 眼睛-思考中动画
│ ├── Speaking // 眼睛-讲话中动画
│ ├── Happy // 眼睛-讲话-开心动画
│ ├── Sad // 眼睛-讲话-伤心动画
实现参考
官网Demo的形象动画源码,参考如下:
平台 | 场景 | 文件名 | 类名 |
Android | 开场动画 |
| |
通话过程动画 |
| ||
iOS | 开场动画 |
| |
通话过程动画 |
| ||
Web | 开场动画 |
| |
通话过程动画 |
实现说明
在官方Demo的展示中,智能体的头部(点头动作)与手部循环播放动画。当触发智能体的状态变化或情绪变化时,眼睛动画将循环执行;在智能体被打断时,手部捂眼睛的动画将单次执行,执行后返回至原有状态。此外,以下几个逻辑需特别处理:
头部上下循环动画时,眼睛也需要跟随上下移动,可以在Lottie中监听头部动画的播放进度来估算偏移的位置,并实时更新眼睛动画的位移。
// 监听头部动画的更新事件 headAnimator.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float animatedFraction = animation.getAnimatedFraction(); float speed = headAnimator.getSpeed(); float progress; if (speed < 0) { progress = 1 - animatedFraction; } else { progress = animatedFraction; } // 通过播放进度来估算偏移位移 float y = eyeOffset * (progress - 1.0f); // 更新眼睛动画的位移 eyeAnimatorContainerView.setTranslationY(y); } });
定义眼睛动画类型,并处理动画的切换逻辑:
出错时最终状态,不能切换
如果下一个是打断状态,不能打断
当前打断状态,不能切换,需要缓存起来,等打断动画执行完成
当前是带表情状态讲话时,不能切换为自然状态讲话
下一个是打断状态或错误状态,需要等当前执行完成
public enum EyeAnimator { None,// 非动画场景 Start,// 智能体启动 Error,// 智能体出错 Interrupting,// 智能体被打断 Thinking,// 智能体思考中,或者加载中 Listening,// 智能体聆听中 Speaking,// 智能体讲话中(自然形态) HappySpeaking,// 智能体讲话中(开心形态) SadSpeaking// 智能体讲话中(伤心形态) } public void setEyeAnimatorType(EyeAnimator type) { Log.d("Animator", "Will Set Animator Type Curr: " + currEyeAnimatorType + " To: " + type); if (type == EyeAnimator.None) { return; } if (currEyeAnimatorType == type) { return; } if (currEyeAnimatorType == EyeAnimator.Error) { // 出错时最终状态,不能切换 return; } if (nextEyeAnimatorType == EyeAnimator.Interrupting || nextEyeAnimatorType == EyeAnimator.Error) { // 如果下一个是打断状态,不能打断 return; } if (currEyeAnimatorType == EyeAnimator.Interrupting) { // 当前打断状态,不能打断,需要等继续执行完成 if (type == EyeAnimator.Speaking && nextEyeAnimatorType.ordinal() > type.ordinal()) { // 下一个是带表情状态讲话时,下一个不能切换为自然状态讲话 return; } nextEyeAnimatorType = type; return; } if (type == EyeAnimator.Speaking && currEyeAnimatorType.ordinal() > type.ordinal()) { // 当前是带表情状态讲话时,不能切换为自然状态讲话 return; } if (type == EyeAnimator.Interrupting || type == EyeAnimator.Error) { // 下一个是打断状态或错误状态,需要等当前执行完成 nextEyeAnimatorType = type; return; } Log.d("Animator", "Set Animator Type Next: " + type + " Curr: " + currEyeAnimatorType); startNextEyeAnimator(type); }
触发智能体打断事件时,捂眼睛动画需要与头部动画同步,在头部动画第一帧播放时同时开始执行(progres=0时),且在执行眼睛动画不能被切换到其他动画。
// 监听头部动画播放结束事件 headAnimator.addAnimatorListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { headAnimator.reverseAnimationSpeed(); headAnimator.playAnimation(); // 头部动画需要循环播放,所以这里需要重新播放 if (nextEyeAnimatorType != EyeAnimator.None) { // 记录下来的需要同步的动画类型 startNextEyeAnimator(nextEyeAnimatorType); // 同步执行动画其他动画(例如:捂眼睛) if (currEyeAnimatorType == EyeAnimator.Interrupting) { nextEyeAnimatorType = EyeAnimator.Listening; } else { nextEyeAnimatorType = EyeAnimator.None; } } } });
由于形象动画比较难使用矢量方式进行设计,会使用较多切片,那么在加载Lottie时会占用较多的内存。为了减少内存消耗,在导出Lottie资源时可以仅导出单向动画,在播放时使用
AutoReverse
模式。
如何实现自定义智能体形象动画
方式一
按照官方Demo的方式,使用官方Demo的控制逻辑,您只需通过After Effects设计与导出自己的Lottie资源,然后进行替换官方的Lottie资源(资源路径:AUIAICall/src/main/assets/Avatar
),完全复用官方Demo的动画播放与控制源码。
方式二
您自行在After Effects中设计动画,然后把需要根据状态变化的部位动画拆分出来(例如:聆听中、思考中、开心的讲话...),参考官方Demo中的动画播放控制逻辑进行控制实现,您也可以在控制实现中加入特殊逻辑的处理。