強(qiáng)制引導(dǎo)需要的內(nèi)容
開(kāi)發(fā)強(qiáng)制引導(dǎo)腻暮,則要求玩家必須點(diǎn)擊指定的地方睦柴、按鈕,否則不予受理捕犬。那么基于這個(gè)通用需求,可以分析出我們核心需要的內(nèi)容主要有兩部分:
- 置灰圖或遮罩圖:我們需要通過(guò)這張圖片酵镜,將屏幕置灰碉碉,并突出需要玩家點(diǎn)擊的按鈕或區(qū)域;
- 非強(qiáng)制引導(dǎo)交互屏蔽:強(qiáng)制引導(dǎo)階段需要屏蔽任何非引導(dǎo)按鈕的點(diǎn)擊等交互操作笋婿。
方案1誉裆,Canvas
使用一張Mask圖片實(shí)現(xiàn)屏幕置灰功能,并屏蔽所有UI交互缸濒。同時(shí)足丢,將需要高亮或突出的按鈕添加Canvas組件,以確保該UI元素不會(huì)被Mask圖片屏蔽庇配。
網(wǎng)上有看到過(guò)這樣的方案斩跌,但是需要手動(dòng)維護(hù)Canvas組件的添加刪除,如果游戲架構(gòu)開(kāi)發(fā)不完善則很可能會(huì)因遺漏導(dǎo)致后續(xù)出現(xiàn)bug捞慌,所以沒(méi)有選用此方案耀鸦。
方案2,事件穿透
使用一張Mask圖片實(shí)現(xiàn)屏幕置灰功能,并屏蔽所有UI交互袖订。使用事件穿透實(shí)現(xiàn)對(duì)指定按鈕交互不受屏蔽影響氮帐。在此基礎(chǔ)上,Mask圖片如果沒(méi)有額外的顯示需求洛姑,則可以直接生成鏤空?qǐng)D片上沐,以實(shí)現(xiàn)置灰其他UI而不置灰指定UI的功能。
后續(xù)開(kāi)發(fā)說(shuō)明以方案2為基準(zhǔn)楞艾。
事件穿透
UGUI的事件穿透参咙,借助EventSystem
就可以實(shí)現(xiàn)。
EventPermeate
代碼如下:
public class EventPermeate : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler
{
/// <summary>
/// 事件穿透對(duì)象
/// </summary>
[HideInInspector] public GameObject target;
/// <summary>
/// 監(jiān)聽(tīng)點(diǎn)擊
/// </summary>
public void OnPointerClick(PointerEventData eventData)
{
PassEvent(eventData, ExecuteEvents.submitHandler);
PassEvent(eventData, ExecuteEvents.pointerClickHandler);
}
/// <summary>
/// 監(jiān)聽(tīng)按下
/// </summary>
public void OnPointerDown(PointerEventData eventData)
{
PassEvent(eventData, ExecuteEvents.pointerDownHandler);
}
/// <summary>
/// 監(jiān)聽(tīng)抬起
/// </summary>
public void OnPointerUp(PointerEventData eventData)
{
PassEvent(eventData, ExecuteEvents.pointerUpHandler);
}
/// <summary>
/// 將事件透下去
/// </summary>
public void PassEvent<T>(PointerEventData data, ExecuteEvents.EventFunction<T> function) where T : IEventSystemHandler
{
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(data, results);
for (int i = 0, imax = results.Count; i < imax; i++)
{
if (target == results[i].gameObject)
{
ExecuteEvents.Execute(results[i].gameObject, data, function);
break;
}
}
}
}
EventPermeate
添加到Mask圖片硫眯,并指定可以穿透的目標(biāo)蕴侧,就可以實(shí)現(xiàn)事件穿透功能。代碼很好理解此處就不再贅述两入。
遮罩圖片的生成
思路
遮罩圖片的生成我使用Image配合自定義Shader實(shí)現(xiàn)净宵。思路如下:
- 繼承Image,重寫(xiě)
OnPopulateMesh
方法谆刨,在這里面去生成我們所需鏤空的形狀塘娶,并對(duì)所有頂點(diǎn)的顏色進(jìn)行賦值- 非鏤空形狀上的頂點(diǎn),正常賦予顏色值
- 鏤空形狀上的頂點(diǎn)痊夭,賦予顏色值,但是alpha值設(shè)為0
- 實(shí)現(xiàn)一個(gè)UGUI的Shader脏里,頂點(diǎn)計(jì)算照舊她我,片元計(jì)算處需要特殊處理:
- 若片元的alpha值為0,則該片元的alpha值就返回0
- 若片元的alpha值不為0迫横,則使用從Image顏色傳遞來(lái)的alpha值
Shader說(shuō)明
下面給出Shader片元算法:
float _MaskAlpha; // 由Image傳入的alpha值
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = i.color;
return fixed4(col.rgb, col.a > 0 ? _MaskAlpha : 0);
}
此處所針對(duì)的需求番舆,是圍繞Mask圖片僅僅是一層半透明遮罩展開(kāi)。如果需要的是一張圖片矾踱,則還需要通過(guò)UV進(jìn)行采樣恨狈,如此一來(lái)在繼承的Image中,每個(gè)頂點(diǎn)需要賦予正確的UV值才可以(計(jì)算很簡(jiǎn)單呛讲,但會(huì)額外增加一些計(jì)算量)禾怠。
Image圖片生成說(shuō)明
覆寫(xiě)OnPopulateMesh
方法生成我們所需要的鏤空?qǐng)D片,核心思路如下(所有生成的三角形保持順時(shí)針):
- 計(jì)算出鏤空部分的AABB贝搁,然后將Image以內(nèi)吗氏、AABB以外的部分按照固定格式生成三角形數(shù)據(jù)
- 限制鏤空部分為凸多邊形,這樣又可以按照以下規(guī)則將AABB內(nèi)的三角形生成出來(lái):
- 尋找凸多邊形的最左點(diǎn)雷逆、最高點(diǎn)弦讽、最右點(diǎn)、最低點(diǎn)
- 從最左點(diǎn)遍歷至最高點(diǎn)膀哲,與AABB左上角依次生成三角形
- 從最高點(diǎn)遍歷至最右點(diǎn)往产,與AABB右上角依次生成三角形
- 從最右點(diǎn)遍歷至最低點(diǎn)被碗,與AABB右下角依次生成三角形
- 從最低點(diǎn)遍歷至最左點(diǎn),與AABB左下角依次生成三角形
- 凸多邊形內(nèi)部以一個(gè)點(diǎn)為起始點(diǎn)仿村,遍歷剩余的點(diǎn)生成這個(gè)凸多邊形的所有三角形
- 尋找凸多邊形的最左點(diǎn)雷逆、最高點(diǎn)弦讽、最右點(diǎn)、最低點(diǎn)
核心算法思路展示如下:
// Image生成核心算法蛮放,rect是Image的Rect,innerPos是需要鏤空的凸多邊形的頂點(diǎn)坐標(biāo)數(shù)據(jù)
private void _PickOutConvex(VertexHelper toFill, Rect rect, List<Vector2> innerPos)
{
toFill.Clear();
// 凸多邊形獲取邊界四點(diǎn)
int left, top, right, bottom;
...
// 頂點(diǎn)數(shù)量
int innerCount = innerPos.Count;
Vector2 min = new Vector2(innerPos[left].x, innerPos[bottom].y);
Vector2 max = new Vector2(innerPos[right].x, innerPos[top].y);
Color maskColor = new Color(color.r, color.g, color.b, 0);
// 凸多邊形AABB的四個(gè)點(diǎn)的計(jì)算與添加
int nA = toFill.currentVertCount;
toFill.AddVert(new UIVertex() { position = new Vector2(min.x, max.y), color = color });
toFill.AddVert(new UIVertex() { position = max, color = color });
toFill.AddVert(new UIVertex() { position = new Vector2(max.x, min.y), color = color });
toFill.AddVert(new UIVertex() { position = min, color = color });
// 凸多邊形頂點(diǎn)數(shù)據(jù)添加
int ni = toFill.currentVertCount;
for (int i = 0; i < innerCount; i++) toFill.AddVert(innerPos[i], maskColor, Vector4.zero);
// 從最左點(diǎn)遍歷至最高點(diǎn)奠宜,與AABB左上角依次生成三角形
for (int i = left, imax = left > top ? top + innerCount : top; i < imax; i++)
{
int index1 = i % innerCount + ni;
int index2 = (i + 1) % innerCount + ni;
toFill.AddTriangle(nA, index2, index1);
}
// 從最高點(diǎn)遍歷至最右點(diǎn)包颁,與AABB右上角依次生成三角形
...
// 從最右點(diǎn)遍歷至最低點(diǎn),與AABB右下角依次生成三角形
...
// 從最低點(diǎn)遍歷至最左點(diǎn)压真,與AABB左下角依次生成三角形
...
// 凸多邊形內(nèi)部三角形的構(gòu)建
for (int i = 1, imax = innerCount - 1; i < imax; i++) toFill.AddTriangle(ni, ni + i, ni + i + 1);
// Image的四個(gè)頂點(diǎn)的添加
int nX = toFill.currentVertCount;
toFill.AddVert(new UIVertex() { position = new Vector2(rect.xMin, rect.yMax), color = color }); // 0
toFill.AddVert(new UIVertex() { position = rect.max, color = color }); // 1
toFill.AddVert(new UIVertex() { position = new Vector2(rect.xMax, rect.yMin), color = color }); // 2
toFill.AddVert(new UIVertex() { position = rect.min, color = color }); // 3
// Image到凸多邊形AABB空間的三角形生成
toFill.AddTriangle(nX + 0, nX + 1, nA + 1);
toFill.AddTriangle(nA + 1, nA + 0, nX + 0);
...
}
有了上述方法后娩嚼,剩下的就是在OnPopulateMesh
方法內(nèi),根據(jù)指定的幾種固定形狀滴肿,生成他們的頂點(diǎn)數(shù)據(jù)岳悟,再傳入_PickOutConvex
方法,即可獲得所需的Mask圖片泼差。
可優(yōu)化
這套實(shí)現(xiàn)贵少,已經(jīng)完成了這套方案下所有的核心點(diǎn)。根據(jù)不同的應(yīng)用場(chǎng)景堆缘,可以擴(kuò)展shader或是Image的算法滔灶,以實(shí)現(xiàn)效果、性能上的需求吼肥。