当前位置:首页 > 系统运维

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 模式复杂。但表达能力却强于它们两者,的确是一个更好的数据组织方案。

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

country_event = {      id = primitive.16     hide_window = yes     trigger = {          is_country_type = primitive         has_country_flag = early_space_age         #NOT = {  has_country_flag = recently_advanced }         OR = {              AND = {                  exists = from                 from = {                      OR = {                          is_country_type = default                         is_country_type = awakened_fallen_empire                     }                 }             }             years_passed > 25         }     }     mean_time_to_happen = {          years = 100         modifier = {              factor = 0.6             has_country_flag = acquired_tech         }     }     immediate = {          remove_country_flag = early_space_age         set_country_flag = primitives_can_into_space         set_country_type = default         change_country_flag = random         if = {              limit = {  is_species_class = MAM }             set_graphical_culture = mammalian_01         }         if = {              limit = {  is_species_class = REP }             set_graphical_culture = reptilian_01         }         if = {              limit = {  is_species_class = AVI }             set_graphical_culture = avian_01         }         if = {              limit = {  is_species_class = ART }             set_graphical_culture = arthropoid_01         }         if = {              limit = {  is_species_class = MOL }             set_graphical_culture = molluscoid_01         }         if = {              limit = {  is_species_class = FUN }             set_graphical_culture = fungoid_01         }         change_government = {              authority = random             civics = random         }         set_name = random         if = {              limit = {                  home_planet = {                      has_observation_outpost = yes                 }             }             home_planet = {                  observation_outpost_owner = {                      country_event = {  id = primitive.17 }                 }             }         }         add_minerals = 1000 # enough for a spaceport and then some         add_energy = 500         add_influence = 300         capital_scope = {              every_tile = {                  limit = {                      has_blocker = yes                     NOR = {                          has_blocker = tb_decrepit_dwellings                         has_blocker = tb_failing_infrastructure                     }                 }                 remove_blocker = yes             }             while = {                  limit = {                       num_pops < 8                     any_tile = {                          has_grown_pop = no                         has_growing_pop = no                         has_blocker = no                     }                 }                 random_tile = {                      limit = {                          has_grown_pop = no                         has_growing_pop = no                         has_blocker = no                     }                     create_pop = {                          species = owner                     }                 }             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_mineral_food_deposit                 set_building = "building_capital_2"             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_mineral_deposit                 set_building = "building_mining_network_1"             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_mineral_deposit                 set_building = "building_mining_network_1"             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_farmland_deposit                 set_building = "building_hydroponics_farm_1"             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_farmland_deposit                 set_building = "building_hydroponics_farm_1"             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_energy_deposit                 set_building = "building_power_plant_1"             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_energy_deposit                 set_building = "building_power_plant_1"             }             random_tile = {                  limit = {                      has_grown_pop = yes                     OR = {                          has_building = "building_primitive_farm"                         has_building = "building_primitive_factory"                         has_building = no                     }                 }                 clear_deposits = yes                 add_deposit = d_energy_deposit                 set_building = "building_power_plant_1"             }             remove_all_armies = yes             create_army = {                  name = random                 owner = PREV                 species = owner_main_species                 type = "defense_army"             }             create_army = {                  name = random                 owner = PREV                 species = owner_main_species                 type = "defense_army"             }             create_army = {                  name = random                 owner = PREV                 species = owner_main_species                 type = "defense_army"             }             create_army = {                  name = random                 owner = PREV                 species = owner_main_species                 type = "defense_army"             }         }         random_owned_ship = {              limit = {  is_ship_size = primitive_space_station }             fleet = {  destroy_fleet = THIS }         }     } } 

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

create_army = {                  name = random                 owner = PREV                 species = owner_main_species                 type = "defense_army"             } 

本质上是服务器租用 ( 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

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

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

分享到:

滇ICP备2023006006号-16