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

Project1

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

[交流讨论] 歪门邪道事件心得[1]:为什么我的事件标题手感不好

[复制链接]

Lv5.捕梦者 (版主)

梦石
28
星屑
10170
在线时间
4673 小时
注册时间
2011-8-22
帖子
1279

开拓者

跳转到指定楼层
1
发表于 2018-4-7 11:01:16 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

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

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

x
本帖最后由 ⑨姐姐 于 2018-9-12 16:36 编辑

一直想开这个坑,今天终于开了。这一系列会专注于一些比较边缘地带的知识,它们经常被作者们当做“玄学”,或者被不屑一顾。但是,每个现象都是有它的道理的,深入了解下去,就能对RM的运作方式更加熟悉,制作出更符合心意的游戏效果。
这系列面向的是有一定事件基础的制作者们,并没有脚本水平的要求(完全不会也没关系)。另一方面,也许写了很长时间脚本但对RM的默认内容关注不多的,也能从中得到一些有用的东西吧。

欢迎反馈关于内容深浅以及描述方式等等的各种问题,也欢迎提供各种大家在RM中的“玄学”以及一直当做习惯却难以理解的地方,在以后的几期中也可以把这些作为主题的。
-------------------------------------------------------------

一天,埃里克百无聊赖地在海洋上行走(不要问他是如何做到的)。


他觉得,是时候开始游戏了。“开始游戏”……就是说需要个游戏标题:



用图片当做三个按钮,然后用并行事件判断当前的选项和图片的透明度,像这样——





可是,埃里克发现了一个问题:按左右键的时候,不是每次都成功的,有时候按了但是毫无反应。
“为什么我的事件标题手感不好呢?明明所有地方都写对了。”

“你遇到问题总是愁眉苦脸,这可不是勇者该有的样子呢。”娜塔莉不知什么时候出现在这里。


“我猜,按键没反应,是不是RM内部的按键输入时好时坏?”

“不可能。”娜塔莉否定了埃里克的猜想,“如果RM的输入真的有问题,为什么其他菜单的窗口都那么流畅呢?”

“难道是很多人说的,事件的效率天生不如脚本,用事件就会卡顿?呜呜呜,我要去学脚本……”

“给我打起精神来你这白毛!你忘了事件的背后就是脚本在运行吗?这两者又有什么区别呢。”

“事件的背后就是脚本?”

“一看你就没有好好听大贤者的讲课。来,我们打开脚本编辑器,找到Game_Interpreter。46行。”

RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2. # * Event Setup
  3. #--------------------------------------------------------------------------
  4. def setup(list, event_id = 0)
  5.   clear
  6.   @map_id = $game_map.map_id
  7.   @event_id = event_id
  8.   @list = list
  9.   create_fiber
  10. end

在埃里克疑惑的目光中,娜塔莉解释道:“你看,这个setup,就是把事件的内容放入脚本中去执行了。list是事件列表,event_id是事件编号。所以我们编辑的每一个事件,其实都会丢到脚本里去执行的。既然这样,为什么要说事件速度慢呢?根本就是无稽之谈。”

埃里克还是没有完全搞懂:“我还记得一些脚本,这种等于号的都只是把一个值放到另一个变量里的操作,比如a=2就是把a变成2。就算我生成了好多个变量,但它们也只是数据倒来倒去而已,事件放进去以后,怎么执行呢?”

“事件执行的奥秘,就在create_fiber里了。”

娜塔莉一遍说着,一遍顺着脚本往下追溯:

RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2. # * Create Fiber
  3. #--------------------------------------------------------------------------
  4. def create_fiber
  5.   @fiber = Fiber.new { run } if @list
  6. end

RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2. [font=&quot]# * Execute[/font]
  3. #--------------------------------------------------------------------------
  4. def run
  5.   wait_for_message
  6.   while @list[@index] do
  7.     execute_command
  8.     @index += 1
  9.   end
  10.   Fiber.yield
  11.   @fiber = nil
  12. end



“埃里克,你看这个run,不就是执行事件的过程吗。execute_command的意思是执行一条事件指令,@index += 1的意思是把事件执行的位置往下移一格。你可以想象,你用铅笔指着一条事件,执行完以后再执行下一条事件的感觉。”

埃里克反而更疑惑了:“从这里看来,我上面写的事件应该是一点问题也没有啊,怎么可能按键失效呢。除非事件没有正常执行,没能通过条件分歧判断到我的按键,才会出现失效的情况吧。”

娜塔莉沉思片刻,两眼突然放出了光芒,把埃里克吓了一跳:“我明白了,你的事件确实没问题,问题出在事件结束的时候。埃里克,我考你一个问题,从上面的run当中,你能看出事件结束的时候会发生什么吗?”

“事件结束的时候,@index就超过事件列表的长度了,所以@list[index]就拿不到事件的内容,这时候while……”

“答对了,while @list[index]的循环就会正常退出,事件也就结束了。接下来呢?”

“接下来……Fiber.yield,这是什么意思呢?”

娜塔莉把代码翻到了Game_Interpreter的236行:

RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2. # * Wait
  3. #--------------------------------------------------------------------------
  4. def wait(duration)
  5.   duration.times { Fiber.yield }
  6. end

“这里duration.times就是重复duration那么多次的操作。你看,一个叫做wait的东西,重复了那么多次的Fiber.yield,还不能猜出Fiber.yield是做什么的?”

埃里克恍然大悟:“wait我知道,就是事件里的等待。用N次某个操作组成了等待N帧,那某个操作就是‘等待1帧’了吧。”

娜塔莉点点头:“在这里确实可以理解成等待1帧。准确地说,是暂时退出Fiber上下文,去执行其他内容,等到1帧以后再来继续执行这个Fiber。”[如果想要了解关于Fiber更多的知识,可以看这篇文章的介绍]

“你还是别讲Fiber啊上下文什么的了,我暂且理解成等待1帧没错吧,那为什么是这里会导致出问题呢?”

“并行事件只要在地图上,它就会循环重启执行,这一点你作为事件党,应该很了解吧。”[如果想要了解它的原理,可以参考Game_Event脚本,一直拉到最后就能看到的def update]

“那是当然,我埃里克再怎么说,也是游戏制作勇者啊。”

“可是你发现了吗,你用来判断按键的并行事件,其实不是和游戏帧率一样,每秒60次,而是每秒30次。”

“每秒30次……也就是实际上一次并行事件占用了2帧吗。是不是因为,当到达下一帧的时候原本并行事件该重启了,结果却因为上面多等待了一帧,结果没有重启?这等待的一帧,就是我的键盘‘失灵’的时机?”

“你终于明白了。”娜塔莉笑道,“实际上你每次按键,都是在赌随机数啊。赌你按键的时刻是不是恰好落在执行事件的一帧上。假如落到了等待的一帧,那就没反应啦。所以快改脚本吧。”

“不行,不行。我作为勇者,怎么能认输呢,我偏要用事件的方式,把这个问题解决了。”

“这回你倒是说对了,说不定有什么其他人的脚本,会利用到这条特性呢。那就看看你能用事件搞出什么花样吧。”

只过了片刻,埃里克就找到了解决问题的办法:




果然,这样处理以后,按键就完全流畅了。

那么今天的课后习题就是,为什么这样处理以后按键流畅了呢?上面提到的“多出的一帧”去哪儿了呢?

用到的工程: DeviantEventTutorial1.zip (1.33 MB, 下载次数: 291)

正确答案

评分

参与人数 12+12 收起 理由
indio + 1 精品文章
多啦A户 + 1 精品文章
水野·迪尔 + 1 精品文章
鑫晴 + 1 精品文章
人民卫戍部队 + 1 精品文章
Mayaru + 1 塞糖
Kim_Shyuen + 1 精品文章
W.Q.C. + 1 精品文章
2256538860 + 1 精品文章
迷糊的安安 + 1 终于知道喂什么了

查看全部评分

Lv4.逐梦者

梦石
2
星屑
6698
在线时间
501 小时
注册时间
2018-3-23
帖子
533

R考场第七期银奖

2
发表于 2018-4-7 11:30:24 | 只看该作者
截图为什么是英文版的啊,这样和看脚本没啥区别吧

点评

抱歉,我手上没有汉化版RM……如果遇到哪里不确定意思的请直接提出来吧,我解释一下。  发表于 2018-4-7 11:50
人家用的正版……  发表于 2018-4-7 11:35
祝好。
回复 支持 1 反对 0

使用道具 举报

Lv4.逐梦者

梦石
5
星屑
3038
在线时间
612 小时
注册时间
2012-11-12
帖子
482

开拓者

3
发表于 2018-4-7 11:38:14 | 只看该作者
唔哇啊,这样解释问题超有意思的啊(感觉在看启蒙读物!画面感超强!)

之前用的标题事件确实有遇到过这种“手感不好”的情况
于是当时高频率/反复地按着左右按键,
发现一个现象,如果你按得越快,则“无效”的概率也就越大
所以当时的一个猜测就是 :
“是不是由于上一次的 ‘←’ 或者 ‘→’ 的执行需要一定的时间,影响到下一个操作的读取,导致失效的?”
现在终于明白了具体原因,原来如此,原来如此。


【课后作业】
我猜想,应该是因为在并行事件里手动加入等待一帧之后,
相当于是把游戏的刷新率减半,也就是每秒30次,
这样就和并行事件的刷新率同步了,
等同于“剔除了”没有执行事件的帧数,
赌随机数的概率也就变成了100%,这样?

点评

答案不对~上面都说了必须要60fps每一帧执行才能保证按键正确……  发表于 2018-4-7 11:50
回复 支持 反对

使用道具 举报

Lv4.逐梦者

梦石
2
星屑
6698
在线时间
501 小时
注册时间
2018-3-23
帖子
533

R考场第七期银奖

4
发表于 2018-4-7 11:54:05 | 只看该作者
MCCF 发表于 2018-4-7 11:30
截图为什么是英文版的啊,这样和看脚本没啥区别吧

正版也有中文版的啊

点评

你家va正版有中文  发表于 2018-4-7 12:27
祝好。
回复 支持 反对

使用道具 举报

Lv5.捕梦者 (版主)

遠航の猫咪

梦石
3
星屑
23209
在线时间
2387 小时
注册时间
2005-10-15
帖子
1166

开拓者

5
发表于 2018-4-7 13:09:47 | 只看该作者
XP一样有这个问题,只不过是20和40罢了。而且XP没有fiber,是靠return false来摸拟的。

点评

我看了一下,XP应该没有这个问题(可能是其他问题)?因为command_end后list已经清空了,不会多出额外1帧  发表于 2018-4-7 13:29
SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
6901
在线时间
7028 小时
注册时间
2013-11-2
帖子
1344

开拓者剧作品鉴家

6
发表于 2018-4-7 13:17:06 | 只看该作者
事件标题的操作手感不好,大部分是因为事件的按钮判定是用Input.press?(按住会重复触发)而不是Input.trigger?(按下到放开只算一次)。
目前现有的事件标题,只要改动按钮判定的方式就能姑且拯救一下手感。

点评

新人总是犯同样的错误(  发表于 2018-4-7 16:06
我晕,到VA了还是用的press?……10年前就对XP的这个press?相当无力吐槽  发表于 2018-4-7 16:00
是的,直接用press也是个大问题,按一下就跳不停了。  发表于 2018-4-7 13:25
回复 支持 反对

使用道具 举报

Lv6.析梦学徒

老鹰

梦石
40
星屑
34831
在线时间
6748 小时
注册时间
2012-5-26
帖子
3261

极短24评委极短23参与极短22参与极短21评委老司机慢点开短篇十吟唱者组别冠军开拓者剧作品鉴家

7
发表于 2018-4-7 13:41:53 | 只看该作者
昨天才在另一个帖子里研究了一波默认的神奇interpreter类2333

这个事件修改后有两个地方与之前不一样了
1、新增了 等待一帧
2、新增了 循环

结合事件解释类里它的运行方法:一直循环直至完成全部指令,在真正取消自己的刷新前等待一帧(或者事件中途出现Fiber.yield,使得它的循环挂起,下一帧时再被resume,从上一帧执行到的位置继续这个循环)*具体见Fiber类
  1. #--------------------------------------------------------------------------
  2. # 运行
  3. #--------------------------------------------------------------------------
  4. def run
  5.   wait_for_message
  6.   while @list[@index] do
  7.     execute_command
  8.     @index += 1
  9.   end
  10.   Fiber.yield
  11.   @fiber = nil
  12. end
复制代码


因此,新增的这个 循环 指令,直接令事件永远不会执行完,也就是这个事件单凭自己是永远不会从run方法里的while @list[@index] do...end中结束的,也就更没有什么取消刷新前挂起一帧了。

但是,如果无聊自己试验过就知道,这种无限循环(无法结束的)会直接令游戏主逻辑卡死,也即画面不会被刷新、按键也不会被响应,
此时 等待一帧 的指令就有用了,我们人为在这个循环里添加一次挂起,当运行到这里时,这个事件这一帧要进行的操作就可以中止了,然后回到主逻辑里继续刷新其他的东西,这样主逻辑不受干扰继续刷新,而等到下一帧时,由于Fiber的特性,就让这个事件从上一帧中止的地方继续执行下去,我们又回到了事件里循环的开头,也即让这个事件随主逻辑1s60帧刷新
*主逻辑:游戏本身实际上是一个大循环,每一帧依次更新着画面、输入、声音等

点评

是的,因为事件没有结束,所以就跳过了多出的一帧……本来帖子也想提一下主逻辑的,但是那样可能内容就太多了,我去加个“看7楼”吧  发表于 2018-4-7 13:53

评分

参与人数 2+2 收起 理由
W.Q.C. + 1 精品文章
⑨姐姐 + 1 正确答案~

查看全部评分

回复 支持 反对

使用道具 举报

Lv6.析梦学徒

梦石
62
星屑
10593
在线时间
2800 小时
注册时间
2010-6-13
帖子
1117

极短25获奖极短24参与极短22参与极短21获奖神奇操作开拓者

8
发表于 2018-4-7 21:24:20 | 只看该作者
好有趣的教学方式啊!!希望持续更新!!对纯事件党来说感觉很有用(XP应该原理相似?)
懂脚本果然能更深层次的挖掘事件的判定机制吗,事件标题手感这个事果然应该不少人遇过啊,
之前还折腾了好一阵子,自己最终是用“与事件接触”的方式来判定选项才让手感稍稍好了一点,
但是按太快还是会有失效的情况,不过至少还能看,也就这么一直用下来了,
(应该是人物走动触发事件的走动过程花的时间比起直接的按键判定花费的时间多得多)
现在看到⑨姐姐的这个帖子又学到了许多,以后就可以拿来试试看了。
手感这个东西确实很影响玩家体验,而且个人认为应该是许多纯事件RM游戏中比较常见的问题,
也希望这个系列能给一些只想用事件的新人一些启发吧
然后果然出现老鹰大大的身影了啊2333,一起吹爆!
(最后顺带借帖偷偷吹爆《审判者》,前几天终于有时间熬夜(平时没太长空闲时间来玩)玩了一周目,
虽然只是粗略的玩了一遍,都已经感受到游戏系统的新颖和超有感觉的故事氛围了,
打算等再空下时间来的时候再好好品味一番!)

点评

XP我下午试了一下没有并行按键的问题,可能在别处会有问题吧……之后会持续更新的,不过内容还没决定,可能会有其他一些有趣的东西。  发表于 2018-4-7 22:17
W.Q.C.最近摸鱼了……吧?
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
4598
在线时间
1206 小时
注册时间
2016-4-7
帖子
982

开拓者

9
发表于 2018-4-8 15:43:28 | 只看该作者
本帖最后由 shitake 于 2018-4-8 15:47 编辑
RaidenInfinity 发表于 2018-4-7 13:17
事件标题的操作手感不好,大部分是因为事件的按钮判定是用Input.press?(按住会重复触发)而不是Input.trig ...


@SailCat

并不能简单的把使用 press 和手感差对应起来。倘若是人物移动,不适用press而是trigger 反而手感会变的更差。同样对于某些选单来说,不能长按就持续切换,反而只能按一下切换一下也是手感差到爆。

这里的核心问题是,输入事件的响应是跨帧的(即响应效果是持续多帧的)。如果使用 press 会在每一帧都触发事件。但之前的事件的响应并没有完成。这种情况下就会手感变差。

实际上在此处,我们需要的是对输入事件序列进行 filter 来过滤掉那些不满足条件的触发(上个事件的响应没有结束)。

将 press 提换成 trigger 更像是一个权宜之计。在事件响应较短的情况下(比如此处的选单),依靠按键事件本身的跨帧(在60 fps 的情况下,即使在快的按下弹起,都会跨越多个游戏帧)来做到延缓触发的效果,只能说是恰好撞对了结果。但是假如需求转变(比如人物移动,播完一个移动动画往往需要持续更多的帧,或者一个更复杂的 UI 切换动画)再用这个思路无疑就是往死胡同里钻。

由于 rm 在事件部分并没有提供上层的抽象。只有最底层的Input判断。所以这一filter流程就只能耦合在项目代码中。在 RMVA 里,对应的是如下代码:

  1.   def move_by_input
  2.     return if !movable? || $game_map.interpreter.running?
  3.     move_straight(Input.dir4) if Input.dir4 > 0
  4.   end
复制代码


对此的更好的处理,是 FRP[1]。注入 elm/ReactiveX 都是此类的很好的例子。在 FRP 中,event 以流(stream)的形式存在,我们可以对其进行 filter/map/merge 等操作。这些操作的返回值依然是事件流(EventStream)。当我们需要的时候,可以在流上订阅一个消费。

一个单击事件合并为双击事件的例子:
  1. click_event_stream = Event.get_event(:click)

  2. double_click_event_stream = click_event_stream.filter{ |e, last| e.time - last.time < 0.3  }

  3. double_click_event_stream.on{ puts 'double click' }
复制代码


除此之外,另一个思路是利用 Fiber。Fiber赋予我们随时挂起程序和返回到之前挂起的地方继续执行的能力。如果不想用 event stream。那么我们可以利用对 Fiber 的 yield 和 resume 来模拟类似的效果。(在这里 Fiber 的调用/挂起栈隐性的承担了 event stream 的工作)

这一思路的具体实现可以看看这个:RGUI::Event

上边的那个例子的实现:
  1. event_manager.on(:click)do |helper|
  2.   helper.filter{ helper.time_min(0.3) }
  3.   helper.trigger(:double_click)
  4. end
复制代码


1. Elm: Concurrent FRP for Functional GUIs

点评

不过赞同内容  发表于 2018-4-8 22:19
嗯……上下文是事件菜单来着,讨论的也是比较多见的新人错误  发表于 2018-4-8 22:18
附庸的附庸不是我的附庸,女儿的女儿还是我的女儿。CK2沉迷ing
回复 支持 反对

使用道具 举报

Lv4.逐梦者

梦石
4
星屑
2965
在线时间
271 小时
注册时间
2017-3-5
帖子
61
10
发表于 2018-4-11 05:20:18 | 只看该作者
本帖最后由 Mayaru 于 2018-4-11 05:26 编辑

(哇!瞧我发现了什么!一个野生的九姐姐教程!青蛙味嘎嘣脆,蛋白质是触手的五倍x)
感谢教程~大概理解了(说起来我之前真的不知道rgss可以用while...那样的话for也可行吗)。但是index我不太确定是代表什么...Index初始值是什么?0吗?

点评

index的第一个选项,是0哦  发表于 2020-12-9 09:33
回复 支持 反对

使用道具 举报

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

本版积分规则

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

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

GMT+8, 2024-12-25 02:49

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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