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
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
TOLERENCE = 1 # 准许误差的帧数
# key 为 nil 时表示没有 QTE 设置
# when_triggered: QTE 触发时执行的脚本
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
if SceneManager.scene_is? Scene_Battle
SceneManager.scene.abs_wait(SCREEN_WAITING)
end
end
def update
return unless @key
@frame -= 1
return terminate if @frame < -TOLERENCE
trigger if hit? && @frame.abs <= TOLERENCE
end
def pos
@key && 1 - @frame.fdiv(WAITING)
end
private
def hit?
Input.trigger?(@key)
end
def trigger
@when_triggered.call
terminate
end
def terminate
@key = nil
end
end
class Sprite_QTE < Sprite
QTE = Taroxd::QTE
def initialize(_ = nil)
super
self.bitmap = Bitmap.new(50, 8)
self.x = 100
self.y = 100
self.z = 200
end
def update
refresh if QTE.pos != @pos
end
def dispose
bitmap.dispose
super
end
private
def refresh
@pos = QTE.pos
bitmap.clear
return unless @pos
pos = @pos * bitmap.width
bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
end
end
class Scene_Battle
alias ub_20141105 update_basic
def update_basic
ub_20141105
Taroxd::QTE.update
end
end
class Spriteset_Battle
alias ct_20141105 create_timer
def create_timer
ct_20141105
@qte_sprite = Sprite_QTE.new
end
alias ut_20141105 update_timer
def update_timer
ut_20141105
@qte_sprite.update
end
alias dt_20141105 dispose_timer
def dispose_timer
dt_20141105
@qte_sprite.dispose
end
end
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
TOLERENCE = 1 # 准许误差的帧数
# key 为 nil 时表示没有 QTE 设置
# when_triggered: QTE 触发时执行的脚本
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
if SceneManager.scene_is? Scene_Battle
SceneManager.scene.abs_wait(SCREEN_WAITING)
end
end
def update
return unless @key
@frame -= 1
return terminate if @frame < -TOLERENCE
trigger if hit? && @frame.abs <= TOLERENCE
end
def pos
@key && 1 - @frame.fdiv(WAITING)
end
private
def hit?
Input.trigger?(@key)
end
def trigger
@when_triggered.call
terminate
end
def terminate
@key = nil
end
end
class Sprite_QTE < Sprite
QTE = Taroxd::QTE
def initialize(_ = nil)
super
self.bitmap = Bitmap.new(50, 8)
self.x = 100
self.y = 100
self.z = 200
end
def update
refresh if QTE.pos != @pos
end
def dispose
bitmap.dispose
super
end
private
def refresh
@pos = QTE.pos
bitmap.clear
return unless @pos
pos = @pos * bitmap.width
bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
end
end
class Scene_Battle
alias ub_20141105 update_basic
def update_basic
ub_20141105
Taroxd::QTE.update
end
end
class Spriteset_Battle
alias ct_20141105 create_timer
def create_timer
ct_20141105
@qte_sprite = Sprite_QTE.new
end
alias ut_20141105 update_timer
def update_timer
ut_20141105
@qte_sprite.update
end
alias dt_20141105 dispose_timer
def dispose_timer
dt_20141105
@qte_sprite.dispose
end
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 不会同时触发多个,我们用一个模块就足以完成数据的管理了。
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
def set(&when_triggered)
end
private
end
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
def set(&when_triggered)
end
private
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 的方法。
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
def set(&when_triggered)
@frame = WAITING
end
def update
@frame -= 1
end
private
def hit?
Input.trigger?(:C)
end
end
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
def set(&when_triggered)
@frame = WAITING
end
def update
@frame -= 1
end
private
def hit?
Input.trigger?(:C)
end
end
首先,这里是把 @frame 初始化成最大值,然后每帧递减的方式实现的。
当然,初始化为 0,然后每帧递增也是可以的。
然后,把 hit? 单独拿出来写成一个方法是为了便于将来修改。
毕竟将来并不一定会用 :C 做按键。甚至可以导入全键盘脚本等。
这一步很轻松地就完成了,接下来是下一步。
2) 利用 Ruby 中 block 的特性,我们可以将效果用 block 包装起来。触发时执行这个效果即可。
这个同样可以轻松完成。
def set(&when_triggered)
@frame = WAITING
@when_triggered = when_triggered
end
private
def trigger
@when_triggered.call
end
def set(&when_triggered)
@frame = WAITING
@when_triggered = when_triggered
end
private
def trigger
@when_triggered.call
end
那么实际触发怎么做呢?
因为我们有(预定)每帧执行的 update。因此我们可以直接在 update 中进行触发效果的判断与处理。
注意到 @frame 的意义就是按键的时间与当前时间的差值,我们可以非常简单地判定当前是不是在时间范围内。
TOLERENCE = 1 # 准许误差的帧数
def update
@frame -= 1
trigger if hit? && @frame.abs <= TOLERENCE
end
TOLERENCE = 1 # 准许误差的帧数
def update
@frame -= 1
trigger if hit? && @frame.abs <= TOLERENCE
end
3) 可以利用时间的推移来表示 QTE 的状态
由于 @frame 的存在,这个要求十分简单。
def pos
1 - @frame.fdiv(WAITING)
end
def pos
1 - @frame.fdiv(WAITING)
end
pos 是 position 的简称,表示位置。
注意,由于 @frame 和 WAITING 都是整数,所以这里不能用除号,要用浮点数的除法 fdiv。
仔细想想,数据的部分到这里就结束了吗?
实际上还是有一点问题的。
比如说,在时间范围内,如果玩家手速够快,连续按了两次按键,那么 QTE 的效果不是就触发了两次了吗?
此外,当前的脚本如何在显示时判断是否需要显示 QTE 界面呢?(也许你可以说 pos > 1,不过这样总是有点让人不爽)
这里的解决办法是,在效果触发或者过期后,将 QTE 表示为不可用的状态。
我们可以用一个实例变量表示 QTE 当前是否启动。
反正要用一个实例变量,顺便把可变按键的效果做了吧。
@key 如果为 nil,表示当前 QTE 处于不可用的状态,否则表示需要按下的按键。
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
TOLERENCE = 1 # 准许误差的帧数
# key 为 nil 时表示没有 QTE 设置
# when_triggered: QTE 触发时执行的脚本
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
end
def update
return unless @key
@frame -= 1
return terminate if @frame < -TOLERENCE
trigger if hit? && @frame.abs <= TOLERENCE
end
def pos
@key && 1 - @frame.fdiv(WAITING)
end
private
def hit?
Input.trigger?(@key)
end
def trigger
@when_triggered.call
terminate
end
def terminate
@key = nil
end
end
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
TOLERENCE = 1 # 准许误差的帧数
# key 为 nil 时表示没有 QTE 设置
# when_triggered: QTE 触发时执行的脚本
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
end
def update
return unless @key
@frame -= 1
return terminate if @frame < -TOLERENCE
trigger if hit? && @frame.abs <= TOLERENCE
end
def pos
@key && 1 - @frame.fdiv(WAITING)
end
private
def hit?
Input.trigger?(@key)
end
def trigger
@when_triggered.call
terminate
end
def terminate
@key = nil
end
end
第二步,制作便于测试的显示部分。
class Sprite_QTE < Sprite
QTE = Taroxd::QTE
def initialize(_ = nil)
super
self.bitmap = Bitmap.new(50, 8)
self.x = 100
self.y = 100
self.z = 200
end
def update
refresh if QTE.pos != @pos
end
def dispose
bitmap.dispose
super
end
private
def refresh
@pos = QTE.pos
bitmap.clear
return unless @pos
pos = @pos * bitmap.width
bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
end
end
class Spriteset_Battle
alias ct_20141105 create_timer
def create_timer
ct_20141105
@qte_sprite = Sprite_QTE.new
end
alias ut_20141105 update_timer
def update_timer
ut_20141105
@qte_sprite.update
end
alias dt_20141105 dispose_timer
def dispose_timer
dt_20141105
@qte_sprite.dispose
end
end
class Sprite_QTE < Sprite
QTE = Taroxd::QTE
def initialize(_ = nil)
super
self.bitmap = Bitmap.new(50, 8)
self.x = 100
self.y = 100
self.z = 200
end
def update
refresh if QTE.pos != @pos
end
def dispose
bitmap.dispose
super
end
private
def refresh
@pos = QTE.pos
bitmap.clear
return unless @pos
pos = @pos * bitmap.width
bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
end
end
class Spriteset_Battle
alias ct_20141105 create_timer
def create_timer
ct_20141105
@qte_sprite = Sprite_QTE.new
end
alias ut_20141105 update_timer
def update_timer
ut_20141105
@qte_sprite.update
end
alias dt_20141105 dispose_timer
def dispose_timer
dt_20141105
@qte_sprite.dispose
end
end
目前这段代码的主要作用还是测试 QTE 的数据是否和预期相同。
最后是场景的逻辑,这里为了速度再次偷一下懒,把画面的暂停写在了 Taroxd::QTE.set 里面。
这样的话,我们并不知道场景会在哪里暂停,也不知道这样的暂停会带来什么后果。
因此,这样的代码是不规范的,仅作测试之用,需要在将来调整。
class << Taroxd::QTE = Object.new
SCREEN_WAITING = WAITING + 60
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
if SceneManager.scene_is? Scene_Battle
SceneManager.scene.abs_wait(SCREEN_WAITING)
end
end
end
class Scene_Battle
alias ub_20141105 update_basic
def update_basic
ub_20141105
Taroxd::QTE.update
end
end
class << Taroxd::QTE = Object.new
SCREEN_WAITING = WAITING + 60
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
if SceneManager.scene_is? Scene_Battle
SceneManager.scene.abs_wait(SCREEN_WAITING)
end
end
end
class Scene_Battle
alias ub_20141105 update_basic
def update_basic
ub_20141105
Taroxd::QTE.update
end
end
最后完整的代码(半成品)如下:
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
TOLERENCE = 1 # 准许误差的帧数
# key 为 nil 时表示没有 QTE 设置
# when_triggered: QTE 触发时执行的脚本
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
if SceneManager.scene_is? Scene_Battle
SceneManager.scene.abs_wait(SCREEN_WAITING)
end
end
def update
return unless @key
@frame -= 1
return terminate if @frame < -TOLERENCE
trigger if hit? && @frame.abs <= TOLERENCE
end
def pos
@key && 1 - @frame.fdiv(WAITING)
end
private
def hit?
Input.trigger?(@key)
end
def trigger
@when_triggered.call
terminate
end
def terminate
@key = nil
end
end
class Sprite_QTE < Sprite
QTE = Taroxd::QTE
def initialize(_ = nil)
super
self.bitmap = Bitmap.new(50, 8)
self.x = 100
self.y = 100
self.z = 200
end
def update
refresh if QTE.pos != @pos
end
def dispose
bitmap.dispose
super
end
private
def refresh
@pos = QTE.pos
bitmap.clear
return unless @pos
pos = @pos * bitmap.width
bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
end
end
class Scene_Battle
alias ub_20141105 update_basic
def update_basic
ub_20141105
Taroxd::QTE.update
end
end
class Spriteset_Battle
alias ct_20141105 create_timer
def create_timer
ct_20141105
@qte_sprite = Sprite_QTE.new
end
alias ut_20141105 update_timer
def update_timer
ut_20141105
@qte_sprite.update
end
alias dt_20141105 dispose_timer
def dispose_timer
dt_20141105
@qte_sprite.dispose
end
end
# 技能公式范例:
# Taroxd::QTE.set { b.add_state 1 }; a.atk * 4 - b.def * 2
module Taroxd end
class << Taroxd::QTE = Object.new
WAITING = 60 # 执行 set 到触发时间的帧数
SCREEN_WAITING = WAITING + 60 # 执行 set 时画面等待的帧数
TOLERENCE = 1 # 准许误差的帧数
# key 为 nil 时表示没有 QTE 设置
# when_triggered: QTE 触发时执行的脚本
def set(key = :C, &when_triggered)
@frame = WAITING
@key = key
@when_triggered = when_triggered
if SceneManager.scene_is? Scene_Battle
SceneManager.scene.abs_wait(SCREEN_WAITING)
end
end
def update
return unless @key
@frame -= 1
return terminate if @frame < -TOLERENCE
trigger if hit? && @frame.abs <= TOLERENCE
end
def pos
@key && 1 - @frame.fdiv(WAITING)
end
private
def hit?
Input.trigger?(@key)
end
def trigger
@when_triggered.call
terminate
end
def terminate
@key = nil
end
end
class Sprite_QTE < Sprite
QTE = Taroxd::QTE
def initialize(_ = nil)
super
self.bitmap = Bitmap.new(50, 8)
self.x = 100
self.y = 100
self.z = 200
end
def update
refresh if QTE.pos != @pos
end
def dispose
bitmap.dispose
super
end
private
def refresh
@pos = QTE.pos
bitmap.clear
return unless @pos
pos = @pos * bitmap.width
bitmap.fill_rect(0, 0, bitmap.width, bitmap.height, Color.new(0,0,0))
bitmap.fill_rect(0, 0, pos, bitmap.height, Color.new(255,255,255))
end
end
class Scene_Battle
alias ub_20141105 update_basic
def update_basic
ub_20141105
Taroxd::QTE.update
end
end
class Spriteset_Battle
alias ct_20141105 create_timer
def create_timer
ct_20141105
@qte_sprite = Sprite_QTE.new
end
alias ut_20141105 update_timer
def update_timer
ut_20141105
@qte_sprite.update
end
alias dt_20141105 dispose_timer
def dispose_timer
dt_20141105
@qte_sprite.dispose
end
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 |