Valve地图格式

From Valve Developer Community
< Zh
Jump to navigation Jump to search
English (en)中文 (zh)Translate (Translate)

Valve地图格式VMF)是一种纯文本文件格式,用于存储原始(预编译)地图数据,由Valve Hammer Editor(en)(4.0版起)用于保存生产阶段的地图和预置件(en)。它以KeyValues(en)格式存储所有地图笔刷和实体的信息,文件扩展名为".vmf"。

.vmf文件是专为起源起源设计的地图源代码。与任何源代码文件一样,它必须经过编译才能使用,因此设计目标是易于阅读和编辑而非执行效率。文件采用Source引擎常用的易读编码形式。本文档将详细说明.vmf文件中定义的所有内容,包括各项元素的含义及其表示方式。这不是带结果的教程,而是对其背后原理的解析。本文档基于2006年1月1日Source SDK Beta(en)中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)
标识是否为完整地图或预置件(en)对象集合

由于从未生成过不同值,formatversion含义未知。修改该值无效,保存或编译时会被重置为"100"。prefab属性定义文件是否包含由笔刷/实体组成的对象(如沙发)而非完整地图。若缺失任何信息,Hammer将按当前状态重建。

可视组(VisGroup)

此类通常为空,但包含Hammer中所有唯一的可视组(en)定义及其结构和属性。以下示例展示完整结构:

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中的笔刷轮廓

可视组(en)与编译的VIS无关。新版Hammer中有两种类型:自动组(由Hammer按笔刷/实体类型生成,不在此定义)和用户创建的自定义组(en)(在此类中定义)。组采用层次树结构,即一个组可以是父组并包含子组。对父组的操作会影响子组,但子组不影响父组。要定义子组,需在父组定义内嵌套该组(见示例)。组结构复杂度无限制,但超过128组会导致Hammer异常。允许多个visgroups类,但Hammer会将其合并。

视图设置

此类包含Hammer中地图特定的视图属性,其他属性不存入地图:

viewsettings
{
      "bSnapToGrid" "1"
      "bShowGrid" "1"
      "bShowLogicalGrid" "0"
      "nGridSpacing" "64"
      "bShow3DGrid" "0"
}
  • bSnapToGrid (bool)
是否启用网格吸附功能
  • bShowGrid (bool)
是否显示2D网格
  • bShowLogicalGrid (bool)
是否在隐藏的逻辑视图(en)中显示网格
  • 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副本
声明世界实体的类型
  • 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中的唯一值,重复会导致轻微异常
面所应用纹理的路径和名称
面纹理的旋转角度(实际由u/v轴计算,此值仅用于显示)
  • lightmapscale (int)
面的光照贴图分辨率
  • smoothing_groups (int)
面所用的平滑组(en)
  • contents (bitfield)
  • flags (bitfield)
内容/表面标志(定义于🖿public/bspflags.h),十进制存储
Hammer Hammer 4.x剥离但编译器读取,使用Hammer++ Hammer++或相关笔刷放入独立instance(en)处理

ID值在笔刷间也需唯一(因立方体贴图(en)等实体需引用特定面)。lightmapscale定义编译时应用于面的光照块大小。smoothing_groups表示面所属的平滑组(en),组内相邻面的光照差异将被平滑处理。通过转二进制可确定分组(最低位为1表示在第1组,最高位为1表示在第32组)。

English (en)中文 (zh)Translate (Translate)

平面

平面是基本的二维对象,可视为三维世界中无限延伸的平坦薄片。平面相交形成笔刷的边和顶点。

 
"plane" "(0 0 0) (0 0 0) (0 0 0)"
  • plane ((顶点) (顶点) (顶点))
定义用于确定平面在三维空间中朝向和位置的三个点

使用三个有序点确定平面朝向,这些点如同支撑纸张的物体。点的高度差异会使平面倾斜,通过调整点的高度可旋转平面。平面必须穿过所有三个定义点,这些点共同决定平面朝向。平面朝向由点的定义顺序决定:从特定方向观察时顺时针定义的点序会使平面朝该方向可见。

以下示例生成32x32地板的平面(z轴平面朝上):

"plane" "(-16 -16 0) (16 -16 0) (16 16  0)"

三个点分别代表面的左下角、左上角和右上角。从顶部观察呈顺时针顺序,因此平面朝上。顶点间距32单位,形成正方形地板。

Brush planes.gif

右侧动画展示通过CSG从六个平面构建简单笔刷的过程(详见附加资源(en))。红/绿/蓝点分别代表第一/二/三点。该笔刷的平面点如下:

"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类处理位移(en)的所有信息。每个顶点信息独立存储,产生大量需整理的数据。该类仅当面为位移(en)时存在,属性覆盖原始面的属性。

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 (布尔值)
标记是否进行细分位移(en)
  • [[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,斜面则含多轴分量)。法线决定地面位移(en)的光照方式及顶点最终位置。示例中:第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# (小数 小数 ...)
顶点沿法线移动的最终距离

距离值使顶点沿法线方向移动形成最终位置。结合法线(en)类数据可构建位移贴图形状。若使用示例中的2-4行数据:第2行上移64单位,第3行下移64单位,第4行外侧两点上移32单位、内侧三点下移32单位。

偏移

此类定义位移贴图各顶点的默认位置,距离(en)值以此为基准计算:

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),用于地面位移(en)。值间差异通过线性插值混合。

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"在可步行区域视图(en)显示绿色高亮),但非0/1/9值编译失败。

允许顶点

此类影响位移贴图的游戏端曲面细分,标记与其他位移贴图共享边但不共享顶点的顶点。

allowed_verts
{
      "10" "-1 -1 -1 -1 -1 -1 -1 -1 -1 -1"
}
  • 10 (整数集)
二进制标志集(-1=全启用),false值将在编译时移除顶点

Hammer中此属性无视觉效果,启用可折叠顶点显示(en)后,禁用顶点显示为粉色立方体。编译时禁用顶点与其相邻点插值拟合(对比编辑器游戏效果)。

English (en)中文 (zh)Translate (Translate)

编辑器

此类信息仅供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向量)
实体在隐藏的逻辑视图(en)中的位置

visgroupid属性可多次定义使笔刷归属多个可视组。group属性定义笔刷所属的可见组(en)(值为笔刷首个子组ID)。visgroupshown属性存在设计矛盾——在组内每个笔刷定义组可见性会导致异常显示。comments属性允许为实体添加开发者注释(在实体属性窗口的"类信息"面板查看),非实体元素上的注释将被Hammer删除。"逻辑视图"是部分Hammer版本中半成品流程图视图,logicalpos存储实体在此视图的位置(默认初始化为X轴正半轴随机位置)。

此类定义可见组(en)及其层级结构:

group
{
      "id" "7"
      editor{}
}
  • id(整数)
组ID中的唯一值

组分配唯一ID,笔刷通过声明groupid归属组。嵌套的editor类维护组层级:子组在其editor类中包含父组ID。当父组解散时,子组保持完整结构。

隐藏

存在两种隐藏类,均包含visgroupshownautovisgroupshown设为"0"的元素:

hidden
{
      solid{}
}

简单存储所有隐藏笔刷。当visgroupshownautovisgroupshown均为真时,Hammer会显示这些笔刷。

hidden
{
      entity{}
}

存储隐藏实体。当两个可见性属性均为真时,Hammer仍会显示这些实体。

实体

笔刷实体与点实体定义方式相同,均在world(en)类外定义(因world类本身是实体)。所有实体结构相同:定义属性后赋予笔刷或位置点。

entity
{
      "id" "19"
      "classname" "func_detail"
      "spawnflags" "0"
      ______
      connections{}
      solid{}
      hidden{}
      "origin" "-512 0 0"
      editor{}
}
  • id(整数)
实体ID中的唯一值
  • classname(字符串)
实体类名
  • spawnflags(整数)
实体启用的标志位
  • origin(顶点)
点实体的存在位置
  • [[Zh/
  1. Connections|connections{]](en)}
  • [[Zh/
  1. Solid|solid{]](en)}
  • [[Zh/
  1. Editor|editor{]](en)}
  • [[Zh/
  1. hidden|hidden{]](en)}
  • [[Zh/
  1. OverlayTransition|overlaytransition{]](en)}

关键元素说明: 1. 下划线部分:实体所有属性键值对(与Hammer禁用"智能编辑"时显示一致) 2. connections{}类:可选的I/O连接信息 3. solid{}子类:存在时转为笔刷实体 4. origin属性:存在时转为点实体 5. 同时存在solidorigin:笔刷实体带原点属性 缺失属性将在编译时按默认值重建。

连接

存储实体所有输出(输入通过输出反向追踪,故不存储):

connections
{
      "OnTrigger" "bob,Color,255 255 0,1.23,1"
      "OnTrigger" "bob,ToggleSprite,,3.14,-1"
}
  • 输出(事件,目标,输入,参数,延迟,次数)
整个输出事件存储为单字符串

字符串结构(求生之路2前为逗号分隔): 1. 目标实体名或类名 2. 触发的目标输入 3. 覆盖参数(空表示无) 4. 触发延迟(秒) 5. 触发次数(-1=无限)

> 注意:省略除末位外的任何值将导致Hammer崩溃,省略末位值默认为1(触发一次)。

覆盖层过渡

待完善: info_overlay_transition(en)在特殊块存储信息,待补充文档

摄像机

存储Hammer的3D视口摄像机(通过摄像机工具(en)创建),不编译进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 (顶点)
摄像机目标点位置

隔离区

存储隔离工具(en)所需信息:

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(构造实体几何)

平面(en)章节所述,笔刷最终形状通过CSG操作生成。主要方法: 1. 相交法:计算平面间交线定义笔刷边 2. 裁剪法(源自Quake):从对齐当前面的大四边形开始,用其他面反复裁剪 附加资源(en)提供详细说明。

裁剪法构建笔刷过程

结语

本文涵盖Hammer生成的.vmf文件所有信息。以下提供参考文件及格式解析实验素材:

附加资源

小作品

这篇文章是一个小作品,您可以帮助我们完善它。