Project1

标题: 半成品QTE系统 with 教程 [打印本页]

作者: taroxd    时间: 2014-11-5 17:00
标题: 半成品QTE系统 with 教程
本帖最后由 taroxd 于 2014-11-8 16:34 编辑

今天开运动会提前一点放学,于是花了半个小时拿着学校图书馆的破电脑随手瞎写了点东西玩玩。用法不告诉你由于时间仓促,没有做美化和注释等工作

我就说,技能公式是个好东西,kira☆~

核心代码是 Taroxd::QTE “模块",和战斗场景里对 update_basic 的修改。Sprite 什么的爱怎么玩怎么玩,爱怎么改怎么改。

仅用于脚本技术交流之用,大概没人愿意真的用到游戏里= =

也许有空的话,我会将这段代码写出的过程当做一个教程来发布。

@KEY  @VIPArcher

RUBY 代码复制
  1. # 技能公式范例:
  2. # Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
  3.  
  4. module Taroxd end
  5.  
  6. class << Taroxd::QTE = Object.new
  7.  
  8.   WAITING = 60  # 执行 set 到触发时间的帧数
  9.  
  10.   SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
  11.  
  12.   TOLERENCE = 1 # 准许误差的帧数
  13.  
  14.   # key 为 nil 时表示没有 QTE 设置
  15.   # when_triggered: QTE 触发时执行的脚本
  16.   def set(key = :C, &when_triggered)
  17.     @frame = WAITING
  18.     @key = key
  19.     @when_triggered = when_triggered
  20.     if SceneManager.scene_is? Scene_Battle
  21.       SceneManager.scene.abs_wait(SCREEN_WAITING)
  22.     end
  23.   end
  24.  
  25.   def update
  26.     return unless @key
  27.     @frame -= 1
  28.     return terminate if @frame < -TOLERENCE
  29.     trigger if hit? && @frame.abs <= TOLERENCE
  30.   end
  31.  
  32.   def pos
  33.     @key && 1 - @frame.fdiv(WAITING)
  34.   end
  35.  
  36.   private
  37.  
  38.   def hit?
  39.     Input.trigger?(@key)
  40.   end
  41.  
  42.   def trigger
  43.     @when_triggered.call
  44.     terminate
  45.   end
  46.  
  47.   def terminate
  48.     @key = nil
  49.   end
  50. end
  51.  
  52. class Sprite_QTE < Sprite
  53.  
  54.   QTE = Taroxd::QTE
  55.  
  56.   def initialize(_ = nil)
  57.     super
  58.     self.bitmap = Bitmap.new(50, 8)
  59.     self.x = 100
  60.     self.y = 100
  61.     self.z = 200
  62.   end
  63.  
  64.   def update
  65.     refresh if QTE.pos != @pos
  66.   end
  67.  
  68.   def dispose
  69.     bitmap.dispose
  70.     super
  71.   end
  72.  
  73.   private
  74.  
  75.   def refresh
  76.     @pos = QTE.pos
  77.     bitmap.clear
  78.     return unless @pos
  79.     pos = @pos * bitmap.width
  80.     bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
  81.     bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
  82.   end
  83.  
  84. end
  85.  
  86. class Scene_Battle
  87.  
  88.   alias ub_20141105 update_basic
  89.   def update_basic
  90.     ub_20141105
  91.     Taroxd::QTE.update
  92.   end
  93. end
  94.  
  95. class Spriteset_Battle
  96.  
  97.   alias ct_20141105 create_timer
  98.   def create_timer
  99.     ct_20141105
  100.     @qte_sprite = Sprite_QTE.new
  101.   end
  102.  
  103.   alias ut_20141105 update_timer
  104.   def update_timer
  105.     ut_20141105
  106.     @qte_sprite.update
  107.   end
  108.  
  109.   alias dt_20141105 dispose_timer
  110.   def dispose_timer
  111.     dt_20141105
  112.     @qte_sprite.dispose
  113.   end
  114.  
  115. end
      

--------------------------教程部分-----------------------

就拿这个简单的 QTE 系统为例。要写脚本,请先回答如下几个问题。

下面的每一个问题的回答都不是标准答案。
你可以有不同的想法,而不同的回答对应着不同的实现。

1. 预期的效果是什么?
  当带有 QTE 的技能发动时,场上会显示一个东西,此时战斗场景暂停。
  然后在指定时机按下 QTE 对应的按键,就可以触发某个效果。

2. 处理逻辑是什么?
  QTE 触发 -> 场景暂停 -> 进行 QTE 的处理 -> 场景继续
  进行 QTE 的处理时,画面会随着 QTE 状态的变化而更新

3. 需要什么数据?
  1) 怎么样算作触发 QTE?
  2) 触发 QTE 的效果是什么?
  3) 怎样表示当前 QTE 的状态?

  在这里,我们可以这么回答:
    1) 在某一段时间之间按下某个按键,就算做 QTE 触发成功
    2) 利用 Ruby 中 block 的特性,我们可以将效果用 block 包装起来。触发时执行这个效果即可。
    3) 可以利用时间的推移来表示

4. 开始写代码

1)数据
  一般来说,你需要一个类来存储数据。
  类似于 Game_Actor 这类,就是游戏数据的体现。
  在这里,由于 QTE 不会同时触发多个,我们用一个模块就足以完成数据的管理了。

RUBY 代码复制
  1. # 技能公式范例:
  2. # Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
  3.  
  4. module Taroxd end
  5.  
  6. class << Taroxd::QTE = Object.new
  7.  
  8.   def set(&when_triggered)
  9.   end
  10.  
  11.   private
  12.  
  13. end


前两行注释是预定的使用方法,也是对第三步中第二个问题的部分回答。
我们后面是要根据这个来写脚本的。

module Taroxd end
初始化一个 Taroxd 模块作为命名的空间,因为 QTE 这个常量名还是很有可能引起冲突的。

class << Taroxd::QTE = Object.new
这行代码可能有些难懂。你可以理解为 module Taroxd::QTE ,然后后面定义模块方法的时候就不需要写 self 了。这是一个偷懒的地方。

private 上方是写要给脚本的其他部分调用的方法,而 private 的下方写内部使用的方法。
两种方法分开写之后,可以使脚本更加清晰。

下面开始解决第三步中的问题。

1) 在某一段时间之间按下某个按键,就算做 QTE 触发成功
于是我们需要表示出“一段时间”,还有“按下某个按键”。

“一段时间”可以从调用 set 开始计算,过了 x 帧后,在 y 帧的误差范围内的一段时间。
x,y 可以是变量。这里为了方便,暂且设置为两个常量。

虽然我们可以用 Graphics.frame_count 来计算帧数,但是这里使用了每帧 update 的方法。

RUBY 代码复制
  1. # 技能公式范例:
  2. # Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
  3.  
  4. module Taroxd end
  5.  
  6. class << Taroxd::QTE = Object.new
  7.  
  8.   WAITING = 60  # 执行 set 到触发时间的帧数
  9.  
  10.   def set(&when_triggered)
  11.     @frame = WAITING
  12.   end
  13.  
  14.   def update
  15.     @frame -= 1
  16.   end
  17.  
  18.   private
  19.  
  20.   def hit?
  21.     Input.trigger?(:C)
  22.   end
  23.  
  24. end


首先,这里是把 @frame 初始化成最大值,然后每帧递减的方式实现的。
当然,初始化为 0,然后每帧递增也是可以的。

然后,把 hit? 单独拿出来写成一个方法是为了便于将来修改。
毕竟将来并不一定会用 :C 做按键。甚至可以导入全键盘脚本等。
这一步很轻松地就完成了,接下来是下一步。

2) 利用 Ruby 中 block 的特性,我们可以将效果用 block 包装起来。触发时执行这个效果即可。
这个同样可以轻松完成。

RUBY 代码复制
  1. def set(&when_triggered)
  2.     @frame = WAITING
  3.     @when_triggered = when_triggered
  4.   end
  5.  
  6.   private
  7.  
  8.   def trigger
  9.     @when_triggered.call
  10.   end


那么实际触发怎么做呢?
因为我们有(预定)每帧执行的 update。因此我们可以直接在 update 中进行触发效果的判断与处理。
注意到 @frame 的意义就是按键的时间与当前时间的差值,我们可以非常简单地判定当前是不是在时间范围内。

RUBY 代码复制
  1. TOLERENCE = 1 # 准许误差的帧数
  2.  
  3.   def update
  4.     @frame -= 1
  5.     trigger if hit? && @frame.abs <= TOLERENCE
  6.   end


3) 可以利用时间的推移来表示 QTE 的状态

由于 @frame 的存在,这个要求十分简单。
RUBY 代码复制
  1. def pos
  2.     1 - @frame.fdiv(WAITING)
  3.   end


pos 是 position 的简称,表示位置。
注意,由于 @frame 和 WAITING 都是整数,所以这里不能用除号,要用浮点数的除法 fdiv。



仔细想想,数据的部分到这里就结束了吗?
实际上还是有一点问题的。

比如说,在时间范围内,如果玩家手速够快,连续按了两次按键,那么 QTE 的效果不是就触发了两次了吗?
此外,当前的脚本如何在显示时判断是否需要显示 QTE 界面呢?(也许你可以说 pos > 1,不过这样总是有点让人不爽)

这里的解决办法是,在效果触发或者过期后,将 QTE 表示为不可用的状态。
我们可以用一个实例变量表示 QTE 当前是否启动。

反正要用一个实例变量,顺便把可变按键的效果做了吧。
@key 如果为 nil,表示当前 QTE 处于不可用的状态,否则表示需要按下的按键。

RUBY 代码复制
  1. # 技能公式范例:
  2. # Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
  3.  
  4. module Taroxd end
  5.  
  6. class << Taroxd::QTE = Object.new
  7.  
  8.   WAITING = 60  # 执行 set 到触发时间的帧数
  9.  
  10.   SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
  11.  
  12.   TOLERENCE = 1 # 准许误差的帧数
  13.  
  14.   # key 为 nil 时表示没有 QTE 设置
  15.   # when_triggered: QTE 触发时执行的脚本
  16.   def set(key = :C, &when_triggered)
  17.     @frame = WAITING
  18.     @key = key
  19.     @when_triggered = when_triggered
  20.   end
  21.  
  22.   def update
  23.     return unless @key
  24.     @frame -= 1
  25.     return terminate if @frame < -TOLERENCE
  26.     trigger if hit? && @frame.abs <= TOLERENCE
  27.   end
  28.  
  29.   def pos
  30.     @key && 1 - @frame.fdiv(WAITING)
  31.   end
  32.  
  33.   private
  34.  
  35.   def hit?
  36.     Input.trigger?(@key)
  37.   end
  38.  
  39.   def trigger
  40.     @when_triggered.call
  41.     terminate
  42.   end
  43.  
  44.   def terminate
  45.     @key = nil
  46.   end
  47. end


第二步,制作便于测试的显示部分。

RUBY 代码复制
  1. class Sprite_QTE < Sprite
  2.  
  3.   QTE = Taroxd::QTE
  4.  
  5.   def initialize(_ = nil)
  6.     super
  7.     self.bitmap = Bitmap.new(50, 8)
  8.     self.x = 100
  9.     self.y = 100
  10.     self.z = 200
  11.   end
  12.  
  13.   def update
  14.     refresh if QTE.pos != @pos
  15.   end
  16.  
  17.   def dispose
  18.     bitmap.dispose
  19.     super
  20.   end
  21.  
  22.   private
  23.  
  24.   def refresh
  25.     @pos = QTE.pos
  26.     bitmap.clear
  27.     return unless @pos
  28.     pos = @pos * bitmap.width
  29.     bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
  30.     bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
  31.   end
  32.  
  33. end
  34.  
  35. class Spriteset_Battle
  36.  
  37.   alias ct_20141105 create_timer
  38.   def create_timer
  39.     ct_20141105
  40.     @qte_sprite = Sprite_QTE.new
  41.   end
  42.  
  43.   alias ut_20141105 update_timer
  44.   def update_timer
  45.     ut_20141105
  46.     @qte_sprite.update
  47.   end
  48.  
  49.   alias dt_20141105 dispose_timer
  50.   def dispose_timer
  51.     dt_20141105
  52.     @qte_sprite.dispose
  53.   end
  54.  
  55. end


目前这段代码的主要作用还是测试 QTE 的数据是否和预期相同。

最后是场景的逻辑,这里为了速度再次偷一下懒,把画面的暂停写在了 Taroxd::QTE.set 里面。
这样的话,我们并不知道场景会在哪里暂停,也不知道这样的暂停会带来什么后果。
因此,这样的代码是不规范的,仅作测试之用,需要在将来调整。


RUBY 代码复制
  1. class << Taroxd::QTE = Object.new
  2.  
  3.   SCREEN_WAITING = WAITING + 60
  4.  
  5.   def set(key = :C, &when_triggered)
  6.     @frame = WAITING
  7.     @key = key
  8.     @when_triggered = when_triggered
  9.     if SceneManager.scene_is? Scene_Battle
  10.       SceneManager.scene.abs_wait(SCREEN_WAITING)
  11.     end
  12.   end
  13.  
  14. end
  15.  
  16. class Scene_Battle
  17.  
  18.   alias ub_20141105 update_basic
  19.   def update_basic
  20.     ub_20141105
  21.     Taroxd::QTE.update
  22.   end
  23. end


最后完整的代码(半成品)如下:

RUBY 代码复制
  1. # 技能公式范例:
  2. # Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
  3.  
  4. module Taroxd end
  5.  
  6. class << Taroxd::QTE = Object.new
  7.  
  8.   WAITING = 60  # 执行 set 到触发时间的帧数
  9.  
  10.   SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
  11.  
  12.   TOLERENCE = 1 # 准许误差的帧数
  13.  
  14.   # key 为 nil 时表示没有 QTE 设置
  15.   # when_triggered: QTE 触发时执行的脚本
  16.   def set(key = :C, &when_triggered)
  17.     @frame = WAITING
  18.     @key = key
  19.     @when_triggered = when_triggered
  20.     if SceneManager.scene_is? Scene_Battle
  21.       SceneManager.scene.abs_wait(SCREEN_WAITING)
  22.     end
  23.   end
  24.  
  25.   def update
  26.     return unless @key
  27.     @frame -= 1
  28.     return terminate if @frame < -TOLERENCE
  29.     trigger if hit? && @frame.abs <= TOLERENCE
  30.   end
  31.  
  32.   def pos
  33.     @key && 1 - @frame.fdiv(WAITING)
  34.   end
  35.  
  36.   private
  37.  
  38.   def hit?
  39.     Input.trigger?(@key)
  40.   end
  41.  
  42.   def trigger
  43.     @when_triggered.call
  44.     terminate
  45.   end
  46.  
  47.   def terminate
  48.     @key = nil
  49.   end
  50. end
  51.  
  52. class Sprite_QTE < Sprite
  53.  
  54.   QTE = Taroxd::QTE
  55.  
  56.   def initialize(_ = nil)
  57.     super
  58.     self.bitmap = Bitmap.new(50, 8)
  59.     self.x = 100
  60.     self.y = 100
  61.     self.z = 200
  62.   end
  63.  
  64.   def update
  65.     refresh if QTE.pos != @pos
  66.   end
  67.  
  68.   def dispose
  69.     bitmap.dispose
  70.     super
  71.   end
  72.  
  73.   private
  74.  
  75.   def refresh
  76.     @pos = QTE.pos
  77.     bitmap.clear
  78.     return unless @pos
  79.     pos = @pos * bitmap.width
  80.     bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
  81.     bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
  82.   end
  83.  
  84. end
  85.  
  86. class Scene_Battle
  87.  
  88.   alias ub_20141105 update_basic
  89.   def update_basic
  90.     ub_20141105
  91.     Taroxd::QTE.update
  92.   end
  93. end
  94.  
  95. class Spriteset_Battle
  96.  
  97.   alias ct_20141105 create_timer
  98.   def create_timer
  99.     ct_20141105
  100.     @qte_sprite = Sprite_QTE.new
  101.   end
  102.  
  103.   alias ut_20141105 update_timer
  104.   def update_timer
  105.     ut_20141105
  106.     @qte_sprite.update
  107.   end
  108.  
  109.   alias dt_20141105 dispose_timer
  110.   def dispose_timer
  111.     dt_20141105
  112.     @qte_sprite.dispose
  113.   end
  114.  
  115. end


将常量 TOLERENCE 的值改大一些,然后测试吧!
测试的结果没有出什么问题,说明这段脚本的数据部分已经完成了。
正巧,QTE 脚本的场景逻辑部分相当简单。所以,这个脚本已经完成了一大半了。

剩下的一小半就是作业啦~

1. 完善显示
  1) 在屏幕上显示应该按的按键(你应该将 @key 暴露给外部)
  2) 美化。
  3) 根据技能目标的不同,调整显示的位置。
  4) 在 QTE 触发成功时,播放动画效果和音效。

2. 把数据部分 Taroxd::QTE.set 中对场景的设置放到 Scene_Battle 中的合适位置。

3. 当前代码中,敌人使用技能也会触发 QTE。请禁止这个功能,或者敌人使用时有一定概率随机触发 QTE。

4. 当前使用该脚本需要在技能公式中设置,要求一定的脚本知识,并且可能会写不下。
   此外,计算伤害是在 QTE 触发之前计算的。当技能使用者为多个目标时,也会有不想要的结果。
   请更换一种设置方式,通过读取备注来设置 QTE 的效果,使设置更加人性化。

5. 思路不要受到这段脚本的束缚。请重新回答一开始提出的问题,重新写出你自己的代码,使之符合你个性化的需求。

6. 为你的代码补全注释。  
作者: RyanBern    时间: 2014-11-5 17:51
VA简约版的也有了?脚本长度越来越短了啊……我得找时间精简一下我的脚本。
那么VX版的谁来做一个?感觉就是QTE们在开会啊
作者: VIPArcher    时间: 2014-11-5 18:11
用法不是在注释里有吗?
等教程
作者: chd114    时间: 2014-11-6 22:07
感觉兼容性应该会很高···另外VA里面一秒是多少帧?貌似不是20···
作者: chd114    时间: 2014-11-8 15:54
技能公式写满了···写不下QAQ




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