精华内容
下载资源
问答
  • Android 使用 DownloadManager 管理系统下载任务的方法

    千次阅读 多人点赞 2014-07-17 09:46:20
    本文详细介绍Android应用管理设备下载状态信息的方法

    从Android 2.3(API level 9)开始Android用系统服务(Service)的方式提供了Download Manager来优化处理长时间的下载操作。Download Manager处理HTTP连接并监控连接中的状态变化以及系统重启来确保每一个下载任务顺利完成。

    在大多数涉及到下载的情况中使用Download Manager都是不错的选择,特别是当用户切换不同的应用以后下载需要在后台继续进行,以及当下载任务顺利完成非常重要的情况(DownloadManager对于断点续传功能支持很好)。

    要想使用Download Manager,使用getSystemService方法请求系统的DOWNLOAD_SERVICE服务,代码片段如下:


    String serviceString =Context.DOWNLOAD_SERVICE; 
    DownloadManager downloadManager; 
    downloadManager = (DownloadManager)getSystemService(serviceString); 


    下载文件

    要请求一个下载操作,需要创建一个DownloadManager.Request对象,将要请求下载的文件的Uri传递给DownloadManager的enqueue方法,代码片段如下所示:

    String serviceString =Context.DOWNLOAD_SERVICE; 
    DownloadManager downloadManager; 
    downloadManager =(DownloadManager)getSystemService(serviceString); 
     
    Uri uri =Uri.parse("http://developer.android.com/shareables/icon_templates-v4.0.zip"); 
    DownloadManager.Request request = newRequest(uri); 
    long reference =downloadManager.enqueue(request); 


    在这里返回的reference变量是系统为当前的下载请求分配的一个唯一的ID,我们可以通过这个ID重新获得这个下载任务,进行一些自己想要进行的操作或者查询下载的状态以及取消下载等等。

    我们可以通过addRequestHeader方法为DownloadManager.Request对象request添加HTTP头,也可以通过setMimeType方法重写从服务器返回的mimetype。

    我们还可以指定在什么连接状态下执行下载操作。setAllowedNetworkTypes方法可以用来限定在WiFi还是手机网络下进行下载,setAllowedOverRoaming方法可以用来阻止手机在漫游状态下下载。

    下面的代码片段用于指定一个较大的文件只能在WiFi下进行下载:

    request.setAllowedNetworkTypes(Request.NETWORK_WIFI); 


     

    Android API level 11 介绍了getRecommendedMaxBytesOverMobile类方法(静态方法),返回一个当前手机网络连接下的最大建议字节数,可以来判断下载是否应该限定在WiFi条件下。调用enqueue方法之后,只要数据连接可用并且Download Manager可用,下载就会开始。要在下载完成的时候获得一个系统通知(notification),注册一个广播接受者来接收ACTION_DOWNLOAD_COMPLETE广播,这个广播会包含一个EXTRA_DOWNLOAD_ID信息在intent中包含了已经完成的这个下载的ID,代码片段如下所示:


    IntentFilter filter = newIntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 
         
    BroadcastReceiver receiver = newBroadcastReceiver() { 
     @Override 
     public void onReceive(Context context, Intent intent) { 
       long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID,-1); 
       if (myDownloadReference == reference) { 
          
       } 
     } 
    }; 
    registerReceiver(receiver, filter); 


    使用Download Manager的openDownloadedFile方法可以打开一个已经下载完成的文件,返回一个ParcelFileDescriptor对象。我们可以通过Download Manager来查询下载文件的保存地址,如果在下载时制定了路径和文件名,我们也可以直接操作文件。我们可以为ACTION_NOTIFICATION_CLICKED action注册一个广播接受者,当用户从通知栏点击了一个下载项目或者从Downloads app点击可一个下载的项目的时候,系统就会发出一个点击下载项的广播。

    代码片段如下:


    IntentFilter filter = newIntentFilter(DownloadManager.ACTION_NOTIFICATION_CLICKED); 
     
    BroadcastReceiver receiver = newBroadcastReceiver() { 
     @Override 
     public void onReceive(Context context, Intent intent) { 
       String extraID =DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS; 
       long[] references = intent.getLongArrayExtra(extraID); 
       for (long reference : references) 
         if (reference == myDownloadReference) { 
           // Do something with downloading file. 
         } 
     } 
    }; 
     
    registerReceiver(receiver, filter); 


    定制Download Manager Notifications的样式

    默认情况下,通知栏中会显示被Download Manager管理的每一个download每一个Notification会显示当前的下载进度和文件的名字

     

    通过Download Manager可以为每一个download request定制Notification的样式,包括完全隐藏Notification。下面的代码片段显示了通过setTitle和setDescription方法来定制显示在文件下载Notification中显示的文字。


    request.setTitle(“Earthquakes”); 
    request.setDescription(“EarthquakeXML”); 


     

    setNotificationVisibility方法可以用来控制什么时候显示Notification,甚至是隐藏该request的Notification。有以下几个参数:

    Request.VISIBILITY_VISIBLE:在下载进行的过程中,通知栏中会一直显示该下载的Notification,当下载完成时,该Notification会被移除,这是默认的参数值。

    Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED:在下载过程中通知栏会一直显示该下载的Notification,在下载完成后该Notification会继续显示,直到用户点击该Notification或者消除该Notification。

    Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION:只有在下载完成后该Notification才会被显示。

    Request.VISIBILITY_HIDDEN:不显示该下载请求的Notification。如果要使用这个参数,需要在应用的清单文件中加上DOWNLOAD_WITHOUT_NOTIFICATION权限。

    指定下载保存地址

    默认情况下,所有通过Download Manager下载的文件都保存在一个共享下载缓存中,使用系统生成的文件名每一个Request对象都可以制定一个下载

    保存的地址,通常情况下,所有的下载文件都应该保存在外部存储中,所以我们需要在应用清单文件中加上WRITE_EXTERNAL_STORAGE权限:


    <uses-permissionandroid:name=”android.permission.WRITE_EXTERNAL_STORAGE”/> 


    下面的代码片段是在外部存储中指定一个任意的保存位置的方法:


    request.setDestinationUri(Uri.fromFile(f)); 


    f是一个File对象。

    如果下载的这个文件是你的应用所专用的,你可能会希望把这个文件放在你的应用在外部存储中的一个专有文件夹中。注意这个文件夹不提供访问控制,所以其他的应用也可以访问这个文件夹。在这种情况下,如果你的应用卸载了,那么在这个文件夹也会被删除。

    下面的代码片段是指定存储文件的路径是应用在外部存储中的专用文件夹的方法:


    request.setDestinationInExternalFilesDir(this, 
     Environment.DIRECTORY_DOWNLOADS, “Bugdroid.png”); 


    如果下载的文件希望被其他的应用共享,特别是那些你下载下来希望被Media Scanner扫描到的文件(比如音乐文件),那么你可以指定你的下载路径在外部存储的公共文件夹之下,下面的代码片段是将文件存放到外部存储中的公共音乐文件夹的方法:


    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC, 
     "Android_Rock.mp3"); 


    在默认的情况下,通过Download Manager下载的文件是不能被Media Scanner扫描到的,进而这些下载的文件(音乐、视频等)就不会在Gallery和

    Music Player这样的应用中看到。

    为了让下载的音乐文件可以被其他应用扫描到,我们需要调用Request对象的allowScaningByMediaScanner方法。

    如果我们希望下载的文件可以被系统的Downloads应用扫描到并管理,我们需要调用Request对象的setVisibleInDownloadsUi方法,传递参数true。

    取消和删除下载

    Download Manager的remove方法可以用来取消一个准备进行的下载,中止一个正在进行的下载,或者删除一个已经完成的下载。

    remove方法接受若干个download 的ID作为参数,你可以设置一个或者几个你想要取消的下载的ID,如下代码段所示:


    downloadManager.remove(REFERENCE_1,REFERENCE_2, REFERENCE_3); 


    该方法返回成功取消的下载的个数,如果一个下载被取消了,所有相关联的文件,部分下载的文件和完全下载的文件都会被删除。

    查询Download Manager

    你可以通过查询Download Manager来获得下载任务的状态,进度,以及各种细节,通过query方法返回一个包含了下载任务细节的Cursor。query方法传递一个DownloadManager.Query对象作为参数,通过DownloadManager.Query对象的setFilterById方法可以筛选我们希望查询的下载任务的ID。也可以使用setFilterByStatus方法筛选我们希望查询的某一种状态的下载任务,传递的参数是DownloadManager.STATUS_*常量,可以指定

    正在进行、暂停、失败、完成四种状态。

    Download Manager包含了一系列COLUMN_*静态String常量,可以用来查询Cursor中的结果列索引。我们可以查询到下载任务的各种细节,包括状态,文件大小,已经下载的字节数,标题,描述,URI,本地文件名和URI,媒体类型以及Media Provider download URI。

    下面的代码段是通过注册监听下载完成事件的广播接受者来查询下载完成文件的本地文件名和URI的实现方法:


    @Override 
    public void onReceive(Context context,Intent intent) { 
     long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID,-1); 
      if(myDownloadReference == reference) { 
          
       Query myDownloadQuery = new Query(); 
       myDownloadQuery.setFilterById(reference); 
           
       Cursor myDownload = downloadManager.query(myDownloadQuery); 
       if (myDownload.moveToFirst()) { 
         int fileNameIdx =  
           myDownload.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME); 
         int fileUriIdx =  
           myDownload.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI); 
     
         String fileName = myDownload.getString(fileNameIdx); 
         String fileUri = myDownload.getString(fileUriIdx); 
         
         // TODO Do something with the file. 
         Log.d(TAG, fileName + " : " + fileUri); 
       } 
       myDownload.close(); 
     
     } 
    } 


    对于暂停和失败的下载,我们可以通过查询COLUMN_REASON列查询出原因的整数码。

    对于STATUS_PAUSED状态的下载,可以通过DownloadManager.PAUSED_*静态常量来翻译出原因的整数码,进而判断出下载是由于等待网络连接

    还是等待WiFi连接还是准备重新下载三种原因而暂停。

    对于STATUS_FAILED状态的下载,我们可以通过DownloadManager.ERROR_*来判断失败的原因,可能是错误码(失败原因)包括没有存储设备,

    存储空间不足,重复的文件名,或者HTTP errors。

    下面的代码是如何查询出当前所有的暂停的下载任务,提取出暂停的原因以及文件名称,下载标题以及当前进度的实现方法:


    // Obtain the Download ManagerService. 
    String serviceString =Context.DOWNLOAD_SERVICE; 
    DownloadManager downloadManager; 
    downloadManager =(DownloadManager)getSystemService(serviceString); 
     
    // Create a query for pauseddownloads. 
    Query pausedDownloadQuery = newQuery(); 
    pausedDownloadQuery.setFilterByStatus(DownloadManager.STATUS_PAUSED); 
     
    // Query the Download Manager for pauseddownloads. 
    Cursor pausedDownloads =downloadManager.query(pausedDownloadQuery); 
     
    // Find the column indexes for the data werequire. 
    int reasonIdx =pausedDownloads.getColumnIndex(DownloadManager.COLUMN_REASON); 
    int titleIdx =pausedDownloads.getColumnIndex(DownloadManager.COLUMN_TITLE); 
    int fileSizeIdx =  
     pausedDownloads.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);     
    int bytesDLIdx =  
     pausedDownloads.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); 
     
    // Iterate over the result Cursor. 
    while (pausedDownloads.moveToNext()) { 
      //Extract the data we require from the Cursor. 
     String title = pausedDownloads.getString(titleIdx); 
      intfileSize = pausedDownloads.getInt(fileSizeIdx); 
      intbytesDL = pausedDownloads.getInt(bytesDLIdx); 
     
      //Translate the pause reason to friendly text. 
      intreason = pausedDownloads.getInt(reasonIdx); 
     String reasonString = "Unknown"; 
     switch (reason) { 
       case DownloadManager.PAUSED_QUEUED_FOR_WIFI :  
         reasonString = "Waiting for WiFi"; break; 
       case DownloadManager.PAUSED_WAITING_FOR_NETWORK :  
         reasonString = "Waiting for connectivity"; break; 
       case DownloadManager.PAUSED_WAITING_TO_RETRY : 
         reasonString = "Waiting to retry"; break; 
       default : break; 
     } 
     
      //Construct a status summary 
     StringBuilder sb = new StringBuilder(); 
     sb.append(title).append("\n"); 
     sb.append(reasonString).append("\n"); 
     sb.append("Downloaded ").append(bytesDL).append(" /" ).append(fileSize); 
     
      //Display the status  
     Log.d("DOWNLOAD", sb.toString()); 
    } 
     
    // Close the result Cursor. 
    pausedDownloads.close();  
    


    展开全文
  • Unity 实现任务系统 TaskSystem

    千次阅读 2018-05-09 11:08:22
    本帖最后由 martin4500 于 2018-5-9 09:57 编辑这段时间比较闲,所以抽时间写了游戏中常见的系统,任务系统,可以接受任务啊,然后通过刷怪等条件去触发任务条件,直到完成任务。这个系统相对来说比较的简单,可以...
    本帖最后由 martin4500 于 2018-5-9 09:57 编辑

    这段时间比较闲,所以抽时间写了游戏中常见的系统,任务系统,可以接受任务啊,然后通过刷怪等条件去触发任务条件,直到完成任务。
    这个系统相对来说比较的简单,可以满足小型的游戏任务系统的基本要求吧!这里面的任务的描述是通过读取Json数据来获取的,当然你也可以去写Excel和XML格式的。序列化Json数据不是用的Unity自带的 JsonUtility.FromJson函数,因为有个更强大的Json解析插件Json .Net for Unity2.0,直接支持对Dictionary类型的支持,这样省事多了,可以在AssetStore直接下载。下面是效果展示: 废话不多说了上代码:
    using System.Collections;

    using System.Collections.Generic;

    using UnityEngine;

    using Newtonsoft.Json;

        public class DataManager : MonoSingletions<DataManager>
        {
            private Dictionary<string, Task> taskAllDic = new Dictionary<string, Task>();

            private TextAsset mTextAsset;

            private void Awake()

            {

                if (mTextAsset == null)
                {
                    mTextAsset = Resources.Load("Txt/Task", typeof(TextAsset)) as TextAsset;
                }
                 taskAllDic = JsonConvert.DeserializeObject<Dictionary<string, Task>>(mTextAsset.text);

               // taskAllDic = UnityEngine.JsonUtility.FromJson <Dictionary<string, Task>> (mTextAsset.text);
                foreach (var item in taskAllDic)
                {
                    string tempName=item.Value.taskName;
                    Debug.Log(tempName);
                }
            }

            public Task GetTaskByID(string Id)

            {
                Task value;
                if (taskAllDic.TryGetValue(Id, out value)) return value;

                else Debug.LogError("不存在这个字典");
                return null;
        }
    }

    这个类主要是来读取Json文本解析数据,也不需要多去解释了MonoSingletions是Mono的单例

    public class Task
        {
            public string taskID;
            public string taskName;
            public string description;
            public List<TaskConditions> taskConditions = new List<TaskConditions>();
            public TaskRewards[] taskRewards = new TaskRewards[2];//最好是用数组比链表要节省空间
            public Task(string _taskId,string _taskName,string _description, TaskRewards[] _taskRewards,List<TaskConditions> _taskConditions)
            {
                taskID = _taskId;
                taskName=_taskName;
                description = _description;
                taskRewards = _taskRewards;
                taskConditions=_taskConditions;

            }
        }
    Task类就是一个JSon数据实例化的载体类,就是根据你的Json文本来写的,记住一点的就是:属性名称一定要和json的属性名称一致。
    public class TaskManager :MonoSingletions<TaskManager>
        {
            TransferEvent transfer = new TransferEvent();
            private Dictionary<string, Task> currentTaskDic = new Dictionary<string, Task>();
            public void AcceptTask(string taskID)
            {
                if (currentTaskDic.ContainsKey(taskID))
                {
                    print(string.Format("{0},任务已经接受过",taskID));
                    return;
                }
                else
                {
                    Task t = DataManager.Instance.GetTaskByID(taskID);
                    if (t == null) return;
                    currentTaskDic.Add(taskID, t);
                    TaskArgs args = new TaskArgs();
                    args.taskId = taskID;
                    TransferEvent.Instance.InvokeEventListen((System.Int16)EventType.OnGetEvent, args);
                }
            }
            /// <summary>
            /// 更新任务进度
            /// </summary>
            /// <param name="args"></param>
            public void CheackTask(TaskArgs args)
            {
                foreach (var item in currentTaskDic)
                {
                    UdateUIData(item, args);
                    CheackFinishTask(item, args);
                }
            }
            //更新数据
            private void UdateUIData(KeyValuePair<string,Task> item,TaskArgs args)
            {
                TaskConditions tc;
                for (int i = 0; i < item.Value.taskConditions.Count; i++)
                {
                    tc = item.Value.taskConditions;
                    if (tc.id == args.id)
                    {
                        if (tc.nowAmount >= tc.targetAmount) return;
                        tc.nowAmount += args.amount;
                        args.taskId = item.Value.taskID;
                        args.amount = tc.nowAmount;
                        if (tc.nowAmount < 0) tc.nowAmount = 0;
                        if (tc.nowAmount >= tc.targetAmount)
                        {
                            tc.nowAmount = tc.targetAmount;
                            tc.isFinish = true;
                        }
                        else tc.isFinish = false;
                        //更新UI数据
                        TransferEvent.Instance.InvokeEventListen((System.Int16)EventType.OnCheackEvent, args);
                    }
                }
            }
            private void CheackFinishTask(KeyValuePair<string, Task> item,TaskArgs args)
            {
                TaskConditions tc;
                for (int i = 0; i < item.Value.taskConditions.Count; i++)
                {
                    tc = item.Value.taskConditions;
                    if (!tc.isFinish) return;//只要是没有完成就返回
                }
                FinishTask(args);

            }
            private void FinishTask(TaskArgs args)
            {
                //调用任务完成事件
                TransferEvent.Instance.InvokeEventListen((System.Int16)EventType.OnFinishEvent, args);
            }
            /// <summary>
            /// 获取任务奖励
            /// </summary>
            /// <param name="args"></param>
            public void GetReward(TaskArgs args)
            {
                if (currentTaskDic.ContainsKey(args.taskId))//当任务存在
                {
                    Task t = currentTaskDic[args.taskId];
                    for (int i = 0; i < t.taskRewards.Length; i++)
                    {
                        TaskArgs a = new TaskArgs();
                        a.id=t.taskRewards.id;
                        a.amount = t.taskRewards.amount;
                        a.taskId = args.taskId;
                        TransferEvent.Instance.InvokeEventListen((System.Int16)EventType.OnRewardEvent, args);
                        currentTaskDic.Remove(args.taskId);
                    }
                }
            }
            public void CancelTask(TaskArgs args)
            {
                if (currentTaskDic.ContainsKey(args.taskId))
                {
                    TransferEvent.Instance.InvokeEventListen((System.Int16)EventType.OnCancelEvent, args);
                    currentTaskDic.Remove(args.taskId);
                }
            }
        }
    TaskManager是管理类,简单的说就是连接任务数据和相关任务操作(UI文本提示,事件的触发等)这里的CheackTask(TaskArgs args是检查任务是否完成和更新UI提示,主要通过对当前任务字典的中的任务内容进行相关赋值实现。其它函数都是比较简单看几眼就知道了就不多说了。
       public enum EventType
        {
            OnGetEvent=0,
            OnCheackEvent=1,
            OnFinishEvent=2,
            OnRewardEvent=3,
            OnCancelEvent=4
        }
        public class TransferEvent
        {
            private List<MesTransfer> mListTransfer ;
            private List<MesTransfer> mAddListener;
            private event MesTransfer OnGetEvent;//接受任务时,更新任务到任务面板等操作
            private event MesTransfer OnCheackEvent;//更新任务信息
            private event MesTransfer OnFinishEvent;//完成任务时,提示完成任务等操作
            private event MesTransfer OnRewardEvent;//得到奖励时,显示获取的物品等操作
            private event MesTransfer OnCancelEvent;//取消任务时,显示提示信息等操作
            private static TransferEvent mInstance;
            public TransferEvent()
            {
                mListTransfer = new List<MesTransfer>();
                mListTransfer.Add(OnGetEvent);
                mListTransfer.Add(OnCheackEvent);
                mListTransfer.Add(OnFinishEvent);
                mListTransfer.Add(OnRewardEvent);
                mListTransfer.Add(OnCancelEvent);

            }
            public static TransferEvent Instance
            {
                get
                {
                    if (mInstance == null)
                        mInstance = new TransferEvent();
                    return mInstance;
                }
            }
            public bool isCheackNull(System.Int16 index)
            {
                return mListTransfer[index] == null ? true:false ;
            }
            //添加事件
            public void AddEventListen(MesTransfer temp,System.Int16 index)
            {
                if (mListTransfer[index]!=null) return;
                mListTransfer[index] += temp;
            }
            //回调事件
            public void InvokeEventListen(System.Int16 index,TaskArgs args)
            {
                if (index > mListTransfer.Count - 1)
                {
                    Debug.Log("链表索引越界");
                    return;
                }
                MesTransfer mes = mListTransfer[index];
                if (mListTransfer[index]!=null)
                {
                    if (!isCheackNull(index)) mes(args);
                }
            }
            //清空所有的监听事件
            public void ClearListTransfer()
            {
                int i = 0;
                IEnumerator listEnumerartor=mListTransfer.GetEnumerator();
                while(listEnumerartor.MoveNext())
                {
                    MesTransfer mes = (MesTransfer)listEnumerartor.Current ;
                    mes -= mAddListener;
                    i++;
                }
                mListTransfer.Clear();
            }
               }

    这是一个事件委托的管理类网上的模板很多,大概都是这个样子了,只要是对事件的增加监听,或者事件回调,和观察者模式很像,主要是为了降低代码之间的耦合性的,如果对委托不是很了解建议自己网上去查,或者照着我这个敲一遍练练手还是可以的。
      public class SMSNotifiler :MonoSingletions<SMSNotifiler>
        {
            private void Start()
            {
                TransferEvent.Instance.AddEventListen(GetPrintInfo, (System.Int16)EventType.OnGetEvent);
                TransferEvent.Instance.AddEventListen(finishPrintInfo, (System.Int16)EventType.OnFinishEvent);
                TransferEvent.Instance.AddEventListen(rewardPrintInfo, (System.Int16)EventType.OnRewardEvent);
                TransferEvent.Instance.AddEventListen(cancelPrintInfo, (System.Int16)EventType.OnCancelEvent);
                TransferEvent.Instance.AddEventListen(cheackPrintInfo, (System.Int16)EventType.OnCheackEvent);
            }
            public void GetPrintInfo(TaskArgs e)
            {
                print("接受任务" + e.taskId);
            }

            public void finishPrintInfo(TaskArgs e)
            {
                print("完成任务" + e.taskId);
            }

            public void rewardPrintInfo(TaskArgs e)
            {
                print("奖励物品" + e.id + "数量" + e.amount);
            }

            public void cancelPrintInfo(TaskArgs e)
            {
                print("取消任务" + e.taskId);
            }
            public void cheackPrintInfo(TaskArgs e)
            {
                print(string.Format("任务是{0},{1}完成了{2}",e.taskId,e.id,e.amount));
            }
    就像类名一样这个就是相关提示信息,由TaskManager中的去发布信息,然后Notifiler去执行相关细节而已,也是非常简单的脚本。

        public class ControllerTest : MonoBehaviour
        {
            private SMSNotifiler mSMSNotifiler;
            private TaskManager mTaskManager;
            void Start()
            {
                if(mSMSNotifiler==null)
                mSMSNotifiler = SMSNotifiler.Instance;

                if (mTaskManager == null)
                    mTaskManager = TaskManager.Instance;
            }
            private void OnGUI()
            {
                if (GUILayout.Button("接受任务Task1"))
                {
                    mTaskManager.AcceptTask("T001");
                }
                if (GUILayout.Button("接受任务Task2"))
                {
                    mTaskManager.AcceptTask("T002");
                }
                if (GUILayout.Button("接受任务T003"))
                {
                    mTaskManager.AcceptTask("T003");
                }
                if (GUILayout.Button("打怪Enemy1"))
                {
                    TaskArgs e = new TaskArgs();
                    e.id = "Enemy1";
                    e.amount = 1;
                    mTaskManager.CheackTask(e);

                }
                if (GUILayout.Button("打怪Enemy2"))
                {
                    TaskArgs e = new TaskArgs();
                    e.id = "Enemy2";
                    e.amount = 1;
                    mTaskManager.CheackTask(e);
                }
            }
        }
    这个ControllerTest挂在unity的主场景中会自动添加相关的调用代码比如:TaskManager,主要是一个你游戏任务的相关操作都可以写这里,这个类只是一个简单的示例,比如添加不同的任务参数种类等等。
    好了这个任务相对比较简单,不过我自己觉得也算是五脏俱全了吧,欢迎给我大佬指正不足的地方。最后是项目的工程文件下载地址https://download.csdn.net/download/martins1994/10402293


    展开全文
  • 【游戏开发实战】Unity从零做一个任务系统(含源码工程 | 链式任务 | 主线任务 | 分支任务)

    本文最终效果如下:
    请添加图片描述
    请添加图片描述
    请添加图片描述
    工程思维导图:
    在这里插入图片描述

    工程源码见文章末尾。

    文章目录

    一、前言

    嗨,大家好,我是新发。
    事情是这样的,有小朋友微信问我如何做任务系统,作为一个热心的技术博主,我都是能帮就帮。今天,我就来做一个任务系统吧。
    在这里插入图片描述

    二、什么是任务系统

    任务系统就是一个有明确目标性的系统。通过设置任务来引导玩家进行游戏,让玩家更快的融入游戏中。
    可以说任务系统几乎是游戏必备的模块,我们随便找个游戏都可以看到任务系统。
    在这里插入图片描述
    根据这位小朋友的需求,是要做 主线任务/支线任务 的系统。
    简单的说,就是有一条 主线任务链,在完成主线任务链上的某个节点时,开启下一个任务,并可以开启一条或多条 支线任务链,主线任务和多条支线任务并行。画个图,方便大家理解:
    在这里插入图片描述

    三、需求文档

    由于只有我一个人,没有策划,那我就先充当策划,给自己写个需求文档吧~

    1、故事背景

    主人公林新发刚刚大学毕业,开始面临一个人生难题:如何走上人生巅峰!
    现在我们为林新发设计一套任务,帮助他走上人生巅峰吧~

    2、任务链设计

    下面,就是走上人生巅峰的任务链啦~
    请添加图片描述

    3、任务规则

    主线任务必须按顺序完成;
    主线任务与支线任务可以并行;
    支线任务并不影响主线任务;
    每完成一个任务都可以得到相应的奖励;
    任务界面只显示当前要执行或已完成但还未领取奖励的任务;
    任务界面中要显示每个任务当前的进度;
    每个任务有个前往按钮,点击前往按钮触发任务执行或跳转到相应的界面;
    每个任务有对应的图标,可配置;
    界面底部有一键领奖按钮,点击一键领奖领取所有可以领奖的任务奖励。

    4、界面样式设计

    使用 原型图设计 软件制作界面样式,如下:
    在这里插入图片描述

    四、从哪里开始着手

    对于萌新来说,拿到需求时可能不知道从哪里开始做,是先写代码还是先做界面?代码又是从哪里开始写?

    我总结了一个客户端开发流程,大家可以按这个流程执行,
    在这里插入图片描述

    五、任务配置表

    1、定义表头字段

    根据需求,我们先定义表头字段,
    在这里插入图片描述

    字段解释:

    字段 数据类型 说明
    task_chain_id int 链id,每个任务都有它对应的链id,同一条链上的任务的链id相同
    task_sub_id int 任务id,它是链上的任务id,不同链的任务id可以重复,从1开始往下自增
    icon string 任务图标
    desc string 任务描述,这个会显示到界面中
    task_target string 任务目标,定义一个字符串来表示任务的目标类别,比如加班5次加班10次的任务目标是一样的,只是数量不同,同理,写博客5篇写博客100篇的任务目标也是一样的
    target_amount int 目标数量,比如加班5次的目标数量就是5,写博客100篇的目标数量就是100
    award string 奖励,json格式,例:{"gold":1000},表示奖励1000金币
    open_chain string 要打开的支线任务,格式:链id|任务id,开启多个链以英文逗号隔开。例:2|1,4|1表示打开 链2的子任务1和打开链4的子任务1

    2、配置表格数据

    根据我们上面设计的任务链,在配置表中配置任务数据,入下:

    注:黄色的是主线任务,每条支线任务我都单独标了颜色方便阅读。

    在这里插入图片描述

    表格保存为链式任务.xlsx,如下
    在这里插入图片描述

    3、转表工具:Excel转Json

    Excel表格是方便策划进行配置数值,游戏中并不是直接读取Excel配置,实际项目中一般都是将Excel转为xmljsonlua或自定义的文本格式的配置。
    我这里就以ExcelJson为例,处理Excel我推荐大家使用python来写工具,我之前写过一篇文章:《教你使用python读写Excel表格(增删改查操作),使用openpyxl库》,里面我详细介绍了使用pythonopenpyxl库来读写Excel,建议大家先认真看一下这篇文章。
    这里我就直接把最终我写好的python代码贴出来,代码也很简单,这里不赘述了~

    import openpyxl
    import json
    
    # excel表格转json文件
    def excel_to_json(excel_file, json_f_name):
        jd = []
        heads = []
        book = openpyxl.load_workbook(excel_file)
        sheet = book[u'Sheet1']
        
        max_row = sheet.max_row
        max_column = sheet.max_column
        # 解析表头
        for column in range(max_column):
            heads.append(sheet.cell(1, column + 1).value)
        # 遍历每一行
        for row in range(max_row):
            if row < 2:
            	# 前两行跳过
                continue
            one_line = {}
            # 遍历一行中的每一个单元格
            for column in range(max_column): 
                k = heads[column]
                v = sheet.cell(row + 1, column + 1).value
                one_line[k] = v
            jd.append(one_line)
        book.close()
        # 将json保存为文件
        save_json_file(jd, json_f_name)
    
    # 将json保存为文件
    def save_json_file(jd, json_f_name):
        f = open(json_f_name, 'w', encoding='utf-8')
        txt = json.dumps(jd, indent=2, ensure_ascii=False)
        f.write(txt)
        f.close()
    
    if '__main__' == __name__:
         excel_to_json(u'链式任务.xlsx', 'task_cfg.bytes')
    

    上面的python代码保存为excel_to_json.py,如下
    在这里插入图片描述
    excel_to_json.py放在上面的链式任务.xlsx文件的同级目录中,执行excel_to_json.py,生成task_cfg.bytes
    在这里插入图片描述
    使用文本编辑器打开task_cfg.bytes,看下生成效果,如下,格式正确:
    在这里插入图片描述

    六、读取配置表

    上面配置表做好了,接下来就可以开始动手Unity部分了。
    Unity中如何读取配置表呢?其实配置表也是一种资源,关于资源读取我之前写过相关文章:
    《Unity游戏开发——新发教你做游戏(三):3种资源加载方式》
    在这里插入图片描述

    这里我就简单处理,通过Resources.Load来读取文件。

    1、资源加载:Resources.Load

    先新建一个Resources文件夹,
    在这里插入图片描述
    task_cfg.bytes放在Resources目录中,
    在这里插入图片描述
    这样我们就可以直接使用Resources.Load来读取task_cfg.bytes文件了,如下:

    string txt = Resources.Load<TextAsset>("task_cfg").text;
    

    2、C#的json库:LitJson

    因为我们使用的是json格式的文本,要解析它我们需要使用json库,这里我推荐使用LitJson,可以在GitHub中找到LitJson的开源项目,
    地址:https://hub.fastgit.org/LitJSON/litjson
    在这里插入图片描述
    我们下载下来后,把src目录中的LitJson文件夹整个拷贝到我们Unity工程中,如下:
    在这里插入图片描述
    这样我们就可以在C#中使用LitJson了。
    使用时引入命名空间:

    using LitJson;
    

    3、任务配置配置读取:TaskCfg.cs脚本

    3.1、创建TaskCfg.cs脚本

    现在我们开始写C#代码,养成好习惯,先建好Scripts目录。我们的数据代码、逻辑代码和界面代码要分开,所以建立DataLogicView三个子目录,
    在这里插入图片描述
    Data目录中新建一个TaskCfg.cs脚本,
    在这里插入图片描述

    3.2、定义任务配置结构:TaskCfgItem

    LitJson提供了一个JsonMapper.ToObject<T>(jsonString)方法,可以直接将json字符串转为类对象,前提是类的字段名要与json的字段相同,所以我们先定义一个与json字段名相同的类TaskCfgItem,如下:

    // TaskCfg.cs
    
    /// <summary>
    /// 任务配置结构
    /// </summary>
    public class TaskCfgItem
    {
        public int task_chain_id;
        public int task_sub_id;
        public string icon;
        public string desc;
        public string task_target;
        public int target_amount;
        public string award;
        public string open_chain;
    }
    
    3.3、定义存储配置的容器

    为了方便在内存中索引配置表,我们使用字典来存储,定义一个用来存放配置数据的字典:

    // TaskCfg.cs
    
    // 任务配置,(链id : 子任务id : TaskCfgItem)
    private Dictionary<int, Dictionary<int, TaskCfgItem>> m_cfg;
    
    3.4、读取配置:LoadCfg

    我们封装一个LoadCfg方法来读取配置,如下:

    // TaskCfg.cs
    
    /// <summary>
    /// 读取配置
    /// </summary>
    public void LoadCfg()
    {
        m_cfg = new Dictionary<int, Dictionary<int, TaskCfgItem>>();
        var txt = Resources.Load<TextAsset>("task_cfg").text;
        var jd = JsonMapper.ToObject<JsonData>(txt);
    
    
        for (int i = 0, cnt = jd.Count; i < cnt; ++i)
        {
            var itemJd = jd[i] as JsonData;
            TaskCfgItem cfgItem = JsonMapper.ToObject<TaskCfgItem>(itemJd.ToJson());
    
            if (!m_cfg.ContainsKey(cfgItem.task_chain_id))
            {
                m_cfg[cfgItem.task_chain_id] = new Dictionary<int, TaskCfgItem>();
            }
            m_cfg[cfgItem.task_chain_id].Add(cfgItem.task_sub_id, cfgItem);
        }
    
    }
    
    3.5、索引任务配置项:GetCfgItem

    为了索引任务配置项,我们再封装一个GetCfgItem方法,

    // TaskCfg.cs
    
    /// <summary>
    /// 获取配置项
    /// </summary>
    /// <param name="chainId">链id</param>
    /// <param name="taskSubId">任务子id</param>
    /// <returns></returns>
    public TaskCfgItem GetCfgItem(int chainId, int taskSubId)
    {
        if (m_cfg.ContainsKey(chainId) && m_cfg[chainId].ContainsKey(taskSubId))
            return m_cfg[chainId][taskSubId];
        return null;
    }
    
    3.6、使用单例模式

    我们希望TaskCfg全局只有一个对象,我们使用单例模式,

    // TaskCfg.cs
    
    // 单例模式
    private static TaskCfg s_instance;
    public static TaskCfg instance
    {
        get
        {
            if (null == s_instance)
                s_instance = new TaskCfg();
            return s_instance;
        }
    }
    

    这样我们就可以通过TaskCfg.instance来调用它的public方法了,如下

    // 调用读取配置的方法
    TaskCfg.instance.LoadCfg();
    
    3.7、思维导图

    画个图,
    在这里插入图片描述

    3.8、TaskCfg.cs完整代码

    最终,TaskCfg.cs完整代码如下:

    /// <summary>
    /// 任务配置读取与查询
    /// 作者:林新发,博客:https://blog.csdn.net/linxinfa
    /// </summary>
    
    using System.Collections.Generic;
    using UnityEngine;
    using LitJson;
    
    public class TaskCfg
    {
        /// <summary>
        /// 读取配置
        /// </summary>
        public void LoadCfg()
        {
            m_cfg = new Dictionary<int, Dictionary<int, TaskCfgItem>>();
            var txt = Resources.Load<TextAsset>("task_cfg").text;
            var jd = JsonMapper.ToObject<JsonData>(txt);
          
            for (int i = 0, cnt = jd.Count; i < cnt; ++i)
            {
                var itemJd = jd[i] as JsonData;
                TaskCfgItem cfgItem = JsonMapper.ToObject<TaskCfgItem>(itemJd.ToJson());
    
                if (!m_cfg.ContainsKey(cfgItem.task_chain_id))
                {
                    m_cfg[cfgItem.task_chain_id] = new Dictionary<int, TaskCfgItem>();
                }
                m_cfg[cfgItem.task_chain_id].Add(cfgItem.task_sub_id, cfgItem);
            }
        }
    
        /// <summary>
        /// 获取配置项
        /// </summary>
        /// <param name="chainId">链id</param>
        /// <param name="taskSubId">任务子id</param>
        /// <returns></returns>
        public TaskCfgItem GetCfgItem(int chainId, int taskSubId)
        {
            if (m_cfg.ContainsKey(chainId) && m_cfg[chainId].ContainsKey(taskSubId))
                return m_cfg[chainId][taskSubId];
            return null;
        }
    
        // 任务配置,(链id : 子任务id : TaskCfgItem)
        private Dictionary<int, Dictionary<int, TaskCfgItem>> m_cfg;
    
        private static TaskCfg s_instance;
        public static TaskCfg instance
        {
            get
            {
                if (null == s_instance)
                    s_instance = new TaskCfg();
                return s_instance;
            }
        }
    }
    
    /// <summary>
    /// 任务配置结构
    /// </summary>
    public class TaskCfgItem
    {
        public int task_chain_id;
        public int task_sub_id;
        public string icon;
        public string desc;
        public string task_target;
        public int target_amount;
        public string award;
        public string open_chain;
    }
    

    七、任务数据增删改查:TaskData.cs脚本

    1、创建TaskData.cs脚本

    严格来说,我们需要在服务端存储任务数据、更新任务进度等,这里我就只是在客户端进行模拟,不做服务端了。
    Scripts / Data目录中新建一个TaskData.cs脚本,来实现任务数据增删改查的功能。
    在这里插入图片描述

    2、定义任务数据:TaskDataItem

    我们要读写任务数据,需要先定义任务数据结构TaskDataItem

    // TaskData.cs
    
    /// <summary>
    /// 任务数据
    /// </summary>
    public class TaskDataItem
    {
        // 链id
        public int task_chain_id;
        // 任务子id
        public int task_sub_id;
        // 进度
        public int progress;
        // 奖励是否被领取了,0:未被领取,1:已被领取
        public short award_is_get;
    }
    

    3、本地数据读写:PlayerPrefs

    Unity提供了一个PlayerPrefs类给我们,可以很方便进行本地持久化数据读写。
    在这里插入图片描述
    读:

    string defaultJson = "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]";
    string jsonStr = PlayerPrefs.GetString("TASK_DATA", defaultJson);
    

    写:

    string jsonStr = "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]";
    PlayerPrefs.SetString("TASK_DATA", jsonStr);
    

    清空:

    PlayerPrefs.DeleteKey("TASK_DATA");
    

    4、定义存储数据的容器

    定义一个容器用于内存中存储数据,

    private List<TaskDataItem> m_taskDatas;
    

    5、从本地读取任务数据

    使用PlayerPrefs.GetString接口从本地读取数据,使用Action cb回调是为了模拟实际场景中从服务端数据库读取数据(异步)的过程,

    /// <summary>
    /// 从数据库读取任务数据
    /// </summary>
    /// <param name="cb"></param>
    public void GetTaskDataFromDB(Action cb)
    {
        // 正规是与服务端通信,从数据库中读取,这里纯客户端进行模拟,直接使用PlayerPrefs从客户端本地读取
        var jsonStr = PlayerPrefs.GetString("TASK_DATA", "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]");
        var taskList = JsonMapper.ToObject<List<TaskDataItem>>(jsonStr);
        for (int i = 0, cnt = taskList.Count; i < cnt; ++i)
        {
            AddOrUpdateData(taskList[i]);
        }
        cb();
    }
    

    6、写任务数据到本地

    使用PlayerPrefs.SetString接口写数据到本地,

    /// <summary>
    /// 写数据到数据库
    /// </summary>
    private void SaveDataToDB()
    {
        var jsonStr = JsonMapper.ToJson(m_taskDatas);
        PlayerPrefs.SetString("TASK_DATA", jsonStr);
    }
    

    7、查询指定任务的数据

    /// <summary>
    /// 获取某个任务数据
    /// </summary>
    /// <param name="chainId">链id</param>
    /// <param name="subId">任务子id</param>
    /// <returns></returns>
    public TaskDataItem GetData(int chainId, int subId)
    {
        for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
        {
            var item = m_taskDatas[i];
            if (chainId == item.task_chain_id && subId == item.task_sub_id)
                return item;
        }
        return null;
    }
    

    8、任务数据增加或更新

    新增任务时,需要对列表进行重新排序,确保主线任务(即task_chain_id1)的任务排在最前面,

    /// <summary>
    /// 添加或更新任务
    /// </summary>
    public void AddOrUpdateData(TaskDataItem itemData)
    {
        bool isUpdate = false;
        for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
        {
            var item = m_taskDatas[i];
            if (itemData.task_chain_id == item.task_chain_id && itemData.task_sub_id == item.task_sub_id)
            {
                // 更新数据
                m_taskDatas[i] = itemData;
                isUpdate = true;
                break;
            }
        }
        if(!isUpdate)
            m_taskDatas.Add(itemData);
        // 排序,确保主线在最前面
        m_taskDatas.Sort((a, b) => 
        {
            return a.task_chain_id.CompareTo(b.task_chain_id);
        });
        SaveDataToDB();
    }
    

    9、任务数据删除

    /// <summary>
    /// 移除任务数据
    /// </summary>
    /// <param name="chainId">链id</param>
    /// <param name="subId">任务子id</param>
    public void RemoveData(int chainId, int subId)
    {
        for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
        {
            var item = m_taskDatas[i];
            if (chainId == item.task_chain_id && subId == item.task_sub_id)
            {
                m_taskDatas.Remove(item);
                SaveDataToDB();
                return;
            }
        }
    }
    

    10、思维导图

    按照惯例,画个图:
    在这里插入图片描述

    11、TaskData.cs完整代码

    最终TaskData.cs完整代码如下:

    /// <summary>
    /// 任务数据增删改查
    /// 作者:林新发,博客:https://blog.csdn.net/linxinfa
    /// </summary>
    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using LitJson;
    using System;
    
    public class TaskData
    {
        public TaskData()
        {
            m_taskDatas = new List<TaskDataItem>();
        }
    
        /// <summary>
        /// 从数据库读取任务数据
        /// </summary>
        /// <param name="cb"></param>
        public void GetTaskDataFromDB(Action cb)
        {
            // 正规是与服务端通信,从数据库中读取,这里纯客户端进行模拟,直接使用PlayerPrefs从客户端本地读取
            var jsonStr = PlayerPrefs.GetString("TASK_DATA", "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]");
            var taskList = JsonMapper.ToObject<List<TaskDataItem>>(jsonStr);
            for (int i = 0, cnt = taskList.Count; i < cnt; ++i)
            {
                AddOrUpdateData(taskList[i]);
            }
            cb();
        }
    
        /// <summary>
        /// 添加或更新任务
        /// </summary>
        public void AddOrUpdateData(TaskDataItem itemData)
        {
            bool isUpdate = false;
            for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
            {
                var item = m_taskDatas[i];
                if (itemData.task_chain_id == item.task_chain_id && itemData.task_sub_id == item.task_sub_id)
                {
                    // 更新数据
                    m_taskDatas[i] = itemData;
                    isUpdate = true;
                    break;
                }
            }
            if(!isUpdate)
                m_taskDatas.Add(itemData);
            // 排序,确保主线在最前面
            m_taskDatas.Sort((a, b) => 
            {
                return a.task_chain_id.CompareTo(b.task_chain_id);
            });
            SaveDataToDB();
        }
    
        /// <summary>
        /// 获取某个任务数据
        /// </summary>
        /// <param name="chainId">链id</param>
        /// <param name="subId">任务子id</param>
        /// <returns></returns>
        public TaskDataItem GetData(int chainId, int subId)
        {
            for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
            {
                var item = m_taskDatas[i];
                if (chainId == item.task_chain_id && subId == item.task_sub_id)
                    return item;
            }
            return null;
        }
    
        /// <summary>
        /// 移除任务数据
        /// </summary>
        /// <param name="chainId">链id</param>
        /// <param name="subId">任务子id</param>
        public void RemoveData(int chainId, int subId)
        {
            for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
            {
                var item = m_taskDatas[i];
                if (chainId == item.task_chain_id && subId == item.task_sub_id)
                {
                    m_taskDatas.Remove(item);
                    SaveDataToDB();
                    return;
                }
            }
        }
    
        /// <summary>
        /// 写数据到数据库
        /// </summary>
        private void SaveDataToDB()
        {
            var jsonStr = JsonMapper.ToJson(m_taskDatas);
            PlayerPrefs.SetString("TASK_DATA", jsonStr);
        }
    
        public void ResetData(Action cb)
        {
            PlayerPrefs.DeleteKey("TASK_DATA");
            m_taskDatas.Clear();
            GetTaskDataFromDB(cb);
        }
    
        public List<TaskDataItem> taskDatas
        {
            get { return m_taskDatas; }
        }
    
        // 任务数据
        private List<TaskDataItem> m_taskDatas;
    }
    
    /// <summary>
    /// 任务数据
    /// </summary>
    public class TaskDataItem
    {
        // 链id
        public int task_chain_id;
        // 任务子id
        public int task_sub_id;
        // 进度
        public int progress;
        // 奖励是否被领取了,0:未被领取,1:已被领取
        public short award_is_get;
    }
    
    

    八、任务逻辑:TaskLogic.cs

    1、创建TaskLogics脚本

    Scripts / Logic目录中创建TaskLogic.cs脚本,
    在这里插入图片描述
    任务的逻辑其实就是进度更新、任务完成后领奖、开启下一个任务、开启分支任务等,我们挨个来实现。

    2、成员:TaskData

    先把TaskData作为成员变量,并提供一个数据属性taskDatas,方便访问,

    private TaskData m_taskData;
    
    public List<TaskDataItem> taskDatas
    {
        get { return m_taskData.taskDatas; }
    }
    
    public TaskLogic()
    {
        m_taskData = new TaskData();
    }
    

    3、获取任务数据

    /// <summary>
    /// 获取任务数据
    /// </summary>
    /// <param name="cb">回调</param>
    public void GetTaskData(Action cb)
    {
        m_taskData.GetTaskDataFromDB(cb);
    }
    

    4、更新任务进度

    使用Action<int, bool>回调是为了模拟实际场景中与服务端通信(异步),处理结果会有个返回码ErrorCode(回调函数第一个参数),客户端需判断ErrorCode的值来进行处理,一般约定ErrorCode0表示成功,回调函数第二个参数是是否任务进度已达成,如果任务达成,客户端需要显示领奖按钮,

    /// <summary>
    /// 更新任务进度
    /// </summary>
    /// <param name="chainId">链id</param>
    /// <param name="subId">任务子id</param>
    /// <param name="deltaProgress">进度增加量</param>
    /// <param name="cb">回调</param>
    public void AddProgress(int chainId, int subId, int deltaProgress, Action<int, bool> cb)
    {
        var data = m_taskData.GetData(chainId, subId);
        if (null != data)
        {
            data.progress += deltaProgress;
            m_taskData.AddOrUpdateData(data);
            var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
            if (null != cfg)
                cb(0, data.progress >= cfg.target_amount);
            else
                cb(-1, false);
        }
        else
        {
            cb(-1, false);
        }
    }
    

    5、领取任务奖励

    是否领奖的状态字段为award_is_get,为0表示未领奖,为1表示已领过奖。
    领完奖的任务从列表中移除,并开启下一个任务(如果配置了开启支线任务,则还需要配套开启对应的支线任务),

    /// <summary>
    /// 领取任务奖励
    /// </summary>
    /// <param name="chainId">链id</param>
    /// <param name="subId">任务子id</param>
    /// <param name="cb">回调</param>
    public void GetAward(int chainId, int subId, Action<int, string> cb)
    {
        var data = m_taskData.GetData(chainId, subId);
        if (null == data)
        {
            cb(-1, "{}");
            return;
        }
        if (0 == data.award_is_get)
        {
            data.award_is_get = 1;
            m_taskData.AddOrUpdateData(data);
            GoNext(chainId, subId);
            var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
            cb(0, cfg.award);
        }
        else
        {
            cb(-2, "{}");
        }
    }
    

    6、一键领取任务的奖励

    遍历所有达成进度且未领奖的任务,支线领奖,并开开启每个领完奖的任务的下一个任务(如果配置了开启支线任务,则还需要配套开启对应的支线任务),

    /// <summary>
    /// 一键领取所有任务的奖励
    /// </summary>
    /// <param name="cb"></param>
    public void OneKeyGetAward(Action<int, string> cb)
    {
        int totalGold = 0;
        var tmpTaskDatas = new List<TaskDataItem>(m_taskData.taskDatas);
    
        for (int i = 0, cnt = tmpTaskDatas.Count; i < cnt; ++i)
        {
            var oneTask = tmpTaskDatas[i];
            var cfg = TaskCfg.instance.GetCfgItem(oneTask.task_chain_id, oneTask.task_sub_id);
            if (oneTask.progress >= cfg.target_amount && 0 == oneTask.award_is_get)
            {
                oneTask.award_is_get = 1;
                m_taskData.AddOrUpdateData(oneTask);
                var awardJd = JsonMapper.ToObject(cfg.award);
                totalGold += int.Parse(awardJd["gold"].ToString());
                GoNext(oneTask.task_chain_id, oneTask.task_sub_id);
            }
        }
        if (totalGold > 0)
        {
            JsonData totalAward = new JsonData();
            totalAward["gold"] = totalGold;
            cb(0, JsonMapper.ToJson(totalAward));
        }
        else
        {
            cb(-1, null);
        }
    }
    

    7、开启下一个任务(含支线)

    约定任务id递增,开启下一个任务就是查找id+1的任务并开启。
    支线任务的开启是open_chain字段,格式链id|任务子id,多个支线以,号隔开,例:3|1,5|1表示开启链3的子任务1链5的子任务1

    /// <summary>
    /// 开启下一个任务(含支线)
    /// </summary>
    /// <param name="chainId">链id</param>
    /// <param name="subId">任务子id</param>
    private void GoNext(int chainId, int subId)
    {
        var data = m_taskData.GetData(chainId, subId);
        var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
        var nextCfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id + 1);
    
        if (1 == data.award_is_get)
        {
            // 移除掉已领奖的任务
            m_taskData.RemoveData(chainId, subId);
    
            // 开启下一个任务
            if (null != nextCfg)
            {
                TaskDataItem dataItem = new TaskDataItem();
                dataItem.task_chain_id = nextCfg.task_chain_id;
                dataItem.task_sub_id = nextCfg.task_sub_id;
                dataItem.progress = 0;
                dataItem.award_is_get = 0;
                m_taskData.AddOrUpdateData(dataItem);
            }
    
            // 开启支线任务
            if (!string.IsNullOrEmpty(cfg.open_chain))
            {
                // 开启新分支
                var chains = cfg.open_chain.Split(',');
                for (int i = 0, len = chains.Length; i < len; ++i)
                {
                    var task = chains[i].Split('|');
                    TaskDataItem subChainDataItem = new TaskDataItem();
                    subChainDataItem.task_chain_id = int.Parse(task[0]);
                    subChainDataItem.task_sub_id = int.Parse(task[1]);
                    subChainDataItem.progress = 0;
                    subChainDataItem.award_is_get = 0;
                    m_taskData.AddOrUpdateData(subChainDataItem);
                }
            }
        }
    }
    

    8、思维导图

    画一下图,
    在这里插入图片描述

    9、TaskLogic.cs完整代码

    最终TaskLogic.cs完整代码如下

    /// <summary>
    /// 任务逻辑
    /// 作者:林新发,博客:https://blog.csdn.net/linxinfa
    /// </summary>
    
    using System.Collections.Generic;
    using UnityEngine;
    using LitJson;
    using System;
    
    public class TaskLogic
    {
        public TaskLogic()
        {
            m_taskData = new TaskData();
        }
    
        /// <summary>
        /// 获取任务数据
        /// </summary>
        /// <param name="cb">回调</param>
        public void GetTaskData(Action cb)
        {
            m_taskData.GetTaskDataFromDB(cb);
        }
    
        /// <summary>
        /// 更新任务进度
        /// </summary>
        /// <param name="chainId">链id</param>
        /// <param name="subId">任务子id</param>
        /// <param name="deltaProgress">进度增加量</param>
        /// <param name="cb">回调</param>
        public void AddProgress(int chainId, int subId, int deltaProgress, Action<int, bool> cb)
        {
            var data = m_taskData.GetData(chainId, subId);
            if (null != data)
            {
                data.progress += deltaProgress;
                m_taskData.AddOrUpdateData(data);
                var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
                if (null != cfg)
                    cb(0, data.progress >= cfg.target_amount);
                else
                    cb(-1, false);
            }
            else
            {
                cb(-1, false);
            }
        }
    
        /// <summary>
        /// 一键领取所有任务的奖励
        /// </summary>
        /// <param name="cb"></param>
        public void OneKeyGetAward(Action<int, string> cb)
        {
            int totalGold = 0;
            var tmpTaskDatas = new List<TaskDataItem>(m_taskData.taskDatas);
    
            for (int i = 0, cnt = tmpTaskDatas.Count; i < cnt; ++i)
            {
                var oneTask = tmpTaskDatas[i];
                var cfg = TaskCfg.instance.GetCfgItem(oneTask.task_chain_id, oneTask.task_sub_id);
                if (oneTask.progress >= cfg.target_amount && 0 == oneTask.award_is_get)
                {
                    oneTask.award_is_get = 1;
                    m_taskData.AddOrUpdateData(oneTask);
                    var awardJd = JsonMapper.ToObject(cfg.award);
                    totalGold += int.Parse(awardJd["gold"].ToString());
                    GoNext(oneTask.task_chain_id, oneTask.task_sub_id);
                }
            }
            if (totalGold > 0)
            {
                JsonData totalAward = new JsonData();
                totalAward["gold"] = totalGold;
                cb(0, JsonMapper.ToJson(totalAward));
            }
            else
            {
                cb(-1, null);
            }
        }
    
        /// <summary>
        /// 领取任务奖励
        /// </summary>
        /// <param name="chainId">链id</param>
        /// <param name="subId">任务子id</param>
        /// <param name="cb">回调</param>
        public void GetAward(int chainId, int subId, Action<int, string> cb)
        {
            var data = m_taskData.GetData(chainId, subId);
            if (null == data)
            {
                cb(-1, "{}");
                return;
            }
            if (0 == data.award_is_get)
            {
                data.award_is_get = 1;
                m_taskData.AddOrUpdateData(data);
                GoNext(chainId, subId);
                var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
                cb(0, cfg.award);
            }
            else
            {
                cb(-2, "{}");
            }
        }
    
        /// <summary>
        /// 触发下一个任务,并开启支线任务
        /// </summary>
        /// <param name="chainId">链id</param>
        /// <param name="subId">任务子id</param>
        private void GoNext(int chainId, int subId)
        {
            var data = m_taskData.GetData(chainId, subId);
            var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
            var nextCfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id + 1);
    
            if (1 == data.award_is_get)
            {
                // 移除掉已领奖的任务
                m_taskData.RemoveData(chainId, subId);
    
                // 开启下一个任务
                if (null != nextCfg)
                {
                    TaskDataItem dataItem = new TaskDataItem();
                    dataItem.task_chain_id = nextCfg.task_chain_id;
                    dataItem.task_sub_id = nextCfg.task_sub_id;
                    dataItem.progress = 0;
                    dataItem.award_is_get = 0;
                    m_taskData.AddOrUpdateData(dataItem);
                }
    
                // 开启支线任务
                if (!string.IsNullOrEmpty(cfg.open_chain))
                {
                    // 开启新分支
                    var chains = cfg.open_chain.Split(',');
                    for (int i = 0, len = chains.Length; i < len; ++i)
                    {
                        var task = chains[i].Split('|');
                        TaskDataItem subChainDataItem = new TaskDataItem();
                        subChainDataItem.task_chain_id = int.Parse(task[0]);
                        subChainDataItem.task_sub_id = int.Parse(task[1]);
                        subChainDataItem.progress = 0;
                        subChainDataItem.award_is_get = 0;
                        m_taskData.AddOrUpdateData(subChainDataItem);
                    }
                }
            }
        }
    
        public void ResetAll(Action cb)
        {
            m_taskData.ResetData(cb);
        }
    
        public List<TaskDataItem> taskDatas
        {
            get { return m_taskData.taskDatas; }
        }
    
        private TaskData m_taskData;
        private static TaskLogic s_instance;
        public static TaskLogic instance
        {
            get
            {
                if (null == s_instance)
                    s_instance = new TaskLogic();
                return s_instance;
            }
        }
    }
    
    

    九、UI界面制作

    1、UI资源获取

    简单的UI资源我是在阿里巴巴矢量图库上找,地址:https://www.iconfont.cn/
    比如搜索按钮
    在这里插入图片描述
    找一个形状合适的,可以进行调色,我一般是调成白色,
    在这里插入图片描述

    因为Unity中可以设置Color,这样我们只需要一个白色按钮就可以在Unity中创建不同颜色的按钮了。
    弄点基础的美术资源,
    在这里插入图片描述
    注:那个头像是我自己用PhotoShop画的哦,我之前用PhotoShop画过一幅原创连环画,如下:
    在这里插入图片描述
    同时,我们还需要任务图标,也找一些,
    在这里插入图片描述
    注意所有要使用UGUI来展示资源都设置为Sprite (2D and UI)格式。
    在这里插入图片描述

    注,关于资源的获取,我之前写过一篇文章:《Unity游戏开发——新发教你做游戏(二):60个Unity免费资源获取网站》,感兴趣的同学可以看下,

    2、场景界面制作:Main场景,人生如梦

    养成好习惯,不管你是单场景还是多场景,入口场景命名为Main
    在场景中使用UGUI简单做下入口界面:MainPanel
    在这里插入图片描述
    这个任务系统的主题我定为:人生如梦。
    在这里插入图片描述

    3、制作列表界面预设:TaskPanel.prefab

    根据需求,我们的任务要以列表的显示展示,使用UGUI制作任务列表界面,
    在这里插入图片描述
    如下,
    在这里插入图片描述
    界面保存为TaskPanel.prefab,放在Resources目录中,
    在这里插入图片描述

    4、制作提示框界面预设:TipsPanel.prefab

    为了在客户端模拟测试,做一个提示框界面,
    在这里插入图片描述
    如下:
    在这里插入图片描述
    界面保存为TipsPanel.prefab,放在Resources目录中,
    在这里插入图片描述
    嘛,顺手做个界面动画吧,

    注:关于动画相关的教程,我之前写过一些,感兴趣的同学可以看下:
    《Unity使用Animator控制动画播放,皮皮猫打字机游戏》
    《Unity动画状态机Animator使用》
    《Unity动画使用混合树BlendTree实现动画过渡控制》
    《新发教你做游戏(七):Animator控制角色动画播放》

    请添加图片描述

    5、制作奖励界面预设:AwardPanel.prefab

    领取任务奖励要有个奖励UI展示,做一个,
    在这里插入图片描述
    界面保存为AwardPanel.prefab,放在Resources目录中,
    在这里插入图片描述

    也顺手做个动画,
    请添加图片描述

    十、编写界面代码

    界面预设制作好了,接下来就是写界面交互的代码了。

    1、入口脚本:Main.cs

    C/C++Main入口函数一样,我们的游戏也需要有一个脚本作为入口脚本。
    我们创建一个Main.cs脚本,挂到场景中的MainPanel节点上,
    在这里插入图片描述
    Main.cs脚本代码如下,主要是做一些全局变量、配置、数据等的初始化,然后显示界面,不过我们任务界面代码还没写,先留个TODO

    using UnityEngine;
    
    /// <summary>
    /// 入口脚本
    /// </summary>
    public class Main : MonoBehaviour
    {
        void Start()
        {
            GlobalObj.s_canvasTrans = GameObject.Find("Canvas").transform;
            // 加载任务配置
            TaskCfg.instance.LoadCfg();
            // 获取任务数据
            TaskLogic.instance.GetTaskData(()=> 
            {
                // TODO: 显示任务界面
                
            });
        }
    }
    
    public class GlobalObj
    {
        public static Transform s_canvasTrans;
    }
    

    2、任务列表界面

    2.1、循环复用列表

    任务界面以列表展示任务,我之前做过一个循环复用列表的功能,可以参见我之前这篇文章:《Unity UGUI实现循环复用列表,显示巨量列表信息,含Demo工程源码》
    在这里插入图片描述
    我把之前写的RecyclingList脚本拷贝过来,
    RecyclingList脚本地址:https://codechina.csdn.net/linxinfa/UnityRecyclingListDemo/-/tree/master/Assets/Scripts/RecyclingList
    在这里插入图片描述
    ScrollVIew挂上RecyclingListView脚本,脚本的ChildObj对象需要是RecyclingListViewItem类型的,我们下面会写一个TaskItemUI继承RecyclingListViewItem,这里ChildObj先留空,
    在这里插入图片描述

    2.2、列表项脚本:TaskItemUI.cs

    Scripts / View目录中创建TaskItemUI.cs脚本,它要继承RecyclingListViewItem

    public class TaskItemUI : RecyclingListViewItem
    

    定义一些UI对象,

    // 描述
    public Text desText;
    // 进度
    public Text progressText;
    // 前往按钮
    public Button goAheadBtn;
    // 领奖按钮
    public Button getAwardBtn;
    // 进度条
    public Slider progressSlider;
    // 任务图标
    public Image icon;
    // 任务类型标记,主线/支线 
    public Image taskType;
    

    TaskItemUI脚本挂到ChildItem节点上,并赋值各个UI对象,
    在这里插入图片描述
    现在我们可以给RecyclingListView脚本赋值ChildObj对象了,
    在这里插入图片描述
    TaskItemUI.cs脚本唯一要做的事情就是根据数据更新UI
    在这里插入图片描述

    // TaskItemUI.cs
    
    public Action updateListCb;
    
    /// <summary>
    /// 更新UI
    /// </summary>
    /// <param name="data"></param>
    public void UpdateUI(TaskDataItem data)
    {
        var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
        if (null != cfg)
        {
            desText.text = cfg.desc;
            // TODO 设置图标
            // icon.sprite 
            
            // TODO 设置主线/支线图标
            // var taskTypeSpriteName = 1 == cfg.task_chain_id ? "zhu" : "zhi";
            // taskType.sprite
            
            progressText.text = data.progress + "/" + cfg.target_amount;
            progressSlider.value = (float)data.progress / cfg.target_amount;
            // 前往按钮
            goAheadBtn.onClick.RemoveAllListeners();
            goAheadBtn.onClick.AddListener(() =>
            {
                // TODO 前往任务
            });
    
    		// 领奖按钮
            getAwardBtn.onClick.RemoveAllListeners();
            getAwardBtn.onClick.AddListener(() =>
            {
                TaskLogic.instance.GetAward(data.task_chain_id, data.task_sub_id, (errorCode, award) => 
                {
                    if(0 == errorCode)
                    {
                        // TODO 领奖界面
                        
                        updateListCb();
                    }
                });
            });
    
            goAheadBtn.gameObject.SetActive(data.progress < cfg.target_amount);
            getAwardBtn.gameObject.SetActive(data.progress >= cfg.target_amount && 0 == data.award_is_get);
        }
    }
    

    上面代码有几个TODO
    1 设置图标我们等下写个图标资源管理器;
    2 任务的前往逻辑,我们要弹出提示框;
    3 领奖要显示奖励界面。
    现在,我们继续往下做。

    2.3、列表界面脚本:TaskPanel.cs

    Scripts / View目录中创建TaskPanel.cs脚本,把它挂到TaskPanel界面的根节点上,
    在这里插入图片描述
    最关键的,定义RecyclingListView成员对象,

    // TaskPanel.cs
    
    public RecyclingListView scrollList;
    

    我们的列表更新就是监听它的ItemCallback回调的,

    // TaskPanel.cs
    
    // 列表item更新回调
    scrollList.ItemCallback = PopulateItem;
    
    // ...
    
    private void PopulateItem(RecyclingListViewItem item, int rowIndex)
    {
    	var child = item as TaskItemUI;
    	// 刷新某个item
    	child.UpdateUI(TaskLogic.instance.taskDatas[rowIndex]);
    	child.updateListCb = () =>
        {
        	// 刷新整个列表
            RefreshAll();
        };
    }
    
    /// <summary>
    /// 刷新整个列表
    /// </summary>
    private void RefreshAll()
    {
        scrollList.RowCount = TaskLogic.instance.taskDatas.Count;
        scrollList.Refresh();
    }
    

    我们需要告诉RecyclingListView我们的列表的item的数量,方便它进行计算,

    // TaskPanel.cs
    
    // 设置数据,此时列表会执行更新
    scrollList.RowCount = TaskLogic.instance.taskDatas.Count;
    

    为了便于显示TaskPanel界面,我们封装一个staticShow方法,

    // TaskPanel.cs
    
    private static GameObject s_taskPanelPrefab;
    
    // 显示任务界面
    public static void Show()
    {
        if (null == s_taskPanelPrefab)
            s_taskPanelPrefab = Resources.Load<GameObject>("TaskPanel");
        var panelObj = Instantiate(s_taskPanelPrefab);
        panelObj.transform.SetParent(GlobalObj.s_canvasTrans, false);
    }
    

    这样,我们就可以在Main.cs脚本中加上这个TaskPanel.Show()的调用了,

    // Main.cs
    
    void Start()
    {
    	// ...
    	
        // 获取任务数据
        TaskLogic.instance.GetTaskData(()=> 
        {
            // 显示任务界面
            TaskPanel.Show();
        });
    }
    

    3、提示框界面:TipsPanel.cs

    Scripts / View目录中创建TipsPanel.cs脚本,先定义三个按钮对象,

    public Button closeBtn;
    public Button addProgressBtn;
    public Button onekeyBtn;
    

    在这里插入图片描述
    TipsPanel预设跟节点挂上TipsPanel.cs脚本,赋值按钮对象,
    在这里插入图片描述
    分别写三个按钮的点击逻辑。
    关闭按钮:

    // TipsPanel.cs
    
    // 关闭按钮
    closeBtn.onClick.AddListener(() =>
    {
        Destroy(gameObject);
    });
    

    进度+1按钮:

    // TipsPanel.cs
    
    private int m_taskChainId;
    private int m_tasksubId;
    private Action updateTaskDataCb;
    
    // 进度+1
    addProgressBtn.onClick.AddListener(() =>
    {
        Destroy(gameObject);
        TaskLogic.instance.AddProgress(m_taskChainId, m_tasksubId, 1, (errorCode, finishTask) =>
        {
            updateTaskDataCb();
        });
    });
    

    一键完成按钮:

    // TipsPanel.cs
    
    // 一键完成
    onekeyBtn.onClick.AddListener(() =>
    {
        Destroy(gameObject);
        var cfg = TaskCfg.instance.GetCfgItem(m_taskChainId, m_tasksubId);
        TaskLogic.instance.AddProgress(m_taskChainId, m_tasksubId, cfg.target_amount, (errorCode, finishTask) =>
        {
            updateTaskDataCb();
        });
    });
    

    同理,为了方便显示,也封装一个静态的Show方法:

    // TipsPanel.cs
    
    private static GameObject s_tipsPanelPrefab;
    // 显示任务界面
    public static void Show(int chainId, int subId, Action cb)
    {
    	if (null == s_tipsPanelPrefab)
    	    s_tipsPanelPrefab = Resources.Load<GameObject>("TipsPanel");
    	var panelObj = Instantiate(s_tipsPanelPrefab);
    	panelObj.transform.SetParent(GlobalObj.s_canvasTrans, false);
    	var panelBhv = panelObj.GetComponent<TipsPanel>();
    	panelBhv.Init(chainId, subId, cb);
    }
    
    public void Init(int chainId, int subId, Action cb)
    {
       	m_taskChainId = chainId;
       	m_tasksubId = subId;
       	updateTaskDataCb = cb;
    }
    

    TaskItemUI.cs脚本的前往按钮补上TipsPanel.Show调用,

    // TaskItemUI.cs
    
    goAheadBtn.onClick.AddListener(() =>
    {
       TipsPanel.Show(data.task_chain_id, data.task_sub_id, () =>
       {
           UpdateUI(data);
       });
    });
    

    在这里插入图片描述

    4、奖励界面:AwardPanel.cs

    Scripts / View目录中创建AwardPanel.cs脚本,
    定义UI对象,

    public Text goldText;
    public Button bgBtn;
    private GameObject m_selfGo;
    
    private void Awake()
    {
        m_selfGo = gameObject;
    }
    

    AwardPanel.cs脚本挂到AwardPanel预设跟节点上,赋值UI对象,
    在这里插入图片描述
    逻辑很简单,显示金币奖励,加个1.5秒自动销毁,点击空白处销毁的逻辑,如下:

    // AwardPanel.cs
    
    public void Init(string award)
    {
        var jd = JsonMapper.ToObject(award);
        goldText.text = jd["gold"].ToString();
        bgBtn.onClick.AddListener(() =>
        {
            SelfDestroy();
        });
    
        // 3秒后自动销毁
        Invoke("SelfDestroy", 1.5f);
    }
    
    private void Awake()
    {
        m_selfGo = gameObject;
    }
    
    private void SelfDestroy()
    {
        if (null != m_selfGo)
        {
            Destroy(m_selfGo);
            m_selfGo = null;
        }
    }
    

    也封装一个静态的Show方法,

    private static GameObject s_awardPanelPrefab;
    
    /// <summary>
    /// 显示奖励界面
    /// </summary>
    public static void Show(string award)
    {
        if (null == s_awardPanelPrefab)
            s_awardPanelPrefab = Resources.Load<GameObject>("AwardPanel");
        var panelObj = Instantiate(s_awardPanelPrefab);
        panelObj.transform.SetParent(GlobalObj.s_canvasTrans, false);
        var panelBhv = panelObj.GetComponent<AwardPanel>();
        panelBhv.Init(award);
    }
    

    TaskItemUI.cs脚本的领奖按钮补上AwardPanel.Show调用,

    // TaskItemUI.cs
    
    getAwardBtn.onClick.AddListener(() =>
    {
        TaskLogic.instance.GetAward(data.task_chain_id, data.task_sub_id, (errorCode, award) => 
        {
            Debug.Log("errorCode: " + errorCode + ", award: " + award);
            if(0 == errorCode)
            {
                AwardPanel.Show(award);
                updateListCb();
            }
        });
    });
    

    在这里插入图片描述

    5、精灵资源管理器

    我们需要根据任务配置来显示任务的图标,封装一个精灵管理器。
    在这里插入图片描述

    Scripts / View目录中创建一个SpriteManager.cs脚本,
    代码如下:

    // SpriteManager.cs
    
    using System.Collections.Generic;
    using UnityEngine;
    
    public class SpriteManager
    {
        /// <summary>
        /// 根据名字加载精灵资源
        /// </summary>
        public Sprite GetSprite(string name)
        {
            if (m_sprites.ContainsKey(name))
                return m_sprites[name];
            var sprite = Resources.Load<Sprite>("Sprites/" + name);
            m_sprites.Add(name, sprite);
            return sprite;
        }
    
    
        private Dictionary<string, Sprite> m_sprites = new Dictionary<string, Sprite>();
        private static SpriteManager s_instance;
        public static SpriteManager instance
        {
            get
            {
                if (null == s_instance)
                    s_instance = new SpriteManager();
                return s_instance;
            }
        }
    }
    

    回到TaskItemUI.cs脚本中,补上精灵设置的调用:

    // TaskItemUI.cs
    
    public void UpdateUI(TaskDataItem data)
    {
    	// ...
    	
    	// 图标
    	icon.sprite = SpriteManager.instance.GetSprite(cfg.icon);
    	// 主线/支线标记
    	var taskTypeSpriteName = 1 == cfg.task_chain_id ? "zhu" : "zhi";
    	taskType.sprite = SpriteManager.instance.GetSprite(taskTypeSpriteName);
    	
    }
    

    这样就可以根据配置显示不同的图标了,
    在这里插入图片描述

    十一、运行测试

    代码写完了,一切就绪,运行Unity,测试效果如下:
    请添加图片描述
    请添加图片描述
    人生如梦,究竟是要选梦醒来还是继续做梦呢?
    请添加图片描述

    十二、工程源码

    本文的工程我一上传到CODE CHINA,感兴趣的同学可以自行下载下来学习。
    工程地址:https://codechina.csdn.net/linxinfa/UnityChainTaskDemo
    注:我的Unity版本是Unity 2020.1.14f1c1 (64-bit)
    在这里插入图片描述

    十三、完毕

    好了,写得有点多了,就写到这里吧~
    人生如梦,祝大家都能走上人生巅峰~
    我是林新发:https://blog.csdn.net/linxinfa
    原创不易,若转载请注明出处,感谢大家~
    喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信,拜拜~

    展开全文
  • 定时任务管理系统 gocron

    千次阅读 2018-06-25 14:13:36
    gocron - 定时任务管理系统 项目简介 使用Go语言开发的定时任务集中调度和管理系统, 用于替代Linux-crontab 查看文档 原有的延时任务拆分为独立项目延迟队列 功能特性 Web界面管理定时任务 ...

    gocron - 定时任务管理系统

    Build Status
    Downloads
    license
    Release

    项目简介

    使用Go语言开发的轻量级定时任务集中调度和管理系统, 用于替代Linux-crontab 查看文档

    原有的延时任务拆分为独立项目延迟队列

    功能特性


    • Web界面管理定时任务
    • crontab时间表达式, 精确到秒
    • 任务执行失败可重试
    • 任务执行超时, 强制结束
    • 任务依赖配置, A任务完成后再执行B任务
    • 账户权限控制
    • 任务类型
      • shell任务

    在任务节点上执行shell命令, 支持任务同时在多个节点上运行

  • HTTP任务
    访问指定的URL地址, 由调度器直接执行, 不依赖任务节点
  • 查看任务执行结果日志
  • 任务执行结果通知, 支持邮件、Slack、Webhook
  • 1.5 新特性

    流程图
    任务
    Slack

    截图(1.4)

    任务列表

    任务日志列表

    节点列表

    用户操作

    系统管理

    支持平台

    Windows、Linux、Mac OS

    环境要求

    MySQL

    下载

    releases

    版本升级

    安装

    二进制安装

    1. 解压压缩包
    2. cd 解压目录
    3. 启动
      • 调度器启动
      • Windows: gocron.exe web
      • Linux、Mac OS: ./gocron web
      • 任务节点启动, 默认监听0.0.0.0:5921
      • Windows: gocron-node.exe
      • Linux、Mac OS: ./gocron-node
    4. 浏览器访问 http://localhost:5920

    源码安装

    • 安装Go 1.9+
    • go get -d github.com/ouqiang/gocron
    • 编译 make
    • 启动
      • gocron ./bin/gocron web
      • gocron-node ./bin/gocron-node

    docker

    docker run --name gocron -p 5920:5920 -d ouqg/gocron

    开发

    1. 安装Go1.9+, Node.js, Yarn
    2. 安装前端依赖 make install-vue
    3. 启动gocron, gocron-node make run
    4. 启动node server cd web/vue && yarn run dev, 访问地址 http://localhost:8080

    访问http://localhost:8080, API请求会转发给gocron

    make 编译

    make run 编译并运行

    make package 打包

    生成当前系统的压缩包 gocron-v1.5-darwin-amd64.tar.gz gocron-node-v1.5-darwin-amd64.tar.gz

    make package-all 生成Windows、Linux、Mac的压缩包

    命令

    • gocron

      • -v 查看版本
    • gocron web

      • –host 默认0.0.0.0
      • -p 端口, 指定端口, 默认5920
      • -e 指定运行环境, dev|test|prod, dev模式下可查看更多日志信息, 默认prod
      • -h 查看帮助
    • gocron-node
      • -allow-root *nix平台允许以root用户运行
      • -s ip:port 监听地址
      • -enable-tls 开启TLS
      • -ca-file CA证书文件
      • -cert-file 证书文件
      • -key-file 私钥文件
      • -h 查看帮助
      • -v 查看版本

    程序使用的组件

    反馈

    提交issue

    ChangeLog

    v1.5

    • 前端使用Vue+ElementUI重构
    • 任务通知
      • 新增WebHook通知
      • 自定义通知模板
      • 匹配任务执行结果关键字发送通知
    • 任务列表页显示任务下次执行时间

    v1.4

    • HTTP任务支持POST请求
    • 后台手动停止运行中的shell任务
    • 任务执行失败重试间隔时间支持用户自定义
    • 修复API接口调用报403错误

    v1.3

    • 支持多用户登录
    • 增加用户权限控制

    v1.2.2

    • 用户登录页增加图形验证码
    • 支持从旧版本升级
    • 任务批量开启、关闭、删除
    • 调度器与任务节点支持HTTPS双向认证
    • 修复任务列表页总记录数显示错误

    v1.1

    • 任务可同时在多个节点上运行
    • *nix平台默认禁止以root用户运行任务节点
    • 子任务命令中增加预定义占位符, 子任务可根据主任务运行结果执行相应操作
    • 删除守护进程模块
    • Web访问日志输出到终端
    展开全文
  • 工作流任务调度系统:Apache DolphinScheduler

    万次阅读 多人点赞 2019-10-28 16:21:51
    Apache DolphinScheduler(目前处在孵化阶段,原名为EasyScheduler)是一个分布式、去中心化、易扩展的可视化DAG工作流任务调度系统,其致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程中开...
  • gocron - 定时任务管理系统 项目简介 使用Go语言开发的轻量级定时任务集中调度和管理系统, 用于替代Linux-crontab 查看文档 原有的延时任务拆分为独立项目延迟队列,小编的qq好友列表获取就是用这个做的定时任务...
  • Linux系统编程——多线程实现多任务

    万次阅读 2015-06-10 18:00:27
    每个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进行创建、切换、撤销操作时,需要较大的系统开销。为了减少系统开销,从进程中演化出了线程。为了让进程完成一定的工作,进程必须至少包含一个线程。...
  • 对话系统 | (4) 任务型对话系统基础

    千次阅读 2020-08-14 21:48:33
    本篇博客内容主要来自第十四届中国中文信息学会暑期学校暨中国中文信息学会《前沿技术讲习班》— 张伟男、车万翔《任务型对话系统》 PPT下载链接 1. 任务型对话系统概述 人机对话系统四大功能/分类 2. 任务型对话...
  • gocron - 定时任务管理系统

    千次阅读 2017-06-13 13:52:45
    gocron - 定时任务管理系统项目简介使用Go语言开发的定时任务集中调度和管理系统, 用于替代Linux-crontab 项目地址功能特性 Web界面管理定时任务, 支持动态添加、删除、编辑任务 crontab时间表达式,精确到秒 任务...
  • 操作系统的主要任务

    千次阅读 2020-01-22 10:56:34
    操作系统(OS)是计算机上运行的最重要的程序。 操作系统管理和控制计算机的动作。...操作系统执行基本的任务,比如:识别来自键盘的输入,将输出结果发送给监视器,管理存储设备上的文件和文件夹,控制箱...
  • 为什么要用异步任务? 在android中只有在主线程才能对ui进行更新操作,而其它线程不能直接对ui进行操作 android本身是一个多线程的操作系统,我们不能把所有的操作都放在主线程中操作 ,比如一些耗时操作。如果...
  • Android 异步任务AsyncTask,执行下载任务

    千次阅读 多人点赞 2016-10-17 14:02:31
     Params :启动任务执行的输入参数的类型。  Progress :后台任务完成的进度值的类型。  Result :后台执行任务完成后返回结果的类型。 使用 AsyncTask 只要如下三步即可。  1、创建 Asy
  • celery是Python开发的分布式异步任务调度系统,Celery支持的消息服务有rmq、redis等 以下代码使用的是redis作为消息队列,当然官网推荐生产环境使用rmq。 RabbitMQ is feature-complete, stable, durable and easy...
  • golang 多机定时任务管理系统

    千次阅读 2018-01-30 10:41:46
     ·任务列表文件路径可以自定义,建议使用版本控制系统;  ·内置日志和监控系统,方便各位同学任意扩展;  ·平滑重加载配置文件,一旦配置文件有变动,在不影响正在执行的任务的前提下,平滑加载;  ·IP、...
  • EasyScheduler大数据调度系统架构分享 导语 EasyScheduler是易观平台自主研发的大数据分布式调度系统。主要解决数据研发ETL 错综复杂的依赖关系,而不能直观监控任务...任务调度系统在大数据平台当中是一个核心的...
  • 分布式定时任务调度系统 Saturn 安装部署

    万次阅读 热门讨论 2017-08-10 11:17:03
    Saturn (定时任务调度系统)是唯品会自主研发的分布式的定时任务的调度平台,目标是取代传统的Linux Cron/Spring Batch Job/Quartz的方式,做到全域统一配置,统一监控,任务高可用以及分片。目前该平台己平稳运行1年...
  • 我们进行开发的地方(当然我们也可以直接在调度中心加任务,建议任务首先在开发中心测试,通过之后再加到调度中心)。 目录介绍 如图所示,开发中心有两个文件夹。分别是个人文档、共享文档。这两个文件夹不允许...
  • UCOS2系统内核讲述(四)_创建任务

    千次阅读 2016-09-06 18:18:02
    Ⅰ、写在前面 ...UCOS2系统内核讲述(三)_TCB任务控制块   上一篇文章讲述了关于TCB(Task Control Block)任务控制块数据结构体的内容。本文学习与应用、也与系统内核紧密相关的一个函数“OSTa
  • Easy Scheduler ...设计特点: 一个分布式易扩展的可视化DAG工作流任务调度系统。致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程中开箱即用。 其主要目标如下: 以DAG图的方...
  • Hadoop - 任务调度系统比较

    千次阅读 2017-03-01 14:52:12
    Hadoop - 工作流- 任务调度系统比较 原文: http://www.cnblogs.com/smartloli/p/4964741.html  1.概述 在Hadoop应用,随着业务指标的迭代,而使其日趋复杂化的时候,管理Hadoop的相关应用会变成一件头疼的...
  • 本篇博文是“Java秒杀系统实战系列文章”的第十一篇,本篇博文我们将借助定时任务调度组件来辅助“失效超时未支付的订单记录”的处理,用以解决上篇博文中采用“RabbitMQ死信队列失效处理超时未支付的订单”的瑕疵!...
  • 分布式场景下如何做定时任务,如何防止定时任务多服务器时的冲突?
  • 背包系统在游戏中是必不可少的,在游戏中,所有获得的物品都会储存在背包里面。背包的种类,我一般将它分成两大类,一种是类似于《吞食天地》的“个人背包”,在游戏中每个人物都有一个背包,每个人的背包都互不影响...
  • 使用DownloadProvider来完成下载任务

    万次阅读 2011-10-29 12:54:26
    在同事在做一个自动更新的任务,也做得差不多,这里面有一个很重要的组成部分就是从网站上下载更新包的问题。这对于很多开发者来说,都不是什么大的问题,网上也可以搜索出很多的源码出来进行下载,也就是用...
  • 基于STM32的多任务系统内核--FSC_STOS代码解析

    千次阅读 多人点赞 2019-03-06 17:59:40
    FSC_STOS(点击下载@px86)是专为STM32芯片编写的多任务系统内核。 *任务之间相互独立,互不干扰。 *提供三种运行模式:时间轮询模式、纯优先级模式、时间片+优先级混合模式。 *采用时间切片轮询方式,每个任务运行...
  • 迅雷 任务出错 无法下载文件?...2、下载任务资源都提示“包含违规内容、据当地法律法规文件无法下载”; 到了本月16号,迅雷官方微博推送了一篇这样的微博:“由于众所周知的原因,部分资源在下载时
  • Apache DolphinScheduler 1.2.1发布,可视化工作流任务调度系统 ​Apache DolphinScheduler 于2020年2月24日正式发布 1.2.1 版,发布内容如下: 新特性: [#1497] 通过 API 创建的工作流在前端展示时自动调整布局...
  • win10系统任务栏透明方法

    千次阅读 2018-08-22 18:46:00
    win10任务栏透明 很多用了TranslucentTB或者StartIsBack的朋友都应该或多或少对这两款软件的缺陷保有遗憾,前者虽然适用于各个Windows版本、绿色小巧,但效果却不大理想,后者虽然效果完美,但必须随着Windows...
  • Ⅰ、写在前面 学习本文之前可以参看我前面的...上一篇文章讲述了关于OSInit函数体中几个关于系统内核重要的函数,本文将针对上一篇文章中OS_InitTCBList(初始化任务控制块)函数重点讲述一下TCB(Task Control Bloc
  • iOS 多任务下载(支持离线)

    千次阅读 2017-03-30 13:13:12
    代码下载代码下载地址效果展示分析说到iOS中的下载,有很多方式可以实现,NSURLConnection(已经弃用)就不说了,AFNetworking也不说了。我使用的是NSURLSession,常用的有3个任务类,NSURLSessionDataTask、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 401,962
精华内容 160,784
关键字:

下载任务系统