Unity 中使用状态机模式来管理UI

时间:2024-07-12 13:11:30

1. 清晰的状态管理

状态机模式允许你以结构化的方式管理不同的UI状态。每个状态(比如主菜单、设置菜单、游戏中界面等)都有其独立的行为和属性,这使得管理复杂UI逻辑变得更加清晰和可维护。

2. 简化的状态切换

状态机模式可以简化不同UI状态之间的切换逻辑。使用状态机,可以很容易地定义状态之间的转换规则,并确保状态切换时的逻辑是正确的和一致的。

3. 分离关注点

通过将UI逻辑分割到不同的状态类中,可以使每个状态类只关心自己的行为和属性。这有助于减少代码的耦合,提高代码的可读性和可维护性。

4. 更容易的扩展和维护

当需要添加新的UI状态或修改现有状态的行为时,状态机模式使得这种修改变得更简单。可以通过添加或修改单个状态类来实现,而不必修改整个UI管理系统。

5. 动画和过渡效果

使用状态机模式,可以轻松地管理UI状态的进入和退出动画。例如,可以在状态进入时播放淡入动画,在状态退出时播放淡出动画。状态机模式可以确保这些动画在状态切换时正确播放。

6. 统一的状态处理逻辑

状态机模式提供了一种统一的方式来处理UI状态的更新、渲染和事件处理。这有助于保持代码的一致性,并避免不同状态处理逻辑的重复代码。

状态接口

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

public interface IState//interface 状态机接口
{
    void Enter();
    void Exit();
    void LogicUpdata();
    void PhysicUpdata();
    void AinamtionEvent();
}

状态机管理器

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

//用于管理状态机切换
public class StateMachina : MonoBehaviour
{
    IState currentState;


    private void Update()
    {
        currentState?.LogicUpdata();
    }
    private void FixedUpdate()
    {
        currentState?.PhysicUpdata();
    }
    public virtual void AnimationEvent()
    {
        currentState?.AinamtionEvent();
    }

    public virtual void SwitchState(IState newState)
    {
        currentState?.Exit();
        currentState=newState;
        currentState.Enter();
    }

}

基础状态类

using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
[RequireComponent(typeof(Canvas))]
[RequireComponent(typeof(CanvasGroup))]
[RequireComponent(typeof(RectTransform))]
public class UIState : MonoBehaviour,IState
{

    protected Canvas canvas;
    protected CanvasGroup canvasGroup;
    protected RectTransform rectTransform;//ui位置

    protected int initialSortingOrder;


    protected virtual void Awake()
    {

        canvas = GetComponent<Canvas>();
        canvasGroup = GetComponent<CanvasGroup>();
        rectTransform= GetComponent<RectTransform>();
        initialSortingOrder = canvas.sortingOrder;
    }


    public virtual void AinamtionEvent()
    {
        
    }

    public virtual void Enter()
    {
        canvas.enabled = true;
    }

    public virtual void Exit()
    {
      canvas.enabled = false;
    }

    public virtual void LogicUpdata()
    {
      
    }

    public virtual void PhysicUpdata()
    {
       
    }
}

具体的ui管理器负责ui的加载和切换状态,设置为单列模式,动态加载ui

using System.Collections.Generic;
using UnityEngine;

public class UIManager : StateMachina
{
    public static UIManager Instance;

    string realPath = "Prefab/Panel/";

    private Dictionary<string, GameObject> prefabDict = new Dictionary<string, GameObject>();
    private IState currentState;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            SwitchPanel(My_UIConst.MainMenuPanel);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public GameObject CreatePanel(string name)
    {
        if (prefabDict.ContainsKey(name))
        {
            return prefabDict[name];
        }

        GameObject panelPrefab = Resources.Load<GameObject>(realPath + name);
        if (panelPrefab == null)
        {
            Debug.LogError($"Failed to load panel prefab: {realPath}{name}");
            return null;
        }

        GameObject panelObject = Instantiate(panelPrefab, gameObject.transform, false);
        prefabDict[name] = panelObject;
        return panelObject;
    }

    public void SwitchPanel(string name)
    {
        UIState newState;
        if (prefabDict.ContainsKey(name))
        {
            newState = prefabDict[name].GetComponent<UIState>();
        }
        else
        {
            GameObject panelObject = CreatePanel(name);
            if (panelObject == null)
            {
                Debug.LogError($"Failed to create panel: {name}");
                return;
            }
            newState = panelObject.GetComponent<UIState>();
        }
        SwitchState(newState);
    }
}

UIManager 挂载在canvas上

ui放在 Resources下面

ui的名称路径


public class My_UIConst 
{
    public const string MainMenuPanel = "Menu/MainMenuPanel";
    public const string UserPanel = "Menu/UserPanel";
    public const string SettingsPanel = "Menu/SettingsPanel";
    // 你可以根据需要添加更多的 UI 名称
}

具体的UI状态类 用dotweet来实现动画效果

using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class My_MainMenu : UIState
{
    public Button userButton;

    public Image bg;
   
    private void Start()
    {
        userButton.onClick.AddListener(() =>
        {
            Debug.Log("主菜单按钮点击 切换用户界面");

            UIManager.Instance.SwitchPanel(My_UIConst.UserPanel);
        });
      

    }
    public override void Enter()
    {
        Debug.Log("进入主菜单");

        DOTween.To(() => canvasGroup.alpha=0, x => canvasGroup.alpha=x, 1, 1);

        // 生成一个随机颜色
        Color randomColor = new Color(Random.value, Random.value, Random.value);
        bg.DOColor(randomColor, 1f); // 渐变到随机颜色

        base.Enter();
    }
    public override void Exit()
    {
        Color randomColor = new Color(Random.value, Random.value, Random.value);
        bg.DOColor(randomColor, 1f); // 渐变到随机颜色
    }

}
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;  // 引入 DOTween 命名空间
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

public class My_UserPanel : UIState
{
    public Button mainMenuButton;

    private void Start()
    {
        mainMenuButton.onClick.AddListener(() =>
        {
            Debug.Log("用户按钮点击 切换主界面");

            UIManager.Instance.SwitchPanel(My_UIConst.SettingsPanel);
        });
    }

    public override void Enter()
    {
        Debug.Log("进入用户界面");

        // 设置初始位置
        canvas.transform.localPosition = new Vector3(-Screen.width, 0, 0);

        rectTransform.SetAsLastSibling();//将渲染等级移动到最后面 显示在最前面
        // 使用 DOTween 平移动画将面板移到屏幕中心
        canvas.transform.DOLocalMoveX(0, 1f).SetEase(Ease.OutQuad);

        base.Enter();
    }

    public override void Exit()
    {
        rectTransform.SetAsLastSibling();//将渲染等级移动到最后面 显示在最前面
        // 在退出时添加平移动画
        canvas.transform.DOLocalMoveX(-Screen.width, 1f).SetEase(Ease.OutQuad).OnComplete(() =>
        {
            base.Exit();
        });
    }
}

using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class My_SettingPanel : UIState
{
    public Button mainMenuButton;
    private void Start()
    {
        mainMenuButton.onClick.AddListener(() =>
        {
            Debug.Log("用户按钮点击 切换主界面");

            UIManager.Instance.SwitchPanel(My_UIConst.MainMenuPanel
                );
        });

    }
    public override void Enter()
    {
        Debug.Log("进入用户界面");

        // 设置初始位置
        canvas.transform.localPosition = new Vector3(Screen.width, 0, 0);

        rectTransform.SetAsLastSibling();//将渲染等级移动到最后面 显示在最前面
        // 使用 DOTween 平移动画将面板移到屏幕中心
        canvas.transform.DOLocalMoveX(0, 1f).SetEase(Ease.OutQuad);

        base.Enter();
    }

    public override void Exit()
    {
        rectTransform.SetAsLastSibling();//将渲染等级移动到最后面 显示在最前面
        // 在退出时添加平移动画
        canvas.transform.DOLocalMoveX(-Screen.width, 1f).SetEase(Ease.OutQuad).OnComplete(() =>
        {
            base.Exit();
        });
    }

}