源码已上传至github,并持续更新,链接请看底部。(本帖跟随github持续更新)

网格顶点动画(变形动画)是针对于物体的形状可以随意变换并记录为关键帧的动画,虽然模型的顶点数据还是应该交给GPU绘制才是正道,CPU刷新模型顶点始终是个吃力不讨好的事(不过我好像至始至终就是在干吃力不讨好的事来着),所以变形动画还是别用到过于复杂的模型之上,毕竟到头来吃力的只会是你的CPU,不过一些简单的模型倒不用担心,像什么旗帜飘扬什么的,不用打开3DMAX(前提是得会用这东西K动画),不用局限于Unity的animator系统(毕竟给你一个做得像旗帜的cube,你能用animator调出一个飘动的动画?),只需简单的几步拖拽便可以K出一个动画,并且可以将动画信息保存为本地文件,实现多项目间复用,同时,顶点数相同的模型也可以复用动画。

传送门(变形动画状态机变形动画骨骼搭建

变形动画完全不同于Unity Animator系统的机制,事实上跟它半毛钱关系都没有,所以这两种动画在同一物体上是可以共同存在的,事实上,众所周知,Animator的关键帧只会记录物体的transform组件的position、rotation以及scale的数值变化(当然其他组件的部分属性它也是可以记录的,比如Image的Color),其余的很多属性改变都不会被它视为有另一关键帧产生,而变形动画只会记录模型的顶点数据作为关键帧,完全不会改动transform组件的属性,所以这两种动画完全可以共存。

好了,进入正题,我以给一个cube调节一个变形动画为例子讲解一下整个流程及实现的思路。

第一步:


为cube添加我们的变形动画编辑器组件(MeshAnimation)

添加动画帧:以Scene场景中当前物体的状态信息保存为一个新的关键帧,这里的代码主要是记录每个顶点的位置

/// <summary>
/// 添加动画帧
/// </summary>
public void AddFrame()
{
Vector3[] vertices = new Vector3[_Vertices.Length];
for (int i = 0; i < _Vertices.Length; i++)
{
vertices[i] = _Vertices[i].transform.position;
}
_VerticesAnimationArray.Add(vertices);
}

我们最好先在cube的初始状态就添加一个动画帧,以便于播放动画时它会从初始状态开始



第二步:


现在我们多添加几个关键帧,目前每帧的状态都是保持在初始形态



第三步:


我们的第一帧就让他保持初始状态,现在选中第二帧,同时在场景中调节cube的形态,当你觉得满意的时候,点击apply应用就可以将物体的状态应用到当前的第二帧数据,当然如果这一关键帧不想要了,点击delete删除即可

/// <summary>
/// 应用动画帧
/// </summary>
public void ApplyFrame()
{
//如果当前动画帧数据存在,则应用当前物体的各顶点数据至当前动画帧
if (_NowSelectFrame >= 0 && _NowSelectFrame < _VerticesAnimationArray.Count)
{
for (int i = 0; i < _Vertices.Length; i++)
{
_VerticesAnimationArray[_NowSelectFrame][i] = _Vertices[i].transform.position;
}
}
}
/// <summary>
/// 删除动画帧
/// </summary>
public void DeleteFrame()
{
//如果当前动画帧数据存在,则删除当前动画帧数据
if (_NowSelectFrame >= 0 && _NowSelectFrame < _VerticesAnimationArray.Count)
{
_VerticesAnimationArray.RemoveAt(_NowSelectFrame);
_NowSelectFrame = -1;
}
}

我们将cube调节成这个样子,然后点击apply应用关键帧



第四步:


选中第三个关键帧,再调到自己满意的形态,并再点击apply应用



第五步:


选中第四个关键帧,这里我们要让他有个缓冲的效果,也就是说跟第三帧的差距小一点

然后我们的第四帧就调成了这个怂样~



第六步:


第五帧我们就要让他发射出去(前几帧是收缩,蓄势,然后第五帧猛地弹出~~有没有一种发射炮弹的感觉~~),当然如果你想复制某一帧的话,只需选中这一帧,点击添加关键帧,最后面就会多出来与此帧相同的一帧,然后在此基础上调节下一帧更方便



第七步:


之后就是给他K几个反弹回来的缓冲关键帧,注意这里选中任意一帧场景中的cube就会变化到那一帧的形态(这种方式是仿Animator的),随意修改之后点击应用可以保存,不点击应用默认改动无效,所以修改之后,如果觉得满意,一定要点击apply应用,否则待你切换到其他帧时,这一帧改动的数据就将丢失

 /// <summary>
/// 选定指定帧
/// </summary>
public void SelectFrame(int frameIndex)
{
//如果当前动画帧数据存在,则选定当前动画帧,所有顶点应用当前动画帧数据
if (frameIndex >= 0 && frameIndex < _VerticesAnimationArray.Count)
{
_NowSelectFrame = frameIndex;
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position = _VerticesAnimationArray[frameIndex][i];
}
}
}

第八步:

完成之后点击预览按钮就可以马上在Scene界面看到cube的动画效果,这里没截图,后面用动画播放器播放的时候再截图

因为脚本就算添加了编辑器执行的标识,它的update函数依然不会逐帧执行,而是在场景物体发生变化的时候才执行,所以这里的动画预览函数不能放在update里,那么只有将之加入到Unity编辑器逐帧刷新周期了

/// <summary>
/// 预览动画
/// </summary>
public void PlayAnimation()
{
//没有动画可以预览
if (_VerticesAnimationArray.Count <= 0)
{
return;
}
//预览从第一帧开始(顶点动画数组下标0)
_AnimationIndex = 0;
//重置记录动画播放上一序列的变量
_AnimationLastIndex = -1;
//重建新的动画片段
_AnimationFragment = new Vector3[_Vertices.Length];
//重置动画播放控制器
_AnimationPlayControl = 0;
//动画进入到第一帧
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position = _VerticesAnimationArray[0][i];
}
_IsPlay = true;
//将刷新动画函数注册到Unity编辑器帧执行模块
EditorApplication.update += PlayingAnimation;
}

动画刷新函数采用将每个关键帧切分为动画片段的方式,将片段循环累加给cube的网格顶点

/// <summary>
/// 动画预览中
/// </summary>
void PlayingAnimation()
{
if (_IsPlay)
{
//动画播放至最后一帧,动画播放完毕
if (_AnimationIndex + 1 >= _VerticesAnimationArray.Count)
{
//动画播放完毕
_IsPlay = false;
//清除刷新动画函数的注册
EditorApplication.update -= PlayingAnimation;
//动画回归到第一帧
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position = _VerticesAnimationArray[0][i];
}
return;
}
//当前动画播放序列不等于上一帧序列,则进入下一帧
if (_AnimationIndex != _AnimationLastIndex)
{
_AnimationLastIndex = _AnimationIndex;
//分割动画片段
for (int i = 0; i < _AnimationFragment.Length; i++)
{
_AnimationFragment[i] = (_VerticesAnimationArray[_AnimationIndex + 1][i] - _VerticesAnimationArray[_AnimationIndex][i])/ _AnimationPlaySpeed;
}
}
//动画进行中
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position += _AnimationFragment[i];
}
//动画控制器计数
_AnimationPlayControl += 1;
//动画控制器记录的一个动画帧播放完毕
if (_AnimationPlayControl >= _AnimationPlaySpeed)
{
_AnimationPlayControl = 0;
_AnimationIndex += 1;
}
RefishMesh();
}
}



第九步:


这里是重点了,记得点击导出动画,如果你直接点击编辑完成或是突然有了什么好想法跑去VS里随意改了下脚本导致Unity编辑器重新编译的话,很遗憾你的动画数据都会丢失,记得导出完毕了之后再点击编辑完成

使用scriptableobject序列化动画数据至asset文件中,这里的坑是真坑,路径必须还得是Asset开头,后缀必须还得是asset,刚开始坑了我不少无辜的时间

/// <summary>
/// 导出动画
/// </summary>
public void ExportAnimation()
{
//动画帧数小于等于1不允许导出
if (_VerticesAnimationArray.Count <= 1)
return;
//创建动画数据文件
MeshAnimationAsset meshAnimationAsset = ScriptableObject.CreateInstance<MeshAnimationAsset>();
//记录动画顶点数
meshAnimationAsset._VertexNumber = _RecordAllVerticesList.Count;
//记录动画帧数
meshAnimationAsset._FrameNumber = _VerticesAnimationArray.Count;
//记录动画帧数据
meshAnimationAsset._VerticesAnimationArray = new Vector3[_VerticesAnimationArray.Count * _RecordAllVerticesList.Count];
for (int n = 0; n < _VerticesAnimationArray.Count; n++)
{
for (int i = 0; i < _VerticesAnimationArray[n].Length; i++)
{
for (int j = 0; j < _AllVerticesGroupList[i].Count; j++)
{
int number = n * _RecordAllVerticesList.Count + _AllVerticesGroupList[i][j];
EditorUtility.DisplayProgressBar("导出动画", "正在导出顶点数据(" + number + "/" + meshAnimationAsset._VerticesAnimationArray.Length + ")......", 1.0f / meshAnimationAsset._VerticesAnimationArray.Length * number);
meshAnimationAsset._VerticesAnimationArray[number] = transform.worldToLocalMatrix.MultiplyPoint3x4(_VerticesAnimationArray[n][i]);
}
}
}
//创建本地文件
string path = "Assets/" + GetComponent<MeshFilter>().sharedMesh.name + "AnimationData.asset";
AssetDatabase.CreateAsset(meshAnimationAsset, path); EditorUtility.ClearProgressBar();
}

如下就是我们导出来的动画数据,可以看到里面包含了10个关键帧,适用于一切有24个网格顶点的模型(网格顶点是可操控顶点的3倍),当然他的原主是cube



第十步:


然后,为cube添加变形动画播放器组件(MeshAnimationPlayer)并将我们的CubeAnimationData拖到其MeshAnimationAsset属性上,每一个MeshAnimationPlayer对应一个AnimationData文件,暂不支持代码中动态变更

MeshAnimationAsset:动画播放器的目标asset文件,顶点数量需与当前挂载物体一致

AnimationPlaySpeed:动画播放速度,注意,这里是值越小播放越快

另外两个参数是开启循环播放和启动时即播放,我们勾选启动播放,然后运行程序,下面是动态效果图

其他效果:

一个看起来有点丑又有点僵硬的机甲变形(用最新的骨架调节方式,虽然这样还是显得一团糟)

原形:

编辑状态:

变形动画:

MeshAnimationPlayer的播放有外部可控开关

/// <summary>
/// 播放动画
/// </summary>
public void Play()
{
//从第一帧开始播放(顶点动画数组下标0)
_AnimationIndex = 0;
//重置记录动画播放上一序列的变量
_AnimationLastIndex = -1;
//重置动画播放控制器
_AnimationPlayControl = 0;
//动画跳转到第一帧
SelectFrame(_AnimationIndex);
_IsPlaying = true;
}
/// <summary>
/// 停止播放
/// </summary>
public void Stop()
{
_IsPlaying = false;
//动画回归到第一帧
SelectFrame(0);
}

以及要获取当前动画是否播放中,可以直接读取_IsPlaying属性。

DLL版插件链接:http://download.csdn.net/detail/qq992817263/9659011

github源码链接:https://github.com/SaiTingHu/MeshAnimation

-----by MeshEditor

Unity插件 - MeshEditor(五) 网格顶点动画(变形动画)的更多相关文章

  1. Unity插件 - MeshEditor(七)变形动画骨骼及蒙皮

    MeshAnimation在物体的顶点比较多的情况下,悲剧是显而可见的,我一个一个的点选顶点肯定得累死,而且对于形态的调控不是很方便,应该说是很麻烦,要知道,骨骼动画因为有了骨骼以及蒙皮信息而有了灵魂 ...

  2. Unity插件 - MeshEditor(六) 变形动画状态机

    变形动画状态机--MeshAnimator,是针对MeshAnimation的状态管理器,有大量类似Unity animator的功能,但MeshAnimator操作会更加简便,更加直观,居家旅(zh ...

  3. Unity插件 - MeshEditor(一) 3D线段作画 & 模型网格编辑器

    之前,因为工作需要,项目中需要动态生成很多的电线,不能事先让模型做好,更不能用LineRenderer之类的,因为画出来没有3D的效果,最主要是拐角的时候还容易破面,而我们要的是真真实实纯3D的电线, ...

  4. Unity插件 - MeshEditor(三) 面片破碎&网格破碎

    网上的unity破碎插件很多,不过想着可以以自己的方式实现也不失为一种乐趣,虽然整体的表现性上显得有些差,但也并不会影响最终的效果,接下来我大致讲解一下破碎一个物体的流程,因为用到了协程计算碎片的原因 ...

  5. Unity插件 - MeshEditor(二) 模型网格编辑器(高级)

    源码已上传至github,并持续更新,链接请看底部.(本帖跟随github持续更新) 继先前的一篇MeshEditor之后,MeshEditor第二版发布,这次在先前的基础上加入了为模型新增顶点以及删 ...

  6. Unity插件 - MeshEditor(八)模型镜像特效

    将静态模型(带MeshFilter)按指定轴向.指定距离克隆一个镜像物体出来,思路很简单,将模型的顶点坐标按指定轴取反,并累加上设定的距离值,然后就完毕了!不过,因为镜像体的顶点镜像于之前模型的顶点, ...

  7. Unity插件 - MeshEditor(四) 模型融化特效

    现在的电影里有很多妖魔在死亡后身体逐渐融化并下滑最后化为一滩黑水的情景,本次出于兴趣大致研究了这个效果,原理是控制模型的顶点向一个方向坍塌,坍塌到最低点时再根据法线方向扩散形成黑水状. 第一步: 添加 ...

  8. jQuery插件实例五:手风琴效果[动画效果可配置版]

    昨天写了个jQuery插件实例四:手风琴效果[无动画版]那个是没有动画效果的,且可配置性不高,本篇为有动画效果.对于一些数据做了动态的计算,以实现自适应. 欢迎大家入群相互交流,学习,新群初建,欢迎各 ...

  9. Unity插件之NGUI学习(4)—— 创建UI2DSprite动画

    创建一个新的Scene.并按 Unity插件之NGUI学习(2)创建UI Root,并在UI Root的Camera下创建一个Panel. 然后在选中Panel,在菜单中选择NGUI->Crea ...

随机推荐

  1. 细胞(cell) 矩阵快速幂

    题目描述小 X 在上完生物课后对细胞的分裂产生了浓厚的兴趣.于是他决定做实验并观察细胞分裂的规律.他选取了一种特别的细胞,每天每个该细胞可以分裂出 x 1 个新的细胞.小 X 决定第 i 天向培养皿中 ...

  2. hdu 4533 线段树(问题转化+)

    威威猫系列故事——晒被子 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Tot ...

  3. Zend引擎探索 之 PHP中前置递增不返回左值

    首先来讲,一般我们对"左值"的理解就是可以出现在赋值运算符的左侧的标识符,也就是可以被赋值.这样讲也许并不十分确切,在不同的语言中对左值的定义也不尽相同.在这里我们讨论前置递增(和 ...

  4. 如何使用 TeamViewer 配置QuickConnect按钮?

    QuickConnect作为TeamViewer中一个比较重要的部分,得到了很多用户的认可.那么在实际运用中,怎么才能设置网页或单个程序的QuickConnect呢?所以小编以此问题为例,教大家如何配 ...

  5. 网易互联网&网易游戏产品经理面试经验

    网易是分网易游戏和网易互联网的,本人都参加了校园招聘面试,最后均拿到了产品经理的offer. 网易是分网易游戏和网易互联网的,先说网易互联网吧,当时是去杭州总部进行面试,我觉得这是我面的最难的面试了. ...

  6. Linux下打包tar.gz

    将heben-addressbookinit打包成heben-addressbookinit.tar.gz格式 方式1:czvf heben-addressbookinit.tar.gz heben- ...

  7. 读书笔记-《Maven实战》-2018/4/17

    第五章 坐标和依赖 1.如同笛卡尔坐标系一样,Maven也通过坐标三元素定位一个资源. <groupId>com.dengchengchao.test</groupId> &l ...

  8. Dubbo介绍和服务架构分析

    Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成.使用zookeeper作为服务的注册中心,对外提供服务 ...

  9. Codeforces Round #305 (Div. 2) B. Mike and Fun 暴力

     B. Mike and Fun Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/548/pro ...

  10. Radio Station

    B. Radio Station time limit per test:  2 seconds memory limit per test:  256 megabytes input: standa ...