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 update
void 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 update
void Start()
{
}
IEnumerator A()
{
print("前");
yield return new WaitForSeconds(2f);
print("后");
}
// Update is called once per frame
void 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 update
void 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 frame
void 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 update
void Start()
{
TimerSys._instance.AddTimeTask();
}
现在封装成方法
//加一个按钮事件
//单例采用封装方法,一次控制顺序,防止单例后运行
public class GameRoot : MonoBehaviour
{
TimerSys timerSys;
// Start is called before the first frame update
void 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 update
void 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 frame
void 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+delay
public 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;//一直打印输出,所以return
for (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 update
void 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 update
void 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);
}
#endregion
private 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年。两个因素所以选1970
public 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;//回调,id
public 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();
else
return 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;//一直打印输出,所以return
for (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;//一直打印输出,所以return
lock (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
相应修改