[Unity 3d] 60 行代码实现通用模态提示窗口

在本文,笔者将教大家使用 60 行不到的代码实现一个通用的模态提示窗口(modal notification window)

前言:

在软件工程中,提示窗口往往必不可少,越多的提示代表越好的用户体验,那如何用最精悍的代码,实现相对够用的模态提示窗口呢?

如何量化“相对够用”:

  • 首先,能够方便弹窗展示提示语
  • 其次,能够以组合形式展示 “确定”、“取消” 按钮,并捕获用户的选择。
  • 然后,有按钮的模态窗口,点击空白区域需要震动消息窗口实现提醒。
  • 接着,没有按钮的模态窗口,点击空白区域关闭,或者震动消息窗口而不关闭(由逻辑内决定何时关闭)
  • 最后,这个提示弹窗最好能够很方便的被调用、复用;拒绝回调(async、await 实现)。

演示:

在贴出细节实现前,先看看实际使用效果:

notification

动画中的配套测试代码,是不是很简单?

using UnityEngine;
using static zFramework.UI.NotificationManager;
public class TestNotification : MonoBehaviour
{
    public void ShowMessage() => _ = ShowAsync("这个消息没有按钮,点空白区域关闭"); //也可以使用 await 等待
    public async void ShowAutoMessage()
    {
        _= ShowAsync("这个消息没有按钮、用户不能关闭,空白区域震窗、2 秒后自动关闭", interactable: false);
        await UniTask.Delay(2000); // 也可以是其他判断关闭提示窗的逻辑
        CloseNotification();
    }
    public async void ShowMessageWithConfirm()
    {
        await ShowAsync("带确认按钮的消息,点空白区域关闭会震窗", true);
        Debug.Log($"{nameof(TestNotification)}: 点击了确定按钮!");
    }
    public async void ShowMessageWithCancel()
    {
        await ShowAsync("带取消按钮的消息,点空白区域关闭会震窗", enablecancel: true);
        Debug.Log($"{nameof(TestNotification)}: 点击了取消按钮!");
    }
    public async void ShowMessageWith2Button()
    {
        var index = await ShowAsync("这个消息有 2 个按钮,点空白区域震窗", enableConfirm: true, enablecancel: true);
        Debug.Log($"{nameof(TestNotification)}: 用户点击了{(index == 0 ? "确定" : "取消")}");
    }
}

实现:

先构建一个通知 Notifacation

using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(CanvasGroup))]
public class Notification : MonoBehaviour
{
    public TextMeshProUGUI text;
    public Transform window;
    public Button mask_button;
    public Button confirm_button;
    public Button cancel_button;
    public CanvasGroup canvasGroup;
    public void Init(string message, bool enableConfirm, bool enablecancel, bool interactable)
    {
        text.text = message;
        confirm_button.gameObject.SetActive(interactable&&enableConfirm);
        cancel_button.gameObject.SetActive(interactable && enablecancel);
        mask_button.onClick.AddListener(() =>
        {
            if (enablecancel || enableConfirm|| !interactable)
                window.DOShakePosition(0.5f, 5);
            else
                Destroy(gameObject);
        });
        canvasGroup.blocksRaycasts = false;
        window.localScale = Vector3.one * 0.2f;
    }
}

再实现一个通知管理器 NotificationManager

using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;
namespace zFramework.UI
{
    public class NotificationManager : MonoBehaviour
    {
        public Notification notificationPrefab;
        static NotificationManager instance;
        static Notification notification;
        void Awake() => instance = this;
        public static void CloseNotification() => Destroy(notification.gameObject);
        public static async UniTask ShowAsync(string message, bool enableConfirm = false, bool enablecancel = false, bool interactable = true)
        {
            notification = Instantiate(instance.notificationPrefab, instance.transform, false);
            notification.Init(message, enableConfirm, enablecancel, interactable);
            notification.canvasGroup.blocksRaycasts = true;
            await notification.window.DOScale(1, 0.5F);
            try
            {
                var cancellation = notification.gameObject.GetCancellationTokenOnDestroy();
                var index = await UniTask.WhenAny(notification.confirm_button.OnClickAsync(cancellation), notification.cancel_button.OnClickAsync(cancellation));
                Destroy(notification.gameObject);
                return index;
            }
            catch (System.Exception)
            {
                return 0;
            }
        }
    }
}

最后 UI 配套

Notification UI搭建

Notification 测试环境搭建

写到最后:

  • 功能如此完善代码却简约至此,夫复何求?
  • 笔者使用了 DotweenUniTask 以及 UniTaskDotween扩展,通过本文,也能更直观感受到基于 async/await 实现的异步当成同步写的魅力,可以跟回调地狱 say 拜拜了您嘞!
  • 在测试代码中,笔者使用了 using static NotificationManager,这是一个非常优雅的语法糖,可以像使用本地方法一样使用 NotificationManager 中的公共方法或其他成员。
  • 在测试代码中,笔者使用了弃元运算符(下划线),用于跳过等待,可以实现多个异步操作并行的直观感受。
  • void 关键字在与 async await 配合使用时,一般用于做入口异步方法,也能够保证函数签名符合 UGUI 组件的事件所需要的方法类型。

版权所有,转载请注明出处..

你可能感兴趣的:([Unity 3d] 60 行代码实现通用模态提示窗口)