Project1

标题: 解读默认战斗系统脚本 [打印本页]

作者: 禾西    时间: 2008-3-1 22:23
标题: 解读默认战斗系统脚本
好了,论文地狱终于过去。这下可以做点自己喜欢的事情,比如RM的脚本研究。

这次要说的是一点关于默认战斗画面的解读。好让大家熟悉一下默认作者的思路。不过,与其说是研究,多少有点不恰当。当中的知识更多地关于教学。面对的主要是新人,因为动不动就从F1学起,恐怕也太过枯燥。禾西是实用主义者,暂时用不上的东西绝对不学。
--------------------------------------------------------------------------------

首先打开一个新工程,按下F11进入脚本编辑,转到Scene_Battle 1这个脚本,然后开始解读。

在line:11,我们可以看到
  def main

  end # line:99

这个方法。

在英语当中,main可以解释为「主」如同在「主要、主题...」中。
而对于Scene类脚本,main方法可以说的唯一的「主体」。当我们运行一个Scene脚本($scene = Scene_×××.new)的时候,脚本就会自动运行当中的main方法。

那么,在main这个方法当中定义了甚么东西呢?这个关系到我们调用战斗画面.new(Scene_Battle.new)会出现甚么东西,所以就看一下吧!

大致来说,main当中包含了这些东西:
  def main
# 战斗之前
    1.初始化(准备) 「战斗用数据」
    2.生成各种窗口
    3.生成活动块
    4.执行过渡(窗口的切换)

# 战斗之中
    5.战斗
    6.更新

# 战斗之后
    7.刷新地图
    8.释放所有窗口
  end # line:99

这个就是战斗画面的整个运作原理。
下面就等我们细看一下,究竟有具体有甚么东西:

1.初始化(准备)
line:12
    # 初始化战斗用的各种暂时数据
    $game_temp.in_battle = true
    $game_temp.battle_turn = 0
    $game_temp.battle_event_flags.clear
    $game_temp.battle_abort = false
    $game_temp.battle_main_phase = false
    $game_temp.battleback_name = $game_map.battleback_name
    $game_temp.forcing_battler = nil
    # 初始化战斗用事件解释器
    $game_system.battle_interpreter.setup(nil, 0)
    # 准备队伍
    @troop_id = $game_temp.battle_troop_id
    $game_troop.setup(@troop_id)


如同解释所说,这里初始化(意思为:「建立,设置」)了「战斗用的临时数据」。作用就如同我们看戏之间,演员们所做的「幕后工作」。这个时候窗口还没有生成。所有的动作都是秘密的,不公开的。

那么这里定义了甚么东西呢?
主要是更改一些标志:
    $game_temp.in_battle = true # 「角色处于战斗当中」标志 为 true
    $game_temp.battle_turn = 0 # 建立「回合数」=0
    $game_temp.battle_event_flags.clear # 清除所有战斗事件
    $game_temp.battle_abort = false # 「战斗中断」标志 为 false
    $game_temp.battle_main_phase = false # 「战斗状态」标志 为 false
    $game_temp.battleback_name = $game_map.battleback_name # 读取「战斗背景」文件名
    $game_temp.forcing_battler = nil # 清除「强制行动的战斗者」


以及建立一些数据:

    # 初始化战斗用事件解释器
    $game_system.battle_interpreter.setup(nil, 0)
    # 准备队伍 <-(这里指的是「敌人队伍」)
    @troop_id = $game_temp.battle_troop_id # 读取敌人队伍ID
    $game_troop.setup(@troop_id) # 把敌人队伍Id 告诉 Game_Troop这个脚本,然后建

立起敌人的所有数据

这样说大概有点模糊。
不过暂时我们不需要理解,这里让人感兴趣的东西不多。
除了当中的某一句以外大部分都是必要的,而且没有办法更改。
所以现在只要知道我们的「游戏程序」已经准备好战斗了,其它回头再看。
先看看那句我们某些人大概会感到有趣的语句先。

    @troop_id = $game_temp.battle_troop_id # 读取敌人队伍ID


这句语法决定了我们将会遇到的敌人。
当我们按全局搜索(Ctrl+Alt+F)时,我们惊奇地发现$game_temp.battle_troop_id在除战斗脚本外三个地方有提及。

Interpreter 6(事件解释)
Scene_Map(地图画面)
Scene_Title(游戏开始画面)

这里代表了我们将可以在遇到三种敌人:
1.明雷-通过事件遇到敌人
2.暗雷-在地图随机遇敌
3.测试-在数据库当中指定测试敌人

这是个有趣的发现,因为通过这句语法。我们顺藤摸瓜地找到了设置地图遇敌的语法:
脚本:Scene_Map
line:102
    # 遇敌计数为 0 且、且遇敌列表不为空的情况下
    if $game_player.encounter_count == 0 and $game_map.encounter_list != []
      # 不是在事件执行中或者禁止遇敌中
      unless $game_system.map_interpreter.running? or
             $game_system.encounter_disabled
        # 确定队伍
        n = rand($game_map.encounter_list.size)
        troop_id = $game_map.encounter_list[n]
        # 队伍有效的话
        if $data_troops[troop_id] != nil
          # 设置调用战斗标志
          $game_temp.battle_calling = true
          $game_temp.battle_troop_id = troop_id
          $game_temp.battle_can_escape = true
          $game_temp.battle_can_lose = false
          $game_temp.battle_proc = nil
        end
      end
    end


至于作用嘛,聪明的你大概已经想到了。反正是题外话,不说太多。
想要改变地图遇敌判断的人就在这里添加判断语法(if)吧。

2.生成命令窗口 以及 更改位置、显示状态、活动状态等。
line:25
    # 生成角色命令窗口
    s1 = $data_system.words.attack
    s2 = $data_system.words.skill
    s3 = $data_system.words.guard
    s4 = $data_system.words.item
    @actor_command_window = Window_Command.new(160, [s1, s2, s3, s4])
    @actor_command_window.y = 160
    @actor_command_window.back_opacity = 160
    @actor_command_window.active = false
    @actor_command_window.visible = false
    # 生成其它窗口
    @party_command_window = Window_PartyCommand.new
    @help_window = Window_Help.new
    @help_window.back_opacity = 160
    @help_window.visible = false
    @status_window = Window_BattleStatus.new
    @message_window = Window_Message.new


这里的说明应该很通俗易懂。
要说的只有解释一下语法:
×××.new是一个窗口生成语法,在Scene类脚本当中被大量使用。

以及生成了些甚么窗口:
01.角色的行动命令窗口
    @actor_command_window = Window_Command.new(160, [s1, s2, s3, s4])

在这以上的
    s1 = $data_system.words.attack
    s2 = $data_system.words.skill
    s3 = $data_system.words.guard
    s4 = $data_system.words.item


都是在转换一些变量,让脚本看起来顺眼一点,以及难以理解一点。
具体如下:
    s1 = $data_system.words.attack # 系统当中的「攻击」描述文字
    s2 = $data_system.words.skill # 系统当中的「技能」描述文字
    s3 = $data_system.words.guard # 系统当中的「防御」描述文字
    s4 = $data_system.words.item # 系统当中的「物品」描述文字

这些文字都可以在数据库的系统的用语里面改变。

然后在初始化行动命令窗口的时候,作为参数返回给系统。
系统会根据这些需要描述的文字,决定窗口行数与高度。
至于语法当中的160,就是窗口的宽。
至于列数,默认脚本没有定义。但是我们可以在Window_Command当中动手脚。

然后改变一下「行动命令窗口」的y值(横向位置)
    @actor_command_window.y = 160

再改变一下「行动命令窗口」的透明度
    @actor_command_window.back_opacity = 160

最后是把「行动命令窗口」活动状态冻结起来(不能作出选择)
    @actor_command_window.active = false

并隐藏起来
    @actor_command_window.visible = false


=.=凸这里是我最诟病的地方。
因为是在主体当中生成,结果全局的「行动命令窗口」都被锁死!所有人用统一的「行动命令窗口」。
当然,我们可以选择不在这里生成。然后只要在dispose(释放,解放)部分修改一下就可以了。
不过,请稍微记得这里的问题。在「战斗」部分我们再探讨这个诟病,到时候你就会知道我所言不虚。
原作者的「某设定」真是让人无爱……—o—|||(这不是人身攻击)

02.「战」或「逃」的选择窗口
    @party_command_window = Window_PartyCommand.new

从这句看来,原作者的趣味有点怪异。(牢骚)
明明是「战」或「逃」的选择窗口,偏偏要用个「全体选择」这样漂亮的词汇来作为变量名字。让人一看之下不知所云。
不过也不影响,反正很多人都对他没有兴趣-v-|||。

03.帮助窗口
    @help_window = Window_Help.new

这里还是稍微改变了窗口的透明度(这样比较好看)
    @help_window.back_opacity = 160

一开始的时候这个窗口是隐藏的
    @help_window.visible = false

这个帮助窗口的用途很大。除了技能、物品的功能描述以外,同时还在逃跑成功的时候提示「逃跑」,使用技能的时候提示技能名字,使用物品时候提示物品名字。在选择敌人时提示敌人名字,选择队友时候提示队友状态等等。这个还是在下次「见面」的时候再说吧!

04.角色状态的窗口
    @status_window = Window_BattleStatus.new

这里就是我们看到sp,hp,人物名字,当前状态的窗口。

脚本:Window_BattleStatus
方法:refresh
line:39
当中调整
主要作用是描绘血条。具体方法就不说了,一般人都是由血条描绘学起的。

05.信息窗口
    @message_window = Window_Message.new

也就是战斗事件当中的对话窗口。没有甚么好说,价值不多,需要改动的人也好像很少。(据我所知)

3.生成活动块
    @spriteset = Spriteset_Battle.new

这里调用的是Spriteset_Battle脚本,作用是生成战斗参与者(我方+敌方)的图象。
具体不说,反正不会改动到。而且也不是三两语可以解释清楚的。
    # 初始化等待计数
    @wait_count = 0

建立了这么一个全类变量而已,具体作用在后面才会提到。主要处理动画进行当中的等待行为。

4.执行过渡(窗口的切换)
    # 执行过渡
    if $data_system.battle_transition == ""
      Graphics.transition(20)
    else
      Graphics.transition(40, "Graphics/Transitions/" +
        $data_system.battle_transition)
    end

进行从以 freeze 方法固定的画面到现在画面的渐变,其实也就是遇敌的画面。设置位置忘记了=v=,知道的同学告诉一下。

5.战斗
    # 开始自由战斗回合
    start_phase1

不要相信注释 囧rz 这里的自动战斗回合真的不知所云。反正知道这里开始调用战斗更新方法,战斗开始了就好……
留以后再说,这个是一个颇重要的语法。(大概==|||)

6.更新
    # 主循环
    loop do
      # 刷新游戏画面
      Graphics.update
      # 刷新输入信息
      Input.update
      # 刷新画面
      update
      # 如果画面切换的话就中断循环
      if $scene != self
        break
      end
    end

从F1我们知道loop do是无限循环语法,直到我们说break(破坏,中断)才停止。
在loop do循环当中,我们不断地调用
      # 刷新游戏画面
      Graphics.update
      # 刷新输入信息
      Input.update
      # 刷新画面
      update

这三个方法。它们使得我们的画面不断被更新,从而达到战斗的效果。
其中:
Graphics.update用于不断地刷新画面(必要语句)
Input.update则在不断监视我们是否有按下键盘上任何的键(必要语句)
然后,update才是我们需要注意的最重要的方法。
它提供了整个战斗过程的解释,我们将会在下面详细地解析一下。现在不是时候,我们还是继续解读main方法。

      # 如果画面切换的话就中断循环
      if $scene != self
        break
      end

也就是当$scene发生改变的时候就结束战斗。这个判断语法的作用是:一旦我们改变变量$scene,战斗就会被强制中断。
这里的作用一定要记住,这是个满重要的语法,一般来说必不可少,而且不对它作出任何的修改。

7.刷新地图
    # 刷新地图
    $game_map.refresh

如同解释所说,当我们通过了,而且中止了loop do的无限循环后,通常我们就会重新返回到地图画面(测试除外)。然后刷新一下地图,这里的刷新,其实主要的还是对于公共事件以及地图事件事件的更新。也就是说,战斗以后应该消失的事件就让它消失,得到物品就得到物品,完成任务就完成任务。


8.释放所有窗口以及活动块
    # 准备过渡
    Graphics.freeze
    # 释放窗口
    @actor_command_window.dispose
    @party_command_window.dispose
    @help_window.dispose
    @status_window.dispose
    @message_window.dispose
    if @skill_window != nil
      @skill_window.dispose
    end
    if @item_window != nil
      @item_window.dispose
    end
    if @result_window != nil
      @result_window.dispose
    end
    # 释放活动块
    @spriteset.dispose
    # 标题画面切换中的情况
    if $scene.is_a?(Scene_Title)
      # 淡入淡出画面
      Graphics.transition
      Graphics.freeze
    end
    # 战斗测试或者游戏结束以外的画面切换中的情况
    if $BTEST and not $scene.is_a?(Scene_Gameover)
      $scene = nil
    end

释放所有当前存在的窗口,不要让它们留在画面当中。
这里要说一下的是:
    # 准备过渡
    Graphics.freeze

根据F1所说:
「准备渐变,固定现在的画面。从这之后一直到调用 transition 方法,其间禁止一切画面更换。」
在这个语法调用的时候,整个画面都会被锁死。不会进行任何的update(刷新)行为。
如果能理解就最好,不能理解也没差。反正一般不需要修改。

另外就是:

line:78以下的几个判断
    if @skill_window != nil
      @skill_window.dispose
    end
    if @item_window != nil
      @item_window.dispose
    end
    if @result_window != nil
      @result_window.dispose
    end

这里的判断主要是释放不是在main当中生成的窗口。比如:
      @skill_window # 技能选择窗口
      @item_window # 物品选择窗口
      @result_window # 战斗结果窗口

这些脚本都是在update过程当中应时生成的。通过这个语法,当最后它们仍然存在的时候(!=nil),就会自动释放。否则,如果直接释放就出现NoMethodError「undefined method 'dispose' for nil:NilClass 」的提示。

此外,还记得上面说过的「诟病」吗?
我说过如果不在main当中生成的窗口,都可以在这里用上述方法释放。(以防万一)
如果选择在update当中生成「行动命令窗口」(如同我所说的一样。),就必须在这里改变dispose方法。
这样说,应该比较清楚吧?

另外,当战斗测试或者游戏结束中,战斗结束后会把$scene归nil,当$scene = nil的时候,我们的游戏会释放窗口,然后跳出结束程序。

至于:
    # 标题画面切换中的情况
    if $scene.is_a?(Scene_Title)
      # 淡入淡出画面
      Graphics.transition
      Graphics.freeze
    end


作用就如同遇敌画面一样:
在战斗结束之后用渐变的方式返回地图。
作用就是让画面好看一点……Orz

main 方法至此大功告成。大家对于main 这个方法总算有了一点理解了吧?
整个战斗画面就是按照这样的逻辑进行。至于更详细的解读,就看下一贴。关于update方法的解读。

如果对于当中的解读有任何的异议或者补充,禾西非常欢迎有人就这个题目来一同「探讨」。
PS:文字是用word转的,有任何错误恕不负责=v=+

============================================================================这里,让我们继续解读战斗画面
在main以后,我们首先遇到judge,然后是battle_end(result),接着是setup_battle_event。
这些方法我们都暂时跳过不看。因为按照我们现在的思维,我们无法理解。

最后来到
line:220
  def update

  end

记得在main方法当中(line:62),我们曾经遇到过update的调用。
对于方法的调用,Ruby(或者RGSS)没有先定义的必要。因此,这里我们先调用了update,然后再在后面定义是完全没有问题的。

update在英语当中解释为更新,在这里更多地表示画面的改变。通过这个语法,我们才能实现「战斗」的效果。否则画面一闪而过,我们不知道发生甚么事就结束了战斗,是件很囧的事情。

原則上,update會不斷地調用(大概一帧一次,亦就是1/40秒一次。)

OK,废话结束。下面我们还是看一下update里面有甚么东西吧!
  def update
    1.执行战斗事件
    2.系统 (计时器)、刷新画面
    3.刷新各窗口
    4.「冻结」战斗画面
    5.进行战斗
  end


1.执行战斗事件
line:221
    # 执行战斗事件中的情况下
    if $game_system.battle_interpreter.running?
      # 刷新解释器
      $game_system.battle_interpreter.update
      # 强制行动的战斗者不存在的情况下
      if $game_temp.forcing_battler == nil
        # 执行战斗事件结束的情况下
        unless $game_system.battle_interpreter.running?
          # 继续战斗的情况下、再执行战斗事件的设置
          unless judge
            setup_battle_event
          end
        end
        # 如果不是结束战斗回合的情况下
        if @phase != 5
          # 刷新状态窗口
          @status_window.refresh
        end
      end
    end

上面这些语句非常复杂,主要是用来进行战斗事件的。所谓「战斗事件」,就是我们在数据库-队伍-战斗事件当中设定的所有行为。当中把一个队伍当作一个单独事件来使用。余下的不说太多。事件的设置是新手知识。

由于这里的复杂性,因此解说相对充分。
这里想补充一下几点:
1.
    # 执行战斗事件中的情况下
    if $game_system.battle_interpreter.running?


这个语法的根本在Interpreter 1的60行

  def running?
    return @list != nil
  end


当自身的列表为空的时候就返回true。这个有点难以理解,不过一般说来很少人需要动它。值得我们留意的是231行的setup_battle_event
这是我们跳过了的方法,刚才我们没有办法读懂,但是现在我们可以再回头去看它。(update部分暂时放下)
他的执行条件值得深究。
一共有四重条件
1.当执行战斗事件中
2.当「强制行动的战斗者」不存在
3.unless(「除非……否则」,英语中作为if的反意词)执行战斗事件结束
4.unless 继续战斗(judge为true)-judge为true的时候结束战斗,为false时继续战斗
关于$game_system.battle_interpreter.running?的问题,我们暂时不研究。因为这个关系到事件解释的问题,就算RTAB脚本也没有动它。
比较吸引我们注意的,应该是那几句刚才skiped(跳过了)的方法:judge,setup_battle_event。
首先是judge
line:103
  def judge
    # 全灭判定是真、并且同伴人数为 0 的情况下
    if $game_party.all_dead? or $game_party.actors.size == 0
      # 允许失败的情况下
      if $game_temp.battle_can_lose
        # 还原为战斗开始前的 BGM
        $game_system.bgm_play($game_temp.map_bgm)
        # 战斗结束
        battle_end(2)
        # 返回 true
        return true
      end
      # 设置游戏结束标志
      $game_temp.gameover = true
      # 返回 true
      return true
    end
    # 如果存在任意 1 个敌人就返回 false
    for enemy in $game_troop.enemies
      if enemy.exist?
        return false
      end
    end
    # 开始结束战斗回合 (胜利)
    start_phase5
    # 返回 true
    return true
  end

这个方法是用来判断「胜」或「败」的。因为方法自身能够被当作变量使用,所以judge有true or false的值,通过return语法(结束并返回值)来付予。
另外,重复一下:judge为true的时候结束战斗,为false时继续战斗

这里是判断「是否需要继续战斗」的方法,条件见「注释」:
    # 全灭判定是真、并且同伴人数为 0 的情况下
    if $game_party.all_dead? or $game_party.actors.size == 0

下面出现了个分歧
# 允许失败的情况下
      if $game_temp.battle_can_lose

      end
# 否则:
      # 设置游戏结束标志
      $game_temp.gameover = true
      # 返回 true
      return true

这里主要用于「明雷」(通过事件发生战斗)。
如果允许失败,就:
       # 还原为战斗开始前的 BGM
        $game_system.bgm_play($game_temp.map_bgm)
        # 战斗结束,返回参数「2」
        battle_end(2)
        # 返回 true否则:
      # 设置游戏结束标志
      $game_temp.gameover = true
      # 返回 true
      return true


这里我们看到调用了一个battle_end(2)的方法,在下面line:135我们找到他的身影
  #--------------------------------------------------------------------------
  # ● 战斗结束
  #     result : 结果 (0:胜利 1:失败 2:逃跑)
  #--------------------------------------------------------------------------
  def battle_end(result)

  end

2是对于结果(result)的付值。通过注释我们知道2是等于逃跑的。不过其实意义不大。整个方法当中只有在
    # 调用战斗返回调用
    if $game_temp.battle_proc != nil
      $game_temp.battle_proc.call(result)
      $game_temp.battle_proc = nil
    end

当中有调用。
作用是告诉Interpreter(事件解释器)这场战斗的结果,然后,根据战斗的结果控制分歧。其余的解释,用到再说。



重新跑回line:157,我们看看setup_battle_event究竟有甚么作为?
line:157
  def setup_battle_event
    # 正在执行战斗事件的情况下
    if $game_system.battle_interpreter.running?
      return
    end
    # 搜索全部页的战斗事件
    for index in 0...$data_troops[@troop_id].pages.size
      # 获取事件页
      page = $data_troops[@troop_id].pages[index]
      # 事件条件可以参考 c
      c = page.condition
      # 没有指定任何条件的情况下转到下一页
      unless c.turn_valid or c.enemy_valid or
             c.actor_valid or c.switch_valid
        next
      end
      # 执行完毕的情况下转到下一页
      if $game_temp.battle_event_flags[index]
        next
      end
      # 确认回合条件
      if c.turn_valid
        n = $game_temp.battle_turn
        a = c.turn_a
        b = c.turn_b
        if (b == 0 and n != a) or
           (b > 0 and (n < 1 or n < a or n % b != a % b))
          next
        end
      end
      # 确认敌人条件
      if c.enemy_valid
        enemy = $game_troop.enemies[c.enemy_index]
        if enemy == nil or enemy.hp * 100.0 / enemy.maxhp > c.enemy_hp
          next
        end
      end
      # 确认角色条件
      if c.actor_valid
        actor = $game_actors[c.actor_id]
        if actor == nil or actor.hp * 100.0 / actor.maxhp > c.actor_hp
          next
        end
      end
      # 确认开关条件
      if c.switch_valid
        if $game_switches[c.switch_id] == false
          next
        end
      end
      # 设置事件
      $game_system.battle_interpreter.setup(page.list, 0)
      # 本页的范围是 [战斗] 或 [回合] 的情况下
      if page.span <= 1
        # 设置执行结束标志
        $game_temp.battle_event_flags[index] = true
      end
      return
    end
  end

这里我们可以看到作者的细心。因为setup_battle_event的其中一个条件是「当执行战斗事件中」
    # 执行战斗事件中的情况下
    if $game_system.battle_interpreter.running?

但是,实际上他真正需要的条件是
        # 执行战斗事件结束的情况下
        unless $game_system.battle_interpreter.running?

在英语当中,if 和 unless 是一对反意词。(if 是「当」;unless是「不当」。但是中文没有这个词语,因此我们姑且称它「除非……否则」),这里本来是一个矛盾的判断,但是由于当中调用了
line:224
      $game_system.battle_interpreter.update

而使得这组矛盾的判断成立,具体原因不需要懂(至少暂时不需要,如果有兴趣的话可以看一下interpreter的脚本)。
回到正题,因为这里调用了一个「矛盾」的条件判断。原本的作者恐怕程序会出错(虽然少见,但是很正常的事。),他在这里多建立了一个判断:
    # 正在执行战斗事件的情况下
    if $game_system.battle_interpreter.running?
      return
    end

这句的意思大概是「当正在执行战斗事件时(出现错误)」,return(在Ruby当中表示方法「结束」)。这样子,可以减少出错的机会。
然后检索所有的战斗事件页(这个就是事件太多会减慢运行速度的原因,搜索全部的事件页是需要不少内存以及时间的。)
    # 搜索全部页的战斗事件
    for index in 0...$data_troops[@troop_id].pages.size
      # 获取事件页
      page = $data_troops[@troop_id].pages[index]

替换变量,把事件的所有条件都记录在临时变量「c」(只能在本方法中调用)当中。当然这个做法是为了让脚本看起来更清爽,减少程序员打字动作,但是弄得脚本模糊不清的一个方法。牢骚就不发了,毕竟自己也常常喜欢这样。
      # 事件条件可以参考 c
      c = page.condition

在取得条件以后,就进行确认条件的动作。当条件复合,就建立并运行事件,此外就结束方法。

这个是比较复杂的问题,就算说了也未必清楚。因此这里稍微带过,毕竟这下的目的不是弄清「事件」的运作方法。==|||

2.系统 (计时器)、刷新画面
line:242
    # 系统 (计时器)、刷新画面
    $game_system.update
    $game_screen.update
    # 计时器为 0 的情况下
    if $game_system.timer_working and $game_system.timer == 0
      # 中断战斗
      $game_temp.battle_abort = true
    end

还记得地图事件上面的「定时器操作」这个选项无?这里的作用就是
    $game_system.update # 更新定时器
    $game_screen.update # 刷新画面


主要用作限时战斗,当定时器为0时,战斗会自动结束:
    # 计时器为 0 的情况下
    if $game_system.timer_working and $game_system.timer == 0
      # 中断战斗
      $game_temp.battle_abort = true
    end

大概就是这个用途而已……-o-

3.刷新各窗口
    # 刷新窗口
    @help_window.update # 刷新帮助窗口
    @party_command_window.update # 刷新「战」或「逃」选择窗口
    @actor_command_window.update # 刷新「行动命令窗口」
    @status_window.update # 刷新「角色状态窗口」
    @message_window.update # 刷新「信息窗口」

所有在main当中生成的窗口都需要在这里进行更新。不是的就免了,会发生NoMathodError的。

    # 刷新活动块
    @spriteset.update

如同注释

    # 处理过渡中的情况下
    if $game_temp.transition_processing
      # 清除处理过渡中标志
      $game_temp.transition_processing = false
      # 执行过渡
      if $game_temp.transition_name == ""
        Graphics.transition(20)
      else
        Graphics.transition(40, "Graphics/Transitions/" +
          $game_temp.transition_name)
      end
    end

执行过渡(也就是渐变),这里的注释很清楚,不需要重复了。


4.「冻结」战斗画面
    # 显示信息窗口中的情况下
    if $game_temp.message_window_showing
      return
    end
    # 显示效果中的情况下
    if @spriteset.effect?
      return
    end
    # 游戏结束的情况下
    if $game_temp.gameover
      # 切换到游戏结束画面
      $scene = Scene_Gameover.new
      return
    end
    # 返回标题画面的情况下
    if $game_temp.to_title
      # 切换到标题画面
      $scene = Scene_Title.new
      return
    end
    # 中断战斗的情况下
    if $game_temp.battle_abort
      # 还原为战斗前的 BGM
      $game_system.bgm_play($game_temp.map_bgm)
      # 战斗结束
      battle_end(1)
      return
    end
    # 等待中的情况下
    if @wait_count > 0
      # 减少等待计数
      @wait_count -= 1
      return
    end
    # 强制行动的角色存在、
    # 并且战斗事件正在执行的情况下
    if $game_temp.forcing_battler == nil and
       $game_system.battle_interpreter.running?
      return
    end

这里有点趣味,原因是这里是冻结窗口画面的判断语句。但是这里所说的「冻结」,并不是真正意义上的冻结。只是结束update的方法,不进行接下来的战斗画面而做出的「伪?冻结」
冻结的「原因」很多,比如:
01.
    # 显示信息窗口中的情况下
    if $game_temp.message_window_showing
      return
    end

这里冻结的理由是信息窗口在开启当中,证明正在进行对话。因此不「想」有动作打扰而冻结画面。
02.
    # 显示效果中的情况下
    if @spriteset.effect?
      return
    end

因为战斗效果显示正在当中而冻结,很合理。不过呢,RTAB的脚本当中删除了这个冻结的判断,从而实现实时战斗效果(技能效果)的显示。
03.
    # 游戏结束的情况下
    if $game_temp.gameover
      # 切换到游戏结束画面
      $scene = Scene_Gameover.new
      return
    end

因为战斗失败而冻结……(紧止鞭尸@=o=)
04.
    # 返回标题画面的情况下
    if $game_temp.to_title
      # 切换到标题画面
      $scene = Scene_Title.new
      return
    end

返回标题画面,因为渐变执行当中而冻结画面。
05.
    # 中断战斗的情况下
    if $game_temp.battle_abort
      # 还原为战斗前的 BGM
      $game_system.bgm_play($game_temp.map_bgm)
      # 战斗结束
      battle_end(1)
      return
    end

战斗中断,因为渐变执行当中而冻结画面。
06.
    # 等待中的情况下
    if @wait_count > 0
      # 减少等待计数
      @wait_count -= 1
      return
    end

由于战斗动画正在播放,所以全体等待。
07.
    # 强制行动的角色存在、
    # 并且战斗事件正在执行的情况下
    if $game_temp.forcing_battler == nil and
       $game_system.battle_interpreter.running?
      return
    end

事件执行当中,而全体人员行动不能。

如果还想加点甚么冻结判断,或者消除那些冻结判断(比如鞭尸的恶趣味 囧rz),都可以在这里改。

5.进行战斗
    # 回合分支
    case @phase
    when 1  # 自由战斗回合
      update_phase1
    when 2  # 同伴命令回合
      update_phase2
    when 3  # 角色命令回合
      update_phase3
    when 4  # 主回合
      update_phase4
    when 5  # 战斗结束回合
      update_phase5
    end

从这里看到,更新那段战斗画面是由@phase决定的。(RTAB当中的RT条就是靠增加了@phase == 0时候的更新实现的。)

这个是战斗画面的重点,留到下次再说。希望这样的解说对于update的运作还算清晰。
作者: 殤。    时间: 2008-3-1 22:24
提示: 作者被禁止或删除 内容自动屏蔽
作者: 永远的卡卡布    时间: 2008-3-1 22:28
提示: 作者被禁止或删除 内容自动屏蔽
作者: 劍之飛龍☆    时间: 2008-3-1 22:38
LZ辛苦了……{/qiang}
作者: 天圣的马甲    时间: 2008-3-2 20:13
好长……但是真的很强。呼叫斑竹过来鼓励并发布。-v-
作者: Iselia雪    时间: 2008-3-2 20:20
提示: 作者被禁止或删除 内容自动屏蔽
作者: 禾西    时间: 2008-3-2 21:34
以下引用永远的卡卡布于2008-3-1 14:28:29的发言:

主站上某分析RTAB战斗系统的文章顺带分析过默认的系统

那個我也看過。但是好像都沒有涉及很多真正意義上的東西,比如每個phase的作用。

以下引用天圣的马甲于2008-3-2 12:13:48的发言:

好长……但是真的很强。呼叫斑竹过来鼓励并发布。-v-

又沒有寫完……發布幹嘛。==a
現在的都是前言,真正的需要解讀的Scene_Battle2-4還沒有完成,後面的工作比我想像中複雜。

以下引用Iselia雪于2008-3-2 12:20:17的发言:

默认战斗系统的精髓就是复杂的刷新判断

這句倒是真理,不介意我寫進帖子裏面吧?-v-


作者: 水迭澜    时间: 2008-3-2 21:48
当年写一个即时的脚本……看到update_phase1...6我已经很晕
结果update_phase4还分为update_phase4_step1……= =
为了搞懂它的结构我花费了大半辈子的精力
作者: 禾西    时间: 2008-3-3 13:51
本帖最后由 后知后觉 于 2009-12-29 16:14 编辑

主题:《解读默认战斗系统脚本(Scene_Battle 2部分)》 原帖[LINE]1,#dddddd[/LINE]文章太长,上一贴写不下。而且沙发被人抢了,所以只好发第二贴。
如果管理员看到的话,最好把两贴合并。

上文提要:

5.进行战斗
    # 回合分支
    case @phase
    when 1  # 自由战斗回合
      update_phase1
    when 2  # 同伴命令回合
      update_phase2
    when 3  # 角色命令回合
      update_phase3
    when 4  # 主回合
      update_phase4
    when 5  # 战斗结束回合
      update_phase5
    end

完毕!
具体参照原帖地址:
解读默认战斗系统脚本(Scene_Battle 1部分)
================================================================================

接下来要说的是「回合分枝」这部分的内容,应该是最多人感到有兴趣的地方,也是战斗画面脚本最重要的地方。从Scene_Battle 2一直写到Scene_Battle 4,用了3页。

那么,首先还是全局了解一下内容:

1.首先我们要理解这个:

    # 回合分支
    case @phase
    when 1  # 自由战斗回合-(实际是「敌人出现」和「战斗事件设置」的回合)
      update_phase1
    when 2  # 同伴命令回合-(实际是「战」或「逃」的选择回合)
      update_phase2
    when 3  # 角色命令回合-(实际是选择「战斗行动」的回合)
      update_phase3
    when 4  # 主回合-(真正的战斗开始)
      update_phase4
    when 5  # 战斗结束回合-(结束战斗)
      update_phase5
    end

上面说了,当@phase等于哪个,就update哪个回合。

2.整体行动步骤表意
         start_phase1(遇见敌人)
                  ┃
                  ┃  更新_phase1
                  ┆
     ┎┈  start_phase2(选择战斗或者逃跑)
     ┃   ┃                    更新 phase2
     ┃返 ┃                case 指令窗口光标位置
     ┃回 ┃                ┃┆                ┃
     ┃   ┃                ┃┃(战)            ┃(逃)
     ┃战 ┃                ┆┃                ┆
     ┃或 ┃            start_phase3    update_phase2_escape
     ┃逃 ┃           (选择行动)             (进行逃跑判断)
     ┃   ┃                 ┃            ┃            ┃  
     ┃选 ┃ 更新 phase3     ┃            ┃(成功)      ┃(失败)
     ┃择 ┃                 ┆            ┆            ┃  
     ┃   ┃        phase3_next_actor   返回地图         ┃
     ┃   ┃                (下个角色)                   ┃
     ┃   ┃                 ┃                          ┃  
     ┃   ┃                 ┆                          ┃
     ┃   ┗━━━┈    start_phase4(主战斗)   ┈━━━━┛
     ┃                      ┃
     ┃     phase4回合结束   ┃更新 phase4
     ┗━━━━━━━━━━━┫
                             ┃if judge(判断为战斗结束) = true
                             ┆
                        start_phase5(结束战斗)
                             ┃
                             ┃update_phase5
                             ┆
                         返回地图

3.phase4的运作。
phase4(主战斗回合)也同样的有一个非常复杂的判断以及行动过程。里面也有的「回合」分枝:
  def update_phase4
    case @phase4_step
    when 1
      update_phase4_step1
    when 2
      update_phase4_step2
    when 3
      update_phase4_step3
    when 4
      update_phase4_step4
    when 5
      update_phase4_step5
    when 6
      update_phase4_step6
    end
  end

这个暂时知道一下运作原理,到时候回头看。

现在开始单独的行动回合解读:

(Scene_Battle 1完结,进入Scene_Battle 2)


1.phase1-「敌人出现」和「战斗事件设置」的回合
  def start_phase1
    # 转移到回合 1
    @phase = 1
    # 清除全体同伴的行动
    $game_party.clear_actions
    # 设置战斗事件
    setup_battle_event
  end

这里的语句不多,意义也只有简单的两点
01.清除全体同伴的行动($game_party.clear_actions)
这个……我也不明白为甚么要在这里清除全体同伴的行动。囧rz
战斗结束以后不清除,留在下一次战斗开始的时候清除。大概是作者在「战」或「逃」的选择窗口以外的另一个恶趣味吧……

02.设置战斗事件(setup_battle_event)
在战斗开始的瞬间启动无条件的战斗事件,比如一开始敌人的说话之类。

此外,禾西曾经遇过一个问题。就是:「怎么在战斗开始之前付予角色某个状态」
但是禾西的回答是在start_phase1这个方法下面添加脚本语句,但是最后答案选定为在main脚本22行以前(渐变发生以前)添加语句。
这里到底有甚么不一样呢?
不如我们做个实验

我们分别「main脚本22行以前」和「start_phase1以降」处添加两段语句。
01.main脚本22行以前
    $game_party.actors[0].add_state(1) # 角色1添加战斗不能的状态


02. start_phase1中
    $game_party.actors[1].add_state(1) if $game_party.actors[1] !=nil # 如果角色2存在就添加战斗不能的状态


结果我们看到,角色1从最初开始就「死掉了」。(战斗图没有显示)
而角色2就是在战斗开始的瞬间莫明其妙地也「死掉了」。(战斗图片从有变成无)

之所以造成这个效果,我们还是可以用「看戏」的比喻。在main的22行以前,舞台上布幕还没有拉开(所有的窗口都还没有生成),因此,这个时候所有的动作都是隐藏的,不可见的。除非我们走去后台(查看脚本),否则我们对现在发生的事情一无所知。而运行到55行,也就是真正调用start_phase1这个语句的时候,布幕已经被打开。所有的行动都是公开的且可见的(除非你用手段把它隐藏起来)。

因此,两者的差别就在于显示与不显示。当我们善用这个微少微少的差异,就可以有一些特别的效果哦!

而接下来,由于这里把@phase设为1,因此update当中会自动调用update_phase1的方法:
  def update_phase1
    # 胜败判定
    if judge
      # 胜利或者失败的情况下 : 过程结束
      return
    end
    # 开始同伴命令回合
    start_phase2
  end

这个语法没有任何作用。==凸,单单为了迎合其它phase(回合)的格式。
当中调用了两个方法:
01.如果judge = true,结束战斗
之所以会出现这个判断,大概是因为有某些人需要「一回合」的战斗。所谓的「一回合」战斗,就是指在开始的时候调用战斗事件,然后结束战斗(击败敌人或者被敌人击败)。这样的情况下,下面的语句就全部没有用途。因此这里有return把它结束掉。

02.start_phase2(开始「战」或「逃」的画面)
意义在于开始「战」或「逃」的选择。

提问区里面有不少人提到怎么去除「战」或「逃」的选择画面。有些人弄得很复杂,其实只要在这段脚本中的「2」改为「3」,脚本就在自动跳过「战」或「逃」的选择,直接开始选择「战斗行动」。不過,当我們按下Esc键的時候,依然会「战」或「逃」的选择。因此我们还要把
Scene_Battle 3
line:58
        # 开始同伴指令回合
        start_phase2

中2改3(当然说明最好也改一下)。
这样,「战」或「逃」的选择回合就永远离开我们了。






2.phase2-「战」或「逃」的选择回合
  def start_phase2

  end

这个回合生成了「战」或「逃」的选项。

不过说是生成也不太恰当。因为其实如果你还记得,这个「战」或「逃」的窗口早在main脚本的37行当中已经生成了(提醒一下,我们现其实在处main方法中的update解释语句当中,也就是63行的位置),不过当时这个窗口被隐藏了起来而已。

通过这个语句
    # 有效化同伴指令窗口
    # 「战」或「逃」的选择活动 = true
    @party_command_window.active = true
    # 「战」或「逃」的选择可视 = true
    @party_command_window.visible = true


马上就把「战」或「逃」的选择窗口显示出来,让我们看到「窗口」出现的假相。不过实际上,这个窗口一直都存在在我们的屏幕当中。


至于
    # 无效化角色指令窗口
    @actor_command_window.active = false
    @actor_command_window.visible = false

作用就是关闭角色的指令窗口。如果你还记得的话,我们在main底下的34行出现过同样的语句。这个窗口应该被关闭而且隐藏起来了才对啊!这里是不是对此一举呢?
答案是:否。
要解开这个问题,我们稍微偷步一下,跑到
Scene_Battle 3
line:58
这里出现了
        # 开始同伴指令回合
        start_phase2

这是甚么意思呢?
其实,这里是一个返回语法。根据上面的图示,在选择「战斗」以后,我们会进入选择战斗行动的选择回合。
但是,在这里我们可以「反悔」(逃跑则没有)。我们可以按下Esc键或者X键返回到「『战』或『逃』的选项画面」(也就是上面这个语句,在按下Esc后重新回到phase2)。但是,这里出现了一个问题。(预知:)因为在phase3(选择战斗行动)当中,角色的指令窗口会被打开。如果直接返回到phase2(「战」或「逃」的选择回合)没有重现隐藏,那么指令窗口就会仍然存在。

让我们再做个实验,直接去除
Scene_Battle 2
line:41
    # 无效化角色指令窗口
    @actor_command_window.active = false
    @actor_command_window.visible = false

然后测试战斗。

当我们选择了「战斗」以后再按下Esc,我们会发现新生成的指令窗口真的没有消失而是很无理头地显示在屏幕当中。
如果我们把删去的语法重新加回去,再次地测试一下。唔,这次好了。当我们反悔的时候,指令窗口消失了(其实是隐藏了)。一切又回到纯正的phase2界面,一点phase3出现过的痕迹都没有。

在下面,我们看到
    # 清除主回合标志
    $game_temp.battle_main_phase = false

这个语法的作用就如同注释一样,当我们处在「主战斗回合」的时候他就会为true,而不是的话就会为false
我知道这样说很废,作者这么无聊设置个这样的变量有什么用呢?
让我们全局收索
    $game_temp.battle_main_phase

我们发现一共有五个地方出现了他的身影。三个处在Scene_Battle当中为他赋值,另外两个是if分歧。我想我们找到需要的东西了。

查看两个if分歧,一个处在Sprite_Battler,另外一个处在Window_BattleStatus。
在Sprite_Battler
line:67
      # 不是主状态的时候稍稍降低点透明度
      if $game_temp.battle_main_phase
        self.opacity += 3 if self.opacity < 255
      else
        self.opacity -= 3 if self.opacity > 207
      end
    end

就如注释所说(迷音:又是「如注释所说」,要你干嘛!),设置这个变量(含有.字眼的其实是方法,但是方法可以当作变量。上面说过,还记得吧?)的目的是调节透明度。作用似乎是使得战斗画面更加美观一点|||。
想要测试实际效果的话就把当中的3改改为其他数字。在脚本Window_BattleStatus中也是一样语句,所以就不赘言了。

    # 清除全体同伴的行动
    $game_party.clear_actions

因为根据上面的表意图可以得知,结束phase4的回合后又会返回到phase2,所以清除全体同伴的行动。
phase2到phase4的关系:

    ┏┄  phase2
    ┃ 逃(失败)  战
    ┃  ┃      ┃
    ┃  ┆      ┆
    ┗ pahse4 <= phase3


不过要说不清除的话会发生甚么错误,这个我也不清楚。即使删除了也没有遇到甚么问题。知道的同学告诉一下。

    # 不能输入命令的情况下
    unless $game_party.inputable?
      # 开始主回合
      start_phase4
    end

这个判读是用在自动战斗的(中了某种状态而不受控制地战斗),当全体都不受控制时(unless $game_party.inputable?),会自动转跳phase4(主战斗回合)。

    # 设置角色为非选择状态
    @actor_index = -1
    @active_battler = nil

设置角色为非选择状态,但是其实下面会众多语句把角色设置为非选择状态。这里的作用基本上等于零==(不排除有某些我没有注意到的情况)。

    # 转移到回合 2
    @phase = 2

告诉update语法自动调用update_phase2,不用再说了吧?不明白的从头再看,最先位置写得很清楚了。


  def update_phase2
    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
    end
  end

update_phase2只有一个判断语句,就是不断地监视我们C键盘,等待我们做出「战」或「逃」的选择。

当我们按下C键或者Space(空格),程序就会根据指令窗口光标停留的位置决定接下来的动作
      # 同伴指令窗口光标位置分支
      case @party_command_window.index
      when 0  # 战斗
      when 1  # 逃跑
      end
      return

这里使用了一个case语法,用法等于if。
以下两个句子是一样的。
  if a == 1
    p "a"
  elsif a == 2
    p "b"
  end
  case a
  when 1
    p "a"
  when 2
    p "b"
  end

当我们的光标(这里用index表示)停留在0(战斗)的位置
就会调用:

        # 演奏确定 SE
        $game_system.se_play($data_system.decision_se)
        # 开始角色的命令回合
        start_phase3

记得上面表意图中,phase2有两个分歧吗?现在我们就进入phase3(选择角色行动)的分歧

而另外,要是我们选择了「逃跑」,就会调用:
        # 不能逃跑的情况下
        if $game_temp.battle_can_escape == false
          # 演奏冻结 SE
          $game_system.se_play($data_system.buzzer_se)
          return
        end
        # 演奏确定 SE
        $game_system.se_play($data_system.decision_se)
        # 逃走处理
        update_phase2_escape

这里有一个分歧:
        if $game_temp.battle_can_escape == false
          return
        end

当我们的战斗是不可逃跑的,系统会播放禁止的音乐,然后结束方法。也就是说如果战斗是不可逃的,我们只能一定选择「战斗」才行。

如果这场战斗是可以逃跑的,我们不会直接逃跑。而会进入另一个phase2的分歧:update_phase2_escape
这个方法在下面可以遇到,作用是判断是否逃跑成功,以及进行相应的动作。
return

结束方法。我们不需要另外一个分歧。

  def update_phase2_escape
    # 逃跑成功判定
    success = rand(100) < 50 * actors_agi / enemies_agi

    # 成功逃跑的情况下
    if success

    else

    end
  end

上面说了,这里是「判断逃跑是否成功并作出相应的动作」的方法。

内部语句大致只有引用当中的两句。一句「success = rand(100) < 50 * actors_agi / enemies_agi」是用来算出是否成功的。
跟下面的句法相同:
    if rand(100) < (50 * actors_agi / enemies_agi)
      success = true
    else
      success = false
    end

当中的enemies_agi数值,是根据
line:89
    # 计算敌人速度的平均值
    enemies_agi = 0
    enemies_number = 0
    for enemy in $game_troop.enemies
      if enemy.exist?
        enemies_agi += enemy.agi
        enemies_number += 1
      end
    end
    if enemies_number > 0
      enemies_agi /= enemies_number
    end

算出的。
其中调用了初始化变量的方法:
    enemies_agi = 0 # 为enemies_agi 先赋值为 0
    enemies_number = 0 # 为enemies_number 先赋值为 0

因为后面使用到
        enemies_agi += enemy.agi
        enemies_number += 1

如果不初始化的话会出现NoMethodError「undefined methodfor Nil:NilClass」

    for enemy in $game_troop.enemies
      if enemy.exist?
        enemies_agi += enemy.agi
        enemies_number += 1
      end
    end

for ×× in ××
end
是一个循环语句,目的是取得敌人队伍中所有敌人的agi(速度)数值,然后全部加在enemies_agi 这个变量之上。
同时,enemies_number 是记录敌人的数量的变量。每当取得一个敌人的agi数值增加1。

之所以这样做,是因为

    # 如果敌人数量大於0
    if enemies_number > 0
      # 「总agi数值」除以「敌人数目」
      enemies_agi /= enemies_number
      # 求得敌人单体的平均速度
    end

如同注释。

这样子,我们就得到了敌人的平均速度。己方的平均速度用了同样的方法,所以就不说了。

当我们获得了enemies_agi(敌人单体平均速度)和actors_agi(己方角色平均速度)。然后我们就可以根据
success = rand(100) < 50 * actors_agi / enemies_agi

来计算成功与否。默认是用rand(100)(在0 ~ 99当中随机抽调「一」个数)来跟「50 * actors_agi / enemies_agi」的结果比较。
如果左边小於右边的话,就成功。

由这里可以知道,如果角色的平均速度是敌人的2倍以上的话,成功率是100%的(因为rand(100)最大也就是99而已)。

接下来,如果成功(if success)
      # 演奏逃跑 SE
      $game_system.se_play($data_system.escape_se)
      # 还原为战斗开始前的 BGM
      $game_system.bgm_play($game_temp.map_bgm)
      # 战斗结束
      battle_end(1)

重点讲一下battle_end(1)

我们一直有意地漠视这个语句(因为在这之前,一直没有太大的用途。)
现在调用到了。我们就查看一下里面有甚么东西。
Scene_Battle 1
line:136
  def battle_end(result)

  end

这个其实是战斗结束的语法。由于某个原因,而被人挪到Scene_Battle 1去(因为在Scene_Battle 1的line:295行有调用。用作中断战斗的后续行为。当时我们没有具体看。)

防止大家忘记了数字的意义,再次重申一下:
  #--------------------------------------------------------------------------
  # ● 战斗结束
  #     result : 結果 (0:胜利 1:失败 2:逃跑)
  #--------------------------------------------------------------------------
用作告诉事件战斗的结果,然后进行事件的分歧。

里面定义了一些东西,不过注释写得很清晰。所以勿用赘言

  def battle_end(result)
    # 清除战斗中标志
    $game_temp.in_battle = false
    # 清除全体同伴的行动
    $game_party.clear_actions
    # 解除战斗用状态
    for actor in $game_party.actors
      actor.remove_states_battle
    end
    # 清除敌人
    $game_troop.enemies.clear
    # 如果调用战斗返回调用不为nil
    if $game_temp.battle_proc != nil
      # 告诉程序战斗的结果
      $game_temp.battle_proc.call(result)
      # 清空战斗返回调用
      $game_temp.battle_proc = nil
    end
    # 返回到地图画面
    $scene = Scene_Map.new
  end

想说一下的是actor.remove_states_battle,调用的是Game_Battler 2,206行以降的remove_states_battle方法。里面定义了:

  def remove_states_battle
    for i in @states.clone
      if $data_states.battle_only
        remove_state(i)
      end
    end
  end

如果角色身上中了的状态是在战斗以后会自动解除的(在数据库-状态-解除条件当中设置),调用这个方法就会把该种状态解除掉。

    $scene = Scene_Map.new

开头忘记说:$scene = ×××.new 表示切换屏幕画面。
当$scene发生改变的时候,main 方法当中的loop do 循环会自动结束,然后释放(dispose)所有窗口。

OK,返回Scene_Battle 2的126行,继续刚才的话题。
如果逃跑失败。程序会清空全体角色的行动,然后开始phase4(主战斗回合)
      # 清除全体同伴的行动
      $game_party.clear_actions
      # 开始主回合
      start_phase4

这里全体同伴角色的行动都被清空,在这样的情况下开始phase4的话,我方角色是不会有任何动作的。但是敌方会,因为敌人的行动是在phase4当中start_phase4里设置的。这样子,我方队员只有挨打的份。
如果希望在逃跑失败的时候添加对话,比如说逃跑,我们可以在这里(start_phase4以前)添加一个语句:
      @help_window.set_text("逃走失败", 1)
      @help_window.visible = true
      @wait_count = 20
      if @wait_count == 0
        @help_window.visible = false
      end

这样子我们就会看到逃跑失败的时候,画面上出现「逃跑失败」的字眼了。每一 @wait_count 好像等于 1/40秒,具体上有差异,可以调到自己喜欢时长。

phase2的解说就到此为止

(注:phase3和phase4的语句都没有出现在Scene_Battle 2当中,但是这里顺带讲一下)
3.phase3-选择战斗动作的回合
这个回合是选择行动类型的回合。画面上会生成指令窗口,然后选择「攻击,技能,物品」等指令,然后选择敌人,开始战斗。
具体因为非常复杂。这里只要假象一下有一个类似这样的语句

  def start_phase3
    # 转移到回合 3
    @phase = 3
  end
  def update_phase3
    # 开始主回合
    start_phase4
  end

就可以了。我们下一帖再说。

4.phase4-主战斗的回合
这个是战斗的主要回合。复杂程度超乎平常。我们将来会花费新一贴的来讲解。
这里幻想一下出现了这样的语句:

  def start_phase4
    # 转移到回合 4
    @phase = 4
  end
  def update_phase4
    if judge # 如果不需要继续战斗
      # 结束战斗
      start_phase5
    else # 如果需要继续战斗
      # 返回「战」或「逃」的选择
      start_phase2
    end
  end

这里调用了一个judge的方法(判断是否战斗结束)。
如果judge为true(战斗结束)
我们就会进入phase5(战斗结束,获得经验,战利品的回合。)
如果敌人还存在,而且我方也还有人。,udge就会为false。这个时候我们需要继续战斗。所以会进入phase2(「战」或「逃」的选择),形成一个循环。
详细的以后再说。

5.phase5-战斗结束,获得经验,战利品的回合。
  def start_phase5

  end

这里定义了三样东西
01.演奏音乐
    # 演奏战斗结束 ME
    $game_system.me_play($game_system.battle_end_me)
    # 还原为战斗开始前的 BGM
    $game_system.bgm_play($game_temp.map_bgm)


02.获得物品

03.显示获得经验與战利品的窗口
    # 生成战斗结果窗口
    @result_window = Window_BattleResult.new(exp, gold, treasures)
    # 设置等待计数
    @phase5_wait_count = 100

先说一说03部分。
战斗结果的窗口是调用Window_BattleResult这个脚本。如果要修改显示的內容,就可以在refresh方法当中修改。比如不显示经验(禾西的恶趣味)。

@phase5_wait_count = 100 这个是用来告诉画面不要刷新,暂时冻结画面的计时器变量。在update_phase5当中使用。
01部分没有甚么好说。

至于02,我多多少少又有点诟病。原因是我认为把「获得物品」的计算方法「锁」在这里不是很好。我之所以说「锁」,是因为这个语句正好位于start_phase5的中部。很多人都欢喜修改战后获得物品的脚本,但是因为这个语句正好位于update_phase5的中间位置,使得我们不能用alias或者main(脚本main)前插件的方法修改。
如果这里的所有语句浓缩成一个方法,比如:
    # 取得战利品
    get_trophies

然后再在下面定义
  def get_trophies

  end

我觉得会好很多。

废话就到此为止。-o-
在02部分
line:147
我们首先初始化了我们需要的数据
    # 初始化 EXP、金钱、宝物
    exp = 0
    gold = 0
    treasures = []

如同上面说的,要是不初始化而直接用来「+= -= *= /= ……」的话就会发生NoMethodError「undefined methodfor Nil:NilClass」

然后下面是一个循环
    # 循环
    for enemy in $game_troop.enemies

    end


如果不是隐藏的敌人
      # 敌人不是隐藏状态的情况下
      unless enemy.hidden

把设置当中敌人战败后的能获得的经验和金钱全部加载在exp(experience的缩写,表示「经验,经历」)gold(金,引申为「金钱,钱财」)两个变量当中。

        # 获得 EXP、增加金钱
        exp += enemy.exp
        gold += enemy.gold

        # 出现宝物判定
        if rand(100) < enemy.treasure_prob
        end

因为默认的数据库当中击败一个敌人只会出现一件宝物(战利品),所以这里理所当然地只有一个判断。
如同上文所说,rand(100)是表示随机抽取一个0 ~ 99的数字,如果默认的机率(enemy.treasure_prob)比这个数字大的话,就会获得战利品。
下面虽然出现了三个if语法,但是其实做的只是一件东西:如果不是设定为没有物品获得。那么就把物品的数据添加到treasures当中去。因为treasures是数组,而$data_××s[enemy.××_id]不是,因此用treasures.push(××)的方法。这个是把新的项目添加到数组尾部的语法。
          if enemy.item_id > 0
            treasures.push($data_items[enemy.item_id])
          end
          if enemy.weapon_id > 0
            treasures.push($data_weapons[enemy.weapon_id])
          end
          if enemy.armor_id > 0
            treasures.push($data_armors[enemy.armor_id])
          end

    # 限制宝物数为 6 个
    treasures = treasures[0..5]

只保留treasures的前6项数据,其他的自动删除。
用途只是恶趣味而已,原因不明。不喜欢的随意把5改为更高。


    # 获得 EXP
    for i in 0...$game_party.actors.size
      actor = $game_party.actors
    end

利用循环把actor变量赋值为己方队友的任何一人

如果角色的.cant_get_exp?为false(可以获得经验)-(如果角色中了不能获得经验的状态.cant_get_exp?就会为true,这个可以在数据库-状态当中设定。战斗不能是默认的不能获得经验状态。)
那么就获得经验。
      if actor.cant_get_exp? == false
        last_level = actor.level
        actor.exp += exp
      end

last_level 记录了角色获得经验前的等级

        if actor.level > last_level
          # 显示升级的提示
          @status_window.level_up(i)
        end

如果角色当前的等级(actor.level)比获得经验前的等级(last_level)高,就显示升级的提示。


    # 获得金钱
    $game_party.gain_gold(gold)
    # 获得宝物
    for item in treasures
      case item
      # 当宝物的父类是物品
      when RPG::Item
        # 调用获得物品的方法
        $game_party.gain_item(item.id, 1)
      # 当宝物的父类是武器
      when RPG::Weapon
        # 调用获得武器的方法
        $game_party.gain_weapon(item.id, 1)
      # 当宝物的父类是防具
      when RPG::Armor
        # 调用获得防具的方法
        $game_party.gain_armor(item.id, 1)
      end
    end

具体可以在Game_Party 当中搜索含有gain字眼的语句。

因为当前的
    @phase = 5
所以update自动调用update_phase5的方法
    # 等待计数大于 0 的情况下
    if @phase5_wait_count > 0
      # 减少等待计数
      @phase5_wait_count -= 1

这个时候画面是静止的,用来播放战斗后的音乐

然后
      # 当等待计数为 0 的情况下
      if @phase5_wait_count == 0
        # 显示结果窗口
        @result_window.visible = true
        # 清除主回合标志
        $game_temp.battle_main_phase = false
        # 刷新状态窗口
        @status_window.refresh
      end
      # 结束方法
      return


让玩家看清楚他们获得了甚么东西。这个画面会一直停留。直到玩家按下C键或者Space(空格)为止。


最后,当玩家按下C键或Space
    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
      # 战斗结束
      battle_end(0)
    end

整个战斗过程结束。
调用战斗结束的方法,返回0(战斗胜利)
释放所有窗口,返回地图。
作者: 禾西    时间: 2008-3-3 15:55
本帖最后由 后知后觉 于 2009-12-29 16:17 编辑

============================================================================
这下说一下phase3-选择战斗动作的回合

在说之前稍稍地抱怨一下,其他的脚本也还好。就是这个phase3(scene_battle 3)写的乱七八糟。让人看着头疼|||

有鉴于此,这个脚本的解读将会跟原定脚本的方法顺序有出入。

首先我们看一下start_phase3

  def start_phase3
    # 转移到回合 3
    @phase = 3
    # 设置觉得为非选择状态
    @actor_index = -1
    @active_battler = nil
    # 输入下一个角色的命令
    phase3_next_actor
  end

真糟糕==!
短短的四句语句我们居然只能看懂1句:(@phase = 3)
完全不能理解
    @actor_index = -1
    @active_battler = nil

虽然在上面我们也遇过这个语句,当时我们也是看得一头烟。

那么我们就先行放下,找到phase3_next_actor(line:23)。就算再不济,至少,我们也要知道最初的时候发生了甚么事情吧!这个方法可是建立进入phase3后最初的设定啊!

可视不如愿的是,phase3_next_actor方法一样糟糕。不过,至少还是可以「稍微地」解读。那么,我们就从这里入手吧!

  def phase3_prior_actor

  end

首先我们遇到了一个循环。

    # 循环
    begin

    end until @active_battler.inputable?

这是一个不断调用的循环,除非达到end until(一般不用end) 的条件,否则就会一直调用begin...end until之间的语句。
而这里的结束条件是@active_battler.inputable?

不知道大家还记得甚么是inputable?呢?反正禾西写到这里已经忘记了,所以再说一次。
方法inputable?的语句写在Game_Battler 1的286行。
条件是两个:
角色不是在隐藏状态(一般这个指的是中了「当作 hp 为0 的状态」的时候),以及restriction(限制,约束)> 1
  def inputable?
    return (not @hidden and restriction <= 1)
  end

如果条件全部符合就返回true,否则就返回false
这个可以在数据库-状态当中查看。这个inputable?主要是受角色身中的状态决定的。

OK,回顾结束。继续说说这个循环。
在@active_battler的inputable?为true之前,这个循环是不会被终结的。
但是这个@active_battler是什么东西呢?

根据字面上看来,应该是当前活动的战斗者(active在拉丁语当中是「行动的」的意思)。
我们记得最初@actor_index的赋值为-1,但是这里的数值应该发生了改变。
让我们先看看循环当中定义了甚么东西再作定论。

      # 角色的明灭效果 OFF
      if @active_battler != nil
        @active_battler.blink = false
      end

如果 @active_battler 不等于 nil,
关闭角色的明灭效果。

唔,我们记得在start_phase3中,@active_battler被定义为nil
这个语句我们现在大概用不上。应该是在后面调用的,先跳过先。


      # 最后的角色的情况
      if @actor_index == $game_party.actors.size-1
        # 开始主回合
        start_phase4
        return
      end

如果@actor_index等于我方队伍的人数减少1,就开始主战斗回合。

怎么可能……@actor_index在start_phase3中被定义为-1。这样子,除非我们的队伍人数为0,否则也没有用。但是说回头,要是我们的队伍人数等于零的话,这场战斗还打甚么嘛!

没有用,跳过。

(看见没有,这个脚本的语句写得不是一般的晦涩难懂。)

在连续失望两次以后,我们终于找到了我们想要的东西。

      # 推进角色索引
      @actor_index += 1
      @active_battler = $game_party.actors[@actor_index]
      @active_battler.blink = true

哦哦哦哦!我们终于找到改变@actor_index赋值的语句了!而且还有@active_battler!这下开始明朗了。
根据这里的语句,我们得知在回合开始的时候,@actor_index是自动赋值为0的!(因为初始化为-1,然后这里+1)。而这个@actor_index在这里看来,似乎是用作取得角色数据用的。

在全局变量$game_party.actors当中,记载了我们战斗队伍中所有角色的数据。至于[]语法,就是用来取得特定人物的数据。这里是[0],所以说现在的@active_battler拥有我们队伍当中的首位队员的数据。

至于下面的@active_battler.blink = true,根据上面所知,似乎是用来改变当前活动角色(@active_battler)的明灭闪烁效果的。
这个涉及内部方法,在Sprite_Battler的75行,我们可以找使用这个作为分歧(if 语句)的语句。根据里面所说:

    # 明灭
    if @battler.blink
      blink_on
    else
      blink_off
    end

如果@battler.blink == true,就调用blink_on。如果@battler.blink == false,就调用blink_off。这两语法在脚本当中找不到具体的定义方法,但是我们可以在F1当中找到解释:

blink_on
闪烁效果为 ON。能周期地反复使精灵亮白的闪烁。是针对指令输入中的角色使用的效果。

blink_off
闪烁效果为 OFF。

这个懂了最好,不懂就算。这里我们不需要样样东西都知道。

接下里,如果当前活动角色的限制为无(或者不能使用魔法而已)。这个循环就会暂时结束,然后调用底下

    # 设置角色的命令窗口
    phase3_setup_command_window


在line:73,我们找到这个方法。

  def phase3_setup_command_window

  end

里面定义了
    # 同伴指令窗口无效化
    @party_command_window.active = false
    @party_command_window.visible = false

关闭并隐藏战逃的选择窗口


    # 角色指令窗口无效化
    @actor_command_window.active = true
    @actor_command_window.visible = true

开启并显示角色指令窗口。

(这里稍微唠叨一下,在讲述main的时候我就抱怨过,作者把@actor_command_window的自由限制了。全局只用一个同样的窗口不止,还到处打开、关闭,打开,关闭。一点灵活性都没有如果要重写战斗脚本的话,这里必然是重写的重点,可惜的是到RTAB的描写也没有触及这个弊端。如果是在使用的时候才建成,而不用的时候才关闭,这个才是最好的方法。这样才能根据角色的不同而实现不同的选择窗口。)


    # 设置角色指令窗口的位置
    @actor_command_window.x = @actor_index * 160
    # 设置索引为 0
    @actor_command_window.index = 0

重新设置指令窗口的横向位置(x值)。并且把光标初始在0(最顶的选项,一般是攻击选择)上面。

OK,最初设定的东西就是这样了。如果这个回合被开始,最左边的角色就开始闪烁,生成选择指令窗口并把光标初始在最顶点的选择项上面。

接下来,就是update的问题了。


继Iselia雪所云(2008),“默认战斗系统的精髓就是复杂的刷新判断”。

在update当中,我们就已经见识过了。同时,在解读phase4运作的时候(虽然只是提及),我们也看到了同样的方法。
虽然理论上面很简单,不外乎就是使用分歧判断执行的语句。这个技巧,我们在学习事件的时候就已经学会了。

update_phase3也不例外,还是用了这个方法。

  def update_phase3

  end

不过,这次的方法有点特殊。因为这次是根据窗口是否存在来判断是否刷新。这是个新方法。可是话说回头,禾西还是乐于使用跟phase4一样的update方法。这个虽然很好,但是相对来说难以读懂(虽然这个比较例外因为它的简单)。简洁性和优越性,永远跟解读性相冲突。

在phase3当中,实际上,我们一共使用了5个窗口(因此一共有五个分歧语法在update_phase3方法当中。)
----光标类
01.敌人光标
02.角色光标
----行动窗口类
03.特技窗口
04.物品窗口
----主界面
05.角色指令窗口

前四个的更新条件都一样(判断是否存在)。只有当存在(!=nil)的时候,程序才会更新他们,否则的话,理得它们死(含有错误代码一堆)
都无关系。

而最后一个(其实我觉得应该排在判断的第一位才对?)就有点特别。它是根据是否活动(.active == true)来判断的。这里顺便说一下(前面忘记了)。
  if a == true
    p "true"
  end

  if a
    p "true"
  end

这两个方法是一样的。

这个update_phase3其实没有甚么好讲的。其实就是这样。
  def update_phase3
    # 如果敌人箭头光标(Sprite-活动块)存在
    if @enemy_arrow != nil
      # 更新选择敌人箭头光标
      update_phase3_enemy_select

    # 如果角色箭头光标(Sprite-活动块)存在
    elsif @actor_arrow != nil
      # 更新选择角色箭头光标
      update_phase3_actor_select

    # 如果特技窗口(窗口)存在
    elsif @skill_window != nil
      # 更新特技选择窗口
      update_phase3_skill_select
    # 如果物品窗口(窗口)存在
    elsif @item_window != nil
      # 更新物品选择窗口
      update_phase3_item_select

    # 如果角色指令窗口(窗口)活动
    elsif @actor_command_window.active
      # 更新角色指令窗口
      update_phase3_basic_command
    end
  end

稍微提一下,当前4个窗口(或者活动块)存在的时候,角色指令窗口将会处在「不活动」、「不显示」的状态。所以理所当然地,也不会被人更新。
其它没有甚么好说,注释写得很清楚。

因为在以上解读当中,我们都没有看到任何对于@enemy_arrow、@actor_arrow,@skill_window,@item_window的定义,因此我们可以断言,在phase3被开始的时候,任何关于以上提及的窗口或活动块都是不存在的。反倒我们看到@actor_command_window.active = true 的语句(默认Scene_Battle 3脚本78行),因此在我们通过进入phase以后,第一个调用的更新方法就是update_phase3_basic_command(这次总算法如其名了)。

在update_phase3的下面我们找到了update_phase3_basic_command的方法。我们一看就发现里面定义了两个监视我们键盘的分歧。

(监视键盘的分歧:指的是「if Input.trigger?(Input::B)」一类的语句。如果我们甚么按键都不按下,那么就不会发生任何事情。如果我们按下相对应的键,程序就马上调用分歧底下的语句。这种技巧在默认窗口脚本,甚至其他可以选择的窗口,当中使用得相当频繁)。

  def update_phase3_basic_command
    # 按下 B 键的情况下
    if Input.trigger?(Input::B)
    end
    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
    end
  end


一般来说,在默认脚本当中,B键指的是否定而C键指的是确认(Esc 或 Space尤如)。

OK,在解读update_phase3_basic_command之前,先介绍两个定义在Scene_Battle 3中方法。

#---------------------------------------------------------
1.phase3_prior_actor
位于line:48
作用是处理角色选择时候的左移。


prior:pri(前,如同pre)+or(者)/引申为「前一位」

2.phase3_next_actor
位于line:23
作用是处理角色选择时候的右移(同时也负担起初始化「角色选择效果」,大家没有忘记在上面才刚说过吧?)

next:下一位
#---------------------------------------------------------


    # 按下 B 键的情况下
    if Input.trigger?(Input::B)
      # 演奏取消 SE
      $game_system.se_play($data_system.cancel_se)
      # 转向前一个角色的指令输入
      phase3_prior_actor
      return
    end

B键分歧没有好讲的地方。使用return的目的是当B键分歧成立的时候,结束方法完全不管C键分歧。


    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
      # 角色指令窗口光标位置分歧
      case @actor_command_window.index
      when 0  # 攻击
      when 1  # 特技
      when 2  # 防御
      when 3  # 物品
      end

C键分歧使用了case语法(基本上等于if的用法)。由指令窗口的光标所在位置(.index)决定动作行为。

(唠叨:其实技能的语句比防御多吧?==!为甚么技能写得比防御先?)
----------------------------------------
在继续以前,先介绍两个常量:
        @active_battler.current_action.kind = 0
        @active_battler.current_action.basic = 0

两者皆来自Game_BattleAction(@active_battler.current_action)
是战斗者独有的「可写变量」(能够改变值)。

001.
  @active_battler.current_action.kind
作用是告诉脚本角色的「动作行为」

具体设置如下: (基本 / 特技 / 物品)

002.
  @active_battler.current_action.basic
作用同样是告诉脚本角色的「战斗行为」

具体设置如下:(攻击 / 防御 / 逃跑 / 甚么也不做)

Q:这两个变量的不同点在于那里呢?

A:
@active_battler.current_action.kind

用于生成行为结果。

    # 行动种类分支
    case @active_battler.current_action.kind
    when 0  # 基本
      make_basic_action_result
    when 1  # 特技
      make_skill_action_result
    when 2  # 物品
      make_item_action_result
    end

=============================================

@active_battler.current_action.kind

用于显示行为表现

    # 攻击的情况下
    if @active_battler.current_action.basic == 0
      # 设置攻击动画 ID
      @animation1_id = @active_battler.animation1_id
      @animation2_id = @active_battler.animation2_id
      return
    end
    # 防御的情况下
    if @active_battler.current_action.basic == 1
      # 帮助窗口显示"防御"
      @help_window.set_text($data_system.words.guard, 1)
      return
    end
    # 逃跑的情况下
    if @active_battler.is_a?(Game_Enemy) and
       @active_battler.current_action.basic == 2
      #  帮助窗口显示"逃跑"
      @help_window.set_text("逃跑", 1)
      # 逃跑
      @active_battler.escape
      return
    end
    # 什么也不做的情况下
    if @active_battler.current_action.basic == 3
      # 清除强制行动对像的战斗者
      $game_temp.forcing_battler = nil
      # 移至步骤 1
      @phase4_step = 1
      return
    end
  end


一般来说,
当@active_battler.current_action.kind为0(基本)的时候,一定存在@active_battler.current_action.basic
        @active_battler.current_action.kind = 0
        @active_battler.current_action.basic = 1

当@active_battler.current_action.kind > 0(特技 / 物品)的时候,一定不存在@active_battler.current_action.basic
        @active_battler.current_action.kind = 1
        @active_battler.current_action.kind = 2

所以我们可以认为@active_battler.current_action.basic是@active_battler.current_action.kind = 0时候的分歧。

basic:base(基本)+ic(的)/引申为「基本的」行为

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

在我们按下C键的时候,程序会根据指「令窗口的光标所在位置(.index)」决定下一步的动作:


    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
      # 角色指令窗口光标位置分歧
      case @actor_command_window.index
      when 0  # 攻击
        # 演奏确定 SE
        $game_system.se_play($data_system.decision_se)
      
        start_enemy_select # 开始选择敌人

      when 1  # 特技
        # 演奏确定 SE
        $game_system.se_play($data_system.decision_se)
      
        start_skill_select # 开始选择特技

      when 2  # 防御
        # 演奏确定 SE
        $game_system.se_play($data_system.decision_se)

        phase3_next_actor# 直接此角色的结束选择窗口,进入下个角色的选择窗口

      when 3  # 物品
        # 演奏确定 SE
        $game_system.se_play($data_system.decision_se)
      
        start_item_select # 开始选择物品

      end

这里有一个语句可以优化。每个分歧都使用了演奏SE的语句虽然安全(不容易出错),但是似乎没有多大用途(在逻辑上说不过去)。其实在按下C键之后播放SE就可以了:
    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
#---------------------------------------------------
      # 演奏确定 SE
      $game_system.se_play($data_system.decision_se)
#---------------------------------------------------
      # 角色指令窗口光标位置分歧
      case @actor_command_window.index
      when 0  # 攻击
        start_enemy_select # 开始选择敌人
      when 1  # 特技
        start_skill_select # 开始选择特技
      when 2  # 防御
        phase3_next_actor# 直接此角色的结束选择窗口,进入下个角色的选择窗口
      when 3  # 物品
        start_item_select # 开始选择物品
      end


然后,我们设置行为「行为」(处理@active_battler.current_action.kind(动作行为) & @active_battler.current_action.basic(战斗行为))

  #-------------------------------------------------------------------------------------------------
  # ● 行为的意义:
  #   @active_battler.current_action.kind                     # 种类 (基本 / 特技 / 物品)
  #   @active_battler.current_action.basic                    # 基本 (攻击 / 防御 / 逃跑 / 甚么也不做)
  #-------------------------------------------------------------------------------------------------
    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
#---------------------------------------------------
      # 演奏确定 SE
      $game_system.se_play($data_system.decision_se)
#---------------------------------------------------
      # 角色指令窗口光标位置分歧
      case @actor_command_window.index
      when 0  # 攻击
    #--------------------------------------------
        # 设置行动
        @active_battler.current_action.kind = 0
        @active_battler.current_action.basic = 0
    #--------------------------------------------
        start_enemy_select # 开始选择敌人
      when 1  # 特技
    #--------------------------------------------
        # 设置行动
        @active_battler.current_action.kind = 1
    #--------------------------------------------
        start_skill_select # 开始选择特技
      when 2  # 防御
    #--------------------------------------------
        # 设置行动
        @active_battler.current_action.kind = 0
        @active_battler.current_action.basic = 1
    #--------------------------------------------
        phase3_next_actor# 直接此角色的结束选择窗口,进入下个角色的选择窗口
      when 3  # 物品
    #--------------------------------------------
        # 设置行动
        @active_battler.current_action.kind = 2
    #--------------------------------------------
        start_item_select # 开始选择物品
      end

具体来说,整个phase3 的 update过程可以用下图表示


               上一位角色的选择窗口
                      ┃按下B键

                   指令窗口

                      ┃按下C键
    ┏━━━━━┳━━┻━━┳━━━━━┓
    ┃(攻击)    ┃(技能)    ┃(防御)    ┃(物品)
    ┃                      ┗━━━━━━━━━┓
    ┃      选择招数                 选择物品   ┃
    ┃                                          ┃
    ┃          ┃                      ┃      ┃
    ┃          ┗━━┳━━━━━┳━━┛      ┃
    ┃                                          ┃
    ┗━━━━━  选择敌人  /  选择队友(or自己) ┃
                                                ┃
                      ┗━━┳━━┛            ┃
                                                ┃
                         下个角色的选择窗口   ━┛


其中,选择招数、选择物品、选择敌人、选择队友四个窗口都是统一使用的。因此,下面的分析没有所谓的逻辑 囧ez

01.选择招数(line:380)

这个环节分为三部分:

start_skill_select、
update_phase3_skill_select(在update_phase3中调用。)
end_skill_select

start_skill_select 的作用是临时生成技能选择窗口。
  #--------------------------------------------------------------------------
  # ● 开始选择特技
  #--------------------------------------------------------------------------
  def start_skill_select
    # 临时生成特技窗口
    @skill_window = Window_Skill.new(@active_battler)
    # 生成关联帮助窗口-(描述技能)
    @skill_window.help_window = @help_window
    # 无效化角色指令窗口
    @actor_command_window.active = false
    @actor_command_window.visible = false
  end

如同注释……

至于特技窗口具体描述,可以参看脚本Window_Skill(帮助窗口类同)。

update_phase3_skill_select 的作用是更新技能选择窗口,如果 @skill_window 不等于 nil 就在 update_phase3 中调用。

  #--------------------------------------------------------------------------
  # ● 刷新画面 (角色命令回合 : 选择特技)
  #--------------------------------------------------------------------------
  def update_phase3_skill_select
    # 设置特技窗口为可视状态
    @skill_window.visible = true
    # 刷新特技窗口
    @skill_window.update
    # 按下 B 键的情况下(取消)
    if Input.trigger?(Input::B)
      # 演奏取消 SE
      $game_system.se_play($data_system.cancel_se)
      # 结束特技选择
      end_skill_select
      return
    end
    # 按下 C 键的情况下(确认)
    if Input.trigger?(Input::C)
      # 获取特技选择窗口现在选择的特技的数据
      @skill = @skill_window.skill
      # 如果无法使用-(技能不存在 或者 技能不能使用)
      if @skill == nil or not @active_battler.skill_can_use?(@skill.id)
        # 演奏冻结 SE
        $game_system.se_play($data_system.buzzer_se)
        return
      end
# 如果可以使用:
      # 演奏确定 SE
      $game_system.se_play($data_system.decision_se)
      # 设置角色将要使用的技能
      @active_battler.current_action.skill_id = @skill.id
      # 设置特技窗口为不可见状态
      @skill_window.visible = false
      # 效果范围是敌单体的情况下
      if @skill.scope == 1
        # 开始选择敌人
        start_enemy_select
      # 如果效果范围是我方单体 或者 我方单体(hp 0)
      elsif @skill.scope == 3 or @skill.scope == 5
        # 开始选择角色
        start_actor_select
      # 此外:(全体、自身、己方全体、己方全体(hp 0))
      else
        # 选择特技结束
        end_skill_select
        # 转到下一位角色的指令输入
        phase3_next_actor
      end
      return
    end
  end

这里主要调用了两个分歧:
当取消(if Input.trigger?(Input::B))的时候直接转入「结束特技选择」;
当确认(if Input.trigger?(Input::C))的时候转入「目标选择」。经过目标选择后转入「下个角色的选择」

另外想说说的是:

      @active_battler.current_action.skill_id = @skill.id

这个变量的作用是记录此个角色将要使用的技能ID,然后在后面调用(生成攻击动画、目标减血、SP消耗等)。

另外这个方法在 update_phase3 中被自动调用如果 @skill_window 变量不等于 nil。

如果@skill_window 变量等于 nil 就停止调用(停止更新)。这就是下面要 nil 化 @skill_window 变量的原因。

end_skill_select 的作用是释放临时生成的窗口,然后 nil 化 @skill_window 变量。
  #--------------------------------------------------------------------------
  # ● 选择特技结束
  #--------------------------------------------------------------------------
  def end_skill_select
    # 释放特技窗口
    @skill_window.dispose
    # nil化
    @skill_window = nil
    # 隐藏帮助窗口
    @help_window.visible = false
    # 有效化角色指令窗口
    @actor_command_window.active = true
    @actor_command_window.visible = true
  end

如同注释,没有值得好说的地方

02.选择物品(line:405)
同上,跳过。物品其实可以看做另一种技能(只是称呼不一样。)

选择物品描述请参看脚本Window_Item

03.选择敌人(生成指向敌人的箭头光标)
主要也是包含了三个步骤:
start_enemy_select
update_phase3_enemy_select(在update_phase3中调用。)
end_enemy_select

具体用法跟01.选择招数相似,就是内部的语句有少量变化。

  #--------------------------------------------------------------------------
  # ● 开始选择敌人
  #--------------------------------------------------------------------------
  def start_enemy_select
    # 生成敌人箭头
    @enemy_arrow = Arrow_Enemy.new(@spriteset.viewport1)
    # 关联帮助窗口
    @enemy_arrow.help_window = @help_window
    # 无效化角色指令窗口
    @actor_command_window.active = false
    @actor_command_window.visible = false
  end

一样,不说。

箭头活动块的描述请移步参看脚本Arrow_Enemy

  #--------------------------------------------------------------------------
  # ● 刷新画面画面 (角色命令回合 : 选择敌人)
  #--------------------------------------------------------------------------
  def update_phase3_enemy_select
    # 刷新敌人箭头
    @enemy_arrow.update
    # 按下 B 键的情况下
    if Input.trigger?(Input::B)
      # 演奏取消 SE
      $game_system.se_play($data_system.cancel_se)
      # 选择敌人结束
      end_enemy_select
      return
    end

    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
      # 演奏确定 SE
      $game_system.se_play($data_system.decision_se)
      # 设置目标(单体) - 根据选择指示箭头的位置确定
      @active_battler.current_action.target_index = @enemy_arrow.index
      # 选择敌人结束
      end_enemy_select
      # 显示特技窗口中的情况下
      if @skill_window != nil
        # 结束特技选择
        end_skill_select
      end
      # 显示物品窗口的情况下
      if @item_window != nil
        # 结束物品选择
        end_item_select
      end
      # 转到下一位角色的指令输入
      phase3_next_actor
    end
  end

基本上相同,但是添加了两个判断:

      # 显示特技窗口中的情况下
      if @skill_window != nil
        # 结束特技选择
        end_skill_select
      end
      # 显示物品窗口的情况下
      if @item_window != nil
        # 结束物品选择
        end_item_select
      end

作用是再次确认特技窗口和物品窗口的关闭(用完顺手关闭是美德。如果指令窗口也这样就好了。)

另外这里提到目标变量:
  #--------------------------------------------------------------------------
      # 设置目标(单体) - 根据选择指示箭头的位置确定
      @active_battler.current_action.target_index = @enemy_arrow.index
  #--------------------------------------------------------------------------

如果是全体技能的话(或者全体攻击)或者自身技能,是不会通过这个方法的,所以目标会在下文改变。

  #--------------------------------------------------------------------------
  # ● 结束选择敌人
  #--------------------------------------------------------------------------
  def end_enemy_select
    # 释放敌人箭头
    @enemy_arrow.dispose
    @enemy_arrow = nil
    # 指令为 [战斗] 的情况下
    if @actor_command_window.index == 0
      # 有效化角色指令窗口
      @actor_command_window.active = true
      @actor_command_window.visible = true
      # 隐藏帮助窗口
      @help_window.visible = false
    end
  end

方法其实基本上也一样,就是添加了一个if判断。
原因是根据上面的解说,除了攻击是直接转跳选择敌人以外,其他的都有一个中介界面(技能选择和物品选择)。因为这个中介画面的存在,我们不能直接返回指令窗口。所以除非行动为攻击,否则我们是不会调用这个返回指令窗口的方法的。

当这个角色行动选择完成以后,现在我们就会转入下个角色的行动选择:

      # 转到下一位角色的指令输入
      phase3_next_actor

这个方法在line:23

  #--------------------------------------------------------------------------
  # ● 转到输入下一个角色的命令
  #--------------------------------------------------------------------------
  def phase3_next_actor

  end

主要操作跟phase3_prior_actor大抵相同

循环:
  def phase3_next_actor
#-------------------------------------------------------
    # 循环
    begin

    # 如果角色是在无法接受指令的状态就再试
    end until @active_battler.inputable?
#-------------------------------------------------------
  end


角色明灭效果:
  def phase3_next_actor
    # 循环
    begin
#-------------------------------------------------------
      # 角色的明灭效果 OFF
      if @active_battler != nil
        @active_battler.blink = false
      end
#-------------------------------------------------------
    # 如果角色是在无法接受指令的状态就再试
    end until @active_battler.inputable?
  end


如果最右边的角色已经被选择就开始主战斗回合:
  def phase3_next_actor
    # 循环
    begin
      # 角色的明灭效果 OFF
      if @active_battler != nil
        @active_battler.blink = false
      end
#-------------------------------------------------------
      # 最后的角色的情况
      if @actor_index == $game_party.actors.size-1
        # 开始主回合
        start_phase4
        return
      end
#-------------------------------------------------------
    # 如果角色是在无法接受指令的状态就再试
    end until @active_battler.inputable?
  end


否则就推进角色索引,取得活动的战斗者:
  def phase3_next_actor
    # 循环
    begin
      # 角色的明灭效果 OFF
      if @active_battler != nil
        @active_battler.blink = false
      end
      # 最后的角色的情况
      if @actor_index == $game_party.actors.size-1
        # 开始主回合
        start_phase4
        return
      end
#-------------------------------------------------------
      # 推进角色索引
      @actor_index += 1
      @active_battler = $game_party.actors[@actor_index]
      @active_battler.blink = true
#-------------------------------------------------------
    # 如果角色是在无法接受指令的状态就再试
    end until @active_battler.inputable?
  end


初始化指令窗口:
  def phase3_next_actor
    # 循环
    begin
      # 角色的明灭效果 OFF
      if @active_battler != nil
        @active_battler.blink = false
      end
      # 最后的角色的情况
      if @actor_index == $game_party.actors.size-1
        # 开始主回合
        start_phase4
        return
      end
      # 推进角色索引
      @actor_index += 1
      @active_battler = $game_party.actors[@actor_index]
      @active_battler.blink = true
    # 如果角色是在无法接受指令的状态就再试
    end until @active_battler.inputable?
#-------------------------------------------------------
    # 设置角色的命令窗口
    phase3_setup_command_window
#-------------------------------------------------------
  end

完了……Phase3解释完毕。
作者: Jousun    时间: 2008-3-3 16:22
re:主题:《解读默认战斗系统脚本(Scene_Battle 2部分)》 [LINE]1,#dddddd[/LINE]我强烈支持!
分析得好透彻
作者: 闪电    时间: 2008-3-4 16:12
re:主题:《解读默认战斗系统脚本(Scene_Battle 2部分)》 [LINE]1,#dddddd[/LINE]收下来研究了,谢谢LZ
作者: 精灵使者    时间: 2008-3-4 21:49
re:主题:《解读默认战斗系统脚本(Scene_Battle 2部分)》 [LINE]1,#dddddd[/LINE]很多东西的语法都很明了。
.index类初始化都是-1,我方逃跑的时候占用了整个回合数(这很明显)。另外,为-1的话就不会显示光标(这个方法在选择类很常用)
其实小数点这个使用很广泛,一个是调用函数,带回返回值。
一个是调用某个类里面的某个变量。
执行.new其实就是
执行这个Scene类的installize函数。
如果有main函数的话,执行main函数在这里定义:
在main函数里面有一句
  while $scene != nil
    $scene.main
  end
就是执行的他。
Scene类调用的window类都是在各自的窗口下进行的。
xxx=window_xxx.new(按照window类里面的描述建立一个窗口)
xxx.active = true/false 这个窗口活动/不活动(不活动的话,光标无法对准焦点)
xxx.visible = true/false 窗口可见/不可见(不可见的话会自动消失)
xxx.x,xxx.y,xxx.z坐标
xxx.contents.opabity 透明度
xxx.font.size/color 字体颜色/大小
xxx.back_opaticy 背景透明度
xxx.skin = 窗口的界面,默认为系统
xxx.dispose窗口整个释放,不可再调用
一次建立整个窗口/释放整个窗口有利于系统的运行,不容易出错。
作者: 禾西    时间: 2008-3-4 21:55
re:主题:《解读默认战斗系统脚本(Scene_Battle 2部分)》 [LINE]1,#dddddd[/LINE]
以下引用精灵使者于2008-3-4 13:49:26的发言:

很多东西的语法都很明了。
.index类初始化都是-1,我方逃跑的时候占用了整个回合数(这很明显)。另外,为-1的话就不会显示光标(这个方法在选择类很常用)
其实小数点这个使用很广泛,一个是调用函数,带回返回值。
一个是调用某个类里面的某个变量。
执行.new其实就是
执行这个Scene类的installize函数。
如果有main函数的话,执行main函数在这里定义:
在main函数里面有一句
while $scene != nil
   $scene.main
end
就是执行的他。

不知道精靈是針對哪段的評價,禾西看得一頭霧水……
禾西自然知道.new的用法和main方法調用等等。!=o=a
作者: 精灵使者    时间: 2008-3-4 21:59
re:主题:《解读默认战斗系统脚本(Scene_Battle 2部分)》 [LINE]1,#dddddd[/LINE]好像是整个脚本的评价。
有些不懂的地方我想要讲一下。例如你跳过去的index = -1等。
还有一些就是对于Scene的窗口控制部分的解释。
据说,如果临时生成窗口或者销毁窗口的话,会引起系统不稳定(我的游戏里就碰到了)
还有,小数点的用法值得商榷。
那个除了是方法调用变量之外,还可以调用函数(引用值),取得返回值。
这个就在前面的类中定义
class A
def a(b)
c = b
return c
end
end
class B
p A.a(D)
END
这样会取得返回值D。
另外new好像是一个内部量,隐含的要执行 installize函数……(ruby内定义)
p.s.顺手合并两贴。
作者: 禾西    时间: 2008-3-4 22:17
哦,原來如此。弄得禾西惶恐不已……某些地方禾西似乎講得不太清楚。補充一下基本知識也好讓人容易讀懂。多謝精靈的幫忙。

另外,對於第二點。
据说,如果临时生成窗口或者销毁窗口的话,会引起系统不稳定(我的游戏里就碰到了)

很不幸地告訴妳,這個我也有遇到過了……Orz
當時我打算把指令選擇的窗口抽離出來,用的時候就create不用的時候就dispose,然後根據任務Id改變指令內容。結果dispose的地方出錯……整個遊戲跳出。==!
但是話說回頭,默認腳本當中使用了很多臨時生成的窗口,幷在不使用的時候釋放。禾西也幫人寫過這樣的腳本。在help當中更新窗口并隨時生成隨時釋放(根據index的位置)。效果似乎很好,暫時也沒有聽人家反映出現問題。因此,只能歸結於運氣。某些腳本(比如默認戰鬥畫面)不適闔使用這個手段。

但是技能選擇畫面和物品選擇畫面都是隨時生成的。我想,應該可以辦到。至於代價,應該是內存吧?

唔,另外……不能給我前面四樓嗎……==我想連在一齊寫啊。但是當初忘記預定樓層了
囧rz
作者: 沉影不器    时间: 2008-3-5 00:01
提示: 作者被禁止或删除 内容自动屏蔽
作者: 精灵使者    时间: 2008-3-5 01:30
以下引用禾西于2008-3-4 14:17:50的发言:
很不幸地告訴,這個我也有遇到過了……Orz
[本贴由作者于 2008-3-4 14:19:04 最后编辑]

orz……又一个把我看成大姐的……
临时不显示的窗口,让他的active(活动)和visible都为false就可以了……
只有以后永久不用的才把他dispose掉……
尽量减少他的次数才对……
另外,似乎前面4楼那个要求好像满足不了……
另外,一些临时窗口可以在开始生成的时候一起生成,销毁的时候一起销毁,这样的话稳定性比较高一些。
作者: maikid    时间: 2008-3-5 02:45
好文啊,好文。希望再多解读几个类
作者: swabwind    时间: 2008-3-5 09:23
是不是XP和VX都可以这样解读的?
作者: ONEWateR    时间: 2008-12-28 19:30
发布完毕
http://rpg.blue/web/htm/news1207.htm
http://rpg.blue/web/htm/news1208.htm
http://rpg.blue/web/htm/news1209.htm
http://rpg.blue/web/htm/news1230.htm
vip += 5
作者: 后知后觉    时间: 2008-12-28 20:58
留个爪印……空了再看{/hx}
作者: RXVincent    时间: 2008-12-29 02:25
收藏下,一定会有用的。
作者: 禾西    时间: 2009-6-12 08:00
本帖最后由 后知后觉 于 2009-12-29 16:20 编辑

前文提要:

               上一位角色的选择窗口
                      ┃按下B键

                   指令窗口

                      ┃按下C键
    ┏━━━━━┳━━┻━━┳━━━━━┓
    ┃(攻击)    ┃(技能)    ┃(防御)    ┃(物品)
    ┃                      ┗━━━━━━━━━┓
    ┃      选择招数                 选择物品   ┃
    ┃                                          ┃
    ┃          ┃                      ┃      ┃
    ┃          ┗━━┳━━━━━┳━━┛      ┃
    ┃                                          ┃
    ┗━━━━━  选择敌人  /  选择队友(or自己) ┃
                                                ┃
                      ┗━━┳━━┛            ┃
                                                ┃
                         下个角色的选择窗口   ━┛

就酱~
#------------------------------------------------------
终于讲到phase4,这里可以说是最多人需要理解的地方。
为甚么?
因为基本上9成以上的战斗效果都是在这里处理。

实际上phase的脚本不止Scene_Battle 4,Game_Battler 3也是phase的一部分,所以我会把两个脚本一起解读的。

在phase4当中,Scene_Battle 4主要处理前台的显示效果,而Game_Battler 3则处理后台的运算效果。

那好,我们就从前台开始吧。这样子比较好理解。

phase4的主题运作跟以上几个phase没有大的差别,主体部分都是由start_phase4和update_phase4组成。

先说start_phase4

  def start_phase4

  end

里面设置了这些东西:

转移phase。(update用)
    # 转移到回合 4
    @phase = 4


设置战斗事件
    # 回合数计数
    $game_temp.battle_turn += 1
    # 搜索全页的战斗事件
    for index in 0...$data_troops[@troop_id].pages.size
      # 获取事件页
      page = $data_troops[@troop_id].pages[index]
      # 本页的范围是 [回合] 的情况下
      if page.span == 1
        # 设置已经执行标志
        $game_temp.battle_event_flags[index] = false
      end
    end

如同解释。
但是这里有一个句子产生了歧义:设置已经执行标志
其实指的是:
「如果战斗条件是回合」,设置已经执行标志为false(没有执行),然后扔给Scene_Battle 1 的 setup_battle_event 去处理。

这是个很笨很笨的方法,但是禾西偏偏想不到优化的办法==|||

设置窗口:
    # 设置角色为非选择状态
    @actor_index = -1
    @active_battler = nil
    # 有效化同伴指令窗口
    @party_command_window.active = false
    @party_command_window.visible = false
    # 无效化角色指令窗口
    @actor_command_window.active = false
    @actor_command_window.visible = false

以前都经常说,这里不说。如果不记得的话就再重头看吧……

设置主回合标志:
    # 设置主回合标志
    $game_temp.battle_main_phase = true

稍微提醒一下:

在Sprite_Battler
line:67
[quote]
      # 不是主状态的时候稍稍降低点透明度
      if $game_temp.battle_main_phase
        self.opacity += 3 if self.opacity < 255
      else
        self.opacity -= 3 if self.opacity > 207
      end
    end

[/quote]
就酱,不记得就看回Scene_Battle 1的解释
#----------------------------------------------------
生成敌人行动:
    # 生成敌人行动
    for enemy in $game_troop.enemies
      enemy.make_action
    end

for...in...
end
语法不说,循环已经讲过了。

说一说:
      enemy.make_action

这个就是敌人的行动设置位置。调用的是 Game_Enemy 脚本中 make_action 方法。
如果你想要提高敌人的AI,就需要在 Game_Enemy 脚本 make_action 方法中改写。

我们稍微看一看默认敌人的AI是怎么算的:
Game_Enemy
line:251
[quote]
  def make_action

  end


里面设置了这些东西:

    # 清除当前行动
    self.current_action.clear

清除上自己一回合的行动
self 在这里指的是该敌人

(self,自/己)

    # 无法行动的情况
    unless self.movable?
      # 过程结束
      return
    end

如果身中不能行动的状态,无法行动。(return方法,返回没有行动)

    # 抽取现在有效的行动
    available_actions = []
    rating_max = 0

初始化两个重要的变量。
其中available_actions将会记录所有敌人可以行用的行为。

availeble:avail-用,eble-同able能够 / 引申为:可以使用的

而rating_max则将记录行为当中概率最高的数值

rating:rate-比例,ing-(名词或形容后缀)/ 引申为 概率
max:(拉丁词根)最大值 - 如同maximum

rating_max:最高概率

取得所有行为的资料:
    for action in self.actions


确认当前回合,并记录入变量n当中:
      n = $game_temp.battle_turn


取得回合条件并记录入a、b当中:
      a = action.condition_turn_a
      b = action.condition_turn_b

回合数是由 a + b*x (x = 0...∞)算出的(具体参考数据库-敌人-行为-事件出现条件-回合)。

确认条件(回合)

      if (b == 0 and n != a) or
         (b > 0 and (n < 1 or n < a or n % b != a % b))
        next
      end

分两种情况:
01.没有b参数存在(b == 0)- 指定回合发生。
如果当前回合 n 不等于 预定回合a,跳过。(条件不符合)

02.如果b参数存在(b > 0)- 一定回合重复发生。
如果当前回合 n 为 0,或者n < a, 或者n % b与a % b不相等,跳过。(条件不符合)

其中%是余除的方法:
1/4 = 0.25
12/5 = 2.5
1%4 = 1
12%5 = 2

这里说一下。
禾西曾经疑问过为甚么要用n % b != a % b这个判断方法而不用n % b != a。
后者似乎更符合逻辑……但是实际上禾西错了。
这是一个数学上的问题。一般情况下,当然应该是n % b != a % b和n % b != a基本上没有差别。
但是,如果a比b大的话就会出现问题了:
假设a == 10, b == 3 ,而 c == 13。按道理,这样的情况下应该要发生事件。但是,如果计算公式是n % b != a
∵c % b = 13 % 3 = 1
∵a == 10
∵1 != 10
∴c % b != a
结果就是不发生事件……就是这样了,看得懂最好,看不懂就算了。反正知道原脚本的判断语句是正确的就好。


确认条件(HP,等级,开关)

      # 确认 HP 条件
      if self.hp * 100.0 / self.maxhp > action.condition_hp
        next
      end
      # 确认等级条件
      if $game_party.max_level < action.condition_level
        next
      end
      # 确认开关条件
      switch_id = action.condition_switch_id
      if switch_id > 0 and $game_switches[switch_id] == false
        next
      end

这些都是基本数学,禾西就不赘言了。
具体变量的含义查找F1(如果有一般英语水平的应该也能看得出来。)

当所有条件都符合的情况下,把该行为记录入可用的行为当中:
      # 符合条件 : 添加本行动
      available_actions.push(action)


改变最高概率:
      if action.rating > rating_max
        rating_max = action.rating
      end


初始化总概率值:
    ratings_total = 0

取得可用行为的资料:
    for action in available_actions


如果行为的概率比最大概率减 3 大
总概率增加两者之间的差额:
      if action.rating > rating_max - 3
        ratings_total += action.rating - (rating_max - 3)
      end

至于为甚么要比最大概率减 3 大,禾西不知道|||(不负责任)

生成敌人行动:
    # 概率合计不为 0 的情况下
    if ratings_total > 0
      # 根据概率合计生成允许差额
      value = rand(ratings_total)
      # 取得可用行为的数据
      for action in available_actions
        # 如果可用行为的概率比(最高概率-3)大
        if action.rating > rating_max - 3
          # 如果允许差额 比 「可用行为的概率」与「最高概率-3」之间的差额小
          if value < action.rating - (rating_max - 3)
            # 自动设置行为 - 如同角色一样
            self.current_action.kind = action.kind
            self.current_action.basic = action.basic
            self.current_action.skill_id = action.skill_id
            # 随机目标(敌人用)- 参考:Game_BattleAction - line:95
            self.current_action.decide_random_target_for_enemy
            return
          else
            # 否则,再缩小允许差额
            value -= action.rating - (rating_max - 3)
          end
        end
      end
    end


[/quote]

OK,所有战斗者的行动都设置好了。

接下来,我们安排战斗者的速度安排出手的先后
    # 生成行动顺序
    make_action_orders

  #--------------------------------------------------------------------------
  # ● 生成行动循序
  #--------------------------------------------------------------------------
  def make_action_orders
    # 初始化序列 @action_battlers
    @action_battlers = []
    # 添加敌人到 @action_battlers 序列
    for enemy in $game_troop.enemies
      @action_battlers.push(enemy)
    end
    # 添加角色到 @action_battlers 序列
    for actor in $game_party.actors
      @action_battlers.push(actor)
    end
    # 确定全体的行动速度
    for battler in @action_battlers
      battler.make_action_speed
   # 参考:Game_Battler 1 line:262
[quote]
  #--------------------------------------------------------------------------
  # ● 确定动作速度
  #--------------------------------------------------------------------------
  def make_action_speed
    @current_action.speed = agi + rand(10 + agi / 4)
  end

    end
    # 按照行动速度从大到小排列
    #         根据current_action.speed排列出手先后
    @action_battlers.sort! {|a,b|
      b.current_action.speed - a.current_action.speed }
  end
[/quote]
这里讲一下
    @action_battlers.sort! {|a,b|
      b.current_action.speed - a.current_action.speed }

根据F1:
sort! {|a, b| ... }
对数组内容进行排序。若带块调用时,将把 2 个参数传给块,然后使用块的计算结果进行比较。若没有块时,使用 <=> 运算符进行比较。sort! 的对数组单元的排序过程具有破环性。

sort 将生成一个经过排序的新数组并返回它,sort! 通常会返回 self。

这里调用的是破坏原数组而根据b.current_action.speed - a.current_action.speed的结果重新排列。
这里的a,b是block,可以把他们视为sort方法的两个返回参数(告诉程序要干甚么)。
後面的是計算式(告訴程序排列的根據,如果不给出就会按照数组元素的大小排列。如果不是数字就会出错。)

(另外,如果可以用sort!就不要用sort。這樣會比較快。)

开始主战斗-准备行动:
    # 移动到步骤 1
    @phase4_step = 1


start_phase4就是这么些东西(很多==|||)




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1