HTML5引擎Construct2技术剖析(四)
来源:互联网 发布:淘宝网车险 编辑:程序博客网 时间:2024/05/21 14:54
接下来继续介绍引擎的初始化过程–解析游戏数据部分。
解析游戏数据
游戏中使用的所有资源(包括场景界面、精灵、事件逻辑、特效等待全部保存在JSON格式的数据模型中,存在data.js文件中)。requestProjectData函数通过XMLHttpRequest函数读取data.js文件,并将内容转换为JSON对象。
xhr = new XMLHttpRequest();xhr.open("GET", "data.js", true);
在详细介绍游戏数据准备过程前,还需要提到一个函数getObjectRefTable。这个函数返回一个数组,数组中的元素是游戏中使用到的插件构造函数、行为构造函数以及条件、动作、表达式等函数。由于每个游戏可能会使用不同的插件、行为和游戏事件逻辑,因此该函数是在发布时自动生成的,元素的次序也是每个变化的,不保证始终不变。之所以使用getObjectRefTable函数,是为了替代原来直接将函数名写到游戏项目数据的方式(函数名会大量出现),减少游戏数据的长度。而且这个函数仅在解析游戏数据时使用。
cr.getObjectRefTable = function () { return [ cr.plugins_.Keyboard, cr.plugins_.Sprite, …};
在成功读取并解析data.js文件之后,会调用loadProject函数进行初始化工作。
self.loadProject(xhr["response"]);
游戏项目在loadProject 函数中,会将data.js中的数据转换为各种游戏对象,其主要步骤包括:
1) 创建系统对象
system_object,提供了公共的条件、动作和表达式函数,用于游戏逻辑建模。
this.system = new cr.system_object(this);
2)初始化插件对象
var pm = data_response[“project”];
变量pm就是游戏项目数据对象,其详细格式前面已经详细介绍过了,这里不再重复。pm[2]就是插件定义数据,遍历数组创建插件对象。
for (i = 0, len = pm[2].length; i < len; i++) { m = pm[2][i]; p = this.GetObjectReference(m[0]); cr.add_common_aces(m, p.prototype); plugin = new p(this);
m[0]是插件在runtime对象的objectRefTable 数组中的索引。GetObjectReference函数实际上就是根据索引返回相应的插件构造函数。
Runtime.prototype.GetObjectReference = function (i) { return this.objectRefTable[i]; };
add_common_aces 函数的作用是根据插件属性类型标志向插件原型添加条件、动作和表达式函数。插件的属性类型标记分类6类:
— position_aces:表示该插件具有位置属性
— size_aces:表示该插件具有大小属性;
— angle_aces:表示该插件具有角度属性
— appearance_aces:表示该插件具有绘制属性(在屏幕上可见)
— zorder_aces:表示该插件具有深度属性
— effects_aces:表示该插件具有特效属性
每类属性对应一组相关的条件、动作和表达式函数。例如,如果插件具有位置属性,则会向该插件上添加CompareX条件函数(用于比较X坐标值)。
cnds.CompareX = function (cmp, x) { return cr.do_cmp(this.x, cmp, x); };
最后,创建出一个插件对象并放入插件数组中。这里需要解析一下引擎中的插件Plugin、和后面出现的对象类型ObjectType和实例Instance之间的关系。利用C++语言概念来解释,Plugin是一个模板类,例如精灵类Sprite<>,ObjectType就是模板类的特例化,例如敌人精灵类EnemySprite, 而Instance才是类的对象实例,例如EnemySprite1、EnemySprite2。
plugin = new p(this);this.plugins.push(plugin);
3)初始化对象类型
插件对象初始化完毕,接下来初始化对象类型ObjectType。pm[3]就是对象类型定义数据。ObjectType对象的初始化工作比较多,需要初始化对象类型中包含的参数、特效、行为以及Family等各种数据。
for (i = 0, len = pm[3].length; i < len; i++) { m = pm[3][i];plugin_ctor = this.GetObjectReference(m[1]); plugin = null;
m[1]是对象类型的使用的插件对象索引,根据索引找到前面已经创建好的插件对象plugin。
for (j = 0, lenj = this.plugins.length; j < lenj; j++) { if (this.plugins[j] instanceof plugin_ctor) { plugin = this.plugins[j]; break; } 然后使用找到的插件对象,创建插件对象实例(就是对象类型) var type_inst = new plugin.Type(plugin);
插件对象的Type函数的实现如下。Type函数可以实现onCreate接口函数,完成自定义的初始化工作。例如精灵插件Sprite在onCreate接口函数完成动画帧的初始化工作。
var pluginProto = cr.plugins_.XXX.prototype; pluginProto.Type = function(plugin) { this.plugin = plugin; this.runtime = plugin.runtime; };var typeProto = pluginProto.Type.prototypetypeProto.onCreate = function(){}
接下来,为插件对象实例的属性进行初始化赋值;如果对象包含纹理文件,则初始化纹理参数;如果对象包含动画数据,则简单赋值不做任何处理,在后面调用OnCreate函数在进行处理。
if (m[6]) { type_inst.texture_file = m[6][0]; type_inst.texture_filesize = m[6][1]; type_inst.texture_pixelformat = m[6][2]; }if (m[7]) { type_inst.animations = m[7]; }
接下来继续进行对象类型的行为对象初始化,首先从GetObjectReference获取行为插件的构造函数,然后从runtime的behaviors数组中查找行为插件是否已经创建,如果没有则新建行为插件对象,把行为插件和使用该行为的对象类型进行双向关联。行为插件的my_types数组记录的是使用该行为的对象类型。
behavior_plugin = new behavior_ctor(this);behavior_plugin.my_types = []; behavior_plugin.my_instances = new cr.ObjectSet();… this.behaviors.push(behavior_plugin); … behavior_plugin.my_types.push(type_inst);
行为插件构造好之后,就可以创建行为插值实例(或者称为行为类型)(与前面提到的插件-对象类型的概念类型),并将其放入对象类型的behaviors数组中。
var behavior_type = new behavior_plugin.Type(behavior_plugin, type_inst); … type_inst.behaviors.push(behavior_type);
初始化完行为对象,继续进行特效初始化。特效初始化非常简单,直接使用特效数据构造一个对象,放入effect_types数组中即可。Shaderindex表示特效的索引,暂时赋值为-1,在后面调用initRendererAndLoader函数中使用glwrap.getShaderIndex根据特效名找到对应的shader程序的索引。
for (j = 0, lenj = m[12].length; j < lenj; j++) { type_inst.effect_types.push({ id: m[12][j][0], name: m[12][j][1], shaderindex: -1, active: true, index: j }); }
如果对象类型的所属插件的singleglobal属性为真,表示该插件是单实例(只能创建一个唯一实例),只能在初始化时创建实例,则游戏中不能创建实例。因此这里通过Instance函数创建一个插件对象实例,并加入对象类型的instances数组中,并在runtime中建立uid字符串的索引。
if (plugin.singleglobal) { var instance = new plugin.Instance(type_inst); instance.uid = this.next_uid++; instance.puid = this.next_puid++; … type_inst.instances.push(instance); this.objectsByUid[instance.uid.toString()] = instance; }}
Instance函数的实现如下。Instance函数也可以实现onCreate接口函数,完成自定义的初始化工作。
pluginProto.Instance = function(type) { this.type = type; this.runtime = type.runtime; };var instanceProto = pluginProto.Instance.prototype;instanceProto.onCreate = function(){}
4)初始化Family集合对象
在游戏中,若干个对象类型可以组成一个Family对象(是一个特殊ObjectType对象),对象类型必须都来自同一个插件。可以给这个Family对象定义特效、参数、行为等数据,当创建对象实例Instance时,对象实例除了具有自身类型所有属性外,还会继承所在Family中的所有属性。Family不支持嵌套,即一个Family属于另一个Family。
Family还有一个好处是,在进行游戏逻辑建模时,如果需要给多类对象类型添加事件触发,则只需添加给Family就行了,而不需要给每个对象类型添加相同的事件触发。一个对象类型可以同时加入多个Family。
for (i = 0, len = pm[4].length; i < len; i++) { var familydata = pm[4][i]; var familytype = this.types_by_index[familydata[0]]; var familymember; 在Family和其包含的对象类型之间建立双向关联,Family的members数组记录了包含的对象类型,而对象类型的families数组则记录了其所属的Family。 for (j = 1, lenj = familydata.length; j < lenj; j++) { familymember = this.types_by_index[familydata[j]]; familymember.families.push(familytype); familytype.members.push(familymember); } }
到目前为止,对象类型和Family都已经完成初始化,接下来将Family中的特效、参数、行为等属性添加到对象类型中。对象类型的family_var_map数组的长度与Family数目相同,记录的是对应索引的Family的变量个数(对象类型属于该Family);family_beh_map、family_fx_map类似分别记录行为个数和特效个数。然后将所有Family的特效加上对象类型原有的特效合并到一个数组中,并放到effect_types数组中。
t.family_var_map = new Array(this.family_count); t.family_beh_map = new Array(this.family_count); t.family_fx_map = new Array(this.family_count); … t.effect_types = all_fx.concat(t.effect_types);
5)初始化容器对象
在游戏中,容器对象用于设计组合对象,例如一个坦克精灵由底盘和炮塔组成(有点类似骨骼动画)。容器中的对象类型可以不是来自同一个插件。容器对象有以下几个特点:
(a)一个对象类型仅能属于一个容器;
(b)容器中任何一个对象类型的实例被创建,容器中的其他对象类型的实例自动被创建;
(c)容器中任何一个对象类型的实例被删除,容器中的其他对象类型的实例自动被删除;
(d)如果容器中任何一个对象类型的实例被事件条件触发,容器中的其他对象类型的实例也会被触发。
注意:在编辑器中,有可能只创建了一个容器中的部分对象类型实例,例如只创建了坦克的底盘,没有炮塔。在游戏运行时,会自动将炮塔创建出来。
可以向容器中加入Array、Dictionary等数据类型,类似于给容器中的对象实例增加了一个动态数据容器,可以记录额外的属性数据。
容器中的每个对象类型只能创建一个实例,假如坦克上有2个炮塔,则需要创建炮塔A和炮塔B两个对象类型;而无法直接创建炮塔的2个实例。
for (i = 0, len = pm[27].length; i < len; i++) { var containerdata = pm[27][i]; var containertypes = []; for (j = 0, lenj = containerdata.length; j < lenj; j++) containertypes.push(this.types_by_index[containerdata[j]]); for (j = 0, lenj = containertypes.length; j < lenj; j++) { containertypes[j].is_contained = true; containertypes[j].container = containertypes; } }
6)初始化界面布局对象
在游戏中,每个游戏场景对应一个Layout对象,其中包含多个Layer图层对象,所有的实例对象Instance必须属于一个Layer对象。
for (i = 0, len = pm[5].length; i < len; i++) { m = pm[5][i]; var layout = new cr.layout(this, m); … }
Layout对象的构造函数中,初始化其中的图层layer对象,将其放入layers数组中。layers数组中高索引的图层先画(位于最底层)。
“` python
for (i = 0, len = lm.length; i < len; i++) { var layer = new cr.layer(this, lm[i]); layer.number = i; … this.layers.push(layer); }
Layer对象的构造函数中,构建本图层初始的实例对象(在界面开始运行时的出现的实例),保存到initial_instances数组中。如果实例的对象类型没有缺省实例数据的话,就把当前实例(即第一个创建的实例)数据作为缺省数据。另外,把实例的对象类型放入到initial_types数组中。this.initial_instances = []; for (i = 0, len = im.length; i < len; i++) { var inst = im[i]; var type = this.runtime.types_by_index[inst[1]]; if (!type.default_instance) { type.default_instance = inst; type.default_layerindex = this.index; } this.initial_instances.push(inst); if (this.layout.initial_types.indexOf(type) === -1) this.layout.initial_types.push(type);
此外,还构建本图层使用的特效对象放入effect_types数组中;把特效使用的参数变量放入effect_params数组中;有些特效在界面开始运行时就激活,updateActiveEffects函数会把所有激活的特效找出来并放入active_effect_types数组中。
this.effect_types = []; this.active_effect_types = []; this.effect_params = []; for (i = 0, len = m[14].length; i < len; i++) { this.effect_types.push({ id: m[14][i][0], name: m[14][i][1], shaderindex: -1, active: true, index: I }); this.effect_params.push(m[14][i][2].slice(0)); } this.updateActiveEffects();
7)初始化游戏逻辑
游戏逻辑采用EventSheet对象来实现,每个Layout对象可以对应一个EventSheet对象, EventSheet对象必须在Layout运行时才能执行(即当游戏进入到一个场景时,对应的Layout开始运行(绘制),相应的EventSheet这个时候才能执行)。
for (i = 0, len = pm[6].length; i < len; i++) { m = pm[6][i]; var sheet = new cr.eventsheet(this, m); …this.eventsheets_by_index.push(sheet); }
EventSheet对象创建完成后,调用每个对象的 postInit函数进行初始化收尾工作。
for (i = 0, len = this.eventsheets_by_index.length; i < len; i++) this.eventsheets_by_index[i].postInit();
postInit函数的工作是找出所有Else事件块的上一个事件,调用其postInit函数进行初始化收尾工作。this.events[i]数组中的元素是EventBlock,其postInit函数的工作稍微多一些:
EventSheet.prototype.postInit = function () { var i, len; for (i = 0, len = this.events.length; i < len; i++) { this.events[i].postInit(i < len - 1 && this.events[i + 1].is_else_block); } };
接下来,调用每个EventSheet对象的 updateDeepIncludes函数处理EventSheet包含关系。这里解释一个包含关系,为了减少游戏逻辑建模和修改工作量,EventSheet对象可以包含其他EventSheet对象,也支持包含的嵌套(多层包含)。这样的好处就是,可以把重复使用的游戏逻辑块保存为一个EventSheet,然后在使用的地方包含进去即可,修改维护也很方便。
EventSheet对象不能包含自己,但是可能会出现A包含B,B又包含A的情况。在这种情况下,A和B都只会包含对方一次,不再循环包含。
for (i = 0, len = this.eventsheets_by_index.length; i < len; i++)
再接下来,调用每个触发器Trigger对象的postInit函数进行初始化收尾工作。
for (i = 0, len = this.triggers_to_postinit.length; i < len; i++) this.triggers_to_postinit[i].postInit();
8)调用initRendererAndLoader函数,进行渲染和资源加载的初始化工作。
initRendererAndLoader函数的主要流程包括:
(a) Canvas初始化,如果支持WebGL,则创建GLWarp对象(对WebGL接口的高层封装)。特效只有在WebGL情况下才有效,因此如果支持WebGL,则遍历所有Layout对象,对其中使用的特效进行初始化准备(特效采用Shader实现)。
(b) 绑定事件处理,例如指针事件、触摸事件、失去焦点事件等。
(c) 准备音频资源列表,调用go函数启动资源加载过程。
- HTML5引擎Construct2技术剖析(四)
- HTML5引擎Construct2技术剖析(一)
- HTML5引擎Construct2技术剖析(二)
- HTML5引擎Construct2技术剖析(三)
- HTML5引擎Construct2技术剖析(五)
- HTML5引擎Construct2技术剖析(六)
- HTML5引擎Construct2技术剖析(七)
- 游戏引擎剖析(四)
- 游戏引擎剖析(四)
- 游戏引擎剖析(四)
- 游戏引擎剖析(四)
- 游戏引擎剖析(四)
- 【转】游戏引擎剖析(四)
- 游戏引擎全剖析(四)
- construct2游戏引擎介绍
- 使用Construct2制作HTML5游戏
- 如何使用construct2制作 HTML5
- 3D游戏引擎技术剖析
- Tomcat的四种项目部署方式
- jquery 单行滚动、批量多行滚动、文字图片翻屏滚动效果代码
- Android APK反编译就这么简单 详解
- 步步为营(五)贪心(4)部分背包问题
- error: linker command failed with exit code 1 (use -v to see invocation)
- HTML5引擎Construct2技术剖析(四)
- C语言,快速排序算法
- androidStudio自动黑盒测试
- ArcEngine开发_导出地图
- jQuery Pagination Plugin
- GitHub上最火的40个Android开源项目(一)
- 配置Snappy压缩
- MySQL的InnoDB和MyISAM区别
- ScrollView