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

Project1

 找回密码
 注册会员
搜索
查看: 9019|回复: 23
打印 上一主题 下一主题

解读默认战斗系统脚本

 关闭 [复制链接]

Lv3.寻梦者

酱油的

梦石
0
星屑
1020
在线时间
2161 小时
注册时间
2007-12-22
帖子
3271

贵宾

跳转到指定楼层
1
发表于 2008-3-1 22:23:14 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
好了,论文地狱终于过去。这下可以做点自己喜欢的事情,比如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的运作还算清晰。
不做頭像做簽名,看我囧冏有神(多謝山人有情提供 )
头像被屏蔽

Lv1.梦旅人 (禁止发言)

喵,小柯的宠物

梦石
0
星屑
50
在线时间
0 小时
注册时间
2007-12-15
帖子
2310
2
发表于 2008-3-1 22:24:26 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复 支持 反对

使用道具 举报

头像被屏蔽

Lv1.梦旅人 (禁止发言)

梦石
0
星屑
50
在线时间
0 小时
注册时间
2008-2-5
帖子
98
3
发表于 2008-3-1 22:28:29 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复 支持 反对

使用道具 举报

Lv2.观梦者

龙骑

梦石
0
星屑
525
在线时间
10 小时
注册时间
2007-12-31
帖子
2030
4
发表于 2008-3-1 22:38:20 | 只看该作者
LZ辛苦了……{/qiang}
回复 支持 反对

使用道具 举报

Lv1.梦旅人

綾川司の姫様<

梦石
0
星屑
50
在线时间
796 小时
注册时间
2007-12-20
帖子
4520

贵宾第3届短篇游戏大赛R剧及RMTV组亚军

5
发表于 2008-3-2 20:13:48 | 只看该作者
好长……但是真的很强。呼叫斑竹过来鼓励并发布。-v-

生命即是责任。自己即是世界。
回复 支持 反对

使用道具 举报

头像被屏蔽

Lv1.梦旅人 (禁止发言)

悔恨的天使

梦石
0
星屑
50
在线时间
0 小时
注册时间
2008-2-26
帖子
726
6
发表于 2008-3-2 20:20:17 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复 支持 反对

使用道具 举报

Lv3.寻梦者

酱油的

梦石
0
星屑
1020
在线时间
2161 小时
注册时间
2007-12-22
帖子
3271

贵宾

7
 楼主| 发表于 2008-3-2 21:34:04 | 只看该作者
以下引用永远的卡卡布于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-

不做頭像做簽名,看我囧冏有神(多謝山人有情提供 )
回复 支持 反对

使用道具 举报

Lv5.捕梦者

御灵的宠物

梦石
12
星屑
8438
在线时间
88 小时
注册时间
2006-12-11
帖子
3148

第2届TG大赛亚军

8
发表于 2008-3-2 21:48:06 | 只看该作者
当年写一个即时的脚本……看到update_phase1...6我已经很晕
结果update_phase4还分为update_phase4_step1……= =
为了搞懂它的结构我花费了大半辈子的精力
我的Lofter:http://nightoye.lofter.com/

回复 支持 反对

使用道具 举报

Lv3.寻梦者

酱油的

梦石
0
星屑
1020
在线时间
2161 小时
注册时间
2007-12-22
帖子
3271

贵宾

9
 楼主| 发表于 2008-3-3 15:55:40 | 只看该作者
本帖最后由 后知后觉 于 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解释完毕。
不做頭像做簽名,看我囧冏有神(多謝山人有情提供 )
回复 支持 反对

使用道具 举报

Lv3.寻梦者

酱油的

梦石
0
星屑
1020
在线时间
2161 小时
注册时间
2007-12-22
帖子
3271

贵宾

10
 楼主| 发表于 2009-6-12 08:00: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就是这么些东西(很多==|||)
不做頭像做簽名,看我囧冏有神(多謝山人有情提供 )
回复 支持 反对

使用道具 举报

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

本版积分规则

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

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

GMT+8, 2024-11-16 08:41

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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