年底了公司展开了一年的工作总结,我同时整理了一下自己的开发框架,整理到ui框架部分顺便想扩充一下动画系统。一直以来我都是用animationcurve+tween去做启动关闭动画,比如坐标移动、大小缩放等。同时市面上大部分应用和游戏也是这种效果,不过仅限于windows系统,如果是mac系统,它那个特有的隐藏显现的动画就很常见了,效果如下:
这效果看起来还可以,主要是相对于常见的ui动画来说,所以我就想去实现一下。
我思考过几种做法:
1.使用transform的scale+animationcurve,这个显然不可能,达不到这种效果
2.美术做一个矩形网格动画,然后将ui面板附着网格动画随着运动,可以完成功能,只需要我们采样出ui面板的当前渲染纹理,然后映射到网格uv上,接着随着运动即可,但是太死板,因为动画是固定的,而且ui面板也并非一个固定的分辨率比例。
3.我自己写shader处理,先采样出ui面板的渲染纹理,然后构建等比例矩形网格,将纹理附着于矩形网格,然后通过特定的算法计算出这个动画,这个比较ok,代码也通用。
首先我们仔细观察这个动画,同时分解出动画的组成部分。我先说一下我的观察结果,大致的组成(并不精准哈,有同学深入研究比我更完善当然更好)如下:
1.矩形变成漏斗形,效果如下:
我们先来想一下怎么处理这种形变,可能有朋友会说,用网格顶点动画,可是你看mac的ui线性平滑度,顶点少了肯定做不到这么平滑,所以我们只能使用uv动画,将这个矩形纹理的uv使用方程做变换,具体的方程组合法我们先看一张分析图:
这里我简化了一下示意图,首先我简化了两边的曲线和底部的质点(我们暂时把A2和B2的交汇点成为质点),当然和macos的原型动画不太相同(macos的原型ui动画是变换成底部titlebar的小的矩形图标,那么大的矩形和小的矩形四个顶点uv是必须通过方程映射的,因为我个人的话unity是缩放到一个质点所以就简化了方程计算过程,当然朋友们可以根据需求去任意修改了,这属于数学问题),那么我们思考一下什么样的方程能够将二维uv矩阵变换二维uv倒三角形,如果我们用一个2x2矩阵是不是很好(没有平移所以不需要用3x3矩阵)?但是仔细一想这个不通用,因为我们只是简化了示意图模型,实际上我们这个“倒三角形”的两边是个曲线的,那么我们分两步走:1.通过A和B和质点得到三角形两条边的方程,那么我们就可以得到任意V坐标轴上A1和B1的uv值。2.得到A1和B1后,可以将原矩形uv线性插值到A1和B1之间,则得到了二维三角形uv数组。
还是写一点计算过程方便理解,如下:
我们计算出两条直线的方程,然后计算V轴上uv坐标,直接写代码加演示吧:
Shader "Custom/MacosUIShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_M1("Mass 1",Range(0,1)) = 0.5
_M2("Mass 2",Range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _M1;
float _M2;
//将矩形uv映射到三角形uv
//x<=x1或x>=x2映射到-1透掉
//x>x1且x<x2映射到0-1
float2 mapuv(float2 uv,float x1,float x2)
{
float y = uv.y;
float x = uv.x;
if(x<=x1)
{
x = -1;
}else if(x>=x2)
{
x = -1;
}else if(x>x1 && x<x2)
{
x = (x-x1)/(x2-x1);
}
return float2(x,y);
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float y = i.uv.y;
float x1 = -_M1*y+_M1;
float x2 = (1-_M2)*y+_M2;
i.uv = mapuv(i.uv,x1,x2);
fixed4 col = tex2D(_MainTex, i.uv);
if(i.uv.x < 0)
{
col.a = 0;
}
return col;
}
ENDCG
}
}
}
演示图:
当然了,如果我们在l1 l2的方程加上sin cos加权,就能做成曲线了,如下:
这里我建议可以简洁的将AP的sin加权用y轴的加权去处理,为了性能考虑,同时效果也差不了多少,代码如下:
fixed4 frag (v2f i) : SV_Target
{
float y = i.uv.y;
float x1 = -_M1*y+_M1;
float sx1 = sin(y*2*PI)*_Power1;
x1 += sx1;
float x2 = (1-_M2)*y+_M2;
float sx2 = sin(y*2*PI)*_Power2;
x2 += sx2;
i.uv = mapuv(i.uv,x1,x2);
fixed4 col = tex2D(_MainTex, i.uv);
if(i.uv.x < 0)
{
col.a = 0;
}
return col;
}
效果如下:
接下来就是uv矩阵收缩到质点就行了(当然macos是将AB点收缩到对应的小矩形图标的A2B2点,这里也是数学算法不同而已,同学们有兴趣扩展一下数学计算就行了,我这里因为只需要收缩到质点就ok),关于如何收缩到质点,我们采用2x2矩阵进行收缩,先写代码:
float2x2 mats = float2x2(
_Sca,0,
0,_Sca
);
i.uv = mul(mats,i.uv);
再frag中先用一个二维矩阵去缩放uv数组,效果如下:
可以看的出来缩放是按(0,0)uv矩阵原点进行的,而我们需要按照质点去缩放,所以还需要一边缩放一边平移,所以我们就需要使用3x3的矩阵去做缩放平移处理,修改一下frag中uv的预处理代码:
float3x3 matt = float3x3
(
_Sca,0,_M1 - _Sca*_M1,
0,_Sca,0,
0,0,0
);
float3 homuv = float3(i.uv.x,i.uv.y,1);
homuv = mul(matt,homuv);
i.uv = float2(homuv.x,homuv.y);
矩阵中_Sca就是缩放值,_M1 - _Sca*_M1,就是随着sca缩放,需要平移的量,大家脑海中想象一下scale不断的放大,漏斗逐渐缩小,其质点向左平移,则需要_M1 - _Sca*_M1向右平移还原质点位置,效果如下:
这样我们大致完成了macos的界面动画的几个组成部分,所以接下来就需要借助一下c#进行动画逻辑处理,当然我们在shader中处理也可以,但不建议这么做,使用CPU语言处理逻辑更加灵活,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class MacOSUILogic : MonoBehaviour
{
[SerializeField] private Material MacOSMat;
[Range(0.1f,2f)]
[SerializeField] public float CombTime = 1f;
[Range(0.1f, 2f)]
[SerializeField] public float PowTime = 1f;
[Range(0.1f, 2f)]
[SerializeField] public float ShrinkTime = 1f;
[SerializeField] public AnimationCurve ShrinkCurve;
[SerializeField] public AnimationCurve ExpandCurve;
void Start()
{
}
private void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 100), "Shrink"))
{
MacOSMat.DOFloat(0.98f, "_M1", CombTime);
MacOSMat.DOFloat(0.98f, "_M2", CombTime);
MacOSMat.DOFloat(0.1f, "_Power1", PowTime);
MacOSMat.DOFloat(0.02f, "_Power2", PowTime);
MacOSMat.DOFloat(50f, "_Sca", ShrinkTime).SetDelay(CombTime).SetEase(ShrinkCurve);
}
if (GUI.Button(new Rect(0, 200, 100, 100), "Expand"))
{
MacOSMat.DOFloat(1f, "_Sca", ShrinkTime).SetEase(ExpandCurve);
MacOSMat.DOFloat(0, "_M1", CombTime).SetDelay(ShrinkTime);
MacOSMat.DOFloat(1, "_M2", CombTime).SetDelay(ShrinkTime);
MacOSMat.DOFloat(0, "_Power1", PowTime).SetDelay(ShrinkTime);
MacOSMat.DOFloat(0, "_Power2", PowTime).SetDelay(ShrinkTime);
}
if (GUI.Button(new Rect(0, 400, 100, 100), "Restore"))
{
MacOSMat.SetFloat("_M1", 0);
MacOSMat.SetFloat("_M2", 1);
MacOSMat.SetFloat("_Power1", 0);
MacOSMat.SetFloat("_Power2", 0);
MacOSMat.SetFloat("_Sca", 1);
}
}
}
我自己也很懒的,所以就用dotween处理了,效果如下:
当然严格来说MacOS的界面动画肯定比我这个算法更加复杂,比如缩放矩阵处理的同时加上曲线浮动采样,达到一种非线性的效果,有兴趣深入的朋友可以自行研究。
现在年前玩路易鬼屋搞得没时间写博客,今天整理年终总结来一篇。