Project1

标题: [教程]RMMV脚本教程——菜单美化(二) [打印本页]

作者: xjzsq    时间: 2019-7-12 12:53
标题: [教程]RMMV脚本教程——菜单美化(二)
本帖最后由 xjzsq 于 2020-8-23 02:38 编辑

全系列教程传送门
RMMV脚本教程——菜单美化(一)

RMMV脚本教程——菜单美化(三)(完结)
写在前面
时隔不到一年半,高考完的我终于决定继续研究MV的脚本,然后写教程啦
之前的教程(下面参考资料有文章链接)发布在Project1(原6R)论坛上,之前的坑还是会慢慢填完的,现在再开个新坑!
这次来美化一下菜单!
首先放一下先行图:
上一篇教程的传送门:  
[教程]RMMV脚本教程——菜单美化(一)
ps:
推荐去我的博客看,以获得最佳观看体验:RMMV脚本教程——菜单美化(二)

注意事项
正文
上次说到哪了
对,这次我们要来改造右边显示角色简要信息的窗口了!
美化角色状态窗口
首先,我们还是要分析这个窗口是在哪里定义的。
Scene_Menu中搜索状态,发现如下脚本:
  1. /**创建状态窗口 */
  2. Scene_Menu.prototype.createStatusWindow = function() {
  3. //状态窗口 = 新 窗口菜单状态(命令窗口 宽 , 0 )
  4. this._statusWindow = new Window_MenuStatus(this._commandWindow.width, 0);
  5. //状态窗口 预约脸图()
  6. this._statusWindow.reserveFaceImages();
  7. //添加窗口(状态窗口)
  8. this.addWindow(this._statusWindow);
  9. };
复制代码
看到预约脸图(这什么鬼才翻译果然是机翻),菜单上显示脸图的地方也就是显示角色基本信息、用来选择角色的窗口了
因此我们打开Window_MenuStatus,我们先去看看预约脸图到底是个什么鬼东西
最后一直搜到他的父类的父类Window_Base,才搜到这个方法:
  1. /**预约脸图 */
  2. Window_Base.prototype.reserveFaceImages = function() {
  3.     //游戏队伍 成员组() 对每一个 角色
  4.     $gameParty.members().forEach(function(actor) {
  5.         //图像管理器 预约脸图(角色 脸图名称())
  6.         ImageManager.reserveFace(actor.faceName());
  7.     //this)
  8.     }, this);
  9. };
复制代码
然而没看出来什么
不过看起来本质是对每一个成员都来一遍ImageManager.reserveFace函数。
然后再去rpg_managers文件夹下的ImageManager.js中搜索,,,
算了,我直接说出来吧
这里说的预约或者预订,大概就是预加载的意思,MV的机制和VA中有些不太一样,VA中是如果图片在Cache中找不到,就去加载文件,而MV中最好预加载一下,然后使用的时候才能保证能正常显示,所以就有了一系列reserve函数。
这里不理解也没多大关系,毕竟对于我们这篇教程来说也用不到这个东西
回归正题,既然我们已经打开了Window_MenuStatus,那么就要看看这里面是怎么描绘窗口内容的了。
还记得在我一年之前的教程里面绘制角色脸图的方法吗?没看过的同学可以去参考资料那里去找链接,或者我的签名档。
没错,就是drawActorFace方法!因此我们搜索这个方法名就能知道是怎么绘制的了。
搜到的结果:
  1. /**绘制项目图像 */
  2. Window_MenuStatus.prototype.drawItemImage = function(index) {
  3.     var actor = $gameParty.members()[index];
  4.     var rect = this.itemRect(index);
  5.     this.changePaintOpacity(actor.isBattleMember());
  6.     this.drawActorFace(actor, rect.x + 1, rect.y + 1, Window_Base._faceWidth, Window_Base._faceHeight);
  7.     this.changePaintOpacity(true);
  8. };
复制代码
我们看到这里绘制了角色的脸图,但是我们注意到这个函数有参数
index,而且这个函数调用一次只能够在rect.x + 1, rect.y +1显示一个脸图,所以说一定是某个函数调用了这个函数很多次,才绘制出来所有角色的脸图。他是如何调用了这个函数多次,index又是什么含义呢?我们再以drawItemImage为关键字搜索:
  1. /**绘制项目 */
  2. Window_MenuStatus.prototype.drawItem = function(index) {
  3.     this.drawItemBackground(index);
  4.     this.drawItemImage(index);
  5.     this.drawItemStatus(index);
  6. };
复制代码
结果发现这个函数只是把
3个函数放在了一起,并不能看出来index的含义,再次搜索drawItem,发现只能搜到定义,却不能搜到其他地方有这个函数?
(黑人问号)这怎么会没有定义?
重点来了!!!
当我们在某个类(Window_MenuStatus)中搜索不到一个有定义却没有引用的函数(drawItem)时,我们珂以尝试去他的父类、父类的父类、甚至是他的祖宗类(XD)中去搜索,理论上说一般能够搜到(其实更简单的方法是直接在mvjs文件夹下的rpg_windows.js中搜索,但是没有中文注释)。
我们去Window_Selectable搜索,发现一下连续的两处结果:
  1. /**绘制所有项目 */
  2. Window_Selectable.prototype.drawAllItems = function() {
  3.     //topIndex = 顶部索引()
  4.     var topIndex = this.topIndex();
  5.     //循环 (开始时 i = 0 ; 当 i < 最大页项目数 ;每次 i++ )
  6.     for (var i = 0; i < this.maxPageItems(); i++) {
  7.         //索引 = 头索引 + i
  8.         var index = topIndex + i;
  9.         //如果(索引<最大项目数())
  10.         if (index < this.maxItems()) {
  11.             //绘制项目(索引)
  12.             this.drawItem(index);
  13.         }
  14.     }
  15. };
  16. /**绘制项目 */
  17. Window_Selectable.prototype.drawItem = function(index) {};
复制代码
我们发现,确实在
Window_SelectabledrawAllItem中引用了drawItem函数,并且Window_SelectabledrawItem是个空函数,说明是在其子函数中再定义选项内容。
回来看drawAllItem这个函数,其中的for循环使得index0循环到最大项目数,在菜单窗口内也就是循环调用描绘了所有角色的选项信息。因此,我们知道,只需要修改Window_MenuStatus中的drawItem方法,改变选项绘制的位置和内容,就可以达到我们预览图内的效果,中间的角色信息我们先不管,主要来看角色信息上下两行选项。
我们想要在选项中绘制角色的行走图,而不是脸图以及其他信息,因此我们需要将Window_MenuStatus.prototype.drawItem中的后两行删掉,第一行为描绘背景,这个大可保留,因为对我们基本没有影响。
之后我们就应该仿照绘制脸图的drawItemImage函数写一个函数来绘制人物行走图,就叫drawItemCharacterImages吧。这里的Character就是行走图的文件名称,大家珂以去游戏文件夹下的img文件夹下看看,里面有一个characters文件夹,这个文件夹里就存放着角色的行走图。
我们在Window_MenuStatus找到drawItemImage
  1. /**绘制项目图像 */
  2. Window_MenuStatus.prototype.drawItemImage = function(index) {
  3.     var actor = $gameParty.members()[index];
  4.     var rect = this.itemRect(index);
  5.     this.changePaintOpacity(actor.isBattleMember());
  6.     this.drawActorFace(actor, rect.x + 1, rect.y + 1, Window_Base._faceWidth, Window_Base._faceHeight);
  7.     this.changePaintOpacity(true);
  8. };
复制代码
我们看到,主要是
drawActorFace函数绘制了脸图,我们只要把他改成绘制行走图即可,我们知道脸图的文件名称是Faces,因此我们猜测绘制行走图的函数名称是drawActorCharacter,我们首先找一下drawActorFace是在哪里定义的,我们在他的定义处搜索,大概就能搜到。
我们在Window_MenuStatus的父类Window_Selectable中没有搜到,那么我们去Window_Selectable的父类Window_Base中搜索,发现珂以搜到drawActorFace,我们再试图搜索drawActorCharacter,发现确实可以搜到,得到以下结果:
  1. /**绘制角色人物 */
  2. Window_Base.prototype.drawActorCharacter = function(actor, x, y) {
  3.     this.drawCharacter(actor.characterName(), actor.characterIndex(), x, y);
  4. };
复制代码
我们看到,和
drawActorFace不同的是,drawActorCharacter没有最后的长和宽两个参数,因此我们将drawItemImage中的内容复制到drawItemCharacterImage函数中,并将drawActorFace改为drawActorCharacter,以及删掉后两个参数,然后运行看看效果吧。
但是当我们运行的时候,却并未看到行走图,还记得刚才的drawItem函数吗?它里面包含了要调用的函数,但是上面定义的drawItemCharacterImage函数没有在这个函数内,也就是说,我们只是定义了这个函数,但没有调用这个函数。
如果想要将行走图显示在菜单窗口,就需要重新定义drawItem这个函数。因此我们将drawItem这个函数复制过来,其他的内容先不动,只将drawItemImage函数的调用改为对drawItemCharacterImage的调用。你会写了吗?
下面是这部分的代码:
  1. //****************
  2. //美化角色状态窗口
  3. //****************
  4. Window_MenuStatus.prototype.drawItemCharacterImage = function(index){
  5.     var actor = $gameParty.members()[index];
  6.     var rect = this.itemRect(index);
  7.     this.changePaintOpacity(actor.isBattleMember());
  8.     this.drawActorCharacter(actor, rect.x + 1, rect.y + 1 );
  9.     this.changePaintOpacity(true);
  10. };
  11. Window_MenuStatus.prototype.drawItem = function(index) {
  12.     this.drawItemBackground(index);
  13.     this.drawItemCharacterImage(index);
  14.     this.drawItemStatus(index);
  15. };
复制代码
如果你的运行结果是菜单上只有三个人物的半个行走图,像这样:

那你就写对了,和脸图描绘不同的是,行走图的描绘中指定的x,y值是的行走图底部中点的坐标,因此我们看不到第一个人物的行走图,并且只能看到其余3个人物的半个行走图。
目前我不打算先改动描绘图片的x,y坐标值,因为我们现在还没有把选项分布改成预览图的样子。
我们看到,控制选项位置的是一个叫rect的东西,他的x,y坐标决定选项的位置。
因此我们对rect的创造函数itemRect进行分析。
Window_MenuStatus中并没有搜到这个函数,因此我们去他的父类Window_Selectable中搜索,得到以下结果:
  1. /**项目矩形 */
  2. Window_Selectable.prototype.itemRect = function(index) {
  3.     //设置新矩形
  4.     var rect = new Rectangle();
  5.     //最大列数
  6.     var maxCols = this.maxCols();
  7.     //矩形宽 = 项目宽
  8.     rect.width = this.itemWidth();
  9.     //矩形高 = 项目高
  10.     rect.height = this.itemHeight();
  11.     //矩形x =  索引 % 最大列数  * (矩形 宽 + 间隔() ) - 滚动x
  12.     rect.x = index % maxCols * (rect.width + this.spacing()) - this._scrollX;
  13.     //矩形y =  索引/ 最大列数  * 矩形高 - 滚动y
  14.     rect.y = Math.floor(index / maxCols) * rect.height - this._scrollY;
  15.     //返回矩形
  16.     return rect;
  17. };
复制代码

在当前的菜单中,选项只有
1列,那么最大列数为1,但是按照我们的要求,最大列数因为4,所以我们将Window_MenuStatus的最大列数改为4
  1. Window_MenuStatus.prototype.maxCols = function() {
  2.     return 4;
  3. };
复制代码

然后运行下看看效果

啊,字都重叠到一起了如果你和我一样,那么就写对了。
我们在选项中不需要绘制人物的信息,只需要绘制行走图,因此将drawItem中的this.drawItemStatus(index);删掉。
我们看到画面中没有行走图,原因刚才说了,是因为我们的原点是人物行走图下边缘的中点,想要让他显示在画面中,横向显示在正中,就需要对x,y坐标进行调整。
我们在itemRect中看到,rect除了有x,y属性以外,还有heightwidth两个属性,分别对应了选项块的长和宽,因此,我们只要将行走图绘制的x坐标加上rect宽度的一半即可;而高度方面与宽度相同,需要将y加上rect的高度的一半,与宽度不同的是,如果我们想要让行走图显示在选项的中间,还要把y坐标加上半个行走图的长度也就是18,因为指定的点在行走图的下边缘。
改好后,就珂以看看效果了!代码如下,运行图我就不放了。
  1. //****************
  2. //美化角色状态窗口
  3. //****************
  4. Window_MenuStatus.prototype.drawItemCharacterImage = function(index){
  5.     var actor = $gameParty.members()[index];
  6.     var rect = this.itemRect(index);
  7.     this.changePaintOpacity(actor.isBattleMember());
  8.     this.drawActorCharacter(actor, rect.x + rect.width / 2 , rect.y + rect.height / 2 + 18 );
  9.     this.changePaintOpacity(true);
  10. };
  11. Window_MenuStatus.prototype.drawItem = function(index) {
  12.     this.drawItemBackground(index);
  13.     this.drawItemCharacterImage(index);
  14. };
  15. Window_MenuStatus.prototype.maxCols = function() {
  16.     return 4;
  17. };
复制代码

之后,我们在游戏数据库里面再加上
4个人物,记得在数据库-系统-初始人物那里把新加上的四个人物加到初始队伍里,便于代码修改以后的效果测试(大致效果见最上面的预览图)。
然后,我们开始着手研究xy坐标的位置问题。
drawActorCharacter的参数我们看到,选项的位置是通过rect的属性决定的,而rect是通过itemRect函数创造的。
因此我们可以通过对itemRect函数进行修改,来实现将后四个选项的y坐标移动到窗口最底部。
通过搜索,在Window_Selectable中找到其定义:
  1. /**项目矩形 */
  2. Window_Selectable.prototype.itemRect = function(index) {
  3.     //设置新矩形
  4.     var rect = new Rectangle();
  5.     //最大列数
  6.     var maxCols = this.maxCols();
  7.     //矩形宽 = 项目宽
  8.     rect.width = this.itemWidth();
  9.     //矩形高 = 项目高
  10.     rect.height = this.itemHeight();
  11.     //矩形x =  索引 % 最大列数  * (矩形 宽 + 间隔() ) - 滚动x
  12.     rect.x = index % maxCols * (rect.width + this.spacing()) - this._scrollX;
  13.     //矩形y =  索引/ 最大列数  * 矩形高 - 滚动y
  14.     rect.y = Math.floor(index / maxCols) * rect.height - this._scrollY;
  15.     //返回矩形
  16.     return rect;
  17. };
复制代码

因为
Window_Selectable是很多选项窗口的父类,所以我们不能直接修改他的itemRect函数。
因此我们珂以在Window_MenuStatus窗口中重新定义一个同名方法,这样不仅避免了对Window_Selectable的子类造成影响,还珂以省去修改Window_MenuStatus中其他需要调用这个方法的语句的麻烦。
因此我们在Window_MenuStatus中重新创建一个itemRect方法,将原方法复制过来,然后进行修改。
我们看到,如果想让后四个角色的显示位置调整到底部,珂以在后四个角色的rect.y加上两个rect.height(因为一页上显示4行选项)。判断当前的rect是否是后四个角色的,我们可以通过比较indexmaxCols的大小来确定角色的行数。
先自己写写代码,然后再对照下和我写的是否一样:
  1. Window_MenuStatus.prototype.itemRect = function(index){
  2.     var rect = new Rectangle();
  3.     var maxCols = this.maxCols();
  4.     rect.width = this.itemWidth();
  5.     rect.height = this.itemHeight();
  6.     rect.x = index % maxCols * (rect.width + this.spacing()) - this._scrollX;
  7.     rect.y = Math.floor(index / maxCols) * rect.height - this._scrollY;
  8.     if( index >= maxCols )rect.y+=rect.height * 2;
  9.     return rect;
  10. };
复制代码

其实,我们只需修改
rect.y那一行为
  1. rect.y = Math.floor(index / maxCols) * rect.height * 3 - this._scrollY;
复制代码

就不用加
if那行了。
原理是第一行的角色的Math.floor(index /maxCols)结果为0,第二行的角色的Math.floor(index / maxCols)结果为1,所以只有第二行角色的rect.y增加了rect.height * 2
现在我们运行一下游戏感觉哪里有些不对——选项的高度太高了
解决的方法是增加一页窗口的可见行数,在Window_MenuStatusdrawItem上面,我们发现了以下函数:
  1. /**可见行数目 */
  2. Window_MenuStatus.prototype.numVisibleRows = function() {
  3.     return 4;
  4. };
复制代码

我们只要把他的行数改
7即可。这里需要注意的是,记得将rect.y改为
  1. rect.y = Math.floor(index / maxCols) * rect.height * 3 - this._scrollY;
复制代码

如果你是用的if,那么将if那行改为
  1. if( index >= maxCols )rect.y+=rect.height * ( this.numVisibleRows() - 2 );
复制代码

效果是一样的。
最后运行下看下结果。

到这里,我们就写完了选项处理的部分,那么这期教程就写道这里吧(貌似只写完了上次预告的1/3)。
后记
这篇教程没完成上次预告的内容
着实抱歉
下期教程,基本上就能完工啦!
效果和预览图上就会一样了。
感谢大家的支持!



作者: xjzsq    时间: 2019-7-12 12:54
本帖最后由 xjzsq 于 2019-7-12 14:03 编辑

占坑待施工
(代码格式问题已修复)
作者: legendxi    时间: 2019-11-26 23:15
精品!!谢谢楼主!可以出一个系列了都!!!




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1