美文网首页
用UGUI开发强制引导功能

用UGUI开发强制引导功能

作者: 卅云川 | 来源:发表于2023-01-18 18:47 被阅读0次

强制引导需要的内容

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

  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的算法,以实现效果、性能上的需求。

相关文章

网友评论

      本文标题:用UGUI开发强制引导功能

      本文链接:https://www.haomeiwen.com/subject/dylfhdtx.html