VScript 基本原理

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


本页面内容由Dazai Nerau译自英文版页面(en). 欢迎任何人补充新内容或者修改其中的错误.


这篇文章旨在阐述有关VScript(en)脚本的基本概念以及使用方法.

表和脚本域

脚本环境由关联数组或者构成 , 彼此之间互相嵌套.

脚本被加载时, 它就会被放置在一个被称为它的脚本域的表中, 然后脚本中的任何代码都能够被执行. 执行完代码后,所有的变量、函数以及类都会被保存在脚本域中.

脚本句柄

与游戏中的实体进行交互是经由 脚本句柄 得以实现的, 脚本句柄是一种用于引用特定实体的对象. 脚本句柄包含了访问器(accessor)和修改器(mutator)方法以读取和修改实体的属性. 方法的可用性取决于游戏和实体的类型. 请阅读各种游戏的脚本的API参考以了解详情。

所有正处于游戏中的服务器端的实体都可以通过CEntities类的对象Entities来搜寻或者迭代。

实体脚本

详阅文章Entity Scripts(en)


常见的VScript用法是使用实体脚本来扩充实体的功能.

添加一个脚本到一个服务器端实体的vscripts (实体脚本)键值将使得该脚本作为实体脚本被加载. 该脚本将在实体产生后被自动执行, 并加载到一个脚本域内,该脚本域由唯一标识符+实体名称或种类名组成; _<unique ID>_<entity name>, 并被放在根表中. 如果一个实体没有任何脚本被执行, 可以通过 CBaseEntity::ValidateScriptScope() 方法来手动创建一个脚本域.

Note.png注意:起源2 有公共脚本域和私有脚本域之分. 实体脚本会被加载到私有脚本域之中.

可以通过在vscripts 键值中指定多个脚本来加载其他脚本, 或者使用 RunScriptFile 输入. 在同一实体上运行的所有脚本将加载到相同的脚本域, 覆盖任何相同的变量和函数.

Think函数可以被设置于thinkfunction 键值(en)中或者由 AddThinkToEnt() 函数来添加, 被指定的函数会以0.1秒一次的频率被调用. 函数也可以通过I/O系统的 RunScriptCode function_name(argument, ...)RunScriptFunction function_name这两个输入来手动调用。

实体脚本有 self (起源1) 或者 thisEntity (起源2)这两个能够引用它自身实体的脚本句柄.

预定义的钩子函数

实体能够从C++端(游戏的底层代码)中调用其脚本域的函数, 我们称之为钩子函数(Hook Functions), 简称钩子.公共实体类被写入了一些预定义的函数调用(predefined function calls),以在特定事件发生时允许脚本执行代码. 例如, 在一个实体脚本中创建一个名为 Precache() 的函数将在实体生成后立即调用该函数, 以允许脚本预缓存自定义的资源. 这些钩子函数不需要注册,并且只要名字没写错的话就会在合适的时候被调用. 请参阅游戏的API文档以了解每个类可用的钩子函数.

待完善: Does Source 2 implement this?

I/O系统(en)交互

触发输出

如果游戏的API支持的话, 脚本可以通过EntFire()DoEntFire() 来对地图上的实体触发输出. 可能还有更多类似的函数, 取决于游戏. (译者注:例如CSGO还有EntFireByHandle()这一函数)

如果函数支持activatorcaller 的参数, 就可以使用"!self" 或者 "!activator" 关键字将输出发送给实体,而不必知道其targetname,前提是句柄有效.

使用RunScriptCode 输入可以通过I/O系统执行任何VScript代码. 代码将在被调用实体的脚本域中运行.

Warning.png警告:在Hammer的输出中绝对不要使用双引号, 因为这样做会破坏地图文件. 这意味着字符串无法通过 RunScriptCode 被传递.


示范代码:

// 将拥有脚本域的实体的生命值设置成500
DoEntFire( "!self", "SetHealth", "500", 0, self, self )

关联输出

通过CBaseEntity::ConnectOutput(string output, string function)方法, 实体输出可以关联到实体的脚本域中的函数.

在函数被调用期间, 变量 activatorcaller 会被设置为I/O链中激活实体与调用实体的句柄, 从而可以轻松地找到是哪个玩家触发了某些内容.

Icon-Bug.png错误:求生之路2中, 这些变量并没有被正确地设置.(译者注:在实践中, 译者并未发现该Bug的存在, 这意味着也许该Bug已经被修复了.)  [todo tested in?]


示范代码:

// 当玩家使用时,点燃道具
function IgniteSelf()
{
	DoEntFire( "!self", "Ignite", "", 0, self, self )
}

// 关联OnPlayerUse输出与脚本函数.
self.ConnectOutput( "OnPlayerUse", "IgniteSelf" )

输入钩子

当实体接收到了输入, 游戏代码会尝试在接收实体的实体脚本中调用格式为 Input<Name of Input>() 的脚本函数. 如果该函数返回 false, 输入就不会被触发.(译者注:这意味着输入可以被打断)

Note.png注意:输入名称区分大小写, 并使用骆驼拼写法格式.

与关联实体输出的函数一样, activatorcaller 这两个变量会被设置为激活实体和调用实体.

Note.png注意:不同于输出函数的Bug, 它们在求生之路2中的设置是正确的.


示范代码:

// 一个门或者按钮的脚本, 监听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的变量,函数和类的表.

API参考

L4D2脚本函数列表(en)

Portal2脚本函数列表(en)

CSGO脚本函数列表(en)

Contagion脚本函数列表(en)

Dota2脚本API(en)

推荐阅读