首页 > 代码库 > 如何优雅的研究 RGSS3 (五) 输入数字的画面

如何优雅的研究 RGSS3 (五) 输入数字的画面

游戏中的名字输入画面是一个非常没有中国特色的场景。

我们知道英文不过26个字母,日语也只有几百个假名,但是汉字的数量实在是太多了,导致名字输入画面在汉化成中文版时只能用部分汉字来填充假名。

输入名字的功能并没有什么重要价值,但是这个功能实现的方法却值得我们研究。

游戏中有一个默认的输入数字的窗口,但是它非常不好用。

今天就来参照名字输入画面编写一个数字输入画面。用于玩家向游戏中输入数字。


涉及到名字输入画面的有三个类:Scene_Name、Window_NameEdit、Window_NameInput。

Scene_Name 是场景类,Window_NameEdit 是显示当前数字的窗口,而 Window_NameInput 是输入数字用的选项。

因此我们至少也需要三个类:Scene_Number、Window_EditNumber 、Window_InputNumber。


Scene_Name 中仅有 prepare、start、on_input_ok 三个方法。

向我们的 Scene_Number 类中也添加这三个方法。但是要将初始化时需要的参数改变一下。

#encoding:utf-8
#==============================================================================
# ■ Scene_Number
#------------------------------------------------------------------------------
#  数字输入画面
#==============================================================================

class Scene_Number < Scene_MenuBase
  #--------------------------------------------------------------------------
  # ● 准备
  #--------------------------------------------------------------------------
  def prepare(var_id, max_char)
    @var_id = var_id
    @max_char = max_char
  end
  #--------------------------------------------------------------------------
  # ● 开始处理
  #--------------------------------------------------------------------------
  def start
    super
    @edit_window = Window_EditNumber.new(@max_char)
    @input_window = Window_InputNumber.new(@edit_window)
    @input_window.set_handler(:ok, method(:on_input_ok))
  end
  #--------------------------------------------------------------------------
  # ● 输入“确定”
  #--------------------------------------------------------------------------
  def on_input_ok
    $game_variables[@var_id] = @edit_window.numberToInt
    return_scene
  end
end

Scene_Number 类中对一些变量的名字做了修改。

初始化方法接受两个参数,一个是要改变的全局变量的编号,我们将输入的数字储存到全局变量中,另一个是最大的字符数,即数字的最大位数。


start 方法中生成了两个窗口类的实例。


Window_EditNumber 是用来显示输入的数字的窗口。

它的大部分代码与 Window_NameEdit 相同。

但是也有不同点需要进行修改。

在它的初始化方法中,我们仅接受 max_char 一个参数。

我们用数组 @number 来储存输入的数字,将它初始化为空,并在其它方法修改对应的操作。

数组并不能直接返回我们需要的整数,所以需要有一个方法 numberToInt 来将数组中的各个位的数字计算为一个整数。

  #--------------------------------------------------------------------------
  # ● 返回输入的数字
  #--------------------------------------------------------------------------
  def numberToInt
    return -1 if @number.empty?
    ans = 0
    for i in @number do
      ans = ans * 10 + i
    end
    return ans
  end

当返回-1时表明数组为空。

在名字输入画面中,一个文字的宽度为一个汉字的宽度,对数字来说显得略大。

  #--------------------------------------------------------------------------
  # ● 获取文字的宽度
  #--------------------------------------------------------------------------
  def char_width
    text_size("0").width
  end
把文字的宽度改为数字的宽度。

它的工作原理是这样的,在窗口初始化时,设置好窗口的大小与坐标,并将与输入数字相关的变量赋一个初值。

每当需要显示的内容改变时,就调用 refresh 方法来重新绘制窗口。

  #--------------------------------------------------------------------------
  # ● 刷新
  #--------------------------------------------------------------------------
  def refresh
    contents.clear
    @max_char.times {|i| draw_underline(i) }
    @number.size.times {|i| draw_char(i) }
    cursor_rect.set(item_rect(@index))
  end
在该方法用 draw_underline 向画面中绘制 @max_char 个下划线,然后将 @number 数组中的元素用 draw_char 方法绘制。最后指定光标的位置。

在绘制下划线和文字的过程中要用到一些辅助方法,其中最重要的是 item_rect (获取项目的绘制矩形)。

  #--------------------------------------------------------------------------
  # ● 获取名字绘制的左端坐标
  #--------------------------------------------------------------------------
  def left
    number_center = (contents_width) / 2
    number_width = (@max_char + 1) * char_width
    return [number_center - number_width / 2, contents_width - number_width].min
  end
  #--------------------------------------------------------------------------
  # ● 获取项目的绘制矩形
  #--------------------------------------------------------------------------
  def item_rect(index)
    Rect.new(left + index * char_width, 0, char_width, line_height)
  end


item_rect 方法调用 left 方法求出最左端的坐标,然后用 index 与 char_width 来计算矩形的偏移量。


为了使窗口的样式更加美观,item_rect 方法中用于确定绘制坐标的参数与 initialize 方法中用于确定窗口大小与坐标参数都要进行合理的调整与设置。


最后是 Window_InputNumber,数字输入画面中,选择数字的窗口。

为了输入10个数字与确认输入,该窗口至少应该有12个选项。

  #--------------------------------------------------------------------------
  # ● 数字表
  #--------------------------------------------------------------------------
  TABLE = [ 7,8,9,
            4,5,6,
            1,2,3,
            0,'归零','确定']
用 4*3 的数字表来表示12个选项。

  #--------------------------------------------------------------------------
  # ● 初始化对象
  #--------------------------------------------------------------------------
  def initialize(edit_window)
    super(edit_window.x+edit_window.width/2-60, edit_window.y + edit_window.height + 8,
          120, fitting_height(4))
    @edit_window = edit_window
    @page = 0
    @index = 0
    refresh
    update_cursor
    activate
  end

在初始化方法中对窗口的大小与坐标做适当的调整使其恰好能显示12个选项。


在输入名字的窗口类 Window_NameInput 中,有太多的 Magic Number,需要将其一一调整,使其适应4*3的选项窗口。

默认按键的处理方法在父类 Window_Selectable 中已经写好了,process_cursor_move 方法用于处理光标的移动无需修改。

但是按下确定和取消键时需要进行我们自己设定的操作,所以重写 process_handling 方法。

  #--------------------------------------------------------------------------
  # ● “确定”、“删除字符”和“取消输入”的处理
  #--------------------------------------------------------------------------
  def process_handling
    return unless open? && active
    process_jump if Input.trigger?(:A)
    process_back if Input.repeat?(:B)
    process_ok   if Input.trigger?(:C)
  end

process_ok 方法在处理确定键时考虑了三种情况:

当前选项位于归零键时,将数字设为0。

当前选项位于数字键时,添加对应数字。

当前选项位于确认键时,判断输入是否为空,若不为空则才可以正常调用 call_ok_handler 调用 Scene_Number 类指定的确认方法。


Window_InputNumber 完整代码如下:

#encoding:utf-8
#==============================================================================
# ■ Window_InputNumber
#------------------------------------------------------------------------------
#  数字输入画面中,选择数字的窗口。
#==============================================================================

class Window_InputNumber < Window_Selectable
  #--------------------------------------------------------------------------
  # ● 数字表
  #--------------------------------------------------------------------------
  TABLE = [ 7,8,9,
            4,5,6,
            1,2,3,
            0,'归零','确定']
  #--------------------------------------------------------------------------
  # ● 初始化对象
  #--------------------------------------------------------------------------
  def initialize(edit_window)
    super(edit_window.x+edit_window.width/2-60, edit_window.y + edit_window.height + 8,
          120, fitting_height(4))
    @edit_window = edit_window
    @page = 0
    @index = 0
    refresh
    update_cursor
    activate
  end
  #--------------------------------------------------------------------------
  # ● 获取字表
  #--------------------------------------------------------------------------
  def table
    return [TABLE]
  end
  #--------------------------------------------------------------------------
  # ● 获取文字
  #--------------------------------------------------------------------------
  def character
    @index < 12 ? table[@page][@index] : ""
  end
  #--------------------------------------------------------------------------
  # ● 判定光标位置是否在“归零”上
  #--------------------------------------------------------------------------
  def is_back?
    @index == 10
  end
  #--------------------------------------------------------------------------
  # ● 判定光标位置是否在“确定”上
  #--------------------------------------------------------------------------
  def is_ok?
    @index == 11
  end
  #--------------------------------------------------------------------------
  # ● 获取项目的绘制矩形
  #--------------------------------------------------------------------------
  def item_rect(index)
    rect = Rect.new
    rect.x = index % 3 * 32 + index % 3 / 5 * 16
    rect.y = index / 3 * line_height
    rect.width = 32
    rect.height = line_height
    rect
  end
  #--------------------------------------------------------------------------
  # ● 刷新
  #--------------------------------------------------------------------------
  def refresh
    contents.clear
    change_color(normal_color)
    12.times {|i| draw_text(item_rect(i), table[@page][i], 1) }
  end
  #--------------------------------------------------------------------------
  # ● 更新光标
  #--------------------------------------------------------------------------
  def update_cursor
    cursor_rect.set(item_rect(@index))
  end
  #--------------------------------------------------------------------------
  # ● 判定光标是否可以移动
  #--------------------------------------------------------------------------
  def cursor_movable?
    active
  end
  #--------------------------------------------------------------------------
  # ● 光标向下移动
  #     wrap : 允许循环
  #--------------------------------------------------------------------------
  def cursor_down(wrap)
    if @index < 9 or wrap
      @index = (index + 3) % 12
    end
  end
  #--------------------------------------------------------------------------
  # ● 光标向上移动
  #     wrap : 允许循环
  #--------------------------------------------------------------------------
  def cursor_up(wrap)
    if @index >= 3 or wrap
      @index = (index - 3 + 12) % 12
    end
  end
  #--------------------------------------------------------------------------
  # ● 光标向右移动
  #     wrap : 允许循环
  #--------------------------------------------------------------------------
  def cursor_right(wrap)
    if @index % 3 < 2
      @index += 1
    elsif wrap
      @index -= 2
    end
  end
  #--------------------------------------------------------------------------
  # ● 光标向左移动
  #     wrap : 允许循环
  #--------------------------------------------------------------------------
  def cursor_left(wrap = false)
    if @index % 3 > 0
      @index -= 1
    elsif wrap
      @index += 2
    end
  end
  #--------------------------------------------------------------------------
  # ● 向下一页移动
  #--------------------------------------------------------------------------
  def cursor_pagedown
    refresh
  end
  #--------------------------------------------------------------------------
  # ● 向上一页移动
  #--------------------------------------------------------------------------
  def cursor_pageup
    refresh
  end
  #--------------------------------------------------------------------------
  # ● 处理光标的移动
  #--------------------------------------------------------------------------
  def process_cursor_move
    last_page = @page
    super
    update_cursor
    Sound.play_cursor if @page != last_page
  end
  #--------------------------------------------------------------------------
  # ● “确定”、“删除字符”和“取消输入”的处理
  #--------------------------------------------------------------------------
  def process_handling
    return unless open? && active
    process_jump if Input.trigger?(:A)
    process_back if Input.repeat?(:B)
    process_ok   if Input.trigger?(:C)
  end
  #--------------------------------------------------------------------------
  # ● 跳转“确定”
  #--------------------------------------------------------------------------
  def process_jump
    if @index != 11
      @index = 11
      Sound.play_cursor
    end
  end
  #--------------------------------------------------------------------------
  # ● 后退一个字符
  #--------------------------------------------------------------------------
  def process_back
    Sound.play_cancel if @edit_window.back
  end
  #--------------------------------------------------------------------------
  # ● 按下确定键时的处理
  #--------------------------------------------------------------------------
  def process_ok
    if is_back?
      @edit_window.restore_default
      @edit_window.add(0)
    elsif !is_ok?
      on_number_add
    elsif is_ok?
      on_number_ok
    end
  end
  #--------------------------------------------------------------------------
  # ● 添加数字字符
  #--------------------------------------------------------------------------
  def on_number_add
    if @edit_window.add(character)
      Sound.play_ok
    else
      Sound.play_buzzer
    end
  end
  #--------------------------------------------------------------------------
  # ● 确定数字
  #--------------------------------------------------------------------------
  def on_number_ok
    if @edit_window.numberToInt == -1
        Sound.play_buzzer
    else
      Sound.play_ok
      call_ok_handler
    end
  end
end

如此三个类就全部写好了。

在游戏的事件中添加如下脚本

SceneManager.call(Scene_Number)

SceneManager.scene.prepare(1, 10)

便可以进入数字输入画面向指定的全局变量中输入数字了。