返回

使用 Mecanim 动画系统来控制 2D 动画

各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://blog.csdn.net/qinyuanpei。今天我想和大家分享的话题是在 Unity3D 中使用 Mecanim 动画系统来控制 2D 动画。

相信在大家的印象中,Mecanim 动画系统主要运用在 3D 动画中,因为 Mecanim 动画系统提供了像动画重定向、人体骨骼动画等 3D 动画的特性,那么 Unity3D 的 Mecanim 动画系统能不能用来控制 2D 动画呢?如果在以前,博主和大家的理解是一样的,认为 Mecanim 只能运用到 3D 动画中,对于 2D 动画只能使用传统的逐帧动画和骨骼动画。可是前不久有位朋友问我,为什么不使用动画组件来控制 2D 动画呢?博主心想啊,这 Mecanim 动画系统真的能控制 2D 动画吗?经过博主查找大量资料和亲身实践,发现 Mecanim 是可以用来控制 2D 动画的,而且由于状态机的引入,我们对动画状态的控制会变得更为简单,从写代码的角度来看,这样可以减少我们的代码量便于维护。那么好了,今天我们就来一起学习下如何使用 Mecanim 动画系统来控制 2D 动画吧!

传统 2D 动画的实现方式

在 Unity3D 中传统 2D 动画的实现方式是基于逐帧动画的原理实现的,这种实现方式在 Unity3D 没有推出 Unity2D 前甚至在 Unity2D 推出后相当长的一段时间内,基本上我们最为常用的实现方式,博主在刚开始学习 Unity3D 的时候通常是以 2D 形式来展开的,因为博主认为 2D 和 3D 在原理上基本是相通的,如果我们掌握了 2D 游戏的基本原理,那么在实现 3D 游戏的时候就会相对容易些。我们来看看一个最简单的 2D 动画的脚本实现:

 1//精灵渲染器
 2private SpriteRenderer mRenderer;
 3//精灵集合
 4public Sprite[] Sprites;
 5//FPS,即每秒钟的画面帧数
 6public float FPS = 24;
 7//精灵索引
 8private int index = 0;
 9//当前时间
10private float currentTime = 0;
11
12void Start () 
13{
14    mRenderer = GetComponent<SpriteRenderer>();
15}
16	
17void Update () 
18{
19    //获取当前时间
20    currentTime += Time.deltaTime;
21    //如果达到了更新画面的时间
22    if(currentTime >= 1 / FPS)
23    {
24        //使索引增加
25        index += 1;
26        //清除时间记录
27        currentTime = 0;
28        //当索引更新到最后一帧时,索引重置
29        if(index >= Sprites.Length)
30        {
31            index = 0;
32        }
33    }
34
35    //更新画面
36    mRenderer.sprite = Sprites[index];
37}

通过分析,我们可以发现这段脚本存在以下问题:

  • 动画维护困难:每增加一个动画就需要添加一个数组,不仅增加了动画的维护难度,同时降低了脚本的效率。
  • 状态维护困难:因为在 Update 方法里实现的是一个动画,因此当我们需要在各个动画状态间进行切换的时候,我们需要使用更多的代码来维护相关逻辑。

使用 Mecanim 动画系统的实现方式

为了解决传统的 2D 动画实现方式中存在的动画维护困难、状态维护困难这两个问题,我们需要一种更好的方案来实现 2D 动画的控制,这种方案需要提供较为方便的动画维护功能,即各个动画是独立的,当改变了某一个动画时,其余的动画不会发生改变。其次,这种方案需要提供较为方便的状态维护功能,即各个动画状态切换是方便的,我们可以更好地从这一种状态切换到另一种状态。关于动画状态切换,大家可以去了解下有限状态机(FSM)的概念,这里我们不做深入的探究,这里我们选择 Unity3D 的 Mecanim 动画系统,因为 Mecanim 动画系统正好解决了这两个问题。好了,下面我们来一起学习一个 2D 动画的实例:

首先我们在场景中创建一个名为 PlayerController 的空物体,然后在该物体的下面增加一个精灵组件(Sprite),并将其命名为 PlayerSprite,这样做的好处是 Unity3D 将为我们自动创建较为规范的命名。好了,现在我们选择 PlayerController 这个物体,然后通过 Window -> Animation 菜单打开 Animation 窗口:

创建第一个动画
创建第一个动画

首先我们点击 AddCurve 按钮,此时将弹出一个对话框让我们保存动画文件,这里我们存储为 Player@Idle.anim,并将其保存在项目目录下的 Animations\Player 目录下,这样可以方便我们维护和查找特定的动画文件。在保存完动画文件后,此时会弹出如下的界面,我们选择 PlayerSprite 节点下的 SpriteRenderer,然后选择 Sprite,因为这里我们的 2D 动画主要是通过改变 SpriteRenderer 的 Sprite 属性来实现的,最后我们点击 Sprite 节点后面的加号来完成对象的选取。此时会在动画窗口中显示时间轴和刻度线,我们将在这里完成动画的编辑。大家可以注意到默认情况下,动画面板添加了两帧,即第 1 帧和最后一帧,其总时间是 1 秒,同时我们注意到这里有一个采样率(Sample),其实这就是当前动画的 FPS 了。好了,现在我们开始制作第一个动画:

动画序列
动画序列

在资源文件夹中,我们可以找到当前动画的图片素材,注意到这个图片中总共有 12 帧画面,因此我们可以按照 0.05s 的间隔来分配整个时间轴,所以我们可以这样添加帧:

为 Idle 动画添加帧
为 Idle 动画添加帧

好了,现在我们就完成了一个 Idle 动画的制作,现在打开角色的动画控制器 PlayerController,这是 Unity3D 为我们自动创建的一个动画控制器,因为我们现在只有一个 Idle 动画,所以在 Animaotr 窗口中我们可以看到只有一个 Idle 状态,现在我们将这个状态设为默认状态。好了,现在我们可以直接运行游戏,发现在场景中角色开始循环播放 Idle 动画了。好了,现在让我们重复刚才的步骤,来完成角色的其余动画。

Idle 动画效果演示
Idle 动画效果演示

经过一番努力,我们现在已经完成了角色所有动画的制作,现在我们来设计角色的动画状态机:

玩家角色控制器设计
玩家角色控制器设计

设计好角色的动画状态机后我们开始来编写脚本,以实现角色动画的控制:

  1using UnityEngine;
  2using System.Collections;
  3
  4public class PlayerController : MonoBehaviour {
  5
  6    public enum PlayerState
  7    {
  8        Idle,
  9        Move,
 10        LightAttack,
 11        WeightAttack
 12    }
 13
 14    public PlayerState State=PlayerState.Idle;
 15    //玩家移动速度
 16    public float WalkSpeed = 0.75f;
 17    public float RunSpeed = 1.5f;
 18    //玩家跳跃力的强度
 19    public float JumpForce = 200f;
 20
 21    //位置限制
 22    public float MinX = -5.80f;
 23    public float MaxX =  5.80f;
 24    public float MinY = -1.80f;
 25    public float MaxY =  0.35f;
 26
 27    //玩家朝向,默认朝右
 28    public bool isFaceRight = true;
 29
 30    //动画组件
 31    private Animator mAnim;
 32    //2D刚体
 33    private Rigidbody2D mRig2D;
 34
 35
 36    void Start () 
 37    {
 38        mAnim=GetComponent<Animator>();
 39        mRig2D=GetComponent<Rigidbody2D>();
 40    }
 41
 42    void Update()
 43    {
 44        SpriteMove();
 45        SpriteAttack();
 46        SpriteJump();
 47        SpriteIdle();
 48    }
 49
 50    /// <summary>
 51    /// 精灵Idle
 52    /// </summary>
 53    private void SpriteIdle()
 54    {
 55        //当玩家无任何操作时恢复到Idle状态
 56        if (!Input.anyKey)
 57        {
 58            mAnim.SetBool("Jump", false);
 59            mAnim.SetBool("Attack", false);
 60            mAnim.SetBool("BigAttack", false);
 61            mAnim.SetBool("Skill", false);
 62            mAnim.SetBool("BigSkill", false);
 63            State=PlayerState.Idle;
 64        }
 65    }
 66
 67    /// <summary>
 68    /// 精灵攻击
 69    /// </summary>
 70    private void SpriteAttack()
 71    {
 72        //轻击,键位J
 73        if(Input.GetKey(KeyCode.J))
 74        {
 75            mAnim.SetBool("Attack", true);
 76            State=PlayerState.LightAttack;
 77        }
 78
 79        //重击,键位K
 80        if(Input.GetKey(KeyCode.K))
 81        {
 82            mAnim.SetBool("BigAttack", true);
 83            State=PlayerState.WeightAttack;
 84        }
 85
 86    }
 87
 88    /// <summary>
 89    /// 精灵跳跃
 90    /// </summary>
 91    private void SpriteJump()
 92    {
 93        if (Input.GetKey(KeyCode.I))
 94        {
 95            mAnim.SetBool("Jump", true);
 96            mRig2D.AddForce(new Vector2(0, Time.deltaTime * JumpForce), ForceMode2D.Impulse);
 97        }
 98    }
 99
100    private void SpriteMove()
101    {
102        float h = Input.GetAxis("Horizontal");
103        float v = Input.GetAxis("Vertical");
104
105        Vector2 mPos = mRig2D.position;
106
107        mAnim.SetFloat("Speed", Mathf.Sqrt(h * h + v * v));
108
109        float mPosX, mPosY;
110
111        if (Mathf.Sqrt(h * h + v * v) > 0.5f){
112            mPosX = mPos.x + h * Time.deltaTime * RunSpeed;
113            mPosY = mPos.y + v * Time.deltaTime * RunSpeed;
114        }else{
115            mPosX = mPos.x + h * Time.deltaTime * WalkSpeed;
116            mPosY = mPos.y + v * Time.deltaTime * WalkSpeed;
117        }
118
119
120        mRig2D.MovePosition(new Vector2(mPosX, mPosY));
121
122        if (h > 0 && !isFaceRight)
123        {
124            FlipSrite();
125        }
126        else if (h < 0 && isFaceRight)
127        {
128            FlipSrite();
129        }
130    }
131
132
133    void FlipSrite()
134    {
135        if(isFaceRight){
136            transform.rotation=Quaternion.Euler(0,180,0);
137            isFaceRight=false;
138        }else{
139            transform.rotation=Quaternion.Euler(0,0,0);
140            isFaceRight=true;
141        }
142    }
143}

好了,现在我们可以来看看最终的效果,博主这里是想利用这些素材来制作一个横板过关的游戏,可是因为文章篇幅有限,所以这部分内容只能留到以后再和大家分享了。

最终效果演示
最终效果演示

Mecanim 动画系统应用扩展

好了,到现在为止,基于 Mecanim 动画系统的 2D 动画控制基本上讲解完了。下面我们说说 Mecaanim 动画系统应用扩展。通过前面的学习,我们知道 Unity2D 使用的 Mecanim 动画系统主要是通过改变游戏体的属性来实现某种特定的动画效果的,例如我们这里的动画是通过改变角色精灵附加的 SpriteRenderer 组件的 Sprite 属性来实现的,因此从本质上来说 Unity2D 的动画控制器是一种属性动画。总体来说,Unity2D 可以实现以下类型的动画:

  • 位移动画:通过 Transform 组件的 Position 属性实现
  • 旋转动画:通过 Transform 组件的 Rotation 属性实现
  • 伸缩动画:通过 Transform 组件的 Scale 属性实现
  • 渐变动画:通过更改指定组件的颜色或材质实现
  • 脚本动画:通过更改指定脚本的变量或字段实现

好了,这就是今天这篇文章的全部内容了,希望大家喜欢!

Built with Hugo
Theme Stack designed by Jimmy