赞 | 65 |
VIP | 231 |
好人卡 | 2 |
积分 | 19 |
经验 | 35171 |
最后登录 | 2024-9-15 |
在线时间 | 1554 小时 |
Lv3.寻梦者
- 梦石
- 0
- 星屑
- 1912
- 在线时间
- 1554 小时
- 注册时间
- 2013-4-13
- 帖子
- 917
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
先分享一个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);
- };
复制代码 收工!
|
评分
-
查看全部评分
|