官网Plane的GitHub
官网视频
官方B站视频
在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。

101 物体的生命周期

//有无勾勾

FixedUpdate

private void FixedUpdate(){public float timer = 0f;public float time = 1f;timer += Time.fixedDeltaTime;

//fixedDeltaTime。下图是1秒1次输出

(问题) NullReferenceException: Object reference not set to an instance of an object

//一直存在,但不影响运行
//unityHub下载不了2020(下载到快完了,全部没了)
//所以从官网下安装包,但又找不到中文包,所以从unityHub下载的2018中文包复制过来
//不知道是不是这原因

102 FixedUpdate

不是固定步长递增

    private void FixedUpdate(){Test02();}void Test02(){print("启动时长:" + Time.realtimeSinceStartup);print("启动时长:" + Time.realtimeSinceStartupAsDouble);print("启动帧数:" + Time.renderedFrameCount);}

最大允许时间步进

驱动刚体运动时,超过最大允许时间步进,就终止此次运算,进行主循环运行,以此保证时间。
所以实际刚体运动会慢点,但忽略不计。

103 脚本执行顺序

需求

//一个物体下两个脚本的执行顺序
//一个物体下同一个脚本的执行顺序

不同脚本的执行顺序

//AB对比同一个物体下运行后,组件的顺序就固定了,后面再调整无用
//AC对比,同一物体下,组建运行顺序从下到上(不同物体也是从下到上)

Unity自带的脚本执行顺序(针对不同脚本)

不同物体下同一个脚本执行顺序

//第一次从下到上
//后面再调整物体顺序,不便了

//作者推荐的,用一个父节点来管理

    public Transform aTrans;public Transform bTrans;// Start is called before the first frame updatevoid Start(){bTrans.GetComponent<NewBehaviourScript103_D_1>().D1();aTrans.GetComponent<NewBehaviourScript103_D_1>().D1();}

(问题) 学习时脚本分类

到网页复制标题
到VS利用Alt键盘修改标题(一般不允许加空格,如下下图是不行的)
MD、bat(另存为ASNI编码,默认的UTF8是会乱码)
“灵者更名”添加空格

//MD 新建文件夹

104 理解Unity主线程设计思想1

//一个线程,主线程,串行运行
//逻辑帧,一个物体从Update到下一次Update的时间

105 理解Unity主线程设计思想2(线程ID)

//不允许在主线程之外访问transform,限制编程环境单线程
//底层运用线程池,不需要开发者管理

    void Start(){ThreadStart threadStart = new ThreadStart(ThreadNew);Thread thread = new Thread(threadStart);thread.Start();print("主线程" + Thread.CurrentThread.ManagedThreadId);}void ThreadNew(){print("新线程" + Thread.CurrentThread.ManagedThreadId);}

106 协程的常规使用1(开启协程的两种方式的区别)

//第一种调用参数上限没有限制
//第一种调用参数上限为1

    void Start(){StartCoroutine(A(1,2));//StartCoroutine("A",1);}IEnumerator A(int a,int b){print("前");yield return new WaitForSeconds(2f);print("后");}

107 协程的常规使用2(终止协程)

问答
//我也测试到StopCoroutine(A()); 对 StartCoroutine(A()); 无效
//视频也讲到StopCoroutine(A()); 对 StartCoroutine(“A”); 无效

    [Tooltip("协程")] Coroutine coroutine;[Tooltip("迭代器")] IEnumerator enumerator;// Start is called before the first frame updatevoid Start(){}IEnumerator A(){   print("前");yield return new WaitForSeconds(2f);print("后");  }// Update is called once per framevoid Update(){StartA();StopA();}void StartA(){if (Input.GetKeyDown(KeyCode.Alpha1)){StartCoroutine(A()); print("开启");}else if (Input.GetKeyDown(KeyCode.Alpha2)){StartCoroutine("A"); print("开启");}else if (Input.GetKeyDown(KeyCode.Alpha3)){coroutine = StartCoroutine(A()); print("开启");}else if (Input.GetKeyDown(KeyCode.Alpha4)){enumerator = A(); print("开启");StartCoroutine(enumerator);}}void StopA(){if (Input.GetKeyUp(KeyCode.Alpha1)){StopCoroutine(A()); print("结束");}else if (Input.GetKeyUp(KeyCode.Alpha2)){StopCoroutine("A"); print("结束");}else if (Input.GetKeyUp(KeyCode.Alpha3)){StopCoroutine(coroutine); print("结束");}else if (Input.GetKeyUp(KeyCode.Alpha4)){StopCoroutine(enumerator); print("结束");}else if (Input.GetKeyDown(KeyCode.Alpha5)){StopAllCoroutines(); print("结束");}}

108 深入理解协程原理1

事件函数的执行顺序
//协程的最大作用是加载资源

失效开启协程的物体

//开启协程后,失效物体,再次激活物体,协程不运行(Unity那张生命周期图,OnDisabled就没协程了)

协程串协程

//单线程的体现

    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");}

协程串协程串协程

//我也蒙这翻译,再串一段协程

//yield像一堵墙,执行顺序如下。方框处是整个协程彻底结束的时候

    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");yield return StartCoroutine(C());print("6");}IEnumerator C(){print("7");yield return new WaitForSeconds(2f);print("8");}

109 深入理解协程原理2(资源加载)

//异步加载Resources文件夹里的某一物体

    ResourceRequest resourceRequest;GameObject go;// Start is called before the first frame updatevoid Start(){StartCoroutine(LoadResourcesAsync());}IEnumerator LoadResourcesAsync(){resourceRequest = Resources.LoadAsync<GameObject>("Cube");//类型,名字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 实现思路分析

//服务器定时任务多
//协程依赖MonoBehavior依赖于Unity,不能在服务器跑Unity。如下图
//借鉴携程是帧驱动的思想实现计时器

201 搭建测试环境

脚本TimerSys(单例),GameRoot

202 初始化脚本顺序

以往单例放在Awake

public class TimerSys : MonoBehaviour
{public static TimerSys _instance;void Awake(){_instance = this;}public void AddTimeTask(){print("定时任务");}
public class GameRoot : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){TimerSys._instance.AddTimeTask();}

现在封装成方法

//加一个按钮事件
//单例采用封装方法,一次控制顺序,防止单例后运行

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();}
public class TimerSys : MonoBehaviour
{public static TimerSys _instance;public void Init(){_instance = this;}public void AddTimeTask(){print("定时任务");}

203 基础定时功能实现

//之前“黑暗之光”时,我用委托做了定时器(用Time.deltaTime的)。有点类似
//方法复制那里如果不是需要加上系统运行时间,参数改为PETimeTask也不错

(问题) NullReferenceException: Object reference not set to an instance of an object

NullReferenceException: Object reference not set to an instance of an object
//空指针
//没有做初始化taskList = new List();

public class TimerSys : MonoBehaviour
{public List<PETimetask> taskList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();}

PETimetask


//任务数据类
using System;public class PETimetask
{public Action callback;//要定时的任务public float destTime;//延时几秒
}

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("添加定时任务");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)//这个判空的直觉我体会不到{task.callback();}              taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引}}}
}

效果

204 增加临时缓存列表

需求

//多线程定时
//增加缓存列表taskTmpList,避免加锁提高效率

代码

public class TimerSys : MonoBehaviour
{[Tooltip("定时任务列表")] public List<PETimetask> taskList;[Tooltip("缓存的定时任务列表")] public List<PETimetask> taskTmpList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();taskTmpList = new List<PETimetask>();     }#region 添加定时任务public void AddTimeTask(Action callback, float delay)//默认毫秒{PETimetask task = new PETimetask();float time = Time.realtimeSinceStartup+ delay;task.callback = callback;task.destTime = time;//taskTmpList.Add(task);}#endregion///// <summary>/// 加载缓存的临时列表<para />/// </summary>void LoadTaskTmpList(){  for (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 执行定时任务<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)//这个判空的直觉我体会不到{task.callback();}taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }}}void Update(){LoadTaskTmpList();RunTaskList();  }}

(需求) 注释方法,全局提示

C# 方法注释,让参数、返回结果可见,并且实现换行显示
//其实这时我需求只需要提示方法是干什么的就行了

205 增加时间单位设置功能

//Time.realtimeSinceStartu的单位是秒,所以毫秒*1000f
//GameRoot的调用相应调整

public class TimerSys : MonoBehaviour
{[Tooltip("定时任务列表")] public List<PETimetask> taskList;[Tooltip("缓存的定时任务列表")] public List<PETimetask> taskTmpList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();taskTmpList = new List<PETimetask>();     }#region 添加定时任务public void AddTimeTask(Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond)//默认毫秒{delay = UnitConversion(delay, unit);//PETimetask task = new PETimetask();float time = Time.realtimeSinceStartup * 1000f+ delay;task.callback = callback;task.destTime = time;//taskTmpList.Add(task);}/// <summary>/// 单位换算成毫秒<para />/// <param name="delay">数值<para /></param>/// <param name="unit">delay的时间单位<para /></param>/// <returns>返回true换算为毫秒的delay</returns>/// </summary>  float UnitConversion(float delay, PETimeUnit unit = PETimeUnit.MillSecond)//{switch (unit){case PETimeUnit.MillSecond:  break;case PETimeUnit.Second: delay = delay * 1000f; break;case PETimeUnit.Minute: delay = delay * 1000f * 60f; break;case PETimeUnit.Hour: delay = delay * 1000f * 60f * 60f; break;case PETimeUnit.Day: delay = delay * 1000f * 60f * 60f * 24f; break;default: { throw new Exception("异常"); }}return delay;}#endregion///// <summary>/// 加载缓存的临时列表<para />/// </summary>void LoadTaskTmpList(){  for (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 执行定时任务<para />/// </summary>  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)//这个判空的直觉我体会不到{task.callback();}taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }}}void Update(){LoadTaskTmpList();RunTaskList();  }
}

206 增加任务循环功能

需求

//delay,执行完一次后,destTime+=delay
//count>1,执行后-1
//count0循环执行
//count1,执行后可以移除该定时任务
//
//采用构造方法
//GameRoot传参调用3次

(代码) PETimetask

public class PETimetask
{public Action callback;//要定时的任务public float destTime;//延时到游戏时间结束public int count;//执行次数public float delay;//延时几秒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)//我没有意识咋{task.callback();}if (task.count == 1){taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }else{if (task.count != 0){task.count--;                   }//定义0==循环task.destTime += task.delay;}......public void AddTimeTask(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);//taskTmpList.Add(task);}

(问题) 可选参数必须出现在所有必要参数之后

//将int count提到前面
//视频是int count=1,也是一个可选参数,所以不用提

207 生成定时任务全局ID

需求分析

//锁里面处理id
//处理超出id(int类型)范围
//int.MaxValue
//锁
//我用taskList[i].id来给id遍历。视频是新建了一个idList专门存储id。(我想尽量减少变量,可能以后有问题,现在找不到问题)
//移除或减少次数,对于idList,都要移除id

(代码) TimerSys

//调用是执行3次

    [Tooltip("定义锁")] private static readonly string obj="lock";[Tooltip("全局id,初始值经过方法后是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)//默认毫秒{......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)//多线程显示唯一id就要锁{id++;while (true){//超出int最大值if (id == int.MaxValue){id = 0;}//是否用过了, 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 增加任务删除功能

//根据id,遍历taskList和taskTmpList,悠久移除,返回true
/;/没采用idList,只用taskList代码简洁了一些

(代码) TimeSys

   /// <summary>/// 删除定时任务<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;}

//调用是3次,下图在第二次进行删除定时任务

209 增加任务替换功能

(需求)

//遍历两个列表进行替换
//原方法是循环输出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);////必在两个表之一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;}

(问题) FuncB执行了两次

taskTmpList.Add(task);使其多运行了一次

 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 清理定时任务全局ID

(需求)

定义一个列表,存储移除的task的id
该列表不为空的,和idList做对比,有的话就移除掉idList里的id
由于我是指直接取taskList[i].id这种形式,没有涉及idList,所以不需要

定义帧的任务数据类和Sys
我是新建一个类FrameTimerSys,原来的命名为TimeTimerSys。视频将这两部分放一起

211 帧定时任务开发1

需求

//使用lambda表达式来简化调用的输出函数
//System.DateTime.Now,系统现在时间
//回调加捕捉异常

lambda

    public void OnAddTimeTaskClick(){id=timerSys.AddTimeTask( ()=> {print("FuncA,id:" + id);print(",时间:"+System.DateTime.Now); },1000f, PETimeUnit.MillSecond,0);//0是循环}

回调加捕捉异常

 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)//我没有意识要检查非空{task.callback();}}catch (Exception e){print(e.ToString());}......

212 帧定时任务开发2;213 测试帧定时任务

需求

//视频用一个每帧递增的frameCounter来代替Time.renderedFrameCount。
//我将frameCounter在没有定时任务时置于0
//新建帧任务数据类
//视频将帧定时的方法跟时间定时,写在一个类,我拆了出来,虽然双方的一些Tool类型的方法一样

PEFrameTask

//帧任务数据类
public class PEFrameTask
{public Action callback;//要定时的任务public int destFrame;//有定时任务时,frameCounter+delaypublic int count;//执行次数public int delay;//延时几帧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("定时任务列表")]  public List<PEFrameTask> taskList;[Tooltip("缓存的定时任务列表")] public List<PEFrameTask> taskTmpList;[Tooltip("定义锁")] private static readonly string obj="lock";[Tooltip("全局id,初始值经过方法后是0,所以-1")] public int id;[Tooltip("有定时任务时的纪元帧")] 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)//默认毫秒{//       int id = GenerateId();PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay,id);//taskTmpList.Add(task);return id;}/// <summary>/// 删除定时任务<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>/// 替换定时任务<para />/// </summary>public bool ReplaceTimerTask(int id,Action callback, int delay, int count = 1){//       PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay, id);////必在两个表之一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>/// 加载缓存的临时列表<para />/// </summary>void LoadTaskTmpList(){if (taskTmpList.Count <= 0) return;//一直打印输出,所以returnfor (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 执行定时任务<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)//我没有意识要检查非空{task.callback();}}catch (Exception e){print(e.ToString());}if (task.count == 1){taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }else{if (task.count != 0){task.count--;//idList.Remove(task.id);}else{//定义0==循环}task.destFrame += task.delay;}}}}void Update(){LoadTaskTmpList();RunTaskList();  }#region Tool/// <summary>/// 生成唯一索引id<para />/// </summary>  int GenerateId(){lock (obj)//多线程显示唯一id就要锁{id++;while (true){//超出int最大值if (id == int.MaxValue){id = 0;}//是否用过了, 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;}#endregion

GameRoot_Frame 调用测试

public class GameRoot_Frame : MonoBehaviour
{FrameTimerSys timerSys;[Tooltip("为了测试删除,替换")] 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 + " " + "帧数:"+Time.renderedFrameCount); },60, 0);//0是循环}public void OnDeleteTimerTaskClick(){timerSys.DeleteTimerTask(id);}public void OnReplaceTimerTaskClick(){bool isReplaced=timerSys.ReplaceTimerTask(id,()=> { print("FuncB"); }, 60,  1);if (isReplaced) { print("替换成功!"); }}}

效果

301 剥离Monobehaviour依赖

需求

//从这开始是服务器上的定时器,官网学员反应难度跟前面两部分对比明显(我也感受到了,没有一个视频敲一些出一个效果的步步前进)
//服务器不能装Unity,所以不能用using UnityEngine。去掉using UnityEngine;后Debug,print,Time,Update等都不能用了。所以要解决这几点。

//GameRoot,UI按钮调用
//TimerSys看,实例并且引用PETime的方法
//PETimer,去除using UnityEngine;和MonoBehaviour,放在服务器上。移植了前两个TimeSys的主体功能。重写update,打印,时间等原来依赖于Unity的部分

增加 删除 更新

GameRoot

public class GameRoot : MonoBehaviour
{TimerSys timerSys;[Tooltip("为了测试删除")] public int id;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<TimerSys>();timerSys.Init();}#region 时间public void OnAddTimeTaskClick(){id = timerSys.AddTimeTask(() =>{print("FuncA,id:" + id);print(",时间:" + System.DateTime.Now);},1000f, PETimeUnit.MillSecond, 0);//0是循环}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(",时间:" + System.DateTime.Now);},60, 0);//0是循环}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 定时任务 增 删 改public int AddTimeTask(Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒{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)//默认毫秒{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 设置日志处理

需求

//实现打印,update(没有继承MonoBehavior,需要TimerSys来驱动), 构造

PETimer的打印

//SetLog被外界调用,传入一个方法引用给自己的委托log
//Log规定自己的委托将会执行带字符串参数的方法体

// PETimer

  private Action<string> log;public void SetLog(Action<String> log){if (log != null){this.log = log;}     }private void Log(string log){this.log(log);}

//TimerSys

    public void Init(){......pt=new PETimer();pt.Init();pt.SetLog((string log)=> { print("初始化"+log); });}

PETimer的Update(靠其他脚本的Update来驱动)

// PETimer

    public void Update(){LoadTaskTmpList();RunTaskList();Frame_LoadTaskTmpList();Frame_RunTaskList();}

//TimerSys

    private void Update(){pt.Update();}

PETimer的构造

//我加上这一段报空

    public PETimer(){taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();}

303 计算机纪年与UTC时区

    //当年出现C语言版本的Unix,32位的int按秒计算是68.1年。两个因素所以选1970public DateTime startDateTime = new DateTime ( 1970, 1, 1, 0, 0, 0,0 );public double GetUTCMillSeconds()//计算时间间隔{/**DateTime.Now本地时间,中国东八区DateTime.UtcNow时间标准时间实际服务器标准时间,到具体国家在进行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}

304 剥离UnityEngine的时间计算依赖

//将destTime和delay的类型改为double
//PETimer中将原本的 Time.realtimeSinceStartup改为GetUTCMillSeconds()
//视频定义了一个全局的nowTime=GetUTCMillSeconds();

PETimer的代码结构

具体的看Plane的github
官网Plane的GitHub PlaneZhong/PETimer

305 移植到控制台工程环境

需求

//VS 新建项目【控制台】添加到原方案
//拖过来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线程计时器

Thread.CurrentThread.ManagedThreadId线程Id

        static void ThreadTest()//那个线程空闲就用哪个线程{double millSeconds = 50d;System.Timers.Timer t = new System.Timers.Timer(millSeconds);//单独Timers有二义性t.AutoReset = true;t.Elapsed += (Object sendeer, ElapsedEventArgs args) =>{Console.WriteLine("线程ID:{0}", Thread.CurrentThread.ManagedThreadId);};t.Start();}

307 整合Timer线程计器

program

        static void DetachedThread()//独立线程{PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask(() => { Console.WriteLine("任务id:{0},线程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); },100d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0);}

PETimer

    public PETimer(int interval=0)//{Init();//发现报空错误,是未初始化//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);//执行Update时间间隔t.AutoReset = true;t.Elapsed += (Object sender, ElapsedEventArgs args) =>{Update();};t.Start();}

效果

308 增加任务回调tid参数

需求

将任务数据类(时间和帧)的callback类型改为public Action callback;。并对相关引用(根据报错)进行修改

PETimer

 public System.Timers.Timer serverTimer;public PETimer(int interval=0)//interval默认时间间隔{Init();//发现报空错误,是未初始化//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);//执行Update时间间隔serverTimer.AutoReset = true;serverTimer.Elapsed += (Object sender, ElapsedEventArgs args) =>{Update();};serverTimer.Start();}void Reset()//重启服务器{id = 0;taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();log = null;serverTimer.Stop();}

Program

        static void DetachedThread()//独立线程{PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask((int id) => { Console.WriteLine("任务id:{0},线程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); },100d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0

309 增加任务Handle设置功能

需求

307的的线程Id是变化的,
1、主体在主线程执行,一些文件I/O分流到独立线程,然后再回到主线程
2、逻辑部分也是多线程,数据修改部分加锁(性能高,开发麻烦,死锁)

如果要采用第一种方式。写任务柄
在Program定义任务包,实现任务柄的入队出队(加锁)

PETimer

修改两次(时间和帧,以下只显示时间的)运行

 public Action<Action<int> ,int > taskHandle;//回调,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)//我没有意识要检查非空{task.callback(task.id);}}......

Program

        private static readonly string obj="lock";static void DetachedThreadBackMainThread()//独立线程{Queue<TaskPack> taskPackQuene = new Queue<TaskPack>();PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask((int id) =>{Console.WriteLine("任务id:{0},线程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());},100d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0);//对立面存在很多等待执行的任务pt.SetHandle( (Action<int> callback, int id)=>{if (callback != null){lock (obj){taskPackQuene.Enqueue(new TaskPack(id, callback));}                  }});//执行while (true){if (taskPackQuene.Count > 0){TaskPack taskPack;lock (obj){taskPack = taskPackQuene.Dequeue();}taskPack.callback(taskPack.id);}}}}
}//任务包
class TaskPack
{public int id;public Action<int> callback;public TaskPack(int id, Action<int> callback ){this.callback = callback;this.id = id;}
}

效果

310 增加一些常用API(时间)

PETime

   #region ToolByTime///<summary>获取当前时间<para /></summary>public double GetUTCMillSeconds()//计算时间间隔{/**DateTime.Now本地时间,中国东八区DateTime.UtcNow时间标准时间实际服务器标准时间,到具体国家在进行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}/// <summary>本地时间<para /></summary>public double GetMillSecondsTime(){nowTime= GetUTCMillSeconds();return nowTime;}/// <summary>本地时间<para /></summary>    public DateTime GetDateTime(){//方法一、异常卡断不会一直运行DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds( nowTime));//方法二、DateTime.Now;异常卡断会一直运行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();}#endregion

Program

        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");}

效果

//当时我没有定义double nowTime,直接用GetUTCMillSeconds(),第二条发生溢栈错误。重新定义了一个nowTime,没报错,如下图。

//报溢栈的代码,但是当我定义一次nowTime后再改回去,错误没有出现了
//

    public double GetUTCMillSeconds()//计算时间间隔{/**DateTime.Now本地时间,中国东八区DateTime.UtcNow时间标准时间实际服务器标准时间,到具体国家在进行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}/// <summary>本地时间<para /></summary>public double GetMillSecondsTime(){return GetUTCMillSeconds();}/// <summary>本地时间<para /></summary>    public DateTime GetDateTime(){//方法一、异常卡断不会一直运行DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds(GetMillSecondsTime()));//方法二、DateTime.Now;异常卡断会一直运行return dateTime;}

311 多线程数据安全处理1 锁Add和加载,锁id

临时列表timeTmpList的添加和清除是多线程的
1、加锁,性能低
2、锁在一个临时列表。临时列表的Add频率较低

id的我一开始就没有设id列表。视频是把Add放在生成id的最后面。在回收的时候锁id

Time

LoadTaskTmpList()的频率比AddTimeTask高,
LoadTaskTmpList()通过if及时return,规避锁
//
Frame的也如上草最

    private static readonly string lockTime = "lockTime";private static readonly string lockFrame = "lockTime";public int AddTimeTask(Action<int> callback, double delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒{delay = UnitConversion(delay, unit);////int id = GenerateId();PETimeTask task = new PETimeTask(callback, GetUTCMillSeconds()+delay, count, delay, id);//taskTmpList.Add(task);return id;}void LoadTaskTmpList(){if (taskTmpList.Count <= 0) return;//一直打印输出,所以returnfor (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}

修改后

    public int AddTimeTask(Action<int> callback, double delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒{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>加载缓存的临时列表<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 多线程数据安全处理2 锁时间Delete

//有困惑的地方是for删除临时列表时,移除时,要不要j–;,解决移除后后面索引的前移

    private List<int> taskDeleteTmpList; public void DeleteTimeTask(int id){lock (lockTime){taskDeleteTmpList.Add(id);Log("事件的删除临时列表的线程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 多线程数据安全处理3 锁帧Delete 锁帧Id

跟上面一样的操作
但也是没有视频的idList

    public void DeleteFrameTask(int id){lock (lockFrame){frame_taskDeleteTmpList.Add(id);Log("事件的删除临时列表的线程id:" + Thread.CurrentThread.ManagedThreadId.ToString());}}private void DeleteFrameTask(){if (frame_taskDeleteTmpList.Count > 0){lock (lockFrame){for (int j = 0; j < frame_taskDeleteTmpList.Count; j++){bool isDeleted = false;for (int i = 0; i < frame_taskList.Count; i++){if (id == frame_taskList[i].id){isDeleted = true;frame_taskDeleteTmpList.RemoveAt(j);frame_taskList.RemoveAt(i);break;}}if (isDeleted){continue;}else{for (int i = 0; i < frame_taskTmpList.Count; i++){if (id == taskTmpList[i].id){frame_taskDeleteTmpList.RemoveAt(j);frame_taskTmpList.RemoveAt(i);break;}}}}frame_taskDeleteTmpList.Clear();}}}

314 多线程数据安全处理4 测试Delete 整合成一个文件

//input=Console.ReadLine(),所以要按d后快速回车

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("任务id:{0},线程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());},1000d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0);//执行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

相应修改

315 课程总结回顾

Unity 定时回调系统技术专题(Siki Plane)相关推荐

  1. 分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)

    分布式定时任务调度系统技术解决方案(xxl-job.Elastic-job.Saturn) 参考文章: (1)分布式定时任务调度系统技术解决方案(xxl-job.Elastic-job.Saturn) ...

  2. 遇见未知的Saturn |准备篇:分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)

    1.业务场景 保险人管系统每月工资结算,平安有150万代理人,如何快速的进行工资结算(数据运算型) 保险短信开门红/电商双十一 1000w+短信发送(短时汇聚型) 工作中业务场景非常多,所涉及到的场景 ...

  3. quarts集群 运维_分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)...

    1.业务场景 保险人管系统每月工资结算,平安有150万代理人,如何快速的进行工资结算(数据运算型) 保险短信开门红/电商双十一 1000w+短信发送(短时汇聚型) 工作中业务场景非常多,所涉及到的场景 ...

  4. 分布式定时任务调度系统技术选型

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:EFbiz blog.csdn.net/guyue35/ar ...

  5. 分布式定时任务调度系统技术选型--转

    http://www.expectfly.com/2017/08/15/%E5%88%86%E5%B8%83%E5%BC%8F%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1% ...

  6. 定时修改列表 服务器版,Unity定时回调(服务端不依赖Update)

    服务器的话就没有Update了 所以我们要吧计时器从Mono和他自带的计时方法剥离出来 管理类只做一些简单的调用 启动Update using System.Collections; using Sy ...

  7. Java 后端开发面试总结:25 个技术专题(最全面试攻略)

    另送福利: java 面试准备 准确的说这里又分为两部分: 1.Java 刷题 2.算法刷题 Java 刷题:此份文档详细记录了千道面试题与详解:  !     私信我回复[03]即可免费获取 很多人 ...

  8. 六大主题报告,四大技术专题,AI开发者大会首日精华内容全回顾

    9月6-7日,2019中国AI开发者大会(AI ProCon 2019) 在北京拉开帷幕.本次大会由新一代人工智能产业技术创新战略联盟(AITISA)指导,鹏城实验室.北京智源人工智能研究院支持,专业 ...

  9. 明日开播 | 7 场不可错过的 AI 技术专题

    如今人工智能已不单单是发表学术论文.刷新正确率的竞赛,抑或全民参与的新闻事件,它早在为各行各业的先行者们创造着实实在在的利润和商业价值.而且,随着算法改进.硬件升级.架构优化,应用人工智能技术带来的收 ...

最新文章

  1. 最全面的homogeneous单应性坐标的定义,以及不同投影,仿射,相似,刚体变换矩阵的关系和自由度分析
  2. bos 获取数据库连接_java解析数据接口获取json对象
  3. 两道JVM面试题,竟让我回忆起了中学时代!
  4. 曹大,欧神开新公众号了
  5. KNN针对中文文本分类
  6. devops 开发_DevOps如何消除开发瓶颈
  7. linux配置内存buffer,Linux中内存buffer和cache的区别
  8. Required String parameter 'images' is not present
  9. 【体系结构】buffer cache的个人理解
  10. Mac下docker安装kali/ubuntu14.04
  11. DNN永日新闻模块(YongRi)免费1.00.09版本下载
  12. Python3 发票导出XML转Excel
  13. 电力系统如何实现时间同步
  14. ae去闪插件deflicker使用_AE去闪烁插件|RevisionFX DEFlicker(AE视频去闪烁插件) V1.4.12 官方版 下载_当下软件园_软件下载...
  15. 网易有道词典去广告版
  16. 64位Ubuntu14.04系统无法解压bin文件的解决方法
  17. 算力网络走向智能社会,云计算初心未改(一)
  18. MacOS下Go语言环境搭建
  19. 汉字转拼音和简拼工具类分享
  20. 自学Java的心路历程

热门文章

  1. 蝉花与冬虫夏草的比较
  2. 全网最强Fiddler抓包实战教程(Android+IOS超级全面图文) 越来越刑
  3. ThinkPHP 路由使用
  4. 关于image-patchs论文中的指标FPR95的一些理解
  5. PyTorch如何获得显卡 Compute Compatibility
  6. 44_Pandas将分类变量转换为虚拟变量(get_dummies)
  7. 兰州市2021高考成绩怎么查询,兰州2021高考成绩排名榜单,兰州各高中高考成绩喜报...
  8. 探索职业:如何用英语询问别人的职业
  9. 如何念好ldquo;开放平台rdquo;这本经?
  10. 【网络爬虫】【java】微博爬虫(一):小试牛刀——网易微博爬虫(自定义关键字爬取微博数据)(附软件源码)...