数据结构大作业-DBLP科学文献管理系统(五)前端Unity设计思想(搜索栏GUI组件应用,状态机,委托,C#线程)

本文详细介绍了如何在Unity中使用C#进行线程编程以优化长时间运行的后端操作,并结合状态机设计简单搜索功能。涉及了委托、线程管理、UI交互和状态转换,适合前端开发者深入理解Unity多线程和状态机应用。
摘要由CSDN通过智能技术生成

终于写到了最后一篇。对应的前后端源码和可执行程序,unitypackage已放在github上,欢迎大

家参考。最后一篇是关于Unity中的程序设计思路。虽然在这个课设任务中前端并不是最重要的考查目标,但其中用到了一些很好的想法也在这里分享下。( 要完结了,撒花!)数据结构大作业-DBLP科学文献管理系统-概述 (C++/C#/Unity)_Sugarzo_mei的博客-CSDN博客

C#中的线程、委托应用

        由于前端中是调用后端dll库,而有些函数运行起来需要很长时间,比如建库函数3G的数据,就要执行半个小时。如果只是将调用简单的写在unity主线程中,那unity就会直接无响应,这时候就需要引入线程思想。将后端dll的函数放在线程中调用,就不会影响到主线程帧率的刷新了,也支持在程序中显示进度。

        这里提醒一句,由于C#是线程不安全性语言,在使用线程时需要考虑资源互斥,死锁等现象。如果涉及到多线程同时工作,必要时需要引入lock等关键字对线程资源进行控制(不过在这里只需要简单使用,就不需要考虑这么多了)

        使用线程,我们需要先引入命名空间:

using System.Threading;

        然后新建线程的引用变量,这里我设置了一个线程的静态管理类方便调用,并设置了对应的委托和回调事件:

public static class ThreadManager
{ 
    private static Thread thread = null;
    /// <summary>
    /// 开始一个线程
    /// </summary>
    /// <param name="runlogic">线程运行逻辑</param>
    /// <param name="callback">线程执行完成后,回调函数主线程逻辑</param>
    public static void StartThread(Action runlogic,Action callback)
    {
        if(thread != null)
        {
            if (thread.IsAlive)
                thread.Abort();
        }
        Debug.Log("新建进程");
        thread = new Thread(delegate() { ThreadRun(runlogic, callback); });
        thread.Start();
    }
    private static void ThreadRun(Action runlogic, Action callback)
    {
        Debug.Log("进入进程");
        runlogic?.Invoke();
        Debug.Log("进程执行完成,进入回调函数");

        MainObject.Instance.ThreadAction += delegate () //将回调事件委托,写入主线程
        {
            callback?.Invoke();
            Debug.Log("回调函数完成,结束进程");
        };

        thread.Abort(); //终止线程
    }
}

        值得注意的时,在unity编辑模式下,即使游戏状态不在运行,脚本中的线程也可能在执行,因此使用完时留下abort()的好习惯。

        受到Unity引擎的限制,Unity自带的API只能在主线程中使用。比如当你的线程计算完成时,如果需要刷新对应的UI或者修改游戏组件,需要将事件通知到主线程中。 在上文中还涉及到一个mainObject的类,它的结构是这样的

public class MainObject : SingletonMono<MainObject>
{
     public Action ThreadAction = null;
     private void Update()
     {
        if(ThreadAction != null)
        {
            ThreadAction.Invoke();
            ThreadAction = null;
        }
     }
}
public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{
	public static T Instance;

	protected virtual void Awake()
	{
		if (Instance == null)
		{
			Instance = this as T;
			DontDestroyOnLoad(gameObject);
		}
		else
		{
			if (Instance != this)
			{
				Destroy(gameObject);
			}
		}
	}
}

        可以看到这里实现了一个单例模式,并通过一个Action委托在update中持续检测。当线程计算完成后,将事件在action中注册,就可以通过主线程修改游戏组件的数据/刷新对应的UI啦!

状态机设计

        状态机是指程序在有限个状态种互相切换的模型,是程序设计中非常常见的设计模式!养成良好的编码习惯是程序员的必备需求。状态机有着容易理解,维护方便的优点。实际使用的状态机根据使用需求可以设计出很多种类,这里因为只是一个小程序,讲解一个最简单的状态机模型。

        我们拿这个建库界面的需求作为分析,实际上他根据不同输入条件,有很多种不同的状态,我们使用一个枚举将它列举出来:

public enum State
    { 
        Null, //未设置的初始值
        Idle, //默认
        Checking, //检查路径中
        Building, //建库中
        Finish, //数据库正确,准备进入程序
        Error,  //路径错误,需要重新配置路径
        Ready, //数据库未建立,但是有dblp,进入准备建库状态
    }

        然后我们实现一个简单的状态表示(这里我使用了抽象类,也可以用Interface接口进行实现),一个状态最简单事件应该有:进入时/退出时/执行时(这里只需要控制对应的UI组件,事件是瞬发的,因此执行时也省去了)

public abstract class IState
{
    public abstract void OnEnter(); //状态进入时
    public abstract void OnExit();  //状态退出时
}
//这里拿finish状态作为例子,继承基类,可以在状态事件中设置对应的UI控制
private class State_Finish : IState
{
    public override void OnEnter()
    {
        Instance.State_dblp.SetState(StateIcon.State.Finish);
        Instance.State_database.SetState(StateIcon.State.Finish);
        GlobalManager.filePath = Instance.path;
        Instance.Enter.interactable = true;
        Instance.Loading.gameObject.SetActive(false);
    }

    public override void OnExit()
    {
       Instance.Enter.interactable = false;
    }
}

        最后我们用字典存储状态,再设置好对应的状态转移函数(执行前一个状态的退出事件再执行当前状态的进入事件),在start或者enable中注册事件,一个简单的状态机模型就设置好了。

    public State programState = State.Null;  //程序当前状态
    private Dictionary<State, IState> StateMachine;
    public void FSM(State state) //切换状态函数
    {
        if (state == programState) //如果状态没有变化
            return;
        StateMachine[programState].OnExit(); //执行前一个状态的退出事件
        programState = state;                //更换状态
        StateMachine[programState].OnEnter();//执行下一个状态的进入事件
    }
    private void OnEnable()
    {
        if(StateMachine == null)
        {
            StateMachine = new Dictionary<State, IState>();
            //注册状态机
            StateMachine.Add(State.Null, new State_Empty());
            StateMachine.Add(State.Idle, new State_Idle());
            StateMachine.Add(State.Building, new State_Building());
            StateMachine.Add(State.Checking, new State_Checking());
            StateMachine.Add(State.Error, new State_Error());
            StateMachine.Add(State.Finish, new State_Finish());
            StateMachine.Add(State.Ready, new State_Ready());
        }
        FSM(State.Idle);
    }

搜索栏GUI

        

        程序中实现的搜索栏主要功能为:下拉菜单、滚动区域、以及根据用户点击的内容可以调跳转到对应的搜索(作者名->作者搜索、标题名->文章搜索)

        这里需要使用ScrollView,绑定好对应的scrollRect组件,保留垂直滑动功能。

需要保证这个高度是随着一个搜索模块的动态变化的,搜索出来的结果也需要垂直排列,所以Content要加上ContentSizeFitter组件,设置垂直适应为PreferredSize,以及配置VecticalLayoutGroup。

 

然后设置好对应的搜索框预制体组件,这里添加TmpText和button组件实现跳转 

         当搜索按钮被按下时,判断当前选择的搜索选项,调用线程执行对应的dll接口,得到结果后以content为父节点,实例化该预制体,设置对应的文件即可(如果可以也可以加入对象池进行优化)

public class SearchManager : SingletonMono<SearchManager>
{   
     /// <summary>
    /// 当搜索按钮被按下时
    /// </summary>
    [Button]
    private void OnSearchButtonClick()
    {
        if (string.IsNullOrEmpty(inputField.text))
            return;

        SetState(SearchState.Searching);

        if(thread != null)
        {
            if(thread.IsAlive)
                thread.Abort();
            Debug.Log("更换线程");
        }
        if(dropdown.captionText.text == "文章搜索")
        {
            thread = new Thread(DLL_SearchTitle);
        }
        if (dropdown.captionText.text == "作者搜索")
        {
            thread = new Thread(DLL_SearchAuthor);
        }
        if (dropdown.captionText.text == "模糊搜索")
        {
            thread = new Thread(DLL_SearchFuzzy);
        }
        if (dropdown.captionText.text == "聚团编号搜索")
        {
            thread = new Thread(SearchAua);
        }
        thread.Start();
    }
    /// <summary>
    /// 外部调用OnSearchButtonClick()接口
    /// </summary>
    public void Search(string input,int type)
    {
        inputField.text = input;
        dropdown.value = type;
        //dropdown.captionText.text = searchType;
        OnSearchButtonClick();
    }
}

        这里的点击属性,调用对应的搜索跳转功能,使用了Lambda表达式,在对应的button.Onclick中添加搜索事件即可

if (buttonActionType == TitleColorSet.SearchActionType.Article)
{
    obj.GetComponent<Button>().enabled = true;
    obj.GetComponent<Button>().onClick.AddListener(delegate ()
    {
        SearchManager.Instance.Search(tex, 0);
    });
}
if (buttonActionType == TitleColorSet.SearchActionType.Author)
{
    obj.GetComponent<Button>().enabled = true;
    obj.GetComponent<Button>().onClick.AddListener(delegate ()
    {
        SearchManager.Instance.Search(tex, 1);
    });
}

    

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值