第八章:行为改良 大部分人都不会遇上这个问题——当你的地图里同时有几百个行为存在,每0.01秒就调用一次它们的onUpdate方法,1秒钟运行数万个触发器,你就会开始体会到什么叫做卡。这就是我们在这一节的主题——怎样在尽可能小的影响游戏演出效果的前提条件下,让游戏中存在尽可能多的onUpdate?
目前我们的onUpdate执行机制非常简单粗暴,就是单纯的每隔0.01秒就执行一次所有GameBehavior的onUpdate方法而已。所以对应的,也有一个很简单粗暴的方法来优化这个问题——不再每隔0.01秒就执行一次所有GameBehavior的onUpdate,改成0.02秒不就好了?假如你的地图里原本可以容纳100个GameBehavior每0.01秒执行一次onUpdate,那么改成0.02秒就可以容纳200个,效果拔群。 当然,你可能会觉得200个还不够,又把0.02改成了0.03,这样就可以容纳300多个GameBehavior了。 如果你观察得仔细,就会发现我在每次计算位移和弹幕的移动的时候,都在速度的后面乘了一个0.01,这就表示我为位移和弹幕设置的速度都是以每秒为单位进行计算的,在onUpdate每隔0.01秒执行一次的情况下,要给速度乘以0.01才能正确让弹幕每秒飞行我所设置的速度。所以当你把onUpdate的执行间隔从0.01改成0.02的时候,如果你不把弹幕和位移onUpdate里的0.01换成0.02,弹幕和位移的速度就都会变慢2倍。诶,真是一件麻烦事。 假如我们要频繁的更改onUpdate的执行间隔的话,这就更麻烦了。所以我们不妨把onUpdate的执行间隔设置成一个变量,将所有的0.01都替换成这个变量,这样的话我们就可以随意调整所有onUpdate的执行间隔了:
在变量管理器中声明一个叫做GameBehavior_s_Period的变量。 然后把GameBehavior_t_TimePeriodic中的事件删除,因为在这里设置的事件并不能很方便的调整其时间间隔。 取而代之的是,我们新建了一个触发器叫做GameBehavior_t_onInit,其事件为地图初始化,动作为为GameBehavior_s_Period赋值,以及为GameBehavior_t_TimePeriodic添加每当游戏经过GameBehavior_s_Period秒的事件:
GameBehavior_t_onInit 事件 地图初始化 条件 动作 设置 GameBehavior_s_Period = 0.02 触发器 - 为 GameBehavior_t_TimePeriodic <预设>添加事件: (时间 - 每当游戏逝去 GameBehavior_s_Period 秒)
然后就是把所有之前用到过0.01秒的地方都修改成GameBehavior_s_Period……因为这个属性只在onUpdate方法中有用,所以我们只要搜索onUpdate就能找到所有可能用到过0.01的触发器了,不算很麻烦。 Ok,这样当你觉得地图因为onUpdate刷新得太频繁而变卡的时候,就可以在GameBehavior_t_onInit中修改GameBehavior_s_Period的初始值,来改变其刷新周期了。
然而这个方法,很蠢。 虽然你的游戏中可能会有300个GameBehavior,将GameBehavior_s_Period设置为0.03刚好可以让你的游戏变得不卡,但是你的游戏会每时每刻都充满了300个行为吗?这应该是不一定的。当然0.03秒和0.01秒差别不大,人的肉眼都很难发现差别,但是如果是0.05或者是0.10呢?差别就体现出来了——你可能会为了游戏中仅仅可能存在几秒钟的性能优化而牺牲整场游戏的视觉效果,这就很不值得了。 所以说让我们来另外想一个更加灵活的方法吧——一个可以根据游戏中的GameBehavior数量来自动调节刷新间隔的方法。
当然,首先你要明白,当你为GameBehavior_t_TimePeriodic添加完了每当游戏过去GameBehavior_s_Period秒的事件之后,无论你怎么修改,GameBehavior_t_TimePeriodic的刷新间隔都不会改变——这个跟War3事件的原理有关,不做详述。所以我们会需要一个新的方法来周期性的运行GameBehavior_t_TimePeriodic——用计时器:
GameBehavior_t_onInit 事件 地图初始化 条件 动作 设置 GameBehavior_s_Period = 0.01 触发器 - 为 GameBehavior_t_TimePeriodic <预设>添加事件: (时间 - GameBehavior_s_CentralTimer 到期) 计时器 - 启动 GameBehavior_s_CentralTimer,应用计时方式: 一次性,计时周期为 GameBehavior_s_Period 秒
GameBehavior_t_TimePeriodic 事件 条件 动作 触发器 - 运行 GobStackPushThis <预设> (检查条件) 触发器 - 运行 GobStackPushCX <预设> (检查条件) 循环动作[GobInt3]从 1 到 Obj_GameBehavior_Num, 运行 (Loop - 动作) Loop- 动作 设置 this = GobInt3 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 Obj_GameBehavior[this] 小于 0 Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Time[this] 小于或等于 0.00 Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onStart[this]不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onStart[this] (检查条件) Else - 动作 Else - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onUpdate[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onUpdate[this] (检查条件) Else - 动作 设置 GameBehavior_Time[this] = (GameBehavior_Time[this] +0.01) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Duration[this] 大于 0.00 GameBehavior_Time[this] 大于或等于 GameBehavior_Duration[this] Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onEnd[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onEnd[this] (检查条件) Else - 动作 触发器 - 运行 GameBehavior_m_Destroy[this] (检查条件) Else - 动作 Else - 动作 触发器 - 运行 GobStackPopCX <预设> (检查条件) 触发器 - 运行 GobStackPopThis <预设> (检查条件) 计时器 - 启动 GameBehavior_s_CentralTimer,应用计时方式: 一次性,计时周期为 GameBehavior_s_Period 秒
这样就可以做到你随时设定GameBehavior_s_Period,GameBehavior_t_TimePeriodic的刷新间隔就会随时改变,并且你的弹幕也不会因为Period的修改而变快或者变慢。 接下来的问题就是Period要怎么修改才好? 有一个很简单的方法——在每次TimePeriodic运行的时候,统计这一次究竟运行了多少次onUpdate,然后乘以一个合适的系数,就能够根据当前有的onUpdate数量来调整TimePeriodic的运行间隔了:
GameBehavior_t_TimePeriodic 事件 条件 动作 设置 GameBehavior_s_Count = 0.00 触发器 - 运行 GobStackPushThis <预设> (检查条件) 触发器 - 运行 GobStackPushCX <预设> (检查条件) 循环动作[GobInt3]从 1 到 Obj_GameBehavior_Num, 运行 (Loop - 动作) Loop- 动作 设置 this = GobInt3 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 Obj_GameBehavior[this] 小于 0 Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Time[this] 小于或等于 0.00 Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onStart[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onStart[this] (检查条件) Else - 动作 Else - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onUpdate[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onUpdate[this] (检查条件) 设置 GameBehavior_s_Count = (GameBehavior_s_Count +GameBehavior_Complexity[this]) Else - 动作 设置 GameBehavior_s_Count = (GameBehavior_s_Count + 0.01) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Duration[this] 大于 0.00 GameBehavior_Time[this] 大于或等于 GameBehavior_Duration[this] Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onEnd[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onEnd[this] (检查条件) Else - 动作 触发器 - 运行 GameBehavior_m_Destroy[this] (检查条件) Else - 动作 Else - 动作 触发器 - 运行 GobStackPopCX <预设> (检查条件) 触发器 - 运行 GobStackPopThis <预设> (检查条件) 设置 GameBehavior_s_Period = (0.01 + ((GameBehavior_s_Count/ 50.00) x 0.01)) 计时器 - 启动 GameBehavior_s_CentralTimer,应用计时方式: 一次性,计时周期为 GameBehavior_s_Period 秒
嗯,你可能会对这个GameBehavior_Complexity是什么东西感到有些疑惑……这个东西的默认值为1,你不妨先把它看成是1,再来看看这个触发器。 啊,并不复杂嘛。在循环所有GameBehavior之前,先将Count重置为0,然后只要有一个GameBehavior调用了onUpdate,就将Count+1,否则的话就只加0.01,最后Period = 0.01+(Count/50)*0.01,意思就是说Period的初始值等于0.01,在这个基础上每增加50个有onUpdate的GameBehavior(100个没有onUpdate的GameBehavior等于1个有onUpdate方法的GameBehavior)就将刷新间隔+1。 那么这样Compelxity就好理解了——Complexity的字面意思是复杂度,代表的是这个GameBehavior的onUpdate方法执行一次相当于执行多少个标准的onUpdate方法。比如说位移的onUpdate方法很简单,就是移动单位而已。而弹幕的onUpdate方法要更复杂一些,包含了位移和选取周围的单位,后者的复杂度要比位移高得多。所以如果我们假设位移的onUpdate方法的复杂度是1的话,弹幕的onUpdate方法的复杂度就可能是2~2.5。更有可能的是,弹幕的onUpdate方法还有可能被一些对象上附加的修饰给扩写了,在修饰类的Create方法里,我们还要增加Complexity的值。比如给弹幕附加了一个AccelBullet,就扩写了onUpdate方法,可能就需要给这个弹幕行为的Complexity+0.5。
这个方法提供的优化效果非常灵活。我在演示地图中以弹幕作为演示,弹幕数量较少时刷新间隔维持在0.01左右,完全感觉不到任何卡顿,而在弹幕数量多的时候,刷新间隔会根据弹幕的数量自动调整,虽然弹幕的移动看上去有一些不连贯,但是不会影响到玩家的操作,并且此时屏幕上的弹幕足有200发,纵使一发弹幕看上去会稍微有些不连贯,在几百发弹幕面前这样的瑕疵也不会显得那么明显了。
通常来讲,使用这样的优化机制就已经足够了。但是在有些情况下你可能会有一些特殊的需求,所以说在下面我再提出一些可以更进一步进行优化的方法。
分批刷新: 对于其他类型的游戏来讲这个现象可能并不明显,但是对于同屏幕几百个弹幕交替出现的游戏来讲,这就很明显了——之前我们延长onUpdate刷新间隔的策略虽然避免了随着GameBehavior增多,在一秒内调用onUpdate方法的次数飙升,但是却没有解决大量的onUpdate方法被集中在同一次TimePeriodic运行中的问题。这将会导致当弹幕增多,TimePeriodic的运行间隔增长到0.x秒的时候,突然所有onUpdate被全部运行产生间歇性卡顿。另外对于同样在这0.x秒钟内被刷出的弹幕而言,尽管它们可能不是在同一时间被刷出,是应该以不同的次序飞行的,但是由于onUpdate每隔0.x秒才全部运行一次,不管是离0.x秒相差0.01秒刷出的弹幕还是相差0.x-0.01秒刷出的弹幕,都会等到这一次onUpdate方法调用的时候才飞出去:
如图所演示,弹幕出现了严重的分层现象——而实际上这是一个随机发射的弹幕,原本不应该有任何层次感。这些弹幕实际上是每隔0.01秒刷3发的速度被刷出来的,但是因为自身的onUpdate方法得不到及时的调用,导致所有在onUpdate刷新间隔中被刷出的弹幕实际上都在同一时间点飞了出去,而不是在不同的时间点上。
解决这个问题的方法就是我们仍然以0.01秒作为TimePeriodic的运行间隔,但是对TimePeriodic在0.01秒内能运行的onUpdate方法数量进行控制,这样既能避免在一秒内TimePeriodic运行太多的onUpdate导致游戏不流畅,也能避免所有onUpdate被集中在一次运行中,导致瞬间顿卡。 但是这个分批的算法会比较复杂……如果你看不懂的话,也不建议花时间去深究,直接把我的演示地图里的触发器复制到你的地图里就行了。具体的做法如下: class GameBehavior { 属性: String Tag(用于区别不同行为的标签)
RealDuration(这个行为持续的时间,小于等于0则代表这个行为持续时间无限大) Real Time(这个行为从创建到现在已经过了多久了,如果超过Duration,则销毁这个行为自身) RealComplexity(复杂度,默认为1,用于优化onUpdate的调用)
static RealPeriod(TimePeriodic的刷新间隔) static RealCount(用于统计一次TimePeriodic运行,运行了多少个onUpdate) static RealLimit(一次TimePeriodic最多能运行多少个onUpdate) static TimerCentralTimer(中央计时器,用于调用TimePeriodic)
staticHashTable BatchList(用于存储每个批次等待的行为列表的哈希表)、 static IntCurrentBatch(当前批次) Int Batch(所在的批次) IntBatchIndex(所在批次列表中的位置) static IntBatchSize(批次最大大小) static IntLastBatch(最后一次循环到的批次) 方法: staticCreate(GobReal1(行为持续时间))->this virtual Destroy()
virtualonStart()//这个虚方法只会被调用一次,会在行为开始计时的时候被调用 virtualonUpdate()//这个虚方法每过0.01秒就会被调用一次 virtualonEnd()//这个虚方法只会被调用一次,会在行为的Time超过Duration的时候被调用
staticCreateBatch()//在等待批次中新建一个批次 staticDestroyBatch(GobInt1(要销毁的等待批次))//使当前等待批次进入备用状态 staticGetNextBatch()->GobInt1(下一个刷新批次) AddToBatch()//将当前GameBehavior加入到当前的等待批次中 RemoveFromBatch()//将当前GameBehavior从其所属Batch中移除 触发器: TimePeriodic(每过Period秒) onInit(地图初始化) }
GameBehavior_s_CreateBatch 事件 条件 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 (在 GameBehavior_s_BatchList 的主索引 GameBehavior_s_CurrentBatch 子索引 0 内提取整数) 小于或等于 0 (在 GameBehavior_s_BatchList 的主索引 0 子索引 GameBehavior_s_CurrentBatch 内提取整数) 小于 0 Then- 动作 -------- 如果目前的批次是激活的,又是空的,就不必新建批次了 -------- 跳过剩余动作 Else- 动作 设置 GameBehavior_s_CurrentBatch = (在 GameBehavior_s_BatchList 的主索引 0 子索引 0 内提取整数) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_CurrentBatch 大于 0 Then- 动作 哈希表 - 在 GameBehavior_s_BatchList 的主索引 0 子索引 0 中保存整数 (在 GameBehavior_s_BatchList 的主索引 0 子索引 GameBehavior_s_CurrentBatch 内提取整数) Else- 动作 哈希表 - 在 GameBehavior_s_BatchList 的主索引 0 子索引 -1 中保存整数 ((在 GameBehavior_s_BatchList 的主索引 0 子索引 -1 内提取整数) + 1) 设置 GameBehavior_s_CurrentBatch = (在 GameBehavior_s_BatchList 的主索引 0 子索引 -1 内提取整数) 哈希表 - 在 GameBehavior_s_BatchList 的主索引 0 子索引 GameBehavior_s_CurrentBatch 中保存整数 -1 游戏 - 显示Debug信息: (|cff00ff00启动批次|r + (转换 GameBehavior_s_CurrentBatch 为字符串))
GameBehavior_s_DestroyBatch 事件 条件 动作 游戏 - 显示Debug信息: (|cffff0000回收批次|r + (转换 GameBehavior_s_CurrentBatch 为字符串)) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GobInt1 大于 0 Then- 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 (在 GameBehavior_s_BatchList 的主索引 0 子索引 GobInt1 内提取整数) 小于 0 Then - 动作 哈希表 - 在 GameBehavior_s_BatchList 的主索引 0 子索引 GobInt1 中保存整数 (在 GameBehavior_s_BatchList 的主索引 0 子索引 0 内提取整数) 哈希表 - 在 GameBehavior_s_BatchList 的主索引 0 子索引 0 中保存整数 GobInt1 哈希表 - 清空 GameBehavior_s_BatchList 中位于主索引 GobInt1 之内的所有数据 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GobInt1 等于 GameBehavior_s_CurrentBatch Then - 动作 触发器 - 运行 GameBehavior_s_CreateBatch <预设> (检查条件) Else - 动作 Else - 动作 Else- 动作
GameBehavior_s_GetNextBatch 事件 条件 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 (在 GameBehavior_s_BatchList 的主索引 0 子索引 -1 内提取整数) 大于 0 Then- 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_LastBatch 大于或等于 (在 GameBehavior_s_BatchList 的主索引 0 子索引 -1 内提取整数) Then - 动作 设置 GameBehavior_s_LastBatch = 0 Else - 动作 设置 GobInt1 = (GameBehavior_s_LastBatch + 1) 设置 GameBehavior_s_LastBatch = GobInt1 Else- 动作 设置 GameBehavior_s_LastBatch = 0 设置 GobInt1 = 0
GameBehavior_AddToBatch 事件 条件 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_CurrentBatch 小于或等于 0 Then- 动作 触发器 - 运行 GameBehavior_s_CreateBatch <预设> (检查条件) Else- 动作 哈希表 - 在 GameBehavior_s_BatchList 的主索引 GameBehavior_s_CurrentBatch 子索引 0 中保存整数 ((在 GameBehavior_s_BatchList 的主索引 GameBehavior_s_CurrentBatch 子索引 0 内提取整数) + 1) 哈希表 - 在 GameBehavior_s_BatchList 的主索引 GameBehavior_s_CurrentBatch 子索引 (在 GameBehavior_s_BatchList 的主索引 GameBehavior_s_CurrentBatch 子索引 0 内提取整数) 中保存整数 this 设置 GameBehavior_Batch[this] = GameBehavior_s_CurrentBatch 设置 GameBehavior_BatchIndex[this] = (在 GameBehavior_s_BatchList 的主索引GameBehavior_s_CurrentBatch 子索引 0 内提取整数) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 (在 GameBehavior_s_BatchList 的主索引 GameBehavior_s_CurrentBatch 子索引 0 内提取整数) 大于或等于 GameBehavior_s_BatchSize Then- 动作 触发器 - 运行 GameBehavior_s_CreateBatch <预设> (检查条件) Else- 动作 游戏 - 显示Debug信息: ((|cffffff00批次|r + (转换 GameBehavior_s_CurrentBatch 为字符串)) + ( |cffffff00添加行为|r +(转换 this 为字符串)))
GameBehavior_RemoveFromBatch 事件 条件 动作 游戏 - 显示Debug信息: ((|cffff00ff批次|r + (转换 GameBehavior_Batch[this] 为字符串)) + ( |cffff00ff移除行为|r +(转换 this 为字符串))) 哈希表 - 在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 GameBehavior_BatchIndex[this] 中保存整数 (在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 (在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 0 内提取整数) 内提取整数) 哈希表 - 在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 (在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 0 内提取整数) 中保存整数 0 哈希表 - 在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 0 中保存整数 ((在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 0 内提取整数) - 1) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 (在 GameBehavior_s_BatchList 的主索引 GameBehavior_Batch[this] 子索引 0 内提取整数) 小于或等于 0 (在 GameBehavior_s_BatchList 的主索引 0 子索引 GameBehavior_Batch[this] 内提取整数) 小于 0 Then- 动作 触发器 - 运行 GobStackPushAX <预设> (检查条件) 设置 GobInt1 = GameBehavior_Batch[this] 触发器 - 运行 GameBehavior_s_DestroyBatch <预设> (检查条件) 触发器 - 运行 GobStackPopAX <预设> (检查条件) Else- 动作 设置 GameBehavior_Batch[this] = 0 设置 GameBehavior_BatchIndex[this] = 0
GameBehavior_s_Create 事件 条件 动作 触发器 - 运行 Obj_GameBehavior_Allocate <预设> (检查条件) 设置 GameBehavior_Duration[this] = GobReal1 设置 GameBehavior_Time[this] = 0.00 设置 GameBehavior_Complexity[this] = 1.00 设置 GameBehavior_Tag[this] = GameBehavior 设置 GameBehavior_m_onStart[this] = DoNothing <预设> 设置 GameBehavior_m_onUpdate[this] = DoNothing <预设> 设置 GameBehavior_m_onEnd[this] = DoNothing <预设> 设置 GameBehavior_m_Destroy[this] = GameBehavior_o_Destroy<预设> 触发器 - 运行 GameBehavior_AddToBatch <预设> (检查条件)
GameBehavior_o_Destroy 事件 条件 动作 触发器 - 运行 GameBehavior_RemoveFromBatch <预设> (检查条件) 设置 GameBehavior_Duration[this] = 0.00 设置 GameBehavior_Time[this] = 0.00 设置 GameBehavior_Complexity[this] = 0.00 设置 GameBehavior_Tag[this] = <空字符串> 设置 GameBehavior_m_onStart[this] = DoNothing <预设> 设置 GameBehavior_m_onUpdate[this] = DoNothing <预设> 设置 GameBehavior_m_onEnd[this] = DoNothing <预设> 设置 GameBehavior_m_Destroy[this] = DoNothing <预设> 触发器 - 运行 Obj_GameBehavior_Deallocate <预设> (检查条件)
GameBehavior_t_onInit 事件 地图初始化 条件 动作 设置 GameBehavior_s_BatchList = (新建哈希表) 设置 GameBehavior_s_Period = 0.01 设置 GameBehavior_s_BatchSize = 50 设置 GameBehavior_s_Limit = 25.00 设置 GameBehavior_s_LastBatch = 1 触发器 - 为 GameBehavior_t_TimePeriodic <预设>添加事件: (时间 - GameBehavior_s_CentralTimer 到期) 计时器 - 启动 GameBehavior_s_CentralTimer,应用计时方式: 一次性,计时周期为 0.01 秒
GameBehavior_t_TimePeriodic 事件 条件 动作 -------- 重置计数器 -------- 设置 GameBehavior_s_Count = 0.00 触发器 - 运行 GobStackPushThis <预设> (检查条件) 触发器 - 运行 GobStackPushReg <预设> (检查条件) -------- 开始循环批次-一次循环的批次数量不超过总批次 -------- 循环动作[GobInt3]从 1 到 (在 GameBehavior_s_BatchList 的主索引 0 子索引 -1 内提取整数), 运行 (Loop - 动作) Loop- 动作 -------- 获取下一个批次到GobInt1 -------- 触发器 - 运行 GameBehavior_s_GetNextBatch <预设> (检查条件) -------- 批次处于激活状态 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 (在 GameBehavior_s_BatchList 的主索引 0 子索引 GobInt1 内提取整数) 小于 0 Then - 动作 -------- 获取当前批次的等待时间并重置 -------- 设置 GameBehavior_s_Period = (在 GameBehavior_s_BatchList 的主索引 GobInt1 子索引 0 内提取实数) 哈希表 - 在 GameBehavior_s_BatchList 的主索引 GobInt1 子索引 0 中保存实数 0.00 -------- 循环同批次创建的GameBehavior -------- 触发器 - 运行 GobStackPushCX <预设> (检查条件) 循环动作[GobInt3]从 1 到 (在 GameBehavior_s_BatchList 的主索引 GobInt1 子索引 0 内提取整数), 运行 (Loop - 动作) Loop - 动作 设置 this = (在 GameBehavior_s_BatchList 的主索引 GobInt1 子索引 GobInt3 内提取整数) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 Obj_GameBehavior[this] 小于 0 Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If- 条件 GameBehavior_Time[this] 小于或等于 0.00 Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If -条件 GameBehavior_m_onStart[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onStart[this] (检查条件) Else - 动作 Else - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onUpdate[this]不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onUpdate[this] (检查条件) 设置 GameBehavior_s_Count = (GameBehavior_s_Count +GameBehavior_Complexity[this]) Else - 动作 设置 GameBehavior_s_Count = (GameBehavior_s_Count + 0.01) 设置 GameBehavior_Time[this] = (GameBehavior_Time[this] +GameBehavior_s_Period) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Duration[this] 大于 0.00 GameBehavior_Time[this] 大于或等于 GameBehavior_Duration[this] Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onEnd[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onEnd[this] (检查条件) Else - 动作 触发器 - 运行 GameBehavior_m_Destroy[this] (检查条件) Else - 动作 Else - 动作 触发器 - 运行 GobStackPopCX <预设> (检查条件) Else - 动作 -------- 每次至少循环一个批次。判断是否达到运行onUpdate方法的次数限制 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_Count 大于或等于 GameBehavior_s_Limit Then - 动作 退出循环 Else - 动作 -------- 累加所有批次的等待时间 -------- 循环动作[GobInt3]从 1 到 (在 GameBehavior_s_BatchList 的主索引 0 子索引 -1 内提取整数), 运行 (Loop - 动作) Loop- 动作 哈希表 - 在 GameBehavior_s_BatchList 的主索引 GobInt3 子索引 0 中保存实数 ((在 GameBehavior_s_BatchList 的主索引 GobInt3 子索引 0 内提取实数) + 0.01) 触发器 - 运行 GobStackPopReg <预设> (检查条件) 触发器 - 运行 GobStackPopThis <预设> (检查条件) -------- 新建一个批次,下一次onUpdate方法调用前创建的行为都将被添加到其中 -------- 触发器 - 运行 GameBehavior_s_CreateBatch <预设> (检查条件) 计时器 - 启动 GameBehavior_s_CentralTimer,应用计时方式: 一次性,计时周期为 0.01 秒
还是稍微说说思路吧:每次运行onUpdate都新建一个批次,在下一次onUpdate之间,所有新建的GameBehavior就都会被添加到这个批次中。如果当前批次已满,就新建新的批次。然后TimePeriodic每次循环的对象就会从GameBehavior换成批次,当然Count计数的规则还是相同的,但是对应的处理措施就不是增加Period了,而是当Count大于等于Limit的时候,循环就会被终止,本次TimePeriodic就结束运行了。结束的时候会记录本次循环终止于哪一个批次,保存到LastBatch,下一次接着LastBatch继续循环。如果循环到了BatchList的尽头,就重头开始继续循环。 实际上这种优化方法是经过两次改良的。上一次改良的思路是为了控制每0.01秒中运行的onUpdate数量,而对GameBehavior进行分批。这种思路相对更为简单,并且也可以避免弹幕分层的数量,但是这种方法会导致一些原本应该在同一批中被刷新的弹幕被分到不同的批次中刷新。比如我一次性创建100个弹幕以360度发射,如果限制每0.01秒只能运行25次onUpdate方法,就会导致这100个弹幕并不能形成完整的环形,而是会参差不齐的飞出去,这就破坏游戏的视觉效果了。所以这一次的改良中每隔0.01秒会新建一个批次,将所有在同一个瞬间被创建的行为添加进去,刷新的时候也是每次至少刷新一个批次,这样就不会因为分批的问题而导致原本应该被同时刷新的行为不再同时被刷新。
呃,但是搞了这么久,实际效果呢……好像并不能看出来很大的差别的样子( 因为一秒能能够流畅的运行的onUpdate的总量是不变的,所以这个方法在一定程度上还是能够起效的,但是当弹幕的数量增大的时候,该发生分层的还是会发生分层……所以弹幕或者行为的优化,最根本的关键还是在于让一秒能够流畅运行的onUpdate数量增多,这又是另外一回事了。
如果你选择直接复制我的触发去用的话,那你大可不必理解我在上面讲得这么一段话,只需要记住这几个要点就行了: 1. Q:原本在同一时间点创建的行为,其onUpdate却没有在同一时间点运行,怎么办? A:检查一下你在同一时间创建的行为数量是不是大于BatchSize,如果是的话就增大GameBehavior_s_BatchSize直到比你在游戏中最多同一时间创建的行为数量还要更大。 2. Q:游戏玩起来还是有点不流畅怎么办? A:你要么选择行为的刷新不流畅,但是游戏和操作本身流畅,要么就是选择两者一起不流畅。如果你选择前者的话,只需要不断的增大GameBehavior_s_Limit的值就ok了。 3. Q:如果分组本身太多了,导致TimePeriodic花费在调度算法上的时间太长了,怎么办? A:尽可能把GameBehavior的创建集中在一个时间点上,比如用每0.1秒创建10个行为来替换每0.01秒创建1一个行为,可以减少批的数量。不过要注意同一时间创建的行为数量最好不要大于GameBehavior_s_BatchSize。
优先树刷新: 有些时候你可能会想要控制行为之间onUpdate的顺序,比如你可能会希望有加血效果的onUpdate优先于有伤害效果的onUpate之前运行,这样单位就会先加血再受到伤害,而不是先受到伤害死亡,之后的血就加不上去了。 那么我们就要解决两个问题:1.怎么给行为加上优先值的设定2.怎么对行为按照优先值进行排序,然后依次运行onUpdate方法。当然这两个问题都不是你解决,而是我已经帮你解决好了,你可以选择把我解决问题的方法搞懂了再拿去用,也可以选择不用搞懂直接拿去用。 class GameBehavior { 属性: String Tag(用于区别不同行为的标签)
Real Duration(这个行为持续的时间,小于等于0则代表这个行为持续时间无限大) Real Time(这个行为从创建到现在已经过了多久了,如果超过Duration,则销毁这个行为自身) Real Complexity(复杂度,默认为1,用于优化onUpdate的调用)
static Real Period(TimePeriodic的刷新间隔) static Real Count(用于统计一次TimePeriodic运行,运行了多少个onUpdate) static Timer CentralTimer(中央计时器,用于调用TimePeriodic)
Real Priority(这个行为的优先级。只读属性,请不要修改它,因为这没有任何作用) GameBehavior Left(左边的子节点,优先级比这个节点更大) GameBehavior Right(右边的子节点,优先级比这个节点更小) GameBehavior Parent(父节点) static GameBehavior Root(根节点) static GameBehavior[] Stack(栈) statuc GameBehavior Current(当前遍历节点) 方法: static Create(GobReal1(行为持续时间),GobReal2(行为的优先级))->this virtual Destroy()
virtual onStart()//这个虚方法只会被调用一次,会在行为开始计时的时候被调用 virtual onUpdate()//这个虚方法每过0.01秒就会被调用一次 virtual onEnd()//这个虚方法只会被调用一次,会在行为的Time超过Duration的时候被调用
static AddChild(GobInt1(父节点),GobInt2(子节点)) static RemoveChild(GobInt1(要删除的节点)) static PrevOrder()->GobIntArray(前序遍历数组) 触发器: TimePeriodic(每过Period秒) onInit(地图初始化) }
GameBehavior_s_AddChild 事件 条件 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GobInt1 大于 0 Obj_GameBehavior[GobInt1] 小于 0 GobInt2 大于 0 Obj_GameBehavior[GobInt2] 小于 0 Then - 动作 Else - 动作 游戏 - 显示Debug信息: (|cffcccccc添加的父节点或子节点不存在?父节点: + ((转换 GobInt1 为字符串) + (,子节点: + ((转换 GobInt2 为字符串) + |r)))) 跳过剩余动作 -------- 判断左右 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Priority[GobInt2] 大于或等于 GameBehavior_Priority[GobInt1] Then - 动作 -------- 左 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Left[GobInt1]大于 0 Then - 动作 -------- 左边已经有子节点了 -------- 触发器 - 运行 GobStackPushAX <预设> (检查条件) 设置 GobInt1 = GameBehavior_Left[GobInt1] 触发器 - 运行GameBehavior_s_AddChild <预设> (检查条件) 触发器 - 运行 GobStackPopAX <预设> (检查条件) Else - 动作 设置 GameBehavior_Left[GobInt1] = GobInt2 设置 GameBehavior_Parent[GobInt2] = GobInt1 Else - 动作 -------- 右 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Right[GobInt1] 大于 0 Then - 动作 -------- 右边已经有子节点了 -------- 触发器 - 运行 GobStackPushAX <预设> (检查条件) 设置 GobInt1 = GameBehavior_Right[GobInt1] 触发器 - 运行 GameBehavior_s_AddChild <预设> (检查条件) 触发器 - 运行 GobStackPopAX <预设> (检查条件) Else - 动作 设置 GameBehavior_Right[GobInt1] = GobInt2 设置 GameBehavior_Parent[GobInt2] = GobInt1
GameBehavior_s_RemoveChild 事件 条件 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GobInt1 大于 0 Obj_GameBehavior[GobInt1] 小于 0 Then - 动作 Else - 动作 跳过剩余动作 -------- 是否有父节点 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Parent[GobInt1] 大于 0 Then - 动作 -------- 有父节点 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Left[GameBehavior_Parent[GobInt1]] 等于 GobInt1 Then - 动作 -------- 在父节点的左边 -------- 设置GameBehavior_Left[GameBehavior_Parent[GobInt1]] = 0 Else - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Right[GameBehavior_Parent[GobInt1]] 等于 GobInt1 Then - 动作 -------- 在父节点的右边 -------- 设置GameBehavior_Right[GameBehavior_Parent[GobInt1]] = 0 Else - 动作 游戏 - 显示Debug信息: (Error:GameBehavior_s_RemoveChild:节点 + ((转换 GobInt1 为字符串) + ( 不在其父节点的任何一侧|r + <空字符串>))) 设置 GameBehavior_Parent[GameBehavior_Left[GobInt1]] = 0 设置 GameBehavior_Parent[GameBehavior_Right[GobInt1]] = 0 -------- 将左右子节点添加给父节点 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Left[GobInt1] 大于 0 Then - 动作 触发器 - 运行 GobStackPushReg <预设> (检查条件) 设置 GobInt2 = GameBehavior_Left[GobInt1] 设置 GobInt1 = GameBehavior_Parent[GobInt1] 触发器 - 运行 GameBehavior_s_AddChild <预设> (检查条件) 触发器 - 运行 GobStackPopReg <预设> (检查条件) Else - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Right[GobInt1] 大于 0 Then - 动作 触发器 - 运行 GobStackPushReg <预设> (检查条件) 设置 GobInt2 = GameBehavior_Right[GobInt1] 设置 GobInt1 = GameBehavior_Parent[GobInt1] 触发器 - 运行 GameBehavior_s_AddChild <预设> (检查条件) 触发器 - 运行 GobStackPopReg <预设> (检查条件) Else - 动作 Else - 动作 -------- 没有父节点 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_Root 等于 GobInt1 Then - 动作 -------- 是根节点 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Left[GobInt1] 大于 0 Then - 动作 -------- 将左节点作为新的根节点 -------- 设置 GameBehavior_s_Root =GameBehavior_Left[GobInt1] 设置GameBehavior_Parent[GameBehavior_Left[GobInt1]] = 0 -------- 将右节点作为左节点的子节点 -------- 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Right[GobInt1]大于 0 Then - 动作 设置GameBehavior_Parent[GameBehavior_Right[GobInt1]] = 0 触发器 - 运行 GobStackPushReg <预设> (检查条件) 设置 GobInt2 = GameBehavior_Right[GobInt1] 设置 GobInt1 = GameBehavior_Left[GobInt1] 触发器 - 运行 GameBehavior_s_AddChild <预设> (检查条件) 触发器 - 运行 GobStackPopReg <预设> (检查条件) Else - 动作 Else - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Right[GobInt1] 大于 0 Then - 动作 -------- 将右节点作为新节点 -------- 设置 GameBehavior_s_Root = GameBehavior_Right[GobInt1] 设置GameBehavior_Parent[GameBehavior_Right[GobInt1]] = 0 -------- 左边没有节点,什么也不做 -------- Else - 动作 -------- 左右节点都没有,这就是一个光棍啊 -------- 设置 GameBehavior_s_Root = 0 Else - 动作 -------- 不是根节点 -------- 游戏 - 显示Debug信息: (Error:GameBehavior_s_RemoveChild:节点 + ((转换 GobInt1 为字符串) + 不是根节点却没有父节点)) 设置 GameBehavior_Left[GobInt1] = 0 设置 GameBehavior_Right[GobInt1] = 0 设置 GameBehavior_Parent[GobInt1] = 0
GameBehavior_s_PrevOrder 事件 条件 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_Root 大于 0 Obj_GameBehavior[GameBehavior_s_Root] 小于 0 Then - 动作 设置 GameBehavior_s_Current = GameBehavior_s_Root 设置 GameBehavior_s_Stack[0] = 0 设置 GobIntArray[0] = 0 Else - 动作 跳过剩余动作 触发器 - 运行 GobStackPushCX <预设> (检查条件) 循环动作[GobInt3]从 1 到 8192, 运行 (Loop - 动作) Loop - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 Or - 任意条件成立 条件 GameBehavior_s_Current大于 0 GameBehavior_s_Stack[0] 大于 0 Then - 动作 循环动作[GobInt3]从 1 到 8192, 运行 (Loop - 动作) Loop - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_Current 大于 0 Then - 动作 -------- 将当前节点入栈 -------- 设置 GameBehavior_s_Stack[0] =(GameBehavior_s_Stack[0] + 1) 设置GameBehavior_s_Stack[GameBehavior_s_Stack[0]] = GameBehavior_s_Current --------设置左节点为当前节点 -------- 设置 GameBehavior_s_Current =GameBehavior_Left[GameBehavior_s_Current] Else - 动作 退出循环 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_Stack[0] 大于 0 Then - 动作 -------- 设置当前节点为栈顶节点 -------- 设置 GameBehavior_s_Current =GameBehavior_s_Stack[GameBehavior_s_Stack[0]] -------- 将当前节点加入数组 -------- 设置 GobIntArray[0] = (GobIntArray[0] + 1) 设置 GobIntArray[GobIntArray[0]] =GameBehavior_s_Current -------- 出栈节点 -------- 设置GameBehavior_s_Stack[GameBehavior_s_Stack[0]] = 0 设置 GameBehavior_s_Stack[0] = (GameBehavior_s_Stack[0]- 1) -------- 设置右节点为当前节点 -------- 设置 GameBehavior_s_Current =GameBehavior_Right[GameBehavior_s_Current] Else - 动作 Else - 动作 退出循环 触发器 - 运行 GobStackPopCX <预设> (检查条件)
GameBehavior_s_Create 事件 条件 动作 触发器 - 运行 Obj_GameBehavior_Allocate <预设> (检查条件) 设置 GameBehavior_Duration[this] = GobReal1 设置 GameBehavior_Time[this] = 0.00 设置 GameBehavior_Complexity[this] = 1.00 设置 GameBehavior_Tag[this] = GameBehavior 设置 GameBehavior_m_onStart[this] = DoNothing<预设> 设置 GameBehavior_m_onUpdate[this] = DoNothing<预设> 设置 GameBehavior_m_onEnd[this] = DoNothing <预设> 设置 GameBehavior_m_Destroy[this] =GameBehavior_o_Destroy <预设> -------- 添加节点到优先树中 -------- 设置 GameBehavior_Left[this] = 0 设置 GameBehavior_Right[this] = 0 设置 GameBehavior_Parent[this] = 0 设置 GameBehavior_Priority[this] = GobReal2 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_s_Root 大于 0 Obj_GameBehavior[GameBehavior_s_Root] 小于 0 Then - 动作 -------- 存在根节点,添加为子节点 -------- 触发器 - 运行 GobStackPushReg <预设> (检查条件) 设置 GobInt1 = GameBehavior_s_Root 设置 GobInt2 = this 触发器 - 运行 GameBehavior_s_AddChild <预设> (检查条件) 触发器 - 运行 GobStackPopReg <预设> (检查条件) Else - 动作 -------- 不存在根节点,将当前节点作为根节点 -------- 设置 GameBehavior_s_Root = this
GameBehavior_o_Destroy 事件 条件 动作 -------- 将节点从优先树中移除 -------- 触发器 - 运行 GobStackPushAX <预设> (检查条件) 设置 GobInt1 = this 触发器 - 运行 GameBehavior_s_RemoveChild <预设> (检查条件) 触发器 - 运行 GobStackPopAX <预设> (检查条件) 设置 GameBehavior_Priority[this] = 0.00 设置 GameBehavior_Duration[this] = 0.00 设置 GameBehavior_Time[this] = 0.00 设置 GameBehavior_Complexity[this] = 0.00 设置 GameBehavior_Tag[this] = <空字符串> 设置 GameBehavior_m_onStart[this] = DoNothing<预设> 设置 GameBehavior_m_onUpdate[this] = DoNothing<预设> 设置 GameBehavior_m_onEnd[this] = DoNothing <预设> 设置 GameBehavior_m_Destroy[this] = DoNothing<预设> 触发器 - 运行 Obj_GameBehavior_Deallocate <预设> (检查条件)
GameBehavior_t_TimePeriodic 事件 条件 动作 设置 GameBehavior_s_Count = 0.00 触发器 - 运行 GobStackPushThis <预设> (检查条件) 触发器 - 运行 GobStackPushCX <预设> (检查条件) -------- 获得按照优先级排序好的GameBehavior -------- 触发器 - 运行 GobStackPushArray <预设> (检查条件) 触发器 - 运行 GameBehavior_s_PrevOrder <预设> (检查条件) 循环动作[GobInt3]从 1 到 GobIntArray[0], 运行 (Loop - 动作) Loop - 动作 设置 this = GobIntArray[GobInt3] 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 Obj_GameBehavior[this] 小于 0 Then - 动作 游戏 - 显示Debug信息: ((运行GameBehavior + (转换 this 为字符串)) + ( ,优先级为 + (转换 GameBehavior_Priority[this] 为字符串))) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Time[this] 小于或等于 0.00 Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onStart[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onStart[this] (检查条件) Else - 动作 Else - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onUpdate[this]不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onUpdate[this] (检查条件) 设置 GameBehavior_s_Count = (GameBehavior_s_Count+ GameBehavior_Complexity[this]) Else - 动作 设置 GameBehavior_s_Count =(GameBehavior_s_Count + 0.01) 设置 GameBehavior_Time[this] =(GameBehavior_Time[this] + GameBehavior_s_Period) 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_Duration[this] 大于 0.00 GameBehavior_Time[this] 大于或等于 GameBehavior_Duration[this] Then - 动作 如果(所有的条件成立) 则运行 (Then - 动作) 否则运行 (Else - 动作) If - 条件 GameBehavior_m_onEnd[this] 不等于 DoNothing <预设> Then - 动作 触发器 - 运行 GameBehavior_m_onEnd[this] (检查条件) Else - 动作 触发器 - 运行 GameBehavior_m_Destroy[this] (检查条件) Else - 动作 Else - 动作 触发器 - 运行 GobStackPopArray <预设> (检查条件) 触发器 - 运行 GobStackPopCX <预设> (检查条件) 触发器 - 运行GobStackPopThis <预设> (检查条件) 设置 GameBehavior_s_Period = (0.01 +((GameBehavior_s_Count / 50.00) x 0.01)) 计时器 - 启动 GameBehavior_s_CentralTimer,应用计时方式: 一次性,计时周期为 GameBehavior_s_Period 秒
呃……但是……运行地图感觉反而变卡了呀!? 恩,这是很正常的。因为这个优化方法提供的是一种优先机制,而不是限制了一秒钟运行的onUpdate方法次数,并不能改善性能。 所以说这个优化方法最好能与之前的分批方法结合,才能同时获得优先机制和性能上的提高。又或是说,这个方法实际上更适合用于处理那些不需要这么频繁的运行的东西。
以上就是两种进一步改良GameBehavior的方法,并且两者侧重于不同点。当然这里实际上还有第三种方法,那就是——有一些行为相比起其他的行为更注重实时性,比如弹幕,这是玩家要看在眼里的东西,所以一旦它们变得迟钝了,就很容易给玩家造成不好的体验。但是有一些东西是玩家看不见的,比如AI,AI的反应时间即使从0.01秒变成0.1秒,对于玩家来说其速度仍然十分迅速。所以按照这种思路我们可以给不同的行为设置权重,让TimePeriodic优先,更多的刷新这些权重更大的行为,而削减那些并不会给我们的游戏造成太大影响的行为的onUpdate运行次数。实际上类似的问题也出现在计算机操作系统中,因此在这方面可以有所参考。不过因为我实在是没时间再做一个第三个方案的演示了(而且相对来说效果也不如前两个那么明显),所以这个问题就交给给位自己探索了。
下一个章节就将是所有教程系列的最终章,以介绍面向对象的各种原则作为结束。实际上对于面向对象的世界来说,这也只是一个开始而已,更多的需要各位自己的探索。
|