注册会员 登录
Project1 返回首页

亿万星辰的深邃空间 https://rpg.blue/?62841 [收藏] [复制] [分享] [RSS]

日志

【跟我一起写脚本】技能商店

热度 3已有 577 次阅读2011-9-17 09:22 |个人分类:坑爹的RGSS教程

虽然是已经有过的系统了,不过这里主要描述的是对于一个脚本的编写过程,希望广大同学能够从中发现一些自己需要的东西,当然,这只是我个人的编写习惯,毕竟会有一些不足,大家也可以提出,共同进步。好了,废话到此,下面开始。

我们首先要做的是,在写脚本前,先构思好整个脚本的框架结构,在玩家操作的哪一步时是什么样子的都要心里有数。必要的时候还可以通过绘图软件简单的进行绘制、修改,免得写好之后出现不足,这样就变得复杂得多了。

先说逻辑上,既然是技能商店,那么就是购买技能,我的设定是,技能都是所有职业通用,不过是对等级以及属性有一些限制,购买的货币是经验点。

想到这里,大致上就应该考虑下面这几个问题,
1 贩卖的技能有多少?全部还是部分,全部的话会不会因为太多而使玩家看到眼花缭乱,部分的话如何设置这个部分技能。我这里为了方便就直接弄成全部技能了。
2 “对等级以及属性有一些限制”,这个限制该如何实现?实现的方法有很多:直接在脚本前通过常量给定;RPG::Skill里增加新的属性字段;或者还有其他的种种方法。我这里使用了第二个方法,从技能的说明文字中提取。
(这里要一提的是,我这里写的这部分解说完全是根据我个人对这个系统的理解写的,大家在写的时候可能会有自己的想法,也许会用其他两种方法里的一种,这并不是说不对,只是大家的侧重点不同而已。)
3 技能的价格如何规定。我决定使用RPG::Skill里增加新的属性字段,通过技能名按逗号分隔的方法。

界面上,不做过多的修饰了,整个界面最上方是一个640*64的help窗口,用于对技能的描述;接着是一个640*64的用于描绘角色名称和经验点的窗口;最后是一个640*352的窗口描绘当前贩卖的技能,描绘格式大致是:

技能名                             : xxxxxx

另外还打算有一个浮动的窗口在指向某个技能时描绘这个技能的需求以及当前角色的对应情况,这个是在下半部分中进行详细的叙述。

好了,下面就可以都手开始写窗口了。

○、我们从上到下依次来写。
①、help窗口,系统本来就有,略了。
②、角色名称信息窗口,我们来详细看看。
首先分析,这个窗口是静态还是动态的,很显然,是静态的,它不参与角色的键盘动作,或者说,它里面没有选项部分,一旦描绘一次,就可以不必再对其进行刷新处理,直到切换角色或者消费EXP购买了某个技能以后才会对其进行刷新。所以我们以一个静态窗口来做模型进行编写,Window_Gold。
复制Window_Gold,在Main脚本前插入,修改名称为Window_SkillShop_Name,同时修改脚本正文部分的名称:
class Window_SkillShop_Name < Window_Base
好了,这样我们需要的窗口的模型就有了,下面开始改造。

    super(0, 0, 160, 64)
改为
    super(0, 64, 640, 64)
这样窗口大小就符合我们的要求了。

接着考虑,这个窗口描绘的是角色的名称和经验点,那么必须要知道角色是谁,所以在窗口初始化的时候,要通过一个参数把角色信息发过来。
  def initialize
改为
  def initialize(actor)
    @actor = actor
这样,角色信息就有了。而且注意,我用@actor来保存了actor的值,这样在类里其他地方,也可以通过@actor来获取这个角色的信息了。提问,如果我没有做这一步的话,是什么结果呢?

然后刷新部分必须要大改一下,保留“self.contents.clear”一句,其余的删掉。
然后根据我们之前的描述开始编写内容。由于只是用作演示,所以我描绘的比较简单。
  #--------------------------------------------------------------------------
  # ● 刷新
  #--------------------------------------------------------------------------
  def refresh
    self.contents.clear
    self.contents.draw_text(4, 0, 160, 32, @actor.name)
    self.contents.draw_text(492, 0, 112, 32, @actor.exp.to_s, 2)
  end
我只描绘了角色的名称和经验点,位置也设置的比较简易,至于想描绘其他信息的同学,可以自行添加。

至此,窗口的功能已经实现,让我们来实验一下,地图上随便摆个事件,操作为执行脚本,脚本内容为Window_SkillShop_Name.new($game_actors[1]),然后看看结果是否如愿显示出了1号角色的名称和目前的经验点。
接着考虑问题,如果只是现在这样就算窗口编写完毕,那么当我们切换角色时,窗口中的内容该如何改变呢?先看我是如何实现的。
  #--------------------------------------------------------------------------
  # ● 角色变更
  #--------------------------------------------------------------------------
  def set_actor(actor)
    if @old_actor.nil? or @old_actor != actor
      @actor = actor
      @old_actor = @actor
      refresh
    end
  end
当切换角色的时候,只要调用这个方法,就可以变更@actor这个变量的值,并重绘窗口。

到此,整个窗口的描绘就已经完成了,而且也可以实现要求的功能。不过还可以做一些小的修缮。
  #--------------------------------------------------------------------------
  # ● 描绘经验点
  #--------------------------------------------------------------------------
  def draw_exp
    self.contents.fill_rect(492, 0, 112, 32, Color.new(0, 0, 0, 0))
    self.contents.draw_text(492, 0, 112, 32, @actor.exp.to_s, 2)
  end
写这个方法在于,在对一个角色进行技能交易以后,窗口中的经验点部分必然会发生刷新,所以有了这个方法。

③、技能出售窗口。
这个窗口首先很类似物品窗口,有选择项,所以它是一个选项类的动态窗口。我们可以用Window_Item为基础对其进行修改。
同样的,复制Window_Item,在Main脚本前插入,修改名称为Window_SkillShop,同时修改脚本正文部分的名称:
class Window_SkillShop < Window_Selectable
这样,模型就好了,下面开始逐步修改。我写的修改次序是根据我个人的习惯来的,同学们可以自行融会贯通。

首先修改初始化部分为。
  #--------------------------------------------------------------------------
  # ● 初始化对像
  #--------------------------------------------------------------------------
  def initialize(actor)
    @actor = actor
    super(0, 128, 640, 352)
    @column_max = 1
    refresh
    self.index = 0
  end
注意修改的几个地方,坐标及大小,还有列数我改成了1,这样就是只显示1列了。还有就是最关键的,增加了actor参数,因为窗口中的内容是会根据角色是否掌握该技能而发生变化的,所以要把角色部分传递进来。

获取技能部分。
  #--------------------------------------------------------------------------
  # ● 获取技能
  #--------------------------------------------------------------------------
  def skill
    return @data[self.index]
  end
这里只把物品改成了技能,这样代码更好读一些。

下面的刷新部分因为前面我说了是列出所有的技能,所以这里就相对简单一些了。
  #--------------------------------------------------------------------------
  # ● 刷新
  #--------------------------------------------------------------------------
  def refresh
    if self.contents != nil
      self.contents.dispose
      self.contents = nil
    end
    @data = []
    # 添加所有技能
    for j in 1...$data_skills.size
      @data.push($data_skills[j])
    end
    # 如果项目数不是 0 就生成位图、重新描绘全部项目
    @item_max = @data.size
    if @item_max > 0
      self.contents = Bitmap.new(width - 32, row_max * 32)
      for j in 0...@item_max
        draw_item(j)
      end
    end
  end
修改成这样就好了,同学们可以对比一下修改的部分,主要是“添加技能”那一块。

之后的描绘内容部分,就是纯粹的对坐标自行发挥的部分了。但是现在我们要考虑一些问题:
第一、技能出售的时候是有一个需要消耗的经验点数的,也就是价格,这个值在一开始也提到过了,需要用给RPG::Skill新增额外属性的方法来进行判断。
第二、技能在窗口中的显示,我暂且归纳出这么几类:经验足够且满足技能的属性点要求、经验足够但不满足技能的属性点要求、经验不足、已掌握的技能,这四种。所以在描绘时还要考虑这个因素。
第三、出售窗口和角色也是有着一一对应的关系的,所以要仿照角色名称信息窗口,增加一个设定角色的方法。

综上所述,我们需要增加若干的方法来为这个描绘界面做铺垫。
技能的购买价格,我们通过技能名里加逗号分隔的方法实现,而技能的等级及属性点要求我们通过在技能的说明栏里加逗号分隔的方法实现。于是有了下面的代码
module RPG
  class Skill
    def name
      return @name.split(",")[0]
    end
    # 技能购买价格
    def exp
      return @name.split(",")[1].to_i
    end
    def description
      return @description.split(",")[0]
    end
    # 技能属性限制值判断(等级 力量 灵活 速度 魔力)
    def limit?(data)
      return false if data.size != 5 or @description.split(",").size != 6
      temp = []
      for j in 1...6
        temp.push(@description.split(",")[j].to_i)
      end
      for j in 0...5
        return false if data[j] < temp[j]
      end
      return true
    end
  end
end

有的同学会问这一堆东西是从哪来的?
其实这是F1中就有的内容,打开F1,搜索RPG,会看到很多RPG::XXX这样的标题,而这些正是我们在工程中的数据库所用的类,也就是Data文件夹里保存的那些文件中的信息所用类。
我们是要对技能下手,好,找到RPG::Skill,到下面可以看到它的定义部分,我们可以看到有很多attr_accessor这样定义过的变量,而通过我们以往所知道的知识,attr_accessor :name是等价于def name以及def name=(name)这两个方法的定义的。下面的工作其实就是要对def name和def description两个方法进行重定义,把对逗号分隔信息这一个理念传达进去,于是就有了上面的方法。
我再挨个解释一下新增加的那几个方法。

先写个技能数据库里的名称:治疗,20。这个技能指的是,治疗技能需要20经验点才可以购买。
def name里的函数体:return @name.split(",")[0]。意思就是要把@name属性(也就是数据库里名称那一栏里我们填的文字)中以“,”分隔的第1部分(程序中的下标最开始是0)内容返回。也就是上面写的“治疗”。
def exp里的函数体:return @name.split(",")[1].to_i。道理和刚才是一样的,是要把@name属性中以“,”分隔的第2部分内容返回,但是要将其转换为整数。最终得到的就是一个整数——20。
下面来看说明部分的处理:回复己方单体少量 HP。,1,60,30,40,20。这个说明指的是,抛开前面的说明文字“回复己方单体少量 HP。”不说,要求购买的角色等级大于1,力量大于60,灵活大于30,速度大于40,魔力大于20。
def description里的内容和刚才的def name是一回事,不再多说了。
重点看看下面的判断方法。
在这里要明白这个判断方法是要做什么判断,我们可以这么想,当我们在判断这个角色是否可以购买这个技能的时候,将这个角色的等级、力量、灵活、速度、魔力5个值通过参数传递给这个技能的这个判断方法,然后这个方法就是要看看角色的这5个值是不是都大于等于技能要求的那5个值,如果满足则可以购买,反之不行。

下面看内容
return false if data.size != 5 or @description.split(",").size != 6
首先这行是做预处理的,当给的参数数量不足5个,或技能的说明栏文字分隔后不足6个的,均视为无效,返回一个伪值。
temp = []
for j in 1...6
  temp.push(@description.split(",")[j].to_i)
end
这部分通过定义一个临时数组temp,存放了说明文字中的后5个数字。
for j in 0...5
  return false if data[j] < temp[j]
end
这个则是用参数中的数字和刚才放在temp里的5个数字进行比对,如果一旦出现不满足条件则返回一个伪值。
return true
如果顺利通过的比对,则返回真。

加入这部分代码之后,技能的判断就OK了。但为了方便,我们要给角色设置一个获取那5个数值的方法,而这个方法,只要定义在Game_Actor中即可。
class Game_Actor < Game_Battler
  # 角色属性值获取(等级 力量 灵活 速度 魔力)
  def data
    return [self.level, self.str, self.dex, self.agi, self.int]
  end
end
这样一来,以后要取这几个值的时候就方便多了。

接着,为了方便在出售窗口对这些逻辑进行判断,我们再在出售窗口Window_SkillShop里增加一个方法。
  #--------------------------------------------------------------------------
  # ● 是否可以购买
  #--------------------------------------------------------------------------
  def can_buy?(skill = nil)
    skill = self.skill if skill.nil?
    # 角色未掌握 并且 各项属性要求都满足 并且 购买技能所需经验小于等于角色经验
    return ([email protected]_learn?(skill.id) and skill.limit?(@actor.data) and skill.exp <= @actor.exp)
  end

先看这个函数的参数,在函数定义时的参数中可以设定缺省值,方法就是直接用“=”+具体值的方法来实现,当参数有缺省值的时候,在调用函数时,可以省略参数。我这里给了skill这个参数的缺省值是nil,目的听我慢慢道来。
skill = self.skill if skill.nil?
这个意思就是说,如果skill(参数)的值为nil的时候,就让skill的值为当前光标指向的技能(self.skill)。
这个的目的就是说,我可以通过参数里给一个具体的技能来判断当前角色是否可以购买这个技能,也可以不写参数,直接判断当前光标指向的技能是否可以购买。
而这个函数最终判断的依据是:角色没有掌握当前技能 并且 该角色的属性值满足技能的属性值要求 并且 角色的经验点足够购买这个技能。

通过增加了这些方法以后,在描绘出售窗口的时候,就容易许多了。
  #--------------------------------------------------------------------------
  # ● 描绘项目
  #     index : 项目编号
  #--------------------------------------------------------------------------
  def draw_item(index)
    skill = @data[index]
    x = 4
    y = index * 32
    rect = Rect.new(x, y, self.width / @column_max - 32, 32)
    self.contents.fill_rect(rect, Color.new(0, 0, 0, 0))
    bitmap = RPG::Cache.icon(skill.icon_name)
    opacity = self.can_buy?(skill) ? 255 : 128
    self.contents.blt(x, y + 4, bitmap, Rect.new(0, 0, 24, 24), opacity)
    self.contents.font.color = Color.new(255, 255, 255, opacity)
    self.contents.draw_text(x + 28, y, 460, 32, skill.name, 0)
    self.contents.draw_text(x + 488, y, 16, 32, ":", 1)
    if @actor.skill_learn?(skill.id)
      self.contents.draw_text(x + 504, y, 96, 32, "已掌握", 1)
    else
      self.contents.draw_text(x + 504, y, 96, 32, skill.exp.to_s, 2)
    end
  end
代码中可以看到,在描绘技能名称时,通过判断技能是否可以购买(self.can_buy?(skill)),来确定这个技能描绘时的透明度。
而后面又根据角色是否已经掌握了这个技能来判断描述时的文字是“已掌握”还是技能的价格。
到这里为止,三个主体窗口就已经编写完毕了,抛开之前说的用于描述技能限制值的具体数值的浮动窗口,现在整个系统已经可以运作了,下面就让我们尝试着让它们一起干活。

要让窗口合理运作,就要编写一个Scene场景类,也就是F11里相对靠下的那一些类,游戏的运作都是在这些场景类中运行的。但书写这部分脚本很多同学都是倍感头痛,下面我写出我个人在编写这类窗口的方法。
首先复制一份已有的脚本“Scene_Shop”到脚本Main之前,并改名为Scene_SkillShop。(并不是因为我们写的是商店就要复制原来的商店场景,我不论写什么场景都是复制Scene_Shop的,原因就是这个类的代码比较容易修改,而且没有多余的判断。其实所有的场景类的结构都是一样的。另外,想自己写的话也完全没有问题。)
好了,复制过来以后先考虑一个问题:大家知道在默认的菜单场景Scene_Menu里,当我们从技能菜单返回主菜单场景时,光标并不是在第一个位置,这个是为什么呢?先去看看Scene_Menu里是如何实现的。
  #--------------------------------------------------------------------------
  # ● 初始化对像
  #     menu_index : 命令光标的初期位置
  #--------------------------------------------------------------------------
  def initialize(menu_index = 0)
    @menu_index = menu_index
  end
原来是有这样的一个初始化方法,通过调用初始化函数.new时,跟一个参数:$scene = Scene_Menu.new(1),这样在出现主菜单场景时,光标就会停留在第二个选项上。
同样的,对于我们今天要写的这个技能商店,是否也需要有这样的设定呢?在打开技能商店时,当前的购买角色可以用类似的方法进行灵活的设置。
所以,我们首先要给我们搬来的代码增加一个初始化方法:
  #--------------------------------------------------------------------------
  # ● 初始化对像
  #     actor_id : 队伍编号
  #--------------------------------------------------------------------------
  def initialize(actor_id = 0)
    @actor_id = actor_id
    @actor = $game_party.actors[actor_id]
  end
通过这个方法,就可以自由的设置一打开商店时当前的购买角色了。
下面的修改会比较的大方,会把整个脚本先删的片甲不留,然后在逐步填满。
main方法最后要保留的就是这些:
  def main
    # 执行过渡
    Graphics.transition
    # 主循环
    loop do
      # 刷新游戏画面
      Graphics.update
      # 刷新输入信息
      Input.update
      # 刷新画面
      update
      # 如果画面切换的话就中断循环
      if $scene != self
        break
      end
    end
    # 准备过渡
    Graphics.freeze
    # 释放窗口
  end
update方法只保留空函数体
  def update
  end
之后的内容统统删掉。
哇哦,一瞬间,脚本变得好短……

下面开始往里面添加我们自己的内容。
首先我们目前的情况,该场景中三个窗口,帮助文字窗口、角色名称窗口、技能出售窗口,所以,我们添加一部分内容到脚本的def main方法里。
  #--------------------------------------------------------------------------
  # ● 主处理
  #--------------------------------------------------------------------------
  def main
    # 生成命令窗口
    @help_window = Window_Help.new
    @shop_window = Window_SkillShop.new(@actor)
    @shop_window.help_window = @help_window
    @name_window = Window_SkillShop_Name.new(@actor)
    # 执行过渡
    Graphics.transition
    # 主循环
    loop do
      # 刷新游戏画面
      Graphics.update
      # 刷新输入情报
      Input.update
      # 刷新画面
      update
      # 如果画面切换的话就中断循环
      if $scene != self
        break
      end
    end
    # 准备过渡
    Graphics.freeze
    # 释放窗口
    @help_window.dispose
    @shop_window.dispose
    @name_window.dispose
  end
细看的话,其实加的东西很少的,就是前面加了窗口的初始化,以及帮助窗口的挂接,后面加了窗口的dispose方法。
再来看看update方法添加了一些什么。
  def update
    # 刷新命令窗口
    @help_window.update
    @shop_window.update
  end
哎?就这两行?别小看这两行,测试一下看看。不过测试前要先注意一点,你的技能数据库里的技能名称啊,技能说明啊,是不是已经做好了那些应该做的准备,增加了对应的属性段了么?如果还没做好的话,赶紧先去做吧。

做好准备工作以后,刚才地图上那个测试用的事件还在么?修改它的脚本内容为:$scene = Scene_SkillShop.new 来进行调用,看看是什么样子。
虽然回车、ESC的都还没用,但是方向键控制,还有帮助窗口对应技能的变化都是没问题了。下面要做的就是针对按键的逻辑部分的处理了。
(下面的内容都是def update里面的,只需要放到@shop_window.update下面即可。)
最简单的一个就是按下B键的操作,这个好多地方都有,照猫画虎来一个先。
    # 按下 B 键的情况下
    if Input.trigger?(Input::B)
      # 演奏取消 SE
      $game_system.se_play($data_system.cancel_se)
      # 切换到菜单画面
      $scene = Scene_Map.new
      return
    end

实现了什么呢?响一声,然后回到地图界面上。
下面来写按下C键的操作。这就需要考虑考虑了,首先得看当前技能出售窗口光标指向的技能是否可以购买,这个不难,刚才我们已经留下了对应的函数(Window_SkillShop--can_buy?),只需要调用判断一下即可;接着如果能购买的话,角色要掌握该技能(Game_Actor--learn_skill),而且还要减掉对应的经验点(Game_Actor--exp);最后刷新一下角色名称窗口和技能出售窗口(Window_SkillShop_Name--draw_exp  Window_SkillShop--refresh)。想好了就动手。
    # 按下 C 键的场合下
    if Input.trigger?(Input::C)
      # 获取当前技能
      @skill = @shop_window.skill
      # 若此技能角色目前无法购买
      unless @shop_window.can_buy?
        # 演奏无效 SE
        $game_system.se_play($data_system.buzzer_se)
        return
      else
        # 演奏确定 SE
        $game_system.se_play($data_system.decision_se)
        # 角色掌握技能
        @actor.learn_skill(@skill.id)
        # 角色经验减少
        @actor.exp -= @skill.exp
        # 窗口重绘
        @name_window.draw_exp
        @shop_window.refresh
      end
      return
    end
首先通过@shop_window.skill获取技能,然后通过@shop_window.can_buy?判断是否可以购买,若无法购买,演奏无效SE并返回;若可以购买则演奏确定SE,角色掌握技能,角色减掉EXP,角色名称窗口重绘经验点,技能出售窗口重绘。
(注意:默认的系统里,EXP的减少会牵扯一个角色降级的问题,我在这里没有做出处理。)

现在测试一下,就可以进行技能的交易了。不过只限于当前一个角色,让我们再问它增加上切换角色的功能。
    # 按下左键的场合下
    if Input.trigger?(Input::LEFT)
      # 演奏光标 SE
      $game_system.se_play($data_system.cursor_se)
      # 若角色不足两名则返回
      return if $game_party.actors.size < 2
      # 前一角色
      @actor_id -= 1
      @actor_id %= $game_party.actors.size
      @actor = $game_party.actors[@actor_id]
      # 刷新对应窗口
      @name_window.set_actor(@actor)
      @shop_window.set_actor(@actor)
      return
    end
    # 按下右键的场合下
    if Input.trigger?(Input::RIGHT)
      # 演奏光标 SE
      $game_system.se_play($data_system.cursor_se)
      # 若角色不足两名则返回
      return if $game_party.actors.size < 2
      # 后一角色
      @actor_id += 1
      @actor_id %= $game_party.actors.size
      @actor = $game_party.actors[@actor_id]
      # 刷新对应窗口
      @name_window.set_actor(@actor)
      @shop_window.set_actor(@actor)
      return
    end
这部分操作没什么难点,关键点一个是@actor_id的增减后的溢出处理,另一个就是两个窗口针对角色改变而出现的重绘。

现在整个场景的编写已经大致结束了,测试一下看看整体的效果。

上半部分完。

在上半部分中,我们已经基本实现了最开始制定的系统目标,只留下了最后一条:
一个浮动的窗口在指向某个技能时描绘这个技能的需求以及当前角色的对应情况。

下半部分中主要讲述这部分的实现过程。

还是老规矩,遇到问题先分析,
1、这个窗口是一个无选择项的静态窗口,所以依然是基于Window_Base类的。
2、这个窗口中需要的数据分别是技能的限制值,以及角色的属性值。后者我们已经在上半部分中写了对应的方法,而前者我们当时没有编写,看来还是要补充一下。还有,技能和角色都要设法传递到函数内部。
3、窗口要浮动该如何实现,显然是纵坐标根据当前光标进行的一系列变化。

想到这里,基本可以开始动工了。

拿出一个用于描绘无选择项窗口的模板结构(Window_Gold),删除掉多余内容后如下:
class Window_Xxxx < Window_Base
  #--------------------------------------------------------------------------
  # ● 初始化窗口
  #--------------------------------------------------------------------------
  def initialize
    super(xxx, yyy, www, hhh)
    self.contents = Bitmap.new(width - 32, height - 32)
    refresh
  end
  #--------------------------------------------------------------------------
  # ● 刷新
  #--------------------------------------------------------------------------
  def refresh
    self.contents.clear
  end
end

动工做了一些修改之后,变成了:
class Window_SkillShop_Info < Window_Base # 名字一定要改
  #--------------------------------------------------------------------------
  # ● 初始化窗口
  #--------------------------------------------------------------------------
  def initialize(skill, actor) # 技能和角色作为参数进行传递
    @skill = skill
    @actor = actor
    super(0, 0, 256, 152) # 坐标先不管,宽和高我定成了这个
    self.contents = Bitmap.new(width - 32, height - 32)
    self.contents.font.size = 16 # 顺便把文字的字号也做个修改
    refresh
  end
  #--------------------------------------------------------------------------
  # ● 刷新
  #--------------------------------------------------------------------------
  def refresh
    self.contents.clear
    self.contents.font.color = system_color # 颜色使用系统颜色
    # 下面就是一些文字的描绘,就是整体结构的设计,不多说了
    self.contents.draw_text(64, 0, 80, 20, "需求值", 1)
    self.contents.draw_text(144, 0, 80, 20, "当前值", 1)
    self.contents.draw_text(4, 20, 40, 20, "等级")
    self.contents.draw_text(4, 40, 40, 20, $data_system.words.str)
    self.contents.draw_text(4, 60, 40, 20, $data_system.words.dex)
    self.contents.draw_text(4, 80, 40, 20, $data_system.words.agi)
    self.contents.draw_text(4, 100, 40, 20, $data_system.words.int)
  end
end

但是到现在为止,其实一直都还没描述真正该描述的部分——技能的限制值和角色的属性值。还记得刚才说要补充一个技能限制值的方法么,来做做看。
(以下方法在下面脚本RPG::Skill的★处)
module RPG
  class Skill
    ★
  end
end

    # 技能属性限制值获取(等级 力量 灵活 速度 魔力)
    def data
      return [0, 0, 0, 0, 0] if @description.split(",").size != 6
      temp = []
      for j in 1...6
        temp.push(@description.split(",")[j].to_i)
      end
      return temp
    end

这段代码很熟悉吧,在上半部分中的限制值判断中有类似的部分,这次单独将这部分摘出来以后,就可以直接让技能获取其限制值了。同时,判断方法也可以简化一下。

    # 技能属性限制值判断(等级 力量 灵活 速度 魔力)
    def limit?(data)
      return false if data.size != 5 or @description.split(",").size != 6
      temp = self.data
      for j in 0...5
        return false if data[j] < temp[j]
      end
      return true
    end

这样用上了新编写的获取方法以后,又简单了不少。
顺便说一下,上面两个方法里我都用到了temp这个变量,有的同学是不是要问,两个temp之间会不会有什么冲突呢?
这里要牵扯到一个变量的作用域,有时也叫生存期。我这里简单说一下子,变量分为局部变量、实变量和全局变量(其实还有一种@@开头的类变量)。其中局部变量是以小写字母或者是“_”开头的,它的作用域只存在于当前的函数中,也可以理解成只要当前函数执行完毕,这个变量将不复存在,所以不同方法中的相同名称的局部变量之间不会有冲突;实变量是以@开头的变量,它的作用域是在类或子类的方法中,也就是在一个类里的所有方法中出现的实变量指的都是一个变量;而全局变量的作用域更广,覆盖整个程序的始终,在程序的任何地方都可以调用。
所以上面例子里的两个temp是没有直接关系的,仅仅是用了相同的名字而已。

有了获取技能限制值的方法以后,我们的窗口可以继续了。
再看看我修改以后的refresh方法
  #--------------------------------------------------------------------------
  # ● 刷新
  #--------------------------------------------------------------------------
  def refresh
    self.contents.clear
    self.contents.font.color = system_color
    self.contents.draw_text(64, 0, 80, 20, "需求值", 1)
    self.contents.draw_text(144, 0, 80, 20, "当前值", 1)
    self.contents.draw_text(4, 20, 40, 20, "等级")
    self.contents.draw_text(4, 40, 40, 20, $data_system.words.str)
    self.contents.draw_text(4, 60, 40, 20, $data_system.words.dex)
    self.contents.draw_text(4, 80, 40, 20, $data_system.words.agi)
    self.contents.draw_text(4, 100, 40, 20, $data_system.words.int)
    for j in 0...5
      self.contents.font.color = normal_color
      self.contents.draw_text(64, 20 + j * 20, 80, 20, @skill.data[j].to_s, 1)
      self.contents.font.color = (@skill.data[j] > @actor.data[j]) ? Color.new(255, 0, 0) : Color.new(0, 255, 0)
      self.contents.draw_text(144, 20 + j * 20, 80, 20, @actor.data[j].to_s, 1)
    end
  end

好了,现在窗口的描绘都已经完成了,还没有测试过?事件里的脚本,加入代码:
Window_SkillShop_Info.new(
$game_skills[1], $game_actors[1])

现在看来,只要把这个窗口代入到场景中就行了!
先等等,想想看有没有缺少什么东西?角色和技能的变更要如何实现呢?
下面再添加两个用于变更窗口中角色和技能的方法。

  #--------------------------------------------------------------------------
  # ● 角色信息变更
  #--------------------------------------------------------------------------
  def set_actor(actor)
    if @old_actor.nil? or @old_actor != actor
      @actor = actor
      @old_actor = @actor
      refresh
    end
  end
  #--------------------------------------------------------------------------
  # ● 技能信息变更
  #--------------------------------------------------------------------------
  def set_skill(skill)
    if @old_skill.nil? or @old_skill != skill
      @skill = skill
      @old_skill = @skill
      refresh
    end
  end

也是似曾相识的,在之前两个窗口中对角色信息的变更时用的也是这样的方法。
现在我们再把窗口代入到场景中去试试看。
Scene_SkillShop的main方法中加入一行初始化
    # 生成命令窗口
    @help_window = Window_Help.new
    @shop_window = Window_SkillShop.new(@actor)
    @shop_window.help_window = @help_window
    @name_window = Window_SkillShop_Name.new(@actor)
    @info_window = Window_SkillShop_Info.new(@shop_window.skill, @actor)
好了,对应的最后增加上窗口的dispose方法。然后接下来看看这个窗口的刷新时机。
有的同学会问,什么事刷新时机,简单点说就是窗口应该在什么时候进行刷新,大家都知道,如果一个窗口在每帧都进行一次刷新也就是refresh操作,那么整个过程的效率将是非常的低,直接的体现就是很卡。那么解决这个问题最简单的方法就是控制刷新时机,让窗口在需要刷新的时候刷新,不需要的时候不动。
结合这个概念,那么新加入的这个窗口的刷新时机就是如下的这几个:
1、按左右键切换角色时。
2、按上下及L/R键切换技能时;
我们先看这两条如何实现。
第1条,因为这个左右键切换角色是我们写在场景类Scene_SkillShop的update方法中的,所以只需要在刷新对应窗口时,顺便刷新一下这个浮动窗口就可以了。左键部分的代码如下,右键的自行编写:
    # 按下 左 键的场合下
    if Input.trigger?(Input::LEFT)
      # 演奏光标 SE
      $game_system.se_play($data_system.cursor_se)
      # 若角色不足两名则返回
      return if $game_party.actors.size < 2
      # 前一角色
      @actor_id -= 1
      @actor_id %= $game_party.actors.size
      @actor = $game_party.actors[@actor_id]
      # 刷新对应窗口
      @name_window.set_actor(@actor)
      @shop_window.set_actor(@actor)
      @info_window.set_actor(@actor)
      return
    end

调用了窗口的set_actor方法,同时也就执行了刷新窗口的操作。
第2条,这个上下和L/R的操作并不是我们写在场景类里的,而是在Window_Selectable中定义的,这该如何操作呢?让我们继续深入查找线索,应该可以想到,现在我们要求的是光标位置改变的时候刷新,那么是否还有什么窗口也是和光标位置改变有关呢?答案就是最上面的那个用于显示技能说明文字的帮助窗口。好了,看来有门了。接下来看看这个帮助窗口的刷新是如何传递的。一番查找之后,很容易就发现是在Window_SkillShop中有一个刷新帮助文本的函数update_help,每次光标位置的改变都会调用这个方法,那么现在我们就考虑一下,如何从这里下手实现另一个窗口的刷新呢?

这里要知道,现在通过出售窗口来对帮助窗口进行刷新这样的效果是如何实现的,将帮助窗口指向出售窗口的一个属性,这样在出售窗口内部控制帮助窗口的刷新就可以实现了。
这个方法首先要说是绝对可行的,要不默认系统里也不可能出现了,不过我今天介绍的是另外的一个方法,书写起来更为简单,理解上也许也更为容易。

因为很显然的,出售窗口和浮动窗口都是同处一个场景类Scene_SkillShop中的,所以,我们可以把当前场景作为一个媒介,来调用当前场景类中的一个方法去刷新浮动窗口,
我们先为场景类Scene_SkillShop写一个用于刷新浮动窗口的方法。
  def update_info
    @info_window.set_skill(@shop_window.skill)
  end
很简单的一句代码。现在如果要刷新浮动窗口,直接调用这个方法即可。那么当前场景是存放在哪个变量中的呢——$scene,还记得刚才说到的变量的作用域么,这里就是一个典型的例子。
现在只需要在出售窗口中刷新帮助文本的地方通过$scene来调用update_info方法,就可以实现浮动窗口的刷新操作了,这样便实现了两个类之间的另一种沟通方式,以一个全局对象来进行沟通。
举个例子,出售窗口和浮动窗口就好像是彼此有好感的一男一女,两个人要传话,就要通过$scene这个中间人来进行,$scene就是他们两个窗口的传话筒。
修改后的出售窗口的update_help方法如下:
  #--------------------------------------------------------------------------
  # ● 刷新帮助文本
  #--------------------------------------------------------------------------
  def update_help
    @help_window.set_text(self.skill == nil ? "" : self.skill.description)
    $scene.update_info
  end
现在把这些修改都做好之后,测试一下,竟然出现了错误提示,检查代码,发现原来是在为出售窗口指定help_window的时候浮动窗口还未生成,而此时就已经调用执行了一次上面的update_help方法了,看来需要调整一下浮动窗口生成的先后次序。
对Scene_SkillShop的main方法前窗口的初始化顺序做如下调整:
    # 生成命令窗口
    @help_window = Window_Help.new
    @shop_window = Window_SkillShop.new(@actor)
    @info_window = Window_SkillShop_Info.new(@shop_window.skill, @actor)
    @shop_window.help_window = @help_window
    @name_window = Window_SkillShop_Name.new(@actor)
再次测试,OK了。

可以看到,不同的技能对应不同的限制值,不同的角色对应不同的属性值,都已经实现了。有两个问题:
一个是窗口的位置忘记做调整了;另一个是,窗口的文字怎么和其他窗口的叠加在一起了。
先来修正窗口坐标,x坐标是固定的,我们可以写在窗口初始化里的super方法里,这里就不多说了。对于y坐标的调整,可以发现,y坐标的变化也是在出售窗口的光标变化时发生的,所以也可以一同写入到update_info方法里。
  def update_info
    # 技能出售窗口y坐标 + 技能出售窗口contents上边缘高度 + 技能出售窗口光标位置 + 技能出售窗口光标高度
    @info_window.y = 128 + 16 + (@shop_window.index - @shop_window.top_row) * 32 + 32
    @info_window.set_skill(@shop_window.skill)
  end
再次测试一下,当这个窗口太低的时候,就会出现超出游戏窗口,下面再来修正一下这个问题。
  def update_info
    # 预先计算y值
    # 技能出售窗口y坐标 + 技能出售窗口contents上边缘高度 + 技能出售窗口光标位置 + 技能出售窗口光标高度
    dst_y = 128 + 16 + (@shop_window.index - @shop_window.top_row) * 32 + 32
    # 若y值已超出屏幕 328 = 480 - 152
    if dst_y > 328
      # 在光标上方时
      @info_window.y = dst_y - 152 - 32
    else
      # 在光标下方时
      @info_window.y = dst_y
    end
    @info_window.set_skill(@shop_window.skill)
  end
再测试一下,坐标问题解决。

下面说文字重叠的问题,Window_Base的z值是100,而其contents可以理解为是描绘在一个z值为101的东西上面,所以只需要把Window_SkillShop_Info的z值适当调整到大于101,改为102或者200,这样就不会出现重叠的问题了。

到此为止,整个脚本的编写就完成了。

鸡蛋
2

鲜花

刚表态过的朋友 (2 人)

评论 (0 个评论)

facelist doodle 涂鸦笔

您需要登录后才可以评论 登录 | 注册会员

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-5-5 09:19

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

返回顶部