Project1
标题: 关于菜单窗口 [打印本页]
作者: Yosokay 时间: 2018-3-14 21:21
标题: 关于菜单窗口
各位巨巨,又来请教一个问题。
想达到打开菜单界面的时候显示控制菜单(commandWindow),物品栏(ItemWindow)和物品说明(helpWindow),是不是应该改写sceneMenu.prototype.create方法呢,但是scenemenu里没有sceneitem的变量,
那么是不是需要把scene_menu去继承scene_item的全部方法,然后在scene_menu.prototype.create中创建itemWindow和helpwindow呢?
希望能详细讲讲。
顺便水一下经验。
作者: ekmomo 时间: 2018-3-14 21:35
不用,把使用物品的相关方法加上就行了。还有,你如果有设置了目标的道具还需要一个角色窗口。
作者: Yosokay 时间: 2018-3-14 21:46
继承的话也不知道如何加,好像也没有同名的方法。
我尝试把sceneitem一些相关的方法直接将对象改成了scenemenu,但是有一些变量好像scenemenu调用不了,运行会出问题
难受
作者: ekmomo 时间: 2018-3-14 22:17
本帖最后由 ekmomo 于 2018-3-14 22:53 编辑
这样?
Menu.rar
(893 Bytes, 下载次数: 90)
作者: ekmomo 时间: 2018-3-14 22:19
本帖最后由 ekmomo 于 2018-3-14 22:54 编辑
仅供参考吧。
作者: Yosokay 时间: 2018-3-14 23:15
十分感谢,要的大概是这种效果
有个不成熟的建议,收我为徒吧大神
关于您的脚本我还有一些问题,明天再打扰您啦。
作者: Yosokay 时间: 2018-3-15 20:24
- Scene_Menu.prototype.start = function() {
- Scene_MenuBase.prototype.start.call(this);
- this._statusWindow.refresh();
- this._actorWindow.refresh();
- };
复制代码
请问这两个refresh在这是刷新什么呢
- Window_MenuStatus.prototype.setFormationMode = function(formationMode) {
- this.show();
- this._formationMode = formationMode;
- };
复制代码
这个好像多了一个this.show(),其目的是为了显示编队?但为什么原函数没有这行代码呢
还有一个比较宽泛的问题,总感觉想写插件来实现功能的时候,改写方法不能够顾及得很到位,有时候debug也找不到问题;还有就是mv里js源码很多函数功能还是看不懂,比如window_selectable里一些方法等。这些需要详细学习吗?我现在基本是:为了粗略实现某功能——>找相应scripting视频——>摸索实现(主要花时间在想代码的功能是什么,改不一定改得出来
作者: ekmomo 时间: 2018-3-16 10:04
第一个问题,refresh()让图片提前加载,如果去掉的话会出现第一次打开没有脸图的问题。
第二个问题,因为默认的菜单界面Window_MenuStatus构造的_statusWindow默认是显示的,我把他隐藏了,而需要显示它的时候都需要setFormationMode,最好的方式就是把show方法加到这里了。
作者: ekmomo 时间: 2018-3-16 10:19
本帖最后由 ekmomo 于 2018-3-16 10:22 编辑
你可以尝试着回答下我的几个问题。
1.为什么类似function Scene_MenuBase()的首字母是大写(大驼峰)而一般函数(如alert)的首字母是小写?
2.this是什么?
3.在prototype下写方法和在initialize里直接定义一个方法(或者属性)的区别是什么?
如果这几个问题你都搞明白了,可以研究以下MV默认的脚本是怎么运行的(从哪开始怎么刷新)。
还有兴趣的话可以研究下pixi引擎渲染逻辑。
最后,多用F8来解决问题。控制台既可以当计算器用,又可以去找对象,找原型,能实时的改游戏内的数据,
可以找内存泄漏,总之这是MV使用JS所带来的最大的便利。
作者: Yosokay 时间: 2018-3-17 15:09
尝试回答您的问题:
1.构造函数以大写字母开头,普通函数以小写字母开头,用作区分。
2.this做引用,取决于函数所处的环境。
3.一头雾水。initialie作为一个变量为其他构造函数的基础?
至于我现在的js水平也就是新手,知道基本的语法语句函数。关于原型翻阅过资料但尚且模模糊糊,一些其他比较杂的概念都是看油管上一些 mv scripting上使用的,并不了解其内里。关于内存泄漏离我可能还有很多距离。
至于研究mv默认脚本运行流程之类,我很乐意,但还是苦于寻找相应的指导。虽然6r和油管有很多脚本教程,但是看的时候还是很迷茫,感觉和用的挂不上钩。反而是我从改脚本出发去进行学习感觉有目的一些,但这时候js本身知识储备不够和不了解mv里的许多东西,问题就凸显了。
作者: ekmomo 时间: 2018-3-17 18:11
本帖最后由 ekmomo 于 2018-3-17 18:31 编辑
基本正确,MV只是为了保持RGSS编码风格所以用了this.initialize.apply(this, arguments)来创造构造函数,其实把initialize方法内容直接写在构造函数内是一样的。
构造函数这个东西在JS里就是一切对象与继承的根本,事实上在早期的JS当中,也只能通过构造函数来实现对象的继承。
让我们打开工程运行游戏,按F8选console(控制台)来创建一个构造函数
function Cat(){
this._name = 'miaomiao';
this._hp = 1;
this.eat = function(){
this._hp += 1;
console.log(this._hp);
};
}
function Cat(){
this._name = 'miaomiao';
this._hp = 1;
this.eat = function(){
this._hp += 1;
console.log(this._hp);
};
}
好了,构造函数创建好了,我们可以通过new操作符来实例化一个对象。
var cat = new Cat;
var cat2 = new Cat;
var cat = new Cat;
var cat2 = new Cat;
我们完全可以像自己定义的对象一样来操作被构造的对象。
cat3 = {
_name : 'miaomiao',
_hp : 1,
eat : function(){
this._hp += 1;
console.log(this._name + ' hp : ' + this._hp);
}
};
cat3.eat();
cat3.eat();
cat3.eat();
cat3 = {
_name : 'miaomiao',
_hp : 1,
eat : function(){
this._hp += 1;
console.log(this._name + ' hp : ' + this._hp);
}
};
cat3.eat();
cat3.eat();
cat3.eat();
我们也可以随时修改被实例化的对象的属性。
cat2._name = "wangcai";
cat3._name = "dahuang";
cat2._name = "wangcai";
cat3._name = "dahuang";
我们要实现构造函数的继承也比较容易,其实你已经接触过了。
function SuperCat(){
this._mp = NaN;
this._sex = "female";
Cat.apply(this, arguments);
}
function SuperCat(){
this._mp = NaN;
this._sex = "female";
Cat.apply(this, arguments);
}
这样新的构造函数不仅有两个新的属性,还有Cat得属性和方法(可以var cat4 = new SuperCat;然后输入cat4自己查看)。但是我们发现一个问题,这喵怎么这么能吃????我们需要修改eat方法。
function Cat(){
this._name = 'miaomiao';
this._hp = 1;
this._eatTooMuch = false;
this.eat = function(){
if (this._hp > 5) this._eatTooMuch = true;
if (this._hp >= 1){
this._hp += (this._eatTooMuch) ? -1 : 1;
console.log(this._hp);
}else{
console.log(this._name + ' die');
}
}
}
function Cat(){
this._name = 'miaomiao';
this._hp = 1;
this._eatTooMuch = false;
this.eat = function(){
if (this._hp > 5) this._eatTooMuch = true;
if (this._hp >= 1){
this._hp += (this._eatTooMuch) ? -1 : 1;
console.log(this._hp);
}else{
console.log(this._name + ' die');
}
}
}
当我们把这行代码输入到控制台中,发现了一个问题:!!!已经构造出来的小猫咪还是无限吃!!!
这不仅仅是需要批量修改的问题,细思恐极,用构造函数创造出来的对象方法也是和属性一样不共用的,每只猫都要独立开辟一块内存来存方法。
你可以想象我们在游戏中Game_Item下的isSkill()方法,如果背包里有3000个道具,它们都用自己独立的方法会多么令人难过。
于是乎JS出现了prototype属性,这个属性是伴随着构造函数的声明就已经默认创建的一个对象,你可以在console中直接输入Cat.prototype或者
cat.__proto__来查看构造函数(原型)及实例化对象的原型。
原型的操作非常简单,我们看下面这个例子很直观。
Cat.prototype.age = 1;//返回1
var cat = new Cat;
var cat2 = new Cat;
cat.age; //返回1
cat.age += 2; //返回3
Cat.prototype.age += 1;//返回2
cat2.age;//返回2
cat.age;//返回3
Cat.prototype.age = 1;//返回1
var cat = new Cat;
var cat2 = new Cat;
cat.age; //返回1
cat.age += 2; //返回3
Cat.prototype.age += 1;//返回2
cat2.age;//返回2
cat.age;//返回3
这就是原型链的作用方式,类似于函数的作用域,如果一个对象自己没有一个属性或者方法,它会去它的原型链上依次查找,如果他自己有这个属性或者方法,它就用自己的。
原型链的继承有很多种方式,MV采用的是圣杯模式
Window_Command.prototype = Object.create(Window_Selectable.prototype);
Window_Command.prototype.constructor = Window_Command;
Window_Command.prototype = Object.create(Window_Selectable.prototype);
Window_Command.prototype.constructor = Window_Command;
这样Window_Command就继承了Window_Selectable的原型链啦。比较蠢的是它连constructor一起继承,所以我们需要定义它自己的构造器(其实这是内聚和效率的取舍问题,知道这么写就行没必要较真)。
而Window_Selectable方法继承自Window_Base。所以当Window_Command构造出来的对象调用draw_Text的时候,它现在自己的方法里找》》发现没有》》在去Window_Command原型上找》发现还没有》》去Window_Selectable原型找》》依然没有》》去Window_Base找,找到了就调用,找不到就报错。(这么说不严谨,你可以在控制台var win = new Window_Base;然后输入win查看他的__proto__,其实他还是有原形的)
而当这个对象refresh()的时候,它找到Window_Command就有刷新了,他就用Window_Command的,而不会用Window_Selectable。
比如我们想改变Window_BattleStatus的光标移动规则,我们直接改Window_BattleStatus.prototype.cursorRight就可以了,这样既不会影响到别的窗口还能达到自己的效果。
再比如我们想让装备的帮助窗口显示更多的信息,我们可以新建一个Window_EquipHelp让它继承Window_Help,实例化的时候用Window_EquipHelp,我们只需要重写Window_EquipHelp的refresh()就可以了。
甚至你可以在实例化的对象上直接改,这样对象会直接调用自己的方法而非原型上的。(只能是由对象调用,了解就行)
最后做个总结,一定要学会多用控制台。
作者: Yosokay 时间: 2018-3-18 15:05
多谢讲解,但还是有地方不清楚
1.在mv里自带的脚本,如Scnen_Base为例:
- function Scene_Base() {
- this.initialize.apply(this, arguments);
- }
- Scene_Base.prototype.initialize = function() {
- Stage.prototype.initialize.call(this);
- this._active = false;
- this._fadeSign = 0;
- this._fadeDuration = 0;
- this._fadeSprite = null;
- this._imageReservationId = Utils.generateRuntimeId();
- };
复制代码
可以去掉this.initialize.apply(this, arguments);吗,为什么要加这一行
2.
- Cat.prototype.age = 1;//返回1
- var cat = new Cat;
- var cat2 = new Cat;
- cat.age; //返回1
- cat.age += 2; //返回3
- Cat.prototype.age += 1;//返回2
- cat2.age;//返回2
- cat.age;//返回3
复制代码
为什么在Cat.prototype.age += 1后cat.age是3而不是4?
3.关于mv脚本运行是不是先把默认脚本全部执行,然后再依次执行插件脚本?
作者: ekmomo 时间: 2018-3-18 21:56
本帖最后由 ekmomo 于 2018-3-18 22:02 编辑
我们先来将你的第三个问题
关于mv脚本运行是不是先把默认脚本全部执行,然后再依次执行插件脚本?
这句话本身没错,但是我觉得你问出这个问题是没有理解解释器跑代码的基本原理。如:
function fn(){
你好这里是一行错误的代码啦啦啦
}
function fn(){
你好这里是一行错误的代码啦啦啦
}
我们把代码在控制台里输入一遍,不会报错。是因为我们即使在函数体(花括号内)里写一万行代码,解释器都不会读到函数体里面去(预编译阶段会抛出基本语法错误,语法错误是什么了解一下就行)。只有在我们调用函数(使用fn()来调用它)的时候,函数才会被执行。
这个例子里的语句被称作函数声明语句,它包括两个部分:函数的声明和函数的定义。它就好像是变量声明和赋值一样(var a = 1),解释器再读到函数声明的时候,只是把函数放入内存。函数体内的所有代码是什么解释器根本不关心。
然后我们讲下函数的重写,它就好像是变量的覆盖。
function fn(){//1
你好这里是一行错误的代码啦啦啦//未执行时被忽略
}
function fn(){//2
console.log("你好!这里是一行正确的代码啦啦啦。")//未执行时被忽略
}
fn();
function fn(){//1
你好这里是一行错误的代码啦啦啦//未执行时被忽略
}
function fn(){//2
console.log("你好!这里是一行正确的代码啦啦啦。")//未执行时被忽略
}
fn();
在控制台复制以上代码运行,我们发现可以正确打印文本。这说明函数被重写以后,存放之前函数的内存就被释放了。
这里要说一点,JS跑代码的时候是从上到下 逐行执行的。在下方定义的函数或者赋值的变量总会覆盖上方的。这也是我们为什么可以在插件里直接重写MV自带的函数以及为什么有些插件需要按顺序排放原因。
fn1();
function fn1(){
console.log("JS逆天");
}
fn1();
function fn1(){
console.log("JS逆天");
}
最后这个例子是预编译阶段声明提前的示例,我们可以先调用再定义一个函数。(了解就行)
然后我们再回答第二个问题var a = 1;
var b = 2;
var c = 3;
function mrScope(){
var a = 4;
b = 5;//自己没申请,直接写全局
function msScope(){
var c = 6;
console.log(a);//mrScope有
console.log(b);//全局有
console.log(c);//自己有
console.log("-----------无情分割-----------");
}
msScope();
}
mrScope();
console.log(a);
console.log(b);
console.log(c);
var a = 1;
var b = 2;
var c = 3;
function mrScope(){
var a = 4;
b = 5;//自己没申请,直接写全局
function msScope(){
var c = 6;
console.log(a);//mrScope有
console.log(b);//全局有
console.log(c);//自己有
console.log("-----------无情分割-----------");
}
msScope();
}
mrScope();
console.log(a);
console.log(b);
console.log(c);
我们讲原型链的时候顺带就把作用域讲了好了。仔细看代码msScope在最里层,他外层有mrScope和全局作用域。我们现在全局定义了a,b,c然后在mrScope作用域里定义了自己的a,和重写了全局的b。(它自己没b所以用了全局的)msScope中,他定义了自己的c然后输出了mrScope的a全局的b(自己和mrScope都没有)和自己的c。所以结果是4,5,6。
而在全局执行的时候,它只能用自己的变量。简而言之就是里层函数作用域没有一个变量时它会依次向上级作用域查找,里可以找外,而外不能找里。
这里还有一个重点就是
funciton er(){
b = 5;
}
console.log(b);
funciton er(){
b = 5;
}
console.log(b);
我们在如果在一个函数里不用var 声明就直接使用一个变量的话,它会变成全局变量。我们回答第一个问题的时候就说过,函数体只有在函数执行的时候才有意义,函数的作用域也是一样,当函数执行完毕时,它的作用域和作用域里面的变量都会被自动销毁(闭包除外)但是如果不用var这个变量提升到全局的话,只有在游戏结束了它才销毁。
原型链的使用原理和作用域相似,原型可以依次查找原型链上的属性。cat.age += 2;等价于
cat.age/*定义过自己就有了,以后原型就管不了了*/ = cat.age/*这里自己还没有就调原型的*/ + 2;
cat.age/*定义过自己就有了,以后原型就管不了了*/ = cat.age/*这里自己还没有就调原型的*/ + 2;
第一个问题 为什么要加this.initialize.apply(this, arguments);
你可以理解为apply和call都是复制代码用的,这俩方法除了写参数的形式不一样以外没区别。如:
function say(){
console.log("5555");
}
function cry(){
var tears =tears || 0;
tears += 1;
say.call(this);//相当于把say的函数体直接复制到这里。
}
cry();
function say(){
console.log("5555");
}
function cry(){
var tears =tears || 0;
tears += 1;
say.call(this);//相当于把say的函数体直接复制到这里。
}
cry();
作者: Yosokay 时间: 2018-3-19 20:53
蟹蟹
但是感觉对initialize还是难理解,譬如:
- function Scene_Base() {
- //this.initialize.apply(this, arguments);
- }
- Scene_Base.prototype.initialize = function() {
- Stage.prototype.initialize.call(this);
- this._active = false;
- this._fadeSign = 0;
- this._fadeDuration = 0;
- this._fadeSprite = null;
- this._imageReservationId = Utils.generateRuntimeId();
- };
复制代码
这样的话会出现错误:undefined is not a function,其原因是因为Stage.prototype.initialize.call(this);这个没有调用进来,导致一些变量和方法的失联嘛?
那假如这是一个独立的方法,是不是可以在这个构造函数省去this.initialize.apply(this,arguments)?譬如:
- function Independent_Scene_Base() {
- //this.initialize.apply(this, arguments);
- }
- Independent_Scene_Base.prototype.initialize = function() {
- //Stage.prototype.initialize.call(this);
- this._active = false;
- this._fadeSign = 0;
- this._fadeDuration = 0;
- this._fadeSprite = null;
- this._imageReservationId = Utils.generateRuntimeId();
- };
复制代码
是不是就不会报错了呢?
作者: ekmomo 时间: 2018-3-20 00:25
本帖最后由 ekmomo 于 2018-3-20 00:41 编辑
你再仔细看下我前面的回复,其实我有解释的。
关键不是initialize,而是apply。apply和call的作用你可以理解成复制代码。
function Scene_Base() {
//this.initialize.apply(this, arguments);
//等同于Scene_Base.initialize.apply(this, arguments);
/*等同于*/
Stage.prototype.initialize.call(this);
this._active = false;
this._fadeSign = 0;
this._fadeDuration = 0;
this._fadeSprite = null;
this._imageReservationId = Utils.generateRuntimeId();
}
function Scene_Base() {
//this.initialize.apply(this, arguments);
//等同于Scene_Base.initialize.apply(this, arguments);
/*等同于*/
Stage.prototype.initialize.call(this);
this._active = false;
this._fadeSign = 0;
this._fadeDuration = 0;
this._fadeSprite = null;
this._imageReservationId = Utils.generateRuntimeId();
}
//Scene_Base.initialize.apply(obj, arguments); 等同于
Stage.prototype.initialize.call(obj);
obj._active = false;
obj._fadeSign = 0;
obj._fadeDuration = 0;
obj._fadeSprite = null;
obj._imageReservationId = Utils.generateRuntimeId();
//Scene_Base.initialize.apply(obj, arguments); 等同于
Stage.prototype.initialize.call(obj);
obj._active = false;
obj._fadeSign = 0;
obj._fadeDuration = 0;
obj._fadeSprite = null;
obj._imageReservationId = Utils.generateRuntimeId();
你把initialize.apply注释了相当于对象在构造时少了很多属性和方法。
//arguments是一个伪数组对象,是函数(或构造函数)执行时的参数列表,如:
function fn(){
console.log(arguments[0]);
console.log(arguments[1]);
}
fn(5, 2, 8);//输出5 2
//arguments是一个伪数组对象,是函数(或构造函数)执行时的参数列表,如:
function fn(){
console.log(arguments[0]);
console.log(arguments[1]);
}
fn(5, 2, 8);//输出5 2
apply 和 call唯一的区别就是,call接受多个参数,apply接受两个参数。
apply和call里第一个参数是一个对象,它用来替换原先函数里的this(就是让这个函数明白是谁调用的它),apply的第二个参数是一个数组,写法是apply(this, [a,b,c])而call得写法是call(this, a, b, c)。
作者: Yosokay 时间: 2018-3-21 13:30
十分感谢,能理解这一点了,下次有问题继续请教
欢迎光临 Project1 (https://rpg.blue/) |
Powered by Discuz! X3.1 |