Unity(四) 基于关键帧的动画与骨骼动画

Unity中有两种类型的动画:基于关键帧的动画和骨骼动画

基于关键帧的动画是最常见的动画形式,也称为帧动画。它将每一帧的动画存储为一个离散的关键帧,然后通过计算每一帧之间的差异来创建动画。这种类型的动画适用于不需要太多交互或程序控制的简单动画。在Unity中,使用Animator来创建基于关键帧的动画。

骨骼动画是通过修改骨骼层次结构中的骨头来实现的。这种类型的动画适用于需要更高程度的交互和程序控制的复杂动画,例如游戏中的角色动画。在Unity中,使用Animation和Animator来创建骨骼动画。

一、基于关键帧的动画

动画中的特定姿势由其关键帧定义。在传统的手绘动画中,关键帧是引导动画师绘制的姿势,是定义动画序列的重要(或关键)图纸。

Unity中有几种方式来制作基于关键帧的动画:

1. 使用Animation

Animation一般用于快速创建简单的动画场景,通过设置若干个关键帧,即可完成。

方法如下 (有点类似于Flash动画制作):

a. 选中目标动画物体,在Inspector面板中点击Add Component按钮,选择Animation组件

b. 在Project面板中创建Animation Clip

c. 在Animation面板中将动画剪辑拖拽到物体的Animation组件上

d. 点击Record按钮开始录制,可调整物体的属性,如位置、旋转、缩放等

e. 点击Stop按钮结束录制

2. 使用Animator Controller

Animator Controller一般用于更复杂的动画制作,它可以创建并维护状态机,并串联起多个Animation,达成复杂动画的制作,这个也可以用于后面骨骼动画的制作

动画状态机(Animation State Machine),需要考虑以下几个方面:

  • 动画状态:定义动画剪辑和动画状态之间的映射关系。
  • 过渡条件:定义两个动画状态之间的转换条件。
  • 过渡时间:定义两个动画状态之间的过渡时间。
  • 动画层:定义动画状态所在的层级,以及层级之间的权重关系。
  • 动画事件:定义动画状态播放过程中触发的事件

Unity(四) 基于关键帧的动画与骨骼动画_第1张图片

如图所示,可在Controller上设定动画的状态,entry入口,exit退出等状态,并根据需要配置各个状态的物体的参数,最后可设置他们进入各个状态的条件

Unity(四) 基于关键帧的动画与骨骼动画_第2张图片

3. 使用DoTween插件

DoTween 是由 Demigiant 开发的,被广泛应用于 Unity 游戏开发中。它是一个流行的动画插件,被许多开发者用于创建流畅、高效的动画效果,提升游戏体验。

这里推荐使用DoTweenPro,可以使用可视化的方式,很方便的完成动画制作,而不需要编写代码。它有两种形式可创建动画,DoTweenAnimation和DoTweenPath。

个人理解DoTweenAnimation一般用于对单一物体的状态改变动画制作,而DoTweenPath用于路径动画的快速制作,例如摄像机飞行等。

Unity(四) 基于关键帧的动画与骨骼动画_第3张图片

Animation的配置一览,可快速通过可视化的方式配置各种效果,例如旋转、放大等,并配置延迟模式,时长,缓动,循环,各种事件等

Unity(四) 基于关键帧的动画与骨骼动画_第4张图片

Path配置一览,可以配置固定的物理行动路线以及各个节点的状态。配置与Animation大同小异。

Unity(四) 基于关键帧的动画与骨骼动画_第5张图片

缓动配置一览,这个也很重要

二、骨骼动画

1. 概念

骨骼动画的制作方式与关键帧动画是一样的,但需要注意的是其底层逻辑完全不同,制作前我们最好是了解一下骨骼动画的制作过程。

模型动画中,骨骼动画一般与线性动画对比,线性动画较为简单,一般是通过顶点位置加入关键帧让动画看起来更加平滑,骨骼动画中,模型具有互相连接的骨骼组成骨架结构,通过改变骨骼的朝向和位置来为模型生成动画,从而更加拟真。

一般骨骼动画制作过程为: 蒙皮->关键帧->关键帧之间插值/姿态调整 

蒙皮的意思其实就是将网格(mesh)和顶点(vertex)绑定到骨骼上

Unity(四) 基于关键帧的动画与骨骼动画_第6张图片

几个概念:

皮肤(Skin):Mesh

骨骼(Skeleton):骨骼上所在的mesh和vertex随着骨骼一起运动

关节(Joints):骨骼连接处,骨骼可以围绕关节运动

一言以蔽之,将骨骼与标准姿势绑定完成后,动画师通过操纵骨骼来制作其关键帧,然后通过在关键帧之间进行插入值形成整个动画。

2. 制作

那么对于普通程序员,在拿到动画师的成果后应该怎么做呢?

以个人行走举例,一般来说需要的几种骨骼动画类型为,静止(可加入一些闲置时候的动画)、行走,后退,左转,右转,跑步等。

a. 拿到各个状态的fbx模型文件

b. 创建Animator Controller,将fbx文件拖动为到state上

c. 创建变量和状态连线,并且设置条件,例如闲置->行走,行走->向右转弯等,这里配置要尽可能的全面。

d. 添加动画脚本,通过按键或鼠标控制角色,并改变变量。需要配置第一人称摄像头,以及控制角色的脚本

PlayerController

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

public class Player : MonoBehaviour
{
    private float moveSpeed;//摄像机的移动速度
    private Animator Anim;
    private GameObject fpvCamera; // 第一人称摄像头
    void Start()
    {
        moveSpeed = -4f;
        Anim = GetComponent();
        fpvCamera = UnityUtils.FindDeActiveObjectFirstOrDefault("CameraPlayer");
    }

    Vector3 rot = new Vector3(0, 0, 0);

    void Update()
    {
        //鼠键控制移动
        WASD();
        if (Input.anyKey)
        {
            this.GetComponent().constraints = RigidbodyConstraints.FreezeRotation;
        }
        else
        {
            this.GetComponent().constraints = RigidbodyConstraints.FreezeAll;
        }
    }

    /// 
    /// 鼠键控制player移动
    /// 
    void WASD()
    {
        if (Input.GetMouseButton(1))
        {
            if (Input.GetAxis("Mouse X") != 0)
            {
                if (Input.GetAxis("Mouse X") < 0.1f && Input.GetAxis("Mouse X") > -0.1f)
                {
                    return;
                }
                this.fpvCamera.transform.Rotate(new Vector3(0, Input.GetAxis("Mouse X") * Time.fixedDeltaTime * 200, 0));
                //clearArrow(false);
            }
            if (Input.GetAxis("Mouse Y") != 0)
            {
                if (Input.GetAxis("Mouse Y") < 0.1f && Input.GetAxis("Mouse Y") > -0.1f)
                {
                    return;
                }
                this.fpvCamera.transform.Rotate(new Vector3(Input.GetAxis("Mouse Y") * Time.fixedDeltaTime * -200, 0, 0));//摄像机的旋转速度
            }
        }

        if (Input.GetKey(KeyCode.W))
        {
            // this.fpvCamera.transform.Translate(-Vector3.forward * Time.deltaTime * moveSpeed);//移动
        }
        if (Input.GetKeyDown(KeyCode.W))
        {
            Anim.SetBool("Walk", true);
        }
        if (Input.GetKeyUp(KeyCode.W))
        {
            Anim.SetBool("Walk", false);
        }
        if (Input.GetKey(KeyCode.S))
        {
            //gameObject.transform.Translate(Vector3.forward * Time.deltaTime * moveSpeed);//向后。
            // this.fpvCamera.transform.Translate(Vector3.forward * Time.deltaTime * moveSpeed);//向后。
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            Anim.SetBool("Back", true);
        }
        if (Input.GetKeyUp(KeyCode.S))
        {
            Anim.SetBool("Back", false);
        }
        if (Input.GetKey(KeyCode.A))
        {
            transform.RotateAround(transform.position, Vector3.up, 45f * Time.deltaTime * moveSpeed);//旋转。
            this.fpvCamera.transform.RotateAround(transform.position, Vector3.up, 30f * Time.deltaTime * moveSpeed);
        }
        //if (Input.GetKeyDown(KeyCode.A))
        //{
        //    Anim.SetBool("Left", true);
        //}
        //if (Input.GetKeyUp(KeyCode.A))
        //{
        //    Anim.SetBool("Left", false);
        //}
        if (Input.GetKey(KeyCode.D))
        {
            transform.RotateAround(transform.position, Vector3.up, -45f * Time.deltaTime * moveSpeed);//旋转。
            fpvCamera.transform.RotateAround(transform.position, Vector3.up, -30f * Time.deltaTime * moveSpeed);
        }
        //if (Input.GetKeyDown(KeyCode.D))
        //{
        //    Anim.SetBool("Right", true);
        //}
        //if (Input.GetKeyUp(KeyCode.D))
        //{
        //    Anim.SetBool("Right", false);
        //}
        if (Input.GetKeyUp(KeyCode.R))
        {
            Anim.SetBool("Run", false);
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            Anim.SetBool("Run", true);
        }
        if (Input.GetKey(KeyCode.R))
        {
            gameObject.transform.Translate(Vector3.forward * Time.deltaTime * 8);//移动
        }
        if (Input.GetKeyUp(KeyCode.F))
        {
            Anim.SetBool("Magic", false);
        }
        if (Input.GetKeyDown(KeyCode.F))
        {
            Anim.SetBool("Magic", true);
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            transform.position += new Vector3(0, 1, 0);//跳过障碍物
        }
    }
}

FPCamera:从默认摄像机转为第一人称摄像机。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FPS : MonoBehaviour
{
    private GameObject fpvCamera; // 第一人称摄像头
    // Start is called before the first frame update
    void Start()
    {
        fpvCamera = UnityUtils.FindDeActiveObjectFirstOrDefault("CameraPlayer");
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void FPSStart()
    {
        Camera2.enableMainView = !Camera2.enableMainView;
        if (Camera2.enableMainView)
        {
            fpvCamera.SetActive(false);
        }
        else
        {
            fpvCamera.SetActive(true);
        }
    }
}

代码很简单,做动画最重要需要尝试

3. CPU Instancing & GPU Instancing

扩展一点内容,以上我们制作的都是CPU Instancing的方式,针对大型游戏等场景,Unity提供了GPU Instancing的方式来提升动画性能(在Web上不可用),其作用如下:

有两种 GPU 加速模式:

1. 缓存每一帧顶点坐标,顶点着色器根据帧数和顶点编号获取顶点数据进行渲染,此为方案 A;

2. 缓存每一帧骨骼变换矩阵,顶点着色器计算蒙皮,此为方案 B;

处理大规模角色,同时不要求动画效果很精细,内存够用的情况,可以采用方案A。

处理逻辑压力大、CPU消耗多并且渲染压力小的情况,可以选择方案B。

插件:GPU Animation Baker Pro

个人也没有相关的制作经验,仅做了了解。如需深入,可以参考文章:

Unity - Manual: GPU instancing

你可能感兴趣的:(3D开发,unity,游戏引擎,动画,骨骼绑定)