动画系统(第一篇)

Posted weixin_42794858

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动画系统(第一篇)相关的知识,希望对你有一定的参考价值。

动画系统概述

Unity 有一个丰富而复杂的动画系统(有时称为“Mecanim”)。该系统具有以下功能:

为 Unity 的所有元素(包括对象、角色和属性)提供简单工作流程和动画设置。
支持导入的动画剪辑以及 Unity 内创建的动画
人形动画重定向 - 能够将动画从一个角色模型应用到另一角色模型。
对齐动画剪辑的简化工作流程。
方便预览动画剪辑以及它们之间的过渡和交互。因此,动画师与工程师之间的工作更加独立,使动画师能够在挂入游戏代码之前为动画构建原型并进行预览。
提供可视化编程工具来管理动画之间的复杂交互。
以不同逻辑对不同身体部位进行动画化。
分层和遮罩功能

动画工作流程
Unity 的动画系统基于动画剪辑的概念;动画剪辑包含某些对象应如何随时间改变其位置、旋转或其他属性的相关信息。每个剪辑可视为单个线性录制。来自外部的动画剪辑由美术师或动画师使用第三方工具(例如 Autodesk® 3ds Max® 或 Autodesk® Maya®)创建而成,或者来自动作捕捉工作室或其他来源。

然后,动画剪辑将编入称为 Animator Controller 的一个类似于流程图的结构化系统中。Animator Controller 充当“状态机”,负责跟踪当前应该播放哪个剪辑以及动画应该何时改变或混合在一起。

一个非常简单的 Animator Controller 可能只包含一个或两个剪辑,例如,使用此剪辑来控制能量块旋转和弹跳,或设置正确时间开门和关门的动画。一个更高级的 Animator Controller 可包含用于主角所有动作的几十段人形动画,并可能同时在多个剪辑之间进行混合,从而当玩家在场景中移动时提供流畅的动作。

Unity 的动画系统还具有用于处理人形角色的许多特殊功能。这些功能可让人形动画从任何来源(例如:动作捕捉、Asset Store 或某个其他第三方动画库)重定向到您的角色模型中,并可调整肌肉定义。这些特殊功能由 Unity 的替身系统启用;在此系统中,人形角色会被映射到一种通用的内部格式中。

所有这些部分(动画剪辑、Animator Controller 和 Avatar)都通过 Animator 组件一起附加到某个游戏对象上。该组件引用了Animator Controller,并(在必需时)引用此模型的 Avatar。Animator Controller又进一步包含所使用的动画剪辑的引用。

上图显示了以下内容:

1.动画剪辑是从外部来源导入的或在 Unity 内创建的。在此示例中,它们是导入的动作捕捉人形动画。 2.动画剪辑显示并排列在Animator Controller中。因此,在 Animator 窗口中会显示Animator Controller的视图。状态(可表示动画或嵌套的子状态机)显示为通过线条连接的节点。此Animator Controller作为资源存在于 Project 窗口中。 3.骨架角色模型(在本示例中为宇航员“Astrella”)具有映射到 Unity 常见替身格式骨骼的特定配置。此映射作为导入的角色模型的一部分存储为 Avatar 资源,并且在 Project 窗口中显示(如图所示)。 4.对角色模型进行动画化时,角色模型会附带一个Animator组件。在上图所示的 Inspector 视图中,您可以看到一个Animator组件,该组件已被分配了Animator Controller和替身。Animator将这些一起用于对模型的动画化过程。仅在对人形角色进行动画化时,才需要引用 Avatar。对于其他动画类型,只需Animator Controller。

Unity 动画系统中有很多的概念和术语。如果在任何时间需要了解术语的含义,请转至动画术语表。

旧版动画系统

虽然在大多数情况下建议使用 Mecanim,但是 Unity 保留了 Unity 4 之前的版本便存在的旧版动画系统。在处理 Unity 4 之前的版本创建的旧内容时可能需要使用它。有关旧版动画系统的信息,请参阅此部分

动画剪辑

动画剪辑是 Unity 动画系统的核心元素之一。Unity 支持从外部源导入动画,并允许在编辑器中使用 Animation 窗口从头开始创建动画剪辑。

外部源动画

从外部源导入的动画剪辑可能包括:

在动作捕捉工作室中捕捉的人形动画
美术师在外部 3D 应用程序(如 Autodesk® 3ds Max® 或 Autodesk® Maya®)中从头开始创建的动画
来自第三方库(例如 Unity 的 Asset Store)的动画集
从导入的单个时间轴剪切的多个剪辑。

Unity 中创建和编辑的动画

在 Unity 的 Animation 窗口中还可以创建和编辑动画剪辑。这些剪辑可针对以下各项设置动画:

游戏对象的位置、旋转和缩放
组件属性,例如材质颜色、光照强度、声音音量
自定义脚本中的属性,包括浮点、整数、枚举、矢量和布尔值变量
自定义脚本中调用函数的时机

外部来源的动画

外部来源的动画按照与常规 3D 文件相同的方式导入 Unity。这些文件(无论是通用 FBX 文件还是来自 Autodesk® Maya®、Cinema 4D、Autodesk® 3ds Max® 等 3D 软件的原生格式)可包含动画数据,这些数据的形式为文件中对象移动的线性录制结果。


在某些情况下,要动画化的对象(例如角色)以及随附的动画可能存在于同一文件中。在其他情况下,动画可能存在于与动画化模型相分离的文件中。

动画可能是特定模型所特有的,不能在其他模型上复用。例如,游戏中的最终 boss 巨型章鱼可能具有独特的肢体和骨骼排列,因此有自己的一组动画。

在其他情况下,您可能拥有一个动画库,这些动画将用于场景中的各种不同模型。例如,许多不同的人形角色可能都使用相同的行走和奔跑动画。在这些情况下,为了预览动画文件,在这些文件中使用简单占位模型是很常见的做法。或者可以使用只有动画数据而没有几何体的动画文件。

导入多个动画时,每个动画可以在项目文件夹中以单独文件形式存在,或者如果先前以连续片段形式从 Motion Builder 或使用 Autodesk® Maya®、Autodesk® 3ds Max® 或其他 3D 包的插件/脚本导出了 FBX 文件,您可以从单个 FBX 文件提取多个动画剪辑。如果您的文件包含了在单个时间轴上排列的多个单独动画,您可能需要执行此操作。例如,长时间的运动捕捉时间轴可能包含几个不同跳跃动作的动画,而您可能希望剪切其中的某些部分以用作单个剪辑而丢弃其余部分。Unity 提供了动画剪切工具来实现此目的;当您在一个时间轴中导入所有动画时,这些工具允许您为每个剪辑选择帧范围。

导入动画文件

在 Unity 中使用任何动画之前,必须先将其导入项目。Unity 可导入本机 Autodesk® Maya®(.mb 或 .ma)、Autodesk® 3ds Max® (.max) 和 Cinema 4D (.c4d) 文件以及通用 FBX 文件(这些文件可从大多数动画包中导出)。

有关更多信息,请参阅导入。

在导入的动画文件中查看和复制数据

您可以在 Animation 窗口中查看导入的动画剪辑的关键帧和曲线。有时,如果这些导入的剪辑存在带有大量关键帧的很多骨骼,信息量可能非常复杂。例如,下图是 Animation 窗口中的人形奔跑动画的情况:

为了简化视图,请选择您希望检查的特定骨骼。然后,Animation 窗口仅显示这些骨骼的关键帧或曲线。

查看导入的动画关键帧时,Animation 窗口提供动画数据的只读视图。要编辑此数据,请在 Unity 中创建新的空动画剪辑(请参阅创建新动画剪辑),然后选择、复制导入的动画剪辑的动画数据并将其粘贴到新的可写动画剪辑中。

人形Avatar

Unity 的动画系统具有处理人形角色的特殊功能。因为人形角色在游戏中很常见,所以 Unity 为人形动画提供专门的工作流程以及扩展工具集。

Unity 使用Avatar系统来识别布局中的特定动画模型是否为人形,以及模型的哪些部分对应于腿、手臂、头和躯干。

由于不同人形角色之间骨骼结构的相似性,可将动画从一个人形角色映射到另一个角色,允许__重定位__和__反向运动学 (IK)__。

Animation 窗口指南

Unity 中的 Animation 窗口__让您可以直接在 Unity 内创建和修改__动画剪辑。它旨在充当外部 3D 动画程序的强大而直接的替代方案。除了对运动进行动画化,编辑器还允许您对材质和组件的变量进行动画化,并使用__动画事件__函数(在时间轴上的指定点调用这些函数)来充实动画剪辑。

请参阅关于动画导入和动画脚本的页面以了解关于这些主题的更多信息。

Animation 窗口和 Timeline 窗口之间有何差异?

Timeline 窗口
Timeline 窗口可用于创建影片内容、游戏序列、音频序列和复杂的粒子效果。可以在同一序列(例如有角色与景物交互的过场动画或脚本序列)中为许多不同的游戏对象设置动画。在 Timeline 窗口中,可以有多种类型的轨道,每个轨道可以包含多个可以移动、修剪和混合的剪辑。这对于创建更复杂的动画序列非常有用,因为这些动画序列需要将许多不同的游戏对象编排在一起。

Timeline 窗口比 Animation 窗口更晚推出。它是在版本 2017.1 中添加到 Unity 的,并取代了 Animation 窗口的一些功能。要了解 Unity 中的 Timeline 窗口,请访问用户手册的 Timeline 部分。

Animation 窗口

Animation 窗口可用于创建各个动画剪辑以及查看导入的动画剪辑。动画剪辑存储单个游戏对象或单个游戏对象层级视图的动画。Animation 窗口可用于为游戏中的独立对象(例如摆动的钟摆、滑动的门或旋转的硬币)设置动画。Animation 窗口一次只能显示一个动画剪辑。

Animation 窗口是在 Unity 4.0 版中添加的。Animation 窗口是比 Timeline 窗口更旧的功能。Animation 窗口提供了一种创建动画剪辑和为单个游戏对象设置动画的简单方法,而在其中创建的剪辑可以用 Animator Controller 进行组合和混合。但是,要创建涉及许多不同游戏对象的更复杂序列,应使用 Timeline 窗口(见上文)。

Animation 窗口的用户界面中有一个“时间轴”(标有时间刻度的水平条),但这与 Timeline 窗口是分开的。

要了解 Unity 中的 Animation 窗口,请访问用户手册的 Animation 部分。

[iOS]过渡动画之高级模仿 airbnb

注意:我为过渡动画写了两篇文章:
第一篇:[iOS]过渡动画之简单模仿系统,主要分析系统简单的动画实现原理,以及讲解坐标系、绝对坐标系、相对坐标系,坐标系转换等知识,为第二篇储备理论基础。最后实现 Mac 上的文件预览动画。
第二篇:[iOS]过渡动画之高级模仿 airbnb ,主要基于第一篇的理论来实现复杂的界面过渡,包括进入和退出动画的串联。最后将这个动画的实现部分与当前界面解耦,并封装为一个普适(其他类似界面也适用)的工具类。


这两篇文章将会带你学到如何实现下图 airbnb 首页类似的过渡动画,同时最重要的,你将学会怎么分析类似的动画,并且知道如何动手实现。GitHub 地址在这里。

技术分享
如果你没看第一篇,那我建议你去看一下第一篇,因为如果没有第一篇的基础,这篇还是比较难理解的。不如,现在就去吧。
好,准备好了吗?现在开始第二篇。这一篇主要基于第一篇的理论来实现复杂的界面过渡,包括进入和退出动画的串联。最后将这个动画的实现部分与当前界面解耦,并封装为一个普适(其他类似界面也适用)的工具类。

01.这个界面的架构

我们首先来分析一下这个界面的架构。窗口上是一个可以上下滚动的 UITableViewController,每个 UITableViewCell 上有一个可以左右滑动的 UICollectionView,在每一个 UICollectionViewCell 上布局一张封面图片和其他元素。很主流的布局,大致就是这样,对吧?

技术分享

02.基于第一篇,我们应该怎么划分这个动画的结构?

上面的动画应该怎么分析呢?什么?我好像听到有同学在说:“太快了?根本看不清?” 好,那我就放慢一点,你再仔细瞧瞧。

技术分享

看清楚没?还没看清?什么?只看到它们在动?有一种轻拿轻放的感觉?

那我们再看张图吧。如果我们脑洞大一点,不要管界面结构,我们把界面想象成为一个平面,那么我们可以按照下面这张图来划分一下动画结构。

技术分享

如你所见,其实动画分为三个部分,UpAnimationPart + CentreAnimationPart + DownAnimationPart,UpAnimationPart 和 DownAnimationPart 的动画可以归为一类,他们只是简单的上移或者下移。重点是中间的 CentreAnimationPart,它和其他部分都有一些区别。

CentreAnimationPart,每张图片都要作为一个单个的个体进行动画,而不能将中间整个模块一起进行动画。为什么呢?因为每张图片最后都要在下个界面的顶部填充满一个控件。这么说太难懂了?意思就是,如果你拿到的是中间区域整体进行动画的话,那么你拿到的将是中间图片区域有遮盖的部分,而右边露出来那片橙色的图片你将拿不到,这个时候当用户点击的刚好又是右边那张露出半边的图片,你将无法实现中间区域的动画。

如果你理解了我所说的,那么你将会理解这两张图片的微妙区别。

技术分享
技术分享

上面两张图片,第二张图片的动画结构是正确的。

划分完动画结构以后,你应该有一种庖丁解牛的感觉。你有没有感觉到和第一篇文章所实现的系统动画已经很像了。希望你能从这种分析和训练中总结出问题的核心:最难的部分是我们的理解,而不是实现。

03.注意点

既然思路都有了,那赶紧写代码吧?吁... 等等,你的思路真的有了吗?能说出来是什么吗?

没关系,学东西哪有那么快。这不是安慰,这是事情的真相。

我们先来看两个知识点和一个注意点。

3.1.Block 的循环引用

可能你已经隐隐意识到了,这个动画已经难免会用到 block 了,block 有很难搞的“循环引用”,你可能仍然搞不懂什么是“引用环”,说清楚这个问题可能要再写一篇文章才够,所以我也不打算在这里说清楚这个问题。

所以我给你的建议是,凡是你拿不准是不是会出现循环引用的地方,你都这么写:

__weak typeof(self) weakSelf = self; 
self.aBlock = ^{ 
      __strong typeof(weakSelf) strongSelf = weakSelf;
       if (!strongSelf) return; 

        // 其它代码
        ... 
  }

为什么这么写?

  • 解除循环引用的问题。__weak 是弱引用,不会将 self 的引用计数器 +1。_strong 将 weakSelf 引用计数器 +1,以保持对 weakSelf 的持有,但是 strongSelf 是一个局部变量,过完这个代码块,strongSelf 就会自动释放,所以解除了循环引用的可能性。

  • 防止应用奔溃。if (!strongSelf) return; 我们假设一种很常见的情况,当 self 已经释放的时候,这个 block 被调起,然后就去访问一个为 nil 的僵尸对象,比如说将 self 的某个属性插入字典什么的,这个时候往字典里插入空元素,自然会造成应用奔溃,有了这一行代码,就不会再出现类似的情况了。

3.2.循环利用池

我们天天在用的 UITableView 为什么性能这么好,很大一部分原因是得益于循环利用池这个设计思想。

技术分享

循环利用池的设计思想可以概括为:

  • 当要用到一个对象的时候,先从 ReusePool 中取,如果 ReusePool 中有缓存就把这个缓存取出来,返还给使用者,然后将这个对象从 ReusePool 中移除。如果 ReusePool 中没有,就创建一个新的对象,返还给使用者。
  • 当一个对象已经移出视野(或不需要使用)的时候,就会将它加入到 ReusePool 中等待再次循环。

基于高性能的这个目的,我们应该给我们的做动画的 UIImageView 实例创建一个 ReusePool。

3.3.如何测试自己计算的 frame 是否正确?

计算 frame 和迁移 frame 是一件很纠结的事情,而且不知道自己究竟有没有算对,如果算不对,就会导致动画错乱。但是如果最后调动画的时候才回过来调 frame,就要反复的检查究竟哪个 frame 算错了。这样是很蛋疼的。

所以我给你一个建议。就是你每算完一个 frame,就在这个 frame 上添加一个占位视图看一下对不对。比方说,像这样添加一个红色的 View 到屏幕上检查一下 frame 对不对:

UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
redView.frame = YourFrame;
[self.view.window addSubview:redView];

3.4.怎么找到 UICollectionViewCell 上那个显示封面图片的 UIImageView?

这个需要你在使用的时候给这个 UIImageView 绑定一个 tag,这样我就能拿到这个 UIImageView。

04.具体实现思路?

让我们总结一下上面分析的内容,看能不能从中分析出我们的具体实现思路。

4.1.动画素材

  • 4.1.1、当用户点击的那一刻,我们首先应该把当前窗口(注意,是窗口 Window)进行截图,备用。

  • 4.1.2、我们需要有一个工具,给这个工具传入裁剪的点和裁剪的类型,就能帮我们把一张图片裁成我们想要的样式。比方说,我们只要截图的上半部分,或者下半部分。

    技术分享
  • 4.1.3、我们需要把用户点击的那个 UICollectionViewCell 上面的那张图片的 Frame 迁移到窗口坐标中,然后计算出需要裁剪的点的位置,最后把窗口截屏和这个点传进去进行裁剪,得到我们做动画需要的图片。

  • 4.1.4、现在做动画需要的元素中我们已经有了 UpAnimationPart 和 DownAnimationPart 需要的图片了,现在只差 CentreAnimationPart 需要的可见 Cell 上面的图片了,这个我们通过 UICollectionView 的 visiableCells 可以拿到。这样一来,做动画的素材已经齐备了。

    技术分享

4.2.动画起始位置

动画的起始位置应该是这个动画最容易的部分。

  • 4.2.1、UpAnimationPart 的起始位置应该是点击那个 Cell 的图片的 Y 坐标以上。如果把 upTailorY 指定为点击那个 Cell 的图片的 Y 坐标的话,那么:

    CGRect upAnimationImageViewFrame_start = CGRectMake(0, 0, JPScreenWidth, upTailorY);
  • 4.2.2、同理,如果把 downTailorY 指定为为点击那个 Cell 的图片的底部(Y 坐标加上图片的高度)。那么,DownAnimationPart 的起始位置应该是:

    CGRect downAnimationImageViewFrame_start = CGRectMake(0, downTailorY, JPScreenWidth, JPScreenHeight - downTailorY);
  • 4.2.3、而中间可见 Cell 的图片的起始位置,都可以通过坐标系迁移直接得到。这样以后,各部分的动画起点位置我们也都有了,这样以后我们就可以在窗口上添加 UIImageView 了。

4.3.动画终点位置

第一篇里说的考验数学功底的部分终于来,还是有点小激动。其实也很简单,你看一张图就知道了:

技术分享
  • 4.3.1、UpAnimationPart 的终点位置很 easy,简单到你可以直接写出来:

    CGRect upAnimationImageViewFrame_end = CGRectMake(0, -upTailorY, JPScreenWidth, upTailorY);
  • 4.3.2、DownAnimationPart 的终点位置是:

    CGRect downAnimationImageViewFrame_end = CGRectMake(0, JPScreenHeight, JPScreenWidth, JPScreenHeight - downTailorY);
  • 4.3.3、CentreAnimationPart 会稍微有点复杂,其实应该分为三种情况的。具体见下图:

    技术分享
    • 4.3.3.1、TapImage 这张被点击的图片,它的终点位置应该很容易确定,就是填充屏幕顶部:
      这个没有异议吧?而其他两类需要参考它的位置来定位。

      CGRect tapAnimationImageViewFrame_end = CGRectMake(0, 0, JPScreenWidth, JPScreenWidth*2.0/3.0);
    • 4.3.3.2、TapImage_Left。请看下面这张图,你肯定明白了,对吧?TapImage 的初始宽度我们知道,左侧图片和 TapImage 的左侧间距我们也可以算出来,屏幕宽度我们也知道,现在利用十字相乘法,我们就可以很快拿到下图红色方框里的值,也就是我们要的终点位置的 X 坐标。

      技术分享
    • 4.3.3.3、TapImage_Right。这个就不用我再赘述了吧?和上面的情况类似。

05.代码实现

我不打算在文章里粘贴代码了,一个,篇幅已经很长了,再粘贴代码,就会让有些“太长不看”的同学感觉压力很大。二来,代码已经放在 GitHub 上了,看代码还是在 Xcode 中更舒服一点。而且我把 Keynote 也一并放上去了。

06.解耦和抽成工具类

如果你按照这个思路去写的话,你会发现所有的代码都会集中在一个方法里,导致这个方法的代码量有三四百行,非常臃肿。而且进入和退出动画居然耦合在一起,要解耦,要抽工具类又感觉无从下手。这个时候就应该要有一种壮士断腕的勇气:“老子一定要把你抽成工具”的决心。有了这个决心,剩下的就是想办法了。

这个动画有很多参数,所以对于哪些是必须的,要有所取舍。也就是要尝试为工具类设计 API。

/*!
 * \~chinese
 * @prama indexPath                用户选中的那个UICollectionViewCell的 indexPath.
 * @prama collectionView           用户选中的那个UICollectionViewCell的 UICollectionView.
 * @prama viewController           动画之前窗口上显示的 viewController.
 * @prama presentViewController    动画完成之后要在窗口上显示的 viewController.
 * @prama afterPresentedBlock      动画完成之后要在 presentViewController 做的事情.
 *
 * @return JPContainIDBlock        关闭动画的 block.
 */

对于解耦,我的理解就是,首先写代码之前就要有“尽量不要耦合”的意识。如果项目特别赶时间,你可以暂时不用太理会耦合,这些很深的东西可能需要长期的积累和对于项目的全局观,这些可以等周末有空或者项目之间有空档期的时候再去细细琢磨。

还有就是要有坚韧不拔的意志,有些时候给某个类解耦的时间可能比你重新写一遍花的时间,还多。但是,总结一下,多出来的时间我们都在思考什么?是不是这些时间都用在比实现功能更高一个层次的事务上了?

07.关于JPNavigationController

这个动画得以最后呈现,和我之前的一个框架是分不开的。也就说,如果没有我之前的那个框架做基础,那么这个动画的关闭部分就无法实现。

具体体现在对于 pop 手势的拦截。

#pragma mark --------------------------------------------------
#pragma mark JPNavigationControllerDelegate

-(BOOL)jp_navigationControllerShouldPushRight{
    [self backBtnClick:nil];
    return NO;
}

大致可以概述为,当用户开始 pop 的时候,我们当前控制器会收到代理方法的询问,询问是否需要继续 pop 行为。在我们这个动画中迫切需要收到这个询问,但是不需要继续 pop 行为,所以我们 return NO。


如果你想了解这个框架的实现,你可以看下面这三篇文章:
第一篇:[iOS]UINavigationController全屏pop之为每个控制器自定义UINavigationBar。这篇文章主要是讲述如何实现自定义导航栏的,所有的思路和实现都是 JNTian的。
第二篇:[iOS]UINavigationController全屏pop之为每个控制器添加底部联动视图。这篇文章讲述,如何在已有的自定义导航栏基础上添加自定义的“底部联动视图”。所有的思路和实现都是我自己的。
第三篇:[iOS]UINavigationController全屏pop之为控制器添加左滑push。这次将讲述如何实现左滑push到绑定的控制器中,并且带有push动画。
或者访问我的 GitHub 的JPNavigationController


08.GitHub 地址

GitHub 地址在这里。

 

感谢分享

以上是关于动画系统(第一篇)的主要内容,如果未能解决你的问题,请参考以下文章

自学游戏开发要怎么开始学习?

Unity 入门笔记 - 02 - 各种动画

Unity游戏教程初步:Animator的使用

[Unity3D]Unity3D游戏开发之自己主动寻路与Mecanim动画系统的结合

游戏开发进阶Unity网格探险之旅(Mesh | 动态合批 | 骨骼动画 | 蒙皮 )

游戏开发进阶Unity网格探险之旅(Mesh | 动态合批 | 骨骼动画 | 蒙皮 )