那么这次我们就来围绕实际做游戏开发需要的知识点来学习,游戏开发最重要的是什么?那肯定是战斗玩法了(当然玩法不一定全是战斗,也有其他益智休闲游戏),其中最重要的就是角色的动作操作了,我们玩家控制着角色走路跑动跳跃飞行攻击等,从本质上来看,就是控制着角色这个网格模型做各种网格变换,再细化一点来看就是控制着网格的每个顶点做位移变换。此时,我们就要学习一下到底角色运动的本质是个什么样的,如果小伙伴们搞过3dmax或者maya动画(或者看过公司美术同事搞过),那么就可以看到他们做角色动画,比如要做一个飞行的动作具体的操作过程是:
①.建模一个人物站立的角色Hero网格。
②.在动画面板中将角色Hero站立的网格状态作为A帧
③.修改角色Hero站立状态为展翅扑腾的状态。
④.将角色Hero展翅扑腾的网格状态作为B帧
⑤.使用max自带的动画插值系统对A帧和B帧进行插值
⑥.就得到了角色Hero从站立到展翅的网格补间动画
简单来说就是角色Hero的站立到展翅,无非就是角色Hero网格的运动,美术同事使用动画插值系统,只需要做出角色站立和展翅两种网格状态就可以补间出整个运动状态。
但是我们是程序而不是美术,假如要我们实现这一整套插值动画系统该怎么做呢?下面我逐一分析和还原这一整套系统的实现过程。
首先是要实现角色Hero站立和展翅两套网格,我就用我的名字做为Hero角色网格吧,坐标系、顶点、三角拓扑、uv信息如下图:
顺便实现mesh创建的代码,如下:
-
using System.Collections;
-
using System.Collections.Generic;
-
using UnityEngine;
-
-
[
ExecuteInEditMode]
-
public
class
StandHero :
MonoBehaviour
-
{
-
private
int mWidth =
10;
-
private
int mHeight =
10;
-
-
private Mesh mMesh;
-
-
public
bool mRefresh =
false;
-
-
void Start()
-
{
-
CreateMesh();
-
}
-
-
void Update()
-
{
-
if(mRefresh)
-
{
-
CreateMesh();
-
mRefresh =
false;
-
}
-
}
-
-
private void CreateMesh()
-
{
-
mMesh =
new Mesh();
-
//构建顶点坐标信息
-
List<Vector3> vertices =
new List<Vector3>()
-
{
-
new Vector3(
0,
8,
0),
-
new Vector3(
2,
10,
0),
-
new Vector3(
3,
9,
0),
-
new Vector3(
1,
7,
0),
-
new Vector3(
4,
7,
0),
-
new Vector3(
4,
6,
0),
-
new Vector3(
1,
6,
0),
-
new Vector3(
1,
5,
0),
-
new Vector3(
3,
5,
0),
-
new Vector3(
3,
4,
0),
-
new Vector3(
1,
4,
0),
-
new Vector3(
1,
3,
0),
-
new Vector3(
5,
3,
0),
-
new Vector3(
5,
2,
0),
-
new Vector3(
1,
2,
0),
-
new Vector3(
1,
0,
0),
-
new Vector3(
0,
0,
0),
-
-
new Vector3(
-1,
0,
0),
-
new Vector3(
-1,
2,
0),
-
new Vector3(
-5,
2,
0),
-
new Vector3(
-5,
3,
0),
-
new Vector3(
-1,
3,
0),
-
new Vector3(
-1,
4,
0),
-
new Vector3(
-3,
4,
0),
-
new Vector3(
-3,
5,
0),
-
new Vector3(
-1,
5,
0),
-
new Vector3(
-1,
6,
0),
-
new Vector3(
-4,
6,
0),
-
new Vector3(
-4,
7,
0),
-
new Vector3(
-1,
7,
0),
-
new Vector3(
-3,
9,
0),
-
new Vector3(
-2,
10,
0),
-
new Vector3(
-0,
8,
0),
-
};
-
//构建三角形顺序信息
-
List<
int> triangles =
new List<
int>
-
{
-
0,
1,
2,
-
0,
2,
3,
-
3,
4,
5,
-
3,
5,
6,
-
7,
8,
9,
-
7,
9,
10,
-
11,
12,
13,
-
11,
13,
14,
-
0,
3,
15,
-
0,
15,
16,
-
-
16,
17,
0,
-
17,
29,
0,
-
18,
19,
21,
-
19,
20,
21,
-
22,
23,
25,
-
23,
24,
25,
-
26,
27,
29,
-
27,
28,
29,
-
29,
30,
0,
-
30,
31,
0
-
};
-
//构建uvs,uv信息根据棋盘格坐标系长宽比进行设置
-
List<Vector2> uvs =
new List<Vector2>();
-
for (
int i =
0; i < vertices.Count; i++)
-
{
-
uvs.Add(
new Vector2((
float)(vertices[i].x + mWidth /
2) / (
float)mWidth, (
float)vertices[i].y / (
float)mHeight));
-
}
-
//构建网格
-
mMesh.vertices = vertices.ToArray();
-
mMesh.triangles = triangles.ToArray();
-
mMesh.uv = uvs.ToArray();
-
GetComponent<MeshFilter>().sharedMesh = mMesh;
-
}
-
}
这里我说明一下,前面我们学过拓扑rectangle网格创建,其实不规则图形网格创建也类似,首先画出坐标系统,按照顺时针或者逆时针标明我们的网格所有顶点0-32标号(我用的顺时针,小伙伴使用逆时针也可以,这个看兴趣,其中0和32标号的顶点为重合顶点,目的是为了拓扑三角形编号),再按照顺时针构建三角拓扑信息{0,1,2 0,2,3 ...},最后根据坐标系中角色外接矩形的长宽(10*10)去等比例计算坐标点对应的uv,因为我们的Hero角色是Y轴对称的,所以动点P(x,y)的uv计算公式为((x+width/2)/ width,y / height)。
最后看下实际效果,如下图:
接下来,我们分别创建扑腾翅膀和收回翅膀的网格状态,这个应该就行简单了,只需要把“羊”的两个翅膀的顶点坐标沿着Z轴方向修改就行了,准确来说就是修改标号12,13和19,20的四个网格顶点,代码我就不重复贴了,如下图:
随便搞了两个鬼畜的动作,不过算是看得出来这两个网格状态就是在“扑腾翅膀”。
接下来我们就开始网格插值动画系统的具体动作实现了,这里大家思考一下,虽然我们创建了站立网格A,拍翅网格B,收翅网格C,但是怎么将B和C的网格状态应用到A上呢?也就是说怎么去操作网格A的顶点,当然是CG shader中的vertex顶点函数去干这个事了(我这不是废话吗)!但是具体怎么去干?怎么在vertex顶点函数中对站立网格A的所有顶点进行变换到拍翅网格B或收翅网格C,这里我们就需要将网格B和C的顶点信息传递到网格A承载的CG shader中,具体怎么做和后续怎么再处理,大家花时间思考一下。
具体实现方法我下篇博客就做讲解,因为要学习五线谱了,今天没时间了。
so,接下来我们继续。