根據Shader動態生成遮罩
源碼地址
git
圓形遮罩鏤空處理腳本:github


using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /// <summary> /// 圓形遮罩鏤空 /// </summary> public class CircleGuidance : MonoBehaviour { public static CircleGuidance instance; /// <summary> /// 高亮顯示目標 /// </summary> public Image target; /// <summary> /// 區域範圍緩存 /// </summary> private Vector3[] corners = new Vector3[4]; /// <summary> /// 鏤空區域中心 /// </summary> private Vector4 center; /// <summary> /// 鏤空區域半徑 /// </summary> private float radius; /// <summary> /// 遮罩材質 /// </summary> private Material material; /// <summary> /// 當前高亮區域半徑 /// </summary> private float currentRadius; /// <summary> /// 高亮區域縮放的動畫時間 /// </summary> private float shrinkTime = 0.5f; /// <summary> /// 事件滲透組件 /// </summary> private GuidanceEventPenetrate eventPenetrate; private void Awake() { instance = this; } public void Init(Image target) { this.target = target; eventPenetrate = GetComponent<GuidanceEventPenetrate>(); if (eventPenetrate != null) { eventPenetrate.SetTargetImage(target); } Canvas canvas = GameObject.Find("Canvas").GetComponent<Canvas>(); //獲取高亮區域的四個頂點的世界座標 target.rectTransform.GetWorldCorners(corners); //計算最終高亮顯示區域的半徑 radius = Vector2.Distance(WorldToCanvasPos(canvas, corners[0]), WorldToCanvasPos(canvas, corners[2])) / 2; //計算高亮顯示區域的中心 float x = corners[0].x + ((corners[3].x - corners[0].x) / 2); float y = corners[0].y + ((corners[1].y - corners[0].y) / 2); Vector3 centerWorld = new Vector3(x, y, 0); Vector2 center = WorldToCanvasPos(canvas, centerWorld); //設置遮罩材質中的中心變量 Vector4 centerMat = new Vector4(center.x, center.y, 0, 0); material = GetComponent<Image>().material; material.SetVector("_Center", centerMat); //計算當前高亮顯示區域的半徑 RectTransform canRectTransform = canvas.transform as RectTransform; if (canRectTransform != null) { //獲取畫布區域的四個頂點 canRectTransform.GetWorldCorners(corners); //將畫布頂點距離高亮區域中心最近的距離昨晚當前高亮區域半徑的初始值 foreach (var corner in corners) { currentRadius = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corner), corner), currentRadius); } } material.SetFloat("_Slider", currentRadius); } /// <summary> /// 收縮速度 /// </summary> private float shrinkVelocity = 0f; private void Update() { //從當前半徑到目標半徑差值顯示收縮動畫 float value = Mathf.SmoothDamp(currentRadius, radius, ref shrinkVelocity, shrinkTime); if (!Mathf.Approximately(value, currentRadius)) { currentRadius = value; material.SetFloat("_Slider", currentRadius); } } /// <summary> /// 世界座標轉換爲畫布座標 /// </summary> /// <param name="canvas">畫布</param> /// <param name="world">世界座標</param> /// <returns></returns> private Vector2 WorldToCanvasPos(Canvas canvas, Vector3 world) { Vector2 position; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, world, canvas.GetComponent<Camera>(), out position); return position; } }
矩形遮罩鏤空處理腳本:canvas


using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /// <summary> /// 矩形遮罩鏤空 /// </summary> public class RectGuidance : MonoBehaviour { public static RectGuidance instance; /// <summary> /// 高亮顯示目標 /// </summary> public Image target; /// <summary> /// 區域範圍緩存 /// </summary> private Vector3[] corners = new Vector3[4]; /// <summary> /// 鏤空區域中心 /// </summary> private Vector4 center; /// <summary> /// 最終的偏移x /// </summary> private float targetOffsetX = 0; /// <summary> /// 最終的偏移y /// </summary> private float targetOffsetY = 0; /// <summary> /// 遮罩材質 /// </summary> private Material material; /// <summary> /// 當前的偏移x /// </summary> private float currentOffsetX = 0f; /// <summary> /// 當前的偏移y /// </summary> private float currentOffsetY = 0f; /// <summary> /// 高亮區域縮放的動畫時間 /// </summary> private float shrinkTime = 0.5f; /// <summary> /// 事件滲透組件 /// </summary> private GuidanceEventPenetrate eventPenetrate; private void Awake() { instance = this; } public void Init(Image target) { this.target = target; eventPenetrate = GetComponent<GuidanceEventPenetrate>(); if (eventPenetrate != null) { eventPenetrate.SetTargetImage(target); } Canvas canvas = GameObject.Find("Canvas").GetComponent<Canvas>(); //獲取高亮區域的四個頂點的世界座標 target.rectTransform.GetWorldCorners(corners); //計算高亮顯示區域在畫布中的範圍 targetOffsetX = Vector2.Distance(WorldToCanvasPos(canvas, corners[0]), WorldToCanvasPos(canvas, corners[3])) / 2f; targetOffsetY = Vector2.Distance(WorldToCanvasPos(canvas, corners[0]), WorldToCanvasPos(canvas, corners[1])) / 2f; //計算高亮顯示區域的中心 float x = corners[0].x + ((corners[3].x - corners[0].x) / 2); float y = corners[0].y + ((corners[1].y - corners[0].y) / 2); Vector3 centerWorld = new Vector3(x, y, 0); Vector2 center = WorldToCanvasPos(canvas, centerWorld); //設置遮罩材質中的中心變量 Vector4 centerMat = new Vector4(center.x, center.y, 0, 0); material = GetComponent<Image>().material; material.SetVector("_Center", centerMat); //計算當前高亮顯示區域的半徑 RectTransform canRectTransform = canvas.transform as RectTransform; if (canRectTransform != null) { //獲取畫布區域的四個頂點 canRectTransform.GetWorldCorners(corners); //計算偏移初始值 for (int i = 0; i < corners.Length; i++) { if (i % 2 == 0) { currentOffsetX = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corners[i]), center), currentOffsetX); } else { currentOffsetY = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corners[i]), center), currentOffsetY); } } } //設置遮罩材質中當前偏移的變量 material.SetFloat("_SliderX", currentOffsetX); material.SetFloat("_SliderY", currentOffsetY); } /// <summary> /// 收縮速度 /// </summary> private float shrinkVelocityX = 0f; private float shrinkVelocityY = 0f; private void Update() { //從當前偏移量到目標偏移量差值顯示收縮動畫 float valueX = Mathf.SmoothDamp(currentOffsetX, targetOffsetX, ref shrinkVelocityX, shrinkTime); float valueY = Mathf.SmoothDamp(currentOffsetY, targetOffsetY, ref shrinkVelocityY, shrinkTime); if (!Mathf.Approximately(valueX, currentOffsetX)) { currentOffsetX = valueX; material.SetFloat("_SliderX", currentOffsetX); } if (!Mathf.Approximately(valueY, currentOffsetY)) { currentOffsetY = valueY; material.SetFloat("_SliderY", currentOffsetY); } } /// <summary> /// 世界座標轉換爲畫布座標 /// </summary> /// <param name="canvas">畫布</param> /// <param name="world">世界座標</param> /// <returns></returns> private Vector2 WorldToCanvasPos(Canvas canvas, Vector3 world) { Vector2 position; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, world, canvas.GetComponent<Camera>(), out position); return position; } }
新手引導管理腳本,經過此腳本管理遮罩跟引導步驟,動態添加按鈕點擊事件等:數組
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /// <summary> /// 新手引導管理 /// </summary> public class GuideManagers : MonoBehaviour { /// <summary> /// 引導步驟數組(如:第一步-》第二步。。。。) /// </summary> public List<GuideUIList> guideList = new List<GuideUIList>(); /// <summary> /// 當前數組索引 /// </summary> private int currentIndex = 0; /// <summary> /// 是否完成全部的新手引導 /// </summary> private bool isFinish = false; /// <summary> /// 遮罩對象 /// </summary> private GameObject maskPrefabs; /// <summary> /// /// </summary> public void Next() { if (isFinish || currentIndex > guideList.Count) { return; } //註銷上一步按鈕點擊事件 if (currentIndex != 0 && guideList[currentIndex - 1].go.GetComponent<EventTriggerListener>() != null) { EventTriggerListener.GetListener(guideList[currentIndex - 1].go).onClick -= null; } if (maskPrefabs == null) { maskPrefabs = Instantiate(Resources.Load<GameObject>("RectGuidance_Panel"), this.transform); } //初始化遮罩 maskPrefabs.GetComponent<RectGuidance>().Init(guideList[currentIndex].go.GetComponent<Image>()); ; currentIndex++; //給當前按鈕添加點擊事件 if (currentIndex < guideList.Count) { EventTriggerListener.GetListener(guideList[currentIndex - 1].go).onClick += (go) => { Next(); }; } //最後一個按鈕點擊事件處理 else if (currentIndex == guideList.Count) { EventTriggerListener.GetListener(guideList[currentIndex - 1].go).onClick += (go) => { maskPrefabs.gameObject.SetActive(false); //註銷最後一個按鈕的點擊事件 EventTriggerListener.GetListener(guideList[currentIndex - 1].go).onClick -= null; }; isFinish = true; } } } /// <summary> /// 引導ui數組 /// </summary> [Serializable] public class GuideUIList { /// <summary> /// 引導步驟對象 /// </summary> public GameObject go; public GuideUIList(GameObject go) { this.go = go; } }