UIScan
Shader "URP/UIScan"
{
    Properties
    {
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {}
        _Color ("Color",Color) = (1,1,1,1)
        [Toggle] _Reverse("Reverse",Integer) = 0
        
        
        [Header(SCAN)]
        _ScanIntensity("Scan Intensity",Range(0,2)) = 1
        _ScanWidth("Scan Width",Range(0,0.5)) = 0.1
        _ScanAngle("Scan Angle",Range(-89,89)) = 0 // 水平为0
        _Feathering("Feathering",Range(0,1)) = 0
        
        [Header(TIME)]
        _TimeDuration("Time Duration",Range(1,5)) = 1
        _TimeInterval("Time Interval",Range(0,5)) = 1
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Transparent"
            "RenderPipeline"="UniversalPipeline"
            "IgnoreProjector"="true"
            "Queue"="Transparent" // Transparent AlphaTest Geometry Background
        }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            HLSLPROGRAM


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

            #pragma vertex Vert
            #pragma fragment Frag



            TEXTURE2D_X(_MainTex);
            SAMPLER(sampler_MainTex);

            CBUFFER_START(UnityPerMaterial)
            half4 _MainTex_ST;


            half4 _Color;
            
            half _ScanIntensity;
            half _ScanWidth;
            half _ScanAngle;
            half _Feathering;

            half _TimeDuration;
            half _TimeInterval;
            int _Reverse;
            CBUFFER_END

            
            struct Attributes
            {
                half4 position : POSITION;
                half2 uv : TEXCOORD0;
                half4 color : COLOR;
            };


            struct Varyings
            {
                half4 position_sv : SV_POSITION;
                half2 uv : TEXCOORD0;
                half4 color : COLOR;
            };


            Varyings Vert(Attributes IN)
            {
                Varyings OUT;
                OUT.position_sv = TransformObjectToHClip(IN.position);
                OUT.uv = TRANSFORM_TEX(IN.uv,_MainTex);
                OUT.color = IN.color;
                return OUT;
            }



            half4 Frag(Varyings IN) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D_X(_MainTex,sampler_MainTex,IN.uv) * IN.color;

                // 时间的变化周期,时间间隔
                half timey = _Time.y;
                half timey_normal = timey % (_TimeDuration + _TimeInterval) / _TimeDuration;
                timey_normal = saturate(timey_normal);


                // 扫描效果
                _ScanAngle = _Reverse==0 ? _ScanAngle : _ScanAngle * -1;
                half k = tan(radians(_ScanAngle));
                half cos_v = cos(radians(_ScanAngle));
                half halfWidth = _ScanWidth / 2 / cos_v;

                half b,y;
                if(k > 0)
                {
                    b = lerp(-halfWidth-k,1+halfWidth,timey_normal);
                    y = k*IN.uv.x + b;
                }
                else
                {
                    b = lerp(-halfWidth,1+halfWidth+abs(k),timey_normal);
                    y = k*IN.uv.x + b;
                }

                
                half y_min = y - halfWidth;
                half y_max = y + halfWidth;
                half uv_y = _Reverse==0 ? IN.uv.y : 1-IN.uv.y;
                if (uv_y >= y_min && uv_y <= y_max)
                {
                    // 羽化效果
                    half4 colAdd = _Color * _ScanIntensity;
                    half feather = smoothstep(halfWidth,0,abs(uv_y - y));
                    colAdd *= feather;
                    col += colAdd; 
                }
                

                
                return col;
            }
            

            
            ENDHLSL
        }
    }
}
UIGray
Shader "URP/UIGray"
{
    Properties
    {
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Transparent"
            "RenderPipeline"="UniversalPipeline"
            "IgnoreProjector"="true"
            "Queue"="Transparent" // Transparent AlphaTest Geometry Background
        }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            HLSLPROGRAM


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

            #pragma vertex Vert
            #pragma fragment Frag



            TEXTURE2D_X(_MainTex);
            SAMPLER(sampler_MainTex);

            CBUFFER_START(UnityPerMaterial)
            half4 _MainTex_ST;
            CBUFFER_END

            
            struct Attributes
            {
                half4 position : POSITION;
                half2 uv : TEXCOORD0;
            };


            struct Varyings
            {
                half4 position_sv : SV_POSITION;
                half2 uv : TEXCOORD0;
            };


            Varyings Vert(Attributes IN)
            {
                Varyings OUT;
                OUT.position_sv = TransformObjectToHClip(IN.position);
                OUT.uv = TRANSFORM_TEX(IN.uv,_MainTex);
                return OUT;
            }



            half4 Frag(Varyings IN) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D_X(_MainTex,sampler_MainTex,IN.uv);
                const half3 gray_multiply = half3(0.222h, 0.707h, 0.071h);
                col.rgb = dot(col.rgb,gray_multiply);
                return col;
            }
            

            
            ENDHLSL
        }
    }
}
简单截屏
        // 世界坐标转RectTransform本地坐标 
        // RenderMode = Screen Space - Camera
        public static Vector2 WorldPosToRtLocalPos(Vector3 worldpos3, RectTransform rectTransform)
        {
            Camera camera = Camera.main;
            Vector2 screenpos2 = RectTransformUtility.WorldToScreenPoint(camera, worldpos3);
            Vector2 newvec2;
            bool isCan = RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenpos2, camera, out newvec2);
            if (isCan) return newvec2;
            else
            {
                MyLog.Info("[WorldPosToRtLocalPos]定位失败");
                return Vector2.zero;
            }
        }

        // RenderMode = Screen Space - Camera
        public static string Screenshot(Camera camera, RectTransform rectTransform)
        {
            if (camera == null || rectTransform == null)
            {
                MyLog.Error("Camera or rectTransform is null");
                return null;
            }

            var path = Application.persistentDataPath + "/Scrshot";

            if (!MyPlatformUtils.ExistsFileOrDir(path)) MyPlatformUtils.CreateDirectory(path);
            Rect rt = rectTransform.rect;

            RenderTexture rtrender = new RenderTexture(Screen.width, Screen.height, 0);
            Texture2D screenShot = new Texture2D((int)rt.width, (int)rt.height, TextureFormat.RGB24, false);

            camera.targetTexture = rtrender;
            camera.Render();
            RenderTexture.active = rtrender;

            Vector2 scrpos2 = RectTransformUtility.WorldToScreenPoint(camera, rectTransform.position);
            scrpos2.x += rectTransform.pivot.x * rt.width * -1;
            scrpos2.y += rectTransform.pivot.y * rt.height * -1;

            float y = Screen.height - scrpos2.y - rt.height;
            MyLog.Info($"[Screenshot] Pos x:{scrpos2.x},y:{y}"); // 左上角原点

            screenShot.ReadPixels(new Rect(scrpos2.x, y, rt.width, rt.height), 0, 0);
            screenShot.Apply();

            camera.targetTexture = null;
            RenderTexture.active = null;
            GameObject.DestroyImmediate(rtrender);

            byte[] bytes = screenShot.EncodeToJPG();

            String newjpg = path + "/scrshot_" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss") + "_temp.jpg";
            MyPlatformUtils.WriteFile(newjpg, bytes);
            MyLog.Info($"[Screenshot] path:{newjpg}");
            return newjpg;
        }
    }
贝塞尔曲线
        // 贝塞尔曲线2阶  3个定位点
        public static Vector3[] GetBezierCurve2(Vector3 start, Vector3 center, Vector3 end, int count = 21)
        {
            Vector3[] newpos = new Vector3[count];
            var maxidx = count - 1;
            for (int i = 0; i <= maxidx; i++)
            {
                var t = i * 1.0f / maxidx;
                Vector3 aa = start + (center - start) * t;
                Vector3 bb = center + (end - center) * t;
                newpos[i] = aa + (bb - aa) * t;
            }

            return newpos;
        }

        // 贝塞尔曲线3阶 4个定位点
        public static Vector3[] GetBezierCurve3(Vector3 start, Vector3 center, Vector3 center2, Vector3 end, int count = 21)
        {
            Vector3[] newpos = new Vector3[count];
            var maxidx = count - 1;
            for (int i = 0; i <= maxidx; i++)
            {
                var t = i * 1.0f / maxidx;
                Vector3 aa = start + (center - start) * t;
                Vector3 bb = center + (center2 - center) * t;
                Vector3 cc = center2 + (end - center2) * t;

                Vector3 aaa = aa + (bb - aa) * t;
                Vector3 bbb = bb + (cc - bb) * t;
                newpos[i] = aaa + (bbb - aaa) * t;
            }

            return newpos;
        }
适应字符串
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using UnityEngine.UI;
using System;

public class BMTextFit : MonoBehaviour
{
    public enum TextCompProp
    {
        TextLegacy,
        TMP_UI,
        TMP_3D
    }


    [SerializeField] private TextCompProp m_textCompProp;


    
    // 字符换字节宽度限制
    private void FitText1()
    {
        var TComp = GetComponent<Text>();
        var origStr = TComp.text;
        var endIndex = 20;
        
        if (String.IsNullOrEmpty(origStr)) return;
        
        int bytesCount = Encoding.UTF8.GetByteCount(origStr);
        if (bytesCount > endIndex)
        {
            int readyLength = 0;
            float readyWidth = 0;
            for (int i = 0; i < origStr.Length; i++)
            {
                // 中日韩 0x4E00-0x9FFF 字 3字节
                // 标点符号如下范围
                // 0x3000-0x303F 0xFF00-0xFFEF 0xFE10-0xFE1F 0xFE30-0xFE4F 3字节
                // 0x00B7, 0x2014, 0x2026, 0x2018, 0x2019, 0x201C, 0x201D 1字节
                readyLength += Encoding.UTF8.GetByteCount(new [] { origStr[i] });
                if (readyLength == endIndex)
                {
                    origStr = origStr.Substring(0, i + 1) + "...";
                    break;
                }
                else if (readyLength > endIndex)
                {
                    origStr = origStr.Substring(0, i) + "...";
                    break;
                }
            }
        }

        TComp.text = origStr;
    }


    
    // 字符串字节渲染宽度限制
    private void FitText2()
    {
        var TComp = GetComponent<Text>();
        TextGenerator textGenerator = TComp.cachedTextGenerator;
        List<UICharInfo> list_ui_char = new List<UICharInfo>();
        textGenerator.GetCharacters(list_ui_char);
        
        var origStr = TComp.text;
        var maxWidth = 300;
        
        if (String.IsNullOrEmpty(origStr)) return;
        
        float readyWidth = 0;
        for (int i = 0; i < origStr.Length; i++)
        {
            readyWidth += list_ui_char[i].charWidth;
            if (maxWidth == readyWidth)
            {
                origStr = origStr.Substring(0, i + 1) + "...";
                break;
            }
            else if(readyWidth > maxWidth)
            {
                origStr = origStr.Substring(0, i) + "...";
                break;
            }
        }
        
        TComp.text = origStr;
    }
    
    
}


屏幕场景偏移
    // 3D场景中 单指滑动屏幕屏幕映射到世界平面的位移增量(世界)
    // 屏幕上的两个点 转换摄像机 平面(点,法线)
    private static Plane s_plane;
    private static Vector3 s_plane_delta;

    public static Vector3 GetDeltaForPlane(Vector2 screen_start,Vector2 screen_end,Vector3 pos_world,Vector3 normal,Camera camera = null)
    {
        if(!camera) camera = Camera.main;

        Ray ray_a = camera.ScreenPointToRay(screen_start);
        Ray ray_b = camera.ScreenPointToRay(screen_end);
        s_plane.SetNormalAndPosition(normal, pos_world);

        float a_distance;
        float b_distance;
        bool a_is = s_plane.Raycast(ray_a, out a_distance);
        bool b_is = s_plane.Raycast(ray_b, out b_distance);

        s_plane_delta.Set(0,0,0);
        if (a_is && b_is)
        {
            Vector3 pos_a = ray_a.GetPoint(a_distance);
            Vector3 pos_b = ray_b.GetPoint(b_distance);
            s_plane_delta.x = pos_b.x - pos_a.x;
            s_plane_delta.y = pos_b.y - pos_a.y;
            s_plane_delta.z = pos_b.z - pos_a.z;
        }

        return s_plane_delta;
    }