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

Project1

 找回密码
 注册会员
搜索

剧本系统-剧情内容写在游戏外

查看数: 3847 | 评论数: 6 | 收藏 6
关灯 | 提示:支持键盘翻页<-左 右->
    组图打开中,请稍候......
发布时间: 2018-7-25 20:19

正文摘要:

本帖最后由 guoxiaomi 于 2018-7-26 01:29 编辑 摘要 RM系列制作的游戏,其数据是以ruby marshal的格式保存。此类数据不能直观的阅读。本文提供了一个“剧情系统”的初步实现:即将剧情文件放在游戏工程之外,在 ...

回复

guoxiaomi 发表于 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
七重 发表于 2018-7-26 07:51:02
本帖最后由 七重 于 2018-7-26 09:36 编辑

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

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

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


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

点评

开关和变量的名字应该存在了system.rvdata2里,剧本里直接写变量名就好了,然后跟着名字去找对应变量?  发表于 2018-7-26 11:05
guoxiaomi 发表于 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
百里_飞柳 发表于 2018-7-25 21:37:01
之前就一直觉得在编辑器里定义事件真的蜜汁心累,尤其是移动路径(虽然之后接入了一个自动寻路+等待结束偷懒)
而且默认的事件编辑器对于文本的翻译也非常不友好

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

对于那个显示选项并跳转标签,之后就直接事件中断了??
大概可以改成在每个选项后加一个跳转到所有选项分歧结束的地方?
可以加个特定 0. 序号用于处理所有选项的结束位置?不过还一个取消时的默认分支没办法啊
七重 发表于 2018-7-25 21:23:25
类似的脚本有用过下,hime的TES。
我觉得学起来是挺麻烦的,主要是新的编辑法则。。。

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

点评

http://hime.be/rgss3/tes.html 这么晚都没睡啊  发表于 2018-7-26 05:28
能给个链接么~我参考一下  发表于 2018-7-26 01:27
guoxiaomi 发表于 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 精品文章

查看全部评分

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

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

GMT+8, 2024-11-21 22:26

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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