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

Project1

 找回密码
 注册会员
搜索
查看: 496|回复: 13

[原创发布] 剧本系统-剧情内容写在游戏外

[复制链接]

Lv4.逐梦者 (版主)

梦石
0
星屑
8999
在线时间
1234 小时
注册时间
2011-7-8
帖子
1957

开拓者

发表于 2018-7-25 20:19:43 | 显示全部楼层 |阅读模式

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

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

x
本帖最后由 guoxiaomi 于 2018-7-26 01:29 编辑

摘要
RM系列制作的游戏,其数据是以ruby marshal的格式保存。此类数据不能直观的阅读。本文提供了一个“剧情系统”的初步实现:即将剧情文件放在游戏工程之外,在游戏运行的时候读取剧情文件生成对应的事件页并执行。此系统分离开游戏的核心和剧情,在游戏的制作、调试、校对和翻译上会带来优势。最后,考虑到标记语言markdown的流行,在这个实现里,剧情文件使用类似markdown的语法。
引言
传统rpg对数值总是有所苛求,这使得作者不愿意把游戏的核心数据公开,并认为玩家查看数据文件=作弊=毁掉游戏体验。但是有些游戏会把一些数值设定暴露给玩家,这会带来以下优势:
  • 开发方面
    • 并行合作进行开发、调试,剧情文本文件和游戏系统的改动互不影响
    • 所有剧情文本文件统一管理,剧情流程直观,可以高效的比对、修改
    • 游戏的多语言版本只需要将剧情文本文件翻译校对,无需提取剧情翻译后再复制回去
    • 用git做版本管理时,可以直接追踪剧情文本文件的改动
    • 剧情文本文件的格式不依赖于制作工具,方便迁移到其他平台
  • 游戏性方面
    • 方便玩家制作mod,提升核心玩家的游戏性
下面是游戏Geneforge 5中的一段剧情文本:
C 代码复制打印
  1. // dlg.txt
  2.  
  3. begintalkscript;
  4.  
  5. variables;
  6.  
  7. begintalknode 1;
  8.     state = -1;
  9.     nextstate = -1;
  10.     condition = get_sdf(1,1) == 0;
  11.     question = "special";
  12.     text1 = "You enter the settlement of Minallah, the only town in these frigid mountains. It is also the gateway to the Foundry, where creations are checked in and taken out.";
  13.     text2 = "Like most cities in western Terrestia, it is centuries old. Unlike most of them, it isn't very impressive. While the Shapers tend to build massive and intimidating settlements, Minallah is mainly a collection of burrows in the rock.";
  14.     text3 = "It is as if the cold and wind wore away all determination to bring any of the standard Shaper glory to Minallah. The only impressive structure is the spire to the west.";
  15.     text4 = "You manage to recall that it is Isenwood's Spire. A huge, natural pinnacle of stone, honeycombed with tunnels. Dozens of narrow windows dot its sides, little pinpricks of light in the gray and the snow.";
  16.     text5 = "Shaper Rawal commanded that you go see him. That is probably where he is.";
  17.     action = SET_SDF 1 1 1;
  18.     code =
  19.         toggle_quest(3,1);
  20.     break;
从上面的例子中可以看出,剧情文本文件里面不仅有对话的内容,还有其他的一些标记符号。而游戏系统所需要做的,就是解析这个文档并生成对应的事件触发。
方法
首先,需要在RGSS3中找到生成事件页的位置,然后需要写一个解释器来解析markdown格式的剧情文本文件。
替换事件页
同样,从RMVA的RGSS3源码中可以看到,RMVA的事件解释器(Game_Interpreter)所做的,正是解析地图文件(比如Map001.rvdata2)中保存的事件指令(RPG::EventCommand)数组,并从开头按照流程执行到数组末尾。解析的代码在Game_Event中(部分内容省略):
RUBY 代码复制打印
  1. class Game_Event < Game_Character
  2.   #--------------------------------------------------------------------------
  3.   # * Set Up Event Page Settings
  4.   #--------------------------------------------------------------------------
  5.   def setup_page_settings
  6.     ...
  7.     @list = @page.list
  8.     @interpreter = @trigger == 4 ? Game_Interpreter.new : nil
  9.   end
  10. end
只需要用钩子方法,在此方法之后替换@list即可:
RUBY 代码复制打印
  1. class Game_Event < Game_Character
  2.   alias _new_list_setup_page_settings setup_page_settings
  3.   def setup_page_settings
  4.     _new_list_setup_page_settings
  5.     @list.replace new_list
  6.   end
  7.  
  8.   def new_list
  9.     ...
  10.   end
  11. end
new_list即生成新的@list的方法,里面包含了解析markdown文件的内容。
markdown主要格式
markdown是一种纯文本格式的标记语言。通过简单的标记语法,它可以使普通文本内容具有一定的格式。这里只简单的介绍本文用到的语法:
  • 多级标题
  • 有序列表
  • 链接和图片
  • 代码和内嵌表达式
多级标题是以多个&#39;#&#39;号开头的行:
C 代码复制打印
  1. # 1级标题
  2. ## 2级标题

有序列表是以数字和小数点开头的行
C 代码复制打印
  1. 1. 选项一
  2. 2. 选项二

链接和图片是用连续的中括号和小括号括起的内容:
C 代码复制打印
  1. [显示的文本](链接地址 "alt text")
  2. ![显示的文本](图片地址 "alt text")

代码是以&#39;`&#39;括起的内容,并且支持多行代码:
C 代码复制打印
  1. 这是`内嵌表达式`。
  2. ```
  3. 这是多行代码
  4. ```


剧情文本

利用好这些格式,就可以很自然的写一段初始的剧情。下面的剧情中首先设置了开关和变量,然后增加了金钱和部分道具。为了对比,后面给出这个剧情生成的事件页图片以供参考。

C 代码复制打印
  1. ## 初始剧情
  2. [Switches](1 "ON")
  3. [Variables](1 "100")
  4. [Party.Gold](200)
  5. [Party.Item](3 "恢复剂")
  6. [Party.Weapon](1 "手斧")
  7. [Party.Armor](1 "布衣")
  8.  
  9. 神秘的声音:
  10. 醒来吧,勇者!
  11.  
  12. ![主角](Actor1.png "1")
  13. 我在哪里?
  14.  
  15. 神秘的声音:
  16. 你已经是第 `$data_retrytimes` 次尝试这个迷宫了,还需要继续吗?
  17.  
  18. ![主角](Actor1.png "1")
  19. 我以前尝试过吗?那这次?
  20. 1. 继续尝试
  21. 2. 放弃治疗
  22.  
  23. ### 继续尝试
  24. ![主角](Actor1.png "1")
  25. 虽然不知道为什么,但是选这个就没错了。
  26.  
  27. ### 放弃治疗
  28. ```
  29. SceneManager.goto(Scene_Title)
  30. ```


读取此文件后生成的事件页如下图所示,需要注意的是`$data_retrytimes`内嵌表达式已经求值完毕。当然书写的剧情文本格式并不强求。由于这个解释器和markdown的格式是自定义的,只是借用了markdown的格式,其实现非常简单,可扩展性强。
捕获.PNG

更多功能

由于事件页是在游戏内解析生成的,合适的解析方法可以根据场合生成不同的事件页,对原来系统的进行扩展。

第一个例子就是上面提到的的内嵌表达式。此功能是文本里插入指示符\v[n]的增强。第二个例子是控制选择项出现的条件。由于选择项是解析生成的,可以在生成的时候控制选择项是否出现。比如上一节提到的例子中,要求尝试次数超过3次才能选择放弃,否则“放弃治疗”的选项不出现。可以参考下面的语法格式:

C 代码复制打印
  1. ![主角](Actor1.png "1")
  2. 我以前尝试过吗?那这次?
  3. 1. 继续尝试
  4. 2. 放弃治疗
  5.     - `$data_retrytimes >= 3`


第三个例子是设置移动路线,在剧情复杂的地方,移动路线的设置往往非常冗长而且麻烦,需要在新打开的窗口里用鼠标点击。这里也给一个语法参考,以简化对移动路线的设置,这里用wasd表示四方向的移动,WASD表示四方向的转身:
C 代码复制打印
  1. [Move](Ev001 "w4 a2 S")
从markdown格式的剧本文本文件解析到事件页的方案能够自定义,给了开发者极大的自由去书写剧本。而不同的使用者可能钟爱于不同的标记文本的书写格式,这里暂无一个统一的标准给出。
总结
本文提供了一个简单的方案将剧情文本写在游戏数据库之外,并且在游戏内解析成事件指令。由于只是解释成事件指令,在与原来的系统可以做到完全兼容的同时,还带来了开发上的诸多便利。此外,利用自定义的解析方式,甚至可以超出原来的事件页的功能。
附录
附录是当前所完成的剧本系统的语法格式和对应的代码,仅供参考。

评分

参与人数 3星屑 +500 +3 收起 理由
唯道集虚 + 500 + 1 精品文章
SixRC + 1 塞糖
百里_飞柳 + 1 精品文章

查看全部评分

熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* 基于SAE的服务器 *

Lv4.逐梦者 (版主)

梦石
0
星屑
8999
在线时间
1234 小时
注册时间
2011-7-8
帖子
1957

开拓者

 楼主| 发表于 2018-7-25 20:19:54 | 显示全部楼层
本帖最后由 guoxiaomi 于 2018-7-26 01:40 编辑

剧本系统
把游戏的剧情文本写在游戏外部,方便后续的更改。可能增加一些新功能,比如,特定剧情的出现条件等。
剧本系统尽量使用markdown的语法,会读取#开头的段落并将后续的文本导入到一个hash里缓存。目前有以下功能:
  • 使用>表示注释,引用的内容会被忽略
  • 使用```括起的内容,会转换成事件脚本
  • 使用![name](pic_name "1")表示“显示文章”的头像
    • 同时会设置“显示文章”的第一行为name
  • 普通的文本会转化为“显示文章”的内容,
    • 如果普通文本之后出现了空行,会转化为下一个显示文章
    • 普通文本末尾的<br>会自动忽略
  • 使用`作为内嵌表达式,会在生成事件的时候求值
    • 即载入地图前
    • 仅对显示文章有效
    • 调用事件的 auto_reload 方法会重新求值
  • 使用有序列表(比如1. xxx)表示选择项,选择项只有跳转到对应标签的功能,取消的话会继续执行
    • 选择项之间不要空开,选择项和后面的文本要空开
    • 在列表项后面插入深一层的无序列表,以控制选项出现的条件(见后面)
    • 调用事件的 auto_reload 方法会重新计算条件
  • 使用<email@com>的格式表示一些特殊的“可见”指令
    • <CommonEvent@16> 调用16号公共事件,注意调用公共事件后会返回
    • <GetItem@16> 选择物品,ID保存在16号变量里
    • <JumpTo@chapter> 标签跳转
    • <AutoEvent@reload> 重新载入本事件
  • 使用 [sth]: para1 "para2"表示一些特殊的“不可见”指令
    • 这个格式的引用在markdown中不显示
    • [Balloon]: event "surprise" 显示气泡
    • [SelfSwitch]: A "ON" 打开独立开关
    • [Dialog]: setting "ON" 对话加强脚本设置
    • [Move]: -1 "w3 S" 设置移动路线
    • [PlaySE]: file "80" 播放SE
  • 使用表格表示商店
    • 商店已经完成,后续其他的想法再说
    • 表格前后都要有空行
  • 使用[chapter](condition)表示有条件的标签跳转,支持短跳
    • 转换成条件分歧-脚本
    • 默认会在条件分歧后创建标签:#chapter,从而允许执行跳转后再回来
  • (高级) 使用[cmd] [paras]的格式输入任意事件指令
    • 中间必须有一个空格
    • 后面的[paras]是一个数组,eval求值后作为参数
    • indent = 0



选项出现的条件
如下所示,多个条件的话必须全部满足。
  1. 我是文本:
  2. 1. 选项1
  3. 2. 金币大于 100 才会出现的选项
  4.     - `$game_party.gold > 100`
复制代码


商店格式
商店格式,可以支持多个物品。
  • 第1列是道具种类,第2列是id,最后一列是价格,如果价格为&#39;-&#39;,则用默认的价格
  • 第二个参数 P = Purchase Only 表示不可卖出。只识别第一个字母

ShopP--
01小血瓶30
其他指令说明
  1. # 这个脚本会重新载入事件页,从而对内嵌表达式和选项出现的条件重新求值
  2. $game_map.events[@event_id].auto_reload
  3. # 可以使用 <AutoEvent@reload> 替代
复制代码
设置移动路线
设置移动路线的格式如下:[Move]: -1 "w2 X1"
  • 第1项是角色的id:-1表示Player,0表示本事件,1以上是事件的id。
  • 如果第一个参数是字符串,会搜索同名的事件
    • 原理是在“设置移动路线”的指令前面加了一段脚本修改了后续事件的内容
    • @list[@index + 1].parameters[1] = ...
  • 使用 [Moves] 使得事件设置为:不等待移动结束
  • 第2项的参数用空格隔开,每一项对应成移动指令,具体规则见下表。区分大小写
    • 通常用大小写区分功能的开(大写)和关(小写)
代码说明
移动-
w1向上移动 1 步
a2向左移动 2 步
s3向下移动 3 步
d4向右移动 4 步
f5向前 5 步
b6向后 6 步
j7.-8跳跃 7, -8
J9.10传送到 9, 10
转向-
W脸朝上
A脸朝左
S脸朝下
D脸朝右
L左转90度
R右转90度
B转180度
FIX面向固定 ON
fix面向固定 OFF
设置-
PASS穿透 ON
pass穿透 OFF
v1设置移动速度 1
V2设置移动频率 2
STEP踏步动画 ON
step踏步动画 OFF
透明-
T透明 ON
t透明 OFF
o128调整透明度为 128
控制-
W60等待 60 帧
X1打开 1 号开关
x2关闭 2 号开关


解析用代码:
RUBY 代码复制打印
  1. # functions:
  2. # auto load event list from the txt file with same name.
  3.  
  4. class Game_Event < Game_Character
  5.  
  6.   alias _auto_load_setup_page_settings setup_page_settings
  7.   def setup_page_settings
  8.     _auto_load_setup_page_settings   
  9.     if @list[0].code == 355 && @list[0].parameters[0] =~ /AUTO:(.+)/
  10.       auto_load(@event.name, $1.strip)
  11.     elsif @auto_load_keys
  12.       auto_load *@auto_load_keys
  13.     end
  14.   end
  15.  
  16.   def auto_load(name, key)
  17.     @auto_load_keys = [name, key]
  18.     @list.replace Auto_Event.load(name).list(key)
  19.   end
  20.  
  21.   def name
  22.     @event.name
  23.   end
  24.  
  25.   def auto_reload
  26.     @page = nil
  27.     refresh
  28.   end
  29. end
  30.  
  31. module Auto_Event
  32.   module_function
  33.  
  34.   Event_List = {}  
  35.  
  36.   def reload # only after .md file changes
  37.     Event_List.keys.each do |name|
  38.       Event_List[name] = load_list_txt(name)
  39.     end
  40.   end
  41.  
  42.   def load(name)
  43.     Event_List[name] ||= load_list_txt(name)
  44.     @data = Event_List[name]
  45.     @list = []
  46.     @keys = []
  47.     self
  48.   end
  49.  
  50.   def load_list_txt(name)
  51.     data, key, value = {}, '', []
  52.     File.open('Events/' + name + '.md', 'r') do |f|
  53.       f.readlines.each do |line|
  54.         # start with # key
  55.         if line =~ /^#+\s*(.+)/
  56.           data[key] = value.join("\n") if key != ''
  57.           key, value = $1, []
  58.           next
  59.         else
  60.           value << line.strip
  61.           next
  62.         end
  63.       end
  64.       # last key
  65.       data[key] = value.join("\n") if key != ''
  66.     end
  67.     data
  68.   end
  69.  
  70.   def list(key)
  71.     @keys << key
  72.     event_keys = []
  73.     while @keys.size != event_keys.size
  74.       @keys.uniq!
  75.       @keys.each do |key|
  76.         next if event_keys.include?(key)
  77.         event_keys << key
  78.         next if !@data[key]
  79.         add(118, 0, [key])
  80.         set_text(@data[key])
  81.         add(115, 0, [])
  82.       end
  83.     end
  84.     add(0, 0, [])
  85.   end
  86.  
  87.   def set_text(text)
  88.     msg_tag, script_tag, choice_tag, table_tag = *[false] * 4
  89.     choices = []
  90.     choice_ignore = []
  91.     table_rows = []
  92.     text.split("\n").each do |line|
  93.       # ------------------------------------------------------------
  94.       # 优先判定是否为脚本
  95.       # ------------------------------------------------------------
  96.       if script_tag
  97.         if line =~ /^```/
  98.           script_tag = false
  99.         else
  100.           add(655, 0, [line])
  101.           msg_tag = false
  102.         end
  103.         next
  104.       end
  105.       # ------------------------------------------------------------
  106.       # 判定注释
  107.       # ------------------------------------------------------------
  108.       if line =~ /^>/
  109.         next
  110.       end
  111.       # ------------------------------------------------------------
  112.       # 继续剩下的判定
  113.       # ------------------------------------------------------------
  114.       case line
  115.       # ------------------------------------------------------------
  116.       # ```识别为 脚本
  117.       # ------------------------------------------------------------
  118.       when /^```/
  119.         add(355, 0, '#~ event scripts')
  120.         script_tag = true
  121.       # ------------------------------------------------------------
  122.       # 空行
  123.       # ------------------------------------------------------------
  124.       when /^\s*$/
  125.         if msg_tag
  126.           msg_tag = false
  127.         end
  128.         if choice_tag
  129.           choice_tag = false
  130.           add_choices(choices, choice_ignore)
  131.         end
  132.         if table_tag
  133.           table_tag = false
  134.           add_table(table_rows)
  135.           table_rows.clear
  136.         end
  137.       # ------------------------------------------------------------
  138.       # 数字列表:选择项
  139.       # ------------------------------------------------------------
  140.       when /^\s*\d+\.\s*(.+)/
  141.         text = $1        
  142.         if choice_tag
  143.           choices << text
  144.         else
  145.           choice_tag = true
  146.           choice_ignore = []
  147.           choices = [text]
  148.         end
  149.       when /^-\s+`(.+)`/
  150.         if choice_tag
  151.           if !eval($1)
  152.             choice_ignore << choices.size - 1
  153.           end
  154.         end
  155.       # ------------------------------------------------------------
  156.       # 图片:对话开始,自动名称和头像
  157.       # ------------------------------------------------------------
  158.       when /^!\[(.+)\]\((.+)\)/
  159.         name, fig = $1, $2
  160.         if fig =~ /"(\d+)"/
  161.           num = $1.to_i
  162.           fig = fig.sub(/"\d+"/, '').strip
  163.         else
  164.           num = 0
  165.         end
  166.         msg_tag = true
  167.         add(101, 0, [fig, num, 0, 2])
  168.         add(401, 0, [name])
  169.       # ------------------------------------------------------------
  170.       # 邮件<>:可见指令
  171.       # ------------------------------------------------------------
  172.       when /^<(.+)@(.+)>/ #
  173.         cmd, para = $1, $2
  174.         case cmd
  175.         when 'CommonEvent'
  176.           if para =~ /\d+/
  177.             add(117, 0, [para.to_i])
  178.           else
  179.             id = $data_common_events.find{|e| e && e.name == para}.id
  180.             add(117, 0, [id])
  181.           end
  182.         when 'GetItem'
  183.           add(104, 0, [para.to_i])
  184.         when 'JumpTo'
  185.           add(119, 0, [para])
  186.           @keys << para
  187.         when 'AutoEvent'
  188.           if para == 'reload'
  189.             add(355, 0, ['$game_map.events[@event_id].auto_reload'])
  190.           end
  191.         end
  192.       # ------------------------------------------------------------
  193.       # [sth]: site "description" 不可见指令
  194.       # ------------------------------------------------------------
  195.       when /^\[(.+)\]\s*:\s*(.+)\s+"(.+)"/
  196.         cmd, para1, para2 = $1, $2, $3
  197.         case cmd
  198.         when 'SelfSwitch' # 独立开关
  199.           _v = (para2 == 'ON')? 0 : 1
  200.           add(123, 0, [para1, _v])
  201.         when 'Move', 'Moves' # 移动事件,等待移动结束
  202.           if para1 =~ /\-*\d+/
  203.             id = para1.to_i
  204.           else
  205.             add(355, 0,
  206.               ["s = $game_map.events.select{|id, e| e.name == '#{para1}'}"]
  207.             )
  208.             add(655, 0, ["@list[@index + 1].parameters[0] = s.keys[0]"])
  209.           end
  210.           route = add_moveroute(para2, cmd == 'Move')
  211.           add(205, 0, [id, route])
  212.         when 'PlaySE'
  213.           add(250, 0, [RPG::SE.new(para1, para2.to_i, 100)])
  214.         when 'Dialog'         
  215.           case para1
  216.           when 'change-skin'
  217.             id = Window_Message::Skin[:var]
  218.             add(122, 0, [id, id, 0, 4, para2])
  219.           end
  220.         end
  221.       # ------------------------------------------------------------
  222.       # [label](script) 条件跳转
  223.       # ------------------------------------------------------------
  224.       when /^\[(.+)\]\((.+)\)/
  225.         label, script = $1, $2
  226.         add(111, 0, [12, script])
  227.         add(119, 1, [label])
  228.         add(0, 1, [])
  229.         add(412, 0, [])
  230.         add(118, 0, ['#' + label])
  231.         @keys << label
  232.       # ------------------------------------------------------------
  233.       # [code][paras] 直接执行命令:
  234.       # [201] [0, map, x, y, 0, 0] 切换地图
  235.       # [214] [] 暂时消除事件
  236.       # [213] [-1, 1, true] Balloon
  237.       # ...
  238.       # ------------------------------------------------------------
  239.       when /^\[(\d+)\] (\[.+\])/
  240.         add($1.to_i, 0, eval($2))
  241.       # ------------------------------------------------------------
  242.       # 表格的情况
  243.       # ------------------------------------------------------------
  244.       when /^.+\|/
  245.         ary = line.split('|')
  246.         if table_tag
  247.           table_rows << ary
  248.         else
  249.           if table_rows.empty?
  250.             table_rows << ary
  251.           elsif ['-'] * table_rows[0].size == ary
  252.             table_tag = true
  253.           else
  254.             table_rows.clear
  255.           end
  256.         end
  257.       # ------------------------------------------------------------
  258.       # 其他的情况
  259.       # ------------------------------------------------------------
  260.       else
  261.         # 移除行尾的 <br/> 或者 <br>
  262.         line.sub!(/<br\/*>$/, '')
  263.         # 转义 `` 中的内容
  264.         while line =~ /`(.*?)`/
  265.           line.sub! /`.*?`/, eval($1).to_s
  266.         end
  267.         # 视为普通的对话
  268.         if msg_tag
  269.           add(401, 0, [line])
  270.         else
  271.           msg_tag = true
  272.           add(101, 0, ['', 0, 1, 2])
  273.           add(401, 0, [line])
  274.         end
  275.       end
  276.     end
  277.     # 如果以选择项结尾
  278.     add_choices(choices, choice_ignore) if choice_tag
  279.     add_table(table_rows) if table_tag
  280.   end
  281.  
  282.   def add(*parameters)
  283.     @list << RPG::EventCommand.new(*parameters)
  284.   end
  285.  
  286.   def add_choices(choices, choice_ignore)
  287.     @keys.concat(choices)
  288.     choice_ignore.each do |i|
  289.       choices[i] = nil
  290.     end
  291.     choices.compact!
  292.     add(102, 0, [choices, 5])
  293.     choices.each_index do |i|      
  294.       add(402, 0, [i, choices[i]])
  295.       add(119, 1, [choices[i]])
  296.       add(0, 1, [])
  297.     end
  298.     add(403, 0, [])
  299.     add(0, 1, [])
  300.     add(404, 0, [])
  301.   end
  302.  
  303.   def add_table(rows)
  304.     case rows[0][0]
  305.     when 'Shop'
  306.       add(302, 0, [-1, 0, 0, 0, rows[0][1] =~ /^P/])
  307.       rows.each do |paras|
  308.         paras[2] = (paras[3] == '-') ? 0 : 1
  309.         add(605, 0, paras.map{|x| x.to_i})
  310.       end
  311.     end
  312.   end
  313.  
  314.   def add_moveroute(string, wait = false)
  315.     route = RPG::MoveRoute.new
  316.     route.repeat = false
  317.     route.skippable = true
  318.     route.wait = wait
  319.     route.list.clear
  320.     list = []
  321.     string.split(' ').each do |c|
  322.       case c
  323.       # move
  324.       when /w(\d+)/ then list.concat([4, []] * $1.to_i) # move up
  325.       when /a(\d+)/ then list.concat([2, []] * $1.to_i) # move left
  326.       when /s(\d+)/ then list.concat([1, []] * $1.to_i) # move down
  327.       when /d(\d+)/ then list.concat([3, []] * $1.to_i) # move right
  328.       when /f(\d+)/ then list.concat([12, []] * $1.to_i) # move forward
  329.       when /b(\d+)/ then list.concat([13, []] * $1.to_i) # move backward
  330.       when /j(.+)/ then list << [14, $1.split('.').map{|i| i.to_i}] # jump
  331.       when /J(.+)/ then list << [45, ["moveto #{$1.sub('.', ',')}"]] # move to
  332.       # turn
  333.       when 'W' then list << [19, []] # turn up
  334.       when 'A' then list << [17, []] # turn left
  335.       when 'S' then list << [16, []] # turn down
  336.       when 'D' then list << [18, []] # turn right
  337.       when 'L' then list << [21, []] # turn left 90
  338.       when 'R' then list << [20, []] # turn right 90
  339.       when 'B' then list << [22, []] # turn 180
  340.       when 'FIX' then list << [35, []] # fix direction on
  341.       when 'fix' then list << [36, []] # fix direction off
  342.       # move setting
  343.       when 'PASS' then list << [37, []] # pass through on
  344.       when 'pass' then list << [38, []] # pass through off
  345.       when /v(\d+)/ then list << [29, [$1.to_i]] # change speed
  346.       when /V(\d+)/ then list << [30, [$1.to_i]] # change frequency
  347.       when 'STEP' then list << [33, []] # step animation on
  348.       when 'step' then list << [33, []] # step animation off
  349.       # opacity
  350.       when 'T' then list << [39, []] # transparent on
  351.       when 't' then list << [40, []] # transparent off
  352.       when /o(\d+)/ then list << [42, [$1.to_i]] # opacity
  353.       # other
  354.       when /W(\d+)/ then list << [15, [$1.to_i]] # wait
  355.       when /X(\d+)/ then list << [27, [$1.to_i]] # switch on
  356.       when /x(\d+)/ then list << [28, [$1.to_i]] # switch off
  357.       end
  358.     end
  359.     list << [0, []]
  360.     list.each do |code, paras|
  361.       route.list << RPG::MoveCommand.new(code, paras)
  362.     end
  363.     route
  364.   end
  365. end

评分

参与人数 1+1 收起 理由
百里_飞柳 + 1 精品文章

查看全部评分

熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* 基于SAE的服务器 *
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
2980
在线时间
2255 小时
注册时间
2015-8-25
帖子
939

开拓者

发表于 2018-7-25 21:23:25 | 显示全部楼层
类似的脚本有用过下,hime的TES。
我觉得学起来是挺麻烦的,主要是新的编辑法则。。。

但是这个脚本本身是带有加密的,我就看到过隔壁的汉化组在这个的解包上焦头烂额。。。
虽然现在已经有专用的提取工具了。

点评

http://hime.be/rgss3/tes.html 这么晚都没睡啊  发表于 2018-7-26 05:28
能给个链接么~我参考一下  发表于 2018-7-26 01:27
回复 支持 反对

使用道具 举报

Lv5.捕梦者

老鹰

梦石
28
星屑
7182
在线时间
3786 小时
注册时间
2012-5-26
帖子
1988

短篇十吟唱者组别冠军开拓者剧作品鉴家

发表于 2018-7-25 21:37:01 | 显示全部楼层
之前就一直觉得在编辑器里定义事件真的蜜汁心累,尤其是移动路径(虽然之后接入了一个自动寻路+等待结束偷懒)
而且默认的事件编辑器对于文本的翻译也非常不友好

不过这一套Markdown类似语法的编辑,其实有时候也有点容易懵……?
如果支持把事件页再导出成Markdown就更好了(做梦.jpg)

对于那个显示选项并跳转标签,之后就直接事件中断了??
大概可以改成在每个选项后加一个跳转到所有选项分歧结束的地方?
可以加个特定 0. 序号用于处理所有选项的结束位置?不过还一个取消时的默认分支没办法啊
——对不起...但我,不能辜负老师最后的期望!
——那我就将这一切,一并斩断;给所有人,尤其是你,一个启明的时代!
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
8999
在线时间
1234 小时
注册时间
2011-7-8
帖子
1957

开拓者

 楼主| 发表于 2018-7-26 01:21:04 | 显示全部楼层
本帖最后由 guoxiaomi 于 2018-7-26 01:26 编辑
百里_飞柳 发表于 2018-7-25 21:37
之前就一直觉得在编辑器里定义事件真的蜜汁心累,尤其是移动路径(虽然之后接入了一个自动寻路+等待结束偷 ...


把事件转化成markdown指令也是可行的,但是格式如果不是按照约定好的话,会有很多麻烦。我这里的设定是markdown里的每一个小节都是以标签开始,以中断事件结束。这是为了结构简单,故意做成小节之间无嵌套关系,但是选择项就不好实现。我这里只好是选项默认跟一个同名的标签跳转。

其实主要还是看个人的约定了~

关于取消,在选项后面还是可以添加内容的,如果取消就执行这之后的内容。也可以在这之后跳转到别的标签。类似下面这样~

  1. ## 主线
  2. 对话1
  3. 1. 跳转1
  4. 2. 跳转2

  5. <JumpTo@跳转3>

  6. ### 跳转1
  7. <JumpTo@跳转3>

  8. ### 跳转2
  9. <JumpTo@跳转3>

  10. ### 跳转3
复制代码

点评

其实我想的是,对于部分比较难排版的指令,直接转回原事件编辑器里改算了,然后导出时就导出成事件指令对象的参数组……  发表于 2018-7-26 11:42
熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* 基于SAE的服务器 *
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
2980
在线时间
2255 小时
注册时间
2015-8-25
帖子
939

开拓者

发表于 2018-7-26 07:51:02 | 显示全部楼层
本帖最后由 七重 于 2018-7-26 09:36 编辑

不过说到这样类似的需求。我是觉得如果有个更加易用的事件编辑器就好了。

比如自己用rm的时候,有时候就会出现变量的编号定好了之后,后面才发现又要加新的,最后东++,西++,结果越来越混乱。。又不能排列整理。
因为编号是固定的,如果要改又要把事件里面的引用修正。

tes算是把事件写在了外面,要调用的时候直接调用相关名字。
如果楼主是做的话,能再做个在外部安排开关与变量就好了。


追记:
过了一会之后。。我才忽然发现这个问题可以靠自力解决。。

点评

开关和变量的名字应该存在了system.rvdata2里,剧本里直接写变量名就好了,然后跟着名字去找对应变量?  发表于 2018-7-26 11:05
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
8999
在线时间
1234 小时
注册时间
2011-7-8
帖子
1957

开拓者

 楼主| 发表于 2018-7-26 16:56:50 | 显示全部楼层
百里_飞柳 发表于 2018-7-25 21:37
之前就一直觉得在编辑器里定义事件真的蜜汁心累,尤其是移动路径(虽然之后接入了一个自动寻路+等待结束偷 ...

其实写一个脚本把事件编辑器里的指令数组导出来也很容易的,我做的时候就写了一个,要不然搞不清楚它是怎么实现的。但是并不是很赞同把数据分开保存在事件编辑器和剧情文本文件里。

一开始确实想着,外挂的文本只是动态替代事件页中的一部分。事件在生成@list时,原有的内容保持不变,外挂的文本所占的位置被替换插入到@list中,如此可做到最大兼容。

做了一点后,觉得这里其实是不需要考虑兼容性的。事件指令都比较简单,复杂的部分主要是循环和分歧。实际操作的时候:
1. 循环和分歧用到的时候比较少,尤其是循环
2. 有时候,循环和分歧不是“最优解”,而是rm事件页这个框架下的无奈之举
3. 利用标签和跳转可以完全替代循环和分歧,但管理混乱+事件页太长

所以未必一定要坚持照着rm的事件框架去写,把常用的几种命令和rm事件框架恰当的结合更好。所以我选择了标签跳转。

全部的内容写到外挂文本里还有一个好处是内容统一管理,工程里放一点,外挂文本里放一点不太好。

我还提供了一个方案是随意书写指令,仍然限定了indent = 0:
  1. [cmd] [paras]
复制代码

用的eval执行后面的中括号的内容生成参数数组。

点评

那个突然又想起来了,这里面可以调用公共事件的,觉得不好写的可以放到公共事件里  发表于 2018-7-28 20:48
比如我这里把所有的地图传送写到了一个事件里,传送的代码就是像这样 [201] [0, 5, 9, 38, 8, 0]  发表于 2018-7-26 21:32
其实我想表达的就是任意指令转换的通用接口XD,毕竟Markdown语法还是比较少的,而事件指令各种奇怪的参数传入,一些麻烦的不如直接事件编辑器写了  发表于 2018-7-26 18:35
熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* 基于SAE的服务器 *
回复 支持 反对

使用道具 举报

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

本版积分规则

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

站长信箱:fux2@moe9th.com|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2019-1-19 20:56

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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