首页 > 其他分享 >Unity 多物体混合动画、值变动画控制器

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

时间:2022-12-13 15:03:41浏览次数:62  
标签:动画 控制器 关键帧 int value Unity lat 属性


索引

  • ​​前言​​
  • ​​示例​​
  • ​​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属性)

Unity 多物体混合动画、值变动画控制器_关键帧

效果图:

Unity 多物体混合动画、值变动画控制器_关键帧_02

2、UGUI Text文本动画

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

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


效果图:

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

3、UGUI Image图片动画

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

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


效果图:

Unity 多物体混合动画、值变动画控制器_属性值_06

4、物体消隐动画

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

Unity 多物体混合动画、值变动画控制器_属性值_07


效果图:

Unity 多物体混合动画、值变动画控制器_属性值_08

使用与解析

1、挂载LinkageAnimation脚本至场景中

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


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

2、控制多个监听物体

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


1、添加新的监听物体:

① 动画编辑窗口右上角 -> Add Target按钮;

② 鼠标右键 -> Add Target选项;

2、删除监听物体:

① 物体的可移动窗口右上角 -> ‘x’按钮;

3、查找监听物体:

① 按住鼠标中间拖动视野;

② 动画编辑窗口右上角 -> Find Target按钮(查找由于拖动等原因消失在视野内的监听物体);

3、监听物体的属性

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


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、使用关键帧制作动画

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


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、控制动画

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


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;
}


标签:动画,控制器,关键帧,int,value,Unity,lat,属性
From: https://blog.51cto.com/u_15911199/5934150

相关文章

  • Unity实现在白板上绘画涂鸦
    前言有段时间没有更新博客了,不知道应该写些什么,太简单感觉没有记录的必要,太难自己都没能理解,不知道如何下手。回归初心,记录自己想记录的东西。需要实现一个白板绘画的功能,......
  • Unity 使用拖尾渲染器模拟简易的管道流体
    关于管道流体关于管道流体,最佳的解决方案肯定是UV动画无疑,在网上看过很多例子几乎都是这样的,毕竟用实时流体计算的话开销确实太大,用粒子系统的话又苦于难以表现出流体的粘稠......
  • Unity UGUI图文混排源码(三) -- 动态表情
    这里是根据图文混排源码(二)进一步修改的,其他链接也不贴了,就贴一个链接就好了,第一次看这文章的同学可以先去看看其他几篇文章1.首先来一个好消息,在最新版本的图文混排中,终于......
  • Unity 简易的UI背景昼夜轮替效果
    在UI背景上实现一个简易的有光影照射的昼夜轮替效果,往往比一个死板的UI背景看起来更加形象生动,比较传统的方式是多图轮流替换的序列帧动画,不过要达到整个UI背景大图的所有地......
  • 手动build unity3d的docker镜像
    手动buildunity3d的docker镜像参考资料docker官方文档:​​DockerDocumentation|DockerDocumentation​​unity3dlinux版的论坛链接,在这里能找到各个版本,以及需要安装......
  • Unity UGUI
    超详细的基础教程传送门:(持续更新中)UnityUGUI的教程好少,幸亏找到一个UGUI的Demo,看了几个例子,以下是一些简单的学习笔记:1.导入UI图片资源2.设置参数:            ......
  • SPINE精品男神帅哥 古代 现代造型 2D动画师精品素材待机源文件 58套
    SPINE精品男神帅哥古代现代造型2D动画师精品素材待机源文件58套文件大小:203MB文件格式:.json、.atlas、.spine、Gif、PNG文件数量:58组 ......
  • unity shaderlab Blend操作
    原文链接: ​​http://www.tiankengblog.com/?p=84​​Blend混合操作是作用于在所有计算之后,是Shader渲染的最后一步,进行Blend操作后就可以显示在屏幕上。shader的计算步骤......
  • 【Unity】超级坦克大战(二)游戏流程
    更新日期:2020年7月9日。项目源码:在终章发布索引​​本章最佳实践​​​​正式开始​​​​登录流程​​​​准备流程​​​​关卡选择流程​​​​闯关流程​​​​启用所......
  • Unity UGUI图文混排(五) -- 一张图集对应多个Text
    继上一篇说的更新了一张图集对应多个Text的功能,为了节省资源嘛这里,但是也没有舍弃之前的一个Text一个图集,因为我感觉应该两个都有用,于是我重新写了一个脚本1.其实大体跟前面......