序列帧动画
- 序列帧动画是事先准备好动画中的每一帧画面,然后依次播放。
- 序列帧动画适合卡通风格的2D游戏,特别是“像素风”,更是绝配。
使用 AnimationClip
[[Animation Clips]]
使用 ShaderGraph
使用 Shader 顶点着色器
- 先定义帧动画的总帧数、图元排列的行数和列数,以及播放速度,在顶点着色时就计算好UV,计算过程是通过当前的时间和速度及总帧数,获得当前所在的帧数,再用帧数计算图片所在的行位置和列位置,最后用行位置和列位置计算UV数据,UV数据传入片元后,片元着色器从图片中提取像素颜色,交给后面的步骤进行渲染。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Shader “UVAni”
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {} // 序列帧贴图
_Total ("total", float) = 1 // 总帧数
_Rows ("rows", float) = 1 // 图元行数
_Cols ("cols", float) = 1 // 图元列数
_Fps ("speed", float) = 1 // 播放速度
}
SubShader
{
Pass
{
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
Lighting Off ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float_Total;
float_Rows;
float_Cols;
float_Fps;
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
} ;
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex); // 顶点3D坐标转换为屏幕2D坐标
float floorModTime = floor(fmod( _Time.y * _Fps, _Total));
// 获得帧数
float uIdx = fmod(floorModTime , _Cols); // 获得横坐标索引
float vIdx = _Rows - 1 - floor(floorModTime / _Rows);
// 获得纵坐标索引
o.uv = float2(v.texcoord.x / _Cols + uIdx / _Cols, v.texcoord.
y / _Rows + vIdx / _Rows); // 计算贴图中的UV位置
return o;
}
float4 frag(v2f i) : COLOR
{
float4 texCol = tex2D(_MainTex,i.uv);
return texCol;
}
ENDCG
}
}
}
C#中修改Mesh顶点UV做序列帧动画
1
2
3
4
5
6
7
8
9
10
float row = 4, col = 2;
tileSize = new Vector2(0.5f, 0.25f);
frameCount = 4;
startIndex = 0 or 4;
curIndex = Mathf.FloorToInt(time * speed % frameCount + startIndex);
minUV_x = curIndex % row * tileSize.x;
minUV_y = 1 - (Mathf.FloorToInt(curIndex / col) + 1) * tileSize.y;
maxUV_x = minUV_x + tileSize.x;
maxUV_y = minUV_y + tileSize.y;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
private float playSpeed; // 播放速度
private float row; // 有多少行
private float col; // 有多少列
private List<FlipBookInfo> flipBookInfos;
private bool isDirty;
private float timeElapsed;
private MeshFilter meshFilter;
private Mesh flipBookMesh;
private Vector3[] vertices;
private int verticesCount;
private Vector2[] uvs;
private int uvsCount;
private int[] triangles;
private int trianglesCount;
private Vector2 tileSize; // 每个tile的尺寸 [0~1]
private void Update()
{
timeElapsed += (Time.deltaTime * playSpeed);
if (timeElapsed > 1)
{
// 计算当前时刻每个序列帧动画播放到了第几帧
foreach (var flipBookInfo in flipBookInfos)
{
int tileIndex = Mathf.FloorToInt(timeElapsed % flipBookInfo.frameCount + flipBookInfo.startIndex);
if (tileIndex != flipBookInfo.curIndex)
{
flipBookInfo.curIndex = tileIndex;
isDirty = true;
}
}
}
if (isDirty)
{
RefreshMesh();
isDirty = false;
}
}
private void RefreshMesh()
{
// 初始化
if (meshFilter == null)
{
Init();
}
// 根据flipBookInfo 构建网格
foreach (var flipBookInfo in flipBookInfos)
{
// 拷贝 triangles 和 vertices
var mesh = flipBookInfo.meshFilter.mesh;
Transform trans = flipBookInfo.meshFilter.transform;
for (int i = 0; i < mesh.triangles.Length; i++)
{
triangles[trianglesCount++] = mesh.triangles[i] + verticesCount;
}
for (int i = 0; i < mesh.vertices.Length; i++)
{
vertices[verticesCount++] = trans.TransformPoint(mesh.vertices[i]);
}
// 根据 curIndex 计算 UV
float minUV_x = flipBookInfo.curIndex % row * tileSize.x;
float minUV_y = 1 - (Mathf.FloorToInt(flipBookInfo.curIndex / col) + 1) * tileSize.y;
float maxUV_x = minUV_x + tileSize.x;
float maxUV_y = minUV_y + tileSize.y;
uvs[uvsCount++] = new Vector2(minUV_x, minUV_y); // 左下
uvs[uvsCount++] = new Vector2(minUV_x, maxUV_y); // 左上
uvs[uvsCount++] = new Vector2(maxUV_x, maxUV_y); // 右上
uvs[uvsCount++] = new Vector2(maxUV_x, minUV_y); // 右下
}
// 更新 Mesh
flipBookMesh.Clear();
flipBookMesh.SetVertices(vertices, 0, verticesCount);
flipBookMesh.SetTriangles(triangles, 0, trianglesCount, 0);
flipBookMesh.SetUVs(0, uvs, 0, uvsCount);
verticesCount = 0;
trianglesCount = 0;
uvsCount = 0;
}
private void Init()
{
meshFilter = GetComponent<MeshFilter>();
flipBookMesh = meshFilter.sharedMesh = new Mesh();
flipBookMesh.name = "FlipBook";
vertices = new Vector3[64];
uvs = new Vector2[64];
triangles = new int[64];
tileSize = new Vector2(1 / col, 1 / row);
}