Unity多物体混合动画、值变动画控制器

您所在的位置:网站首页 unity商业源码 Unity多物体混合动画、值变动画控制器

Unity多物体混合动画、值变动画控制器

2022-12-31 04:29| 来源: 网络整理| 查看: 265

Unity多物体混合动画、值变动画控制器 发表于2018-03-01 评论0 2.2k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学堂游戏程序行业精英群

711501594 在场景中有大量的物体,都按照同一频率在运动时,此时可以使用LinkageAnimation做优化,但LinkageAnimation应该被称作值变动画才更合适,因为他支持针对所有组件(包括自定义组件)的属性做值变动画,属性满足以下要求:该属性类型必须是被LinkageAnimation所识别的类型,目前有:Bool,Color,Float,Int,Quaternion,String,Vector2,Vector3,Vector4,Sprite,可以自行添加任意类型。该属性必须是可读可写属性(不包括字段)。该属性必须是实例属性(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、控制多个监听物体a.添加新的监听物体:①动画编辑窗口右上角 -> Add Target按钮;②鼠标右键 -> Add Target选项;b.删除监听物体:① 物体的可移动窗口右上角 -> ‘x’按钮;c.查找监听物体:①按住鼠标中间拖动视野;②动画编辑窗口右上角 -> Find Target按钮(查找由于拖动等原因消失在视野内的监听物体);3、监听物体的属性a.添加新的属性:① 物体的可移动窗口下方 -> Add Property按钮(可以添加任意组件的任意已知、可读、可写属性);b.删除属性:① 属性左边的‘x’按钮;源码解析使用反射提取目标组件的对应属性: if (GUI.Button(new Rect(5, h, _width - 10, 16), "Add Property")) { GenericMenu gm = new GenericMenu(); //获取所有组件 Component[] cps = lat.Target.GetComponents(); 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、使用关键帧制作动画a.添加新的关键帧:①动画编辑窗口右上角 -> Add Frame按钮;②鼠标右键 -> Add Frame选项;b.删除关键帧:① 选中某一关键帧 -> Delete Frame按钮;c.复制关键帧:① 选中某一关键帧 -> Clone Frame按钮;d.记录关键帧的值:① 选中某一关键帧 -> Get Value In Scene按钮(将当前所有监听物体的被监听属性值记录到当前选中的关键帧);e.提取关键帧的值:① 选中某一关键帧 -> Set Value To Scene按钮(将当前选中关键帧的值赋予到场景中所有监听物体的被监听属性中);源码解析每一个关键帧中都有属性值仓库,可以通过索引提取属性值或是存储属性值,核心代码也是使用反射: /// /// 获取目标属性值并记录到当前关键帧 /// 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 + "!"); } } } } } /// /// 设置当前关键帧数据至目标属性值 /// 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、控制动画a.播放动画: LinkageAnimation la; la.Playing = true; b.暂停动画: LinkageAnimation la; la.Playing = false; c.停止动画: LinkageAnimation la; la.Stop(); d.重新播放动画: LinkageAnimation la; la.RePlay(); e.添加帧回调:①属性面板 -> Add CallBack按钮(例:当动画执行到第一帧时会呼叫Translate函数);f.删除帧回调:①属性面板 -> CallBack List -> ‘x’按钮;源码解析针对被监听目标的组件和属性,我这里选择只将组件名称和属性名字做序列化,在运行时才会动态去获取组件和属性,如果获取失败,则这个动画无效,这样做的好处是降低了数据结构的耦合性、序列化的复杂度: /// /// 初始化运行时控件 /// 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(); } 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,所以这取决于具体的实现代码): /// /// 更新动画帧 /// 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的实现,其实很简单,很多类型可以直接调用官方的插值方法,如果要添加自定义的类型,这里必须要实现他的插值算法: /// /// 根据类型在两个属性间插值 /// 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; } 源码链接:https://github.com/SaiTingHu/LinkageAnimation来自:http://blog.csdn.net/qq992817263/article/details/78487996

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签:

Unity移动前端JavaScript场景GithubList

本文作者

feng 暂无简介 Mesh绘制交互网格 Unity基本介绍与使用 Unity使用RenderTexture实现实时阴影绘制 Unity 贴图压缩方法及对比 Unity3D AsssetBundle加载效率比较 GWB公众号 腾讯游戏学堂公众号


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3