Project1
标题:
RPG Maker MV 的源码研究 十一
[打印本页]
作者:
沉滞的剑
时间:
2019-8-22 06:20
标题:
RPG Maker MV 的源码研究 十一
先分享一个RM里关于类和继承的知识
比如在RM里声明一个'类'和它的'继承'的方法是这样的
function Window_Base() {
this.initialize.apply(this, arguments);
}
Window_Base.prototype = Object.create(Window.prototype);
Window_Base.prototype.constructor = Window_Base;
复制代码
我们创建一个'实例'的方法是
const window = new Window_Base()
复制代码
我们首先明确几个概念
1. 函数也是对象
对象具有属性, 单纯来看'prototype'是'Window_Base'对象的一个属性
当然这个属性是特殊的
2. js没有原生的'类'的支持, js是通过原型链的方式实现继承
Window_Base是一个正常的对象, 也是一个函数
和你用其他方式创建一个对象每有本质的区别
js拥有一个被称为原型的属性, __proto__, 它可以是一个对象也可以为空
JS作为一门动态语言, 它的属性是在运行时才被确定的
所以当你试图访问一个对象的属性的时候(比如a.b), 编译器必须去'找'这个属性
如果直接在这个对象中找到了这个属性, 那么就返回这个属性的值
但是如果没有, 它还会尝试在它的原型里查找
如果还是没有, 它就会在原型的原型里查找, 直到查找到或者原型为null
这个就是js实现继承的特性: 原型链, 这一过程也被称之为动态委托
3. new关键字是一个语法糖
我们可以写一个等价的方法来描述这个new
const $new = ($class, ...args) => {
const result = {
__proto__: $class.prototype
}
$class.call(result, ...args)
return result
}
复制代码
说一下constructor
每个对象都有一个constructor
它指向对象的构造器, 比如
Object的constructor指向的是Object
数组的constructor指向的是Array
它并不一定是必须的, 没有这一句一样可以正确创建对象
Window_Base.prototype.constructor = Window_Base;
但是如果没有这一句, 就无法通过obj.constructor来判断对象类型了
我们再说一下Object.create(obj)方法
一个最简单的等价函数为
Object.create = function (proto) {
function F() { }
F.prototype = proto;
return new F();
};
复制代码
我们替换成我们之前写的$new, 就等价于
Object.create = function (proto) {
return { __proto__: proto }
};
复制代码
我们用RM的方式定义两个类
// 这个相当于定义了一个构造函数
function ClassA() {
this.a = 10
}
// 我们让A类型继承于Object.prototype
ClassA.prototype = Object.create(Object.prototype);
// 等价于 ClassA.prototype = {__proto__: Object.prototype }
// 最后让对象的原型的构造器函数指向ClassA
ClassA.prototype.constructor = ClassA;
// 我们再创建另一个继承于A的类型B
function ClassB() {
// 相当于super
ClassA.call(this)
this.b = 20
}
ClassB.prototype = Object.create(ClassA.prototype);
// 等价于 ClassB.prototype = {__proto__: ClassA.prototype }
ClassB.prototype.constructor = ClassB;
复制代码
那么现在我们来$new(ClassB)并和new ClassB做一个对比
他们的结构是完全一样的:
ClassB {a:10, b: 20} ▼
a: 10
b: 20
__proto__: ClassA ▼ <= ClassB.prototype
► constructor: ƒ ClassB()
► __proto__: Object <= ClassA.prototype
ES5引入了class关键词, 可以看作是上一步骤的语法糖
class ClassB extends ClassA {
constructor() {
super()
this.b = 20
}
}
复制代码
当然只能用new关键词来实例化, 避免了被错用
new ClassB的结果:
ClassB {a:10, b: 20} ▼
a: 10
b: 20
__proto__: ClassA ▼
► constructor: class ClassB
► __proto__: Object
对象 -> ClassB.原型 -> ClassA.原型
由于是动态委托, 所以我们可以随时修改原型链
比如:
ClassB.prototype.hello = function () { this.b = 100 }
复制代码
我们来看我们之前的对象自动就发生了变化
ClassB {a:10, b: 20} ▼
a: 10
b: 20
__proto__: ClassA ▼
► constructor: class ClassB
► hello: ƒ ()
► __proto__: Object
同理如果添加
ClassA.prototype.hello = function () { this.a = 100 }
那么hello就会出现在obj.__proto__.__proto__里面
有兴趣可以自己动手试一试
以上是比正文还长的废话
=============================================
第十章
原文再续, 书接上一回
上一回说到Window_Command增加了Command的概念
我们终于可以讲讲水了这么多回才介绍的主角Window_TitleCommand
这个是我们在RM中遇到的第一个菜单
Window_TitleCommand.prototype.initialize = function () {
Window_Command.prototype.initialize.call(this, 0, 0);
this.updatePlacement(); // 重置位置
this.openness = 0; // 初始为未打开状态
this.selectLast(); // 选择最后的Command
};
// 记忆唤醒大法~
Window_Command.prototype.initialize = function (x, y) {
this.clearCommandList();
this.makeCommandList(); // 关注这个
var width = this.windowWidth();
var height = this.windowHeight();
Window_Selectable.prototype.initialize.call(this, x, y, width, height);
this.refresh();
this.select(0);
this.activate();
};
Window_TitleCommand.prototype.updatePlacement = function () {
this.x = (Graphics.boxWidth - this.width) / 2; // 居中
this.y = Graphics.boxHeight - this.height - 96; // 使底部距离边缘96像素
};
Window_TitleCommand.prototype.selectLast = function () {
// 如果有最后的Command, 则选择
if (Window_TitleCommand._lastCommandSymbol) {
this.selectSymbol(Window_TitleCommand._lastCommandSymbol);
} else if (this.isContinueEnabled()) {
this.selectSymbol('continue');
}
};
复制代码
注意Window_TitleCommand._lastCommandSymbol是个静态方法
这就是说明它在整个游戏周期一旦被设定就一直有效
Window_TitleCommand.prototype.processOk = function () {
Window_TitleCommand._lastCommandSymbol = this.currentSymbol();
Window_Command.prototype.processOk.call(this);
};
复制代码
每当选择一个Command的时候, 这个Command的Symbol就会记录下来
下一次打开标题菜单的时候就会被选中
makeCommandList在Window_Command里是没有实际内容的
这个一半交由各个子类自己实现
Window_TitleCommand.prototype.makeCommandList = function () {
this.addCommand(TextManager.newGame, 'newGame');
this.addCommand(TextManager.continue_, 'continue', this.isContinueEnabled());
this.addCommand(TextManager.options, 'options');
};
// 判断Continue是否可选, 就是判断有没有存档
Window_TitleCommand.prototype.isContinueEnabled = function () {
return DataManager.isAnySavefileExists();
};
复制代码
又出现了个没排面的TextManager, 无视它
简单看看老熟人DataManager又干了什么吧
DataManager.loadGlobalInfo = function () {
var json;
try {
// 尝试读取global.rpgsave
json = StorageManager.load(0);
} catch (e) {
console.error(e);
return [];
}
// 如果读取成功
if (json) {
var globalInfo = JSON.parse(json);
// 剔除已经不存在的存档
for (var i = 1; i <= this.maxSavefiles(); i++) {
if (!StorageManager.exists(i)) {
delete globalInfo;
}
}
// 返回存档
return globalInfo;
} else {
return [];
}
};
DataManager.isThisGameFile = function (savefileId) {
// 读取全局文件
var globalInfo = this.loadGlobalInfo();
if (globalInfo && globalInfo[savefileId]) {
// 如果是全局本地模式
if (StorageManager.isLocalMode()) {
return true;
} else {
// 否则额外判断一下存档信息
var savefile = globalInfo[savefileId];
return (savefile.globalId === this._globalId &&
savefile.title === $dataSystem.gameTitle);
}
} else {
return false;
}
};
// 如果有一个存档存在则返回true
DataManager.isAnySavefileExists = function () {
var globalInfo = this.loadGlobalInfo();
if (globalInfo) {
for (var i = 1; i < globalInfo.length; i++) {
if (this.isThisGameFile(i)) {
return true;
}
}
}
return false;
};
复制代码
Window_TitleCommand基本上功能就这些了
不过我们还要稍微回顾一下Scene_Tittle对它的处理
Scene_Title.prototype.createCommandWindow = function () {
this._commandWindow = new Window_TitleCommand();
this._commandWindow.setHandler('newGame', this.commandNewGame.bind(this));
this._commandWindow.setHandler('continue', this.commandContinue.bind(this));
this._commandWindow.setHandler('options', this.commandOptions.bind(this));
this.addWindow(this._commandWindow);
};
复制代码
可以看出, handler是在外面定义的, 连接的是Scene_Title本身的方法
这个就是Window和Scene交互的方法了
===============================
最后我们简单练个手
我们想办法给这个菜单加一个退出Command:
// 来我们第一步先重写一下makeCommandList
// 使用大括号是防止里面的变量污染全局
{
const temp = Window_TitleCommand.prototype.makeCommandList
Window_TitleCommand.prototype.makeCommandList = function () {
temp.call(this)
this.addCommand('退出', 'test_exit');
};
}
// 然后在修改一下createCommandWindow
Scene_Title.prototype.createCommandWindow = function () {
this._commandWindow = new Window_TitleCommand();
this._commandWindow.setHandler('newGame', this.commandNewGame.bind(this));
this._commandWindow.setHandler('continue', this.commandContinue.bind(this));
this._commandWindow.setHandler('options', this.commandOptions.bind(this));
this._commandWindow.setHandler('test_exit', SceneManager.terminate);
this.addWindow(this._commandWindow);
};
复制代码
收工!
作者:
eterry11211
时间:
2019-8-22 09:24
给大佬膝盖
欢迎光临 Project1 (https://rpg.blue/)
Powered by Discuz! X3.1