索引

  • 前言
  • 示例
    • 1、4个Cube的联动动画
    • 2、UGUI Text文本动画
    • 3、UGUI Image图片动画
    • 4、物体消隐动画
  • 使用与解析
    • 1、挂载LinkageAnimation脚本至场景中
    • 2、控制多个监听物体
    • 3、监听物体的属性
      • 源码解析
    • 4、使用关键帧制作动画
      • 源码解析
    • 5、控制动画
      • 源码解析
    • 源码链接

前言

因为工作中有用到,所以我抽出空闲把之前的LinkageAnimation优化了一下,如果有类似的需求(比如场景中有大量的物体,都按照同一频率在运动),那么这个工具可能适合你,当然如果你的环境是2017,TimeLine会是一个更好的解决方案。

不过,LinkageAnimation应该被称作值变动画才更合适,因为他支持针对所有组件(包括自定义组件)的属性做值变动画,属性满足以下要求:

1、该属性类型必须是被LinkageAnimation所识别的类型,目前有:Bool,Color,Float,Int,Quaternion,String,Vector2,Vector3,Vector4,Sprite,可以自行添加任意类型。
2、该属性必须是可读可写属性(不包括字段)。
3、该属性必须是实例属性(Instance)。

只要是满足以上要求的属性,将他所属脚本挂在场景物体上,就可以监听该物体,通过关键帧动画操控其值。

示例

1、4个Cube的联动动画

动画帧面板:(控制Transform组件的localRotation属性)

效果图:

2、UGUI Text文本动画

动画帧面板:(控制Text组件的text属性、fontSize属性)

效果图:

3、UGUI Image图片动画

动画帧面板:(控制Image组件的sprite属性)

效果图:

4、物体消隐动画

动画帧面板:(控制MeshRenderer组件的enabled属性)

效果图:

使用与解析

1、挂载LinkageAnimation脚本至场景中


一个LinkageAnimation实例对应一个动画组,点击Edit Animation按钮可以打开动画编辑界面,编辑整个动画组。

2、控制多个监听物体


1、添加新的监听物体:
① 动画编辑窗口右上角 -> Add Target按钮;
② 鼠标右键 -> Add Target选项;
2、删除监听物体:
① 物体的可移动窗口右上角 -> ‘x’按钮;
3、查找监听物体:
① 按住鼠标中间拖动视野;
② 动画编辑窗口右上角 -> Find Target按钮(查找由于拖动等原因消失在视野内的监听物体);

3、监听物体的属性


1、添加新的属性:
① 物体的可移动窗口下方 -> Add Property按钮(可以添加任意组件的任意已知、可读、可写属性);
2、删除属性:
① 属性左边的‘x’按钮;

源码解析

使用反射提取目标组件的对应属性:

if (GUI.Button(new Rect(5, h, _width - 10, 16), "Add Property")){GenericMenu gm = new GenericMenu();//获取所有组件Component[] cps = lat.Target.GetComponents<Component>();for (int m = 0; m < cps.Length; m++){//获取组件类型Type type = cps[m].GetType();//获取组件的所有属性PropertyInfo[] pis = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);for (int n = 0; n < pis.Length; n++){PropertyInfo pi = pis[n];string propertyType = pi.PropertyType.Name;//替换属性名称为标准名称propertyType = LinkageAnimationTool.ReplaceType(propertyType);//检测属性类型是否为合法类型bool allow = LinkageAnimationTool.IsAllowType(propertyType);if (allow){//属性为可读可写的属性if (pi.CanRead && pi.CanWrite){gm.AddItem(new GUIContent(type.Name + "/" + "[" + propertyType + "] " + pi.Name), false, delegate (){//添加属性成功LAProperty lap = new LAProperty(type.Name, propertyType, pi.Name);AddProperty(lat, lap);});}}}}gm.ShowAsContext();}

4、使用关键帧制作动画


1、添加新的关键帧:
① 动画编辑窗口右上角 -> Add Frame按钮;
② 鼠标右键 -> Add Frame选项;
2、删除关键帧:
① 选中某一关键帧 -> Delete Frame按钮;
3、复制关键帧:
① 选中某一关键帧 -> Clone Frame按钮;
4、记录关键帧的值:
① 选中某一关键帧 -> Get Value In Scene按钮(将当前所有监听物体的被监听属性值记录到当前选中的关键帧);
5、提取关键帧的值:
① 选中某一关键帧 -> Set Value To Scene按钮(将当前选中关键帧的值赋予到场景中所有监听物体的被监听属性中);

源码解析

每一个关键帧中都有属性值仓库,可以通过索引提取属性值或是存储属性值,核心代码也是使用反射:

/// <summary>/// 获取目标属性值并记录到当前关键帧/// </summary>private void GetPropertyValue(int index){for (int i = 0; i < _LA.Targets.Count; i++){LinkageAnimationTarget lat = _LA.Targets[i];if (lat.Target){LAFrame laf = lat.Frames[index];for (int j = 0; j < lat.Propertys.Count; j++){//通过名称获取组件Component cp = lat.Target.GetComponent(lat.Propertys[j].ComponentName);if (cp != null){//通过名称获取属性PropertyInfo pi = cp.GetType().GetProperty(lat.Propertys[j].PropertyName);if (pi != null){//获取属性值object value = pi.GetValue(cp, null);//重新记录到关键帧仓库laf.SetFrameValue(j, value);}else{Debug.LogWarning("目标物体 " + lat.Target.name + " 的组件 " + lat.Propertys[j].ComponentName + " 不存在属性 " + lat.Propertys[j].PropertyName + "!");}}else{Debug.LogWarning("目标物体 " + lat.Target.name + " 不存在组件 " + lat.Propertys[j].ComponentName + "!");}}}}}
/// <summary>/// 设置当前关键帧数据至目标属性值/// </summary>private void SetPropertyValue(int index){for (int i = 0; i < _LA.Targets.Count; i++){LinkageAnimationTarget lat = _LA.Targets[i];if (lat.Target){LAFrame laf = lat.Frames[index];for (int j = 0; j < lat.Propertys.Count; j++){//通过名称获取组件Component cp = lat.Target.GetComponent(lat.Propertys[j].ComponentName);if (cp != null){//通过名称获取属性PropertyInfo pi = cp.GetType().GetProperty(lat.Propertys[j].PropertyName);if (pi != null){//为属性设置值pi.SetValue(cp, laf.GetFrameValue(j), null);}else{Debug.LogWarning("目标物体 " + lat.Target.name + " 的组件 " + lat.Propertys[j].ComponentName + " 不存在属性 " + lat.Propertys[j].PropertyName + "!");}}else{Debug.LogWarning("目标物体 " + lat.Target.name + " 不存在组件 " + lat.Propertys[j].ComponentName + "!");}}}}}

5、控制动画


1、播放动画:

        LinkageAnimation la;la.Playing = true;

2、暂停动画:

        LinkageAnimation la;la.Playing = false;

3、停止动画:

        LinkageAnimation la;la.Stop();

4、重新播放动画:

        LinkageAnimation la;la.RePlay();

5、添加帧回调:
① 属性面板 -> Add CallBack按钮(例:当动画执行到第一帧时会呼叫Translate函数);
6、删除帧回调:
① 属性面板 -> CallBack List -> ‘x’按钮;

源码解析

针对被监听目标的组件和属性,我这里选择只将组件名称和属性名字做序列化,在运行时才会动态去获取组件和属性,如果获取失败,则这个动画无效,这样做的好处是降低了数据结构的耦合性、序列化的复杂度:

    /// <summary>/// 初始化运行时控件/// </summary>private void InitComponent(){for (int i = 0; i < Targets.Count; i++){LinkageAnimationTarget lat = Targets[i];if (lat.Target){if (lat.PropertysRunTime == null){lat.PropertysRunTime = new List<LAPropertyRunTime>();}for (int j = 0; j < lat.Propertys.Count; j++){LAProperty lap = lat.Propertys[j];//获取组件Component cp = lat.Target.GetComponent(lap.ComponentName);//获取属性PropertyInfo pi = cp ? cp.GetType().GetProperty(lap.PropertyName) : null;//该属性动画是否有效bool valid = (cp != null && pi != null);LAPropertyRunTime laprt = new LAPropertyRunTime(valid, cp, pi);lat.PropertysRunTime.Add(laprt);}}}}

播放动画时,每种类型的属性都会采用线性插值算法进行播放(当然有些类型无法做到线性插值,比如bool,所以这取决于具体的实现代码):

    /// <summary>/// 更新动画帧/// </summary>private void UpdateFrame(LinkageAnimationTarget lat, int currentIndex, int nextIndex){if (lat.Target){LAFrame currentLAF = lat.Frames[currentIndex];LAFrame nextLAF = lat.Frames[nextIndex];for (int i = 0; i < lat.PropertysRunTime.Count; i++){//当前属性名LAProperty lap = lat.Propertys[i];//当前属性运行时实例LAPropertyRunTime laprt = lat.PropertysRunTime[i];//属性动画有效if (laprt.IsValid){//根据播放位置进行插值object value = LinkageAnimationTool.Lerp(currentLAF.GetFrameValue(i), nextLAF.GetFrameValue(i), lap.PropertyType, _playLocation);//重新设置属性值laprt.PropertyValue.SetValue(laprt.PropertyComponent, value, null);}}}}

关于插值方法Lerp的实现,其实很简单,很多类型可以直接调用官方的插值方法,如果要添加自定义的类型,这里必须要实现他的插值算法:

    /// <summary>/// 根据类型在两个属性间插值/// </summary>public static object Lerp(object value1, object value2, string type, float location){object value;switch (type){case "Bool":value = location < 0.5f ? (bool)value1 : (bool)value2;break;case "Color":value = Color.Lerp((Color)value1, (Color)value2, location);break;case "Float":float f1 = (float)value1;float f2 = (float)value2;value = f1 + (f2 - f1) * location;break;case "Int":int i1 = (int)value1;int i2 = (int)value2;value = (int)(i1 + (i2 - i1) * location);break;case "Quaternion":value = Quaternion.Lerp((Quaternion)value1, (Quaternion)value2, location);break;case "String":string s1 = (string)value1;string s2 = (string)value2;int length = (int)(s1.Length + (s2.Length - s1.Length) * location);value = s1.Length >= s2.Length ? s1.Substring(0, length) : s2.Substring(0, length);break;case "Vector2":value = Vector2.Lerp((Vector2)value1, (Vector2)value2, location);break;case "Vector3":value = Vector3.Lerp((Vector3)value1, (Vector3)value2, location);break;case "Vector4":value = Vector4.Lerp((Vector4)value1, (Vector4)value2, location);break;case "Sprite":value = location < 0.5f ? (Sprite)value1 : (Sprite)value2;break;default:value = null;break;}return value;}

源码链接

[获取源码]

Unity 多物体混合动画、值变动画控制器相关推荐

  1. Unity 让物体同时播放两种动画

    在用Unity开发工程中,会遇到需要让一个物体同时播放两种动画的情况,举个简单的例子: 一只小鸟,这只小鸟会拍翅膀,这只小鸟也会按一定的轨迹飞行.   实现这个需求有几种思路: 1. 用动画控制器控制 ...

  2. 【Flutter】Animation 动画 ( Flutter 动画基本流程 | 创建动画控制器 | 创建动画 | 设置值监听器 | 设置状态监听器 | 布局中使用动画值 | 动画运行 )

    文章目录 一.创建动画控制器 二.创建动画 三.设置值监听器 四.设置状态监听器 五.布局中使用动画值 六.动画运行 七.完整代码示例 八.相关资源 Flutter 动画基本流程 : ① 创建动画控制 ...

  3. 【Flutter】Animation 动画 ( AnimatedBuilder 动画使用流程 | 创建动画控制器 | 创建动画 | 创建动画作用的组件 | 关联动画与组件 | 动画执行 )

    文章目录 ◯.AnimatedBuilder 引入 一.创建动画控制器 二.创建动画 三.创建动画作用的组件 四.创建 AnimatedBuilder 关联动画与组件 五.动画运行 六.完整代码示例 ...

  4. 【Flutter】Animation 动画 ( AnimatedWidget 动画使用流程 | 创建动画控制器 | 创建动画 | 创建 AnimatedWidget 动画组件 | 动画运行 )

    文章目录 ◯.AnimatedWidget 组件引入 一.创建 AnimatedWidget 动画组件 二.创建动画控制器 三.创建动画 四.动画运行 五.完整代码示例 六.相关资源 Animated ...

  5. Unity 多物体联动动画

    分享一个前几天写的插件,当时为了做多个物体的简单动画(只有移动.旋转.缩放之类的)同时运动效果而写的,说白了算不上什么高级的联动动画,就只是同时控制多个物体协调运动而已,像什么机械类的原理动画展示,类 ...

  6. Unity Mecanim动画系统 之 动画混合树(Blend Trees)的简单使用

    Unity Mecanim动画系统 之 动画混合树(Blend Trees)的简单使用 目录 Unity Mecanim动画系统 之 动画混合树(Blend Trees)的简单使用 一.简单介绍 二. ...

  7. Unity 2D教程 | 骨骼动画:创建动画

    转载自:2016-02-13 Unity官方平台 本教程主要讲解Unity引擎自带的2D骨骼动画工具,以及2D动画的基本概念.本篇会添加一些动画,如默认状态.跳动.坠落等. 基础动画理论 制作动画要牢 ...

  8. unity新动画系统之动画层和动画遮罩

    这一节来说说unity动画层layer和遮罩avatarMask: weight 权重,对应着这一层动画在所有层动画中所占的比例.以上图来说明,new layer中的weight为0,模型的动画效果就 ...

  9. Unity3D教程:动画融合、动画层、动画混合、附加动画、程序动画、动画重放和取样

    原帖地址:http://www.unitymanual.com/5323.html 动画脚本 Animation Scripting Unity's 动画系统允许你创建一个漂亮的动画蒙皮角色,动画系统 ...

最新文章

  1. Nginx + FastCgi + Spawn-fcgi + c 的架构
  2. 类.接口.多态.向上转型.向下转型
  3. spring整合atomikos实现分布式事务的方法示例_分布式事务中的XA和JTA
  4. Python 进程间通信 Queue / Pipe - Python零基础入门教程
  5. JAVA设置流中当前位置_java文件流的问题!急
  6. Bash shell中的位置参数$#,$*,$@,$0,$1,$2...及特殊参数$?,$-等的含义 1
  7. 探索Android FrameWork底层开发视频全套
  8. BCompare文件对比软件使用总结
  9. HTML+CSS(PC端+移动端)
  10. php程序员自我描述_php程序员自我评价简历范文
  11. 代码审计jizhiCMS 后台getshell
  12. 图片怎么改成jpg格式
  13. Centos7重置用户密码
  14. 博客整理——K米测评
  15. PHP支付宝转账到支付宝账号
  16. 基于物联网的智慧农业监测系统(前端界面有web端和微信小程序端)
  17. 《安全评估报告》7条回答范例
  18. [CTSC2010]珠宝商(点分治+根号分治+后缀自动机)
  19. 手机共享笔记本wifi热点
  20. pymysql获取要查询的字段名(列名)

热门文章

  1. 【wxPython】wxPython获取系统字体
  2. 让星星⭐月亮告诉你,设计模式入门
  3. 井通科技SWTC三个方面:合规,应用和社区
  4. [附源码]计算机毕业设计JAVA网上花店系统
  5. 批处理文件的@echo off
  6. 面试官:谈谈Spring中用到了哪些设计模式?
  7. BOSE耳机降级教程 适用QC30 QC35 QC35Ⅱ Noise Cancelling Headphones 700等多个型号(原方法有反馈可以使用 具体没测试 可以自行测试一下)...
  8. HBuildX 打包内存溢出问题
  9. UE4 无网环境下(禁用网络、网线断了等)情况下如何使用像素流送(pixelstreaming)
  10. Linux常见命令详解