响应系统
The Response System(响应系统)用于决定 NPC(或玩家)在想要说什么或是否说什么时应该使用哪种语言(和/或动画)。
![Note.png](/w/images/thumb/c/cc/Note.png/10px-Note.png)
![求生之路](/w/images/thumb/a/ad/Icon-L4D.png/16px-Icon-L4D.png)
![求生之路2](/w/images/thumb/8/8b/Icon-L4D2.png/16px-Icon-L4D2.png)
大多数响应系统用户都是为了响应游戏中发生的事件而发言。语音可以从代码或地图的输入中触发。触发语音涉及响应概念,它对应于系统中的响应。概念是用于特定事件的语音类别,例如注意到危险 (TLK_DANGER
) 或看到敌人 (TLK_STARTCOMBAT
)。虽然一个概念可以单独用于基本和简单的对话,但语音也可以通过各种标准来触发,这些标准描述了与该概念和/或说话者的当前状态和周围世界相关的事物。这允许复杂的对话树涉及不同情况下的不同台词。
例如,当玩家杀死敌方 NPC 时,HL2 情节系列中的npc_alyx
会说出TLK_PLAYER_KILLED_NPC
概念。这个概念对于与 NPC 相关的条件或它被杀死的方式有额外的标准。如果玩家通过爆头杀死了 NPC,则响应系统将选择需要该标准的响应,这通常会导致艾利克斯称赞玩家的射击或评价这是爆头。
响应系统通常使用scripts/talker
目录中的脚本。当它接收到一个概念及其标准时,它会在响应脚本中搜索与其概念和标准相匹配的规则(rules)。当它找到一个时,它会使用该规则的响应(response)或响应选择。响应可以是声音脚本 、句子 ,甚至是实例化的排编场景 。
尽管有这些优点,但大多数 NPC 默认不使用此系统。例如,npc_pigeon
默认只使用声音脚本,不使用响应系统。
以下是使用响应系统的地方的快速列表:
- 在
半衰期2 中,大多数玩家同伴 NPC(公民 、艾利克斯 等)使用 NPC 语音响应系统。
- 在
军团要塞2 中,所有兵种(all classes)都使用响应系统来进行语音命令和嘲讽响应。
- 在
求生之路系列 中,所有幸存者都使用响应系统来响应玩家在世界上的行为和事件。
- 在
传送门2 中,Atlas 和 P-body(合作模式下的机器人)都使用响应系统来进行嘲讽手势。
- 在
反恐精英:全球攻势 中,所有玩家角色都使用响应系统并跟踪已使用的响应。
- 在
半衰期:爱莉克斯 中,参见 HLALYX:Response_rules.txt 。
![Cpp-16px.png](/w/images/5/53/Cpp-16px.png)
CAI_ExpresserHost<>
模板类的CBaseFlex
派生的类上,该模板类在CAI_BaseActor
上自动实现。所有 NPC 和玩家都已从CBaseFlex
派生,因此你通常只想使用CAI_ExpresserHost<>
,但响应系统可以使用DispatchResponse()
函数以更有限的方式在任何实体上使用,这就是env_speaker
的工作方式。GetResponseSystem()
可用于使概念搜索特定的响应系统树,如果你计划在没有CAI_ExpresserHost<>
的情况下使用DispatchResponse()
,则必须将其覆盖。目的
创建响应系统,以便 NPC、玩家等使用统一且强大的系统来管理语音。它不仅允许开发人员和作家创建复杂的对话树,而且还允许模组制作者轻松修改或向游戏添加新语音,而无需编写代码或修改 BSP 文件,同时仍然可以完全控制语音的方式和使用次数。
该系统最适合用于来自 AI 系统的语音,因为这些台词将在整个游戏中被多次说出。 (例如,市民在装填武器时会说些什么。很多市民会在游戏过程中装填武器。)
这是一个来自 NPC 使用响应系统的复杂对话树的示例:
- I just killed an enemy. 我刚刚杀死了一个敌人。
- Did I use a shotgun? 我用了霰弹枪吗?
- "Eat shotgun! 吃霰弹枪!"
- Was the enemy really close? 敌人真的很近吗?
- "You got too close! 你离的太近了!"
- "Thanks for getting close! 感谢你靠近!"
- Was the enemy a
npc_headcrab
? 敌人是npc_headcrab
吗?
- "You're not getting MY head! 你没有得到我的头!"
- "I hate headcrabs! 我恨猎头蟹!"
你还可以使用标准组合。
- Did I use a shotgun against a
npc_headcrab
really close to me? 我用霰弹枪对付离我很近的npc_headcrab
吗?
- "You got too close to my shotgun, headcrab! 你离我的霰弹枪太近了,猎头蟹!"
- Did I use a shotgun against a
你还可以控制单独的行,使其在一定时间内不重复,或者根本不重复。
构成
![Note.png](/w/images/thumb/c/cc/Note.png/10px-Note.png)
响应系统由四个核心部分组成:Concepts(概念), Criteria(标准), Rules(规则),和 Response Groups(响应组)。它们的使用方法如下:
- 一个 NPC 为一个语音概念(Concept)请求一行语音。
- 例如,假设我们的 NPC 为
TLK_SHOT
概念请求一行语音。 NPC 被敌人击中时会说出这个概念。
- 例如,假设我们的 NPC 为
- NPC 收集了一堆反映 NPC 当前状态的标准(Criteria)以及有关世界状态的其他相关数据。许多概念也有修饰符(Modifier),这是特定概念特有的标准,通常反映事件本身。
- 当 NPC 为
TLK_SHOT
概念请求一行时,游戏会将 NPC 当前的健康状况等作为默认标准集的一部分进行组合。然后,它附加了概念的修饰符,其中可能包括射击他们的敌人的类型、他们受到的伤害量等。
- 当 NPC 为
- 概念和标准 被传递到 NPC 的响应系统中,该系统通常是所有 NPC 共享的单个全局实例。
- 响应系统搜索其规则(Rules)的大列表。
- 每个规则都包含一个标准列表,该列表根据 NPC 预先设置的标准进行测试。请注意,概念在此阶段被视为高优先级标准。
- 每个规则都会根据有多少标准是正确的而获得一个分数。如果至少一个标准被标记为“必需”(required)并且不满足,则永远不会选择该规则。大多数标准都设置为必需,但不需要的标准只会提高规则的分数。
- 在我们的
TLK_SHOT
示例中,可能有多个包含不同行的TLK_SHOT
规则。一条规则可能包含测试“NPC 是否离射击他的敌人非常近”的标准。另一个规则可能会测试是否是特定类型的敌人向他开枪(例如联合军士兵)。另一个规则可能会测试 NPC 是否在射击后剩余的生命值低于 25%。
- 在我们的
- 响应系统对其列表中的所有规则进行评分,并选择得分最高的一项。得分最高的规则指定一个响应组(Response Group)。
- 响应组只是可能的响应列表,每个响应可能是一行语音和/或动画。一个响应根据响应组和个人响应的设置选择。选择有效响应后,NPC将执行该响应。
- 在我们的
TLK_SHOT
示例中,假设选择了<25% 健康规则。该规则将有一个响应组,其中可能包含一系列行,例如“One more shot like that, and I'm done for! 再打一枪,我就完蛋了!”、“Oh man, I'm in trouble! 哦,伙计,我有麻烦了!”或“I need medical attention over here! 我需要医疗照顾!” TLK_SHOT
的另一条规则可以检查射击他们的敌人是否是联合军士兵,并指向一个响应组,其中包含“That Combine soldier's got me pinned down! 那个联合军士兵让我被压制了!”或“Help me with this soldier! 帮我对付这个士兵!”。另一个规则可以检查敌人是否是联合武装直升机,并用“That gunship is kicking my butt! 那艘武装直升机在踢我的屁股!”和“Someone help me take down that gunship before it kills me! 有人帮我在它杀了我之前拿下那艘武装直升机!”指向一个组。
- 在我们的
- 如果没有任何规则符合给定的标准[或者所选的反应组没有重复并且已经筋疲力尽(exhausted)],NPC不会说任何话。
概念
concept(概念) 是一个字符串 ,它表示角色语音尝试的高级原因。 代码中定义了一组概念,这些概念将被自动调用,但从技术上讲,概念只是字符跟踪的特殊高级标准。它们与任何东西无关,你可以在响应文件中自由创建你自己的。你可以在代码中或使用参与者的DispatchResponse
或SpeakResponseConcept
输入来调用概念。
以下是半条命 2 中使用的一些预定义 NPC 概念的列表:
TLK_HELLO 当我第一次遇见玩家。 TLK_IDLE 当我被闲置了一段时间。 TLK_USE 当玩家尝试使用(+use)我。 TLK_PLPUSH 当玩家把我推开。 TLK_STARE 当玩家盯了我一会儿。 TLK_DANGER 当我察觉周围有危险(比如手雷)。 TLK_WOUND 当我已经受伤。 TLK_HIDEANDRELOAD 当我打算藏起来然后换弹。 TLK_PLYR_PHYSATK 当我被玩家扔过来的物体砸中了。
不是所有的 NPC 都会说所有的概念,也不是所有的 NPC 在相同的情况下都会说概念。有关完整列表,请参阅响应概念列表 。
标准集(Criteria Set)
标准(Criteria)是一组条件,其中包含与对话者的当前状态相关的数据,以及每当请求尝试进行对话时概念的情况。它可以被解释为一组 键值 。以下是一个由npc_alyx
创建的标准集的例子,她试图说话因为她刚刚杀死了当前的敌人:
concept = TLK_ENEMY_DEAD NPC 尝试说话的概念。 map = d3_c17_07 当前地图的名称。 classname = npc_alyx 正在说话的NPC的classname 。 name = alyx 正在说话的NPC的targetname 。 health = 75 正在说话的NPC的生命值。 healthfrac = 0.9375 正在说话的NPC的生命值与它最大生命值的比值(默认 npc_alyx 的最大生命值是80)。 skill.cfg = 1 目前的 skill 级别。 timesinceseenplayer = 0.090000 从正在说话的NPC看见玩家时过去的时间。 distancetoenemy = 312.639679 正在说话的NPC距它当前的敌人的距离。 activity = ACT_RUN 正在说话的NPC正在执行的动画活动。 npcstate = [NPCState::Combat] 正在说话的NPC的 AI 状态。 enemy = npc_combine_s 正在说话的NPC的当前敌人的classname 。 speed = 79.235 正在说话的NPC的移动速度。 weapon = weapon_alyxgun 正在说话的NPC握着的武器。 distancetoplayer = 211.240692 正在说话的NPC距玩家的距离。 seeplayer = 1 正在说话的NPC能否看见玩家。 seenbyplayer = 0 正在说话的NPC是否在玩家的视野内。 readiness = agitated 正在说话的NPC的readiness 级别。 playerhealth = 100 玩家目前生命值。 playerhealthfrac = 1.000 玩家目前生命值的生命值与它最大生命值的比值。 playerweapon = weapon_shotgun 玩家握着的武器。 playeractivity = ACT_WALK 玩家正在执行的动画活动。 playerspeed = 0.000 玩家的移动速度。
这个概念默认没有任何修改器。上列表中所有的标准都是通用的且是为每个概念收集的。
上面列表中的标准可以通过规则的标准列表进行检查,并用于决定将哪个响应组用于所需概念。比如说:
- “enemy(敌人)”标准可用于选择对 TLK_ENEMY_DEAD 概念的正确响应。艾利克斯(Alyx)可以说“I took care of that soldier! 我解决了那个士兵!”或“I took care of that headcrab! 我解决了那个猎头蟹!”而不是做一般性说话。
- 如果她(艾利克斯)在击杀敌人时生命值小于20%,healthfrac 字段(field)可用于选择“Phew, that was close! 唷,太险了!”一句。
- distancetoenemy 字段可用于她在远距离或近距离杀死敌人时选择不同的语句。比如说“Those guys are scary when they get that close! 那些人靠得那么近就很可怕!”或“It's not easy hitting 'em at that range. 在那个范围内击中他们并不容易。”
尽管上面列出的标准是一般性的而不是针对概念而设置的,但标准在不同的情况下总是会有所不同,并且可能并不总是可用(比如说不在战斗中的 NPC 不会有 enemy 或 distancetoenemy 标准)。此外,地图制作者可以为特定的 NPC 或游戏中的所有 NPC 附加额外的标准。有关详细信息,请参阅 #Response contexts。
规则标准
根据规则是有一个标准列表的,这些标准会根据人物的标准集进行测试。当规则得到分数(point)时,会根据给定数据检查每个标准,并且该规则会收到成功匹配的标准的分数(point)。用于规则的标准获得分数由标准的权重决定。
标准在脚本文档中的定义格式(见下文):
criterion <criterion name> <key to check> <desired value> <optional: weight X> <optional: required>
参数如下:
- criterion name
- 不得与现有标准匹配。
- key to check
- 该标准将检查的人物标准集中的键值。
- desired value
- 条件集中的键的请求值。以下是使用格式:
- Numeric values: "0", "1", or "100".
- Inverse Numeric values: "!=0" (如果值不等于 0 则匹配)
- String value: "npc_alyx", "weapon_alyxgun", or "npc_combine_s".
- Enumerated value: "[NPCState::Combat]".
- 范围(Ranges):
- ">0" : 如果值大于 0,则匹配
- "<=0.5" : 如果值小于或等于 0.5,则匹配
- ">10,<=50" : 如果值大于 10 且小于或等于 50,则匹配
- ">0,<[NPCState::Alert] : 如果值大于 0 且小于 NPCState::Alert 的枚举值,则匹配。
注意:这默认不支持通配符。
- weight X
- 这是一个可选参数,其中 X 是该标准在匹配时值所得的分数(point)。如果未指定,则标准默认为 1 point。
- required
- 这是一个可选参数,要想使用rules containing,则得需要此标准。如果所需标准未成功匹配,则包含它的规则所得分数会为0并立即跳过。大多数标准都使用此参数。
关于半条命2的一些实例:
- 下面的例子中定义了一个名为PlayerNear的标准 (这是用于检查与确保玩家在以对话NPC以中心500格的范围内)
criterion "PlayerNear" "distancetoplayer" "<500" required
- 下面的例子中则定义了一个名为IsCitizen的标准 (这是用于检查与确保说话的NPC是
npc_citizen
)
criterion "IsCitizen" "classname" "npc_citizen" "required"
- 下面的例中定义了一个名为IsMap_d3_c17_12的标准,(这是用于检查与确保游戏当在d3_c17_12.bsp上。这也有助于使一张地图中的所有citizen说出与其他地图不同的台词)
criterion "IsMap_d3_c17_12" "map" "d3_c17_12" "required"
- 下面的例子中定义了一个名为IsBob的标准,(这是用于检查与确保说话的NPC的目标名称为“bob”。这是游戏中独特的citizen,这个标准很容易让他说出特定的台词)
criterion "IsBob" "targetname" "bob" required
![Tip.png](/w/images/thumb/4/45/Tip.png/9px-Tip.png)
规则
一个规则 (Rule) 包括标准列表和至少一个响应组。该规则为成功匹配演讲者的标准集的每个标准接收分数。得分最高的规则将指向其中一个响应组,该响应组用于确定NPC将使用的确切语音。规则是在脚本文件中定义的(见下文)。使用以下格式:
rule <rule name> { criteria <criterion name 1> [optional: <criterion name 2> <criterion name 3> etc.] response <response group name> [optional: <response group name 2> etc.] [optional: matchonce] [optional: applyContext <data>] }
参数如下:
- rule name : 规则的名称。不得与现有规则匹配。
- criteria : 规则应根据的标准集进行评分。
- response : 如果此规则得分最高,则应选择的响应组列表。
- matchonce : 一个可选参数,如果指定了该参数,则一旦选择该规则一次,该参数将导致该规则被停用。
- applyContext : 应用响应上下文的可选参数。有关详细信息,请参阅#响应上下文。
例如,以下文本定义了一个名为 CitizenTalkStare 的规则。ConceptTalkStare 是一个检查说话的 NPC 想说的概念是否为“TLK_STARE”的标准。IsCitizen 是一个检查说话的 NPC 是否是公民 的标准。NPCIdle 是一个检查以确保 NPC 的状态为“NPCState::Idle”的标准。如果此规则得分最高,则将使用的响应组为 CitizenTalkStare。
rule CitizenTalkStare { criteria ConceptTalkStare IsCitizen NPCIdle response CitizenTalkStare }
请注意,规则名称和响应组名称可以是相同的,因为规则名称只需要在规则之间唯一,而响应组名称只需在组之间唯一。
响应组
一个响应组包含一组可能的响应,以及一些可选的数据,用于定义如何使用这些响应。当规则选择了一个响应组时,从列表中选择一个响应并交给发言者使用。
响应组是在脚本文件内定义的(见下文)。使用以下格式:
response <response name> { [optional: permitrepeats] [optional: sequential] [optional: norepeat] <response type> <response> <optional: ...> <response type> <response> <optional: ...> <response type> <response> <optional: ...> }
响应组的参数如下:
- permitrepeats:如果指定,该组中的响应可以重复。如果未指定,默认行为是在重复任何响应之前使用列表中的所有响应。
- sequential:如果指定,将按照在组中列出的顺序使用响应。如果未指定,默认行为是从列表中随机选择响应。
- norepeat:如果指定,一旦播放了列表中的所有响应,响应组将被禁用。选择该响应组的任何规则都将对发言者返回无响应。
响应
响应是由系统选择然后由说话者使用的实际响应。响应组可以根据需要列出尽可能多的响应,每个响应都应该以下类型中的一个:
- speak : 该响应是一个音效脚本 或原始音频文件。
- sentence : 该响应是
sentences.txt
中的一段句子 - scene : 该响应是一个
.vcd
文件。详见场景编排的实现 。 - response : The response is a reference to another response group which should be selected instead.
- print : 该响应是一段在
developer 1
模式下位于说话者位置生成的占位用响应。 - entityio (存在于自
以来)[证实] : 该响应是一个I/O事件 ,它以说话者作为触发者(activator)对特定的实体触发IO事件,格式为
entityio "<name> <input> <param>"
。这与Followup Responses不一样,后者将会在下文进行介绍
响应后延迟参数
每个响应都支持各种可选参数。
- nodelay:在响应完成后,允许发言者立即再次发言。
- defaultdelay:在响应完成后,发言者将在2.8到3.2秒之间的随机时间内不被允许发言。
- delay X:在响应完成后,发言者将在X秒内不被允许发言。X也可以是一个范围,例如"5.5,7.5"。
- weapondelay X:当响应开始时,发言者将在X秒内不能开火武器。默认仅在HL2 NPC盟友上可用。
- speakonce:防止重复使用响应。
- odds X:如果指定,选择此响应时,发言者有可能保持沉默而不是说出响应。odds 是取消响应的机会(0-100%),例如25表示不说话的几率为25%。
- respeakdelay:如果指定,除非概念在至少X秒内没有被说过,否则不能使用此响应。X也可以是一个范围,例如"5.5,7.5"。
- soundlevel:如果指定,应该在响应中使用该声音级别,而不是默认的SNDLVL_TALKING。
- displayfirst:如果指定,应首先使用此响应(忽略权重参数)。
- displaylast:如果指定,应最后使用此响应(忽略权重参数)。
- weight:如果指定,用于加权选择列表中的响应。默认情况下,所有响应的权重均为1。请注意,响应在选择了所有其他响应之前不会重复,这意味着一旦选择了所有高权重的响应,系统将只计算权重较低的响应。可以通过permitrepeats来抵消这一点。
- noscene:阻止响应系统为speak响应创建自动生成的场景。
- stop_on_nonidle:当响应被说出时,当NPC进入非空闲状态时停止场景。在Source 2013中,仅在HL2剧集模组中的scene响应上起作用。
- predelay:当选择响应时,直到过了X秒才会实际说出来。仅在scene响应上起作用。X也可以是一个范围,例如"5.5,7.5"。
例如,下面的响应组由市民 用于回应TLK_STARE概念。市民将按照它们在列表中的顺序使用响应(由于 sequential 参数)。每个被选择的响应都会停止NPC发言一段随机时间,介于10到20秒之间。
response "CitizenTalkStare" { sequential scene "scenes/npc/$gender01/doingsomething.vcd" delay "10,20" scene "scenes/npc/$gender01/getgoingsoon.vcd" delay "10,20" scene "scenes/npc/$gender01/waitingsomebody.vcd" delay "10,20" }
脚本文件
/scripts/talker/response_rules.txt
是基本脚本文件,包含响应规则系统使用的所有条件、规则和响应组。该文件还可以包括使用 #include 关键字的其他文件,这允许你根据 NPC、地图等干净地划分规则。请注意,一些实体,如 env_speaker
,指定了自己的脚本文件,其中包含实体要使用的条件、规则和响应组的子集。有关火车站终端公告使用的示例,请参见 scripts/talker/terminal_pa.txt
。
响应上下文
响应上下文类似于地图作者设置的标准。它们通过“Response Contexts”(ResponseContext
)键值或AddContext
输入进行设置,格式如下:key:value;key:value;key:value;
……其中key是“键”,value是“值”。
![Blank image.png](/w/images/b/ba/Blank_image.png)
后续响应
求生之路引入了“后续响应”,即在响应后发生的事件。它们被触发为响应参数,类似于odds、predelay等。
![](/w/images/5/53/Cpp-16px.png)
- fire:通过I/O系统使用发言者作为激活器和调用者触发一个输入。格式为
fire <target> <input> <delay>
。不支持参数。 - then:导致在实体上分派另一个响应。格式为
then <target> <concept> <response contexts> <delay>
。用于使角色动态地相互响应。
![Note.png](/w/images/thumb/c/cc/Note.png/10px-Note.png)
可以使用一些独特的目标名称:
- self:发言响应的实体。
- subject:使用发言者“Subject”上下文中找到的实体名称(如果存在)。游戏为
info_remarkable
响应设置了这个。 - from:使用发言者“From”上下文中找到的实体名称(如果存在)。游戏将其设置为上一个后续响应者的名称,允许后续响应来回弹跳。
- any:分派到
rr_followup_maxdist
(默认为1800)中存储的任何有效响应者。 - all:分派到
rr_followup_maxdist
(默认为1800)中存储的所有有效响应者。
![Note.png](/w/images/thumb/c/cc/Note.png/10px-Note.png)
来自Left 4 Dead 2中的示例coach.txt
:
Response _c1m4startelevator4bCoach { scene "scenes/Coach/WorldC1M4B01.vcd" then mechanic _c1m4startelevator5a foo:0 -2.313 //Son, you got a DEAL. scene "scenes/Coach/WorldC1M4B02.vcd" then mechanic _c1m4startelevator5a foo:0 -5.790 //Ha HA! All the way to New Orleans! Baby, that sounds like a PLAN. scene "scenes/Coach/WorldC1M4B05.vcd" then mechanic _c1m4startelevator5b foo:0 -6.334 //Normally I wouldn't do this. But in these circumstances, I think Mr. Gibbs, Jr. ain't gonna mind. scene "scenes/Coach/WorldC1M4B10.vcd" then mechanic _c1m4startelevator5b foo:0 -2.685 //Forgive us Jimmy, but we need your car. }
高级响应规则的使用
响应系统可以以许多高级和专业的方式使用。以下是一些用于系统高级用途的技巧和窍门:
- 使用
DispatchResponse
触发自定义响应 - 实际上,任何可以在脚本文件中找到的响应概念都可以传递给NPC,只要它不限于AI预定义的TLK_*概念。
- 例如,您可能希望在玩家成功解决谜题的一部分时,旁观组中的NPC发表祝贺的响应概念。
- HL2中的玩家盟友还具有更高级处理和条件的
SpeakResponseConcept
输入。
查看EP1/EP2中Alyx的响应脚本或L4D幸存者的响应脚本,以获取更多高级响应系统使用示例。
调试
![Blank image.png](/w/images/b/ba/Blank_image.png)
注意
- 要能够使用响应系统,必须使用由 CAI_BaseActor 类派生的 NPC。
- 参见
scripts/talker/npc_*.txt
文件以获取特定 NPC 响应规则的示例。 scripts/talker/response_rules.txt
是响应规则系统的列表。如果为新 NPC 添加新的响应规则脚本文件,请确保在scripts/talker/response_rules.txt
文件的末尾通过 #include 语句包括该文件。
另请参阅
(译注:以下链接链接到英文页面)
外部链接
- Elan Ruskin 在 GDC 2012 上的“用于上下文对话和游戏逻辑的规则数据库”演示文稿,它描述了响应系统及其演变。 (幻灯片:[1])
- Two Bots One Wrench: Environmentally Responsive Speech Video, 演示视频 #1
- Two Bots One Wrench: Memory And Context, 演示视频 #2
- Two Bots One Wrench: Conversation, 演示视频 #3