NGUI的新手引导的实现

  先声明一下,UNITY新手,如果说的有不对的地方,欢迎各位大神指正。

  最近在项目需要实现新手引导,最基础的需求就是需要一个带黑色遮罩的引导UI,类似下图这种:

 

  对,就是这么敷衍的UI,因为是我随手做的。

  这里有两个关键点:

  1. 黑色的遮罩要怎么做,才能挡住其他部分而留出目标的按钮

  2.点击事件要怎么处理

  我找到过一种解决方案: 添加一个顶层UI,添加一个黑色半透明的UIWidget,然后COPY一份要引导的空间的GameObject, 然后加到这个引导的UI上,只要这个COPY的GAMEOBJECT的深度比半透明黑的要高就行了。然后收到点击时间后,再去模拟点击原来的控件。这个方案应该是可行的,但是我没有采纳,主要是不想要这个拷贝的步骤,点击事件的模拟也不是一件开心的事情。我决定用另外一个方案,上层的遮罩的黑色半透明区域和boxcolider都只覆盖目标以外的部分,点击事件的话直接添加原来的事件监听里,来省下GameObject的拷贝的过程。

 

  实现我想要的黑色的遮罩的效果,最早我是想用一个笨办法:加4个UIWidget和4个BoxColider,就能彻底围住目标控件。这么做也不是不行,实在是有些丑陋,所幸我发现已经有人分享这样的控件: http://blog.csdn.net/lzdidiv/article/details/72293727

  感谢这位作者,我测试过,效果完全没问题。

  接下来是点击事件的问题。我添加了一个脚本NavControl,用来定位目标控件和做点击事件的添加的:

using System;
using System.Collections.Generic;
using UnityEngine;

public class NavControl : MonoBehaviour
{
    public static List<NavControl> activeControlList = new List<NavControl>();

    public string nodeId;
    public UIWidget targetWidget;

    private UIEventListener.VoidDelegate clickCallback;
    public bool AddOnClickEvent(UIEventListener.VoidDelegate callback)
    {
        if (callback == null)
            return false;

        UIEventListener listener = this.gameObject.GetComponent<UIEventListener>();
        if (listener == null)
        {
            UIToggle toggle = this.gameObject.GetComponent<UIToggle>();
            if (toggle)
            {
                EventDelegate.Add(toggle.onChange, onClick2);
            }
            else
            {
                Debug.LogError(string.Format("onAddClickEvent Can't not find event listener:{0}", this.nodeId));
                return false;
            }
        }
        else
        {
            listener.onClick += onClick;
        }

        clickCallback = callback;
        return true;
    }

    public void RemoveOnClickEvent()
    {
        UIEventListener listener = this.gameObject.GetComponent<UIEventListener>();
        if (listener == null)
        {
            UIToggle toggle = this.gameObject.GetComponent<UIToggle>();
            if (toggle)
            {
                EventDelegate.Remove(toggle.onChange, onClick2);
            }
            else
            {
                Debug.LogError(string.Format("onRemoveClickEvent Can't not find event listener:{0}", this.nodeId));
            }
            return;
        }
        else
        {
            listener.onClick -= onClick;
        }
    }

    public static NavControl getNavControl(string key)
    {
        for(int i = 0; i < activeControlList.Count; ++i)
        {
            if (activeControlList[i].nodeId == key)
            {
                return activeControlList[i];
            }
        }

        return null;
    }

    private void onClick(GameObject go1)
    {
        if (clickCallback == null || targetWidget == null)
            return;

        clickCallback(targetWidget.gameObject);
    }

    private void onClick2()
    {
        if (clickCallback == null || targetWidget == null)
            return;

        clickCallback(targetWidget.gameObject);
    }

    void Awake()
    {
        if (targetWidget == null)
        {
            targetWidget = this.gameObject.GetComponent<UIWidget>();
        }
    }

    void OnEnable()
    {
        activeControlList.Add(this);
    }

    void OnDisable()
    {
        activeControlList.Remove(this);
    }
}

  在AddOnClickEvent函数里面,可以看到我支持了两种情况的点击事件的插入:  UIEventListener和UIToggle, 在我的项目里,这两种已经足够覆盖新手引导的需求,如果有其他的需求,扩展这里的逻辑就是。

  在这个脚本里面,有一个nodeId的变量,nodeId必须是全局唯一的,这个是用到标记新手引导对应的控件。而另外一个成员targetWidget, 是用来做遮罩的对位的,如果没有设置,会默认从当前的GameObject上获取UIWidget

  接下来我写了个贼简单的demo,提供我的思路给大家参考。

  如第一个敷衍的图,上面有三个按钮, 现在我需要引导玩家依次点击按钮111,按钮222和按钮Close, prefabe结构如下:

  在btn1,btn2和btn3上,我都添加NavControl的脚本

这里的NodeId我设置成和GameObject的名字一样,以示区分。

在nav的gameObject下我添加UIMaskWidget的脚本。

TestUI挂上了一个简单的测试脚本,代码如下:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TestNav : MonoBehaviour {
    private GameObject btn1;
    private GameObject btn2;
    private GameObject btn3;
    private GameObject btnStart;
    private UIMaskWidget maskWidget;
    private Queue<string> navQueue = new Queue<string>();

	// Use this for initialization
	void Start () {
        navQueue.Enqueue("btn1");
        navQueue.Enqueue("btn2");
        navQueue.Enqueue("btn3");

        btn1 = gameObject.transform.FindChild("btn1").gameObject;
        btn2 = gameObject.transform.FindChild("btn2").gameObject;
        btn3 = gameObject.transform.FindChild("btn3").gameObject;
        btnStart = gameObject.transform.FindChild("btnStart").gameObject;
        maskWidget = gameObject.transform.FindChild("nav").GetComponent<UIMaskWidget>();

        UIEventListener.Get(btnStart).onClick += (GameObject go1) =>
        {
            btn1.SetActive(true);
            btn2.SetActive(true);
            btn3.SetActive(true);
            maskWidget.gameObject.SetActive(true);

            btnStart.SetActive(false);

            showNextNav();
        };

        UIEventListener.Get(btn1).onClick += (GameObject go) =>
        {

        };

        UIEventListener.Get(btn2).onClick += (GameObject go) =>
        {

        };

        UIEventListener.Get(btn3).onClick += (GameObject go) =>
        {

        };
	}

    void showNextNav()
    {
        if (navQueue.Count == 0)
        {
            maskWidget.gameObject.SetActive(false);
            return;
        }

        string key = navQueue.Dequeue();
        NavControl control = NavControl.getNavControl(key);
        if (control != null)
        {
            if (control.targetWidget != null)
            {
                this.maskWidget.transform.position = control.targetWidget.transform.position;
                this.maskWidget.width = control.targetWidget.width;
                this.maskWidget.height = control.targetWidget.height;

                control.AddOnClickEvent((GameObject btnGo) => {
                    showNextNav();
                    }
                );
            }
        }
    }
	
	// Update is called once per frame
	void Update () {
	
	}
}

  关键的逻辑就在showNextNav这个函数里面, 调用NavControl的静态函数获取目标控件,然后设置UIMaskWidget的位置和遮罩的区域。

  代码比较简单,只是演示我的思路,有疑问或者异议,欢迎讨论。

  我的代码是基于unity5.3.4, ngui 3.8.2, 测试工程链接: https://files.cnblogs.com/files/lyrers/NavControl.zip。

 

posted @ 2018-02-25 23:04  捡贝壳的小孩  阅读(969)  评论(1编辑  收藏  举报