如何优雅的研究 RGSS3 (一) 场景中窗口的工作原理

来源:互联网 发布:软件注册码怎么破 编辑:程序博客网 时间:2024/05/15 07:27

*其实我根本不会 ruby 也不会 RGSS3,以下内容都是我瞎编的*


在RGSS3中,场景是应该是游戏的基本组成单位。

一个场景就是一个包含相同逻辑的的连续完整的画面,两个场景对象的场景类相同说明它们之间的逻辑相同,拥有相似的功能。

比如标题画面啦,地图啦,物品栏啦,技能栏啦,装备栏啦,保存啦,读档啦,战斗画面啦,GameOver画面啦等等就是默认的场景。

打开脚本可以发现,所有的场景都有一个共同的父类:Scene_Base。

所以我们从这个游戏中所有 Scene 类的父类开始研究。


一打开 Scene_Base 首先看到的就是主逻辑 main 方法,main 在各种场景类中起到了入口的作用,调用一个场景就是执行它的main方法。

而场景的 main 方法是在 SceneManager 模块中的 run 方法中调用的。

  #--------------------------------------------------------------------------  # ● 运行  #--------------------------------------------------------------------------  def self.run    DataManager.init    Audio.setup_midi if use_midi?    @scene = first_scene_class.new    @scene.main while @scene  end
可以发现,变量 @scene 是 方法 first_scene_class 返回的类的一个实例,而 first_scene_class 返回的是我们定义的玩家打开游戏后的第一个场景的类。

  #--------------------------------------------------------------------------  # ● 获取最初场景的所属类  #--------------------------------------------------------------------------  def self.first_scene_class    $BTEST ? Scene_Battle : Scene_Title  end
通常情况下是标题画面,在战斗测试的情况下是战斗场景。

获得 @scene 之后就是一个 while 循环,它不停的调用场景实例的 main 方法直到场景不再存在(即游戏结束)。


现在回到 Scene_Base 类研究 main 方法,看看 @scene 究竟在循环执行什么内容。

  #--------------------------------------------------------------------------  # ● 主逻辑  #--------------------------------------------------------------------------  def main    start    post_start    update until scene_changing?    pre_terminate    terminate  end
大意是,先执行开始处理,再执行开始后处理,然后不停更新直到场景改变,然后执行结束前处理,再执行结束处理。

可见场景中画面的改变全是依靠的 update 方法。

  #--------------------------------------------------------------------------  # ● 更新画面  #--------------------------------------------------------------------------  def update    update_basic  end  #--------------------------------------------------------------------------  # ● 更新画面(基础)  #--------------------------------------------------------------------------  def update_basic    Graphics.update    Input.update    update_all_windows  end
而 update 方法中只有一个 update_basic 方法的调用。
update_basic 中依次进行了图像(Graphics)、输入(Input)和窗口(windows)的更新。

  #--------------------------------------------------------------------------  # ● 更新所有窗口  #--------------------------------------------------------------------------  def update_all_windows    instance_variables.each do |varname|      ivar = instance_variable_get(varname)      ivar.update if ivar.is_a?(Window)    end  end
所以这个更新所有窗口的方法就是本次要研究的重点了。

instance_variables、instance_variable_get、is_a?都是 ruby 元编程中的技巧。

instance_variables 能以字符串数组的形式返回对象的实例变量名。

instance_variable_get(varname) 取得并返回对象的实例变量的值,用变量 varname 来指定实例变量名。

ivar.is_a?(Window) 判断变量 ivar 是不是类 Windows 的实例。

如果是 ivar 是窗口类的实例,那么就调用 ivar 中的 update 方法,即更新这个窗口。

因此 update_all_windows 方法实际上就是将场景实例中的所有实例变量遍历一边,如果是窗口实例的话就更新它。


接下来看游戏中所有窗口的父类 Window_Base 是怎样更新的。

class Window_Base < Window
显然 Window_Base 是 Window 的子类。

  #--------------------------------------------------------------------------  # ● 更新画面  #--------------------------------------------------------------------------  def update    super    update_tone    update_open if @opening    update_close if @closing  end
首先调用了父类 Window 的更新方法。

然后更新色调,如果处于正在打开窗口的过程中则更新打开处理,如果处于正在关闭窗口的过程则更新关闭处理。

可见窗口的开闭动画是在 Window_Base 中处理的。


现在回到场景类看窗口是怎样和场景关联起来的。

由于 Scene_Base 中没有窗口,所以现在以 Scene_Title 做例子。

  #--------------------------------------------------------------------------  # ● 开始处理  #--------------------------------------------------------------------------  def start    super    SceneManager.clear    Graphics.freeze    create_background    create_foreground    create_command_window    play_title_music  end
Scene_Title 中的开始处理方法多了一些内容,不过不要在意这些细节,直接看 create_command_window 方法。

  #--------------------------------------------------------------------------  # ● 生成指令窗口  #--------------------------------------------------------------------------  def create_command_window    @command_window = Window_TitleCommand.new    @command_window.set_handler(:new_game, method(:command_new_game))    @command_window.set_handler(:continue, method(:command_continue))    @command_window.set_handler(:shutdown, method(:command_shutdown))  end
所以在这个方法里新建了一个窗口类 Window_TitleCommand 的实例,这是标题画面中,选择“开始游戏/继续游戏”的窗口。

这样标题画面场景就与标题选项窗口关联了起来,在场景刷新的时候也会刷新窗口。

说起来 Window_TitleCommand 还是 Window_Command 的子类,Window_Command 是带有指令选择的窗口的父类。

Window_Command 又是 Window_Selectable 的子类,Window_Selectable 是拥有光标移动、滚动功能的窗口的父类。

set_handler 是 Window_Selectable 中定义的方法,command_new_game、command_continue、command_shutdown 则是 Scene_Title 中的方法。

  #--------------------------------------------------------------------------  # ● 设置动作对应的处理方法  #     method : 设置的处理方法 (Method 实例)  #--------------------------------------------------------------------------  def set_handler(symbol, method)    @handler[symbol] = method  end
正如注释所说,当窗口实例执行 symbol 对应的操作时,就调用场景中对应的 method 的方法,这是闭包的知识,它将方法作为了变量。

总之不要在意这些细节,现在看 Window_Command 好了。

class Window_Command < Window_Selectable  #--------------------------------------------------------------------------  # ● 初始化对象  #--------------------------------------------------------------------------  def initialize(x, y)    clear_command_list    make_command_list    super(x, y, window_width, window_height)    refresh    select(0)    activate  end
它在初始化的时候调用了 make_command_list 方法。

  #--------------------------------------------------------------------------  # ● 生成指令列表  #--------------------------------------------------------------------------  def make_command_list  end  #--------------------------------------------------------------------------  # ● 添加指令  #     name    : 指令名称  #     symbol  : 对应的符号  #     enabled : 有效状态的标志  #     ext     : 任意的扩展数据  #--------------------------------------------------------------------------  def add_command(name, symbol, enabled = true, ext = nil)    @list.push({:name=>name, :symbol=>symbol, :enabled=>enabled, :ext=>ext})  end
所有 Window_Command 的子类可以通过调用 add_command 添加选项到选项列表 @list 中。

@list 接受四个参数,后两个有默认值,所以前两个才是重点。

name 是指令名称,symbol 是对应的符号。


回到 Window_TitleCommand 看它重写的 make_command_list 方法。

  #--------------------------------------------------------------------------  # ● 生成指令列表  #--------------------------------------------------------------------------  def make_command_list    add_command(Vocab::new_game, :new_game)    add_command(Vocab::continue, :continue, continue_enabled)    add_command(Vocab::shutdown, :shutdown)  end
它添加了三个选项:新游戏、继续游戏、关闭游戏对应的 symbol。


我已经有点乱了...

捋一捋继承关系

Window_Base

Window_Selectable

Window_Command

Window_TitleCommand

以下内容可以跳过

-------------------------------------------------------------------------------------------------

一些跟 symbol 有关的内容。

当一个窗口中的选项被选中时。

调用 Window_Command 中的 select_symbol(symbol) 方法,用方法 select(i) 选中 symbol 所在的选项。

这里的 symbol 就是之前在 make_command_list(生成指令列表)方法中调用 add_command(添加指令)添加的 symbol 。

  #--------------------------------------------------------------------------  # ● 将光标移动到指定的标志符对应的选项  #--------------------------------------------------------------------------  def select_symbol(symbol)    @list.each_index {|i| select(i) if @list[i][:symbol] == symbol }  end

select(i) 是 Window_Selectable 中定义的方法。

  #--------------------------------------------------------------------------  # ● 选择项目  #--------------------------------------------------------------------------  def select(index)    self.index = index if index  end
它指定变量 index 指向选择的选项编号。

在 Window_Selectable 中调用 call_handler 来可以执行之前用 set_handler 方法注册的方法。

  #--------------------------------------------------------------------------  # ● 调用处理方法  #--------------------------------------------------------------------------  def call_handler(symbol)    @handler[symbol].call if handle?(symbol)  end

---------------------------------------------------------------------------------------------------

#¥#@%@#¥#@%@#

总之就是在 Window_TitleCommand 用 make_command_list 注册了一些 symbol。

在 Scene_Title 中用 create_command_window 调用 Window_TitleCommand 的实例 @command_window 中的 set_handler 方法将窗口中的 symbol 与场景中的方法关联起来。这样当窗口中的选项被选中时就会执行场景中定义的方法。


于是这就是场景中窗口的工作原理。


0 0