Project1

标题: 【渣作品】RGSS3教程(二) 菜单的美化【2013.8.2-16:40完结】 [打印本页]

作者: 945127391    时间: 2013-7-30 09:03
标题: 【渣作品】RGSS3教程(二) 菜单的美化【2013.8.2-16:40完结】
本帖最后由 945127391 于 2013-8-2 16:46 编辑

看前须知
一.本教程提供给一些已经有一点脚本基础的学习者们(至少你要知道变量啊函数啊什么的),如果你没有达到,那我希望你打开着VA的F1文档来看这篇教程;
二.如果你已经有一点脚本基础了,我也希望你打开着F1来看,因为我将在以下的教程中引用到许多F1里的内容;
三.本教程不会太多的说一些理论性的东西(比如浮点数是什么啊之类的),免得说多错多= =;
四.看这个教程需要知道如何描绘窗口内容,因为我不会费时间多说这方面的内容;
五.如果有什么疑问或者发现了什么错误之处,又或者有什么意见或建议,欢迎回复,毕竟我也是个学习者而已;
六 . 此教程有点长,大家可以分P看,毕竟作者也是分P写的= =。
前言
那个……额……上次似乎有点偷懒,教程写到后面就烂尾了= =,所以在此向大家道个歉。
于是现在又发一个给大家,希望能给大家带来帮助的说!
首先,本次教程的目的就是把菜单给稍微美化一下,不过素材什么的……在下美工无力啊!
好了,下面就开始教程的正文。
关于场景(Scene)
所谓的“场景”,就是负责各种画面处理的类,你可以把它看做是一个把许多东西集合在一起的整体。
基本上,我们在游戏里看到的都是一个个场景,标题是一个场景(Scene_Title)、地图是一个场景(Scene_Map)、菜单也是一个场景(Scene_Menu)。而一个个连接起来的场景,就构成了我们在游戏中的画面切换。
而由于这次要修改的是菜单画面,所以要修改Scene_Menu这个类。请在Main之前新建一个脚本页,里面输入以下语句:
  1. class Scene_Menu < Scene_MenuBase
  2. end
复制代码
这样子的话,其实是对游戏里的菜单场景造不成任何影响的。
学过RGSS2的同学可能会有疑问了:为什么被继承的父类是Scene_MenuBase而不是Scene_Base呢?
这是RGSS3相对于RGSS2的变化之一——多了一个Scene_MenuBase,实际上这个类是把原本应该在Scene_Base里的截图背景相关的一些东西挪过来了。
那继承Scene_BaseScene_MenuBase有什么区别呢?
一般来说,如果你不需要该场景的背景是地图画面的截图的话,就可以继承Scene_Base(如标题画面),否则就要继承Scene_MenuBase
此外,Scene_MenuBase里还有快速切换角色——在状态画面、装备画面或者技能画面中按下键盘上的Q键或者W键可以快速切换到上一个角色或者下一个角色的处理,这里就不多说了。
在场景类里,有几个方法是尤为重要的,他们分别是:
  1. start
  2. post_start
  3. update
  4. pre_terminate
  5. terminate
复制代码
无论多么复杂的场景类,都离不开这几个方法。
以下就是关于这几个方法的说明:
名字
说明
start
用来定义进入场景时的处理,一般显示窗口或者精灵什么的都在这里定义。
post_start
用来定义进入场景后的处理,比如窗口开启的动画(参见标题画面的选项窗口)。
update
用来定义更新画面时的处理,一般刷新精灵之类的都会在这里定义。
pre_terminate
用来定义释放画面前的处理,比如窗口关闭的动画(参见标题画面的选项窗口)。
terminate
用来定义释放画面(退出场景)时的处理,一般用于精灵类的释放。
这里的解释比较简(han)略(hu),因为接下来就要用到这些方法——的其中三个。
好吧其实这里不需要post_start以及pre_terminate,所以就不说这两个家伙了。
接下来,在刚刚定义的Scene_Menu里加上以下几句话:
  1. #----------------------------------------------------------------------------
  2. # * 开始处理
  3. #----------------------------------------------------------------------------
  4. def start
  5.   super
  6. end
  7. #----------------------------------------------------------------------------
  8. # * 更新画面
  9. #----------------------------------------------------------------------------
  10. def update
  11.   super
  12. end
  13. #----------------------------------------------------------------------------
  14. # * 释放画面
  15. #----------------------------------------------------------------------------
  16. def terminate
  17.   super
  18. end
复制代码
这个是把Scene_Menu里的startupdateterminate三个方法给重定义了一遍,相信不需要我详细解释了吧。
然后按确定保存更改,测试游戏,进入菜单画面。如果你看到了下图的样子,就说明你改对了。

按一下上下左右……没反应!
当然,我们看到的只是一个截图背景罢了,只是一张图片。
现在退出测试,开始完善这个菜单。
窗口类(Window)相关
虽然我写菜单都是从选项窗口开始写起的,但这次从容易的先开始:金钱窗口。
先新建一个脚本页,里面输入以下内容:
  1. class Window_NewGold < Window_Base
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super(0, 368, 160, 48)
  7.     refresh
  8.   end
  9.   #----------------------------------------------------------------------------
  10.   # * 描绘内容
  11.   #----------------------------------------------------------------------------
  12.   def refresh
  13.     draw_icon(262, 0, 0)
  14.     draw_currency_value($game_party.gold, Vocab::currency_unit, 28, 0, self.contents.width-32)
  15.   end
  16. end
复制代码
啥,你不知这是啥意思?
好吧,其实我某个乱糟糟的教程里已经说过了——除了draw_currency_value这个方法。
这是在Window_Base里定义的一个专门用于描绘货币的方法,与直接用draw_text来描绘的区别如下图:
← draw_curreney_value($game_party.gold, Vocab::currency_unit, 4, 0, self.contents.width - 8)
← self.contents.draw_text(4, 0, self.contents.width - 8, 24, "#{$game_party.gold}"+Vocab::currency_unit)
可以见到,用draw_currency_value的话语句比较短,效果也比较漂亮。
此方法带有5个必填参数,分别是:
draw_currency_value(value, unit, x, y, width)
value - 要描绘的的数量
unit - 金钱的单位
x - 描绘位置的左上角x坐标
y - 描绘位置的左上角y坐标
width - 描绘内容的最大宽度

此方法的定义在Window_Base的第556~565行,即:
  1. #--------------------------------------------------------------------------
  2. # ● 绘制货币数值(持有金钱之类的)
  3. #--------------------------------------------------------------------------
  4. def draw_currency_value(value, unit, x, y, width)
  5.   cx = text_size(unit).width
  6.   change_color(normal_color)
  7.   draw_text(x, y, width - cx - 2, line_height, value, 2)
  8.   change_color(system_color)
  9.   draw_text(x, y, width, line_height, unit, 2)
  10. end
复制代码
其次,Vocab::currency_unit是啥?
这是在用语模块(Vocab)定义的一个方法,其本质是获取数据库里,系统页面的“货币单位”里的内容。
Voacb里还定义了很多其他的用语,以后我们会用到,现在先放下。
最后一个问题:为什么不直接叫Window_Gold而要叫Window_NewGold呢?
为了避免混乱——默认的脚本里本来就有一个叫Window_Gold的窗口,一旦我们用了这个窗口名就代表对其原始的方法进行了覆盖。更糟糕的是,这个窗口不仅在菜单画面中被调用,在地图画面、商店界面(Scene_Shop)中都有他的身影,如果我们对其进行修改,会牵一发而动全身导致地图画面以及商店界面的变动。而将名字改为Windo_NewGold的话,这两个窗口就是互不相关的了。
当以后你的脚本复杂起来以后,你就要更加考虑清楚这种问题,否则可能会出现不可预知的BUG,甚至导致世界线变动!(众:喂!你个中二!)
好了,现在保存脚本测试游戏打开菜单……
.
..
...
....
.....
……
众:为什么没有显示你是不是在耍我们!
先不要激动,那个,你没有告诉程序在哪里显示什么时候显示你叫它怎么显示?
其实RGSS3中显示窗口的方法与RGSS2中一样,不过我们此处先做一个小手脚:在start里头加入:
  1. create_gold_window
复制代码
然后在Scene_Menu里加入:
  1. #---------------------------------------------------------------------------
  2. # * 创建金钱窗口
  3. #----------------------------------------------------------------------------
  4. def create_gold_window
  5. end
复制代码
好了,这就完成了一个方法的调用了。
接下来,在create_gold_window里加入:
  1. @gold_window = Window_NewGold.new
复制代码
这样就创建了一个Window_NewGold的实例并且将其代入了@gold_window中,也就是显示了这个窗口。
显示其他窗口也是这样——只不过要把变量名及窗口名改一改,有参数的时候在new后面用括号把参数值按顺序括起来。
记得要遵循同一场景内,不同的窗口不能用同名变量
这里,我们先看看效果:

嗯……很不错的样子呢……
但是,有人可能有疑问了——为什么不直接在start中创建,要创建一个方法然后调用呢?
这个涉及到兼容性的问题。试想一下,如果有一个外挂脚本要使菜单里的金钱窗口消失,那么你没有用我说的方法而是直接在start里创建了的话,那他就要把整个start修改,这样就会牵连到其他窗口,导致BUG;但如果你使用了我说的方法,那么这个外挂脚本就只需要将create_gold_window重定义为空的方法就可以了。
提高兼容性,或许在自己用而不打算对外发布的脚本中没设么要紧的,但要是你写一个脚本要对外发布的话,兼容性则是很重要的东西——直接关系到你的脚本的实用性。
选项窗口相关
接下来,我们就要开始写菜单选项了,不过,要加点图标上去。
RGSS3中,比较大的改动就是在选项窗口里,不仅把竖直选项窗口和横向选项窗口分成了两个选项窗口,而且还不能像以前一样直接用Window_Command什么的创建,这就意味着,无论你的选项窗口多简单,都要新定义一个类。
此处我不对这种改动评论,好或不好就见仁见智了。
现在,我们在Main前新建一个脚本页,在里面写上:
  1. class Window_MenuCommand < Window_Command
  2. end
复制代码
好了,这里为什么用回Window_MenuCommand这个名字呢?
因为就目前来说,只有Scene_Menu一处调用到了Window_MenuCommand,若用回原来的名字,不仅害处不大,反而还有好处——继续看下去吧。
关于选项窗口,也有几个重要的方法,这里就不列表了,因为列表很麻烦= =
然后在Window_MenuCommand里加入以下语句:
  1. #----------------------------------------------------------------------------
  2. # * 初始化
  3. #----------------------------------------------------------------------------
  4. def initialize
  5.   super(0, 0)
  6. end
  7. #----------------------------------------------------------------------------
  8. # * 获取窗口宽度
  9. #----------------------------------------------------------------------------
  10. def window_width
  11.   return 160
  12. end
  13. #----------------------------------------------------------------------------
  14. # * 创建命令列表
  15. #----------------------------------------------------------------------------
  16. def make_command_list
  17. end
复制代码
然后再把他显示出来——先自己尝试着写一下,再看看和下面的有没有区别吧!

Scene_Menu里添加以下语句:
  1. #----------------------------------------------------------------------------
  2. # * 创建菜单选项
  3. #----------------------------------------------------------------------------
  4. def create_menu_command
  5.   @menu_command = Window_MenuCommand.new
  6. end
复制代码
然后再往start里添加:
  1. create_menu_command
复制代码
现在来看看效果吧:

咦,为什么菜单选项里一个选项都没有呢?
因为我们还没有设定里面的选项。
但是我们要在这里先把另一个说了——window_width.
RGSS3里的选项窗口,其初始的宽度与长度都是以window_width以及window_height这两个方法的返回值来决定的,当然,在这之后你可以在外部使用widthheight这两个属性来变更窗口的宽度与长度。
了解了这一点以后,我们开始向里面添加命令。
RGSS3中,给选项窗口添加命令的指令最终都要被make_command_list这个方法调用,所以我们先在make_command_list里加入以下指令:
  1. add_command(Vocab::item,   :item,   true)
复制代码
然后看效果:

出现一个了,因为我们只添加了一个。
关于add_command这个方法,这是在Window_Command里定义一的一个方法,共带有四个参数:两个必填参数,两个可选参数,相关的解释如下:
add_command(name, symbol, [enabled], [ext])
name - 选项显示的名称
symbol - 选项唯一的“标签”,注意必须是Symbol类的对象,也就是:开头的
enabled - 有效情况,true为有效,false为无效
ext - 备注

默认脚本里的说明如下:
add_command(name, symbol, [enabled], [ext])
name    : 指令名称
symbol  : 对应的符号
enabled : 有效状态的标志
ext     : 任意的扩展数据

对于symbol这个属性,我们可以把它看做是选项的一个标签,当然可以任意起,不过这个东西是用于识别各个选项的,对后期按确定键的处理起到至关重要的作用。
然后把焦点关注到Vocab::item这里,
还记得我们说过的Vocab::currency_unit么?这个也是一样,是获取数据库里,用语事件页中物品指令中“物品”的内容。
这里,我列一个表格,让大家更方便的了解这一些方法……不过只有菜单指令哦~
方法
对应的用语
Vocab::item
Vocab::skill
Vocab::equip
Vocab::status
Vocab:: formation
Vocab::save
Vocab::game_end
     
其实剩下的几个选项也不是很难——在make_command_list里加上:
  1. add_command(Vocab::skill,     :skill,  true)
  2. add_command(Vocab::equip,     :equip,  true)
  3. add_command(Vocab::status,    :status, true)
  4. add_command(Vocab::formation, :formation, true)
  5. add_command(Vocab::save,      :save, true)
  6. add_command(Vocab::game_end,  :game_end)
复制代码
嘛……找到规律了吧……
效果如下:

现在呢,我们要考虑一件事——选项的有效状态
众所周知,当队伍内没有队员的时候,技能、装备以及状态都是无效的,这时候就需要用到add_command里的enabled属性了
首先,把:
  1. add_command(Vocab::skill,     :skill,  true)
  2. add_command(Vocab::equip,     :equip,  true)
  3. add_command(Vocab::status,    :status, true)
复制代码
改成:
  1. add_command(Vocab::skill,     :skill,  !$game_party.members.empty?)
  2. add_command(Vocab::equip,     :equip,  !$game_party.members.empty?)
  3. add_command(Vocab::status,    :status, !$game_party.members.empty?)
复制代码
诶诶,那句!$game_party.members.empty?是神马意思?
先不去管那个!,单独看$game_party.members.empty?这一堆。
$game_party是储存了Game_Party的实例的全局变量;Game_Party是用来管理队伍的类,什么步数、金钱、持有物品都归他管;而members则是一个在Game_Party定义的方法,返回一个由Game_Actor组成的数组——也就是队伍中的队员。
empty?则是数组的的方法,是用来判断数组为不为空(无元素)的,F1的说法是:
若元素数为 0,返回 true。

而前面的!,就做到了把跟在它后面的值逆过来的效果——true变成falsefalse变成true
也就是说,当队伍中的人不为空的时候,$game_party.members.empty?返回false,受到前面的!的影响,变成了true——有效;
当队伍中的人为空的时候,$game_party.members.empty?返回true,受到前面的!的影响,变成了false——无效。
这样就就达到我们的效果了。
还有一个类似的地方,就是整队选项,他是在队员数量($game_party.members.size)小于或者等于1的时候才变成无效的,大家先试着写一下,再看看和下面的是否一样?

把:
  1. add_command(Vocab::formation, :formation, true)
复制代码
改成:
  1. add_command(Vocab::formation, :formation, $game_party.members.size > 1)
复制代码
有没有人改成:
  1. add_command(Vocab::formation, :formation, !$game_party.members.size <= 1)
复制代码
的呢?
其实上面这两句效果都一样,只不过上面的一句不用转这么多弯,不是么?
同时,在事件指令里有一个“启用/禁用整队”的指令,所以还要加点东西。
储存是否启用整队选项的变量叫做$game_system.formation_disabled,当你启用整队的时候,它就为false,否则为true
那么我们就把:
  1. add_command(Vocab::formation, :formation, $game_party.members.size > 1)
复制代码
改成:
  1. add_command(Vocab::formation, :formation, $game_party.members.size > 1 && !$game_system.formation_disabled)
复制代码
就好了。
P.S &&等价于and
现在来看看效果:
无队员:

一个队员:

两个队员:

哇咔咔,很完美的样子!
诶诶,等等……
哇啊啊啊啊啊啊!我忘记了物品!
物品似乎也是在空队伍的时候无效的……
不过,我很懒= =,不如你帮我写了好不TAT……
答案最后揭晓
但是,我们忽略了另外一个可能无效的选项——存档选项。
在事件指令里,有一个叫做“启用/禁用存档”的指令,我们要根据他来决定存档选项有没有效。储存是否启用存档的变量叫做$game_system.save_disabled,当启用存档时为false,否则为true
像之前的一样,把
  1. add_command(Vocab::save,      :save, true)
复制代码
改成
  1. add_command(Vocab::save,      :save, !$game_system.save_disabled)
复制代码
请自行测试效果(喂!)。
好了,你确定你记住刚才学到的东西了么?
真的记住了?
好……
3、2、1……
window_widthmake_command_list这两个方法给删了!
你现在的表情是这样的么→
好了,我们测试游戏打开菜单……

……
菜单选项居然没有消失!
好感动TAT ←滚……
为什么会这样呢……
请看这个类的名字——Window_MenuCommand
默认脚本里也有一个Window_MenuCommand
也就是说,现在其实是在调用默认脚本里的make_command_list
这就是我说的好处了——可以省下很多时间和精力。
接下来,我们要给选项加上图标了。
选项的内容描绘方式有点特殊,他是先定义一个描绘单独选项的方法(draw_item),再多次调用这个方法来描绘整个窗口的内容。
所以我们这次不直接改refresh,而是去改draw_item
不过,最一开始做的应该是设定选项对应的图标ID。
诶,我刚才没叫你删initialize吧……所以我们就在我们新建的Window_MenuCommandinitialize里加入:
  1. @command_icon_index = [260, 96, 170, 16, 280, 375, 12]
复制代码
切记:要放在super之前!
这一串数字,就是图标ID。但是,是哪个选项对应哪个ID呢?
比如说,物品是第一个选项,它对应的ID就是第一个(260);技能是第二个选项,它对应的ID就是第二个(96)……以此类推。
然后,我们在Window_MenuCommand里新建一个draw_item方法,即:
  1. def draw_item(index)
  2. end
复制代码
现在看到了么,其实这个方法是带有一个参数——index的,这个用来告诉此方法:我要描绘的选项是第几个选项。
现在,我们测试游戏进入菜单,如果像下面这样,你就做对了——因为没有任何内容被描绘上去。

然后接下来就是重头戏了。
选项窗口有两个很方便的方法:item_rectitem_rect_for_text。它们是由Window_Selectable定义的,作用是你给出index,他就可以帮你自动算出描绘这个选项的范围。
然而,这两个有什么不同呢?
← 此图中,白色+红色为item_rect获取的范围,红色则是item_rect_for_text获取的范围。
你可以这样理解——item_rect是选择框的描绘范围,item_rect_for_text则是描绘文字的范围,而他们两个都带有相同的一个必填参数——index
这样的话,我们可以先在draw_item里加上这样一句话:
  1. rect = item_rect_for_text(index)
复制代码
这样,就获得了这次描绘的范围了,接下来的描绘都是在该范围内的。
接下来,我们开始描绘图标:在draw_item里添上这样一句话:
  1. draw_icon(@command_icon_index[index], rect.x, rect.y)
复制代码
然后看看效果:

嘛,图标描绘出来了,接下来到文字了。
原本来说,文字可以直接描绘在整个rect里的,但现在多了一些图标,所以x坐标就要向右移动24px,宽度也要相应的减少24px,否则文字会描绘不完全。
所以就在draw_item里添上这样一句话:
  1. self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
复制代码
现在看看效果吧!

成……成功了的说!
不过,那个command_name(index)看着很面生啊,是用来干啥的?
command_name(index)Window_Command里定义的方法,是用来获取index号选项的名字的。
不过呢……在下好像忽略了一点东西……
是的,选项无效时的半透明效果要是这样改的话就不见了。
我给个小提示给你,你试一下自己写,然后再对照一下下面的吧!
Tips:command_enabled?(index)         获取index号指令的有效状态,true为有效,false为无效。

draw_item里的
  1. draw_icon(@command_icon_index[index], rect.x, rect.y)
  2. self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
复制代码
改成
  1. draw_icon(@command_icon_index[index], rect.x, rect.y, command_enabled?(index))
  2. self.contents.font.color.alpha = (command_enabled?(index) ? 255 : 160)
  3. self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
复制代码
就好了
如果你写成
  1. if command_enabled?(index) == true
  2.   draw_icon(@command_icon_index[index], rect.x, rect.y, true)
  3.   self.contents.font.color.alpha =255
  4.   self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
  5. else draw_icon(@command_icon_index[index], rect.x, rect.y, false)
  6.   self.contents.font.color.alpha =160
  7.   self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
  8. end
复制代码
也没有什么大问题的说……只不过,脚本看起来长了很多不是么?
好了好了,附上预览图一枚

最后,我们的Window_MenuCommand是这样的:
  1. class Window_MenuCommand < Window_Command
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     @command_icon_index = [260, 96, 170, 16, 280, 375, 12]
  7.     super(0, 0)
  8.   end
  9.   #----------------------------------------------------------------------------
  10.   # * 描绘选项
  11.   #----------------------------------------------------------------------------
  12.   def draw_item(index)
  13.     rect = item_rect_for_text(index)
  14.     draw_icon(@command_icon_index[index], rect.x, rect.y, command_enabled?(index))
  15.     self.contents.font.color.alpha = (command_enabled?(index) ? 255 : 160)
  16.     self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
  17.   end
  18. end
  19.   
复制代码
而Scene_Menu是这样的:
  1. class Scene_Menu < Scene_MenuBase
  2.   #----------------------------------------------------------------------------
  3.   # * 开始处理
  4.   #----------------------------------------------------------------------------
  5.   def start
  6.     super
  7.     create_menu_window
  8.     create_gold_window
  9.   end
  10.   #----------------------------------------------------------------------------
  11.   # * 更新画面
  12.   #----------------------------------------------------------------------------
  13.   def update
  14.     super
  15.   end
  16.   #----------------------------------------------------------------------------
  17.   # * 释放画面
  18.   #----------------------------------------------------------------------------
  19.   def terminate
  20.     super
  21.   end
  22.   #----------------------------------------------------------------------------
  23.   # * 创建菜单选项
  24.   #----------------------------------------------------------------------------
  25.   def create_menu_window
  26.     @menu_command = Window_MenuCommand.new
  27.   end
  28.   #----------------------------------------------------------------------------
  29.   # * 创建金钱窗口
  30.   #----------------------------------------------------------------------------
  31.   def create_gold_window
  32.     @gold_window = Window_NewGold.new
  33.   end
  34. end
复制代码
你写对了吗?
你可能会很诧异我说了这么多,最终脚本只有这么少。是的,脚本就是这样,看起来很少其实很多内涵,就像人生……(众:PIA!你在这里装什么博学!)
菜单选项就这样告一段落了,接下来要写的是人物选单——没错,就是那个占了画面约大部分,画着几个人头的东东……
现在开始写菜单的人物状态栏……但是不是原版的那种。
不管怎么说,第一步总是要新建窗口然后显示的——在Main前新建一个脚本页,在里面输入:
  1. class Window_NewMenuStatus < Window_Command
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super(160, 0)
  7.   end
  8.   #----------------------------------------------------------------------------
  9.   # * 获取窗口的宽度
  10.   #----------------------------------------------------------------------------
  11.   def window_width
  12.     return 384
  13.   end
  14.   #----------------------------------------------------------------------------
  15.   # * 获取窗口的高度
  16.   #----------------------------------------------------------------------------
  17.   def window_height
  18.     return 416
  19.   end
  20.   #----------------------------------------------------------------------------
  21.   # * 更新窗口
  22.   #----------------------------------------------------------------------------
  23.   def update
  24.     super
  25.   end
  26.   #----------------------------------------------------------------------------
  27.   # * 生成指令列表
  28.   #----------------------------------------------------------------------------
  29.   def make_command_list
  30.   end
  31. end
复制代码
我相信这些方法都不需要我一个一个去解释了吧,之前都说过了的= =
然后,我们就要把他显示出来了。
先试着自己写一下怎么样?

Scene_Menu里加入:
  1. def create_menustatus_command
  2.   @menustatus_command = Window_NewMenuStatus.new
  3.   @menustatus_command.active = false
  4. end
复制代码
然后在start里加入:
  1. create_menustatus_command
复制代码
于是乎,显示出来以后的效果就是:

没错,旁边多出来的那个东西就是我们新建的窗口——很快,他就会漂亮起来……
现在里面还什么都没有——因为我们什么都没有写。
现在第一步——添加命令。
这次添加命令与往日不同,我们使用for...in...end循环来添加命令,主要原因是因为我们不知道玩家打开菜单时队伍里会有多少个人。
虽然人数实在可预估的范围内(0~8),但针对每一种可能性写不同的语句显然是不实际的。
而使用for...in...end是一种十分快捷的方式。
因此,我们在make_command_list里添加以下语句:
  1. for actor in $game_party.members
  2.   s = "a[#{actor.id}]"
  3.   add_command(actor.name, s.to_sym, !actor.death_state?, actor.id)
  4. end
复制代码
是的,就是这么简单的四句话。
在这一个循环中,actor这个变数不断地被代入为新的量——当第一次循环的时候,actor$game_party.members[0],第二次循环的时候,actor$game_party.members[1]……第n次循环的时候,actor$game_party.members[n-1]
而且我也说过,$game_party.members里都是Game_Actor里的实例,所以上述脚本的namedeath_state?以及id这三个方法都是Game_Actor的,但是请注意,death_state?是在Game_Actor的父类(Game_Battler)的父类Game_BattlerBase里定义的。这三个方法的作用分别是:
(假设下面的actor是$game_actors[x])
actor.id                      获取该角色的ID
actor.name               获取该角色的名称
actor.death_state?   获取该角色是否被附加了死亡状态

这些关于角色的东西,我们之后会说。
然后现在看看to_sym这个方法。
这是字符串类所定义的方法,作用是把一个字符串(Sring)对象转换为Symbol对象,比如:
  1. p "TZ".to_sym   # => :TZ
复制代码
【这种标准的F1格式你不会看不懂的……】
而F1文档的说明是这样的:
返回对应字符串的符号值(Symbol)。
使用 Symbol#id2name 获取对应符号的字符串。

那上面的这几句脚本究竟向Window_NewMenuStatus窗口传达了什么指令呢?
举个例子,如果某次循环中,循环到的角色是第五号角色,名字叫“No.5”,活着的话,它向Window_NewMenuStatus传达的指令就是下面这句:
  1. add_command("No.5", :a[5], true, 5)
复制代码
就是这么简单罢了。
然后,让我们看看效果:

阿咧……怎么像个菜单一样?
这时候,我们就要像之前一样修改一下draw_item了。不过在此之前,我们先要把选择框变一变。
先将draw_item重定义——在Window_NewMenuStatus里加入:
  1. def draw_item(index)
  2. end
复制代码
现在,我们说一下修改选项框的思路:
之前我跟大家说过,item_rect是用来获取选项框的的范围的,也就是说,选项框的大小、位置都由它的返回值决定。所以我们只需要改动item_rect这个方法就好了。
Window_NewMenuStatus里加入:
  1. def item_rect(index)
  2. end
复制代码
这样就把这个方法重定义了,现在进入游戏的话会出错哦。
因为某些地方调用了这个方法,但是这个方法现在被修改了,也就是说他没有按系统预期的那样返回一个矩形类(Rect),就出错了。
现在,我们来看看我们需要把选项框改成怎样。
嘛,我的想法是这样的:在窗口的上方按顺序显示队伍中前四个人的行走图,下方显示后四个的行走图,然后选项框就在这几个行走图之间移动。中间就根据玩家目前的选择,显示对应角色的状态。
啥?没听懂?那就看下去吧。
根据之前的设定,我们的选项框的宽度应该是窗口内容宽度的1/4,高度的话……就决定为48吧!
而它的x坐标和y坐标要根据index而变化:当index<4(0~3)的时候,选择框在上面,所以x为选择框的宽度*indexy为0;
index>=4(4~7)的时候,选择框在下面,也就是x为现则框的宽度*(index-4),y为窗口高度-选择框的高度;
所以,我们就在item_rect里头加入如下脚本:
  1. rect = Rect.new
  2. rect.width = self.contents.width / 4
  3. rect.height = 48
  4. rect.x = (index < 4 ? index * rect.width : (index - 4) * rect.width)
  5. rect.y = (index < 4 ? 0 : self.contents.height - rect.height)
  6. return rect
复制代码
【看不懂(index < 4 ? 0 : self.contents.height - rect.height)这句话里的():以及?是什么意思么?回去F1复习一下吧!】
这里,我要给大家说一下矩形类(Rect)。
关于这个类,之前也出现过。简单地说,这是一个限定范围的类。只要决定了xywidthheight四个属性,就可以给某些操作限定范围——比如描绘文字、填充颜色,甚至是限定精灵的可视范围。
矩形类我们不可以直接看到,但是它是一个十分有用而且方便的类。
当你要新建一个矩形类的实例的时候,可以使用以下脚本:
Rect.new(x, y, width, height)
Rect.new
创建一个位置在(x, y),宽度为width,高度为height的矩形类实例。

如果没有填参数的话,默认为(0, 0, 0, 0)
现在你应该看得懂上面的那几句脚本里第一句是什么意思了吧。
而矩形类有四个属性:xywidthheight。顾名思义,他们分别是获取或改变矩形的x坐标、y坐标宽度和高度的。
思考一下,为什么上面的脚本里要先修改矩形的宽度和高度,再修改xy坐标?
接下来,我们看一下效果。
先把Scene_Menu里,create_menustatus_command方法中的那一句@menustatus_command.active = false变成注释,然后进入游戏看效果。
这时候,按下上下键,右边的框口的选择框会移动。
【此时,菜单的选项框也会跟着移动,正常来说是不会这样的,只不过我们现在是为了测试效果而已。】
index < 4的时候(图中的index=0):

index >= 4的时候(图中的index=4):

现在,把@menustatus_command.active = false的注释取消。接下来我们要描绘选项了。
选项是由一个个人物的行走图组成的,我们就要把人物行走图画上去。
原本直接描绘到窗口的contents上是可以的,但在这里我不这么做,而是将其先描绘到一个空白的的位图类(Bitmap)对象上,再将这个位图对象描绘到窗口上。
于是,我们就要先新建一个位图类对象——在draw_item里添加这样两句话:
  1. rect = item_rect_for_text(index)
  2. b = Bitmap.new(rect.width, rect.height)
复制代码
这样就新建了一个位图对象了。
现在,我们要在这个位图对象上描绘行走图。
实际上,描绘行走图并不难,难就难在要描绘的范围的计算——行走图分两种:文件名是$开头的和不是$开头的,而这两种行走图描绘范围的计算是有一点不一样的。所以我们先设立这两种情况的条件分歧。
draw_item里加上以下的脚本:
  1. cn = $game_actors[@list[index][:ext]].character_name
  2. ci = $game_actors[@list[index][:ext]].character_index
  3. cb = Cache.character(cn)
  4. if cn[0] == "[        DISCUZ_CODE_43        ]quot;
  5. else
  6. end
复制代码
【cn = character name;ci = character_index;cb = character bitmap】
首先,@list[index][:ext]这句话获取的是现在描绘的这个选项的扩展数据。根据之前的脚本,这个选项的扩展数据就是这个队员的角色ID。
其次,character_namecharacter_indexGame_Actor里定义的实例变量,分别是获取角色的行走图名称和序号。
接着,Cache是一个模块,用于快速载入图像。如何快速载入呢?当你第一次载入这个图像的时候,游戏会从文件里读取,然后储存到内存里;你再次载入这个图像的时候,它会直接从内存里拿出来,而不用从文件里再次读取。
character则是Cache模块里的一个方法,代表读取游戏文件目录下Graphics\Characters的图像。
最后,cn[0] == "$"这句话是用来判断角色的行走图文件名第一个字符是否为$的。
有的同学可能会问了:Window_Base里描绘行走图的方法里好像不是这样判断的啊?
是的,Window_Base中的draw_character方法里,用的是正则表达式一类的东西。但由于在下对正则表达式不太熟悉,所以就用了这个简单的方法。
如果有路过的大神的话,可以在楼下解释一下正则表达式什么的……
好了好了,现在要决定描绘范围了。
首先,第一种是$开头的,这种比较简单一点。因为是单个行走图,所以宽度就是行走图图片的宽度的1/3,高度就是行走图图片的1/4。
而我们要保留的那一块,就是第一行的第二个。所以x坐标是图片宽度的1/3,y坐标就是0。
根据我们上面的计算,我们在if cn[0] == "$"里加入以下脚本:
  1. cr = Rect.new
  2. cr.x = cb.width / 3
  3. cr.width = cb.width / 3
  4. cr.height = cb.height / 4
复制代码
【cr = character rect】
而文件名开头没有$的,就是4*2个行走图拼在一起的了,这时候就需要用到character index了。
宽度和高度没什么难的——宽度为图片宽度的1/12,高度为图片高度的1/8。
但是x坐标和y坐标就略微复杂一点了,详细的计算方法如下:
x = ci % 4 * (图片宽度 / 4) + 图片宽度 / 12
y = ci / 4 * (图片宽度 / 2)
所以我们在if cn[0] == "$"else里,加入以下脚本:
  1. cr = Rect.new
  2. cr.x = ci % 4 * (cb.width / 4) + cb.width / 12
  3. cr.y = ci / 4 * (cb.height / 2)
  4. cr.width = cb.width / 12
  5. cr.height = cb.height / 8
复制代码
其原理和描绘头像的计算方法很像。
接下来,就要进行描绘了。
draw_item里加入以下脚本:
  1. b.blt((b.width - cr.width) / 2, (b.height - cr.height) / 2, cb, cr)
  2. self.contents.blt(rect.x, rect.y, b, b.rect)
复制代码
这样就描绘出来了,现在让我们看看效果吧:

最后,把我们创建的图片都给释放掉,要不会白占内存,所以在draw_item里加入:
  1. b.dispose
  2. cb.dispose
复制代码
然后,我们就要考虑一件事——人物是否死亡。
在添加命令的时候,我们把人物是否死亡的状态储存在了选项的有效状态上,也就是说选项无效的话角色就是死亡的。
原本可以直接半透明描绘的,但是我不想这样,我要更加漂亮一点——死亡的角色用黑白来描绘。
这也是我为什么不直接把行走图描绘到窗口上的原因,因为可以直接在我们创建的位图对象上修改。
但是要怎么把彩色变成黑白呢?这里有一个简单的方法——逐个像素更改颜色。
这种方法对付小图的话是没有问题的,但是大图就不要这样了,要不卡出翔来可不怪我哦~
这个方法的原理是这样的:先获取某一个点的颜色,再将其RGB值的平均值算出来,最后将这个点的颜色的RGB值都改为刚刚算出来的平均值。
这样的话,就要用到位图类的两个方法了:
get_pixel(x, y)
获取指定像素点 (x, y) 的色彩(Color 色彩类 )。
set_pixel(x, y, color)
设定指定像素点 (x, y) 的色彩(Color 色彩类 )。

嗯,上面这两个方法的解释都来源于F1。
这里说一下色彩类(Color)。
色彩类就是一个代表颜色的类,他储存了关于颜色的四个属性:RGBA。
我们在填充颜色,或者改变文字颜色之类的都是要用到这个类的实例。
若你要创建色彩类的实例,可以用以下的脚本:
Color.new(red, green, blue[, alpha])
Color.new (RGSS3)
生成色彩对象。alpha 值省略时使用 255。
如果参数没有指定,则默认设定为(0, 0, 0, 0)。

色彩类有四个属性:redgreenblue以及alpha(填充度,可看做是透明度)。
回到我们的主题,刚才说到要逐点改变颜色,所以我们就在draw_item里面,b.blt((b.width - cr.width) / 2, (b.height - cr.height) / 2, cb, cr)之后,self.contents.blt(rect.x, rect.y, b, b.rect)之前加入以下这段脚本:
  1. unless command_enabled?(index)
  2.   for y in 0...b.height
  3.     for x in 0...b.width
  4.     end
  5.   end
  6. end
复制代码
这样的话,就组成了一个嵌套循环:先扫描第一行的像素,在扫描第二行的像素……
而这种写法,要循环3936次。
是不是看起来不太多?是的,这种小图是很少,但要是以RM的标准窗口大小——544*416来算的话,如果使用逐点扫描的方法,就要循环226304次,会十分卡,所以说这个方法不适合用于大图的原因就是这个。
接下来,我们就要获取该点的颜色了——还记得刚刚说的get_pixel方法吗?在第二个循环的里面加入以下脚本:
  1. c = b.get_pixel(x, y)
复制代码
此时,c就被带入了该点的颜色,然后我们再计算这个点颜色的RGB的数值的平均值,在第二个循环内继续加入:
  1. m = (c.red + c.green + c.blue) / 3
复制代码
最后,改变该点的颜色,在第二个循环内继续加入:
  1. b.set_pixel(x, y, Color.new(m, m, m, c.alpha))
复制代码
有没有发现这一句里面,后面的色彩类生成实例的时候,我没有让它的alpha值省略,而是取了该点原本的alpha,这是为了防止有一些行走图有半透明的地方,如果取缺省值的话,这些半透明的地方会全部变为不透明。
好了,测试结果吧!

唔,看起来不错
但是不知道你们有没发现,其实一个选项的范围内是有十分之多空白地方,也就是alpha为零的地方。这些地方即使计算了,描绘上去也是透明的,也就是说白白计算了。为了节省内存,我们把这些地方跳过。所以要在第二个循环里,c = b.get_pixel(x, y)之前加入:
  1. next if b.get_pixel(x, y).alpha == 0
复制代码
这样子的话,当这一个点是透明的时候,他就会直接跳过。
  ← 这一块的计算:没有加透明判断之前计算了3936次,加了透明判断以后计算了678次。
这种使系统跳过无谓的操作是很重要的,简单点说,就是可以“防卡”。
接下来,我们要在中间空白的地方画上角色的基本状态了。
这个倒是没什么难的——请尝试自己写。
Tips - 要新建一个方法哦~

好了,看看你写的是不是正确的吧:
先在Window_NewMenuStatus里加入:
  1. def draw_actor_status
  2.   actor = $game_actors[@list[@index][:ext]]
  3.   y = item_rect_for_text(0).height + 5
  4.   h = self.contents.height - 2 * (item_rect_for_text(0).height + 5)
  5.   self.contents.clear_rect(Rect.new(0, y, self.contents.width, h))
  6.   draw_actor_face(actor, 0, y)
  7.   draw_actor_name(actor, 0, y)
  8.   draw_actor_level(actor, 96, y)
  9.   draw_actor_nickname(actor, 168, y)
  10.   draw_actor_hp(actor, 96, y + line_height, self.contents.width - 96)
  11.   draw_actor_mp(actor, 96, y + 2 * line_height, self.contents.width - 96)
  12.   draw_actor_icons(actor, 96, y + 3 * line_height)
  13.   for i in 0...6
  14.     draw_actor_param(actor, 0, y + (4 + i) * line_height, i)
  15.   end
  16.   for i in 0...5
  17.     draw_item_name(actor.equips[i], self.contents.width / 2, y + (4 + i) * line_height) if actor.equips[i] != nil
  18.   end
  19. end
复制代码
然后在updaterefresh里都加入:
  1. draw_actor_status
复制代码
就好了。
这里有几句话需要解释一下:
首先:draw_actor_param是在Window_Base里定义的的一个用于描绘角色能力值的方法,有以下四个必填参数:
draw_actor_param(actor, x, y, param_id)    描绘角色的能力值
actor - 要描绘的角色
x - 描绘的x坐标
y - 描绘的y坐标
param_id - 描绘的能力值ID,数字和对应描绘的能力值如下:

数字
能力值
0
最大HP
1
最大MP
2
物理攻击
3
物理防御
4
魔法攻击
5
魔法防御
然后还有一个就是draw_item_name,这是在Window_Base里定义的一个描绘物品、装备或者技能的方法,可以想一下物品栏里的一个个物品就是用它描绘出来的。
它有以下五个参数,三个必填两个选填,分别是:
draw_item_name(item, x, y, [enabled], [width])
item - 要描绘的物品
x - 描绘的x坐标
y - 描绘的y坐标
enabled - 有效标志,默认为true;为false时半透明描绘
width - 描绘宽度,默认为172

最后,有木有看到actor.equips这东西。
这里的equips是在Game_Actor里定义的一个方法,用于获取主角目前的装备的数组,注意里面的元素都是实例化的——即$data_weapons[x]或者$data_armors[x],并不是数字而已。
现在,让我们把Scene_Menu中的create_menustatus_command里的@menustatus_command.active = false注释掉,然后进游戏里测试。


哇呼~成功了!
接下来要把这个窗口小小的优化一下。
还记得F1里有这样一句话么:
此处理需要花费时间,因此不建议每画格重绘一次文字。

但是现在中间的部位的确是每帧重绘一次的,这样会导致不必要的内存消耗,所以我们要他在光标移动的时候才重绘。
注意仔细观察移动光标的四个方法(Window_Selectable的221~252行):
  1. #--------------------------------------------------------------------------
  2. # ● 光标向下移动
  3. #--------------------------------------------------------------------------
  4. def cursor_down(wrap = false)
  5.   if index < item_max - col_max || (wrap && col_max == 1)
  6.     select((index + col_max) % item_max)
  7.   end
  8. end
  9. #--------------------------------------------------------------------------
  10. # ● 光标向上移动
  11. #--------------------------------------------------------------------------
  12. def cursor_up(wrap = false)
  13.   if index >= col_max || (wrap && col_max == 1)
  14.     select((index - col_max + item_max) % item_max)
  15.   end
  16. end
  17. #--------------------------------------------------------------------------
  18. # ● 光标向右移动
  19. #--------------------------------------------------------------------------
  20. def cursor_right(wrap = false)
  21.   if col_max >= 2 && (index < item_max - 1 || (wrap && horizontal?))
  22.     select((index + 1) % item_max)
  23.   end
  24. end
  25. #--------------------------------------------------------------------------
  26. # ● 光标向左移动
  27. #--------------------------------------------------------------------------
  28. def cursor_left(wrap = false)
  29.   if col_max >= 2 && (index > 0 || (wrap && horizontal?))
  30.     select((index - 1 + item_max) % item_max)
  31.   end
  32. end
复制代码
先不管那些if xxx,我们只看每个方法中间的那个select
这是在Window_Selectable里定义的一个方法,有一个必填参数index,用处是将选择框移动到第index个选项。
我们需要他在选择框移动的时候重绘,在这里写就刚刚好了。所以照例,把Window_NewMenuStatusupdate给删掉,然后加上下面这几句话:
  1. def select(index)
  2.   super
  3.   draw_actor_status
  4. end
复制代码
进入游戏测试——达到效果了。
接下来,要做很重要的一步:把Scene_Menu中的create_menustatus_command里的@menustatus_command.active = false的注释取消,因为我们要做按键判断了。
在此之前,我先把自己写的脚本放上来吧,看看大家写对了没有:
Window_NewMenuStatus
  1. <blockquote>class Window_NewMenuStatus < Window_Command
复制代码
Scene_Menu
  1. class Scene_Menu < Scene_MenuBase
  2.   #----------------------------------------------------------------------------
  3.   # * 开始处理
  4.   #----------------------------------------------------------------------------
  5.   def start
  6.     super
  7.     create_menu_window
  8.     create_menustatus_command
  9.     create_gold_window
  10.   end
  11.   #----------------------------------------------------------------------------
  12.   # * 更新画面
  13.   #----------------------------------------------------------------------------
  14.   def update
  15.     super
  16.   end
  17.   #----------------------------------------------------------------------------
  18.   # * 释放画面
  19.   #----------------------------------------------------------------------------
  20.   def terminate
  21.     super
  22.   end
  23.   #----------------------------------------------------------------------------
  24.   # * 创建菜单选项
  25.   #----------------------------------------------------------------------------
  26.   def create_menu_window
  27.     @menu_command = Window_MenuCommand.new
  28.   end
  29.   #----------------------------------------------------------------------------
  30.   # * 创建人物状态选单
  31.   #----------------------------------------------------------------------------
  32.   def create_menustatus_command
  33.     @menustatus_command = Window_NewMenuStatus.new
  34.     @menustatus_command.active = false
  35.   end
  36.   #----------------------------------------------------------------------------
  37.   # * 创建金钱窗口
  38.   #----------------------------------------------------------------------------
  39.   def create_gold_window
  40.     @gold_window = Window_NewGold.new
  41.   end
  42. end
复制代码
但是,可能有同学已经发现了一件事:菜单无法退出。
是的,因为我们还没有写相应的脚本。
用过RGSS2的同学们或许都很清楚,RGSS2中的按键响应都是在update中用条件分歧(按键判断)来实现的。但在RGSS3中有更加简单,或者说是智能的方法来实现这个功能——直接告诉他响应时要调用的方法。
我们拿退出菜单来作为例子吧。我们都知道,退出菜单实际是当菜单画面的主菜单被激活时,按下Esc键时的动作。于是我们在Scene_Menu中的create_menu_window方法里加入以下这句话:
  1. @menu_command.set_handler(:cancel,    method(:return_scene))
复制代码
首先,我给大家解释一下set_handler这个方法:
这是在Window_Selectable里定义的的一个方法,是用于定义对应动作的处理方法的,有两个必填参数:
set_handler(symbol, method)
symbol - 动作的Symbol对象,常用的有以下两个:

Symbol
对应的动作
:ok
按下确定键(C键)
:cancel
按下Esc键(B键)
method - 对应的方法,一定要是Method对象,简单地说就是method(:方法名)。
return_scene这个方法是在Scene_Base定义的,用途是返回(呼叫)上一个场景——从哪里来就回到哪里去。
所以那句脚本的意思就是(当@menu_command被激活的情况下)按下Esc键的时候返回上一个场景(一般是地图画面)。
现在测试一下……嗯,可以返回了呢。
现在就要设定按下回车键时执行的方法了,和设定按下取消键的方法差不多,但是又有一点区别。不管怎么说,我们先把按下确定键是要执行的方法给定义了吧。
因为不同的选项按下确定键的时候都有不同的处理,所以我们要分别设定。这里先把物品选项的方法给设定了先吧,在Scene_Menu里添加以下脚本:
  1. def item_ok
  2. end
复制代码
然后在create_menu_window方法里添加以下脚本:
  1. @menu_command.set_handler(:item  ,    method(:item_ok))
复制代码
阿咧,那个:item是什么东东?
还记得我给大家解释add_command这个方法的时候说到了一个叫symbol的属性么?这家伙最有用的地方就是在这里了——识别各个不同的选项。
请翻到默认的Window_MenuCommand里的第48行,写着这样一句话:
  1. add_command(Vocab::item,   :item,   main_commands_enabled)
复制代码
看到了吧,物品选项所对应的symbol:item;相同的,技能选项对应的symbol对应的是:skill、装备选项对应的symbol:equip、状态选项对应的symbol:status、整队选项对应的symbol:formation、存档选项对应的symbol:save、结束游戏这个选项对应的symbol:game_end
这样子设定的话,就能够是定对应的选项被选择时按下回车键要执行的方法了。
但是这样的话,我们选定物品然后按下回车键的时候仍然没有反应,那是因为我们给其设定的方法是空的。
这个选项的操作很简单——转移到物品选择画面(Scene_Item)。而RGSS3里呼叫场景的方法与RGSS2又有不同——不是$scene = xxx.new,而是SceneManager.call(xxx)
也是因为这个原因,呼叫场景的时候不能够传递参数了。
所以我们在item_ok这个方法里加上以下这句话:
  1. SceneManager.call(Scene_Item)
复制代码
再进入游戏进行测试——可以转移到物品栏里了。
而接下来,我们要一下子设定三个选项按下确认件要执行的方法——技能、装备、状态以及整队这四个选项。因为这四个选项按下确认键以后执行的动作都是一样的:将焦点交给人物状态窗口。其实就等于先冻结选项窗口,再激活人物状态窗口。所以我们要在Scene_Menu里加上这样一段脚本:
  1. def command_personal
  2.   @menu_command.active = false
  3.   @menustatus_command.active = true
  4. end
复制代码
然后在create_menu_window里加入以下四句:
  1. @menu_command.set_handler(:skill ,    method(:command_personal))
  2. @menu_command.set_handler(:equip ,    method(:command_personal))
  3. @menu_command.set_handler(:status,    method(:command_personal))
  4. @menu_command.set_handler(:formation,    method(:command_personal))
复制代码
好了,测试一下,选择技能、装备和状态的其中一个,按下回车键,焦点会自动转移到状态窗口里。
但是现在在状态窗口处按下回车键,依然没有响应,因为我们还没有设定。
先不要心急着设定状态窗口,先把菜单选项弄好吧。还剩下存档(:save)和结束游戏(:end_game)两个个选项呢!这两个选项确定后都是要跳转到另一个场景的,分别是存档画面(Scene_Save)以及结束游戏画面(Scene_End)。先尝试着自己写一下在和下面对答案吧!

Scene_Menu里加入以下脚本:

  1. def save_ok
  2.   SceneManager.call(Scene_Save)
  3. end
  4. def game_end_ok
  5.   SceneManager.call(Scene_End)
  6. end
复制代码
然后在create_menu_window里加入:
  1. @menu_command.set_handler(:save     ,    method(:save_ok))
  2. @menu_command.set_handler(:game_end ,    method(:game_end_ok))
复制代码
OK!完成!
现在到状态选单的确认键被按下时的处理了。上面已经说过,由于新的召唤场景方式,导致召唤场景是不可以同时传递参数,为此,RGSS3中多了一个实例变量:$game_party.menu_actor,专门用于储存在菜单换面选择的角色ID。
而且,由于技能状态和装备都是同一个处理,但又要进入不同的场景,所以我们要加一个条件分歧,来判断是从哪个选项进入状态窗口的。
这很简单,因为你在选项窗口按下回车键以后,选项窗口被冻结,这种情况下,选项窗口是不会对任何按键有响应的。也就是说,你在状态窗口里按下回车键的时候,菜单里被选择的选项仍然没有变。这样我们就可以直接获取菜单选项的选择了。
所以在Scene_Menu里加入以下脚本:
  1. def status_ok
  2.   $game_party.menu_actor = $game_actors[@menustatus_command.current_ext]
  3.   case @menu_command.current_symbol
  4.   when :skill
  5.   when :equip
  6.   when :status
  7.   when :formation
  8.   end
  9. end
复制代码
然后在create_menustatus_command里加这样一句话:
  1. @menustatus_command.set_handler(:ok, method(:status_ok))
复制代码
这时候,菜单里的状态窗口按下回车键依然不会有任何反应。但我在这里要向大家解释两个方法:current_extcurrent_symbol
current_ext是用于获得这个选项窗口当前选择的选项的扩展数据的;而current_symbol则是用于获得这个选项窗口当前选择的选项的符号(symbol)的。
所以上面的那一堆脚本里的第二行:$game_party.menu_actor = $game_actors[@menustatus_command.current_ext]是用来将当前选择的角色的实例代入到$game_party.menu_actor里的。(如果你还记得我们当时是怎么生成这个窗口的选项的话应该会很容易理解)。
而第三行的@menu_command.current_symbol是直接返回当前选择的选项对应的符号的。
接下来,我们要给每个不同的情况加上不同的指令。在when :skillwhen :equip之间加上:
  1. SceneManager.call(Scene_Skill)
复制代码
这个就是当选择了技能选项时的情况。
然后在when :equipwhen :status之间加入:
  1. SceneManager.call(Scene_Equip)
复制代码
这个就是选择了装备技能时的情况。
最后在when :statuswhen :formation之间加入:
  1. SceneManager.call(Scene_Status)
复制代码
这个就是选择了状态选项时的情况。
这样的话,status_ok方法应该会变为以下这样:
  1. def status_ok
  2.   $game_party.menu_actor = $game_actors[@menustatus_command.current_ext]
  3.   case @menu_command.current_symbol
  4.   when :skill
  5.     SceneManager.call(Scene_Skill)
  6.   when :equip
  7.     SceneManager.call(Scene_Equip)
  8.   when :status
  9.     SceneManager.call(Scene_Status)
  10.   when :formation
  11.   end
  12. end
复制代码
改到这里,菜单选项应该除了整队以外其他选项都可以用了。
要完成整队选项,就要先对“整队”这一个操作做一个分析。
所谓整队,其实就是选两个队员然后交换位置,再不断地重复罢了=。=;当然,重复就要玩家自己去重复了,我们要做的只是前面而已。
而在Game_Party里就已经有一个交换队员位置的方法了:
swap_order(index1, index2)
将队伍中第index1个队员和第index2个队员的位置交换。

而要将选择的队员储存起来(因为要选两个),就要用到变量。这里为了便于理解,我用了数组。要是以后熟练的话直接储存之前选择的一个队员就好了。
这样的话,先把初始化的方法写出来。
虽然说现在这种召唤场景的方式无法传递初始化的参数,但是初始化这个方法依然会被调用。所以初始化变量什么的依然建议在initialize里进行。
于是乎,我们在Scene_Menu里加上这段话:
  1. def initialize
  2.   super
  3. end
复制代码
然后往里面添加变量,叫什么好呢……好吧,在下英语渣,就叫@select_actor吧哈哈哈哈哈!
所以就在initialize里加入以下这句:
  1. @select_actor = []
复制代码
就可以把这个变量初始化了。
仅有一个变量是没用的,还要给这个变量派上用途。还记得我们之前写的status_ok方法里,后面有一个when :formation吗?
这个就是为整队选项留出来的。
那他是怎么执行的呢?
首先,当你第一次按下回车键的时候,也就是选定了第一个人物。此时把人物在队伍里的index,即状态选单目前选择的index代入到@select_actor的第0个位置上。
然后第二次按下回车键的时候,就是选定了第二个人物。这时候先将选定的第二个人物在队伍里的index,也就是状态选单目前选择的index代入到@select_actor的第1个位置上,然后交换队员的位置,再将焦点重新还给菜单选项、最后清空@select_actor
那怎么判断什么时候是第一次按,什么时候是第二次按呢?
不知道大家有没有发现,开始初始化的时候@select_actor是空的,按照上面的说法,整队执行到最后一步的时候@select_actor也是空的。所以当玩家是第一次按下回车键的时候,@select_actor必定是空的!
这样子的话,由于第二次按下回车键的时候,@select_actor里面已经有一个元素了,所以这个数组的长度应该为1。
所以我们现在在when :formation下面插入一个条件分歧:
  1. case @select_actor.size
  2.   when 0
  3.   when 1
  4. end
复制代码
这个就是用来判断@select_actor的长度为什么的语句了。由于我们现在的情况下只有两种可能——0个、1个和2个。而两个的不用判断,因为判断了也没用= =。
然后我们把
把人物在队伍里的index,即状态选单目前选择的index代入到@select_actor的第0个位置上

这个操作用脚本写出来,即:
  1. @select_actor[0] = @menustatus_command.index
复制代码
再插入到when 0的下面。
然后我们把
这时候先将选定的第二个人物在队伍里的index,也就是状态选单目前选择的index代入到@select_actor的第1个位置上
交换队员的位置
将焦点重新还给菜单选项
清空@select_actor

这四步也用脚本写出来,即:
  1. @select_actor[1] = @menustatus_command.index
  2. $game_party.swap_order(@select_actor[0], @select_actor[1])
  3. @select_actor = []
  4. @menustatus_command.refresh
  5. @menu_command.active = true
复制代码
【话说这里为什么会有五句脚本,是因为我在之前的步骤里没有说“刷新窗口(@menustatus_command.refresh)”这一步——但这是必须的。】
并插入到when 1的下面。
现在进入游戏测试……咦,为啥按下第一次回车键以后就不能动了呢?
因为实际上当你为某个选项窗口设定了按下回车键是执行的方法的时候,你按下回车的时候,系统会自动帮你冻结这个窗口。所以我们要在when 0里再加上一句:
  1. @menustatus_command.active = true
复制代码
这时候再测试,是不是发现可以了呢?
最后,我们还没有写状态窗口取消时的脚本。
也就是在状态窗口里选择人物时,按下Esc键就把焦点交回给菜单的脚本。
这就由大家来写吧,不过下面有答案哦~(虽然不是在最后)
虽然这样看起来已经很不错了,但实际上还有一个隐性的小BUG——请从存档界面退出到菜单界面,你就会发现这个BUG的了。
没错,无论我们是从哪一个地方返回到菜单画面,被选择的选项都会是第一个。
因为Window_Command中的initialize里选择了第一个选项。
我们要修改这一个BUG,就要先记录我们离开菜单画面的时候选项窗口所选的选项的index
这次我们用一个实例变量来储存这个数据——先在Scene_MenuBase的初始化里定义一个实例变量。
实例变量由于可以在该类以及其子类中调用。而且在该范围内,同名的类变量一定是同一个变量。利用这个特性,我们可以用它来储存选项ID。
但在此之前,我们先在Main之前新建一个脚本,然后在这个脚本里输入:
  1. class Scene_MenuBase < Scene_Base
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super
  7.     @last_index = 0 if @last_index == nil
  8.   end
  9. end
复制代码
这是用于初始化实例变量的。由于实例变量刚被创建出来(未被赋值)的时候的默认值为nil,利用此特性,我们可以让他在刚被创建时就被赋值为0。而之后因为无论任何情况都不会是nil,所以这句话在从地图进入菜单到从菜单回到地图的过程中只会执行一次,避免重复赋值导致之前所做的更改被覆盖。
然后,我们在Scene_Menucreate_menu_window方法内加上这样一句话:
  1. @menu_command.select(@last_index)
复制代码
最后,分别在item_okcommand_personalsave_ok以及game_end_ok这四个方法里的末尾处加入这句:
  1. @last_index = @menu_command.index
复制代码
好了,我们进入游戏测试,发现在进入某个画面再返回菜单的时候,选择框不会“灵异”地选到第一位了。
那个这个方法的原理是什么呢?
首先,我们在Scene_MenuBaseinitialize里定义并初始化了@last_index变量,这样就可以使从地图进入菜单的时候,菜单的选项为第一个了。
然后,在我们确认菜单选项时,@last_index被代入了菜单选单当前的选择项的index@last_index = @menu_command.index)。
由于在之后的场景中,@last_index都木有被修改,于是到我们退回去菜单画面的时候,@last_index仍然保持着我们进入那个场景时的值。
此时,@menu_command.select(@last_index)这句话发挥作用了,他将选项框移动到了第@last_index个选项上,所以就达到了我们的目的了。
好了好了,这个环节终于结束了。这个时候,我写的脚本是这样子的:
Window_MenuCommand
  1. class Window_MenuCommand < Window_Command
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     @command_icon_index = [260, 96, 170, 16, 280, 375, 12]
  7.     super(0, 0)
  8.   end
  9.   #----------------------------------------------------------------------------
  10.   # * 描绘选项
  11.   #----------------------------------------------------------------------------
  12.   def draw_item(index)
  13.     rect = item_rect_for_text(index)
  14.     draw_icon(@command_icon_index[index], rect.x, rect.y, command_enabled?(index))
  15.     self.contents.font.color.alpha = (command_enabled?(index) ? 255 : 160)
  16.     self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
  17.   end
  18. end
复制代码
Window_NewMenuStatus
  1. class Window_NewMenuStatus < Window_Command
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super(160, 0)
  7.   end
  8.   #----------------------------------------------------------------------------
  9.   # * 获取窗口的宽度
  10.   #----------------------------------------------------------------------------
  11.   def window_width
  12.     return 384
  13.   end
  14.   #----------------------------------------------------------------------------
  15.   # * 获取窗口的高度
  16.   #----------------------------------------------------------------------------
  17.   def window_height
  18.     return 416
  19.   end
  20.   #----------------------------------------------------------------------------
  21.   # * 更新内容
  22.   #----------------------------------------------------------------------------
  23.   def refresh
  24.     super
  25.     draw_actor_status
  26.   end
  27.   #----------------------------------------------------------------------------
  28.   # * 生成指令列表
  29.   #----------------------------------------------------------------------------
  30.   def make_command_list
  31.     for actor in $game_party.members
  32.       s = "a[#{actor.id}]"
  33.       add_command(actor.name, s.to_sym, !actor.death_state?, actor.id)
  34.     end
  35.   end
  36.   #----------------------------------------------------------------------------
  37.   # * 描绘选项
  38.   #----------------------------------------------------------------------------
  39.   def draw_item(index)
  40.     rect = item_rect_for_text(index)
  41.     b = Bitmap.new(rect.width, rect.height)
  42.     cn = $game_actors[@list[index][:ext]].character_name
  43.     ci = $game_actors[@list[index][:ext]].character_index
  44.     cb = Cache.character(cn)
  45.     if cn[0] == "[        DISCUZ_CODE_84        ]quot;
  46.       cr = Rect.new
  47.       cr.x = cb.width / 3
  48.       cr.width = cb.width / 3
  49.       cr.height = cb.height / 4
  50.     else
  51.       cr = Rect.new
  52.       cr.x = ci % 4 * (cb.width / 4) + cb.width / 12
  53.       cr.y = ci / 4 * (cb.height / 2)
  54.       cr.width = cb.width / 12
  55.       cr.height = cb.height / 8
  56.     end
  57.     b.blt((b.width - cr.width) / 2, (b.height - cr.height) / 2, cb, cr)
  58.     unless command_enabled?(index)
  59.       for y in 0...b.height
  60.         for x in 0...b.width
  61.           next if b.get_pixel(x, y).alpha == 0
  62.           c = b.get_pixel(x, y)
  63.           m = (c.red + c.green + c.blue) / 3
  64.           b.set_pixel(x, y, Color.new(m, m, m, c.alpha))
  65.         end
  66.       end
  67.     end
  68.     self.contents.blt(rect.x, rect.y, b, b.rect)
  69.     b.dispose
  70.     cb.dispose
  71.   end
  72.   #----------------------------------------------------------------------------
  73.   # * 获取项目的绘制矩形
  74.   #----------------------------------------------------------------------------
  75.   def item_rect(index)
  76.     rect = Rect.new
  77.     rect.width = self.contents.width / 4
  78.     rect.height = 48
  79.     rect.x = (index < 4 ? index * rect.width : (index - 4) * rect.width)
  80.     rect.y = (index < 4 ? 0 : self.contents.height - rect.height)
  81.     return rect
  82.   end
  83.   #----------------------------------------------------------------------------
  84.   # * 描绘角色的状态
  85.   #----------------------------------------------------------------------------
  86.   def draw_actor_status
  87.     actor = $game_actors[@list[@index][:ext]]
  88.     y = item_rect_for_text(0).height + 5
  89.     h = self.contents.height - 2 * (item_rect_for_text(0).height + 5)
  90.     self.contents.clear_rect(Rect.new(0, y, self.contents.width, h))
  91.     draw_actor_face(actor, 0, y)
  92.     draw_actor_name(actor, 0, y)
  93.     draw_actor_level(actor, 96, y)
  94.     draw_actor_nickname(actor, 168, y)
  95.     draw_actor_hp(actor, 96, y + line_height, self.contents.width - 96)
  96.     draw_actor_mp(actor, 96, y + 2 * line_height, self.contents.width - 96)
  97.     draw_actor_icons(actor, 96, y + 3 * line_height)
  98.     for i in 0...6
  99.       draw_actor_param(actor, 0, y + (4 + i) * line_height, i)
  100.     end
  101.     for i in 0...5
  102.       draw_item_name(actor.equips[i], self.contents.width / 2, y + (4 + i) * line_height) if actor.equips[i] != nil
  103.     end
  104.   end
  105.   #----------------------------------------------------------------------------
  106.   # * 选择项目
  107.   #----------------------------------------------------------------------------
  108.   def select(index)
  109.     super
  110.     draw_actor_status
  111.   end
  112. end
复制代码
Window_NewGold
  1. class Window_NewGold < Window_Base
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super(0, 368, 160, 48)
  7.     refresh
  8.   end
  9.   #----------------------------------------------------------------------------
  10.   # * 描绘内容
  11.   #----------------------------------------------------------------------------
  12.   def refresh
  13.     draw_icon(262, 0, 0)
  14.     draw_currency_value($game_party.gold, Vocab::currency_unit, 28, 0, self.contents.width-32)
  15.   end
  16. end
复制代码
Scene_MenuBase
  1. class Scene_MenuBase < Scene_Base
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super
  7.     @last_index = 0 if @last_index == nil
  8.   end
  9. end
复制代码
Scene_Menu
  1. class Scene_Menu < Scene_MenuBase
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super
  7.     @select_actor = []
  8.   end
  9.   #----------------------------------------------------------------------------
  10.   # * 开始处理
  11.   #----------------------------------------------------------------------------
  12.   def start
  13.     super
  14.     create_menu_window
  15.     create_menustatus_command
  16.     create_gold_window
  17.   end
  18.   #----------------------------------------------------------------------------
  19.   # * 更新画面
  20.   #----------------------------------------------------------------------------
  21.   def update
  22.     super
  23.   end
  24.   #----------------------------------------------------------------------------
  25.   # * 释放画面
  26.   #----------------------------------------------------------------------------
  27.   def terminate
  28.     super
  29.   end
  30.   #----------------------------------------------------------------------------
  31.   # * 创建菜单选项
  32.   #----------------------------------------------------------------------------
  33.   def create_menu_window
  34.     @menu_command = Window_MenuCommand.new
  35.     @menu_command.set_handler(:item,      method(:item_ok))
  36.     @menu_command.set_handler(:skill,     method(:command_personal))
  37.     @menu_command.set_handler(:equip,     method(:command_personal))
  38.     @menu_command.set_handler(:status,    method(:command_personal))
  39.     @menu_command.set_handler(:formation, method(:command_personal))
  40.     @menu_command.set_handler(:save,      method(:save_ok))
  41.     @menu_command.set_handler(:game_end,  method(:game_end_ok))
  42.     @menu_command.set_handler(:cancel,    method(:return_scene))
  43.     @menu_command.select(@last_index)
  44.   end
  45.   #----------------------------------------------------------------------------
  46.   # * 创建人物状态选单
  47.   #----------------------------------------------------------------------------
  48.   def create_menustatus_command
  49.     @menustatus_command = Window_NewMenuStatus.new
  50.     @menustatus_command.set_handler(:ok, method(:status_ok))
  51. <blockquote>    @menustatus_command.set_handler(:cancel, method(:status_cancel))
复制代码
其实,到了现在这个样子,菜单已经可以用了。
但是丧心病狂的阿婆主……啊不,丧心病狂的楼主还想把他再美化一下。
关于精灵类(Sprite)

终于说到精灵了,要@一下精灵么(精灵:【严肃脸】这一点都不好笑。)
好吧,RGSS3中的精灵,指的是一个用来显示图片的类。要显示图片,光有Bitmap是没用的,还要用精灵将他显示到画面上。
我们这次就用精灵来把我们刚刚写的菜单给美化一下。但首先第一步,我们要把显示的窗口的背景给透明了。
create_menustatus_command里的最后加入:
  1. @menu_command.opacity = 0
复制代码
create_menustatus_command里的最后加入:
  1. @menustatus_command.opacity = 0
复制代码
create_gold_window里的最后加入:
  1. @gold_window.opacity = 0
复制代码
这时候就可以进入游戏测试了。

就是这样,窗口的背景都消失了(准确来说是变透明了),但内容还在。
这里,我们用到了窗口类的一个属性——opacity
这是控制窗口的透明度的,默认为255,范围是在0~255之间。注意,窗口描绘的内容的透明度不受他控制。
F1的说明如下:
窗口的不透明度(0~255)。超出此范围的数值会自动修正。

现在,我们要在背景中加一点东西。
先把这幅图片放到你的工程目录下的Graphics\System里。

然后在Scene_Menu里加入以下脚本:
  1. def create_frame_sprite
  2.   @frame_sprite = Sprite.new
  3.   @frame_sprite.bitmap = Cache.system("素材1")
  4. end
复制代码
【如果你保存文件的时候给他起的文件名不是“素材1”的话,请把上面的脚本里双引号里的“素材1”三个字改为你的文件的文件名。】
最后在start里的super之后(即所有生成窗口的方法调用之前),输入一句:
  1. create_frame_sprite
复制代码
然后还要把刷新和释放分别写在update和dispose里,在update方法里加入:
  1. @frame_sprite.update
复制代码
在dispose里加入:
  1. @frame_sprite.dispose
复制代码
这样就行了。
刷新大家都知道有什么用,但是释放有什么用呢?
释放的作用在于你离开场景的时候,把不需要的东西从内存中释放掉,免得占内存。
此外,如果不释放的话,精灵是不会消失的。
现在进入游戏进行测试:

没错,出现的那一团黑黑的就是我们显示出来的图片。这样子虽然没有很明显的在菜单选项和状态画面之间画一条线,但是视觉上已经把他们两个分开了。
现在,我来给大家解释一下@frame_sprite = Sprite.new这句话。这句话就是创建了一个精灵类的实例,然后代入到@frame_sprite变量里的。
而下面的一句:@frame_sprite.bitmap = Cache.system("素材1"),就是用于告诉这个精灵:我要你显示什么图片。
bitmap是精灵类的一个方法,用于设定或获取精灵要显示的图片。返回值一定是位图类(Bitmap)的实例,设定的时候也一定要代入位图类的实例才行。
F1里是这样说的:
引用作为精灵起始点的位图(Bitmap 位图类 )。

但是,为什么我要强调要把这个方法的调用放到索要窗口的创建之前,super之后呢?
这关系到z值的大小。
在RGSS3中,(默认)越后创建的精灵的z值越大,也就越靠近玩家。
那么,创建截图背景的方法调用在super里,这样的话就可以使我们现实出来的这个图片显示再截图背景的前面。
同理,调用在创建窗口的方法后面也是为了使其显示在窗口的后面。
现在,我们给菜单选项做一点改造。
原本在菜单选项里,是用一个选项框来把正在被选择的选项框起来的。但是我现在要把这个框弄掉,然后在被选择的的选项后面显示一个菱形,也就是下面这个:
【现在就可以把它放到你的工程目录下的Graphics\System里了】
如果要达到这样的目的的话,我们就要先把选择框隐藏起来。
之前说过,Window_Command里的item_rect方法可以控制选择框显示的位置的大小,所以我们要修改item_rect了。
但是获取描绘项目内容范围的item_rect_for_text是以item_rect为基准的,item_rect被改变了,item_rect_for_text的返回值无可避免的也会被改变。可我不想让他被改变,要怎么办呢?
答案是——给方法“备份”。
alias这个方法是个很重要的方法,但是之前一直没有机会说。简单来说,此方法就是给某个方法起个别名的。
F1的说法是这样的:
给一个方法或全局变量指定其别名。指定的方法名称可以是标识符或 符号 (不可使用如 obj.method 这样的表达式)。调用方法时不会计算别名的参数表达式。
使用方法别名时,既使重新定义该方法,该方法的原有定义依然会被保留。当想要改变方法的行为,又要在新定义的方法内使用旧定义方法的结果时可以使用。

而且该方法的调用方式有点与众不同—:
  1. alias 新方法名 旧方法名
复制代码
我们一般都不用括号把这两个参数括起来。
如果还是理解不能,就这样理解吧:新建一个方法,把旧方法的内容统统复制到里面去。
于是我们在Window_MenuCommand里加入这样一句话:
  1. alias nir item_rect
复制代码
nirnew_item_rect,想到NTR的面壁去……啥,我才不去面壁……】
这样就给item_rect这个方法起了个别名叫nir了。
然后再添加下面的脚本:
  1. def item_rect(index)
  2.   return Rect.new
  3. end
复制代码
这样子,选项框无论在你选择什么选项都好,其位置都是(0,0),大小都是0*0,这样我们就看不到了~
最后,把item_rect_for_text方法从Window_Selectable里原封不动的复制过来,再把里面的
  1. rect = item_rect(index)
复制代码
改成
  1. rect = nir(index)
复制代码
,这就OK了。
现在进入游戏测试吧!

就是这样,选择框消失了,而选项内容却安然无恙。
现在,我们要在Scene_Menu里用刚刚学会的方法把那个小菱形显示出来。
大家先试着写一下吧,和刚才显示背景的方法一样哦,也是用精灵。

start的最后加入:
  1. create_select_sprite
复制代码
然后在Scene_Menu里加入:
  1. def create_select_sprite
  2.   @select_sprite = Sprite.new
  3.   @select_sprite.bitmap = Cache.system("素材2")
  4.   @select_sprite.x = 148
  5.   @select_sprite.y = 12 + @menu_command.index * 24 + (@select_sprite.bitmap.height - 24) / 2
  6. end
复制代码
【如果你保存文件的时候(或者有其他文件),文件名不是叫素材2的话,就要把上面这段脚本的第3句中括号里的“素材2”三个字改为你的文件的文件名了】
然后和之前一个一样,在update里加入:
  1. @select_sprite.update
复制代码
dispose里加上:
  1. @select_sprite.dispose
复制代码
好了,进入游戏测试:

很好,菱形显示出来了。但是按上下左右键,他依然没有动。不过不急,我们先把上面的脚本解释一下吧。
我觉得,上面的一坨脚本里,只有两个需要解释的东西:
(1)@select_sprite.x@select_sprite.y
xy这两个都是在精灵类里定义的方法,用于获取或者设定精灵的x坐标和y坐标。
(2)@select_sprite.bitmap.height
这个height是在位图类(Bitmap)里定义的。之前说过精灵的bitmap属性只会返回Bitmap对象,所以这里是用于获取图像的高度的。
对应的还有width,是用于获取图像的宽度的。
但是,他为什么没有动呢?
因为我们没有在update里实时更新这个精灵的坐标。
由于这个精灵只是沿着y轴移动,所以它的x坐标是不会变的,也就不用更改x坐标了。
y坐标,就和我们创建的时候时的一样算法。由于@menu_command.index变化,导致该精灵的y坐标也跟着变化。
所以我们在update方法里再加上这样一句话:
  1. @select_sprite.y = 12 + @menu_command.index * 24 + (@select_sprite.bitmap.height - 24) / 2
复制代码
【说一下,之所以这么长,是因为要达到菱形的中心点的y坐标对齐选项的中心点y坐标的效果,加上楼主数学不好没有化简=。=】
好了,现在菱形就跟着一起移动了呢!我这个菜单也就改好了。最后的效果图如下:

脚本如下:
  1. class Scene_MenuBase < Scene_Base
  2.   #----------------------------------------------------------------------------
  3.   # * 初始化
  4.   #----------------------------------------------------------------------------
  5.   def initialize
  6.     super
  7.     @last_index = 0 if @last_index == nil
  8.   end
  9. end
  10. class Window_MenuCommand < Window_Command
  11.   #----------------------------------------------------------------------------
  12.   # * 重命名方法
  13.   #----------------------------------------------------------------------------
  14.   alias nir item_rect
  15.   #----------------------------------------------------------------------------
  16.   # * 初始化
  17.   #----------------------------------------------------------------------------
  18.   def initialize
  19.     @command_icon_index = [260, 96, 170, 16, 280, 375, 12]
  20.     super(0, 0)
  21.   end
  22.   #----------------------------------------------------------------------------
  23.   # * 描绘选项
  24.   #----------------------------------------------------------------------------
  25.   def draw_item(index)
  26.     rect = item_rect_for_text(index)
  27.     draw_icon(@command_icon_index[index], rect.x, rect.y, command_enabled?(index))
  28.     self.contents.font.color.alpha = (command_enabled?(index) ? 255 : 160)
  29.     self.contents.draw_text(rect.x + 24, rect.y, rect.width - 24, rect.height, command_name(index))
  30.   end
  31.   #----------------------------------------------------------------------------
  32.   # * 获取项目的描绘矩形
  33.   #----------------------------------------------------------------------------
  34.   def item_rect(index)
  35.     return Rect.new
  36.   end
  37.   #----------------------------------------------------------------------------
  38.   # * 获取项目的绘制矩形(内容用)
  39.   #----------------------------------------------------------------------------
  40.   def item_rect_for_text(index)
  41.     rect = nir(index)
  42.     rect.x += 4
  43.     rect.width -= 8
  44.     rect
  45.   end
  46. end
  47. class Window_NewMenuStatus < Window_Command
  48.   #----------------------------------------------------------------------------
  49.   # * 初始化
  50.   #----------------------------------------------------------------------------
  51.   def initialize
  52.     super(160, 0)
  53.   end
  54.   #----------------------------------------------------------------------------
  55.   # * 获取窗口的宽度
  56.   #----------------------------------------------------------------------------
  57.   def window_width
  58.     return 384
  59.   end
  60.   #----------------------------------------------------------------------------
  61.   # * 获取窗口的高度
  62.   #----------------------------------------------------------------------------
  63.   def window_height
  64.     return 416
  65.   end
  66.   #----------------------------------------------------------------------------
  67.   # * 更新内容
  68.   #----------------------------------------------------------------------------
  69.   def refresh
  70.     super
  71.     draw_actor_status
  72.   end
  73.   #----------------------------------------------------------------------------
  74.   # * 生成指令列表
  75.   #----------------------------------------------------------------------------
  76.   def make_command_list
  77.     for actor in $game_party.members
  78.       s = "a[#{actor.id}]"
  79.       add_command(actor.name, s.to_sym, !actor.death_state?, actor.id)
  80.     end
  81.   end
  82.   #----------------------------------------------------------------------------
  83.   # * 描绘选项
  84.   #----------------------------------------------------------------------------
  85.   def draw_item(index)
  86.     rect = item_rect_for_text(index)
  87.     b = Bitmap.new(rect.width, rect.height)
  88.     cn = $game_actors[@list[index][:ext]].character_name
  89.     ci = $game_actors[@list[index][:ext]].character_index
  90.     cb = Cache.character(cn)
  91.     if cn[0] == "[        DISCUZ_CODE_2547        ]quot;
  92.       cr = Rect.new
  93.       cr.x = cb.width / 3
  94.       cr.width = cb.width / 3
  95.       cr.height = cb.height / 4
  96.     else
  97.       cr = Rect.new
  98.       cr.x = ci % 4 * (cb.width / 4) + cb.width / 12
  99.       cr.y = ci / 4 * (cb.height / 2)
  100.       cr.width = cb.width / 12
  101.       cr.height = cb.height / 8
  102.     end
  103.     b.blt((b.width - cr.width) / 2, (b.height - cr.height) / 2, cb, cr)
  104.     unless command_enabled?(index)
  105.       for y in 0...b.height
  106.         for x in 0...b.width
  107.           next if b.get_pixel(x, y).alpha == 0
  108.           c = b.get_pixel(x, y)
  109.           m = (c.red + c.green + c.blue) / 3
  110.           b.set_pixel(x, y, Color.new(m, m, m, c.alpha))
  111.         end
  112.       end
  113.     end
  114.     self.contents.blt(rect.x, rect.y, b, b.rect)
  115.     b.dispose
  116.     cb.dispose
  117.   end
  118.   #----------------------------------------------------------------------------
  119.   # * 获取项目的绘制矩形
  120.   #----------------------------------------------------------------------------
  121.   def item_rect(index)
  122.     rect = Rect.new
  123.     rect.width = self.contents.width / 4
  124.     rect.height = 48
  125.     rect.x = (index < 4 ? index * rect.width : (index - 4) * rect.width)
  126.     rect.y = (index < 4 ? 0 : self.contents.height - rect.height)
  127.     return rect
  128.   end
  129.   #----------------------------------------------------------------------------
  130.   # * 描绘角色的状态
  131.   #----------------------------------------------------------------------------
  132.   def draw_actor_status
  133.     actor = $game_actors[@list[@index][:ext]]
  134.     y = item_rect_for_text(0).height + 5
  135.     h = self.contents.height - 2 * (item_rect_for_text(0).height + 5)
  136.     self.contents.clear_rect(Rect.new(0, y, self.contents.width, h))
  137.     draw_actor_face(actor, 0, y)
  138.     draw_actor_name(actor, 0, y)
  139.     draw_actor_level(actor, 96, y)
  140.     draw_actor_nickname(actor, 168, y)
  141.     draw_actor_hp(actor, 96, y + line_height, self.contents.width - 96)
  142.     draw_actor_mp(actor, 96, y + 2 * line_height, self.contents.width - 96)
  143.     draw_actor_icons(actor, 96, y + 3 * line_height)
  144.     for i in 0...6
  145.       draw_actor_param(actor, 0, y + (4 + i) * line_height, i)
  146.     end
  147.     for i in 0...5
  148.       draw_item_name(actor.equips[i], self.contents.width / 2, y + (4 + i) * line_height) if actor.equips[i] != nil
  149.     end
  150.   end
  151.   #----------------------------------------------------------------------------
  152.   # * 选择项目
  153.   #----------------------------------------------------------------------------
  154.   def select(index)
  155.     super
  156.     draw_actor_status
  157.   end
  158. end
  159. class Window_NewGold < Window_Base
  160.   #----------------------------------------------------------------------------
  161.   # * 初始化
  162.   #----------------------------------------------------------------------------
  163.   def initialize
  164.     super(0, 368, 160, 48)
  165.     refresh
  166.   end
  167.   #----------------------------------------------------------------------------
  168.   # * 描绘内容
  169.   #----------------------------------------------------------------------------
  170.   def refresh
  171.     draw_icon(262, 0, 0)
  172.     draw_currency_value($game_party.gold, Vocab::currency_unit, 28, 0, self.contents.width-32)
  173.   end
  174. end
  175. class Scene_Menu < Scene_MenuBase
  176.   #----------------------------------------------------------------------------
  177.   # * 初始化
  178.   #----------------------------------------------------------------------------
  179.   def initialize
  180.     super
  181.     @select_actor = []
  182.   end
  183.   #----------------------------------------------------------------------------
  184.   # * 开始处理
  185.   #----------------------------------------------------------------------------
  186.   def start
  187.     super
  188.     create_frame_sprite
  189.     create_menu_window
  190.     create_menustatus_command
  191.     create_gold_window
  192.     create_select_sprite
  193.   end
  194.   #----------------------------------------------------------------------------
  195.   # * 更新画面
  196.   #----------------------------------------------------------------------------
  197.   def update
  198.     super
  199.     @select_sprite.y = 12 + @menu_command.index * 24 + (@select_sprite.bitmap.height - 24) / 2
  200.     @frame_sprite.update
  201.     @select_sprite.update
  202.   end
  203.   #----------------------------------------------------------------------------
  204.   # * 释放画面
  205.   #----------------------------------------------------------------------------
  206.   def terminate
  207.     super
  208.     @frame_sprite.dispose
  209.     @select_sprite.dispose
  210.   end
  211.   #----------------------------------------------------------------------------
  212.   # * 创建边框精灵
  213.   #----------------------------------------------------------------------------
  214.   def create_frame_sprite
  215.     @frame_sprite = Sprite.new
  216.     @frame_sprite.bitmap = Cache.system("素材1")
  217.   end
  218.   #----------------------------------------------------------------------------
  219.   # * 创建选择精灵
  220.   #----------------------------------------------------------------------------
  221.   def create_select_sprite
  222.     @select_sprite = Sprite.new
  223.     @select_sprite.bitmap = Cache.system("素材2")
  224.     @select_sprite.x = 148
  225.     @select_sprite.y = 12 + @menu_command.index * 24 + (@select_sprite.bitmap.height - 24) / 2
  226.   end
  227.   #----------------------------------------------------------------------------
  228.   # * 创建菜单选项
  229.   #----------------------------------------------------------------------------
  230.   def create_menu_window
  231.     @menu_command = Window_MenuCommand.new
  232.     @menu_command.set_handler(:item,      method(:item_ok))
  233.     @menu_command.set_handler(:skill,     method(:command_personal))
  234.     @menu_command.set_handler(:equip,     method(:command_personal))
  235.     @menu_command.set_handler(:status,    method(:command_personal))
  236.     @menu_command.set_handler(:formation, method(:command_personal))
  237.     @menu_command.set_handler(:save,      method(:save_ok))
  238.     @menu_command.set_handler(:game_end,  method(:game_end_ok))
  239.     @menu_command.set_handler(:cancel,    method(:return_scene))
  240.     @menu_command.select(@last_index)
  241.     @menu_command.opacity = 0
  242.   end
  243.   #----------------------------------------------------------------------------
  244.   # * 创建人物状态选单
  245.   #----------------------------------------------------------------------------
  246.   def create_menustatus_command
  247.     @menustatus_command = Window_NewMenuStatus.new
  248.     @menustatus_command.set_handler(:ok    , method(:status_ok))
  249.     @menustatus_command.set_handler(:cancel, method(:status_cancel))
  250.     @menustatus_command.active = false
  251.     @menustatus_command.opacity = 0
  252.   end
  253.   #----------------------------------------------------------------------------
  254.   # * 创建金钱窗口
  255.   #----------------------------------------------------------------------------
  256.   def create_gold_window
  257.     @gold_window = Window_NewGold.new
  258.     @gold_window.opacity = 0
  259.   end
  260.   #----------------------------------------------------------------------------
  261.   # * "物品"选项确定
  262.   #----------------------------------------------------------------------------
  263.   def item_ok
  264.     SceneManager.call(Scene_Item)
  265.     @last_index = @menu_command.index
  266.   end
  267.   #----------------------------------------------------------------------------
  268.   # * "技能""装备""状态""整队"选项确定
  269.   #----------------------------------------------------------------------------
  270.   def command_personal
  271.     @menu_command.active = false
  272.     @menustatus_command.active = true
  273.     @last_index = @menu_command.index
  274.   end
  275.   #----------------------------------------------------------------------------
  276.   # * "存档"选项确定
  277.   #----------------------------------------------------------------------------
  278.   def save_ok
  279.     SceneManager.call(Scene_Save)
  280.     @last_index = @menu_command.index
  281.   end
  282.   #----------------------------------------------------------------------------
  283.   # * "结束游戏"选项确定
  284.   #----------------------------------------------------------------------------
  285.   def game_end_ok
  286.     SceneManager.call(Scene_End)
  287.     @last_index = @menu_command.index
  288.   end
  289.   #----------------------------------------------------------------------------
  290.   # * 状态窗口确定
  291.   #----------------------------------------------------------------------------
  292.   def status_ok
  293.     $game_party.menu_actor = $game_actors[@menustatus_command.current_ext]
  294.     case @menu_command.current_symbol
  295.     when :skill
  296.       SceneManager.call(Scene_Skill)
  297.     when :equip
  298.       SceneManager.call(Scene_Equip)
  299.     when :status
  300.       SceneManager.call(Scene_Status)
  301.     when :formation
  302.       case @select_actor.size
  303.       when 0
  304.         @select_actor[0] = @menustatus_command.index
  305.         @menustatus_command.active = true
  306.       when 1
  307.         @select_actor[1] = @menustatus_command.index
  308.         $game_party.swap_order(@select_actor[0], @select_actor[1])
  309.         @select_actor = []
  310.         @menustatus_command.refresh
  311.         @menu_command.active = true
  312.       end
  313.     end
  314.   end
  315.   #----------------------------------------------------------------------------
  316.   # * 状态窗口取消
  317.   #----------------------------------------------------------------------------
  318.   def status_cancel
  319.     @menu_command.active = true
  320.     @menustatus_command.active = false
  321.   end
  322. end
复制代码
不知道大家是觉得长还是短呢?反正我是觉得很短了。
总结
这堂课(这是一堂吗喂!)我们学了什么呢?
(一)用语模块
Vocab::item          —— 物品选项用语
Vocab::skill            —— 技能选项用语
Vocab::equip         —— 装备选项用语
Vocab::status        —— 状态选项用语
Vocab::formation —— 整队选项用语
Vocab::save           —— 存档选项用语
Vocab::game_end —— 结束游戏选项用语
(二)Window相关
window.new                 —— 创建窗口类实例
draw_currency_value —— 描绘金钱数量以及单位
draw_actor_param     —— 描绘角色能力值
draw_item_name        —— 描绘道具名字及图标
active                            —— 获取或设定窗口冻结状态
(三)选项窗口(Window_Selectable及其子类)相关
draw_item                  —— 描绘单个选项
item_rect                    —— 获取选项(选项框)的绘制范围
item_rect_for_text    —— 获取选项内容的绘制范围
make_command_list —— 生成指令列表
add_command           —— 添加指令
select                           —— 选择某个项目
index                           —— 获取目前选择的选项的序号(index)
current_symbol         —— 获取当前选择的选项的符号(Symbol)
current_ext                —— 获取当前选择的选项的扩展内容
(四)位图类(Bitmap)相关
get_pixel —— 获取位图上某一点的颜色
set_pixel —— 设定位图上某一点的颜色
width      —— 获取该位图宽度
height     —— 获取该位图高度
dispose   —— 释放位图
(五)精灵类(Sprite)相关
Sprite.new —— 生成精灵类实例
btmap        —— 获取或设定要显示的位图
x                 —— 获取或设定精灵左上角的x坐标
y                 —— 获取或设定精灵左上角的y坐标
(六)场景类(Scene)相关
start                                     —— 开始处理
post_start                           —— 开始后处理
update                                 —— 更新画面
pre_terminate                     —— 释放前处理
terminate                             —— 释放画面
SceneManager.call(scene) —— 呼叫场景
(七)色彩类(Color)相关
Color.new(red, blue, green, alpha) —— 创建色彩类实例
Color.new                                          —— 创建色彩类实例
red                                                      —— 获取或设定红色值
blue                                                    —— 获取或设定蓝色值
green                                                 —— 获取或设定绿色值
alpha                                                  —— 获取或设定填充度(透明度)
(八)矩形类(Rect)相关
Rect.new(x, y, width, height) —— 创建矩形类实例
Rect.new                                  —— 创建矩形类实例
(九)其它
$game_party.members                   —— 获取目前队员数组
$game_party.gold                            —— 获取目前持有金钱数
$game_actors[x].character_name —— 获取该角色行走图文件名
$game_actors[x].character_index —— 获取该角色行走图序号
$game_actors[x].death_state?      —— 获取该角色死否已经死亡
课后作业(选做)
(一)请在菜单画面里新增一个窗口,大小与金钱窗口一样,位置在金钱窗口上面,紧挨着金钱窗口,里面显示玩家目前行走的步数。
Tips:获取玩家目前行走的步数:
  1. $game_party.steps
复制代码
效果图:

(二)写到最后,楼主才发现这个菜单有一个很大的BUG……
死亡的角色无法进入技能、装备、状态界面!甚至无法被整队!
这是由于死亡的角色=无效选项所造成的。
对于无效选项,系统对其的确定动作一律不执行定义的方法。
不过楼主已经修改好了……
我就不发出来,你自己去改吧哈哈哈哈哈~(众:PIA)
Tips1:获取当前选项是否有效:
  1. current_item_enabled?
复制代码
Tips2:请在Window_Selectable的第315~327行寻找一个叫process_ok的方法,并解读一下。
我会告诉你答案在最后吗?
(一)物品选项的改法
把:
  1. add_command(Vocab::item,      :item,   true)
复制代码
改成:
  1. add_command(Vocab::item,      :item,   !$game_party.members.empty?)
复制代码
就好了。
(二)作业答案,仅供参考
第一个作业:
Scene_Menu中的start里加入:
  1. create_step_window
复制代码
然后在Scene_Menu里加入:
  1. def create_step_window
  2.   @step_window = Window_Base.new(0, 320, 160, 48)
  3.   @step_window.contents.draw_text(0, 0, @step_window.contents.width, 24, "#{$game_party.steps}步", 2)
  4.   @step_window.opacity = 0
  5. end
复制代码
即可
第二个作业:
Window_NewMenuStatus里加入:
  1. def process_ok
  2.   Sound.play_ok
  3.   Input.update
  4.   deactivate
  5.   call_ok_handler
  6. end
复制代码
(三)编后语
嘛,花了这么多天终于写完了呢!
我承认我是有点懒啦~让大家久等了真的很不好意思的说……
这次写教程,由于语死早,表达能力有障碍(喂!),有些地方解释起来有点吃力,如果看不懂的话可以楼下回复或者私信。
发现什么错漏了也可以提出!我很虚心的嘿嘿~
不过,不知道有没有人有耐心看到这里来呢……
2013.8.2 945127391
作者: feizhaodan    时间: 2013-7-30 10:16
讲的很详细、好评。
作者: yagami    时间: 2013-7-30 11:34
太长了 还是继续XP好了 不过看到LZ有个地方写  &&等价于or。
我记得&&= and   || = or
作者: Luciffer    时间: 2013-7-30 12:59
好评!@Mic_洛洛 @迷糊的安安  
作者: 星尘泪    时间: 2013-8-2 10:21
提示: 作者被禁止或删除 内容自动屏蔽
作者: lioasdfghjkl    时间: 2013-8-3 10:49
我来拜读好友的教程
作者: kuerlulu    时间: 2013-8-7 11:13
好长{:2_277:}果断先右键
等我看完RGSS2再来看。
作者: zhangfuzhou    时间: 2013-10-14 18:27
点击物品和技能之后,在按到下方的选择的时候会跳出Scene_ItemBase 81行出错,点击没有物品的空白地方或者武器之类不能使用的物品就会出错,能修改一下不
作者: zhangfuzhou    时间: 2013-10-15 14:54
已改好~~~
作者: 945127391    时间: 2013-11-3 20:45
zhangfuzhou 发表于 2013-10-15 14:54
已改好~~~

呀,居然粗错了,可以详细说说是怎么错的还有你是怎么改的吗
作者: a146782002    时间: 2014-10-27 22:02
这一整个教程感觉要消化下去还是要有点时间,看来只能慢慢嗑了
作者: sjl2323    时间: 2015-1-28 01:08
写的好棒!感谢po主,不过想问下这个教程(一)在哪里呢? 翻了po主的帖子没有找到QAQ




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