HTML5引擎Construct2技术剖析
Posted 伪装狙击手
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HTML5引擎Construct2技术剖析相关的知识,希望对你有一定的参考价值。
接上一节来介绍游戏主循环的实现。
(3) 游戏循环处理
tick函数负责游戏的周期更新,只有一个布尔参数参数background_wake,表示当前游戏是否运行在后台。如果等于true,则此次tick函数调用后将停止回调。tick函数的主要流程为:
1) 根据游戏运行速度和全屏模式,更新相关参数和Canvas的尺寸。
2) 如果是Layout加载界面,则检查加载状态,更新进度;如果加载完成则触发OnLoadFinished事件。
var done = this.areAllTexturesAndSoundsLoaded();
this.loadingprogress = this.progress;
if (done)
this.isloading = false;
this.progress = 1;
this.trigger(cr.system_object.prototype.cnds.OnLoadFinished, null);
3) 调用logic函数处理游戏逻辑。
logic函数的主要流程是:
(a)记录运行耗时,用于统计帧率和CPU利用率等分析;只有2次logic函数调用时间间隔超过1秒时,才进行统计;
var cur_time = cr.performance_now();
if (cur_time - this.last_fps_time >= 1000)
this.last_fps_time += 1000;
this.fps = this.framecount;
this.framecount = 0;
this.cpuutilisation = this.logictime;
this.logictime = 0;
(b)更新游戏逻辑时间kahanTime和墙钟时间wallTime。dt1表示2次tick函数调用的墙钟时间间隔,如果tick时间间隔等于0,则按60FPS来计算;如果累计超过10次tick函数调用的时间间隔均等于0,则直接按60FPS来更新游戏时间(不再进行时间间隔计算)。timescale可以理解为游戏运行速度,与dt1相乘就是游戏的逻辑时间间隔。需要说明的是,有可能游戏处于调试状态,因此如果tick调用的时间间隔大于500毫秒,则认为游戏在调试,则停止更新游戏时间;如果tick调用的时间间隔大于100毫秒,则仍然按100毫秒间隔更新时间(游戏逻辑执行的最大时间间隔是100毫秒, 如果时间间隔大于100毫秒且小于500毫秒,游戏执行会变慢(类似慢镜头))。
var ms_diff = cur_time - this.last_tick_time;
…
this.dt1 = ms_diff / 1000.0;
…
this.dt = this.dt1 * this.timescale;
this.kahanTime.add(this.dt);
this.wallTime.add(this.dt1);
(d) 根据全屏模式和当前状态,调整画面大小。
(e) 调用system_object的runWaits函数处理正在等待的事件逻辑,ClearDeathRow函数的作用是更新对象实例列表,确保新建的实例加入,并回收被删除的实例。isInOnDestroy用来防止对象实例列表被更新,即在runWaits函数运行时,如果调用了ClearDeathRow函数,则什么也不做。
this.ClearDeathRow();
this.isInOnDestroy++;
this.system.runWaits();
this.isInOnDestroy--;
…
runWaits函数的主要流程是:
首先获取当前使用的事件栈,getCurrentEventStack函数就是根据event_stack_index索引返回对应的栈对象。event_stack是栈数组。
var evinfo = this.runtime.getCurrentEventStack();
Runtime.prototype.getCurrentEventStack = function ()
return this.event_stack[this.event_stack_index];
;
然后,遍历waits数组中每个等待状态的事件,判断其等待条件是否满足,如果满足则调用resume_actions_and_subevents继续执行动作,如果包含子事件,则继续处理子事件。如果触发器被成功触发,则设置deleteme为true,标志触发器失效等待回收。
for (i = 0, len = this.waits.length; i < len; i++)
w = this.waits[i];
…
w.ev.resume_actions_and_subevents();
this.runtime.clearSol(w.solModifiers);
w.deleteme = true;
触发器分为2类:一类是信号触发,例如之前看到的OnLoadFinished事件;一类是时间触发,w.time表示时间执行的逻辑时间,如果大于等于kahanTime则被触发。
下面讲一下waits数组中的对象是怎么来的?在system_object的动作函数中有Wait、WaitForSignal等函数。WaitForSignal函数表示等待信号触发(信号通过标签tag字符串来区别),调用allocWaitObject会分配一个等待对象来记录等待信号的属性,并放入 waits数组中等待下一次运行runWaits函数时检测是否被触发。
WaitForSignal函数等待的信号由Signal函数来发出。事件块模式与对象实例是相对独立的关系,可以比喻为算法和数据的关系。一个事件块可以同时运行多次,每次事件块运行时会有自己的sol数据,相互不会干扰。
Wait函数与WaitForSignal函数类似,但不是等待信号触发,而是等待经过多长时间。当时间到达时就被触发。
最后,回收无用的触发器,freeWaitObject函数将触发器重新放入waitobjrecycle数组中等待复用。
for (i = 0, j = 0, len = this.waits.length; i < len; i++)
w = this.waits[i];
…
if (w.deleteme)
freeWaitObject(w);
(f) 调用当前场景的EventSheet的run函数查找到符合条件的 EventBlock,检查条件函数是否成立,并执行相应的动作。
4) 执行完游戏逻辑,实例状态已经更新。这时调用渲染函数,更新游戏画面。如果设置了截屏标志,则会将当前游戏画面保存起来,并触发OnCanvasSnapshot事件,用户可以在事件触发动作中对截屏图片进行处理,例如保存到文件。
if (this.glwrap)
this.drawGL();
else
this.draw();
…
if (this.snapshotCanvas)
this.snapshotData = this.canvas.toDataURL(this.snapshotCanvas[0], this.snapshotCanvas[1]);
this.trigger(cr.system_object.prototype.cnds.OnCanvasSnapshot, null);
drawGL函数的实现如下,调用当前layout的drawGL函数,将渲染命令写入批处理GLBatchJob中,然后调用present函数执行批处理命令更新画面。draw函数则直接调用当前layout的draw函数直接完成绘制。
Runtime.prototype.drawGL = function ()
this.running_layout.drawGL(this.glwrap);
this.glwrap.present();
;
5) 请求下一次tick函数调用,如果不支持 requestAnimationFrame函数,则使用SetTimeout方式来执行渲染(16毫秒对应60FPS, 实际定时器时间很难保证精确)。记录raf_id是为了后面可能要调用cancelAnimationFrame函数取消渲染更新。记录timeout_id是为了后面可能要调用clearTimeout函数取消渲染更新。
if (raf)
this.raf_id = raf(this.tickFunc, this.canvas);
else
this.timeout_id = setTimeout(this.tickFunc, this.isMobile ? 1 : 16);
以上是关于HTML5引擎Construct2技术剖析的主要内容,如果未能解决你的问题,请参考以下文章