设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
楼主: RyanBern
打印 上一主题 下一主题

[原创发布] 【14.7.5第二版更新】RGSS1脚本入门参考

[复制链接]

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

21
 楼主| 发表于 2013-11-15 09:43:26 | 只看该作者
本帖最后由 RyanBern 于 2015-8-26 22:05 编辑

[box=RoyalBlue]
第6章节:场景的使用(二)
[/box]

       说正题前,扯一点题外话。好久没填坑了,眼睁睁看着帖子沉了不太好啊,不过貌似这个教程贴没什么回复呢,偶好伤心啊,我的水平就这么差么,啊啊啊……最近忙得要死,也没时间写了,明天就有一门考试等着呢……
6.1  朴素的任务脚本
       在我们真正开始这部分内容,让我们回顾一下场景制作的一般步骤。首先是定义main方法,第一步是建立各种可见的对象,然后进行主循环,最后进行释放dispose操作。其中,主循环要进行三步刷新,Graphics.update,Input.update,update,其中,我们要重点定义update的内容,包括自动更新和对各种输入的反应。那么我们想要写一个脚本时,先判断需不需要一个独立的窗口来处理这个问题。对于我们今天的“朴素的任务脚本”来说,就比较适合用一个场景来描述任务。
6.1.1  游戏对象的设计:任务对象
       在6R中,已经有各色各类的任务脚本,本人见到的第一个任务脚本,是采用的物品——武器——道具描述法,即把任务数据存放在RMXP内部数据库中。这样做的好处就是方便不会用脚本的人来设计任务,但缺点就是它把任务这一和物品,武器,道具不太相同的游戏对象混在一起了,因此,我们采取定义新对象的方式,考虑这是一种RPG内部数据,因此也把它定义在RPG模块下为好。
       一个任务应该具有的基本属性有:名称,内容。当然,内容你可以再扩展很多很多。比方说内容可以分为具体描述,难度,奖励,目标的人物或地点或物品,结算任务的地点和人物等等。在这里,我们只挑选几个有代表性的定义就OK。
RUBY 代码复制
  1. module RPG
  2.   class Journal
  3.     attr_accessor :name           # 名称(String)
  4.     attr_accessor :description       # 描述(String 数组)
  5.     attr_accessor :difficulty        # 难度(Integer)
  6.     attr_accessor :reward_gold     # 奖励金钱(Integer)
  7.     attr_accessor :reward_items    # 奖励物品(数组,内部元素依然是数组,格式为[物品ID, 物品数量])
  8.     attr_accessor :reward_weapons  # 奖励武器,格式同上
  9.     attr_accessor :reward_armors    # 奖励防具,格式同上
  10.     def initialize
  11.       @name = ""
  12.       @description = ["","",""]
  13.       @difficulty = 0
  14.       @reward_gold = 0
  15.       @reward_items = []
  16.       @reward_weapons = []
  17.       @reward_armors = []
  18.     end
  19.   end
  20. end

       在这里,我们初始化的时候,并没有急着描述任务的内容,这个到后面的时候再写也不迟。每个属性后面都有注释,来说明是用什么样的结构来表示这个属性。这点在后面的initialize方法里面也能看出来。
       注意,这里的@description表示一个含有三个字符串的数组,这是考虑到draw_text无法换行,而描述又比较多,写下三行会比较美观。难度@difficulty是自己定义的,可以有很多等级,在这里我们定义6个等级(0到5),当然,0级就是那种只要电脑不死机就能完成的任务,5级当然就是要花很大力气才能通关的任务。
       当然,作为一种游戏的数据,地位和$data_items之类的应该相当,都应该在游戏刚打开的时候就被载入。但是,由于定义的性质不同,其他的数据早在你用RMXP之时,就已经安静地躺在data文件夹下,用的时候读取文件即可,但是我们新定义的RPG::Journal却不是这样,因此我们只能在游戏中处理它(会浪费一些时间,不过数据规模不大的话影响应该很小)。
       和其他数据一样,我们用一个全局变量(其实是一个数组)$data_journals来表示所有任务的数据,为了和游戏内部统一,我们把$data_journals的0号单元置为nil,其余的都是RPG::Journal类的对象。我们需要定义一下方法,这个方法我们定义为RPG模块的模块方法(module function):
RUBY 代码复制
  1. module RPG
  2.   # 定义模块方法 get_journal_data
  3.   def self.get_journal_data
  4.     data = [nil]
  5.     # 1号任务
  6.     journal = RPG::Journal.new
  7.     journal.name = "第一个任务"
  8.     journal.description[0] = "随便玩玩就可以啦"
  9.     journal.difficulty = 0
  10.     journal.reward_items = [[1,1],[2,1]]
  11.     data.push(journal)
  12.     # 1号任务完毕
  13.     return data
  14.   end
  15. end

       在这里,我们看到了,有关任务奖励的,其实是一个二维数组(金钱的除外),表示奖励,每一个大数组的元素又是一个含有2个元素的小数组,前面那个表示ID,后面那个表示数量。我们只需要模仿这个模式,逐个定义即可。最后那个return是必须的,以便$data_journals接受我们的返回值。完成这个方法后,一定要在Scene_Title相关载入数据的下面写上$data_journals = RPG.get_journal_data,在这里就不再写代码示例了。有关多个任务数据的定义,直接复制上面代码中“1号任务——1号任务完毕”中间的部分,依次排在return data之前,就可以定义其他的任务,ID就是脚本编辑器中的顺序(建议用注释标明)。这样,我们的游戏对象就定义完成了。
       不过,还有一个问题,就是如何保存队伍中当前的任务,要知道数据库中的任务和实际有的任务是不一样的(就像数据库有所有的道具,但是队伍中只含有一部分)。考虑到任务是队伍的一个属性,因此可以定义在Game_Party内部,当然也可以单独存放在别的位置里面。在这里我们用一种特殊的方法来表示,借助游戏变量$game_variables来存储。这是因为$game_variables是可以写到存档数据内部的,虽然这里存储着游戏中所有的自定义变量(默认是数值,就是事件编辑器里面的“变量操作”操作的变量),但是实际上可以存储任何东西。我们就用它来存储当前队伍的任务数据。我们选定一个变量ID,例如1号来保存我们的当前任务,考虑到任务只有两种状态(即接受和完成),我们只需要定义一个数组来保存当前所有任务的ID即可。具体实现方法就是在初始化游戏的时候(Scene_Title里面),写下$game_variables[1] = [],来初始化当前任务内容,不过,1号变量在游戏中就不能作为它用了,这点要格外注意。
       第二版注:$game_variables是个筐,什么都可以往里装。虽然此话不假,但是这样做违反了脚本编写的一致性,现在更推荐将任务存储在Game_Party的内部。不过这样对教程的改动略大,因此第二版对此不加改动,希望大家注意。
6.1.2  任务的窗口描述
       有了这个,我们才能在窗口内描述任务的细节,以便让玩家更清楚了解情况。首先我们要知道,任务窗口大概分为两部分,一是描述所有当前的任务名称,二是描述任务细节。这个跟我们道具的显示是一样的,分为显示道具名字和说明。因此,我们要制作两个窗口为好。
       首先是显示任务名字的部分:
       回忆一下窗口的制作过程,是初始化——描绘(refresh)——刷新窗口(如果有需要的话),初始化做的工作是设置窗口位置和大小,refresh是描述窗口内容(不要重复刷新,很浪费时间)。
       如果大家窗口已经使用熟练的话,应该很容易。这个窗口的描绘请参考Window_Item。
RUBY 代码复制
  1. # 显示当前任务的窗口
  2. class Window_Journal < Window_Selectable
  3.   # 对象初始化
  4.   def initialize
  5.     super(0, 64, 240, 416)
  6.     self.index = 0
  7.     refresh
  8.   end
  9.   # 取得当前光标选中的任务信息
  10.   def journal
  11.     return @data[self.index]
  12.   end
  13.   # 刷新
  14.   def refresh
  15.     # 如果内容被设置了就释放
  16.     if self.contents != nil
  17.       self.contents.dispose
  18.       self.contents = nil
  19.     end
  20.     @data = []
  21.     # 添加当前任务
  22.     for i in $game_variables[JOURNAL]
  23.       @data.push($data_journals[i])
  24.     end
  25.     # 取得最大项目数
  26.     @item_max = $game_variables[JOURNAL].size
  27.     # 最大项目数不为 0 就开始描绘
  28.     if @item_max > 0
  29.       self.contents = Bitmap.new(width - 32, @item_max * 32)
  30.       for i in 0...@item_max
  31.         draw_item(i)
  32.       end
  33.     else
  34.       self.contents = Bitmap.new(width - 32, 32)
  35.       self.contents.draw_text(4, 0, width - 36, 32, "无任务")
  36.     end
  37.   end
  38.   # 描绘项目
  39.   def draw_item(i)
  40.     x = 4
  41.     y = i * 32
  42.     bitmap = RPG::Cache.icon("Journal.png")
  43.     name = @data[i].name
  44.     self.contents.blt(x, y, bitmap, Rect.new(0,0,24,24))
  45.     self.contents.draw_text(x+28, y, width-x-36-24, 32, name)
  46.   end
  47. end

       在这里,我们用一个常量JOURNAL来表示队伍中含有的任务数组在$game_variables里面存放的位置,当然可以随便更改。这个窗口是仿照了Window_Item脚本,没写太多注释,大家可以对比着看一下,都比较简单。
       然后就是显示任务具体内容的窗口,这个用Window_Base生成就好,不过还是有些地方需要大家注意一下。首先大家要清楚,这个窗口是为了描述某个具体任务的,因此必须要有一个私有的实变量来保存当前描绘的任务,当然你不必将它设置为属性。还有一点就是关于描述任务奖励的,考虑到任务奖励种类比较多,为了美观,我们把它们合并在一起来描绘。
RUBY 代码复制
  1. # 描绘任务具体内容的窗口
  2. class Window_Journal_Contents < Window_Base
  3.   def initialize
  4.     super(240, 64, 400, 416)
  5.     self.contents = Bitmap.new(width - 32, height - 32)
  6.     @journal = nil
  7.     refresh
  8.   end
  9.   # 设置当前要描绘的任务
  10.   def journal=(journal)
  11.     # 如果任务和当前任务有差异
  12.     if @journal != journal
  13.       @journal = journal
  14.       refresh
  15.     end
  16.   end
  17.   def refresh
  18.     self.contents.clear
  19.     self.contents.font.size = 18
  20.     self.contents.font.color = system_color
  21.     self.contents.draw_text(0, 0, 72, 24, "具体内容")
  22.     self.contents.draw_text(0, 96, 36, 24, "难度")
  23.     self.contents.draw_text(0, 144, 72, 24, "任务奖励")
  24.     if @journal != nil
  25.       self.contents.font.color = normal_color
  26.       (0..2).each do |i|
  27.         self.contents.draw_text(0, 24 + 24 * i, width - 32, 24, @journal.description[i])
  28.       end
  29.       diff = "★" * @journal.difficulty + "☆" * (5 - @journal.difficulty)
  30.       self.contents.draw_text(0, 120, width - 32, 24, diff)
  31.       total = 0
  32.       for item in @journal.reward_items
  33.         x = 184 * (total % 2)
  34.         y = 168 + 24 * (total / 2)
  35.         name = $data_items[item[0]].name
  36.         icon = RPG::Cache.icon($data_items[item[0]].icon_name)
  37.         number = item[1]
  38.         self.contents.blt(x, y, icon, Rect.new(0,0,24,24))
  39.         self.contents.draw_text(x + 28, y, 126, 24, name)
  40.         self.contents.draw_text(x + 28 + 126, y, 30, 24, number.to_s, 2)
  41.         total += 1
  42.       end
  43.       for item in @journal.reward_weapons
  44.         x = 184 * (total % 2)
  45.         y = 168 + 24 * (total / 2)
  46.         name = $data_weapons[item[0]].name
  47.         icon = RPG::Cache.icon($data_weapons[item[0]].icon_name)
  48.         number = item[1]
  49.         self.contents.blt(x, y, icon, Rect.new(0,0,24,24))
  50.         self.contents.draw_text(x + 28, y, 126, 24, name)
  51.         self.contents.draw_text(x + 28 + 126, y, 30, 24, number.to_s, 2)
  52.         total += 1
  53.       end
  54.       for item in @journal.reward_armors
  55.         x = 184 * (total % 2)
  56.         y = 168 + 24 * (total / 2)
  57.         name = $data_armors[item[0]].name
  58.         icon = RPG::Cache.icon($data_armors[item[0]].icon_name)
  59.         number = item[1]
  60.         self.contents.blt(x, y, icon, Rect.new(0,0,24,24))
  61.         self.contents.draw_text(x + 28, y, 126, 24, name)
  62.         self.contents.draw_text(x + 28 + 126, y, 30, 24, number.to_s, 2)
  63.         total += 1
  64.       end
  65.       if @journal.reward_gold > 0
  66.         str = "获得金钱:" + @journal.reward_gold.to_s
  67.         y = 168 + ((total-1) / 2 + 1) * 24
  68.         self.contents.draw_text(0, y, width - 32, 24, str)
  69.       elsif total == 0
  70.         self.contents.draw_text(0, 168, 18, 24, "无")
  71.       end
  72.     end
  73.   end
  74. end

       注意那个journal方法的定义,只有在@journal和参数不相等的时候,才进行刷新,这样也是为了减少refresh调用的次数。
       中间refresh定义得比较啰嗦,不过物品武器装备这三个在一起真的好烦啊,希望高手能精简一下哈。大家可能注意到了,我们现在创建的两个窗口的y坐标都是64而不是0,这是由于我们在最顶端要说明每个窗口是干什么用的。
6.1.3  任务场景的制作
       有了这两个窗口,任务场景的制作就会相当简单。新加的东西不多,不过要注意这里在场景中生成Window_Base的方法,因为这个窗口内容太单一了,我们就不单独给设一个类了。
       另外,在update中,要保持左右窗口描述的任务一致,因此我们要不定期执行这个语句:
RUBY 代码复制
  1. @contents_window.journal = @journal_window.journal

       在这里,我们假设在地图Scene_Map上可以查看任务,那么在Scene_Map脚本也要进行输入的处理,这里略去过程了,想必大家已经会怎么弄了吧。
RUBY 代码复制
  1. # 查看任务的场景
  2. class Scene_Journal
  3.   def main
  4.     @journal_window = Window_Journal.new
  5.     @contents_window = Window_Journal_Contents.new
  6.     @base1 = Window_Base.new(0, 0, 240, 64)
  7.     @base1.contents = Bitmap.new(208, 32)
  8.     @base1.contents.draw_text(0, 0, 96, 32, "任务名称")
  9.     @base2 = Window_Base.new(240, 0, 400, 64)
  10.     @base2.contents = Bitmap.new(368, 32)
  11.     @base2.contents.draw_text(0, 0, 96, 32, "具体内容")
  12.     Graphics.transition
  13.     loop do
  14.       Graphics.update
  15.       Input.update
  16.       update
  17.       if $scene != self
  18.         break
  19.       end
  20.     end
  21.     Graphics.freeze
  22.     @base1.dispose
  23.     @base2.dispose
  24.     @journal_window.dispose
  25.     @contents_window.dispose
  26.   end
  27.   def update
  28.     @base1.update
  29.     @base2.update
  30.     @journal_window.update
  31.     @contents_window.journal = @journal_window.journal
  32.     # 按下 B 键的情况下,返回地图
  33.     if Input.trigger?(Input::B)
  34.       $game_system.se_play($data_system.cancel_se)
  35.       $scene = Scene_Map.new
  36.     end
  37.   end
  38. end

       来看看这个脚本的效果吧,这个朴素的任务脚本就做完了,是不是很简单?

6.2  默认回合制战斗场景的解读
       RMXP中最复杂的场景脚本应当是这个回合制战斗了。虽然作为一个游戏的默认系统,但是如果接触RGSS时间不长,恐怕也难以写出一个没有BUG的回合制脚本。需要指出的是,游戏默认Scene_Battle虽然很长,但是从算法上和原理上都不难理解,从这个角度上来说也非常适合新手学习。
6.2.1  准备工作
       我们打开Scene_Battle,翻开它的分割定义1,可以看到main方法以及update方法。有了解读脚本的基础,我们对main方法的基本定义模式已经非常熟悉了。这里main方法的结构和普通场景相似,初始化数据——生成窗口——主循环——释放。但是在这里,我们要额外生成战斗场景用的活动块,这里需要调用Spriteset_Battle类的一个对象,这里Spriteset_Battle是一个特殊的类,这个活动块里面含有几乎所有战斗画面上显示的内容(窗口除外),例如角色的战斗图形,战斗背景图等等。另外,战斗场景中需要显示技能和物品的窗口,但是在main方法里面没有生成它们。这样做的原因可能是为了节省内存空间,但是一遍一遍生成可能会以时间为代价。
       接下来的update才是这里的关键所在。在update之前,先定义了几个Scene_Battle的内部方法,我们暂时可以先不看。update进行的方法,首先是刷新战斗事件,然后刷新系统对象,再然后是计时器,窗口,活动块。刷新完毕后,本应该等待玩家输入以便进行条件刷新,但是在这之前要等待一些效果执行完毕(包括手动等待),才能接受玩家的输入,这个机制和我们之前讲的输入等待是一样的。之后,就是各种条件刷新了。
       在条件刷新下,我们看到,这里并不是根据窗口的激活情况进行的判断刷新,而是根据战斗进行的回合种类进行分拆。说是各种回合,其实就是在一个回合下的不同阶段而已。在后面我们会看到,在同一个阶段进行的刷新,才进一步根据窗口激活情况不同而进行选择刷新。所以,大家也要多多借鉴这种刷新机制,如果场景比较简单,就可以通过窗口激活状况进行刷新;如果场景比较复杂,那么你可能也要引入一个类似于“回合”的变量来控制。
6.2.2  五个战斗阶段的解读
       接下来我们就要分别说说这五个阶段Scene_Battle都做了些什么。
       打开Scene_Battle分割定义2,在这个分割定义中,分别定义了第一阶段,第二阶段,第五阶段。这些阶段的执行都比较简单,为了方便放在一起了。
  • 第一阶段:自由战斗回合
           这里说得很不清楚,自由战斗回合实际上就是指事先处理各种战斗事件,强制行动,玩家不能操控的回合。在战斗事件的设置中,如果选择了“回合0”,那么这样的事件会立即在进入场景后执行。其余的“回合X”,就是在经过了X回合之后的自由战斗回合(第一阶段)立即执行。如果执行完毕,那么就进入第二阶段。第一阶段就是这样短暂。
  • 第二阶段:同伴命令回合
           实际上就是进行队伍总体操作的回合,这场战斗你是打还是不打,如果选择“战斗”则进入第三阶段,如果选择“逃跑”,则进行相应的处理。在这里我们看到了逃跑的基本处理,代码如下:
    RUBY 代码复制
    1. def update_phase2_escape
    2.     # 计算敌人速度的平均值
    3.     enemies_agi = 0
    4.     enemies_number = 0
    5.     for enemy in $game_troop.enemies
    6.       if enemy.exist?
    7.         enemies_agi += enemy.agi
    8.         enemies_number += 1
    9.       end
    10.     end
    11.     if enemies_number > 0
    12.       enemies_agi /= enemies_number
    13.     end
    14.     # 计算角色速度的平均值
    15.     actors_agi = 0
    16.     actors_number = 0
    17.     for actor in $game_party.actors
    18.       if actor.exist?
    19.         actors_agi += actor.agi
    20.         actors_number += 1
    21.       end
    22.     end
    23.     if actors_number > 0
    24.       actors_agi /= actors_number
    25.     end
    26.     # 逃跑成功判定
    27.     success = rand(100) < 50 * actors_agi / enemies_agi
    28.     # 成功逃跑的情况下
    29.     if success
    30.       # 演奏逃跑 SE
    31.       $game_system.se_play($data_system.escape_se)
    32.       # 还原为战斗开始前的 BGM
    33.       $game_system.bgm_play($game_temp.map_bgm)
    34.       # 战斗结束
    35.       battle_end(1)
    36.     # 逃跑失败的情况下
    37.     else
    38.       # 清除全体同伴的行动
    39.       $game_party.clear_actions
    40.       # 开始主回合
    41.       start_phase4
    42.     end
    43.   end

           逃跑成功与否取决于处于战斗中的敌人和角色速度平均值的大小(已经阵亡的战斗者和没有出现的战斗者不算在内),如果敌人和角色速度平均值相等,则由50%的几率成功逃跑。当然,如果逃跑失败了,游戏还是要继续的,这时候所有角色都没有行动,整个队伍只能被敌人痛扁一顿……当然,如果你不喜欢这种逃跑的设定,完全可以通过修改,跳过第二阶段,不妨自己试一下吧。
  • 第三阶段:角色命令回合
           这个阶段相对前两个阶段,比较复杂,但是思路还是很明确的。在这个阶段,玩家为各个角色设定行动(当然行动能否执行取决于主回合执行的情况)。按照角色在队伍中的顺序来为各个角色设定他们的行为,首先是角色的基本命令,然后根据基本命令来选择接下来要显示的窗口。
    RUBY 代码复制
    1. def update_phase3
    2.     # 敌人光标有效的情况下
    3.     if @enemy_arrow != nil
    4.       update_phase3_enemy_select
    5.     # 角色光标有效的情况下
    6.     elsif @actor_arrow != nil
    7.       update_phase3_actor_select
    8.     # 特技窗口有效的情况下
    9.     elsif @skill_window != nil
    10.       update_phase3_skill_select
    11.     # 物品窗口有效的情况下
    12.     elsif @item_window != nil
    13.       update_phase3_item_select
    14.     # 角色指令窗口有效的情况下
    15.     elsif @actor_command_window.active
    16.       update_phase3_basic_command
    17.     end
    18.   end

           这就是第三阶段,根据窗口激活的不同来选择刷新方法。在这里,我们看到了特技窗口和道具窗口在这个地方生成。
    RUBY 代码复制
    1. def start_skill_select
    2.     # 生成特技窗口
    3.     @skill_window = Window_Skill.new(@active_battler)
    4.     # 关联帮助窗口
    5.     @skill_window.help_window = @help_window
    6.     # 无效化角色指令窗口
    7.     @actor_command_window.active = false
    8.     @actor_command_window.visible = false
    9.   end

           上面的方法是开始选择特技,在最后的结束特技选择时,@skill_window会被释放掉。
  • 第四阶段:主回合
           实际上就是战斗真正进行的阶段,前面几个阶段都是准备工作。
           在这个阶段,系统会自动生成敌人的作战行动(见方法start_phase4中的语句),而后决定行动的先后次序,代码如下。
    RUBY 代码复制
    1. def make_action_orders
    2.     # 初始化序列 @action_battlers
    3.     @action_battlers = []
    4.     # 添加敌人到 @action_battlers 序列
    5.     for enemy in $game_troop.enemies
    6.       @action_battlers.push(enemy)
    7.     end
    8.     # 添加角色到 @action_battlers 序列
    9.     for actor in $game_party.actors
    10.       @action_battlers.push(actor)
    11.     end
    12.     # 确定全体的行动速度
    13.     for battler in @action_battlers
    14.       battler.make_action_speed
    15.     end
    16.     # 按照行动速度从大到小排列
    17.     @action_battlers.sort! {|a,b|
    18.       b.current_action.speed - a.current_action.speed }
    19.   end

           首先是把所有的敌人和角色都放到@action_battlers这个数组中。然后为所有即将行动的战斗者确定行动速度,具体方法参见Game_Battler分割定义1里面的make_action_speed,行动速度为当前战斗者的速度加上一个随机平移量,范围是0到10+战斗者的速度/4。而后将目标数组按照速度大小,由小到大排序。
           而后我们看到了update_phase4的原型,同样地,这个刷新操作也是根据步骤来进行,第四阶段共分为6个步骤。为什么要这样分呢?原因是第四阶段不需要进行玩家的任何输入(当然公共事件的除外),而现实战斗者行动效果的时候,要一个个地显示,即不会出现两个战斗者同时进行物理攻击或者释放特技的情况(如果需要改,可以自定义),因此这6个步骤是针对每一个战斗者而设计的。系统从刚刚生成的@action_battlers中,按照速度从大到小,依次选出一个战斗者(实际上就是@action_battlers[0])作为当前行动的战斗者@active_battler,然后再进行各种操作。如此循环,当@action_battlers数组里面的元素都取出时,表示所有战斗者行动已经处理完毕,那么要开始一个新的回合。
    • 【步骤1】准备行动
             由于每一次行动都会影响到此次战斗胜败的判定(特指玩家胜败的判定),所以要在这一步进行一个判断,如果战斗胜败能够确定(即主角队伍和敌人队伍之一全灭),那么直接结束主回合,进入战斗的第五个阶段(在主角队伍胜利的情况下),否则才能进行下面的内容。具体判断方法参见Scene_Battle分割定义1的judge方法,战斗结束方法参见battle_end(result)方法。如果战斗需要继续进行,那么刷新战斗事件(注意不是物品或者技能的公共事件),从@action_battlers取出行动速度最大的作为@active_battler,进行各种准备操作(例如连续伤害和状态变化),然后就可以进行步骤2了。
    • 【步骤2】开始行动
            在这一步骤中,主要进行的是各种行动效果的判断,具体看代码:
      RUBY 代码复制
      1. def update_phase4_step2
      2.     # 如果不是强制行动
      3.     unless @active_battler.current_action.forcing
      4.       # 限制为 [敌人为普通攻击] 或 [我方为普通攻击] 的情况下
      5.       if @active_battler.restriction == 2 or @active_battler.restriction == 3
      6.         # 设置行动为攻击
      7.         @active_battler.current_action.kind = 0
      8.         @active_battler.current_action.basic = 0
      9.       end
      10.       # 限制为 [不能行动] 的情况下
      11.       if @active_battler.restriction == 4
      12.         # 清除行动强制对像的战斗者
      13.         $game_temp.forcing_battler = nil
      14.         # 移至步骤 1
      15.         @phase4_step = 1
      16.         return
      17.       end
      18.     end
      19.     # 清除对像战斗者
      20.     @target_battlers = []
      21.     # 行动种类分支
      22.     case @active_battler.current_action.kind
      23.     when 0  # 基本
      24.       make_basic_action_result
      25.     when 1  # 特技
      26.       make_skill_action_result
      27.     when 2  # 物品
      28.       make_item_action_result
      29.     end
      30.     # 移至步骤 3
      31.     if @phase4_step == 2
      32.       @phase4_step = 3
      33.     end
      34.   end

             这里的代码结构非常简单,首先是要判断当前战斗者能不能进行行动,如果能进行行动,那么能进行什么样的行动。这里的restriction就表示行动的限制,4为完全不能行动,2和3分别表示普通攻击同伴和普通攻击敌人。而后便真正进行行动效果的计算。
             在这里略去对行动效果计算的方法的解读,不过强烈建议大家看看这3个方法:attack_effect,skill_effect,item_effect,这三个方法可以在Game_Battler3里面寻找。这里面说的都是各种行动的效果是如何定义的,我们在改动战斗系统时,改动最频繁的就是这三个方法了。
    • 【步骤3、步骤4、步骤5】显示动画
             步骤3显示的是行动方的动画,步骤4显示的是对象方的动画,步骤5显示的是各种伤害(包括行动放和对象方),没什么好讲的,主要功能的实现是利用了@spriteset(战斗活动块)的方法,这个实例在一开始就已经生成了。大家如果有兴趣,可以翻开F1,搜索RPG::Sprite,在这里有战斗活动块的所有方法和属性的定义。
    • 【步骤6】公共事件
             在执行完动画之后,才轮到公共事件的处理。因此公共事件是每一个行动者行动的最后一步。执行完这一步后,返回到步骤1,进行下一个战斗者(如果有的话)的行动。
  • 第五阶段:结束战斗回合
           这个阶段只有在角色队伍胜利的情况下才进行。主要功能是显示战后所得,获得战斗胜利的奖励。这里的方法非常简单,大家可以自行解读start_phase5,update_phase5执行的实际上是战斗结果窗口的显示,需要等待100帧才能显示战斗结果窗口,在主角按下C键的时候,结束战斗,返回地图画面。


       这样,整个默认的战斗系统就被我们解读完了,不知道效果如何。总觉得自己跟什么都没说似的呢……不过具体效果还要看大家体会了,我没有提及的代码,大家最好也看看,这样能为自己改脚本提供思路。

       这个脚本教程贴已经快要接近尾声了哈,感觉自己已经把能说明白的东西都说了。下一章节,可能是最后一个章节了,我们将会谈谈Ruby内部的一些机制,这也是我使用Ruby多年的体会,大家就敬请期待吧。
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

22
 楼主| 发表于 2013-11-16 10:14:22 | 只看该作者
本帖最后由 RyanBern 于 2015-8-24 12:13 编辑

[box=RoyalBlue]
第7章节:尾声
[/box]

       呼~写了这么久,这个脚本教程贴总算要杀青了。不过打开脚本编辑器,我们还有一些部分没有说到,比如说Interpreter类的脚本,我们就没深入讨论。不过,这对于一般的需要来说,已经足够了。那么在最后一章,我们要说一些零碎的内容。说是零碎,其实是编程的死角,这也是一个脚本党的必备知识。
7.1  个人的几个体会
7.1.1 关于alias
       这个词我们在很久之前就已经提到了,但是一直没有说它的用法,现在就补上吧。alias的意思是“别名”,在这里是给函数取别名。具体的使用方法是:alias 新方法名 旧方法名,新方法名和旧方法名用空格隔开。那么这个功能有什么用呢?主要是利用在方法的重定义上。当我们要重新定义一个方法,又不想覆盖原来的方法,那么alias就派上用场了。请看下面的例子:
RUBY 代码复制
  1. class Person
  2.   attr_accessor :name
  3.   def initialize(name)
  4.     @name = name
  5.   end
  6.   def hello
  7.     print "我是" + @name
  8.   end
  9.   alias old_hello hello
  10.   def hello
  11.     print “Hello, I am ” + @name
  12.   end
  13. end
  14. # 测试一下
  15. ryan = Person.new("Ryan")
  16. ryan.hello
  17. ryan.old_hello

       如果按照上述定义,第一个语句会在屏幕上显示“Hello, I am Ryan”,第二个语句会在屏幕上显示“我是Ryan”,可见alias具有保留原有方法的功能。
       在这里,我们看到alias的威力还不算很大。这个语句一般用作整合脚本上,特别是给不会脚本的人写脚本时,我们要写出一个完整的脚本,让用户贴在Main前面即可,但是有时候并不是这样,假设我们要修改前面默认脚本的内容,如果不用alias,那么只能进行重定义。但是对于一些方法,我们可以这样:
假如你给Game_Temp增加一个名叫test_value的属性,并在initialize中将它初始化,我们可以用三种方法。一是直接在Game_Temp上进行修改,不过,如果写脚本给伸手党,那就不太好,二是把整个Game_Temp需要重定义的地方定义一遍,对于Game_Temp这样的脚本来说,重定义一遍无异于整体复制,脚本显得很不整洁,三就是利用alias,具体实现过程如下:
RUBY 代码复制
  1. class Game_Temp
  2.   attr_accessor :test_value
  3.   alias old_initialize initialize
  4.   def initialize
  5.     @test_value = 0
  6.     old_initialize
  7.   end
  8. end

       在这里,我们是先对initialize进行别名,然后重定义,在定义过程中,调用了原方法。因为这里的initialize做的工作就是赋值初始化,因此这样写是没有问题的。
       不过,需要注意的是,不能给同一个方法起两个相同的别名,这样说似乎比较别扭,我们来看下面的例子:
       假如上面的代码已经定义好,那么我现在继续往Game_Temp里面增加一个叫test_bool的属性(Game_Temp中已经有了我们刚刚增加的test_value,利用的也是alias),由于我们现在的initialize的功能已经能够初始化test_value,因此我们利用alias时,进行重定义的肯定是initialize方法而非old_initialize方法,但是我们不能写下面的:
RUBY 代码复制
  1. class Game_Temp
  2.   attr_accessor :test_bool
  3.   alias old_initialize initialize
  4.   def initialize
  5.     @test_bool = false
  6.     old_initialize
  7.   end
  8. end

       这样写的话,脚本肯定会出错,因为你又将initialize别名为old_initialize,而刚才那个名字已经有了对应的方法,这样就会出现一个符号表示两个方法的情况,引发内部冲突。因此这个时候就不能用old_initialize做别名了,应该换成别的。

7.1.2 关于类变量@@xxxx
       类变量我们在讲类的时候顺便也提了一句,这种变量我们一般不会用到,不过好歹也说一下吧。类变量和通常我们讲的类当中的实例变量不同,类变量作用于整个类的上面。作为一个类生成的实例,这个实例本身可以访问该类的类变量。这样说有些模糊,我们看下面的例子:
RUBY 代码复制
  1. class A
  2.   # 定义类变量 @@a
  3.   @@a = 1
  4.   def initialize
  5.     # 定义实例变量 @a
  6.     @a = 1
  7.   end
  8.   def a
  9.     return a
  10.   end
  11.   def a=(a)
  12.     @a = a
  13.   end
  14.   def ab
  15.     return @@a
  16.   end
  17.   def ab=(ab)
  18.     @@a = ab
  19.   end
  20. end
  21. a1 = A.new
  22. a2 = A.new
  23. a1.a = 2
  24. a2.a = 3
  25. a1.ab = 5
  26. print a1.a  # => 2
  27. print a2.a  # => 3
  28. print a1.ab  # => 5
  29. print a2.ab  # => 5

       在这里,@a表示我们通常用到的实例变量,@@a表示类变量,在这里我们看到,@a这个变量是每个实例私有的变量,而@@a这个变量是两个实例公用的变量。因此我们得到结论,只要是属于同一个类的同一个类变量,无论用这个类的哪个实例修改它,都会有同样的效果,无论用这个类的哪一个实例访问它,都会得到同样的值。
       值得注意的是,类本身,也可以对类变量进行修改,不过要来借助类方法来完成。类方法和模块方法比较类似,定义和使用的模式基本相同。
RUBY 代码复制
  1. class A
  2.   # 定义类变量 @@var
  3.   @@var = 0
  4.   # 定义类方法
  5.   def self.var
  6.     return @@var
  7.   end
  8.   def self.var=(var)
  9.     @@var = var
  10.   end
  11. end
  12. A.var = 5
  13. p A.var # => 5


7.1.3  相同和相等•clone•变量和指针
       这个也是我们经常说的一个问题,Ruby是以C语言为基础语言编写的,实际上是对C做的一个优化。我们都知道C语言中有指针这个东西,而Ruby中我们却看不到它。不是说Ruby中没有指针,而是Ruby已经采取某种方式将指针优化掉了。我们在编写程序的同时,就在使用大量的指针,只是我们从未发觉而已。为什么又说起这个事情来呢,这是源于本人最近的一道作业题,我嫌用C太复杂,于是就用Ruby了,但是有一个地方怎么也通不过,一时想破脑袋也没想明白。不过后来好在经过F1指点,终于明白了问题所在,接下来我们就看一下。
       我们在前面说了,赋值运算符“=”的作用是将右边的值赋给左边,传递过去的就是对象的引用。所以,当你把一个数组或者一个类的实例赋给一个变量,那么实际传递过去的就是变量的引用,如果你把这个值又赋给另外一个变量,那么依然是传递引用,对一个变量进行的操作必定会影响另一个。因此,有时候我们需要生成一模一样的一个对象,又不想让它们有任何关系,就要用到clone方法,这是Object类(最大的类)的方法,任何类的实例都可以调用,因此,写b = a.clone,就能把a和b区别开来,而且他们的内容也完全一样。我们知道利用比较运算符“==”可以判断两个对象是否相等,而这个运算符只能判断两个对象的值是否相等,例如,有b = a.clone,然后判断b == a,这个结果通常是true,不过,a和b并不相同,在Object类里面也有一个判断是否相同的方法equal?,如果执行b.equal?(a),我们得到的结果将会是false,因为equal?是判断二者是否相同的方法,在这里,a和b仅仅是内容相同,而它们实际上是两个“变量”(即内存的地址不同,这并不稀奇,就好比你和你的双生同胞不是同一个人一样)。
但是,即使是这样,也会遇到一些费解的问题。例如,有下面的定义:
RUBY 代码复制
  1. class A
  2.   attr_accessor :data
  3.   def initialize
  4.     @data = []
  5.   end
  6. end
  7. x1 = A.new
  8. x1.data[0] = 0
  9. x2 = x1.clone
  10. x2.data[0] = 1
  11. print x1.data[0]

       在这里,我们看到屏幕上显示的是1,这个最初看起来是非常奇怪的事情。我们明明对x1做了复制,按理说x1和x2的应该只是内容一样而已,为什么对x2的修改也影响到x1呢?这里我们注意的是,clone进行的只是浅层次的复制,它只能复制对象里面所有变量的内容,而不能复制变量引用的内容。我们知道,一个变量如果表示数组,那实际上就是指向数组的引用,在这里@data就是一个数组,表示的就是数组的引用,而clone做复制时,仅仅把这个引用的值复制了过去,因此它们表示的还是同一个东西。除非你将x2的data重新定向,否则在x2的data上面的改动还是会影响x1。
7.1.4  sprintf表达式
       说实话,学C语言的人在Ruby里面看到这个,应该感到无比亲切吧。这可以说是为数不多被保留下来的语句啊。sprintf这个名字蛮奇怪的,print是“打印、输出”的意思,那么后面的f,缩写的是format,表示“格式”,连在一起就表示“格式输出”,当然printf也是C的基本函数之一,前面的s,是表示输出的去向,s缩写的是string,表示这个函数将一定的内容输出到一个字符串内。在C语言中,sprintf的第一个参数是表示接受输入的字符串(字符数组指针),但是在Ruby里面,这个字符串直接作为sprintf的返回值。具体使用方法,如果学过C了,肯定都知道,如果不清楚就F1看看,其中“%”表示格式转换描述,例如写a = sprintf("%d %d", x1, x2),就是把x1和x2的值分别替换两个%d,然后做成字符串送到a中,我们注意%d是整数转换描述,如果x1表示的不是整数,那么就会出错的,因此,格式输出一定要严格控制,千万不能出现转换错误。

7.2  写在最后
       这个RGSS1教程帖就这样结束了,不知道能坚持看到最后的你们,能不能有所收获呢?我不敢期望这个教程的效果有多么强大,但是,只要它能为大家写游戏做出一丝一毫的贡献,我就知足了。
       每次写完一章,我都很期待大家的回帖,不过从第四章开始,这个帖子基本没有什么回帖的了,于是我就一直连帖下去,一直连帖,直到最后我发现大家似乎已经没有心情看下去了。我想这也是我个人方面的原因,第5章和第6章中间间隔将近一个月,或者说是我才疏学浅,但总之,我写这个东西的目的就是让大家能学会分拆脚本,根据默认脚本或者其他大神的作品的构造来提炼出自己的东西。
       很多人看到脚本教程,给出的评价,大多都是“变量计算、类那里还行,到后面就什么也看不懂了”、“看完教程后就学会了一个p函数,别的都没学会”,我其实还满希望我的教程能摆脱这个评价,但是现在想想,或许这个教程帖也难逃此厄运。毕竟,如果没有经历系统的学习,没有理解计算机工作的原理,只是走马观花地看教程,恐怕也没什么效果。这样的教程写出来,结果往往都是,会脚本的人更厉害了,不会脚本的人依然没学到什么……总之,虽然截稿了,但是很郁闷。

       6R站上已经有了很多RMXP的经典教程,而且侧重点各有不同。推荐大家在阅读教程帖子的时候,在多个教程之间比对,发现其中的不同,从而提出问题。毕竟所有的教程都会有疏漏和错误,写出一个完美的教程也实在是太过于困难。

       最后,建议想要学习脚本的同学,一定要多看,多提问,多练习,少伸手。学习的初期可能做不出自己想要的东西,这没有关系,学习脚本切忌急功近利,心急往往很容易冲淡学习脚本的热情。起初建议大家模仿默认脚本来修改和练习,慢慢磨合,今后使用脚本才能更加得心应手。

点评

↓依你之见还应该再加点什么呢?  发表于 2013-11-16 13:26
这就尾声了什么情况- -不过最后一章还是很赞- -很多教程都不说这一点的  发表于 2013-11-16 12:34
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
0
星屑
33455
在线时间
5108 小时
注册时间
2012-11-19
帖子
4878

开拓者

23
发表于 2013-11-16 18:06:32 | 只看该作者
本帖最后由 芯☆淡茹水 于 2013-11-16 18:10 编辑

   在一篇 Ruby 语言教学里看到的,觉得很经典。

  Ruby 的理念是:一切都是对象。包括:数值,字符串,数组,哈希,类。

  其中讲到 类 的概念,觉得很形象。

比如:

@SEX     @age     @height
  1. #==============================================================================
  2. # 定义一个“人”的类(概念),也就是说,在大家的脑海里,“人”是怎样怎样的。
  3. # “人”有名字;有性别;有身高;年龄,,等,这些是属于一个“人”的参数。
  4. # “人”会说话;会行走;会吃饭,,,等,这些就属于是方法。
  5. #==============================================================================
  6. class Person   
  7.   #------------------------------------------------------------------------
  8.   # 定义一些参数,除了 “性别” 只能读取不可改变外,其它都能变动,比如“姓名”,
  9.   # “年龄”,,,,。当然,一个“人”的参数是很多的,这里只列举一部分。
  10.   #------------------------------------------------------------------------
  11.   attr_accessor :name     # 姓名
  12.   attr_reader   :sex      # 性别
  13.   attr_accessor :age      # 年龄
  14.   attr_accessor :height   # 身高
  15.   #-------------------------------------------------------------------------
  16.   # 初始化。生成一个新的“人”时,需要指定生成的这个“人”的一些参数并代入。
  17.   #-------------------------------------------------------------------------
  18.   def initialize(name, sex, age, height)
  19.     @name = name
  20.     [url=home.php?mod=space&uid=103045]@SEX[/url] = sex
  21.     [url=home.php?mod=space&uid=6132]@age[/url] = age
  22.     [url=home.php?mod=space&uid=291977]@height[/url] = height
  23.   end
  24.   #--------------------------------------------------------------------------
  25.   # 定义一个最简单的“说话”方法,比如:自我介绍。
  26.   #--------------------------------------------------------------------------
  27.   def talk
  28.     return "我的名字叫:" + @name + ",性别:" + @sex + ",年龄:" + @age.to_s + "岁,身高:" + @height.to_s + "cm。"
  29.   end
  30. end
  31. #===============================================================================
  32. #===============================================================================
  33. # 你是游戏的制作者,你就是这个游戏的上帝。现在上帝要创造一个新的“人”,
  34. # 名叫:RyanBern,性别:男,年龄:18,身高:175cm。首先用一个变量代入并表示
  35. # 这个人,比如用:rb 。以后要指定这个人,都用 rb 来表示。
  36. #===============================================================================
  37. rb = Person.new("RyanBern", "男", 18, 175)
  38. #------------------------------
  39. # 接下来显示和他的谈话。
  40. #------------------------------
  41. p rb.talk    #“我的名字叫:RyanBern,性别:男,年龄:18岁,身高:175cm。”
  42. #==============================================================================
复制代码

点评

除了一切都是对象之外还有一个特点就是一切都与对象紧密联系,例如talk方法可以不写return  发表于 2013-11-16 19:39
xp vx va mv  va mz 各类型脚本/插件定制
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

24
 楼主| 发表于 2013-11-16 20:19:52 | 只看该作者
芯☆淡茹水 发表于 2013-11-16 18:06
在一篇 Ruby 语言教学里看到的,觉得很经典。

  Ruby 的理念是:一切都是对象。包括:数值,字符串, ...

嗯,说得很形象,感谢你的帮忙哈!
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1185
在线时间
1564 小时
注册时间
2008-7-30
帖子
4418

贵宾

25
发表于 2013-11-19 09:24:12 | 只看该作者
写教程是一件很苦逼的事。首先,很多人都不具有程序设计的基础。而如果要先讲授程序设计语言本身(Ruby),再讲解框架(RGSS),那整个教程就会成为一个非常庞大的工程(剖析RGSS系统就非常复杂了)。

程序设计语言里面的概念就非常杂,诸如赋值、作用域、面向对象、编程范式等,没有一定编程基础的人很难很快掌握这些概念。一方面,为了能使用脚本来达到自己想要的目的,玩家希望“最小化”学习Ruby,但这样又会造成部分知识的缺失,导致后面的内容无法理解。例如,就编写RGSS脚本来说,我认为完全没必要学习Fiber(纤程)这种东西,因为为了深刻理解这个东西,又需要引入大量的其它知识。然而,RGSS3中最重要的一块:Window_Message就是使用Fiber来实现的。这就又不得不要求读者学习Fiber。

最主要的问题不在于教程的好坏,而在于读者本身的出发点——这也是一个悖论。为了写脚本→急功近利的学→学不好。这也是为什么不太推荐新手玩家学脚本的原因。

程序设计是一门艺术,它涵盖了各种技艺,要想掌握这门艺术,确实没有我们原本想象中的那么简单。

点评

这么看来我这个RGSS1教程贴的确是渣作,sigh……  发表于 2013-11-19 15:32
Fiber……原来RGSS3已经这么丧心病狂了么- -  发表于 2013-11-19 12:50
DK别来无恙。。请教有没有推荐的C++书英文最好  发表于 2013-11-19 09:57

See FScript Here:https://github.com/DeathKing/fscript
潜心编写URG3中。
所有对URG3的疑问和勘误或者建议,请移步至发布页面。
欢迎萌妹纸催更
回复 支持 反对

使用道具 举报

Lv1.梦旅人

巫女会长

梦石
0
星屑
60
在线时间
1028 小时
注册时间
2009-10-24
帖子
3470

贵宾

26
发表于 2013-11-19 09:58:45 | 只看该作者
其实ruby还是很好上手的。。

点评

C++不太了解。不过我认识的人都推C++ Prime。  发表于 2013-11-22 00:54
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1185
在线时间
1564 小时
注册时间
2008-7-30
帖子
4418

贵宾

27
发表于 2013-11-22 00:56:00 | 只看该作者
@RyanBern。 Nope,没有渣与不渣,只有用心与不用心。

点评

同道中人啊,感谢您的支持!  发表于 2013-11-22 08:31

See FScript Here:https://github.com/DeathKing/fscript
潜心编写URG3中。
所有对URG3的疑问和勘误或者建议,请移步至发布页面。
欢迎萌妹纸催更
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
313 小时
注册时间
2013-9-17
帖子
485
28
发表于 2013-11-22 18:45:32 | 只看该作者
感谢楼主分享,学习了!
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
1240
在线时间
18 小时
注册时间
2013-11-5
帖子
2
29
发表于 2014-6-27 18:27:35 | 只看该作者
kuerlulu 发表于 2013-10-13 12:57
好顶赞!本来我也想写的 看lz发了我就来看看
赋值符号=是让左右相同,不一定是右边给左边,只要其中一个 ...

中文变量……没试过
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
19 小时
注册时间
2014-6-14
帖子
29
30
发表于 2014-6-30 02:11:57 | 只看该作者
楼主辛苦了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

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

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

GMT+8, 2024-11-21 22:02

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表