VScript 脚本系统
VScript 让地图作者用脚本语言(默认 Squirrel)驱动实体逻辑。引擎侧的 vscript 模块负责创建语言虚拟机;server.dll 在每张地图开始时启动 VM、注册游戏函数并执行 mapspawn 脚本。
相关源码文件#
vscript/vscript.cpp、public/vscript/ivscript.hgame/server/vscript_server.cpp、game/client/vscript_client.cpp- 实体脚本:
game/server/baseentity.cpp、game/server/util.cpp vscript/languages/(Squirrel、Lua、Python、GameMonkey 等绑定)
1. 脚本管理器#
CScriptManager::CreateVM(language):Squirrel 总是可用;Lua 仅在非主机且非 Linux 平台编译;Python 与 GameMonkey 分别受 VPYTHON_ENABLED、VGM_ENABLED 控制;销毁时按语言分派:vscript/vscript.cpp:20-102。它以 VSCRIPT_INTERFACE_VERSION 暴露,启动器与专用服务器都把 vscript 加入模块表:vscript/vscript.cpp:102、launcher/launcher.cpp:746、dedicated/sys_windows.cpp:280。默认语言 SL_DEFAULT = SL_SQUIRREL:public/vscript/ivscript.h:140。
2. 服务端 VM 的生命周期#
CVScriptGameSystem 是一个 CAutoGameSystemPerFrame:关卡实体创建前(LevelInitPreEntity)允许脚本创建实体并调用 VScriptServerInit,实体创建后禁止,关卡结束后 VScriptServerTerm:game/server/vscript_server.cpp:683-706。注释说明 VM 不跨关卡保留:game/server/vscript_server.cpp:689-692。
VScriptServerInit:
- 默认语言为
SL_DEFAULT,可用-scriptlang gamemonkey|squirrel|python覆盖,未知名称则不启动 VM:game/server/vscript_server.cpp:463-485。 - 创建 VM 后注册一组全局函数,例如
ShowMessage、SendToConsole、GetMapName、TraceLine、Time、FrameTime、EntFire、EntFireByHandle、CreateProp、DispatchParticleEffect等:game/server/vscript_server.cpp:489-518。 - 让游戏规则注册自己的脚本函数,注册实体迭代器
Entities:game/server/vscript_server.cpp:525-528。 - Squirrel 时运行内置的服务端脚本,然后执行
mapspawn,可选连接调试器:game/server/vscript_server.cpp:530-540。 - 失败或
scriptmanager不存在时g_pScriptVM = NULL:game/server/vscript_server.cpp:546-557。
脚本通过 EntFire 触发的是实体 I/O(见 实体系统),因此与地图连线共享同一事件队列语义(推断,依据函数说明“Generate and entity i/o event”)。
3. 实体级脚本作用域#
每个服务端实体都可以拥有自己的脚本作用域,让地图作者把脚本挂到具体实体上:
| 机制 | 行为 | 证据 |
|---|---|---|
| Hammer 键值 | vscripts(空格分隔的脚本列表)与 thinkfunction 是 CBaseEntity 的数据描述字段 | game/server/baseentity.cpp:2236-2237 |
| 何时运行 | DispatchSpawn( pEntity, bRunVScripts ) 在 Spawn 前调用 RunVScripts 与 RunPrecacheScripts | game/server/util.cpp:1955-1975 |
| 作用域创建 | ValidateScriptScope 在 scriptmanager 或 g_pScriptVM 不存在时直接失败;否则以实体的脚本 ID 初始化作用域,并把 self 设为实体的脚本实例 | game/server/baseentity.cpp:8512-8544 |
| 多脚本链式调用 | RunVScripts 依次执行列表中的脚本;Squirrel 下对部分回调做调用链合并,避免后一个脚本覆盖前一个;Python 的调用链未实现 | game/server/baseentity.cpp:8550-8614 |
| 脚本 Think | 设置了 thinkfunction 时注册 ScriptThink;脚本函数返回的数值作为下次间隔,否则使用 sv_script_think_interval | game/server/baseentity.cpp:8616-8619、game/server/baseentity.cpp:7542-7556 |
| I/O 输入 | 所有实体都接受 RunScriptFile、RunScriptCode、CallScriptFunction 三个输入 | game/server/baseentity.cpp:2419-2421、game/server/baseentity.cpp:7373-7391 |
| 调用脚本函数 | CallScriptFunction 先确保作用域存在,再在作用域中查找并调用函数 | game/server/baseentity.cpp:7445-7466 |
4. 客户端 VM#
client.dll 也编译了一份精简的脚本系统(game/client/client_base.vpc:463):
CVScriptGameSystem(客户端版)在LevelInitPreEntity时调用VScriptClientInit,关卡结束时终止,每帧在FrameUpdatePostEntityThink中驱动 VM:game/client/vscript_client.cpp:163-192。VScriptClientInit使用与服务端相同的-scriptlang选择逻辑,只注册GetMapName、Time、DoIncludeScript、GetDeveloperLevel与游戏规则的脚本函数;实体迭代器的注册被注释掉;Squirrel 下运行内置客户端脚本后执行mapspawn:game/client/vscript_client.cpp:75-130。
与服务端相比,客户端 VM 暴露的接口很少。从注册的函数看,它不能直接操纵服务端实体(推断)。
约束与边界#
- 各语言运行时的源码在
vscript/languages下,是否参与 CS:GO 构建取决于平台与宏;本页只核对了CreateVM的条件编译。 - 实体作用域依赖全局 VM;VM 不存在(例如
-scriptlang指定了未知语言)时,实体脚本、脚本 Think 与三个脚本输入都会静默失败,只输出开发者消息:game/server/baseentity.cpp:8516-8526。 - 未展开 Squirrel 内置脚本(
g_Script_vscript_server/g_Script_vscript_client)的内容,以及具体游戏规则通过RegisterScriptFunctions暴露的函数清单。