Unity中有两种类型的动画:基于关键帧的动画和骨骼动画
基于关键帧的动画是最常见的动画形式,也称为帧动画。它将每一帧的动画存储为一个离散的关键帧,然后通过计算每一帧之间的差异来创建动画。这种类型的动画适用于不需要太多交互或程序控制的简单动画。在Unity中,使用Animator来创建基于关键帧的动画。
骨骼动画是通过修改骨骼层次结构中的骨头来实现的。这种类型的动画适用于需要更高程度的交互和程序控制的复杂动画,例如游戏中的角色动画。在Unity中,使用Animation和Animator来创建骨骼动画。
动画中的特定姿势由其关键帧定义。在传统的手绘动画中,关键帧是引导动画师绘制的姿势,是定义动画序列的重要(或关键)图纸。
Unity中有几种方式来制作基于关键帧的动画:
Animation一般用于快速创建简单的动画场景,通过设置若干个关键帧,即可完成。
方法如下 (有点类似于Flash动画制作):
a. 选中目标动画物体,在Inspector面板中点击Add Component按钮,选择Animation组件
b. 在Project面板中创建Animation Clip
c. 在Animation面板中将动画剪辑拖拽到物体的Animation组件上
d. 点击Record按钮开始录制,可调整物体的属性,如位置、旋转、缩放等
e. 点击Stop按钮结束录制
Animator Controller一般用于更复杂的动画制作,它可以创建并维护状态机,并串联起多个Animation,达成复杂动画的制作,这个也可以用于后面骨骼动画的制作。
动画状态机(Animation State Machine),需要考虑以下几个方面:
如图所示,可在Controller上设定动画的状态,entry入口,exit退出等状态,并根据需要配置各个状态的物体的参数,最后可设置他们进入各个状态的条件
DoTween 是由 Demigiant 开发的,被广泛应用于 Unity 游戏开发中。它是一个流行的动画插件,被许多开发者用于创建流畅、高效的动画效果,提升游戏体验。
这里推荐使用DoTweenPro,可以使用可视化的方式,很方便的完成动画制作,而不需要编写代码。它有两种形式可创建动画,DoTweenAnimation和DoTweenPath。
个人理解DoTweenAnimation一般用于对单一物体的状态改变动画制作,而DoTweenPath用于路径动画的快速制作,例如摄像机飞行等。
Animation的配置一览,可快速通过可视化的方式配置各种效果,例如旋转、放大等,并配置延迟模式,时长,缓动,循环,各种事件等
Path配置一览,可以配置固定的物理行动路线以及各个节点的状态。配置与Animation大同小异。
缓动配置一览,这个也很重要
骨骼动画的制作方式与关键帧动画是一样的,但需要注意的是其底层逻辑完全不同,制作前我们最好是了解一下骨骼动画的制作过程。
模型动画中,骨骼动画一般与线性动画对比,线性动画较为简单,一般是通过顶点位置加入关键帧让动画看起来更加平滑,骨骼动画中,模型具有互相连接的骨骼组成骨架结构,通过改变骨骼的朝向和位置来为模型生成动画,从而更加拟真。
一般骨骼动画制作过程为: 蒙皮->关键帧->关键帧之间插值/姿态调整
蒙皮的意思其实就是将网格(mesh)和顶点(vertex)绑定到骨骼上
几个概念:
皮肤(Skin):Mesh
骨骼(Skeleton):骨骼上所在的mesh和vertex随着骨骼一起运动
关节(Joints):骨骼连接处,骨骼可以围绕关节运动
一言以蔽之,将骨骼与标准姿势绑定完成后,动画师通过操纵骨骼来制作其关键帧,然后通过在关键帧之间进行插入值形成整个动画。
那么对于普通程序员,在拿到动画师的成果后应该怎么做呢?
以个人行走举例,一般来说需要的几种骨骼动画类型为,静止(可加入一些闲置时候的动画)、行走,后退,左转,右转,跑步等。
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);
}
}
}
代码很简单,做动画最重要需要尝试
扩展一点内容,以上我们制作的都是CPU Instancing的方式,针对大型游戏等场景,Unity提供了GPU Instancing的方式来提升动画性能(在Web上不可用),其作用如下:
有两种 GPU 加速模式:
1. 缓存每一帧顶点坐标,顶点着色器根据帧数和顶点编号获取顶点数据进行渲染,此为方案 A;
2. 缓存每一帧骨骼变换矩阵,顶点着色器计算蒙皮,此为方案 B;
处理大规模角色,同时不要求动画效果很精细,内存够用的情况,可以采用方案A。
处理逻辑压力大、CPU消耗多并且渲染压力小的情况,可以选择方案B。
插件:GPU Animation Baker Pro
个人也没有相关的制作经验,仅做了了解。如需深入,可以参考文章:
Unity - Manual: GPU instancing