Unity 多物体混合动画、值变动画控制器
来源:互联网 发布:做pv的软件 编辑:程序博客网 时间:2024/05/17 13:42
前言
因为工作中有用到,所以我抽出空闲把之前的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; }
源码链接
github源码链接:https://github.com/SaiTingHu/LinkageAnimation
一起学习和进步
- Unity 多物体混合动画、值变动画控制器
- Unity 多物体联动动画
- 多物体动画
- Unity 2D动画控制器详解
- unity之动画控制器组件基本概念
- Unity 让物体同时播放两种动画
- Unity-动画
- unity动画
- js动画2 多物体运动
- 多物体运动动画js脚本
- javascript动画特效 多物体运动 源代码
- Unity游戏开发之动画播放与动画控制器 Animator Controller
- Unity Mecanim动画的实现(十):动画混合树、子状态机和状态行为
- Unity Mecanim动画的实现(十):动画混合树、子状态机和状态行为
- iOS - 多控制器切换(带滑动动画)
- qml动画控制器AnimationController
- 视图控制器切换动画
- 控制器转场动画
- js设置以及取得cookie
- 求1+2!+3!+...20!的两种方法
- Python容器、迭代器、生成器
- 保存excel下栽
- 浪潮涌动,扎堆上市后又扎堆退出,北京男子在金融平台踩雷10次
- Unity 多物体混合动画、值变动画控制器
- Apache Camel 基本概念理解
- Python学习-入门
- Win8.1+VS2013+WDK8.1+VirtualBox or VMware 驱动开发环境配置
- [分享]觉得不错的网站
- 一个功能强大的自定义SeekBar
- 网络传输之网络协议
- centos firewall
- Java删除文件夹以及文件