IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Paradox 的数据文件格式

云风的 BLOG 2017-10-15 09:57:25 累计浏览 1,401 次
本机暂存

   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 直接翻译成一个条件表达式。

同分类推荐文章

  1. 使用deepseek进行Oracle恢复,引起重大故障 (2026-06-22 10:56:00)
  2. 接手一个只差临门一脚的数据库恢复 (2026-06-18 00:13:09)
  3. 我做了一个 AI 版的 StarRocks 升级风险扫描工具,直接帮我定位到一个风险 (2026-06-15 01:00:00)

查看更多 数据库 文章 →

建议继续学习

  1. Nginx与Lua (累计阅读 5,672)
  2. Lua GC 的源码剖析 (2) (累计阅读 5,079)
  3. Ameba , 一个简单的 lua 多线程实现 (累计阅读 4,983)
  4. Lua GC 的源码剖析 (4) (累计阅读 4,776)
  5. Proto Buffers in Lua (累计阅读 4,404)
  6. Lua GC 的源码剖析 (1) (累计阅读 4,390)
  7. 一个 Lua 内存泄露检查工具 (累计阅读 4,010)
  8. Lua GC 的源码剖析 (6) 完结 (累计阅读 3,927)
  9. 回调还是消息队列 (累计阅读 3,898)
  10. RedBridge(redis的http接口) (累计阅读 3,860)