2.1.1. 应用协程合成简单逻辑
协程解决异步工作:当遇到一些须要异步解决的程序需要时,能够应用协程来实现
应用协程的长处:简略,易于实现
实例:应用协程代替无限状态机 (基于原书的代码做了些许批改以及减少了正文)
//一个示意村民的类
public class Villager : MonoBehaviour{
public float maxSatiation = 10f; //最大饱食度public float maxFatigue = 10f; //最大困倦值const float minSatiation = 0.2f; //最小饱食度const float minFatigue = 0.2f; //最小困倦值private float satiation; //以后饱食度private float fatigue; //以后困倦值Coroutine currentCoroutine; //以后的状态(协程)//OnEnable在脚本被激活时立刻执行void OnEnable(){ satiation = maxSatiation; //初始化饱食度,设为最大值 fatigue = maxFatigue; //初始化困倦值,设为最大值 StartCoroutine(Tick()); //开始“游戏循环”的协程 }//模仿“[游戏]循环”,相似MonoBehaviour的Update办法IEnumerator Tick(){ //每帧进行一次循环 while(true) { DecrePerFrame(satiation); //缩小饱食度 DecrePerFrame(fatigue); //缩小困倦值 //如果饿死了且以后的状态为空,开始“吃”协程,并定“吃”为以后状态 if(satiation < minSatiation && currentCoroutine == null) { currentCoroutine = StartCoroutine(Eat()); } //如果困死了,不论当初在干啥,间接开始“睡”协程,并定“睡”为以后状态 if(fatigue < minFatigue) { currentCoroutine = StartCoroutine(Sleep()); } //进展一帧 yield return null; }}IEnumerator Eat(){ //每帧吃一点,吃到饱为止 while(satiation < maxSatiation) { IncrePerFrame(satiation); yield return null; } //吃饱了,以后状态改回空 currentCoroutine = null;}IEnumerator Sleep(){ //立刻进行以后正在干的事(例如"吃") StopCoroutine(currentCoroutine); //如果没睡够,持续睡 while(fatigue < maxFatigue) { IncrePerFrame(fatigue); yield return null; } //睡够了,以后状态改为空 currentCoroutine = null;
2.1.2. 自定义的插值公式
什么是插值(interpolation):在两个值/地位之间定义一个新的值/地位
插值通用公式:P01 = (1 - u) P0 + u P1;
u是什么:在线性内插中,u为一个0-1之间的浮点数,用于决定咱们取得的插值更凑近P0还是P1. (线性外插的u是<0或者>1的,在[游戏]中并不罕用)
罕用的插值公式
线性插值:u = u
缓进:u = u * u
缓出:u = 1 - (1 - u) * (1 - u)
缓进出:u = ((u - 1) (u - 1) (u - 1) + 1) ((u - 1) (u - 1) * (u - 1) + 1)
Sin波长:u = u + range(0, 1) sin(u 2 * PI)
2.1.3. 音讯模块的设计
音讯/事件治理:游戏中往往有大量相互连贯的游戏元素,而他们非常须要音讯零碎的反对。
音讯/事件的作用:在一个事件产生时,与之相干的后果被一并触发(例如,在www.cungun.com击杀一个敌人,咱们的成就零碎要记录咱们多击杀了一个敌人,这就是音讯/事件的用武之地)
音讯模块的缓存:通常状况下,一个事件被触发后,会立刻告诉所有订阅的监听者,但这并不适用于所有的状况(例如玩家取得新武器,背包内的新武器会高光显示,但此时玩家还没关上背包,而等到关上时事件却早就告诉过监听者了)
实例:音讯模块的繁难实现 (基于原书的代码简化成伪代码)
public class MessageManager{
Dictionary<string, Action<object[]>> messageDict; //存储音讯以及相关联的监听者的字典Dictionary<string object[]> dispatchCacheDict; //缓存区public void Subscribe(string messageKey, Action<object[]> action){ if(messageKey in messageDict.Keys) { //Set action as a new subscriber of messageDict[messageKey]; //如果曾经存在这个音讯,那么给他加一个订阅者(监听者) } else { //Add new messageKey and new action to messageDict //否则,退出新的音讯 }}public void Unsubscribe(string message){ messageDict.Remove(message);}public void Dispatch(string message, object[] args = null, bool addToCache = false){ if(addToCache) { //add message and args into cache //如果抉择退出缓存,就将传入的所有事件退出缓存 } else { //trigger all the co-related actions in this message //否则,触发所有与该信息关联的所有 }}public void ProcessDispatchCache(string message){ //如果message存在于缓存中 if(message in dispatchCacheDict.Keys) { //执行缓存中与该信息关联的所有事件,随后从缓存中移除message Dispatch(message, dispatchCacheDict[message]); dispatchCacheDicr.Remove(message); }}}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.
2.1.4. 模块间的治理与协调
单例模式的治理
避免销毁后的调用:在单例中退出判断,若已被销毁,则防止调用
避免单例被反复创立:因为单例个别被标记为DontDestroyOnLoad,在场景切换时,单例会被再次创立,需退出判断防止反复创立单例
脚本执行优先级:在ProjectSetting里,寻找Script Execution Order,在其中对脚本优先级进行设置,能无效防止NullReferenceException(例如A脚本在Awake时想要获取B脚本的单例,但B的优先级在A后,那么A就不能胜利获取到B,所以须要设置B的优先级高于A)