这一节我们来实现一个简单的时间系统
ITimeSystem 时间系统肯定是在系统层的,它需要游戏一开始初始化时就能统计时间信息。
在Scripts——System文件夹下新建TimeSystem文件夹,并在其中新建ITimeSystem
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 using System;using System.Collections.Generic;using UnityEngine;using FrameWorkDesign;namespace ShootingEditor2D { interface ITimeSystem : ISystem { float CurrentSeconds { get ; } void AddTaskDelay (float seconds, Action onDelayFinish ) ; } public class TimeSystem : AbstractSystem , ITimeSystem { public float CurrentSeconds { get ; private set ; } private LinkedList<DelayTask> mDelayTasks = new LinkedList<DelayTask>(); public void AddTaskDelay (float seconds, Action onDelayFinish ) { var delayTask = new DelayTask() { Seconds = seconds, OnFinish = onDelayFinish, State = DelayTaskState.NonStart }; mDelayTasks.AddLast(delayTask); } protected override void OnInit () { CurrentSeconds = 0 ; var timeSystemGameObj = new GameObject(nameof (TimeSystemUpdateBehaviour)); var updateBehaviour = timeSystemGameObj.AddComponent<TimeSystemUpdateBehaviour>(); updateBehaviour.OnUpdate += OnUpdate; } public class TimeSystemUpdateBehaviour : MonoBehaviour { public event Action OnUpdate; private void Update () { OnUpdate?.Invoke(); } } void OnUpdate () { CurrentSeconds += Time.deltaTime; if (mDelayTasks.Count > 0 ) { var currentNode = mDelayTasks.First; while (currentNode != null ) { var nextNode = currentNode.Next; var delayTask = currentNode.Value; if (delayTask.State == DelayTaskState.NonStart) { delayTask.State = DelayTaskState.Started; delayTask.StartSeconds = CurrentSeconds; delayTask.FinishSeconds = CurrentSeconds + delayTask.Seconds; } else if (delayTask.State == DelayTaskState.Started) { if (CurrentSeconds >= delayTask.FinishSeconds) { delayTask.State = DelayTaskState.Finish; delayTask.OnFinish.Invoke(); delayTask.OnFinish = null ; mDelayTasks.Remove(currentNode); } } currentNode = nextNode; } } } } public class DelayTask { public float Seconds { get ; set ; } public Action OnFinish { get ; set ; } public float StartSeconds { get ; set ; } public float FinishSeconds { get ; set ; } public DelayTaskState State { get ; set ; } } public enum DelayTaskState { NonStart, Started, Finish } }
当前时间系统的设计图如下
注意:
此时间系统初始化后一直存在,代码内并没有注销方式
时间系统需要依赖Unity的Update,所以添加了TimeSystemUpdateBehaviour
内部类,并在OnInit
的时候挂载了这个类
在TimeSystemUpdateBehaviour
中可以看到,Unity每一帧都发布OnUpdate这个事件,这样TimeSystem
内部的OnUpdate
方法就能及时刷新,TimeSystemUpdateBehaviour
其实算是个表现层,所以采用事件发布的这种形式来与TimeSystem
分离,注意这种思路
每调用一次AddTaskDelay
方法,就会new一个DelayTask
,我们用链表纪录这些DelayTask
并用while遍历链表,选择链表是因为我们不确定DelayTask
的多少,代码使用while遍历链表的方式比foreach效率要高,要注意这种方式
一定要时刻记住引用该置空的时候就要置空,当一个DelayTask
结束时,这个类就没有用了,它内部的引用(OnFinish
)就要置空
然后在ShootingEditor2D
脚本中注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using FrameWorkDesign;namespace ShootingEditor2D { public class ShootingEditor2D : Architecture <ShootingEditor2D > { protected override void Init () { this .RegisterSystem<IStatSystem>(new StatSystem()); this .RegisterSystem<IGunSystem>(new GunSystem()); this .RegisterSystem<ITimeSystem>(new TimeSystem()); this .RegisterModel<IPlayerModel>(new PlayerModel()); } } }
测试 每当我们要做一个新的系统,肯定需要场景来反复测试。
在Assets文件夹下新建Tests文件夹,并在此文件夹内新建TimeSystem文件夹,在其中新建脚本TimeSystemTest
和场景TimeSystemTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using UnityEngine;namespace ShootingEditor2D.Tests { public class TimeSystemTest : MonoBehaviour { void Start () { Debug.Log(Time.time); ShootingEditor2D.Interface.GetSystem<ITimeSystem>() .AddTaskDelay(3 , () => { Debug.Log(Time.time); }); } } }
将此脚本挂载在场景任意对象上,开始游戏3秒后就会有Debug弹出。
这种写一个脚本挂到一个独立场景下试运行,是最简单的单元测试 的方法,我们可以测试更复杂的TimeSystem功能,比如游戏暂停时,游戏继续时。当游戏项目特别复杂的时候,写一些单元测试来保证某个系统正常运转是非常重要的。
Test Runner Unity内部已经提供了单元测试的框架,能够集成并记录所有的测试项目,再开发的后期能够多次进行测试,更加规范。
About Unity Test Framework | Test Framework | 1.1.33 (unity3d.com)
记住写单元测试的目的,是为了保证系统正常运转,后期运行已经积累的单元测试可以节省很多人力测试的工作量,除了单元测试还有集成测试,人力(黑盒)测试,但并不是要所有地方都要按照单元测试的方式写代码,不过单元测试的确可以提高设计能力。