Valve地图格式
Valve地图格式(VMF)是一种纯文本文件格式,用于存储原始(预编译)地图数据,由Valve Hammer Editor (4.0版起)用于保存生产阶段的地图和预置件 。它以KeyValues 格式存储所有地图笔刷和实体的信息,文件扩展名为".vmf"。
.vmf文件是专为起源设计的地图源代码。与任何源代码文件一样,它必须经过编译才能使用,因此设计目标是易于阅读和编辑而非执行效率。文件采用Source引擎常用的易读编码形式。本文档将详细说明.vmf文件中定义的所有内容,包括各项元素的含义及其表示方式。这不是带结果的教程,而是对其背后原理的解析。本文档基于2006年1月1日Source SDK Beta 中Hammer 4.1生成的文件编写。
.vmf文件结构
.vmf中的代码结构简单易懂,这种结构在引擎的许多其他部分也很常见。以下是面向初学者的结构概述及术语说明:
// 这是注释 类名_1 { "属性_1" "值_1" "属性_2" "值_2" 类名_2 { "属性_1" "值_1" } }
所示名称仅为占位符,采用它们将被引用的命名规范:类(Classes)、属性(Properties)和值(Values)。本文档每节结构相同:标题为要解释的类名,随后简要说明该类定义的内容,下方提供代码示例,最后列出每个属性及其有效数据类型和解释。.vmf文件仅包含几种数据类型:
标记 | 说明 |
---|---|
int | 整数值 |
dec | 十进制数值 |
sz | 字符串 |
bool | 布尔值(true/false的二进制表示) |
vertex | XYZ坐标点,由3个用空格分隔的十进制值表示 |
rgb | 颜色值,使用3个0-255的整数(分别对应红/绿/蓝通道),空格分隔 |
属性说明将紧接在代码示例下方,按以下格式呈现:
- 属性_1 (数据类型):
- 解释该属性的含义及取值注意事项。
当某个属性需要单独解释,或是某类的属性实为另一个独立类时,将在代码示例下方添加链接说明。这表示该内容有独立章节解释,不包含在当前类定义中。所有属性定义后,将整体讨论该类及其属性对最终结果的影响。
常规结构
.vmf文件的常规结构如下:
versioninfo{} visgroups{} viewsettings{} world{} entity{} hidden{} cameras{} cordon{}
各章节功能可以单击结构跳到对应说明。
注意:顺序对Hammer不重要,但VBSP可能在特定类顺序错误时报错(如“Error: displacement found on a(n) worldspawn entity...”)。在Hammer中重新打开并保存地图可恢复默认顺序。
版本信息
此类包含Hammer创建文件的版本信息和保存次数,本质是文件头,与地图内容无关:
versioninfo { "editorversion" "400" "editorbuild" "3325" "mapversion" "0" "formatversion" "100" "prefab" "0" }
- editorversion (int)
- 创建文件所用的Hammer版本,4.00版对应"400"
- editorbuild (int)
- 生成文件时Hammer的补丁号
- mapversion (int)
- 文件保存次数,用于比较新旧版本
- formatversion (100)
- 未知(很可能是VMF文件格式版本)
- prefab (bool)
- 标识是否为完整地图或预置件 对象集合
由于从未生成过不同值,formatversion
含义未知。修改该值无效,保存或编译时会被重置为"100"。prefab
属性定义文件是否包含由笔刷/实体组成的对象(如沙发)而非完整地图。若缺失任何信息,Hammer将按当前状态重建。
可视组(VisGroup)
此类通常为空,但包含Hammer中所有唯一的可视组 定义及其结构和属性。以下示例展示完整结构:
visgroups { visgroup { "name" "Tree_1" "visgroupid" "5" "color" "65 45 0" } visgroup { "name" "Tree_2" "visgroupid" "1" "color" "60 35 0" visgroup { "name" "Branch_1" "visgroupid" "2" "color" "0 192 0" } visgroup { "name" "Branch_2" "visgroupid" "3" "color" "0 255 0" visgroup { "name" "Leaf" "visgroupid" "4" "color" "255 0 0" } } } }
- name (sz)
- 组名称,是Hammer中的唯一标识符
- visgroupid (int)
- 所有可视组ID中唯一的值,重复会导致Hammer轻微异常
- color (rgb)
- 组的颜色,可应用于Hammer中的笔刷轮廓
可视组 与编译的VIS无关。新版Hammer中有两种类型:自动组(由Hammer按笔刷/实体类型生成,不在此定义)和用户创建的自定义组 (在此类中定义)。组采用层次树结构,即一个组可以是父组并包含子组。对父组的操作会影响子组,但子组不影响父组。要定义子组,需在父组定义内嵌套该组(见示例)。组结构复杂度无限制,但超过128组会导致Hammer异常。允许多个visgroups类,但Hammer会将其合并。
视图设置
此类包含Hammer中地图特定的视图属性,其他属性不存入地图:
viewsettings { "bSnapToGrid" "1" "bShowGrid" "1" "bShowLogicalGrid" "0" "nGridSpacing" "64" "bShow3DGrid" "0" }
- bSnapToGrid (bool)
- 是否启用网格吸附功能
- bShowGrid (bool)
- 是否显示2D网格
- bShowLogicalGrid (bool)
- 是否在隐藏的逻辑视图 中显示网格
- nGridSpacing (int)
- 网格线间距值
- bShow3DGrid (bool)
- 是否显示3D网格
若bSnapToGrid
为真,Hammer会强制用户操作点对齐nGridSpacing
倍数(不影响已载入顶点)。其他Hammer属性设置仅作用于编辑器。缺失信息将被重置为默认值。
World
.vmf文件中,world类包含Hammer的所有世界笔刷信息(即无实体附加的笔刷)。world
类形式与其他实体相同但有特殊属性:
world { "id" "1" "mapversion" "1" "classname" "worldspawn" "skyname" "sky_wasteland02" Solid{} Hidden{} Group{} }
- id (int)
- 世界类ID中的唯一值
- mapversion (sz)
- 来自versioninfo类的mapversion副本
- classname (worldspawn)
- 声明世界实体的类型
- skyname (sz)
- 使用的天空盒名称
World类定义实际采用实体形式,可包含大量属性(此处省略)。关键点:World的classname
必须为"worldspawn",否则地图无法正确定义和编译("worldspawn"是游戏必需的实体类型)。编译时必须包含skyname
属性避免错误。允许多个world类,但Hammer会合并为单个定义(保留首个类的属性,后续同名属性覆盖前者)。
笔刷
此类代表Hammer中的单个笔刷,含1个属性和2个子类。笔刷由其所有围成形状的面定义。无效笔刷会导致Hammer拒绝加载或自动修复。
solid { "id" "1" side{} editor{} }
- id (int)
- 笔刷ID中的唯一值,重复会导致轻微异常
solid
类必须定义至少四个面。因Hammer计算方式,无效笔刷会导致编辑器或游戏错误。
面
此类定义单面的所有相关数据(朝向、纹理和属性)。plane属性定义面的朝向,面边界由相交平面计算得出故不存储。u轴/v轴属性需单独解释:
side { "id" "6" "plane" "(512 -512 -512) (-512 -512 -512) (-512 -512 512)" "material" "BRICK/BRICKFLOOR001A" "uaxis" "[1 0 0 0] 0.25" "vaxis" "[0 0 -1 0] 0.25" "rotation" "0" "lightmapscale" "16" "smoothing_groups" "0" "contents" "1" "flags" "0" dispinfo{} }
- id (int)
- 面ID中的唯一值,重复会导致轻微异常
- plane()
- material (sz)
- 面所应用纹理的路径和名称
- 面纹理的旋转角度(实际由u/v轴计算,此值仅用于显示)
- lightmapscale (int)
- 面的光照贴图分辨率
- smoothing_groups (int)
- 面所用的平滑组
- contents (bitfield)
- flags (bitfield)
- 内容/表面标志(定义于
public/bspflags.h
),十进制存储 - 被
Hammer 4.x剥离但编译器读取,使用
Hammer++或相关笔刷放入独立instance 处理
ID值在笔刷间也需唯一(因立方体贴图 等实体需引用特定面)。lightmapscale
定义编译时应用于面的光照块大小。smoothing_groups
表示面所属的平滑组 ,组内相邻面的光照差异将被平滑处理。通过转二进制可确定分组(最低位为1表示在第1组,最高位为1表示在第32组)。
平面
平面是基本的二维对象,可视为三维世界中无限延伸的平坦薄片。平面相交形成笔刷的边和顶点。
"plane" "(0 0 0) (0 0 0) (0 0 0)"
- plane ((顶点) (顶点) (顶点))
- 定义用于确定平面在三维空间中朝向和位置的三个点
使用三个有序点确定平面朝向,这些点如同支撑纸张的物体。点的高度差异会使平面倾斜,通过调整点的高度可旋转平面。平面必须穿过所有三个定义点,这些点共同决定平面朝向。平面朝向由点的定义顺序决定:从特定方向观察时顺时针定义的点序会使平面朝该方向可见。
以下示例生成32x32地板的平面(z轴平面朝上):
"plane" "(-16 -16 0) (16 -16 0) (16 16 0)"
三个点分别代表面的左下角、左上角和右上角。从顶部观察呈顺时针顺序,因此平面朝上。顶点间距32单位,形成正方形地板。
右侧动画展示通过CSG从六个平面构建简单笔刷的过程(详见附加资源 )。红/绿/蓝点分别代表第一/二/三点。该笔刷的平面点如下:
"plane" "(-128 32 128) (128 32 128) (128 0 128)" "plane" "(-128 0 0) (128 0 0) (128 32 0)" "plane" "(-128 32 128) (-128 0 128) (-128 0 0)" "plane" "(128 32 0) (128 0 0) (128 0 128)" "plane" "(128 32 128) (-128 32 128) (-128 32 0)" "plane" "(128 0 0) (-128 0 0) (-128 0 128)"
关键注意事项示例:
"plane" "(-32 -32 0) (32 -32 0) (32 32 0)" "plane" "(32 -32 0) (32 32 0) (-32 32 0)" "plane" "(32 32 0) (-32 32 0) (-32 -32 0)" "plane" "(-32 32 0) (-32 -32 0) (32 -32 0)"
以上定义均生成相同平面。同一面上的不同点序始终定义相同平面,否则笔刷无效。
面的所有边顶点由笔刷各平面相交确定:顶点是三个及以上平面的交点,边是两个平面沿线的交线。此为基础定义不可更改。问题在于顶点用于定义平面,因此交点必须严格匹配,否则将生成无效笔刷。定义平面时,任何其他定义顶点必须位于该平面或笔刷内其他平面上。
U/V轴
u轴和v轴是纹理专用坐标系。u轴对应x轴,v轴对应y轴,两者共同定义纹理在面上的显示方式。
"uaxis" "[1 0 0 0] 0.25" "vaxis" "[0 1 0 0] 0.25"
- u/v轴 ([x y z 平移] 缩放)
- xyz为十进制值代表轴向,随后是平移值,末尾为总缩放值
xyz值决定纹理在对应轴的显示比例。值1表示在标准空间显示一次完整纹理,值2表示显示两次。此设置仅应用于纹理的单一轴,且相对于真实xyz轴。这意味着可将纹理设置在平面不存在的轴上(如xy平面无需z轴定义)。负值会翻转纹理,若纹理显示在平面不存在的轴上,Hammer将报错。
倒数第二个值是纹理原点的平移量(Hammer显示时四舍五入但保存精确值)。末尾十进制是包含xyz定义的总缩放值。缩放比为1时,纹理1像素对应1Hammer单位。
位移信息
dispinfo
类处理位移 的所有信息。每个顶点信息独立存储,产生大量需整理的数据。该类仅当面为位移 时存在,属性覆盖原始面的属性。
dispinfo { "power" "2" "startposition" "[-512 -512 0]" "elevation" "0" "subdiv" "0" normals{} distances{} offsets{} offset_normals{} alphas{} triangle_tags{} allowed_verts{} }
- power (2,3,4)
- 计算行列数的幂值(仅此三值可编译)。缺省时Hammer假定为4。若数据基于其他幂值,顶点数据将被挤压到起始角
- startposition (顶点)
- 左下角在xyz空间的实际位置
- elevation (浮点数)
- 沿顶点法线方向作用于所有点的整体位移
- Subdiv (布尔值)
- 标记是否进行细分位移
- [[Zh/VMF_(Valve_Map_Format)#Normals|法线{}}}
- [[Zh/VMF_(Valve_Map_Format)#Distances|距离{}}}
- [[Zh/VMF_(Valve_Map_Format)#Offsets|偏移{}}}
- [[Zh/VMF_(Valve_Map_Format)#Offset_normals|offset_normals{}}}
- [[Zh/VMF_(Valve_Map_Format)#Alpha|alpha{}}}
- [[Zh/VMF_(Valve_Map_Format)#Traingle_tags|traingle_tags{}}}
- [[Zh/VMF_(Valve_Map_Format)#Allowed_verts|allowed_verts{}}}
Hammer中存在临时值"scale",不存入文件仅影响当前移动缩放。
所有基于网格的子类遵循相同规则:行数公式为2n+1(n=power值)。行号从startposition
定义的0号顶点开始。每行以row#存储,#为行号(部分列含多个值)。
法线
此类定义各顶点的法线(从面朝外指向的线),用于光照着色和顶点定位:
normals { "row0" "X0 Y0 Z0 X1 Y1 Z1 X2 Y2 Z2 X3 Y3 Z3 X4 Y4 Z4" "row1" "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" "row2" "0 0 1 0 0 1 0 0 1 0 0 1 0 0 1" "row3" "0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 -1" "row4" "0 0 1 0 0 -1 0 0 -1 0 0 -1 0 0 1" }
- row# ((x y z) (x y z) ...)
- 每组十进制值定义顶点的法线
法线是从顶点绘制的单位长度假想线。十进制值代表线在各轴的分量(纯轴向为1,斜面则含多轴分量)。法线决定地面位移 的光照方式及顶点最终位置。示例中:第1行全部朝上(0,0,1),第2行朝下(0,0,-1),第3行内侧三点朝下、外侧两点朝上。
> 注意:朝上法线(0,0,1)配合负距离等效于朝下法线(0,0,-1)配合正距离。
距离
距离值表示顶点沿法线移动的量(非法线则距离不生效):
distances { "row0" "#0 #1 #2 #3 #4" "row1" "0 0 0 0 0 " "row2" "64 64 64 64 64" "row3" "64 64 64 64 64" "row4" "32 32 32 32 32" }
- row# (小数 小数 ...)
- 顶点沿法线移动的最终距离
距离值使顶点沿法线方向移动形成最终位置。结合法线 类数据可构建位移贴图形状。若使用示例中的2-4行数据:第2行上移64单位,第3行下移64单位,第4行外侧两点上移32单位、内侧三点下移32单位。
偏移
此类定义位移贴图各顶点的默认位置,距离 值以此为基准计算:
offsets { "row0" "X0 Y0 Z0 X1 Y1 Z1 X2 Y2 Z2 X3 Y3 Z3 X4 Y4 Z4" "row1" "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" "row2" "0 0 64 0 0 64 0 0 64 0 0 64 0 0 64" "row3" "0 0 -64 0 0 -64 0 0 -64 0 0 -64 0 0 -64" "row4" "0 0 32 0 0 -32 0 0 -32 0 0 -32 0 0 32" }
- row# (顶点 顶点 ...)
- 相对于原始位置的顶点新位置
offsets
定义相对于原始计算位置的新顶点位置。本例数据与前两个类组合效果相同。Hammer不修改这些点。
偏移法线
此类与法线类几乎相同(数据结构一致)。差异在于功能:此类定义法线类的基准法线,不影响偏移类。Hammer无法直接修改这些值,仅能根据面朝向自动设置。
透明度
此类包含各顶点的透明度值(0-255),用于地面位移 。值间差异通过线性插值混合。
alphas { "row0" "#0 #1 #2 #3 #4" "row1" "0 0 0 0 0 " "row2" "255 255 255 255 255" "row3" "0 0 0 0 0" "row4" "128 0 0 0 128" }
- row# (小数 小数 ...)
- 0-255的十进制值
值表示次纹理覆盖基础纹理的程度。示例:第1行纯次纹理(255),第2行纯基础纹理(0),第3行外侧两点半透明(128),内侧三点纯基础纹理(0)。
三角标记
此类存储位移中每个三角形(非顶点)的信息。数据量由2n决定(n=power值),行列号从起点向外定义。
triangle_tags { "row0" "#0 #0 #1 #1 #2 #2 #3 #3 #4 #4" "row1" "9 9 9 9 9 9 9 9" "row2" "0 0 0 0 0 0 0 0" "row3" "1 1 1 1 1 1 1 1" "row4" "9 9 9 9 9 9 9 9" }
- row# (((0,1,9) (0,1,9))...)
- 每组代表一个正方形内的两个三角形,仅0/1/9可编译
值定义三角形朝向:"9"=z轴坡度极小;"1"=z轴坡度可步行;"0"=z轴坡度不可攀爬。Hammer接受任意值(如"2"在可步行区域视图 显示绿色高亮),但非0/1/9值编译失败。
允许顶点
此类影响位移贴图的游戏端曲面细分,标记与其他位移贴图共享边但不共享顶点的顶点。
allowed_verts { "10" "-1 -1 -1 -1 -1 -1 -1 -1 -1 -1" }
- 10 (整数集)
- 二进制标志集(-1=全启用),false值将在编译时移除顶点
Hammer中此属性无视觉效果,启用可折叠顶点显示 后,禁用顶点显示为粉色立方体。编译时禁用顶点与其相邻点插值拟合(对比编辑器与游戏效果)。
编辑器
此类信息仅供Hammer使用,与地图本身无关:
editor { "color" "0 255 0" "visgroupid" "2" "groupid" "7" "visgroupshown" "1" "visgroupautoshown" "1" "comments" "仅存在于实体上" "logicalpos" "[34 28]" }
- color (rgb)
- 笔刷轮廓线和平面的显示颜色
- visgroupid (int)
- 笔刷所属可视组ID
- group (int)
- 笔刷所属可见组ID
- visgroupshown (bool)
- 该组是否可见
- visgroupautoshown (bool)
- 自动组是否可见
- comments (字符串)
- 在"类信息"选项卡显示的注释(仅实体有效)
- logicalpos (2D向量)
- 实体在隐藏的逻辑视图 中的位置
visgroupid
属性可多次定义使笔刷归属多个可视组。group
属性定义笔刷所属的可见组 (值为笔刷首个子组ID)。visgroupshown
属性存在设计矛盾——在组内每个笔刷定义组可见性会导致异常显示。comments
属性允许为实体添加开发者注释(在实体属性窗口的"类信息"面板查看),非实体元素上的注释将被Hammer删除。"逻辑视图"是部分Hammer版本中半成品流程图视图,logicalpos
存储实体在此视图的位置(默认初始化为X轴正半轴随机位置)。
组
此类定义可见组 及其层级结构:
group { "id" "7" editor{} }
- id(整数)
- 组ID中的唯一值
组分配唯一ID,笔刷通过声明groupid
归属组。嵌套的editor
类维护组层级:子组在其editor
类中包含父组ID。当父组解散时,子组保持完整结构。
隐藏
存在两种隐藏类,均包含visgroupshown
或autovisgroupshown
设为"0"的元素:
hidden { solid{} }
简单存储所有隐藏笔刷。当visgroupshown
和autovisgroupshown
均为真时,Hammer会显示这些笔刷。
- 类型2**(位于文件根 ):
hidden { entity{} }
存储隐藏实体。当两个可见性属性均为真时,Hammer仍会显示这些实体。
实体
笔刷实体与点实体定义方式相同,均在world
类外定义(因world类本身是实体)。所有实体结构相同:定义属性后赋予笔刷或位置点。
entity { "id" "19" "classname" "func_detail" "spawnflags" "0" ______ connections{} solid{} hidden{} "origin" "-512 0 0" editor{} }
- id(整数)
- 实体ID中的唯一值
- classname(字符串)
- 实体类名
- spawnflags(整数)
- 实体启用的标志位
- origin(顶点)
- 点实体的存在位置
- [[Zh/
- Connections|connections{]] }
- [[Zh/
- Solid|solid{]] }
- [[Zh/
- Editor|editor{]] }
- [[Zh/
- hidden|hidden{]] }
- [[Zh/
- OverlayTransition|overlaytransition{]] }
关键元素说明:
1. 下划线部分:实体所有属性键值对(与Hammer禁用"智能编辑"时显示一致)
2. connections{}
类:可选的I/O连接信息
3. solid{}
子类:存在时转为笔刷实体
4. origin
属性:存在时转为点实体
5. 同时存在solid
和origin
:笔刷实体带原点属性
缺失属性将在编译时按默认值重建。
连接
存储实体所有输出(输入通过输出反向追踪,故不存储):
connections { "OnTrigger" "bob,Color,255 255 0,1.23,1" "OnTrigger" "bob,ToggleSprite,,3.14,-1" }
- 输出(事件,目标,输入,参数,延迟,次数)
- 整个输出事件存储为单字符串
字符串结构(前为逗号分隔):
1. 目标实体名或类名
2. 触发的目标输入
3. 覆盖参数(空表示无)
4. 触发延迟(秒)
5. 触发次数(-1=无限)
> 注意:省略除末位外的任何值将导致Hammer崩溃,省略末位值默认为1(触发一次)。
覆盖层过渡
摄像机
存储Hammer的3D视口摄像机(通过摄像机工具 创建),不编译进BSP文件。
cameras { "activecamera" "1" camera { "position" "[-1093.7 1844.91 408.455]" "look" "[-853.42 1937.5 175.863]" } camera { "position" "[692.788 1394.95 339.652]" "look" "[508.378 1493 347.127]" } camera { "position" "[-4613.89 2528.77 -2834.88]" "look" "[-4533.53 2950.1 -2896.85]" } }
无摄像机时:
cameras { "activecamera" "-1" }
- activecamera (整数)
- 设置Hammer 3D视图的当前摄像机(-1=无摄像机,默认定位世界原点朝北)
- position (顶点)
- 摄像机在空间中的眼睛位置
- look (顶点)
- 摄像机目标点位置
隔离区
存储隔离工具 所需信息:
cordon { "mins" "(99999 99999 99999)" "maxs" "(-99999 -99999 -99999)" "active" "0" }
- mins (顶点)
- 矩形区域右上角坐标
- maxs (顶点)
- 矩形区域左下角坐标
- active (布尔值)
- 是否启用隔离区
两点定义的空间外对象将被排除渲染/编译。
新版Hammer(L4D起)格式:
cordons { "active" "0" cordon { "name" "cordon" "active" "1" box { "mins" "(-1204 -1512 -748)" "maxs" "(836 444 1128)" } } }
隔离区组(cordons
)包含多个隔离区(cordon
),每个隔离区由mins
/maxs
定义矩形区域。
CSG(构造实体几何)
如平面 章节所述,笔刷最终形状通过CSG操作生成。主要方法: 1. 相交法:计算平面间交线定义笔刷边 2. 裁剪法(源自Quake):从对齐当前面的大四边形开始,用其他面反复裁剪 附加资源 提供详细说明。
结语
本文涵盖Hammer生成的.vmf文件所有信息。以下提供参考文件及格式解析实验素材:
附加资源
- VMF文件:将笔刷转换为3D网格 - CSG构建笔刷的详细说明