设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 3134|回复: 1
打印 上一主题 下一主题

[交流讨论] RPG Maker MV 的源码研究 其七

[复制链接]

Lv3.寻梦者

梦石
0
星屑
1912
在线时间
1554 小时
注册时间
2013-4-13
帖子
917
跳转到指定楼层
1
发表于 2019-8-17 12:47:32 | 只看该作者 回帖奖励 |正序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
继续了~继续了~
想了好久要更什么部分, 因为既可以跳到下个场景去还可以看看Title_Scene用了的一些东西
还有一大堆欠债...
不过我最后决定还是讲一个跟实际制作插件关系比较大的内容吧
讲讲用到的CommandWindow, 不过首先还是把基础类整理个大框吧

===============
第六章 Window, 神使

回顾一下, 之前我们在Scene_Title里遇见了CommandWindow:
  1. Scene_Title.prototype.createCommandWindow = function() {
  2.     this._commandWindow = new Window_TitleCommand();
  3.     this._commandWindow.setHandler('newGame',  this.commandNewGame.bind(this));
  4.     this._commandWindow.setHandler('continue', this.commandContinue.bind(this));
  5.     this._commandWindow.setHandler('options',  this.commandOptions.bind(this));
  6.     this.addWindow(this._commandWindow);
  7. };
复制代码

CommandWindow属于RM对象中比较庞大的那一家族Window类
可能许多人开始学RM就是从创建一个对话框开始的(我就是
那也是一个Window
那么今天我们就分析一下连接系统和玩家的界面Window类的原理吧

  1. function Window() {
  2.     this.initialize.apply(this, arguments);
  3. }

  4. Window.prototype = Object.create(PIXI.Container.prototype);
  5. Window.prototype.constructor = Window;
复制代码


和Scene一样, window也是继承于Container类,
所以他也可以有儿子, 孙子...

Window是一个基础类, 它具有所有Window的子类的共通特性
下面是它的构造器, 能看得出他有很多的属性, 我把简单的几个属性用注释的方法标记了
具体到后面还会再说明

  1. Window.prototype.initialize = function () {
  2.     PIXI.Container.call(this);

  3.     this._isWindow = true; // 表示这个对象是一个窗口
  4.     this._windowskin = null; // 窗口皮肤
  5.     this._width = 0; // 宽度
  6.     this._height = 0; //高度
  7.     this._cursorRect = new Rectangle(); // 光标矩形
  8.     this._openness = 255; // 开启状态, 255是完全开启, 0是完全关闭
  9.     this._animationCount = 0; // 动画计时
  10.     this._padding = 18; // 内边距(边框到内容的间隔)
  11.     this._margin = 4; // 外边距(边框外的间距)
  12.     this._colorTone = [0, 0, 0]; // 色调
  13.     this._windowSpriteContainer = null; // 精灵容器
  14.     this._windowBackSprite = null; // 后退图标
  15.     this._windowCursorSprite = null; // 光标图标
  16.     this._windowFrameSprite = null; // 帧图标
  17.     this._windowContentsSprite = null; // 内容
  18.     this._windowArrowSprites = []; // 见到图标
  19.     this._windowPauseSignSprite = null; // 暂停符号
  20.     this._createAllParts();
复制代码
注意带下划线的代表是私有属性(只是认为约定, 实际上没有效果)
暴露给其他对象的属性是不带下划线_的, 比如 _windowskin和windowskin
但是它们之间是有联系的:
  1. Object.defineProperty(Window.prototype, 'windowskin', {
  2.     get: function () {
  3.         return this._windowskin;
  4.     },
  5.     set: function (value) {
  6.         if (this._windowskin !== value) {
  7.             this._windowskin = value;
  8.             this._windowskin.addLoadListener(this._onWindowskinLoad.bind(this));
  9.         }
  10.     },
  11.     configurable: true
  12. });
复制代码

我们一般读取一个属性的方法是 obj.name
一般设置一个属性的方法是 obj.name = value
但是其实还有一种别的方法来定义属性, 那就是Object.defineProperty和Object.defineProperties
(如果你用过Vue的话你就知道我在说什么, 当然现在还有Proxy了, 不过就好像见到了老朋友一样亲切
Object.defineProperty(Window.prototype, 'windowskin', ... } 就代表定义Window.prototype.windowskin属性
后面的一个对象里定义了这个属性的属性(the attributes of property)
set代表设置这个属性的方法
比如我定义一个属性obj.double的set方法为 x => {obj._x * 2}, 那么当我写 obj.double = 10的时候, 实际上就调用了set方法,  将obj._x的值设置成了20
这个set就好像是一个强盗, 他把你原来的赋值方法给拦截了下来, 重新用他的方法执行了一遍
而get也是同理, 当你访问obj.double的时候就会触发get方法, 如果get方法是 () => obj._x 的话就会把obj._x的值返回来
configurable代表你是否还可以修改这个属性, 如果设置成false你再想修改get和set方法就会报错的
总而言之以后每当对取windowskin这个属读取的时候就会返回内部属性_windowskin,
而对windowskin进行赋值的时候, 则会调用这个set属性,
这里首先会判断_windowskin和将要被赋予的值是否相等
如果相等则跳过赋值和加载的逻辑
因为加载图片是一个很好资源的过程
所以这里这个属性拦截的作用就是避免重复加载相同资源减少浪费

除了优化相关的, set和get拦截还有别的用处
比如: contents
  1. Object.defineProperty(Window.prototype, 'contents', {
  2.     get: function () {
  3.         return this._windowContentsSprite.bitmap;
  4.     },
  5.     set: function (value) {
  6.         this._windowContentsSprite.bitmap = value;
  7.     },
  8.     configurable: true
  9. });
复制代码

实际上指向并操作的是this._windowContentsSprite.bitmap
对比 this._windowContentsSprite.bitmap 和 this.contents 你会发现这样写代码变得整洁了

还有width和height(包括margin和padding我就不粘了, 意思是一样的)
  1. /**
  2. * The width of the window in pixels.
  3. *
  4. * @property width
  5. * @type Number
  6. */
  7. Object.defineProperty(Window.prototype, 'width', {
  8.     get: function () {
  9.         return this._width;
  10.     },
  11.     set: function (value) {
  12.         this._width = value;
  13.         this._refreshAllParts();
  14.     },
  15.     configurable: true
  16. });

  17. /**
  18. * The height of the window in pixels.
  19. *
  20. * @property height
  21. * @type Number
  22. */
  23. Object.defineProperty(Window.prototype, 'height', {
  24.     get: function () {
  25.         return this._height;
  26.     },
  27.     set: function (value) {
  28.         this._height = value;
  29.         this._refreshAllParts();
  30.     },
  31.     configurable: true
  32. });
复制代码

主要作用是每次修改的时候调用强制刷新

  1. /**
  2. * The opacity of the window without contents (0 to 255).
  3. *
  4. * @property opacity
  5. * @type Number
  6. */
  7. Object.defineProperty(Window.prototype, 'opacity', {
  8.     get: function () {
  9.         return this._windowSpriteContainer.alpha * 255;
  10.     },
  11.     set: function (value) {
  12.         this._windowSpriteContainer.alpha = value.clamp(0, 255) / 255;
  13.     },
  14.     configurable: true
  15. });
复制代码

在opacity(contentsOpacity, backOpacity, openness, )里, 还可以限制输入的数值在(0, 255)中, 并且转换为百分比记录在变量中
读取的时候又可以还原回原数据

Window类把Object.defineProperty的骚操作基本都给演示了一遍
希望大家写脚本的时候可以学到两招

现在说说window.update, 顺便把之前Scene的update方法再贴出来联系一下
  1. <blockquote><blockquote>Scene_Base.prototype.updateChildren = function() {
复制代码
之前说它们都是Container, 所以本质上是个树状结构, update父容器就会遍历其所有子容器
因为大家都有这个update方法, 尽管父类和子类不一定是一个类(大概率不是)
依然可以执行update

update其实不执行多少重绘方法的
因为重绘是个非常昂贵的操作
所以只有在需要重绘的地方我们才去重绘
比如移动和改变大小

  1. Window.prototype.move = function (x, y, width, height) {
  2.     this.x = x || 0;
  3.     this.y = y || 0;
  4.     if (this._width !== width || this._height !== height) {
  5.         this._width = width || 0;
  6.         this._height = height || 0;
  7.         this._refreshAllParts();
  8.     }
  9. };
复制代码



大家看到 XX || 0 这个操作千万不要迷惑
这个JS的技巧之一
如果我执行window.move()
那么x, y, width, height 的值就是undefined
我们知道JS的对象是有真值的(可以通过!!obj)来判断
而||(or)操作其实是如果左侧的对象的真值为true, 那么返回左边, 否则返回右边
而&&(and)操作其实如果左侧的对象的真值为true, 那么返回右边, 否则返回左边
而且这些操作如果前一个条件满足了, 后面的判断就不继续了
和你认为的是不是有点不一样?
width || 0 其实就是设定width的默认值为0
这个是以前js没有参数默认值时候人们使用的技巧
现在有了拆包操作, 和默认值等方法, 这样的奇怪代码就会减少了
&&也有一个常用技巧
比如我们知道访问undefined的一个属性会报错, 这也是我们经常遇到的错误信息
  1. Uncaught TypeError: Cannot read property 'a' of undefined
复制代码

诶呀, 是不是都很熟悉啊, 有没有看到这个就想要哭啊
fear not, 这一般都是没处理好空值引起的
比如我们有一个对象 x, x可能是个对象也可能undefined
那么如果新手来写就会写成
  1. if(x !== undefined){
  2.     return x.y
  3. } else {
复制代码
实际上undefined的真值是false我们可以直接写
  1. return x && x.y
复制代码
如果x是undefined就不会执行x.y这个会报错的指令了!


好回到move方法上来
我们注意的核心方法是_refreshAllParts
这个方法之前也出现了很多次比如在windowSkin的set方法里
  1. this._windowskin.addLoadListener(this._onWindowskinLoad.bind(this));
复制代码
的回调函数
  1. Window.prototype._onWindowskinLoad = function () {
  2.     this._refreshAllParts();
  3. };
复制代码

那它实际上也是一组方法的调用
  1. Window.prototype._refreshAllParts = function () {
  2.     this._refreshBack();
  3.     this._refreshFrame();
  4.     this._refreshCursor();
  5.     this._refreshContents();
  6.     this._refreshArrows();
  7.     this._refreshPauseSign();
  8. };
复制代码
里面都是和重绘内容相关的
因为我线性代数学的不好, 绘图部分我就先无视了=. =(留下了学渣的泪水
我们只要记住这些方法是更新画面的就好了
不过有一点refresh只负责绘制, 它不会改变数据

这些就是Window类的主要方法, 还有一些设置方法大家有兴趣可以自己去看
等到以后如果遇到我们就拿出来讲一讲

Window类只是提供了基本功能, Window家族也是个继承关系非常复杂的类
我们接下来主要是还是围绕CommandWindow来讲
再然后推进到下一个场景去



评分

参与人数 5+5 收起 理由
小恶魔有两只脚 + 1 塞糖
eterry11211 + 1 塞糖
xjzsq + 1 我很赞同
玄羽 + 1 塞糖
康姆图帕帕 + 1 塞糖

查看全部评分

夏普的道具店

塞露提亚-道具屋的经营妙方同人作品
发布帖:点击这里

Lv4.逐梦者

梦石
0
星屑
14538
在线时间
718 小时
注册时间
2011-7-16
帖子
1428

开拓者

2
发表于 2019-8-21 20:15:28 | 只看该作者
学习了新的知识
RMMV网络插件,开源免费,内含服务器端,无需强制登录,云数据,弹幕,云存档,排名,兑换码,版本检测,可自由上架下架删除。q群399090587
免打包运行MV游戏,云游戏,安卓App雷神游戏厅,在线玩游戏,上传下载游戏
开源游戏:重装机兵之重装归来【RMMV制作】全球首款按照美剧分季分集的方式发布的游戏
体素画 -- MV画3D像素图的画板
RMMV显示3D模型和场景的插件
RMMV显示spine骨骼动画的插件
RMMV秘密通道插件
突破敌群数量上限8个的插件
在rmmv中显示gif动态图片的插件
一款可以在mv游戏界面的任意位置显示任意文字的插件
RMMV Toast 插件 带物品得失提示,可以设置开启关闭 兼容yep itemcore
制作一个改名卡道具插件、调整标题页面菜单的插件、在标题页面之前显示大段文字的插件、标题页面显示版本号的插件
物品得失自动提示自动上色自动换行插件
我的Q群 663889472
另外,我的插件、范例、游戏都在这里
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-9-24 21:21

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表