新手引导系统

新手引导是游戏中必不可少的系统。

原理:
1.添加一个灰色的遮罩层
2.高亮显示引导玩家的内容,比如需要点击的按钮
3.显示提示语:“点击此处”

要解决的问题:
1.统一的触发接口
2.统一的引导基类
3.符合开闭原则
4.按钮如何触发原按钮的点击事件,并触发下一步引导
5.如果不是按钮,点击背景触发下一步引导
6.遮罩锯齿(直接提到遮罩上,不会有锯齿)
7.记录是否已引导过


case 1:一个简单引导案例模拟
如图所示,只有一个按钮和一张图片。
1.引导玩家点击按钮
2.高亮图片
3.结束引导

这是一个很常见的引导需求,也是可以说明引导的核心逻辑。

开发步骤:
1.首先需要GuideManager,管理引导是否开启、是否已经引导过、以及创建引导的功能类。
a.需要一个enum,区分不同的类型

public enum EGuideType
{
    TestPanel = 1 << 1,
    TestPanel_2 = 1 << 2,
    TestPanel_3 = 1 << 3,
}

按位存储,可以节约内存。
b.主逻辑

public class GuideManager : MonoBehaviour
{
    private static GuideManager instance;
    public static GuideManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<GuideManager>();
            }
            return instance;
        }
    }

    //功能开关
    public static bool bOpen = true;
    //PlayerPrefs存库的 KEY
    private const string KEY_GUIDE = "GuildRecord";

    //是否需要引导
    public bool Need(EGuideType guideType)
    {
        if (!bOpen)
            return false;

        int data = PlayerPrefs.GetInt(KEY_GUIDE, 0);
        return (data & (int)guideType) != (int)guideType;
    }

    //尝试直接进入引导
    public void TryEnter(EGuideType guideType)
    {
        if (!this.Need(guideType))
            return;

        string typeName = "Guide" + guideType.ToString();
        Assembly assembly = Type.GetType(typeName).Assembly;
        GuideBase guide = assembly.CreateInstance(typeName) as GuideBase;//使用反射代替工厂,符合开闭原则
        guide.Init(this);
        guide.Next();
    }

    //记录引导
    public void Save(EGuideType guideType)
    {
        int data = PlayerPrefs.GetInt(KEY_GUIDE, 0);
        int value = data | (int)guideType;
        PlayerPrefs.SetInt(KEY_GUIDE, value);
        PlayerPrefs.Save();
    }
}

2.引导封装成基类,提取公共方法,易于扩展

public abstract class GuideBase
{
    public int step;
    public GuideManager manager;

    public abstract EGuideType GuideType { get; }

    public virtual void Init(GuideManager guideManager)
    {
        this.step = 0;
        this.manager = guideManager;
    }

    //===========================提取公共方法=====================
    //显示背景遮罩
    public void ShowMask()
    {
        GuideMask.Instance.Show(this);
    }
    //提高Button的物体的层级,统一点击按钮进行下一步引导
    public void RaiseTop(Button buttton)
    {
        GuideMask.Instance.RaiseTopButton(buttton);
    }
    //提高非Button的物体的层级,统一点击背景进行下一步引导
    public void RaiseTop(Transform transform)
    {
        GuideMask.Instance.RaiseTopTransform(transform);
    }
    //设置描述
    public void SetTip(string text, Vector2 position)
    {
        GuideMask.Instance.SetTip(text, position);
    }
    //隐藏背景遮罩
    public void HideMask()
    {
        GuideMask.Instance.Hide();
    }
    //存储
    public void Save()
    {
        GuideManager.Instance.Save(this.GuideType);
    }
    //===========================提取公共方法=====================

    //开启下一步引导,这里预定义了6步,基本可以满足需求
    //IEnumerator可以等几秒动画、网络请求、加载等因素
    public void Next()
    {
        this.step++;
        switch (this.step)
        {
            case 1:
                manager.StartCoroutine(ExecuteStep1());
                break;
            case 2:
                manager.StartCoroutine(ExecuteStep2());
                break;
            case 3:
                manager.StartCoroutine(ExecuteStep3());
                break;
            case 4:
                manager.StartCoroutine(ExecuteStep4());
                break;
            case 5:
                manager.StartCoroutine(ExecuteStep5());
                break;
            case 6:
                manager.StartCoroutine(ExecuteStep6());
                break;
        }
    }

    public virtual IEnumerator ExecuteStep1()
    {
        yield break;
    }
    public virtual IEnumerator ExecuteStep2()
    {
        yield break;
    }
    public virtual IEnumerator ExecuteStep3()
    {
        yield break;
    }
    public virtual IEnumerator ExecuteStep4()
    {
        yield break;
    }
    public virtual IEnumerator ExecuteStep5()
    {
        yield break;
    }
    public virtual IEnumerator ExecuteStep6()
    {
        yield break;
    }
}

3.对于遮罩的逻辑
1.copyBtn,点击触发下一步
2.copyTransform,点击背景触发下一步
3.设置提示问题

public class GuideMask : MonoBehaviour
{
    private static GuideMask instance;
    public static GuideMask Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<GuideMask>();
                instance.btnMask.gameObject.SetActive(true);
                instance.txtTip.gameObject.SetActive(true);
                instance.Hide();
            }
            return instance;
        }
    }

    public Button btnMask;
    private bool canClick;
    public Text txtTip;

    //当前引导
    private GuideBase guide;

    //引导目标的copyBtn
    private Button btnGuide;
    private Button btnGuideCopy;
    //引导目标的copyTr
    private Transform trGuide;
    private Transform trGuideCopy;

    private void Awake()
    {
        btnMask.onClick.AddListener(OnBtnMaskClicked);
    }

    public void Show(GuideBase guide)
    {
        this.guide = guide;
        gameObject.SetActive(true);
    }
    public void Hide()
    {
        this.guide = null;
        gameObject.SetActive(false);
    }

    public void SetTip(string text, Vector2 anchoredPos)
    {
        this.txtTip.text = text;
        this.txtTip.rectTransform.anchoredPosition = anchoredPos;
    }

    public void RaiseTopButton(Button _button)
    {
        //原有按钮
        this.btnGuide = _button;
        //创建新按钮(也可以把原按钮的层提上去)
        this.btnGuideCopy = GameObject.Instantiate(_button.gameObject).GetComponent<Button>();
        this.btnGuideCopy.onClick.AddListener(OnBtnGuideCopyClicked);
        this.btnMask.gameObject.SetActive(true);
        this.btnGuideCopy.transform.SetParent(btnMask.transform);
        this.btnGuideCopy.transform.position = _button.transform.position;
        this.btnGuideCopy.transform.localScale = Vector3.one;
    }

    public void RaiseTopTransform(Transform _transform)
    {
        this.trGuide = _transform;
        this.trGuideCopy = GameObject.Instantiate(_transform.gameObject).transform;
        this.btnMask.gameObject.SetActive(true);
        this.trGuideCopy.SetParent(btnMask.transform);
        this.trGuideCopy.position = _transform.position;
        this.trGuideCopy.localScale = Vector3.one;
        //设置背景可点击
        this.canClick = true;
    }

    private void OnBtnGuideCopyClicked()
    {
        //触发原按钮点击方法
        this.btnGuide.OnSubmit(null);
        //删除新创建的按钮
        Destroy(this.btnGuideCopy.gameObject);
        this.btnGuide = null;
        this.btnGuideCopy = null;
        //触发下一步引导
        guide.Next();
    }

    private void OnBtnMaskClicked()
    {
        if (this.canClick)
        {
            this.trGuide = null;
            this.trGuideCopy = null;
            this.canClick = false;
            //触发下一步引导
            guide.Next();
        }
    }
}

这样基本就完成逻辑了。

4.添加GuideTestPanel,继承GuideBase,开始引导
1.第一步:显示遮罩,提高button
2.第二步:提高image的层级
3.第三步:关闭遮罩,记录引导

public class GuideTestPanel : GuideBase
{
    public override EGuideType GuideType
    {
        get
        {
            return EGuideType.TestPanel;
        }
    }

    public override IEnumerator ExecuteStep1()
    {
        this.ShowMask();
        Button btn = TestPanel.Instance.btn;
        this.RaiseTop(btn);
        this.SetTip("请按照提示点击继续", new Vector2(0f, -585f));
        yield break;
    }

    public override IEnumerator ExecuteStep2()
    {
        Transform tr = TestPanel.Instance.img.transform;
        this.RaiseTop(tr);
        this.SetTip("点击任意位置继续", new Vector2(0f, -585f));
        yield break;
    }

    public override IEnumerator ExecuteStep3()
    {
        this.HideMask();
        this.Save();
        yield break;
    }
}

5.触发引导

public class TestPanel : MonoBehaviour
{
    private static TestPanel instance;
    public static TestPanel Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<TestPanel>();
            }
            return instance;
        }
    }

    public Button btn;
    public Image img;

    private void Awake()
    {
        btn.onClick.AddListener(OnBtnClicked);
    }

    private void Start()
    {
        //这里触发引导,一行代码即可
        GuideManager.Instance.TryEnter(EGuideType.TestPanel); 
    }

    public void OnBtnClicked()
    {
        Debug.LogError("Btn Clicked!!!!");
    }
}

OK!满足需求。
PS:实际会读取配置表,根据表中步骤再进行对应引导。

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