Unity 定时回调系统技术专题(Siki Plane)
官網(wǎng)Plane的GitHub
官網(wǎng)視頻
官方B站視頻
在多線(xiàn)程中,每個(gè)線(xiàn)程都有自己的資源,但是代碼區(qū)是共享的,即每個(gè)線(xiàn)程都可以執(zhí)行相同的函數(shù)。
101 物體的生命周期
//有無(wú)勾勾
FixedUpdate
private void FixedUpdate(){public float timer = 0f;public float time = 1f;timer += Time.fixedDeltaTime;//fixedDeltaTime。下圖是1秒1次輸出
(問(wèn)題) NullReferenceException: Object reference not set to an instance of an object
//一直存在,但不影響運(yùn)行
//unityHub下載不了2020(下載到快完了,全部沒(méi)了)
//所以從官網(wǎng)下安裝包,但又找不到中文包,所以從unityHub下載的2018中文包復(fù)制過(guò)來(lái)
//不知道是不是這原因
102 FixedUpdate
不是固定步長(zhǎng)遞增
private void FixedUpdate(){Test02();}void Test02(){print("啟動(dòng)時(shí)長(zhǎng):" + Time.realtimeSinceStartup);print("啟動(dòng)時(shí)長(zhǎng):" + Time.realtimeSinceStartupAsDouble);print("啟動(dòng)幀數(shù):" + Time.renderedFrameCount);}最大允許時(shí)間步進(jìn)
驅(qū)動(dòng)剛體運(yùn)動(dòng)時(shí),超過(guò)最大允許時(shí)間步進(jìn),就終止此次運(yùn)算,進(jìn)行主循環(huán)運(yùn)行,以此保證時(shí)間。
所以實(shí)際剛體運(yùn)動(dòng)會(huì)慢點(diǎn),但忽略不計(jì)。
103 腳本執(zhí)行順序
需求
//一個(gè)物體下兩個(gè)腳本的執(zhí)行順序
//一個(gè)物體下同一個(gè)腳本的執(zhí)行順序
不同腳本的執(zhí)行順序
//AB對(duì)比同一個(gè)物體下運(yùn)行后,組件的順序就固定了,后面再調(diào)整無(wú)用
//AC對(duì)比,同一物體下,組建運(yùn)行順序從下到上(不同物體也是從下到上)
Unity自帶的腳本執(zhí)行順序(針對(duì)不同腳本)
不同物體下同一個(gè)腳本執(zhí)行順序
//第一次從下到上
//后面再調(diào)整物體順序,不便了
//作者推薦的,用一個(gè)父節(jié)點(diǎn)來(lái)管理
(問(wèn)題) 學(xué)習(xí)時(shí)腳本分類(lèi)
到網(wǎng)頁(yè)復(fù)制標(biāo)題
到VS利用Alt鍵盤(pán)修改標(biāo)題(一般不允許加空格,如下下圖是不行的)
MD、bat(另存為ASNI編碼,默認(rèn)的UTF8是會(huì)亂碼)
“靈者更名”添加空格
//MD 新建文件夾
104 理解Unity主線(xiàn)程設(shè)計(jì)思想1
//一個(gè)線(xiàn)程,主線(xiàn)程,串行運(yùn)行
//邏輯幀,一個(gè)物體從Update到下一次Update的時(shí)間
105 理解Unity主線(xiàn)程設(shè)計(jì)思想2(線(xiàn)程ID)
//不允許在主線(xiàn)程之外訪(fǎng)問(wèn)transform,限制編程環(huán)境單線(xiàn)程
//底層運(yùn)用線(xiàn)程池,不需要開(kāi)發(fā)者管理
106 協(xié)程的常規(guī)使用1(開(kāi)啟協(xié)程的兩種方式的區(qū)別)
//第一種調(diào)用參數(shù)上限沒(méi)有限制
//第一種調(diào)用參數(shù)上限為1
107 協(xié)程的常規(guī)使用2(終止協(xié)程)
問(wèn)答
//我也測(cè)試到StopCoroutine(A()); 對(duì) StartCoroutine(A()); 無(wú)效
//視頻也講到StopCoroutine(A()); 對(duì) StartCoroutine(“A”); 無(wú)效
108 深入理解協(xié)程原理1
事件函數(shù)的執(zhí)行順序
//協(xié)程的最大作用是加載資源
失效開(kāi)啟協(xié)程的物體
//開(kāi)啟協(xié)程后,失效物體,再次激活物體,協(xié)程不運(yùn)行(Unity那張生命周期圖,OnDisabled就沒(méi)協(xié)程了)
協(xié)程串協(xié)程
//單線(xiàn)程的體現(xiàn)
void Start(){StartCoroutine(A());}IEnumerator A(){print("1");yield return new WaitForSeconds(3f);print("2");yield return StartCoroutine(B());print("3");}IEnumerator B(){print("4");yield return new WaitForSeconds(2f);print("5");}協(xié)程串協(xié)程串協(xié)程
//我也蒙這翻譯,再串一段協(xié)程
//yield像一堵墻,執(zhí)行順序如下。方框處是整個(gè)協(xié)程徹底結(jié)束的時(shí)候
109 深入理解協(xié)程原理2(資源加載)
//異步加載Resources文件夾里的某一物體
ResourceRequest resourceRequest;GameObject go;// Start is called before the first frame updatevoid Start(){StartCoroutine(LoadResourcesAsync());}IEnumerator LoadResourcesAsync(){resourceRequest = Resources.LoadAsync<GameObject>("Cube");//類(lèi)型,名字yield return resourceRequest;go = ( resourceRequest.asset) as GameObject;if (go != null){Instantiate(go);}else{throw new System.Exception("異常");}} // Update is called once per framevoid Update(){if (resourceRequest != null && go != null){print(resourceRequest.progress);}}110 實(shí)現(xiàn)思路分析
//服務(wù)器定時(shí)任務(wù)多
//協(xié)程依賴(lài)MonoBehavior依賴(lài)于Unity,不能在服務(wù)器跑Unity。如下圖
//借鑒攜程是幀驅(qū)動(dòng)的思想實(shí)現(xiàn)計(jì)時(shí)器
201 搭建測(cè)試環(huán)境
腳本TimerSys(單例),GameRoot
202 初始化腳本順序
以往單例放在Awake
public class TimerSys : MonoBehaviour {public static TimerSys _instance;void Awake(){_instance = this;}public void AddTimeTask(){print("定時(shí)任務(wù)");} public class GameRoot : MonoBehaviour {// Start is called before the first frame updatevoid Start(){TimerSys._instance.AddTimeTask();}現(xiàn)在封裝成方法
//加一個(gè)按鈕事件
//單例采用封裝方法,一次控制順序,防止單例后運(yùn)行
203 基礎(chǔ)定時(shí)功能實(shí)現(xiàn)
//之前“黑暗之光”時(shí),我用委托做了定時(shí)器(用Time.deltaTime的)。有點(diǎn)類(lèi)似
//方法復(fù)制那里如果不是需要加上系統(tǒng)運(yùn)行時(shí)間,參數(shù)改為PETimeTask也不錯(cuò)
(問(wèn)題) NullReferenceException: Object reference not set to an instance of an object
NullReferenceException: Object reference not set to an instance of an object
//空指針
//沒(méi)有做初始化taskList = new List();
PETimetask
//任務(wù)數(shù)據(jù)類(lèi) using System;public class PETimetask {public Action callback;//要定時(shí)的任務(wù)public float destTime;//延時(shí)幾秒 }GameRoot
public class GameRoot : MonoBehaviour {TimerSys timerSys;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<TimerSys>();timerSys.Init();}public void OnAddtimeTaskClick(){timerSys.AddTimeTask(FuncA, 2f);}void FuncA(){print("FuncA");} }TimerSys
public class TimerSys : MonoBehaviour {public List<PETimetask> taskList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();}public void AddTimeTask(Action callback, float destTime){print("添加定時(shí)任務(wù)");PETimetask task = new PETimetask();float time = Time.realtimeSinceStartup + destTime;task.callback = callback;task.destTime = time;//taskList.Add(task);}// Update is called once per framevoid Update(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup < task.destTime){continue;}else{if (task.callback != null)//這個(gè)判空的直覺(jué)我體會(huì)不到{task.callback();} taskList.Remove(task);i--;//移除List自動(dòng)接上去,所以還需要從原索引}}} }效果
204 增加臨時(shí)緩存列表
需求
//多線(xiàn)程定時(shí)
//增加緩存列表taskTmpList,避免加鎖提高效率
代碼
public class TimerSys : MonoBehaviour {[Tooltip("定時(shí)任務(wù)列表")] public List<PETimetask> taskList;[Tooltip("緩存的定時(shí)任務(wù)列表")] public List<PETimetask> taskTmpList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();taskTmpList = new List<PETimetask>(); }#region 添加定時(shí)任務(wù)public void AddTimeTask(Action callback, float delay)//默認(rèn)毫秒{PETimetask task = new PETimetask();float time = Time.realtimeSinceStartup+ delay;task.callback = callback;task.destTime = time;//taskTmpList.Add(task);}#endregion///// <summary>/// 加載緩存的臨時(shí)列表<para />/// </summary>void LoadTaskTmpList(){ for (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 執(zhí)行定時(shí)任務(wù)<para />/// </summary> void RunTaskList(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup < task.destTime){continue;}else{if (task.callback != null)//這個(gè)判空的直覺(jué)我體會(huì)不到{task.callback();}taskList.Remove(task);i--;//移除List自動(dòng)接上去,所以還需要從原索引 }}}void Update(){LoadTaskTmpList();RunTaskList(); }}(需求) 注釋方法,全局提示
C# 方法注釋,讓參數(shù)、返回結(jié)果可見(jiàn),并且實(shí)現(xiàn)換行顯示
//其實(shí)這時(shí)我需求只需要提示方法是干什么的就行了
205 增加時(shí)間單位設(shè)置功能
//Time.realtimeSinceStartu的單位是秒,所以毫秒*1000f
//GameRoot的調(diào)用相應(yīng)調(diào)整
206 增加任務(wù)循環(huán)功能
需求
//delay,執(zhí)行完一次后,destTime+=delay
//count>1,執(zhí)行后-1
//count0循環(huán)執(zhí)行
//count1,執(zhí)行后可以移除該定時(shí)任務(wù)
//
//采用構(gòu)造方法
//GameRoot傳參調(diào)用3次
(代碼) PETimetask
public class PETimetask {public Action callback;//要定時(shí)的任務(wù)public float destTime;//延時(shí)到游戲時(shí)間結(jié)束public int count;//執(zhí)行次數(shù)public float delay;//延時(shí)幾秒public PETimetask(Action callback, float destTime, int count, float delay){this.callback = callback;this.destTime = destTime;this.count = count;this.delay = delay;} }(代碼) TimerSys
void RunTaskList(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup * 1000f < task.destTime){continue;}else{if (task.callback != null)//我沒(méi)有意識(shí)咋{task.callback();}if (task.count == 1){taskList.Remove(task);i--;//移除List自動(dòng)接上去,所以還需要從原索引 }else{if (task.count != 0){task.count--; }//定義0==循環(huán)task.destTime += task.delay;}......public void AddTimeTask(Action callback,float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count=1)//默認(rèn)毫秒{delay = UnitConversion(delay, unit);// float time = Time.realtimeSinceStartup * 1000f+ delay;PETimetask task = new PETimetask(callback, time, count, delay);//taskTmpList.Add(task);}(問(wèn)題) 可選參數(shù)必須出現(xiàn)在所有必要參數(shù)之后
//將int count提到前面
//視頻是int count=1,也是一個(gè)可選參數(shù),所以不用提
207 生成定時(shí)任務(wù)全局ID
需求分析
//鎖里面處理id
//處理超出id(int類(lèi)型)范圍
//int.MaxValue
//鎖
//我用taskList[i].id來(lái)給id遍歷。視頻是新建了一個(gè)idList專(zhuān)門(mén)存儲(chǔ)id。(我想盡量減少變量,可能以后有問(wèn)題,現(xiàn)在找不到問(wèn)題)
//移除或減少次數(shù),對(duì)于idList,都要移除id
(代碼) TimerSys
//調(diào)用是執(zhí)行3次
[Tooltip("定義鎖")] private static readonly string obj="lock";[Tooltip("全局id,初始值經(jīng)過(guò)方法后是0,所以-1")] public int id;//[Tooltip("id列表")] public List<int> idList;public static TimerSys _instance;public void Init(){......//idList = new List<int>();id = -1;}public void AddTimeTask(Action callback,float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count=1)//默認(rèn)毫秒{......int id = GenerateId();PETimetask task = new PETimetask(callback, time, count, delay, id);//taskTmpList.Add(task);//idList.Add(id);}/// <summary>/// 生成唯一索引id<para />/// </summary> int GenerateId(){ lock(obj)//多線(xiàn)程顯示唯一id就要鎖{id++;while (true){//超出int最大值if (id == int.MaxValue){id = 0;}//是否用過(guò)了, bool isUsed = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isUsed = true;break;}}if (isUsed) id++;else break;} }return id;}208 增加任務(wù)刪除功能
//根據(jù)id,遍歷taskList和taskTmpList,悠久移除,返回true
/;/沒(méi)采用idList,只用taskList代碼簡(jiǎn)潔了一些
(代碼) TimeSys
/// <summary>/// 刪除定時(shí)任務(wù)<para />/// </summary>public bool DeleteTimeTask(int id){bool isExisted = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isExisted = true;taskList.RemoveAt(i);break;}}for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isExisted = true;taskTmpList.RemoveAt(i);break;}}return isExisted;}//調(diào)用是3次,下圖在第二次進(jìn)行刪除定時(shí)任務(wù)
209 增加任務(wù)替換功能
(需求)
//遍歷兩個(gè)列表進(jìn)行替換
//原方法是循環(huán)輸出FuncA,新方法定為輸出一次FuncB
(代碼)
public bool ReplaceTimeTask(int id,Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1){delay = UnitConversion(delay, unit);// float time = Time.realtimeSinceStartup * 1000f + delay;PETimetask task = new PETimetask(callback, time, count, delay, id);////必在兩個(gè)表之一bool isReplaced = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isReplaced = true;taskList[i] = task;break;}}if (isReplaced == false){for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isReplaced = true;taskTmpList[i] = task;break;}}}return isReplaced;}(問(wèn)題) FuncB執(zhí)行了兩次
taskTmpList.Add(task);使其多運(yùn)行了一次
public bool ReplaceTimeTask(int id,Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1){delay = UnitConversion(delay, unit);// float time = Time.realtimeSinceStartup * 1000f + delay;PETimetask task = new PETimetask(callback, time, count, delay, id);//// taskTmpList.Add(task);210 清理定時(shí)任務(wù)全局ID
(需求)
定義一個(gè)列表,存儲(chǔ)移除的task的id
該列表不為空的,和idList做對(duì)比,有的話(huà)就移除掉idList里的id
由于我是指直接取taskList[i].id這種形式,沒(méi)有涉及idList,所以不需要
定義幀的任務(wù)數(shù)據(jù)類(lèi)和Sys
我是新建一個(gè)類(lèi)FrameTimerSys,原來(lái)的命名為T(mén)imeTimerSys。視頻將這兩部分放一起
211 幀定時(shí)任務(wù)開(kāi)發(fā)1
需求
//使用lambda表達(dá)式來(lái)簡(jiǎn)化調(diào)用的輸出函數(shù)
//System.DateTime.Now,系統(tǒng)現(xiàn)在時(shí)間
//回調(diào)加捕捉異常
lambda
public void OnAddTimeTaskClick(){id=timerSys.AddTimeTask( ()=> {print("FuncA,id:" + id);print(",時(shí)間:"+System.DateTime.Now); },1000f, PETimeUnit.MillSecond,0);//0是循環(huán)}回調(diào)加捕捉異常
void RunTaskList(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup * 1000f < task.destTime){continue;}else{try{if (task.callback != null)//我沒(méi)有意識(shí)要檢查非空{task.callback();}}catch (Exception e){print(e.ToString());}......212 幀定時(shí)任務(wù)開(kāi)發(fā)2;213 測(cè)試幀定時(shí)任務(wù)
需求
//視頻用一個(gè)每幀遞增的frameCounter來(lái)代替Time.renderedFrameCount。
//我將frameCounter在沒(méi)有定時(shí)任務(wù)時(shí)置于0
//新建幀任務(wù)數(shù)據(jù)類(lèi)
//視頻將幀定時(shí)的方法跟時(shí)間定時(shí),寫(xiě)在一個(gè)類(lèi),我拆了出來(lái),雖然雙方的一些Tool類(lèi)型的方法一樣
PEFrameTask
//幀任務(wù)數(shù)據(jù)類(lèi) public class PEFrameTask {public Action callback;//要定時(shí)的任務(wù)public int destFrame;//有定時(shí)任務(wù)時(shí),frameCounter+delaypublic int count;//執(zhí)行次數(shù)public int delay;//延時(shí)幾幀public int id;//索引public PEFrameTask(Action callback, int destFrame, int count, int delay, int id){this.id = id;this.callback = callback;this.destFrame = destFrame;this.count = count;this.delay = delay;} }FrameTimerSys
public class FrameTimerSys : MonoBehaviour {[Tooltip("定時(shí)任務(wù)列表")] public List<PEFrameTask> taskList;[Tooltip("緩存的定時(shí)任務(wù)列表")] public List<PEFrameTask> taskTmpList;[Tooltip("定義鎖")] private static readonly string obj="lock";[Tooltip("全局id,初始值經(jīng)過(guò)方法后是0,所以-1")] public int id;[Tooltip("有定時(shí)任務(wù)時(shí)的紀(jì)元幀")] public int frameCounter = 0;//[Tooltip("id列表")] public List<int> idList;public static FrameTimerSys _instance;public void Init(){_instance = this;taskList = new List<PEFrameTask>();taskTmpList = new List<PEFrameTask>();//idList = new List<int>();id = -1;}#region 增刪改public int AddTimerTask(Action callback,int delay, int count=1)//默認(rèn)毫秒{// int id = GenerateId();PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay,id);//taskTmpList.Add(task);return id;}/// <summary>/// 刪除定時(shí)任務(wù)<para />/// </summary>public bool DeleteTimerTask(int id){bool isExisted = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isExisted = true;taskList.RemoveAt(i);break;}}for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isExisted = true;taskTmpList.RemoveAt(i);break;}}return isExisted;}/// <summary>/// 替換定時(shí)任務(wù)<para />/// </summary>public bool ReplaceTimerTask(int id,Action callback, int delay, int count = 1){// PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay, id);////必在兩個(gè)表之一bool isReplaced = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isReplaced = true;taskList[i] = task;break;}}if (isReplaced == false){for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isReplaced = true;taskTmpList[i] = task;break;}}}return isReplaced;}#endregion///// <summary>/// 加載緩存的臨時(shí)列表<para />/// </summary>void LoadTaskTmpList(){if (taskTmpList.Count <= 0) return;//一直打印輸出,所以returnfor (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 執(zhí)行定時(shí)任務(wù)<para />/// </summary> void RunTaskList(){if (taskList.Count <= 0){frameCounter=0;return;}frameCounter++;for (int i = 0; i < taskList.Count; i++){PEFrameTask task = taskList[i];if (frameCounter < task.destFrame){continue;}else{try{if (task.callback != null)//我沒(méi)有意識(shí)要檢查非空{task.callback();}}catch (Exception e){print(e.ToString());}if (task.count == 1){taskList.Remove(task);i--;//移除List自動(dòng)接上去,所以還需要從原索引 }else{if (task.count != 0){task.count--;//idList.Remove(task.id);}else{//定義0==循環(huán)}task.destFrame += task.delay;}}}}void Update(){LoadTaskTmpList();RunTaskList(); }#region Tool/// <summary>/// 生成唯一索引id<para />/// </summary> int GenerateId(){lock (obj)//多線(xiàn)程顯示唯一id就要鎖{id++;while (true){//超出int最大值if (id == int.MaxValue){id = 0;}//是否用過(guò)了, bool isUsed = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isUsed = true;break;}}if (isUsed) id++;else break;}}return id;}#endregionGameRoot_Frame 調(diào)用測(cè)試
public class GameRoot_Frame : MonoBehaviour {FrameTimerSys timerSys;[Tooltip("為了測(cè)試刪除,替換")] public int id;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<FrameTimerSys>();timerSys.Init();}public void OnAddTimerTaskClick(){id=timerSys.AddTimerTask( ()=> {print("FuncA,id:" +id + " " + "幀數(shù):"+Time.renderedFrameCount); },60, 0);//0是循環(huán)}public void OnDeleteTimerTaskClick(){timerSys.DeleteTimerTask(id);}public void OnReplaceTimerTaskClick(){bool isReplaced=timerSys.ReplaceTimerTask(id,()=> { print("FuncB"); }, 60, 1);if (isReplaced) { print("替換成功!"); }}}效果
301 剝離Monobehaviour依賴(lài)
需求
//從這開(kāi)始是服務(wù)器上的定時(shí)器,官網(wǎng)學(xué)員反應(yīng)難度跟前面兩部分對(duì)比明顯(我也感受到了,沒(méi)有一個(gè)視頻敲一些出一個(gè)效果的步步前進(jìn))
//服務(wù)器不能裝Unity,所以不能用using UnityEngine。去掉using UnityEngine;后Debug,print,Time,Update等都不能用了。所以要解決這幾點(diǎn)。
//GameRoot,UI按鈕調(diào)用
//TimerSys看,實(shí)例并且引用PETime的方法
//PETimer,去除using UnityEngine;和MonoBehaviour,放在服務(wù)器上。移植了前兩個(gè)TimeSys的主體功能。重寫(xiě)update,打印,時(shí)間等原來(lái)依賴(lài)于Unity的部分
增加 刪除 更新
GameRoot
public class GameRoot : MonoBehaviour {TimerSys timerSys;[Tooltip("為了測(cè)試刪除")] public int id;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<TimerSys>();timerSys.Init();}#region 時(shí)間public void OnAddTimeTaskClick(){id = timerSys.AddTimeTask(() =>{print("FuncA,id:" + id);print(",時(shí)間:" + System.DateTime.Now);},1000f, PETimeUnit.MillSecond, 0);//0是循環(huán)}public void OnDeleteTimeTaskClick(){timerSys.DeleteTimeTask(id);}public void OnReplaceTimeTaskClick(){bool isReplaced = timerSys.ReplaceTimeTask(id, () => print("FuncB"), 1000f, PETimeUnit.MillSecond, 1);if (isReplaced) { print("替換成功!"); }}#endregion#region 幀public void OnAddFrameTaskClick(){id = timerSys.AddFrameTask(() =>{print("FuncA,id:" + id);print(",時(shí)間:" + System.DateTime.Now);},60, 0);//0是循環(huán)}public void OnDeleteFrameTaskClick(){timerSys.DeleteFrameTask(id);}public void OnReplaceFrameTaskClick(){bool isReplaced = timerSys.ReplaceFrameTask(id, () => { print("FuncB"); }, 60, 1);if (isReplaced) { print("幀替換成功!"); }}#endregion }TimerSys
public class TimerSys :MonoBehaviour {public static TimerSys _instance;public PETimer pt;public void Init(){_instance = this;pt=new PETimer();pt.Init();pt.SetLog((string log)=> { print("初始化"+log); });}#region 定時(shí)任務(wù) 增 刪 改public int AddTimeTask(Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默認(rèn)毫秒{return pt.AddTimeTask(callback, delay, unit, count);}public bool DeleteTimeTask(int id){return pt.DeleteTimeTask(id);}public bool ReplaceTimeTask(int id, Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1){return pt.ReplaceTimeTask(id,callback, delay, unit, count);}#endregion#region 幀 增 刪 改public int AddFrameTask(Action callback, int delay, int count = 1)//默認(rèn)毫秒{return pt.AddFrameTask(callback,delay,count);}public bool DeleteFrameTask(int id){return pt.DeleteFrameTask(id);}public bool ReplaceFrameTask(int id, Action callback, int delay, int count = 1){return pt.ReplaceFrameTask(id, callback, delay, count);}#endregionprivate void Update(){pt.Update();} }302 設(shè)置日志處理
需求
//實(shí)現(xiàn)打印,update(沒(méi)有繼承MonoBehavior,需要TimerSys來(lái)驅(qū)動(dòng)), 構(gòu)造
PETimer的打印
//SetLog被外界調(diào)用,傳入一個(gè)方法引用給自己的委托l(wèi)og
//Log規(guī)定自己的委托將會(huì)執(zhí)行帶字符串參數(shù)的方法體
// PETimer
//TimerSys
public void Init(){......pt=new PETimer();pt.Init();pt.SetLog((string log)=> { print("初始化"+log); });}PETimer的Update(靠其他腳本的Update來(lái)驅(qū)動(dòng))
// PETimer
public void Update(){LoadTaskTmpList();RunTaskList();Frame_LoadTaskTmpList();Frame_RunTaskList();}//TimerSys
private void Update(){pt.Update();}PETimer的構(gòu)造
//我加上這一段報(bào)空
public PETimer(){taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();}303 計(jì)算機(jī)紀(jì)年與UTC時(shí)區(qū)
//當(dāng)年出現(xiàn)C語(yǔ)言版本的Unix,32位的int按秒計(jì)算是68.1年。兩個(gè)因素所以選1970public DateTime startDateTime = new DateTime ( 1970, 1, 1, 0, 0, 0,0 );public double GetUTCMillSeconds()//計(jì)算時(shí)間間隔{/**DateTime.Now本地時(shí)間,中國(guó)東八區(qū)DateTime.UtcNow時(shí)間標(biāo)準(zhǔn)時(shí)間實(shí)際服務(wù)器標(biāo)準(zhǔn)時(shí)間,到具體國(guó)家在進(jìn)行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}304 剝離UnityEngine的時(shí)間計(jì)算依賴(lài)
//將destTime和delay的類(lèi)型改為double
//PETimer中將原本的 Time.realtimeSinceStartup改為GetUTCMillSeconds()
//視頻定義了一個(gè)全局的nowTime=GetUTCMillSeconds();
PETimer的代碼結(jié)構(gòu)
具體的看Plane的github
官網(wǎng)Plane的GitHub PlaneZhong/PETimer
305 移植到控制臺(tái)工程環(huán)境
需求
//VS 新建項(xiàng)目【控制臺(tái)】添加到原方案
//拖過(guò)來(lái)PETimer,PETimeTask
Program.cs
using System;namespace TimedCallback_VS {class Program{static void Main(string[] args){Test();Console.WriteLine("Hello World!");Console.ReadKey();}static void Test(){PETimer pt = new PETimer();pt.Init();pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask( ()=> { Console.WriteLine(pt.id+" "+DateTime.Now); }, 1000d,PETimeUnit.MillSecond,0);while (true){pt.Update();}}} }306 Timer線(xiàn)程計(jì)時(shí)器
Thread.CurrentThread.ManagedThreadId線(xiàn)程Id
static void ThreadTest()//那個(gè)線(xiàn)程空閑就用哪個(gè)線(xiàn)程{double millSeconds = 50d;System.Timers.Timer t = new System.Timers.Timer(millSeconds);//單獨(dú)Timers有二義性t.AutoReset = true;t.Elapsed += (Object sendeer, ElapsedEventArgs args) =>{Console.WriteLine("線(xiàn)程ID:{0}", Thread.CurrentThread.ManagedThreadId);};t.Start();}307 整合Timer線(xiàn)程計(jì)器
program
static void DetachedThread()//獨(dú)立線(xiàn)程{PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask(() => { Console.WriteLine("任務(wù)id:{0},線(xiàn)程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); },100d, //執(zhí)行AddTimeTask的時(shí)間間隔PETimeUnit.MillSecond,0);}PETimer
public PETimer(int interval=0)//{Init();//發(fā)現(xiàn)報(bào)空錯(cuò)誤,是未初始化//taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();//if (interval >= 0){DetachedThread(interval);}}void DetachedThread(int interval){System.Timers.Timer t = new System.Timers.Timer(interval);//執(zhí)行Update時(shí)間間隔t.AutoReset = true;t.Elapsed += (Object sender, ElapsedEventArgs args) =>{Update();};t.Start();}效果
308 增加任務(wù)回調(diào)tid參數(shù)
需求
將任務(wù)數(shù)據(jù)類(lèi)(時(shí)間和幀)的callback類(lèi)型改為public Action callback;。并對(duì)相關(guān)引用(根據(jù)報(bào)錯(cuò))進(jìn)行修改
PETimer
public System.Timers.Timer serverTimer;public PETimer(int interval=0)//interval默認(rèn)時(shí)間間隔{Init();//發(fā)現(xiàn)報(bào)空錯(cuò)誤,是未初始化//taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();//if (interval >= 0){DetachedThread(interval);}}void DetachedThread(int interval){serverTimer = new System.Timers.Timer(interval);//執(zhí)行Update時(shí)間間隔serverTimer.AutoReset = true;serverTimer.Elapsed += (Object sender, ElapsedEventArgs args) =>{Update();};serverTimer.Start();}void Reset()//重啟服務(wù)器{id = 0;taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();log = null;serverTimer.Stop();}Program
static void DetachedThread()//獨(dú)立線(xiàn)程{PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask((int id) => { Console.WriteLine("任務(wù)id:{0},線(xiàn)程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); },100d, //執(zhí)行AddTimeTask的時(shí)間間隔PETimeUnit.MillSecond,0309 增加任務(wù)Handle設(shè)置功能
需求
307的的線(xiàn)程Id是變化的,
1、主體在主線(xiàn)程執(zhí)行,一些文件I/O分流到獨(dú)立線(xiàn)程,然后再回到主線(xiàn)程
2、邏輯部分也是多線(xiàn)程,數(shù)據(jù)修改部分加鎖(性能高,開(kāi)發(fā)麻煩,死鎖)
如果要采用第一種方式。寫(xiě)任務(wù)柄
在Program定義任務(wù)包,實(shí)現(xiàn)任務(wù)柄的入隊(duì)出隊(duì)(加鎖)
PETimer
修改兩次(時(shí)間和幀,以下只顯示時(shí)間的)運(yùn)行
public Action<Action<int> ,int > taskHandle;//回調(diào),idpublic void SetHandle(Action<Action<int>, int> handle){this.taskHandle = handle;}void RunTaskList(){......else{Action<int> callback = task.callback;try{if (taskHandle != null){taskHandle(callback, task.id);}else if (task.callback != null)//我沒(méi)有意識(shí)要檢查非空{task.callback(task.id);}}......Program
private static readonly string obj="lock";static void DetachedThreadBackMainThread()//獨(dú)立線(xiàn)程{Queue<TaskPack> taskPackQuene = new Queue<TaskPack>();PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask((int id) =>{Console.WriteLine("任務(wù)id:{0},線(xiàn)程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());},100d, //執(zhí)行AddTimeTask的時(shí)間間隔PETimeUnit.MillSecond,0);//對(duì)立面存在很多等待執(zhí)行的任務(wù)pt.SetHandle( (Action<int> callback, int id)=>{if (callback != null){lock (obj){taskPackQuene.Enqueue(new TaskPack(id, callback));} }});//執(zhí)行while (true){if (taskPackQuene.Count > 0){TaskPack taskPack;lock (obj){taskPack = taskPackQuene.Dequeue();}taskPack.callback(taskPack.id);}}}} }//任務(wù)包 class TaskPack {public int id;public Action<int> callback;public TaskPack(int id, Action<int> callback ){this.callback = callback;this.id = id;} }效果
310 增加一些常用API(時(shí)間)
PETime
#region ToolByTime///<summary>獲取當(dāng)前時(shí)間<para /></summary>public double GetUTCMillSeconds()//計(jì)算時(shí)間間隔{/**DateTime.Now本地時(shí)間,中國(guó)東八區(qū)DateTime.UtcNow時(shí)間標(biāo)準(zhǔn)時(shí)間實(shí)際服務(wù)器標(biāo)準(zhǔn)時(shí)間,到具體國(guó)家在進(jìn)行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}/// <summary>本地時(shí)間<para /></summary>public double GetMillSecondsTime(){nowTime= GetUTCMillSeconds();return nowTime;}/// <summary>本地時(shí)間<para /></summary> public DateTime GetDateTime(){//方法一、異常卡斷不會(huì)一直運(yùn)行DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds( nowTime));//方法二、DateTime.Now;異常卡斷會(huì)一直運(yùn)行return dateTime;}public int GetYear(){return GetDateTime().Year;}public int GetMonth(){return GetDateTime().Month;}public int GetWeek(){return (int)GetDateTime().DayOfWeek;}public int GetDay(){return GetDateTime().Day;}public string GetLocalTimeString(){DateTime dateTime = GetDateTime();string dateTimeString = GetTimeString(dateTime.Hour)+",";dateTimeString += GetTimeString(dateTime.Minute) + ",";dateTimeString += GetTimeString(dateTime.Second);return dateTimeString;}public string GetTimeString(int time){if (time == 0)return "0" + time.ToString();elsereturn time.ToString();}#endregionProgram
static void TimeTest(){PETimer pt = new PETimer(50);Console.WriteLine(pt.GetUTCMillSeconds().ToString());Console.WriteLine(pt.GetMillSecondsTime().ToString());Console.WriteLine(pt.GetDateTime().ToString());Console.WriteLine(pt.GetYear().ToString());Console.WriteLine(pt.GetMonth().ToString());Console.WriteLine(pt.GetWeek().ToString());Console.WriteLine(pt.GetDay().ToString());Console.WriteLine("\n");}效果
//當(dāng)時(shí)我沒(méi)有定義double nowTime,直接用GetUTCMillSeconds(),第二條發(fā)生溢棧錯(cuò)誤。重新定義了一個(gè)nowTime,沒(méi)報(bào)錯(cuò),如下圖。
//報(bào)溢棧的代碼,但是當(dāng)我定義一次nowTime后再改回去,錯(cuò)誤沒(méi)有出現(xiàn)了
//
311 多線(xiàn)程數(shù)據(jù)安全處理1 鎖Add和加載,鎖id
臨時(shí)列表timeTmpList的添加和清除是多線(xiàn)程的
1、加鎖,性能低
2、鎖在一個(gè)臨時(shí)列表。臨時(shí)列表的Add頻率較低
id的我一開(kāi)始就沒(méi)有設(shè)id列表。視頻是把Add放在生成id的最后面。在回收的時(shí)候鎖id
Time
LoadTaskTmpList()的頻率比AddTimeTask高,
LoadTaskTmpList()通過(guò)if及時(shí)return,規(guī)避鎖
//
Frame的也如上草最
修改后
public int AddTimeTask(Action<int> callback, double delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默認(rèn)毫秒{delay = UnitConversion(delay, unit);////int id = GenerateId();PETimeTask task = new PETimeTask(callback, GetUTCMillSeconds()+delay, count, delay, id);//lock (lockTime){taskTmpList.Add(task);}return id;}/// <summary>加載緩存的臨時(shí)列表<para /></summary>void LoadTaskTmpList(){if (taskTmpList.Count <= 0) return;//一直打印輸出,所以returnlock (lockTime){for (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}}鎖id
312 多線(xiàn)程數(shù)據(jù)安全處理2 鎖時(shí)間Delete
//有困惑的地方是for刪除臨時(shí)列表時(shí),移除時(shí),要不要j–;,解決移除后后面索引的前移
private List<int> taskDeleteTmpList; public void DeleteTimeTask(int id){lock (lockTime){taskDeleteTmpList.Add(id);Log("事件的刪除臨時(shí)列表的線(xiàn)程id:"+Thread.CurrentThread.ManagedThreadId.ToString());}}public void DeleteTimeTask(){if (taskDeleteTmpList.Count > 0){lock (lockTime){for (int j = 0; j < taskDeleteTmpList.Count ; j++){bool isDeleted = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isDeleted = true;taskDeleteTmpList.RemoveAt(j);j--;taskList.RemoveAt(i);break;}}if (isDeleted){continue;}else{for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){taskDeleteTmpList.RemoveAt(j);j--;taskTmpList.RemoveAt(i);break;}}}}taskDeleteTmpList.Clear(); }}}public void Update(){......DeleteTimeTask();......313 多線(xiàn)程數(shù)據(jù)安全處理3 鎖幀Delete 鎖幀Id
跟上面一樣的操作
但也是沒(méi)有視頻的idList
314 多線(xiàn)程數(shù)據(jù)安全處理4 測(cè)試Delete 整合成一個(gè)文件
//input=Console.ReadLine(),所以要按d后快速回車(chē)
Program
static void FinalTest(){Queue<TaskPack> taskPackQuene = new Queue<TaskPack>();PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });int id=pt.AddTimeTask((int id) =>{Console.WriteLine("任務(wù)id:{0},線(xiàn)程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());},1000d, //執(zhí)行AddTimeTask的時(shí)間間隔PETimeUnit.MillSecond,0);//執(zhí)行while (true){ pt.Update();Console.WriteLine("while中的id"+id);string input = Console.ReadLine();if (input == "d"){pt.DeleteTimeTask(id);Console.WriteLine("刪除");}if (taskPackQuene.Count > 0){TaskPack taskPack;lock (obj){taskPack = taskPackQuene.Dequeue();}taskPack.callback(taskPack.id);}}}效果
Frame
相應(yīng)修改
315 課程總結(jié)回顧
總結(jié)
以上是生活随笔為你收集整理的Unity 定时回调系统技术专题(Siki Plane)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用Python获取春节档电影影评,制作
- 下一篇: 2016.02.16我的上司对我说的一席