Unity 翻页特效实现

发表于2017-03-22
评论8 1.4w浏览

Unity 翻页特效实现

游戏中都会有一些特效存在,这样才能吸引到玩家,就拿翻页特效这个特效来说,有了它,游戏场景中会变的既有特点又有趣,下面我们就来看看在unity中翻页特效的实现方法。

需求介绍:

我们是一款战棋类卡牌游戏,UI系统中有一个卡库界面,为了让页面切换有一个比较生动的过程,策划希望采用翻页效果。我们的卡库界面如下:

在页面切换时,出现如下的翻页效果:

实现过程:

AssetStore上其实有较多翻页特效插件,比较知名的有MegaFiers,不过我们最终还是不打算使用它们,大致有三个原因:一是,第三方插件的翻页效果会有一定的效率问题,没有专门为其做优化,比如页面的pool。尤其是MegaFiers,在变形网格过程中居然会不断的创建新顶点,效率低下可想而知。二是,第三方插件提供的翻页特效参数不够丰富,页面表现不够生动。三是,在不是特别了解第三方插件的情况下,尤其是我们只是用其中很小一部分功能的情况下,引入一个大包,容易引起整体工程的不稳定性。所以,干脆自己重新实现一个翻页特效,这样比较容易掌控效果表现和性能优化。

一,        翻页过程建模:

要实现网格平面的翻页过程,首先得对这个过程做数学建模:

(相关论文PDFhttp://www2.parc.com/istl/groups/uir/publications/items/UIR-2004-10-Hong-DeformingPages.pdf

我们要模拟一本书上的页面从一面翻向另外一面,这期间有两个过程:

1,翻转:页面以书本的装订线为轴,翻动页面从0180角度的过程。

2,变形:页面在翻动过程中卷曲的表现。

翻转很简单,如果不考虑变形,就是直接将一个页面绕装订线从0角度翻转到180度,可以很好的模拟硬板页面的翻页过程,如下图所示:

但对于薄纸页面,这样做就会显得比较生硬,所以需要配合翻转加上变形的过程才会比较真实。翻页的实现主要难点在于页面变形的推导,我们下面重点来分析其数学模型。

页面在翻动变形的时候,好像围绕着一个圆锥体旋转,所以我们使用圆锥体来模拟页面卷曲的过程。如下图所示:

上图黄色平面为书本展开的平面,右半边为翻转角度为0度的页面(即翻页的起始角度),左半边为翻转角度为180度的页面(即翻页的最终角度)。上图将一个半角为θ的圆锥体放到两页面中间,圆锥体表面与书本装订线相交于y轴(向上为正),x轴与书本下边缘同轴(向左为正),顶点A为圆锥体顶点。

将上图几何内容细化,并做一些辅助线,如下图:

 

页面的卷曲是随θ的变化而变化的,而θ是通过时间t得到的,是已知量,假设P点是页面上任意一点,在遍历顶点的过程会得到P点的具体值,所以P点也是已知的。我们最终要求的是P点在圆锥面上的投影T点。以A为圆心,AP为半径(设为R),在xy平面上画一个圆Y轴交于点S

圆锥体的几何原理告诉我们,弧会映射到圆锥面上形成另一段等长的弧在与圆锥体底面平行的圆上,圆心为点C,半径为

我们假设角,根据弧长弧度公式得到:

其中:

接下来,我们来求解T点的三个分量

我们先来求

由于C点和S点都在yz平面上,所以过C点可以找到一条直径和x轴平行的直径,且该直径和直线CS垂直,如下图所示:

D点为T点在该直径上的投影,于是得到:

接下来求

换一张yz平面的侧面视图,注意,由于Unity是左手坐标系,所以z轴向下,如下图:

Q点是T点在线SC上的投影,K点为Q点在y轴上的投影,于是得到

其中:

将上述条件代入等式:

最终得到

  

二,   Unity代码实现

有了以上推导结果,代码实现就较为简单了。

首先创建一个类PageMesh,用于负责页面Mesh的创建,它提供两个主要处理函数:

第一个函数BuildMesh

根据传入的网格左下角位置,网格密度信息,页面正反面材质,正面所使用的Texture2D,进行创建网格。如下图:在UI前创建一张页面Mesh

 

这里有几个小细节需要注意:

1         页面Mesh是分正反面的,所以需要分别创建正面和反面的Mesh,如下:

 

int[] ftontTriangles = newint[triangleCount];

int[] backTriangles = newint[triangleCount];

// create page plane mesh

………

// set submesh

mesh.subMeshCount =2;

mesh.SetTriangles(ftontTriangles,0);

mesh.SetTriangles(backTriangles,1);

 

2,由于一本书每页内容都不一样,所以需要至少为每个页面的正面单独创建一个材质实例,如下:

     

      //set sharedMaterials(not materials)

Material [] materials = newMaterial[2];

materials[0] = newMaterial(frontMaterial);

materials[1] = newMaterial(backMaterial);

meshRenderer.sharedMaterials= materials;

 

3,由于翻页之前,需要对页面UI进行截图,保存到Texture2D上,在翻回的时候能够还原上一页内容,所以针对每个PageMesh都在外部创建一个指定为页面大小的Texture2D给其正面材质,如下:

// get page ui rect

Rectrect = ScreenShotManager.GetScreenRect(rectTransform, Camera.main);

 

// create texture2D for aMesh

Texture2Dtex =

newTexture2D((int)rect.width,(int)rect.height,TextureFormat.RGB24, false);

 

// set texture to frontpage material

meshRenderer.sharedMaterials[0].SetTexture("_MainTex", tex);

4UICanvas需要设置为Screen Space – Camera模式,使得页面Mesh通过设置z轴深度显示在UI前面。

第二个函数ReCaculateMesh

根据传入的百分比时间t(范围0-100),来计算页面Mesh上每个顶点在当前时间所对应的位置。其中GetThetaApexRho函数根据传入的t取得Theta(圆锥半角θ),Apex(圆锥顶点y分量值),Rho(当前页面绕装订线翻转的角度(0-180度)),Mesh顶点位置通过这三个值算得。

这里需要注意的细节:

1 GetThetaApexRho内部计算是通过一个外部配置的阶段数组插值得到ThetaApexRho的,这个阶段数组是一个经验值,需要通过不段实验来得到一个较为自然的结果,而且不同的页面宽高比也会有不同的经验数值。大致经验是我们翻书的时候,刚开始是先让页面从展开状态变得卷曲起来,然后翻动过程中,卷曲程度逐渐变小,最后翻到180度时恢复到展开状态。展开状态时Theta90度,卷曲程度越大角度越小,范围在(090)。以下是我们调试过程中得到的一个经验数组,因为Apex的值和Theta的值都是影响页面的卷曲程度,所以固定它的值(1.875f)会更简单些。

如下:

step为当前阶段的百分比值)

publicPageTurnStage[] pageTurnStages =

{

newPageTurnStage(){ step =0.0f, rho = 0.0f, thetaAngle = 90.0f, apexValue =-1.875f },

newPageTurnStage(){ step =50.0f, rho = 0.0f, thetaAngle =10.0f, apexValue = -1.875f },

newPageTurnStage(){ step =70.0f, rho = 50.0f, thetaAngle =10.0f, apexValue = -1.875f },

newPageTurnStage(){ step =90.0f, rho = 110.0f, thetaAngle =15.0f, apexValue = -1.875f },

newPageTurnStage(){ step =100.0f, rho = 180.0f, thetaAngle =90.0f, apexValue = -1.875f },

};

2 CurlTurn函数正是应用了前面推导的页面变形公式,如下:

privateVector3CurlTurn(Vector3p, floattheta, floatapex)

    {

floatR = Mathf.Sqrt((p.x * p.x)+ Mathf.Pow((p.y - apex), 2.0f));

floatr = R * Mathf.Sin(theta);

floatbeta = Mathf.Asin(p.x / R)/ Mathf.Sin(theta);

p.x = r * Mathf.Sin(beta);

p.y =(R + apex) - ((r * (1 - Mathf.Cos(beta)))* Mathf.Sin(theta));

p.z = -(r * (1 - Mathf.Cos(beta))) * Mathf.Cos(theta);

returnp;

    }

在页面变形之后,再对整个PageMesh物体进行翻转变换,即得到最终的顶点位置:

transform.eulerAngles

= newVector3(transform.eulerAngles.x, rho, transform.eulerAngles.z);

3,  优化细节:因为页面Mesh顶点位置的变形和翻转计算是每帧进行的,所以这个过程如果不断创建新的顶点会造成很大的性能和GC问题,上述提到的MeshFiers的翻页功能就存在这个问题。所以,在创建页面Mesh的时候,在原始位置顶点数组originVertexes的基础上,我们会多创建一个顶点数组deformateVetexes,每帧通过ThetaRhoApex以及originVertexes位置得到该时间点对应的变换后的顶点数组deformateVetexes

 // create two vetexarray

privateVector3[] originVertexes;

privateVector3[] deformateVetexes;

originVertexes = newVector3[vetexCount];

......// create originvertexes

deformateVetexes = newVector3[vetexCount];

// every frame calculatedeformateVetexes

deformateVetexes = f(originVertexes,theta, rho, apex);

meshFilter.mesh.vertices = deformateVetexes;

有了PageMesh之后,我们还需要有一个PageMeshManager对其进行内存池管理,以及截图流程的处理,这里就不细讲了。为了方便使用,PageMeshManager直接挂在某个矩形形状的UI上,配置好翻页时间,页面材质、翻页音效以及翻页阶段的经验数组,就能直接展示翻页特效。对于不同质地的页面,寻找其对应的音效,能够让翻页过程显得更加真实,配置如下:

效果动画:

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引