本帖最后由 aasll 于 2016-12-3 14:57 编辑
第五章 js对象实力装逼
第一节 任务系统
话说rpg游戏里面很重要的一个系统就是任务系统。但是在RPG Maker MV当中,并没有现成的任务系统来给我们使用。
虽然我们可以将事件,变量和开关联合使用来达到“任务”的效果,但是这终究是不太方便。所以,我们就来做一个任务系统,将上面的功能封装起来,方便我们的使用,毕竟有一个显式的“任务”比一堆不明所以的开关和变量更加符合我们的思维方式,不是吗?
另外,我们顺便学习一下js的面向对象编(zhuang)程(bi)。
第二节 需求分析
简而言之,我们的任务系统到底需要什么?
要炫酷的动画提示,还要跟着提示音效,还要与众不同的界面效果。
这些都不对!!!
需求分析,应当把最核心的功能给分析出来,我们制作的时候,也应当这样,先做好基本的功能,然后以此为框架,逐步完善。
至于炫酷的动画……那是什么鬼?
好了,有了这个共识,我们会发现,程序只剩下数据和功能,连输入输出的界面都不需要考虑了。这样的话,问题就简单多了。
开始分析。
我们的任务系统是基于原版MV系统的,不是基于其他大神的插件系统的。当然,我们的原则是,尽量不修改原文件,尽量不修改系统对象。
任务系统:当然是像其他游戏一样的。有各种任务,还需要实时监测任务完成的条件,对任务的状态进行管理。
任务:要有编号,名字,描述,当前所处的状态,以及各种条件下触发的动作等等。
任务数据库:用于存放静态的预定义好的任务。
第三节 对象和原型链简介
一个js对象,应该像下面一样创建:
没错,这就是一个函数,不过js里面的函数可和别的语言的函数不同,它可以用来当做对象/类使用。
当然,在es6的新标准里,我们可以用语法糖class,从而制作像(只是看上去)其他语言里面的类一样的东西。
我们通过new来创建一个对象的副本,并且在同时运行function内的代码,这样的函数就叫做构造器。
定义在function体里面的变量,只能在function里面使用,如果外面要调用的话是不行的。那么我们该如何在外面调用呢?
js提供了原型链这种神奇的东西。简而言之,定义在原型链上的变量都是可以被继承下去的,并且可以在外面调用。通过一个简单的“.”,我们就可以访问原型链上的东西了。就像下面这样,我们可以在原型链上定义新的对象(函数也可以看做对象)。
- var Cat=function(){
- var m=258;//在构造器内部。
- this.mm=123;//不在原型链上,可以访问。
- }
- Cat.prototype.mmm=456;//在原型链上,一旦修改,全部会变。
- var cat1=new Cat();//创建新Cat对象,事实上是创建了对原型链的引用。
- var cat2=new Cat();//创建新Cat对象
- alert(cat1.m);//不可以访问
- alert(cat1.mm)//可以访问
- alert(cat1.mmm)//可以访问
- cat2.mm=5;
- alert(cat1.mm);//仍然是123
- cat2.mmm=3;
- alert(cat1.mmm);//也变成了3
复制代码
我们也可以在外面使用prototype来访问原型链,像下面这样定义一个函数。
- Cat.prototype.Say=function(){
- return “Miao Miao Miao…”;
- }
复制代码
但是要注意的是,从同一个Cat上new出来的所有对象,它们的原型链是共享的,也就是说,只要在任意一个对象上改变了原型链的值,那么就会影响所有的对象。如下:
- cat1.mm=567;
- alert(cat2.mm);//结果也是567
复制代码
这种特性方便了继承,但是也造成了一些不便,所以我们一般只在原型链上放共同的特性,以及不会变化的函数。
那么如果我们想要创造每个对象可以取不同的值的属性,该怎么办呢?看下面的代码:
- var Dog=function(){
- this.name=”The Dog Haven’t Name”;
- }
- Dog.prototype.getName=function(){
- return this.name;
- };
- Dog.prototype.setName=function(value){
- this.name=value;
- };
复制代码
像这样,定义在原型链上的函数里面的内容,是可以访问构造器里面定义的变量的(像上面的name),这样我们就可以为不同的Dog设置不同的name了,使用的时候只需要通过两个函数getName和setName,而这两个函数对所有的Dog来讲只有一份。
这只是一种简单的封装方法,也是js原生支持的一种方法,关于如何用js实现面向对象的各种特性,可以专门百度,有很多更好的办法。不过在这里,我们的方法就够了。
第四节 任务和管理者
简单明白了原型链之后,我们就可以正式开始了,首先,要定义一个Task对象,这应该是所有的任务对象的原型(与类相似的)。任务要有各种各样的属性和方法。
我们像下面的图5-1这样定义一个Task,注意,仍然按我的风格,放进命名空间里面装逼~~~
需要说明的是,id,title之类的属性都是各不相同的,所以应当放在构造器中定义,而且后面的几个函数也一样,分别对应着各种情况下要自动响应的动作。在我们的设计中每个Task通过定义各自的这些动作来完成自动的处理。
图5-1
那么后面当然还要在原型链上定义对上面的属性和方法的操作了,否则在外边是访问不到的。具体的内容可以看附件中的实际插件的脚本。
然后,我们就该定义一个管理者了——Manager。为什么非要管理者呢?难道你不觉得这样的分层设计更加的明(ke)白(yi)可(zhuang)靠(bi)吗?
这个Manager就简单多了,只需要一个任务列表(数组),用来储存我们生成的具体的Task对象。还有一些方法,用来把对任务列表的操作封装起来。调用的时候更加简单明白。定义如图5-2。
好吧,我的起名水平和封装水平一样,但是这不要紧。毕竟大致已经够我使用了。
图5-2
第五节 静态设定——一个不是json的json表
好吧,我们难道非得把数据写在事件里?每次都要new一个?还有,任务是事件,但是任务信息可以算是数据了,写在动态生成的地方真的好吗?
基于以上及其他原因考虑,我们将要把所有已经设计好的任务,放在一个json表里面,这样就可以方便我们设计和管理已经制作好的任务了。
但是,个人风格,为了写注释,我不用json文件,而是改写成js,把它和插件一起加载。这样除了使用的时候麻烦一些,倒也不错。
此外,我们还要在Manager里面专门定义一个函数,用来读取json(暂时仍然这么叫吧)的信息,并且转换成我们真正的Task并存在Manager的任务列表里面。这就是loadTaskFile函数的功能了。
Tasks,这个json一样的列表,关于他的具体内容可以参看附件中的文件。毕竟东西有点多,而且里面也是加了比较详细的注释的。
但是,这个loadTaskFile函数,我们还是可以在这里分析一下的,如图5-3。
图5-3
先将任务列表清空,然后来一个循环遍历Tasks这个(伪?)json。每次都创建一个新的Task,然后根据Tasks里面的值来设定这个新的Task对应的值(这一部分功能被放到了createByData函数里面)。最后把新的Task放进任务列表里面。然后执行一些操作:将state设置为Unrecive,将avaliable设置为true,执行自己定义的init函数。事实上就相当于进行了初始化。
同样的,自己定义的begin会在开始任务的时候执行,ifFinish会在判定任务是否完成时当做判据(返回值),end会在任务结束时执行。当然这些都要自己进行设计。
第六节 是监视者还是窗口
好了,一切设计的都很完美,但是关键的一点仍然没有进行——如何对变量进行管理?让任务达到条件自动改变状态,自动执行动作。这些都需要一个监视者,每时每刻都在执行这一任务——判定任务所处的状态。这实际上是要并行完成的,但是,我们考虑到窗口的事件模型的话。我们可以用一种比较“简单”的方法来完成这件事情——画一个窗口,然后在窗口的update里面添加上相关的判定部分,顺便还能显示一些我们所需要的信息。(这些都不重要,真的,没有任务窗口,不显示任务信息难道就是没有任务了?)
如图5-4,我们就这样完成了一个任务系统。
图5-4
第七节 把我的变量存起来
等等,我要存档的话,任务会不会白做了?
显然是的,因为这些代码都是每次开启游戏都会执行一遍的。也就是说,目前为止你并不能保存下来任何东西。
那该怎么办呢?
别着急,我的独门(?)秘(pian)诀(fang)即将登场。
图5-5
如图5-5,我们重写了两个MV的方法,DataManger的saveGame和loadGame,从而让游戏在读档和存档的时候执行我们所要干的事情~~~如图5-6所示。
在保存前,我们遍历了manager的任务列表,并且把每个任务的variables带着名字存到了一个新的空对象里面。
然后,我们使用JSON.stringify函数,将一个js对象变成了一个字符串。这样,就可以把它放进某个MV的变量(在事件窗口用到的那种)中去了。
接下来在保存,那就是实打实的被保存起来了。
下次读取的时候,只要把这个字符串上的值用JSON.parse函数还原回来就好了,这简直是太机智了。
这种方法需要付出一个MV变量槽,以及重写存档读档的函数。用不用自己决定吧。
最后的效果gif,如图5-7。
图5-6
图5-7
具体这个插件如何使用,还要再细看附件中的示例和代码。
|