读一读

可以发现下拉组件打开的内容是复制Template的,所以可以利用这个,创建两个脚本,一个用来接受,一个用来监听是不是打开了。

Dropdown挂载接受脚本,Template挂载监听脚本

image.png


using System;

public class ReceiveDropDownStatus : UIMonoViewComponent
{
    public Action<bool> StatusChange;
}


using Frame.Runtime.UI;

public class ListenDropDownStatus : UIMonoViewComponent
{
    private ReceiveDropDownStatus m_Receive;

    // Start is called before the first frame update
    void Start()
    {
        if (m_Receive == null)
        {
            m_Receive = this.transform.parent.GetComponent<ReceiveDropDownStatus>();
        }

        if (this.gameObject.name == "Dropdown List")
        {
            if (m_Receive.StatusChange != null)
            {
                m_Receive.StatusChange(true);
            }
        }
    }

    private void OnDisable()
    {
        if (this.gameObject.name == "Dropdown List")
        {
            if (m_Receive.StatusChange != null)
            {
                m_Receive.StatusChange(false);
            }
        }
    }
}



ui可以添加canvas并设置overrideSorting和sortingOrder,如果需要交互需要添加GraphicRaycaster组件

3d物体需要修改MeshRenderer的sortingOrder属性。

再利用好深度可以很好的处理好这种叠层的问题,规划好sortingOrder


using UnityEngine;
using UnityEngine.UI;
using Frame.Runtime.UI;

public class SortOrderSet : UIMonoViewComponent
{
    [SerializeField]
    private int SortOrder = 0;

    [SerializeField]
    private int m_UISortOrder = 0;
    [SerializeField]
    private int m_RenderSortOrder = 0;

    public void SetUISortOrder(int order, bool raycast = true )
    {
        Canvas canvas = GetComponent<Canvas>();
        if (canvas == null)
        {
            canvas = gameObject.AddComponent<Canvas>();
        }

        GraphicRaycaster gr = GetComponent<GraphicRaycaster>();
        if (raycast)
        {
            if (gr == null)
            {
                gameObject.AddComponent<GraphicRaycaster>();
            }
        }
        else
        {
            if (gr != null)
            {
                DestroyImmediate(gr);
            }
        }

        m_UISortOrder = order;
        canvas.overrideSorting = true;
        canvas.sortingOrder = m_UISortOrder;
    }

    public void SetRenderSortOrder(int order)
    {
        m_RenderSortOrder = order;
        foreach (var render in transform.GetComponentsInChildren<MeshRenderer>(true))
        {
            render.sortingOrder = m_RenderSortOrder;
        }
    }
}



一、网格重建优化 Rebuild

  1. UI层级关系尽量简单

  2. 动静分离,动态元素单独放到特定的Canvas中

  3. active操作会触发耗时的rebuild


二、合批优化

  1. 同一图集内容尽量放在同一个层级中

  2. 少用Mask,会打断合批且自身额外加2DC

  3. 不要用空的Image,打断合批

  4. 必要时候可以将公用的ui加到需要的图集中


三、OverDraw优化

  1. 禁用看不到的ui的canvas

  2. 实现一个响应Raycast但不参与渲染的EmptyGraphic,用于扩大点击范围等


四、字体

  1. 使用TeshmeshPro替代原来Text

  2. 使用艺术字体


五、其他

  1. 扩展工具禁用Raycast,需要的手动点开


一、创建一个用来控制效果开启和关闭的开关,获取处理过的贴图的管理类

using UnityEngine;

public class GlassCtrl : MonoBehaviour
{
    public static int Iterations = 2;//高斯模糊处理次数
    public static int takeShot = 0;//1只处理一次获取贴图,-1一直处理更新贴图
    public static RenderTexture renderTexture = null;//保存处理过的贴图

    public void StartTakeShot(int width, int height, int takeShot)
    {
        ReleaseRenderTexture();
        renderTexture = new RenderTexture(width, height, 32);
        GlassCtrl.takeShot = takeShot;
    }

    public void StopTakeShot()
    {
        takeShot = 0;
    }

    public void SetIterations(int time)
    {
        Iterations = time;
    }

    public void ReleaseRenderTexture()
    {
        if (renderTexture != null)
        {
            Destroy(renderTexture);
            renderTexture = null;
        }

    }

    public RenderTexture GetShotTexture()
    {
        return renderTexture;
    }
}


二、创建一个ScriptableRenderPass

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class GlassBlurRenderPass : ScriptableRenderPass
{
    private CommandBuffer cmd;
    private string cmdName;
    private RenderTargetHandle dest;
    private Material m_blurMat;
    private RenderTargetIdentifier source { get; set; }
    RenderTargetHandle m_temporaryColorTexture;
    RenderTargetHandle blurredID;
    RenderTargetHandle blurredID2;
    public GlassBlurRenderPass(GlassBlurRenderPassFeature.Settings param)
    {
        renderPassEvent = param.renderEvent;
        cmdName = param.cmdName;
        m_blurMat = param.blurMat;
 
        blurredID.Init("blurredID");
        blurredID2.Init("blurredID2");
    }
    
    public void Setup(RenderTargetIdentifier src, RenderTargetHandle _dest)
    {
        this.source = src;
        this.dest = _dest;
    }
    
     public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (GlassCtrl.takeShot == 0)
            return;
        if (renderingData.cameraData.isSceneViewCamera) return;
        //如果cullingMask非UI层的camera,返回
        // if (!((renderingData.cameraData.camera.cullingMask & 1<<LayerMask.NameToLayer("UI")) > 0))
        //     return;
 
        cmd = CommandBufferPool.Get(cmdName);
        Vector2[] sizes = {
                new Vector2(Screen.width, Screen.height),
                new Vector2(Screen.width / 2, Screen.height / 2),
                new Vector2(Screen.width / 4, Screen.height / 4),
                new Vector2(Screen.width / 8, Screen.height / 8),
            };
 
        RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
 
        opaqueDesc.depthBufferBits = 0;
        cmd.GetTemporaryRT(m_temporaryColorTexture.id, opaqueDesc, FilterMode.Bilinear);
        cmd.Blit(source, m_temporaryColorTexture.Identifier());
        for (int i = 0; i < GlassCtrl.Iterations; ++i)
        {
 
            cmd.GetTemporaryRT(blurredID.id, opaqueDesc, FilterMode.Bilinear);
            cmd.GetTemporaryRT(blurredID2.id, opaqueDesc, FilterMode.Bilinear);
 
            cmd.Blit(m_temporaryColorTexture.Identifier(), blurredID.Identifier());
            cmd.SetGlobalVector("offsets", new Vector4(2.0f / sizes[i].x, 0, 0, 0));
            cmd.Blit(blurredID.Identifier(), blurredID2.Identifier(), m_blurMat);
            cmd.SetGlobalVector("offsets", new Vector4(0, 2.0f / sizes[i].y, 0, 0));
            cmd.Blit(blurredID2.Identifier(), blurredID.Identifier(), m_blurMat);
 
            cmd.Blit(blurredID.Identifier(), m_temporaryColorTexture.Identifier());
        }
//把最终内容Blit到一个RenderTexture上。
        cmd.Blit(blurredID.Identifier(), GlassCtrl.renderTexture);
        if (GlassCtrl.takeShot == 1)
            GlassCtrl.takeShot = 0;
        
 
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
        //Debug.LogError("glass pass render");
    }
}


使用的shader为:

Shader "PostEffect/SeparableGlassBlur" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "" {}
	}

	HLSLINCLUDE
	
	#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

	struct appdata_img {
		float4 vertex:POSITION;
		float2 texcoord:TEXCOORD0;
	};
	struct v2f {
		float4 pos : POSITION;
		float2 uv : TEXCOORD0;

		float4 uv01 : TEXCOORD1;
		float4 uv23 : TEXCOORD2;
		float4 uv45 : TEXCOORD3;
	};
	
	float4 offsets;
	
	sampler2D _MainTex;
	
	v2f vert (appdata_img v) {
		v2f o;
		VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
		o.pos = vertexInput.positionCS;
		o.uv.xy = v.texcoord.xy;

		o.uv01 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1);
		o.uv23 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 2.0;
		o.uv45 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 3.0;
		return o;
	}
	
	half4 frag (v2f i) : COLOR {
		half4 color = float4 (0,0,0,0);

		color += 0.40 * tex2D (_MainTex, i.uv);
		color += 0.15 * tex2D (_MainTex, i.uv01.xy);
		color += 0.15 * tex2D (_MainTex, i.uv01.zw);
		color += 0.10 * tex2D (_MainTex, i.uv23.xy);
		color += 0.10 * tex2D (_MainTex, i.uv23.zw);
		color += 0.05 * tex2D (_MainTex, i.uv45.xy);
		color += 0.05 * tex2D (_MainTex, i.uv45.zw);
		
		return color;
	}

	ENDHLSL
	
Subshader {
 Pass {
	  ZTest Always Cull Off ZWrite Off

      HLSLPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      ENDHLSL
  }
}

}


三、创建一个ScriptableRendererFeature

using UnityEngine;
using UnityEngine.Rendering.Universal;
 
public class GlassBlurRenderPassFeature : ScriptableRendererFeature
{
//设置一个RendererFeature的配置,写上要用的参数
    [System.Serializable]
    public class Settings
    {
        public RenderPassEvent renderEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        public LayerMask layerMask = -1;
        public Material blurMat;
        public string textureName = "";
        public string cmdName = "";
        public string passName = "";
    }
 
    GlassBlurRenderPass m_ScriptablePass;
    private RenderTargetHandle dest;
    public Settings settings; //这个在可视化中设置
    public override void Create()
    {
        m_ScriptablePass = new GlassBlurRenderPass(settings);
        m_ScriptablePass.renderPassEvent = settings.renderEvent;
        
    }
 
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        var src = renderer.cameraColorTarget;
        dest = RenderTargetHandle.CameraTarget;
//把camera渲染到的画面src 传入GlassBlurRenderPass里。
        m_ScriptablePass.Setup(src,this.dest);
//注入队列
        renderer.EnqueuePass(m_ScriptablePass);
    }
}


四、将ScriptableRendererFeature添加到Render Feature,填写自己定义的参数内容

image.png

image.png

image.png


五、业务控制和注意

通过逻辑控制GlassCtrl然后获取贴图就可以了。注意的是URP这个处理只对主相机渲染的内容进行处理(Base Camera),对添加到主相机下面的Overlay相机不会影响,所以必要时刻需要调整需要玻璃化的相机为主相机。一般都是背景做毛玻璃效果,内容还是在上面的,所以这应该不会有问题。



using UnityEngine;
using TMPro;

public class DynamicTMP : MonoBehaviour
{
    public TMP_Text tmp;
    public float Strength = 3.6f;
    public float Space = 0.03f;
    public float Frequently = 2f;

    void Update()
    {
        tmp.ForceMeshUpdate();
        var textInfo = tmp.textInfo;
        for (int i = 0; i < textInfo.characterCount; i++)
        {
            var charInfo = textInfo.characterInfo[i];
            if(!charInfo.isVisible) continue;

            var verts = textInfo.meshInfo[charInfo.materialReferenceIndex].vertices;
            for (int j = 0; j < 4; j++)
            {
                var orig = verts[charInfo.vertexIndex + j];
                verts[charInfo.vertexIndex + j] = orig + new Vector3(0, Mathf.Sin(Time.time * Frequently + orig.x * Space) * Strength, 0);
            }
        }

        for (int i = 0; i < textInfo.meshInfo.Length; i++)
        {
            var meshInfo = textInfo.meshInfo[i];
            meshInfo.mesh.vertices = meshInfo.vertices;
            tmp.UpdateGeometry(meshInfo.mesh, i);
        }
    }
}



public static bool IsEmoji(Char c)
{
    var _unicodeCategory = char.GetUnicodeCategory(c);

    switch (_unicodeCategory)
    {
        case UnicodeCategory.OtherSymbol:
        case UnicodeCategory.Surrogate:
        case UnicodeCategory.ModifierSymbol:
        case UnicodeCategory.NonSpacingMark:
        {
            return true;
        }
        default:
        {
            return false;
        }
    }
}



选中TextmeshPro创建的Font Asset下的Material

在Inspector中点击设置按钮-Create Material Preset

调整新创建出来的材质属性,在需要的TextMesh组件中设置Material Preset

image.png


从右往左的语言。

NBidi download | SourceForge.net


using MiniJSON;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;

public class TextMeshFontAssetManager : MonoSingleton<TextMeshFontAssetManager>
{
    private Dictionary<string, TMP_FontAsset> AddFontWithPathList = new Dictionary<string, TMP_FontAsset>();

    [DllImport("__Internal")]
    private static extern string __NT_GetSystemFonts();

    public void AddFontAsset(ScriptableObject fontAsset)
    {
        if (CheckFontAsset(fontAsset)) return;
        var def = TMP_Settings.defaultFontAsset;
        def.fallbackFontAssetTable.Add(fontAsset as TMP_FontAsset);
    }

    public void RemoveFontAsset(ScriptableObject fontAsset)
    {
        if (!CheckFontAsset(fontAsset)) return;
        var def = TMP_Settings.defaultFontAsset;
        def.fallbackFontAssetTable.Remove(fontAsset as TMP_FontAsset);
    }

    public bool CheckFontAsset(ScriptableObject fontAsset)
    {
        TMP_FontAsset font = fontAsset as TMP_FontAsset;
        var def = TMP_Settings.defaultFontAsset;
        return def.fallbackFontAssetTable.Contains(font);
    }

    public void AddWithOSFont(List<string> tb)
    {
        //这里是获取到设备上的字体路径列表 提取名字把需要的动态添加
        Dictionary<string, string> fontPaths = new Dictionary<string, string>();
        IEnumerable tempPaths = null;
#if UNITY_IPHONE && !UNITY_EDITOR
        //IOS的Font.GetPathsToOSFonts在2019.4.7之前获取的为空,所以写了个接口获取,接口内容在Unity/IOS笔记本
        string jsonData = __NT_GetSystemFonts(); 
         if (!string.IsNullOrEmpty(jsonData))
         {
             tempPaths = Json.Deserialize(jsonData) as List<object>;
         }
 #else
        tempPaths = Font.GetPathsToOSFonts();
 #endif

        if (tempPaths == null)
            return;
        
        foreach (string path in tempPaths)
        {
            string key = Path.GetFileNameWithoutExtension(path);
            if(!fontPaths.ContainsKey(key))
                fontPaths.Add(key, path); 
        }

        for (int i = 0; i < tb.Count; i++)
        {
            string fontname = tb[i];
            if (fontPaths.ContainsKey(fontname))
            {
                AddFontAssetByFontPath(fontPaths[fontname]);
            }
        }
    }

    //可以从网上下载字体或获取到本地自带字体
    public void AddFontAssetByFontPath(string fontPath)
    {
        if (AddFontWithPathList.ContainsKey(fontPath))
            return;

        Font font = new Font(fontPath);
        TMP_FontAsset tp_font = TMP_FontAsset.CreateFontAsset(font, 20, 2, GlyphRenderMode.SDFAA, 512, 512);
        AddFontAsset(tp_font);
        AddFontWithPathList.Add(fontPath, tp_font);
    }

    public void RemoveFontAssetByFontPath(string fontPath)
    {
        if (!AddFontWithPathList.ContainsKey(fontPath))
            return;

        TMP_FontAsset tp_font = AddFontWithPathList[fontPath];
        RemoveFontAsset(tp_font);
    }

    public int GetSystemLangeuage()
    {
        return (int)Application.systemLanguage;
    }
}



先制作一张包含所有表情的图集,导入Unity,修改图片类型和模式,然后编辑图片,切割为一个一个Sprite表情(可以使用工具自动完成,TexturePacker打包图集和TexturePacker Importer插件将打包的图集转化)

image.png


然后右键大的Sprite,创建出Sprite Asset

image.png


最后,打开TMP Settings,将创建的Sprite Asset设置为默认的表情

image.png


使用:

打开Sprite Asset,可以在SpriteCharacterTable看到一个个的表情和对应的Index

使用的时候输入<sprite=Index>就可以