| 赞 | 4  | 
 
| VIP | 71 | 
 
| 好人卡 | 22 | 
 
| 积分 | 7 | 
 
| 经验 | 32145 | 
 
| 最后登录 | 2013-8-9 | 
 
| 在线时间 | 184 小时 | 
 
 
 
 
 
Lv2.观梦者 天仙 
	- 梦石
 - 0 
 
        - 星屑
 - 680 
 
        - 在线时间
 - 184 小时
 
        - 注册时间
 - 2008-4-15
 
        - 帖子
 - 5023
 
 
  
 
 | 
	
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员  
 
x
 
雪流星脚本教程第三弹:Scene脚本教程三 
 
RMXP的说明档: 
RGPXP.rar 
 
小幽的VX实用脚本查询手册: 
VX实用脚本查询手册 
 
 
雪流星脚本教程第一弹:Scene脚本教程一 
雪流星脚本教程第二弹:Scene脚本教程二 
 
 
继续使用上一个工程,打开脚本编译器,再新增一个新的脚本组。 
这里假设你已经看过以前的教程,所以以前说过的不再赘叙。 
 
这次我们来重新写一个另一个画面:Scene_Item,也就是物品画面 
 
这次我们会讲到很多方法调用,看起来可能会有点乱,我会照着使用物品的流程写,。 
开始了。 
 
首先我们来看两张截图 
  
![]()  
玩“大家来找碴”吗? 
没错,看到什么地方不同了吗? 
 
注意到了吗?第一张截图里,选择人物时,它的背景是后面的窗口,也就是物品选择窗口 
而第二张截图里面,它的背景和物品窗口的背景是一致的。 
 
在注意看这里: 
 ![]()  
第一张截图的窗口是重叠的,而第二张截图中,右边的窗口被切掉了。(事实上我认为这是默认脚本的一个失误,后面会讲到修补方法。) 
 
倒底是怎么回事呢?如果说是透明度的话,那为什么第一张截图里看得到后面的窗口却看不到背景,而第二张截图里看得到背景却看不到后面的窗口? 
 
答案:显示端口──Viewport。 
 
Viewport是用来控制精灵显示用的,那么跟窗口又有什麽关系呢?首先先讲讲精灵。 
精灵(Sprite又称为活动块,「精灵」是直译)是用来显示图片的,凡是在VX里你看得到的图片、图像, 
包括人物行走图、地图图块、显示图片、远景、动画、窗口皮肤,全部都是用精灵来显示的。 
 
而Viewport可以控制精灵的显示位置、显示大小。简单的说,就是把精灵限制在画面的一个区域内。 
 
为了要看它的效果,我们先来开始写我们的物品画面 
注意名称是Scene_Item_N,避免覆盖原来的脚本,因为我们要来回检查效果。 
在地图上设置一个NPC,使用命令脚本 $scene = Scene_Item_N.new 
 
class Scene_Item_N < Scene_Base 
  #-------------------------------------------------------------------------- 
  # ● 开始处理 
  #-------------------------------------------------------------------------- 
  def start 
    super 
    create_menu_background 
    @viewport = Viewport.new(0, 0, 544, 416) 
    @help_window = Window_Help.new 
    @help_window.viewport = @viewport 
    @item_window = Window_Item.new(0, 56, 544, 360) 
    @item_window.viewport = @viewport 
    @item_window.help_window = @help_window 
    @item_window.active = true 
    @target_window = Window_MenuStatus.new(0, 0) 
    @target_window.visible = false 
  end 
  #-------------------------------------------------------------------------- 
  # ● 结束处理 
  #-------------------------------------------------------------------------- 
  def terminate 
    super 
    dispose_menu_background 
    @viewport.dispose 
    @help_window.dispose 
    @item_window.dispose 
    @target_window.dispose 
  end 
  #-------------------------------------------------------------------------- 
  # ● 更新画面 
  #-------------------------------------------------------------------------- 
  def update 
    super 
    update_menu_background 
    @help_window.update 
    @item_window.update 
    @target_window.update 
  end 
end 
 
蓝色的先不要管,我们来看红色的那几句。 
@viewport = Viewport.new(0, 0, 544, 416) 
生成一个显示端口对象,座标(0, 0)宽高(544, 416) 
也就是占据了整个画面大小 
@help_window.viewport = @viewport 
@item_window.viewport = @viewport 
这两句将物品和帮助窗口与显示端口间连接起来(也就是「关联」) 
 
好了,我们来测试看看: 
 ![]()  
由於还没有做按键判断,而且也没有物品,现在所以窗口还不会动。好了先关闭。 
 
修改那句生成显示端口的代码为: 
@viewport = Viewport.new(0, 0, 180, 416) 
也就是修改它的宽度,看看效果: 
 ![]()  
相信一些聪明的看官已经猜出最前面的两张图的效果是怎麽做的了。 
就是在显示选择目标窗口的时候,修改显示端口的宽度,也就是说, 
目标窗口下面变成透明的,所以直接透过去看到下面的背景。 
把显示端口的宽度调回去,我们继续。 
 
接下来来做按键判断,因为我们有两个需要按键判断的窗口(物品和目标),所以我们需要两套判断。 
-       if @item_window.active
 
 -       update_item_selection
 
 -     end
 
 -     
 
 -     if @target_window.active
 
 -       update_target_selection
 
 -     end
 
  复制代码 
 
因为我们在这里调用了两个新的方法,所以定义一下: 
 -     def update_item_selection
 
 -   end
 
 -   
 
 -   def update_target_selection
 
 -   end
 
  复制代码 
还有等下会用的返回画面: 
 -     def return_scene
 
 -     $scene = Scene_Map.new  # 先返回地图,完全做好後再修改
 
 -   end
 
  复制代码 
 
先来做物品窗口的判断: 
-       if Input.trigger?(Input::B)              # 按下取消键时
 
 -       Sound.play_cancel                      # 播放取消音效
 
 -       return_scene                           # 返回场景
 
 -     end
 
 -     if Input.trigger?(Input::C)              # 按下确定键时
 
 -       @item = @item_window.item              # 获取物品
 
 -       if @item != nil                        # 物品不为空时
 
 -         $game_party.last_item_id = @item.id  # 记录上次使用物品
 
 -       end
 
 -       if $game_party.item_can_use?(@item)    # 物品可以使用时
 
 -         Sound.play_decision                  # 播放确定音效
 
 -         determine_item                       # 确认物品
 
 -       else                                   # 物品不可使用时
 
 -         Sound.play_buzzer                    # 播放冻结音效
 
 -       end
 
 -     end
 
  复制代码 
 
「获取物品」、「记录上次使用物品」、「物品可以使用时」都是事先定义好的方法,我们现在不需要探究 
除此之外没有什麽新的代码,都是上次讲过的……除了这句:determine_item # 确认物品 
当然这又是一个我们要定义的方法 
-     def determine_item
 
 -     # 内建方法:判断物品使用对象是否为同伴
 
 -     if @item.for_friend?
 
 -       show_target_window(@item_window.index % 2 == 0)  # 显示目标窗口
 
 -       # 内建方法:判断物品使用对象是否为全体
 
 -       if @item.for_all?
 
 -         @target_window.index = 99                      # 设目标窗口选项为全体
 
 -       else
 
 -         # 上次选择目标是否小于目标最大数
 
 -         if $game_party.last_target_index < @target_window.item_max
 
 -           # 设目标窗口光标位置为上次选择目标位置
 
 -           @target_window.index = $game_party.last_target_index
 
 -         else
 
 -           # 设目标窗口光标位置为第一个队员
 
 -           @target_window.index = 0
 
 -         end
 
 -       end
 
 -     else
 
 -       # 无目标使用物品
 
 -       use_item_nontarget
 
 -     end
 
 -   end
 
  复制代码 
 
内建方法什麽的就不必谈了,只要知道它是干啥用的就行了。 
「无目标使用物品」後面会谈到,到时再一起解说。 
先来谈谈这行吧: 
if $game_party.last_target_index < @target_window.item_max  # 上次选择目标是否小于目标最大数 
 
last_tartget_index是在Game_Party里面定义好、用来记忆上次所选择的目标,和「记录上次使用物品」是一样的。 
不同的是,记录上次物品是在Window_Item里面调用的,所以我们暂且不说, 
而纪录上次选择目标是在这个脚本里调用并修改,所以特别拿出来说。 
 
if $game_party.last_target_index < @target_window.item_max 
这行就是确认上次所选择的目标是不是小於目前的目标窗口选项大小,其实也就是队员人数。 
 
什麽时候会发生「上次所选目标大於目前目标选项大小」呢? 
发生队员变动的时候。 
 
那麽为什麽又要检查「上次所选目标大於目前目标选项大小」呢? 
因为为了让玩家的手感更佳,在选择的时候记录上次的目标,这样进入目标窗口时, 
光标就直接在那个目标上面了。 
 
如果原本你的队伍有4个人,你对第4个人使用了物品,那麽last_target_index便会记录这个队员。 
                last_target_index = 3 
而之後有一个队员离开了队伍,於是你的队伍只剩下3人。 
                @target_window.item_max = 3 
这时再次使用物品时,由於你的队伍只剩下3人,也就是你的目标窗口已经没有了「第四个」选项 
那麽光标就没有位置可去,於是就会: 
 ![]()  
那啥,变成了全选了。 
 
原因是在目标窗口内本身的定义就是当光标的位置被设为@item_max和100之间时,当全选处理。 
但是更惨的是,按下确定键後会出现错误。(因为实际传送进入的参数是不存在的第四个队员。) 
PS:这张截图是用默认脚本修改测试的,不是用现在所写的脚本测试。 
 
再来就是show_target_window(@item_window.index % 2 == 0)  # 显示目标窗口这句。 
又是一个我们要再定义的方法。 
-     def show_target_window(right)
 
 -     @item_window.active = false                       # 无效化物品窗口
 
 -     width_remain = 544 - @target_window.width         # 计算剩馀宽度
 
 -     @target_window.x = right ? width_remain : 0       # 计算 X 座标
 
 -     @target_window.visible = true                     # 可视化目标窗口
 
 -     @target_window.active = true                      # 有效化目标窗口
 
 -     # 修改显示端口宽度
 
 -     if right
 
 -       @viewport.rect.set(0, 0, width_remain, 416)
 
 -     else
 
 -       @viewport.rect.set(@target_window.width, 0, width_remain, 416)
 
 -     end
 
 -   end
 
  复制代码 
 
首先我们先看一下调用时所使用的参数 
@item_window.index % 2 == 0 
这是一句布林表达式(Boolean Expression)。 
呃~~这位同学,你有什麽问题吗? 
某学生:「老师,啥是『布林表达式』?」 
「『布林表达式』都不知道!先去做300下青蛙跳。」 
(某学生做完回来) 
「累不累?」 
某学生:「累~~~~」 
「好,那句『累不累?』就是『布林表达式』。」  
 
布林表达式,简单的说,就是一句是非题,也就是说它的答案(返回值) 
一定只有「是」(true)与「非」(false) 
如同刚才所说的「累不累?」这个问题只会有2个答案:「累」或是「不累」 
(「累~~」和「有点累~~」都算累啦!!) 
布林表达式就是我们在条件分歧的时候,在if後面接得那段。 
 
@item_window.index % 2  就是用物品窗口当前光标的位置(从0开始计算)除以2之後所得的馀数。 
任何数字除以2之後的馀数就只会有两种:单数得1,复数得0。 
那麽当光标位置为复数时(也就是在左栏时),@item_window.index % 2就会返回0, 
所以@item_window.index % 2 == 0 返回true 
然後这个true就是我们调用show_target_window用的参数了。 
 
事实上%2这种算式在很多语言中被当作布林表达式来使用, 
因为这些语言将0视为false处理。 
但是Ruby的解释器只认false和nil为false,所以才要在後面加上 ==   
 
在show_target_window里面,刚才传进来的参数被命名为right,也就是「右」, 
那麽我们刚才得到的true就是说「在右边」的意思。 
什麽东西在右边呢?当然是目标窗口啦。 
 
关键句就是:@target_window.x = right ? width_remain : 0 
 
那个问号和冒号是怎麽回事? 
 
这个就相当於一个if…else…end条件分歧 
问号的前面就是一个布林表达式,冒号就相当於else。 
问号与冒号间就是当布林表达式为true时的返回值。 
冒号後面就是当布林表达式为false时的返回值。 
 
所以将@target_window.x = right ? width_remain : 0整句展开就等於 
if right 
  @target_window.x = width_remain 
else 
  @target_window = 0 
end 
 
是不是变得很长呢?记得在第一弹里有说过吧,脚本要越短越好。 
 
顺便一提,width_remain是由上一句width_remain = 544 - @target_window.width来的 
 
再来if right~end这段就是最前面讲到的显示端口调整了。 
 
PS: 
记不记得前面说过,默认的物品画面有一个我认为不完善的地方? 
就是两个窗口重叠时,物品窗口好像被切掉一样? 
(事实上默认的这个方法是绝对正确的,因为EB不知道我们会用什麽样的窗口外观。 
不同窗口外观会有不同的效果。) 
 
修改方式就是把显示端口的宽度调一下: 
   if right 
     @viewport.rect.set(0, 0, width_remain + 3, 416) 
     @viewport.ox = 0 
   else 
     @viewport.rect.set(@target_window.width - 3, 0, width_remain+3, 416) 
     @viewport.ox = @target_window.width-3 
   end 
默认的窗口外观,3像素就足够了,如果你使用另外的窗口外观,自己多试几次看看吧! 
效果(注意重叠的部分): 
 ![]()   
调整完之後呢?注意到这句了吗? 
@target_window.active = true 
 
@target_window已经有效化了,当然就开始更新了,所以来定义一下目标窗口的更新方法。 
 
-     def update_target_selection
 
 -     if Input.trigger?(Input::B)                 # 按下取消键时
 
 -       Sound.play_cancel                         # 播放取消音效
 
 -       if $game_party.item_number(@item) == 0    # 当物品为0时
 
 -         @item_window.refresh                    # 刷新物品窗口
 
 -       end
 
 -       hide_target_window                        # 隐藏目标窗口
 
 -     end
 
 -     if Input.trigger?(Input::C)                 # 按下确定键时
 
 -       if not $game_party.item_can_use?(@item)   # 物品无法使用时
 
 -         Sound.play_buzzer                       # 播放冻结音效
 
 -       else
 
 -         determine_target                        # 判断目标
 
 -       end
 
 -     end
 
 -   end
 
  复制代码 
 
大多都是一些看过的脚本代码了 
取消键的判断、确定键的判断、物品能不能使用的判断、播放音效…… 
这些就不用再重复讲解了。 
等等……按下取消键的时候,为什麽要判断物品的数目是否为0呢?为什麽要刷新物品窗口呢? 
 
我们先来想想,当按下取消键时,会怎麽样?  回到物品选择窗口。 
如果我们使用了消耗物品,物品的数目是不是会变呢? 
如果物品用完了,是不是应该从物品窗口中消失呢?(显示个   XXX   :0 不太好看吧?) 
 
这就是为何物品窗口需要先刷新的原因了。 
 
按下确定键的分歧会延伸的比较长,等一下再继续讲,先来讲讲按下取消键後,所调用的hide_target_window 
与show_target_window相反,hide_target_window是把目标窗口隐藏起来: 
-      def hide_target_window
 
 -     @item_window.active = true
 
 -     @target_window.visible = false
 
 -     @target_window.active = false
 
 -     @viewport.rect.set(0, 0, 544, 416)
 
 -   end
 
 
  复制代码 
 
与show_target_window不同之处在於:hide_target_window只管将目标窗口消失、物品窗口活化和显示端口回复而已。不需要管目标窗口是在左边还是在右边。 
 
讲完hide_target_window,继续讲刚才在目标窗口按下确定键的分歧判断。 
 
当玩家按下确定键时,判断物品是否为可用,若不可用则发出警告(也就是冻结音效)。 
若物品可用,则进入下一段,也就是目标判断: 
-     def determine_target
 
 -     used = false                           # 初始化used这个区域变量
 
 -     if @item.for_all?                      # 判断物品是否为全体使用
 
 -       for target in $game_party.members    # 循环队伍中所有成员
 
 -         target.item_effect(target, @item)  # 对成员使用物品
 
 -         used = true unless target.skipped  # 若成员不可使用物品则不修改used的值
 
 -       end
 
 -     else                                   # 物品是单体使用
 
 -       # 将上次选择目标替换为现在选中的目标
 
 -       $game_party.last_target_index = @target_window.index
 
 -       # 选择队伍中所选中的队员
 
 -       target = $game_party.members[@target_window.index]
 
 -       target.item_effect(target, @item)    # 对成员使用物品
 
 -       used = true unless target.skipped    # 若成员不可使用物品则不修改used的值
 
 -     end
 
 -     
 
 -     # 判断物品是否使用了
 
 -     if used
 
 -       use_item_nontarget                   # 无目标使用物品
 
 -     else
 
 -       Sound.play_buzzer                    # 播放冻结音效
 
 -     end
 
 -   end 
 
  复制代码 
 
「对成员使用物品」这段是Game_Battler内的方法,这里就不讲解了。 
「循环队伍中所有成员」这里就是用了循环,会一一将队伍中的成员代入target中。详细去看F1中的for循环。 
 
关於物品是否对全体使用,其实内容都很像,只不过全体使用时是用了循环,而单体使用就只接让选中的成员使用物品。 
还有「将上次选择目标替换为现在选中的目标」这个就是前面有讲到的,让玩家手感比较好的功能,就是在这里记录的。 
 
used 是用来测试物品是否使用成功,如果成功的话,就调用use_item_nontarget,无目标使用物品。 
说是「无目标使用物品」,其实应该说是使用物品时所需要调用到的一个方法(无论有无目标)。 
但是有目标的话要先判断目标才能调用,无目标时就直接调用。 
所以在determine_item里,当所选择的物品范围非我方队员时,就直接调用这个方法。 
 
定义一下use_item_nontarget: 
-     def use_item_nontarget
 
 -     Sound.play_use_item                    # 播放使用物品音效
 
 -     $game_party.consume_item(@item)        # 队伍消耗该物品
 
 -     @item_window.draw_item(@item_window.index) # 重新描绘该项目
 
 -     @target_window.refresh                 # 刷新目标窗口
 
 -     if @item.common_event_id > 0           # 若物品调用公共事件
 
 -                                            # 则呼叫该公共事件
 
 -       $game_temp.common_event_id = @item.common_event_id
 
 -       $scene = Scene_Map.new               # 返回地图画面
 
 -     end
 
 -   end
 
  复制代码 
 
到了$game_party.consume_item(@item)这行时,Game_Party会判断物品是否为消耗品,若是消耗品,则物品数量减1。 
@item_window.draw_item(@item_window.index)是在物品数量变更时,在物品窗口修改拥有数量。 
@target_window.refresh是刷新目标窗口(如补血後的血量等)。 
 
if @item.common_event_id > 0 如果物品没有调用公共事件的话,默认的公共事件ID为0。大於0就是有公共事件啦。 
有公共事件之後要做什麽,当然是呼叫公共事件,所以: 
$game_temp.common_event_id = @item.common_event_id 
然後返回地图画面 
$scene = Scene_Map.new 
 
好了,现在我们的物品画面完成了,可以运行了。如果有出错的是你的错,非本教程有任何问题(推卸责任先~~) 
事实上上面写完的脚本的确有一个我故意写逻辑错误(不知道什麽是逻辑错误的请看最下面的附录。),我们来测试看看: 
先设置如下事件,图形随意,开始条件「确定键」,内容: 
  增减HP:全体同伴, - 9999 
  增减物品:[回复剂]+1 
  增减物品:[超回复剂]+1 
  增减物品:[完全回复剂]+1 
 
PS:上面的物品名称是用了我翻译过的数据库。 
 
对了,顺便把几个初始角色的HP调小一点,比「回复剂」的回复量小就行了(例如我调的是10),这样比较能显得出效果。 
记得还要设置最最上面讲的那个调用$scene=Scene_Item_N.new的那个NPC。 
 
好,测试: 
结果发现,选择物品「回复剂」时,目标窗口出现,1号角色立刻回复了体力,但是~~我们还没选择目标啊?? 
再试一次(不退出游戏),测试「超回复剂」,也是一出现目标窗口,就听到冻结的音效(因为1号角色的体力已经补满了)。 
 
为什麽会出现这样的问题呢? 
这就是回家作业(喂~~太不负责了吧???) 
 
其实问题出现在我们的update上: 
-       if @item_window.active
 
 -       update_item_selection
 
 -     end
 
 -     
 
 -     if @target_window.active
 
 -       update_target_selection
 
 -     end
 
  复制代码 
有问题吗?语法上没有问题,但是逻辑上就很有问题了。 
什麽问题呢? 
那就是物品窗口和目标窗口同时更新。 
 
在show_target_window时,虽然我们先无效化了物品窗口,在将目标窗口活化,但是实际发生的速度非常微小, 
所以物品窗口和目标窗口可以说有一瞬间是同时活化的,所以update_item_selection和update_target_selection就会(几乎)同时被调用, 
在这小小的一瞬间,你的手指肯定还没放开确定键吧? 
所以update_target_selection立刻判断确定键已经被按下,所以1号角色的HP就被补满了。 
 
那麽要怎麽修改呢?很简单,让update_item_selection和update_target_selection永远不可能同时被调用。 
方法就是把两个条件分歧变成一个: 
-         if @item_window.active
 
 -       update_item_selection
 
 -     elsif @target_window.active
 
 -       update_target_selection
 
 -     end 
 
  复制代码 
修改後,直到你放开确定键後後再按一次,1号角色才会补血,否则就算你选择物品後按住确定键,1号角色也不会补血。 
 
 
如果各位同学拿原来的Scene_Item来比较,就会发现这里写的脚本几乎和原来的一模一样。 
因为这次主要要讲解的是整个场景的方法调用和流程,以後在写scene时,先在脑中构思一下整个流程, 
最好能画一个流程图,可以让你在写脚本时不会搞混。 
 
附带一下物品画面的粗略流程图(看不清的话请按此下载): 
 ![]()  
本教程就到此为止。 
 
附录:语法错误和逻辑错误 
什麽是语法错误呢? 
语法错误,最简单的就是拼错字,或是调用方法(函数)时使用了错误的格式等。 
一般来说,语法错误都能够由解释器(或有些语言的编译器)找出,并返回错误讯息。 
这种错误只要你去察看F1的说明就能够修正了。 
 
最难找的错误莫过於逻辑错误了,就是我们一般称的BUG。因为这种错误一般在语法上都没有错, 
而是在调用方法时、使用变量时出现的错误。 
例如本教程里的BUG只是在条件分歧时(几乎)同时调用了两个方法而导致错误。 
逻辑错误通常因人而异,也没有一定的修正方式 
怎麽修改就要看功力了。 
建议先做一个像上面的流程图,这样会比较容易找出你逻辑上错误的部分。 
 
还有就是在一些条件分歧的地方插入一些如 
 
来测试是不是有运行到脚本的某个分歧,或是检查变量是否赋值错误。  
 
              [本贴由 风雪优游 于 2008-4-2 22:03:22 进行了编辑] |   
 
 
 
 |