CD Code Digest Hub /CS:GO

VScript 脚本系统

9 分钟阅读 6 个章节

VScript 让地图作者用脚本语言(默认 Squirrel)驱动实体逻辑。引擎侧的 vscript 模块负责创建语言虚拟机;server.dll 在每张地图开始时启动 VM、注册游戏函数并执行 mapspawn 脚本。

相关页面:实体系统 · 客户端启动链

相关源码文件#

  • vscript/vscript.cpp、public/vscript/ivscript.h
  • game/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:

  1. 默认语言为 SL_DEFAULT,可用 -scriptlang gamemonkey|squirrel|python 覆盖,未知名称则不启动 VM:game/server/vscript_server.cpp:463-485。
  2. 创建 VM 后注册一组全局函数,例如 ShowMessage、SendToConsole、GetMapName、TraceLine、Time、FrameTime、EntFire、EntFireByHandle、CreateProp、DispatchParticleEffect 等:game/server/vscript_server.cpp:489-518。
  3. 让游戏规则注册自己的脚本函数,注册实体迭代器 Entities:game/server/vscript_server.cpp:525-528。
  4. Squirrel 时运行内置的服务端脚本,然后执行 mapspawn,可选连接调试器:game/server/vscript_server.cpp:530-540。
  5. 失败或 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 与 RunPrecacheScriptsgame/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_intervalgame/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 暴露的函数清单。