Paradox的数据文件格式

Paradox 是我很喜欢的一个游戏公司,在所谓 P 社 5 萌中,十字军之王和钢铁雄心都只有浅尝,但在维多利亚和群星上均投入了大量时间和精力。 这些游戏基于同一套引擎,所以数据文件格式也是共通的。P 社开放了 Mod ,允许玩家来修改游戏,所以数据文件都是明文文本存放在文件系统中,这给了我们一个极好的学习机会:对于游戏从业者,我很有兴趣看看成熟引擎是如何管理游戏数据和游戏逻辑的。

据我所接触到的国内游戏公司,包括我们自己公司在内,游戏数据大都是基于 excel 这种二维表来表达的。我把它称为 csv 模式。这种模式的特点是,基础数据结构基于若干张二维表,每张表有不确定的行数,但每行有固定了列数。用它做基础数据结构的缺陷是很明显的,比如它很难表达树状层级结构。这往往就依赖做一个中间层,规范一些使用格式,在其上模拟出复杂数据结构。

另一种在软件行业广泛使用的基础数据结构是 json/xml 模式。json 比 xml 要简单。它的特点就是定义了两种基础的复合结构,字典和数组,允许结构嵌套。基于这种模式管理游戏数据的我也见过一些。不过对于策划来说,编辑树结构的数据终究不如 excel 拉表方便。查看起来也没有特别好的可视化工具,所以感觉用的人要少一些。

最开始,我以为 P 社的数据文件是偏向于后一种 json 模式。但实际研究下来又觉得有很大的不同。今天我尝试用 lpeg 写了一个简单的 parser 试图把它读进 lua vm ,写完 parser 后突然醒悟过来,其实它就是基于的嵌套 list ,不正是 lisp 吗?想明白这点后,有种醍醐灌顶的感觉,的确 lisp 模式要比 json 模式简洁的多,并不比 csv 模式复杂。但表达能力却强于它们两者,的确是一个更好的数据组织方案。

我们来看一个从群星中随便摘录的例子(有点长,但挺有代表性):

 
 
 
  1. country_event = { 
  2.     id = primitive.16 
  3.     hide_window = yes 
  4.  
  5.     trigger = { 
  6.         is_country_type = primitive 
  7.         has_country_flag = early_space_age 
  8.         #NOT = { has_country_flag = recently_advanced } 
  9.         OR = { 
  10.             AND = { 
  11.                 exists = from 
  12.                 from = { 
  13.                     OR = { 
  14.                         is_country_type = default 
  15.                         is_country_type = awakened_fallen_empire 
  16.                     } 
  17.                 } 
  18.             } 
  19.             years_passed > 25 
  20.         } 
  21.     } 
  22.  
  23.     mean_time_to_happen = { 
  24.         years = 100 
  25.  
  26.         modifier = { 
  27.             factor = 0.6 
  28.             has_country_flag = acquired_tech 
  29.         } 
  30.     } 
  31.  
  32.     immediate = { 
  33.         remove_country_flag = early_space_age 
  34.         set_country_flag = primitives_can_into_space 
  35.         set_country_type = default 
  36.         change_country_flag = random 
  37.         if = { 
  38.             limit = { is_species_class = MAM } 
  39.             set_graphical_culture = mammalian_01 
  40.         } 
  41.         if = { 
  42.             limit = { is_species_class = REP } 
  43.             set_graphical_culture = reptilian_01 
  44.         } 
  45.         if = { 
  46.             limit = { is_species_class = AVI } 
  47.             set_graphical_culture = avian_01 
  48.         } 
  49.         if = { 
  50.             limit = { is_species_class = ART } 
  51.             set_graphical_culture = arthropoid_01 
  52.         } 
  53.         if = { 
  54.             limit = { is_species_class = MOL } 
  55.             set_graphical_culture = molluscoid_01 
  56.         } 
  57.         if = { 
  58.             limit = { is_species_class = FUN } 
  59.             set_graphical_culture = fungoid_01 
  60.         } 
  61.         change_government = { 
  62.             authority = random 
  63.             civics = random 
  64.         } 
  65.         set_name = random 
  66.         if = { 
  67.             limit = { 
  68.                 home_planet = { 
  69.                     has_observation_outpost = yes 
  70.                 } 
  71.             } 
  72.             home_planet = { 
  73.                 observation_outpost_owner = { 
  74.                     country_event = { id = primitive.17 } 
  75.                 } 
  76.             } 
  77.         } 
  78.         add_minerals = 1000 # enough for a spaceport and then some 
  79.         add_energy = 500 
  80.         add_influence = 300 
  81.         capital_scope = { 
  82.             every_tile = { 
  83.                 limit = { 
  84.                     has_blocker = yes 
  85.                     NOR = { 
  86.                         has_blocker = tb_decrepit_dwellings 
  87.                         has_blocker = tb_failing_infrastructure 
  88.                     } 
  89.                 } 
  90.                 remove_blocker = yes 
  91.             } 
  92.             while = { 
  93.                 limit = {  
  94.                     num_pops < 8 
  95.                     any_tile = { 
  96.                         has_grown_pop = no 
  97.                         has_growing_pop = no 
  98.                         has_blocker = no 
  99.                     } 
  100.                 } 
  101.                 random_tile = { 
  102.                     limit = { 
  103.                         has_grown_pop = no 
  104.                         has_growing_pop = no 
  105.                         has_blocker = no 
  106.                     } 
  107.                     create_pop = { 
  108.                         species = owner 
  109.                     } 
  110.                 } 
  111.             } 
  112.             random_tile = { 
  113.                 limit = { 
  114.                     has_grown_pop = yes 
  115.                     OR = { 
  116.                         has_building = "building_primitive_farm" 
  117.                         has_building = "building_primitive_factory" 
  118.                         has_building = no 
  119.                     } 
  120.                 } 
  121.                 clear_deposits = yes 
  122.                 add_deposit = d_mineral_food_deposit 
  123.                 set_building = "building_capital_2" 
  124.             } 
  125.             random_tile = { 
  126.                 limit = { 
  127.                     has_grown_pop = yes 
  128.                     OR = { 
  129.                         has_building = "building_primitive_farm" 
  130.                         has_building = "building_primitive_factory" 
  131.                         has_building = no 
  132.                     } 
  133.                 } 
  134.                 clear_deposits = yes 
  135.                 add_deposit = d_mineral_deposit 
  136.                 set_building = "building_mining_network_1" 
  137.             } 
  138.             random_tile = { 
  139.                 limit = { 
  140.                     has_grown_pop = yes 
  141.                     OR = { 
  142.                         has_building = "building_primitive_farm" 
  143.                         has_building = "building_primitive_factory" 
  144.                         has_building = no 
  145.                     } 
  146.                 } 
  147.                 clear_deposits = yes 
  148.                 add_deposit = d_mineral_deposit 
  149.                 set_building = "building_mining_network_1" 
  150.             } 
  151.             random_tile = { 
  152.                 limit = { 
  153.                     has_grown_pop = yes 
  154.                     OR = { 
  155.                         has_building = "building_primitive_farm" 
  156.                         has_building = "building_primitive_factory" 
  157.                         has_building = no 
  158.                     } 
  159.                 } 
  160.                 clear_deposits = yes 
  161.                 add_deposit = d_farmland_deposit 
  162.                 set_building = "building_hydroponics_farm_1" 
  163.             } 
  164.             random_tile = { 
  165.                 limit = { 
  166.                     has_grown_pop = yes 
  167.                     OR = { 
  168.                         has_building = "building_primitive_farm" 
  169.                         has_building = "building_primitive_factory" 
  170.                         has_building = no 
  171.                     } 
  172.                 } 
  173.                 clear_deposits = yes 
  174.                 add_deposit = d_farmland_deposit 
  175.                 set_building = "building_hydroponics_farm_1" 
  176.             } 
  177.             random_tile = { 
  178.                 limit = { 
  179.                     has_grown_pop = yes 
  180.                     OR = { 
  181.                         has_building = "building_primitive_farm" 
  182.                         has_building = "building_primitive_factory" 
  183.                         has_building = no 
  184.                     } 
  185.                 } 
  186.                 clear_deposits = yes 
  187.                 add_deposit = d_energy_deposit 
  188.                 set_building = "building_power_plant_1" 
  189.             } 
  190.             random_tile = { 
  191.                 limit = { 
  192.                     has_grown_pop = yes 
  193.                     OR = { 
  194.                         has_building = "building_primitive_farm" 
  195.                         has_building = "building_primitive_factory" 
  196.                         has_building = no 
  197.                     } 
  198.                 } 
  199.                 clear_deposits = yes 
  200.                 add_deposit = d_energy_deposit 
  201.                 set_building = "building_power_plant_1" 
  202.             } 
  203.             random_tile = { 
  204.                 limit = { 
  205.                     has_grown_pop = yes 
  206.                     OR = { 
  207.                         has_building = "building_primitive_farm" 
  208.                         has_building = "building_primitive_factory" 
  209.                         has_building = no 
  210.                     } 
  211.                 } 
  212.                 clear_deposits = yes 
  213.                 add_deposit = d_energy_deposit 
  214.                 set_building = "building_power_plant_1" 
  215.             } 
  216.             remove_all_armies = yes 
  217.             create_army = { 
  218.                 name = random 
  219.                 owner = PREV 
  220.                 species = owner_main_species 
  221.                 type = "defense_army" 
  222.             } 
  223.             create_army = { 
  224.                 name = random 
  225.                 owner = PREV 
  226.                 species = owner_main_species 
  227.                 type = "defense_army" 
  228.             } 
  229.             create_army = { 
  230.                 name = random 
  231.                 owner = PREV 
  232.                 species = owner_main_species 
  233.                 type = "defense_army" 
  234.             } 
  235.             create_army = { 
  236.                 name = random 
  237.                 owner = PREV 
  238.                 species = owner_main_species 
  239.                 type = "defense_army" 
  240.             } 
  241.         } 
  242.         random_owned_ship = { 
  243.             limit = { is_ship_size = primitive_space_station } 
  244.             fleet = { destroy_fleet = THIS } 
  245.         } 
  246.     } 

起初,我很疑惑在这个格式中,为啥赋值和相等都用的 = ,这不是容易引起歧义么?但是你从 lisp 的角度来看就简单了。等于号只是为了便于策划书写和阅读的一个变形。所谓 id = primitive.16 你可以理解为 ( id, primitive.16 ) 而 iscountrytype = default 一样可以理解为 ( iscountrytype , default ) 。 而

 
 
 
  1. create_army = { 
  2.                 name = random 
  3.                 owner = PREV 
  4.                 species = owner_main_species 
  5.                 type = "defense_army" 
  6.             } 

本质上是 ( create_army , ( ( name, random ) , (owner, PREV), (species, owner_main_species), (type, "defense_army") ) )。

基础数据结构只要能表达出来,怎么理解这些 list 是更上层的工作,这就和我们在 csv 中去模拟树结构是一样的道理。只不过 years_passed > 25 这样的东西,被翻译成 ( years_passed, > , 25 ) 有三个元素。上层解析的时候,如果确定它是一个逻辑表达式,就很容易在 2 个元素的 list 中间插入一个 = 补全。

这种结构很容易描述一些控制结构,比如上面例子中的 if 。我还在其它数据中发现了 repeat while 等控制结构,这些都是上层的工作,和底层数据模型无关。但不得不说,lisp 模式比 csv 模式更容易做此类控制结构。

把这种数据结构翻译成 lua 也很容易:只需要用 lua table 的 array 来保存即可。但为了使用方便,可以加一个代理结构。如果上层业务想把一个 list 解析成字典,就在 cache 中临时生成一个 hash 表加快查询即可。我们甚至可以把它直接存在 C 内存中,只在 lua 中暴露出遍历以及高层的访问方法。所谓高层的访问方法指,可以直接读取 if repeat 等控制结构,或是把带 AND OR 这样的复合 list 直接翻译成一个条件表达式。

原文链接:https://blog.codingnow.com/2017/07/paradox_data_format.html#more

【本文为专栏作者“云风”的原创稿件,转载请通过联系原作者获取授权】

戳这里,看该作者更多好文

名称栏目:Paradox的数据文件格式
当前URL:http://www.shufengxianlan.com/qtweb/news14/467664.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联