用UGUI开发强制引导功能

强制引导需要的内容

开发强制引导,则要求玩家必须点击指定的地方、按钮,否则不予受理。那么基于这个通用需求,可以分析出我们核心需要的内容主要有两部分:

  1. 置灰图或遮罩图:我们需要通过这张图片,将屏幕置灰,并突出需要玩家点击的按钮或区域;
  2. 非强制引导交互屏蔽:强制引导阶段需要屏蔽任何非引导按钮的点击等交互操作。

方案1,Canvas

使用一张Mask图片实现屏幕置灰功能,并屏蔽所有UI交互。同时,将需要高亮或突出的按钮添加Canvas组件,以确保该UI元素不会被Mask图片屏蔽。

网上有看到过这样的方案,但是需要手动维护Canvas组件的添加删除,如果游戏架构开发不完善则很可能会因遗漏导致后续出现bug,所以没有选用此方案。

方案2,事件穿透

使用一张Mask图片实现屏幕置灰功能,并屏蔽所有UI交互。使用事件穿透实现对指定按钮交互不受屏蔽影响。在此基础上,Mask图片如果没有额外的显示需求,则可以直接生成镂空图片,以实现置灰其他UI而不置灰指定UI的功能。


后续开发说明以方案2为基准。

事件穿透

UGUI的事件穿透,借助EventSystem就可以实现。

EventPermeate代码如下:

public class EventPermeate : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler
{
    /// <summary>
    /// 事件穿透对象
    /// </summary>
    [HideInInspector] public GameObject target;

    /// <summary>
    /// 监听点击
    /// </summary>
    public void OnPointerClick(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.submitHandler);
        PassEvent(eventData, ExecuteEvents.pointerClickHandler);
    }

    /// <summary>
    /// 监听按下
    /// </summary>
    public void OnPointerDown(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.pointerDownHandler);
    }

    /// <summary>
    /// 监听抬起
    /// </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图片,并指定可以穿透的目标,就可以实现事件穿透功能。代码很好理解此处就不再赘述。

遮罩图片的生成

思路

遮罩图片的生成我使用Image配合自定义Shader实现。思路如下:

  1. 继承Image,重写OnPopulateMesh方法,在这里面去生成我们所需镂空的形状,并对所有顶点的颜色进行赋值
    • 非镂空形状上的顶点,正常赋予颜色值
    • 镂空形状上的顶点,赋予颜色值,但是alpha值设为0
  2. 实现一个UGUI的Shader,顶点计算照旧,片元计算处需要特殊处理:
    • 若片元的alpha值为0,则该片元的alpha值就返回0
    • 若片元的alpha值不为0,则使用从Image颜色传递来的alpha值

Shader说明

下面给出Shader片元算法:

float _MaskAlpha; // 由Image传入的alpha值

fixed4 frag(v2f i) : SV_Target
{
    fixed4 col = i.color;
    return fixed4(col.rgb, col.a > 0 ? _MaskAlpha : 0);
}

此处所针对的需求,是围绕Mask图片仅仅是一层半透明遮罩展开。如果需要的是一张图片,则还需要通过UV进行采样,如此一来在继承的Image中,每个顶点需要赋予正确的UV值才可以(计算很简单,但会额外增加一些计算量)。

Image图片生成说明

覆写OnPopulateMesh方法生成我们所需要的镂空图片,核心思路如下(所有生成的三角形保持顺时针):

  1. 计算出镂空部分的AABB,然后将Image以内、AABB以外的部分按照固定格式生成三角形数据
  2. 限制镂空部分为凸多边形,这样又可以按照以下规则将AABB内的三角形生成出来:
    1. 寻找凸多边形的最左点、最高点、最右点、最低点
      1. 从最左点遍历至最高点,与AABB左上角依次生成三角形
      2. 从最高点遍历至最右点,与AABB右上角依次生成三角形
      3. 从最右点遍历至最低点,与AABB右下角依次生成三角形
      4. 从最低点遍历至最左点,与AABB左下角依次生成三角形
    2. 凸多边形内部以一个点为起始点,遍历剩余的点生成这个凸多边形的所有三角形

核心算法思路展示如下:

// Image生成核心算法,rect是Image的Rect,innerPos是需要镂空的凸多边形的顶点坐标数据
private void _PickOutConvex(VertexHelper toFill, Rect rect, List<Vector2> innerPos)
{
    toFill.Clear();
    
    // 凸多边形获取边界四点
    int left, top, right, bottom;
    ...

    // 顶点数量
    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的四个点的计算与添加
    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 });

    // 凸多边形顶点数据添加
    int ni = toFill.currentVertCount;
    for (int i = 0; i < innerCount; i++) toFill.AddVert(innerPos[i], maskColor, Vector4.zero);

    // 从最左点遍历至最高点,与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);
    }

    // 从最高点遍历至最右点,与AABB右上角依次生成三角形
    ...
    // 从最右点遍历至最低点,与AABB右下角依次生成三角形
    ...
    // 从最低点遍历至最左点,与AABB左下角依次生成三角形
    ...

    // 凸多边形内部三角形的构建
    for (int i = 1, imax = innerCount - 1; i < imax; i++) toFill.AddTriangle(ni, ni + i, ni + i + 1);

    // Image的四个顶点的添加
    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方法内,根据指定的几种固定形状,生成他们的顶点数据,再传入_PickOutConvex方法,即可获得所需的Mask图片。

可优化

这套实现,已经完成了这套方案下所有的核心点。根据不同的应用场景,可以扩展shader或是Image的算法,以实现效果、性能上的需求。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容