//一个表示村民的类 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; }}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.
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)
原文:https://blog.51cto.com/u_14967986/2839555