VScript 基本原理
本页面内容由Dazai Nerau译自英文版页面 . 欢迎任何人补充新内容或者修改其中的错误.
这篇文章旨在阐述有关VScript 脚本的基本概念以及使用方法.
Contents
表和脚本域
脚本环境由关联数组或者 表 构成 , 彼此之间互相嵌套.
脚本被加载时, 它就会被放置在一个被称为它的脚本域的表中, 然后脚本中的任何代码都能够被执行. 执行完代码后,所有的变量、函数以及类都会被保存在脚本域中.
脚本句柄
与游戏中的实体进行交互是经由 脚本句柄 得以实现的, 脚本句柄是一种用于引用特定实体的对象. 脚本句柄包含了访问器(accessor)和修改器(mutator)方法以读取和修改实体的属性. 方法的可用性取决于游戏和实体的类型. 请阅读各种游戏的脚本的API参考以了解详情。
所有正处于游戏中的服务器端的实体都可以通过CEntities
类的对象Entities
来搜寻或者迭代。
实体脚本
详阅文章Entity Scripts
常见的VScript用法是使用实体脚本来扩充实体的功能.
添加一个脚本到一个服务器端实体的vscripts
(实体脚本)键值将使得该脚本作为实体脚本被加载. 该脚本将在实体产生后被自动执行, 并加载到一个脚本域内,该脚本域由唯一标识符+实体名称或种类名组成; _<unique ID>_<entity name>
, 并被放在根表中. 如果一个实体没有任何脚本被执行, 可以通过 CBaseEntity::ValidateScriptScope()
方法来手动创建一个脚本域.
![Note.png](/w/images/thumb/c/cc/Note.png/10px-Note.png)
可以通过在vscripts
键值中指定多个脚本来加载其他脚本, 或者使用 RunScriptFile
输入. 在同一实体上运行的所有脚本将加载到相同的脚本域, 覆盖任何相同的变量和函数.
Think函数可以被设置于thinkfunction
键值 中或者由 AddThinkToEnt()
函数来添加, 被指定的函数会以0.1秒一次的频率被调用. 函数也可以通过I/O系统的 RunScriptCode function_name(argument, ...)
和RunScriptFunction function_name
这两个输入来手动调用。
实体脚本有 self
(起源1) 或者 thisEntity
(起源2)这两个能够引用它自身实体的脚本句柄.
预定义的钩子函数
实体能够从C++端(游戏的底层代码)中调用其脚本域的函数, 我们称之为钩子函数(Hook Functions), 简称钩子.公共实体类被写入了一些预定义的函数调用(predefined function calls),以在特定事件发生时允许脚本执行代码. 例如, 在一个实体脚本中创建一个名为 Precache()
的函数将在实体生成后立即调用该函数, 以允许脚本预缓存自定义的资源. 这些钩子函数不需要注册,并且只要名字没写错的话就会在合适的时候被调用. 请参阅游戏的API文档以了解每个类可用的钩子函数.
I/O系统 交互
触发输出
如果游戏的API支持的话, 脚本可以通过EntFire()
和 DoEntFire()
来对地图上的实体触发输出. 可能还有更多类似的函数, 取决于游戏. (译者注:例如CSGO还有EntFireByHandle()
这一函数)
如果函数支持activator
和 caller
的参数, 就可以使用"!self" 或者 "!activator" 关键字将输出发送给实体,而不必知道其targetname,前提是句柄有效.
使用RunScriptCode
输入可以通过I/O系统执行任何VScript代码. 代码将在被调用实体的脚本域中运行.
![Warning.png](/w/images/thumb/c/cb/Warning.png/10px-Warning.png)
RunScriptCode
被传递.
示范代码:
// 将拥有脚本域的实体的生命值设置成500
DoEntFire( "!self", "SetHealth", "500", 0, self, self )
关联输出
通过CBaseEntity::ConnectOutput(string output, string function)
方法, 实体输出可以关联到实体的脚本域中的函数.
在函数被调用期间, 变量 activator
和 caller
会被设置为I/O链中激活实体与调用实体的句柄, 从而可以轻松地找到是哪个玩家触发了某些内容.
示范代码:
// 当玩家使用时,点燃道具
function IgniteSelf()
{
DoEntFire( "!self", "Ignite", "", 0, self, self )
}
// 关联OnPlayerUse输出与脚本函数.
self.ConnectOutput( "OnPlayerUse", "IgniteSelf" )
输入钩子
当实体接收到了输入, 游戏代码会尝试在接收实体的实体脚本中调用格式为 Input<Name of Input>()
的脚本函数. 如果该函数返回 false
, 输入就不会被触发.(译者注:这意味着输入可以被打断)
![Note.png](/w/images/thumb/c/cc/Note.png/10px-Note.png)
与关联实体输出的函数一样, activator
和 caller
这两个变量会被设置为激活实体和调用实体.
示范代码:
// 一个门或者按钮的脚本, 监听Unlock输入
// 禁止门被打开, 除非尝试开门5次(进行了5次unlock输入).
UnlockCounter <- 5 // 实体脚本域的本地计数器
// 接收到Unlock输入时调用
function InputUnlock()
{
UnlockCounter--
if( UnlockCounter <= 0 )
{
return true // 允许解锁
}
return false // 打断输入
}
术语表
- Entity handle(实体句柄,也称为EHANDLE)
- 一个能够传递C++ 的EHANDLE的不透明的实体引用(类似于指针). 只能与其他句柄进行比较或传递给期望需要用到它们的API函数, 很少被使用到.
- Script handle(脚本句柄)
- 具有C++实体对象的访问器和修改器的实体实例. 在C++代码中表示为HSCRIPT typedef.
- Script scope(脚本域)
- 脚本的执行上下文(Execution context).它是一个包含了VScript的变量,函数和类的表.