| 赞 | 6 |
| VIP | |
| 好人卡 | |
| 积分 | 8 |
| 经验 | |
| 最后登录 | 2025-12-17 |
| 在线时间 | 71 小时 |
- 梦石
- 0
- 星屑
- 782
- 在线时间
- 71 小时
- 注册时间
- 2017-12-2
- 回帖
- 12
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有账号?注册会员
×
本帖最后由 KurozawaRuby 于 2021-3-15 00:31 编辑
很久之前就想写的一个东西,最近找到了历史遗留的老代码,看了十几秒后删了重写了一份,发上来给大家玩。
因为也没实现什么实际功能,也需要使用者有一定的脚本能力,就发个讨论吧。
我们经常会碰到需要为游戏系统中的角色,技能,物品等新增一些额外属性的时候,之前挺多脚本的做法是在备注区(note)走正则匹配,这样做每加一个属性就需要新增一个正则和一个读取属性的方法,比较麻烦。也有大大使用元编程等手段自动生成了这些代码,但作为一个稍懂一点ruby的码农,怎么能不造个自己的轮子呢?
于是就有了这个备注扩展脚本,它可以在备注栏定义值类型、函数类型和ruby原始代码等的变量,通过变量名的Symbol获取,帮助我们走出正则解析等的迷宫,专注于游戏系统内的逻辑实现。
备注扩展脚本会在加载数据库(DataManager.load_databas)完成后初始化备注扩展的属性,扩展的属性被追加到RPG::BaseItem, RPG::Map, RPG::Tileset这三个类中,你可以通过 ext(:name) 的方式去获取。
我们可以在备注中添加 '---' 分隔符来兼容其它的脚本,如果备注中包含以三个英文减号'---'开头的行,则会将其作为分隔行。默认情况下,脚本不会解析没有分隔行的备注。有分隔行时,脚本默认会解析第一个分隔行之前的部分,而将该分隔行之后的部分作为原始备注设置回去,因此其它脚本在运行时进行正则匹配,就无法感知到备注扩展的内容。当然,我们也可以通过修改脚本内设置的常量来改变这样(需要有分隔行才解析,解析分隔行之前的部分)的策略。
变量定义规则:
首先,以'#'号开头的,且不在多行解析范围内的行为注释行。
以赋值符号 '=' '=>' '=$' '=@' 为界,左边的作为变量名(不允许带空格),或函数定义解析。右边作为值解析,如果右边不为空,则视为单行定义。如果右边为空,则为视为多行定义,直到下一个空行(或分隔行)为止。
比如备注区备注
- name1 = test
- name2 =
- t
- e
- s
- t
- ---
- 原始注释
复制代码
上面的代码定义了name1和name2,值都是'test'(默认设定是删除多行文本间的换行符的,这个也可以修改常量配置),对应的data对象使用 ext(:name1) 就可以拿到它的值了,而其它脚本在运行时读取的注释,只有下面的“原始注释”了
脚本支持 值类型,函数类型,模板类型,原始ruby代码 四种定义,分别对应 '=' '=>' '=$' '=@' 四种符号。
值类型(=):
最简单的类型,直接写就行了,单行定义时会自适应数字(整数和浮点数)和字符串,不需要你自己转换了。字符串还可以支持"#{}"的表达式,不过表达式的值是通过eval在解析时直接确定的,所以不能通过ext方法访问krubynote的值或是游戏开关变量等全局变量的值了。如果你需要那样的功能,你可以使用模板(或者函数或者原始ruby,都是一样的,只是模板最简单)。
- 简介 = 这个物品的名字是#{name}
- 最大数量 = 100
- 百分比 = 20.0
- 整数部分为0的小数 = .2
- ---
- 原始注释
复制代码
函数类型(=>)
函数类型写法比较特殊,我是按照我自己写的比较偷懒的习惯,函数名+空格+参数列表空格分隔放在赋值符号"=>"的左边,右边放ruby代码,特殊情况是单行有参数的函数可以直接写等号,看起来更好看(多行或没有参数就只能写=>了,不然解析成值类型了)
- add a b = a + b
- fun => puts self
- condition user =>
- user.hp_rate > 20
- ---
- 原始注释
复制代码
函数类型定义拿到的是lambda,需要你传参调用(使用.call或[]调用)
比如上例调用时
- # 只做演示,真正使用时别忘了判空
- ext(:add).call(1, 2)
- ext(:fun)[]
- ext(:condition)[user]
复制代码
模板类型(=$):
运行时确定"#{}"表达式的值的字符串类型,最开始设计就是为了写复杂的简介,以下是例子:
- 简介 =$
- #{name}是比较强力的技能,冷却时间为#{ext(:cd)}回合,伤害公式为#{damage.formula}
- cd = 5
- ---
- 原始注释
复制代码
这样只要照着下面重写一下description方法,这个简介就生效了,你也可以就着F1文档补充一点代码,把其它的属性写全(当然,底下实现技能冷却时间还需要你自己再写写代码才行,而且默认简介框的大小。。。):
- class RPG::BaseItem
- def description
- ext(:简介) || @description
- end
- end
复制代码
原始ruby代码(=@):
最简单粗暴强力的功能,上面所有的功能都可以写ruby代码解决,但万一没注意漏个逗号什么的调起来报错信息会很蛋疼(函数类型也有类似问题)。
- list =@ [1,2,3]
- fun =@ -> { puts self }
- dict =@
- {
- a: "1",
- b: 2
- }
- ---
- 原始注释
复制代码
代码:
请注意根据自己的需要调整最开始4个常量哦。
最后一段重写DataManager.load_database时,最开始是直接用我之前整的after:类宏的,后来觉得发上来尽量减少依赖,就写成alias的形式了 https://rpg.blue/forum.php?mod=viewthread&tid=484700
[pre lang="ruby"]#encoding: utf-8
# -------------------------------------------------------------------
# - KRuby module by KurozawaRuby
# -------------------------------------------------------------------
module KRuby
end
# -------------------------------------------------------------------
# - KRuby::Note module
# - 备注扩展 by KurozawaRuby
# - 在备注内可按照格式方便地定义一些变量,变量支持数字、字符串、函数类型。
# - 在脚本内,备注变量内通过 RPG::BaseItem#ext 方法根据变量名获取。
# - 可以较好地兼容其他备注相关脚本,需要用户对脚本有一定了解。
# -------------------------------------------------------------------
# - 备注扩展框架以备注中第一个三个英文减号 '---' 开头的行作为分隔行,默认将
# - 分隔行之前的行作为扩展备注进行解析,分隔行之后的为原始备注以兼容。
# - 你可自行修改 KRuby::Note 下 USE_DEFAULT 和 APPEAR_START 等常量。
# -
# - 备注扩展变量在游戏初始化加载数据(DataManager.load_database)后解析。
# - 扩展变量定义的基本语句为:
# - 单行: <name>=<value>
# - 多行: <name>=\n(换行符
# - <multiline_value>\n(换行符
# - (空行
# - ex: num = 1
# - str = 这个物品的名字是#{name},它是...
# - test =
# - 测试
# - 多行
# -
# - 以第一个等号 '=' 为标志,等号之前的为变量名,不允许带空格(最好是英文数字
# - 下划线,因为代码中我们通过变量名Symbol访问),等号之后的:
# - 1. 有内容,为单行,先判断内容格式是否符合数字,如果是简单数字格式则自动
# - 转为浮点数或整数,如果没有则为字符串
# - 2. 无内容,为多行,扩展变量值为下行开始至第一个空行内的字符串内容。
# - * 等号左右可以加空格,不影响。
# - * 字符串不需要使用引号,且可以使用"#{}"表达式,self为该RPG::BaseItem对象
# -
# - 扩展变量共有三类 值类型:val 模板类型:tpl 函数类型:fun
# - 上述的基本语句定义的就是值类型的变量,它在解析完成后固定,因此在其中使用
# - "#{$game_variables[12]}"等语句无法达成在游戏运行时根据变量变化的效果,如
# - 果需要实现类似功能,你需要使用模板或函数。
# -
# - 模板类型:
# - 在等号之后添加'$'符号就可以定义模板('='和'$'之间不能有空格)同样支持多行,
# - 通过 ext 方法获取的值为字符串类型(在运行动态生成,可以使用全局变量)
# - ex: template =$ variable 14 = #{$game_variables[12]}
# -
# - 函数类型:
# - 在等号之后添加'>'符号就可以定义函数('='和'>'之间不能有空格)同样支持多行,
# - 函数定义(参数名和代码请符合ruby的规范):
# - <funcution_name> <param_1>... <param_n> => <function_body>
# - 函数名、参数列表按照空格分隔(注意参数列表也是空格分隔,而非逗号),放在
# - '=>' 符号的左边,符号的右边(多行的情况下同样为下一行至第一个空行)作为
# - ruby 代码解析成 lambda
# - 函数类型通过 ext 方法获取的值为 lambda 对象,必须手动传参调用。
# - * 单行函数在有参数的情况下,可以省略'>',写成更好看的形式:
# - ex: add a b = a + b
# - test => $game_variables[12] > 12
# - skill_condition user = user.hp_rate < 0.1
# -
# - 其它ruby类型(值类型:val的扩展):
# - 在等号之后添加'@'符号可以定义其它ruby类型的值,此时 '=@' 的右边(多行的
# - 情况下同样为下一行至第一个空行)作为 ruby 代码在解析时直接执行,self为
# - 该RPG::BaseItem对象
# - 通过 ext 方法获取的值为 ruby 代码执行的返回值。
# - 这样的写法有时候ruby代码写的语法不对就很难定位,请先在irb中测试好
# - 代码同样在 DataManager.load_database 后执行,与值类型:val相同
# -
# - ex: dict =@ { a:1, b:2 }
# - arr =@ [1, 'str', 2.0]
# -
# -------------------------------------------------------------------
module KRuby::Note
# -----------------------------------------------------------------
# - KRuby::Note::USE_DEFAULT
# - 备注里没有出现 '---' 分隔符的情况下
# - true: 使用 Note 解析备注
# - false: 不适用 Note
# -----------------------------------------------------------------
USE_DEFAULT = false
# -----------------------------------------------------------------
# - KRuby::Note::APPEAR_START
# - 备注出现 '---' 分隔符时
# - true: 解析第一个分隔符之前的部分
# - false: 解析第一个分隔符之后的部分
# -----------------------------------------------------------------
APPEAR_START = true
# -----------------------------------------------------------------
# - KRuby::Note::DEL_LINE_SEP
# - 删除多行文本的换行符
# -----------------------------------------------------------------
DEL_LINE_SEP = true
# -----------------------------------------------------------------
# - 在同一个备注里重复定义相同的名字时报错(false时覆盖先前的)
# -----------------------------------------------------------------
FAIL_ON_REDEF = true
# -----------------------------------------------------------------
# - KRuby::Note::Parser 备注解析器
# - 使用:
# - # item 为 RPG::BaseItem 等包含备注的类的对象
# - Parser.new(item).do_final
# -----------------------------------------------------------------
class Parser
def initialize(item)
@item = item
@data = {}
@org = ''
@state = KRuby::Note::APPEAR_START ? :ext : :org
@line = 0
@def_line = 0
@after_sep = false
@name = nil
@val = ''
@type = nil
end
def << (str)
@line += 1
case @state
when :ext
on_ext(str)
when :org
on_org(str)
when :next
on_next(str)
else
raise "在第#{@line}行,解析器的状态[#{state}]未定义"
end
end
def do_final
on_final
[@org, @data]
end
private
def on_org(str)
return on_sep(:ext) if str.start_with?('---') && !@after_sep
@org += str + "\n"
end
def on_ext(str)
@def_line = @line
return on_sep(:org) if str.start_with?('---') && !@after_sep
on_ext_real(str)
end
def on_sep(state)
return if @after_sep
@after_sep = true
@state = state
end
def on_ext_real(str)
return if str.empty?
str = str.lstrip
return if str.start_with?('#')
idx = str.index('=')
raise "无法解析第#{@line}行:非注释行且没有找到'='" unless idx
@name = str[0...idx].strip
str = str[idx+1..-1]
case str[0]
when '>'
@val = str[1..-1]
@type = :fun
when '$'
check_name
@val = str[1..-1].lstrip
@type = :tpl
when '@'
check_name
@val = str[1..-1]
@type = :ruby
else
@val = str.lstrip
@type = :val
end
if @val.empty?
@val = ''
@state = :next
return
end
if @name.index(' ')
@type = :fun
end
parse_new_val(true)
end
def on_next(str)
if str.start_with?('---') && !@after_sep
parse_new_val
return on_sep(:org)
end
return parse_new_val if str.empty?
@val += str
@val += "\n" if @type == :fun || !KRuby::Note::DEL_LINE_SEP
end
def check_name
if @name.index(' ')
raise "无法解析第#{@line}行:变量名[#{@name}]包含空格"
end
end
def parse_new_val(single_line = false)
@state = :ext
case @type
when :fun
new_fun
when :val
single_line ? new_val_single : new_val
when :ruby
new_val(@item.instance_eval(@val))
when :tpl
new_tpl
else
raise "无法解析第#{@line}行:未知的类型[#{@type}]"
end
reset
end
def new_fun
fns = @name.split(' ')
name = fns[0]
params = fns[1..-1]
codes = @val
@name = name.to_sym
new_ext(create_lambda(params, codes), :fun)
end
def new_val(val = nil)
unless val
val = calc_val
end
new_ext(val, :val)
end
def new_val_single
if @val =~ /^(\d*(\.?)\d+)$/
new_val($2.empty? ? $1.to_i : $1.to_f)
else
new_val
end
end
def new_tpl
tpl = eval("proc { \n#{tpl_code(@val)}\n }")
new_ext(-> { @item.instance_eval(&tpl) }, :tpl)
end
def calc_val
if @val.index('#{')
@item.instance_eval(tpl_code(@val))
else
@val
end
end
def reset
@name = nil
@val = ''
@type = nil
end
def new_ext(value, type)
@name = @name.to_sym
info = KRuby::Note::ExtInfo.new(@name, value, type)
old = @data[@name]
if old
on_redef
end
@data[@name] = info
end
def on_redef
raise "已经定义过的变量名#{@name}在第#{@def_line}行被重新定义" if KRuby::Note::FAIL_ON_REDEF
end
def on_final
parse_new_val if @name
end
def create_lambda(params, codes)
eval "-> #{params.join(', ')} {\n#{codes}\n}"
end
def tpl_code(txt)
%Q(<<-hIRJi5_FOSy___\n#{txt}\nhIRJi5_FOSy___\n.chomp)
end
end
# -----------------------------------------------------------------
# - KRuby::Note::ExtInfo 备注扩展信息
# - name 扩展的字段名, Symbol, 请尽量使用小写字母和下划线
# - type 扩展字段的类型, Symbol, 枚举值:
# - :val 已经计算出来的值类型
# - :tpl 模板类型
# - :fun 函数类型
# - value 扩展字段的值 值类型时是 String|Fixnum|Float, 否则是 Lambda
# -----------------------------------------------------------------
class ExtInfo
attr_reader :name, :type
def initialize(name, value, type=:val)
@name = name
@value = value
@type = type
end
# ---------------------------------------------------------------
# - 获取 value 模板类型需要调用 Lambda 计算结果
# ---------------------------------------------------------------
def value
type == :tpl ? @value[] : @value
end
end
# -----------------------------------------------------------------
# - KRuby::Note::DataExt 扩展模块
# - 用于混入 RPG::BaseItem 等有备注 @note 的类
# - 提供 init 和 ext 两个方法
# -----------------------------------------------------------------
module DataExt
def init
@exts = {}
return unless @note
return if @note.empty?
begin
org, result = parse_note
@note = org
@exts = result
rescue => e
# TODO KRuby.puts_err e
raise "处理[#{self.class}|id:#{@id}]的备注时出现错误: #{e}"
end
end
def ext(name)
item = @exts[name]
item ? item.value : nil
end
private
def parse_note
lines = @note.split("\n")
has_sep = lines.any? { |line| line.start_with?('---') }
return [@note, {}] unless has_sep || KRuby::Note::USE_DEFAULT
p = KRuby::Note::Parser.new(self)
lines.each { |line| p << line.chomp }
p.do_final
end
[
RPG::BaseItem,
RPG::Map,
RPG::Tileset,
].each do |clazz|
clazz.send :include, self
end
end
end
# -----------------------------------------------------------------
# - DataManager load_database方法加钩子
# - 用于初始化备注扩展变量
# -----------------------------------------------------------------
class << DataManager
alias :load_database_krubynote :load_database
def load_database
load_database_krubynote
puts "> 开始解析krubynote"
start = Time.now
[
$data_actors,
$data_classes,
$data_skills,
$data_items,
$data_weapons,
$data_armors,
$data_enemies,
$data_states,
$data_tilesets
].each do |arr|
arr.compact.each(&:init)
end
ms = (Time.now - start) * 1000
puts "> krubynote解析完成,耗时 #{ms.to_i}ms"
puts $data_actors[1].ext(:desc)
end
end[/pre]
赠品:
使用这个库,整一个自动切换状态的被动技能吧:
(老代码警告,意义不明的写法警告)
- # 技能自动状态的简单逻辑,依赖 KRuby::Note
- class Game_Battler
- alias refresh_org_autoswitch_skill refresh
- def refresh
- refresh_org_autoswitch_skill
- skills.each { |s| switch_auto_skill(s) }
- end
-
- private
- def switch_skill(id, state)
- @skill_switches ||= {}
- last = @skill_switches[id]
- @skill_switches[id] = state
- state ^ last
- end
- def switch_auto_skill(skill, init = false)
- fn = skill.ext(:switch_condition)
- return unless fn
- res = fn[self]
- return unless switch_skill(skill.id, res) || init
- ids = skill.ext(:switch_states)
- return unless ids
- ids.each do |id|
- next if id == 0
- add = id > 0
- id = -id unless add
- add ^ res ? remove_state(id) : add_state(id)
- end
- end
- end
- class Game_Actor
- alias learn_skill_org_autoswitch_skill learn_skill
- def learn_skill(skill_id)
- learn_skill_org_autoswitch_skill(skill_id)
- switch_auto_skill($data_skills[skill_id], true)
- end
- end
复制代码
使用:技能里备注
顺便:
模板类型和带#{}符号的值类型会使用HEREDOC的模板代码进行eval,使用的标志为 'hIRJi5_FOSy___' 所有多行文本内出现内容为 'hIRJi5_FOSy___' 的行时解析会出问题(我感觉是不可能出现,但是还是说一声吧)。
|
评分
-
查看全部评分
|