Left 4 Dead 2

Left 4 Dead 2

Otillräckligt med betyg
如何制作一个变异模式
Av Mono
该指南包含一份新手起步教程,以及一些进阶的参考
懒得编程可以翻到最后,我写了一个小工具
阅读本指南要求一定的面向对象编程知识
   
Utmärkelse
Favorit
Favoritmarkerad
Avfavoritmarkerad
前置知识:VScript系统
在Left 4 Dead 2(下称L4D2)中引入的VScript系统是用于脚本编写的虚拟机,它充当Source引擎和外部脚本之间的抽象绑定层。其基于Squirrel语言开发,并提供一些访问、修改游戏数据的接口。
Squirrel语言的官方网站:squirrel-lang[squirrel-lang.org]
(本文不需要你直接去学习Squirrel,我们将在后面的实例中学会它的基本应用)

根据用途,VScript脚本可分为以下4种:

1. 导演脚本(Director Scripts)
VScript最常见的用法,用于影响导演系统(AI Director)的行为。这种脚本的应用范围很广,从控制感染者刷新(普通僵尸、特感、Tank、Witch等)到自定义事件(如天气状况、飞机轰炸),以及复杂的阶段性尸潮和自定义结局。一局游戏中的大多数活动都是以这种方式实现的。

导演脚本的工作原理主要是重写导演系统使用的一些变量,将其写入DirectorOptions表中。导演系统将检查这张表中是否有内容,并根据这些值改变自身的默认行为。

一次只能运行一个导演脚本。执行一个新的导演脚本将终止当前正在运行的脚本,并删除它在DirectorOptions中设置的所有值。

2. 实体脚本(Entity Scripts)
另一个常见用途是将脚本附加到实体。该脚本可以访问、读取、添加和修改实体的各种属性。

任何实体都可以附加一个脚本,并且能够为它指定Think函数(每0.1秒在实体上自动运行一次的函数,如普通僵尸的Think就是寻找并攻击生还者,参考Valve开发者文档)。

一些实体还具有VScripts的特殊功能,其中最常见的例子是point_script_use_target,它允许将实体变成可触发特定事件的按钮。

(本文不涉及实体脚本的运用)

3. 全局脚本(Global Scripts)
可以根据游戏模式、地图名称使得脚本在地图加载时运行。这些脚本通常用在突变模式和第三方地图中,用于设置一些必要的游戏环境。

全局脚本可以绑定钩子函数(Hook),在特定的事件触发时被调用(例如玩家/对象受到伤害时)。

全局脚本有许多实用程序功能和特性,本文将重点介绍这类脚本的运用

4. 其他
VScript可用在其他一些地方,如HUD显示,或在关卡之间存储一些数据,此处不再赘述。

---------------------------------
脚本文件位置
VScript以 .nut(纯文本) 或 .nuc(加密格式) 为后缀。在VPK文件中,VScript位于scripts/vscripts/目录下。官方战役的一些脚本可在这些地方找到:
  • left 4 dead 2/left4dead2/pak01_dir.vpk
  • left 4 dead 2/left4dead2_dlc1/pak01_dir.vpk
  • left 4 dead 2/left4dead2_dlc2/pak01_dir.vpk
  • left 4 dead 2/left4dead2_dlc3/pak01_dir.vpk
  • left 4 dead 2/update/pak01_dir.vpk
  • left 4 dead 2/sdk_content/scripting/scripts/vscripts/
这些官方战役脚本通常是加密的 .nuc 格式,可以到这里下载其纯文本格式:Decompiled VScripts[www.dropbox.com]

准备工具:VSLib + 编辑器
VSLib (VScript Library) 是一个轻量级的函数库,封装了一些VScript的调用接口。使用VSLib可以极大地简化我们的代码编写。大名鼎鼎的 Admin System 就是使用VSLib开发的。

到这里下载VSLib:L4D2Scripters/vslib[github.com]

你可以自由选择用于编辑 .nut 文件的编辑器,这里我使用的是 Sublime Text 3,它具有支持Squirrel语言高亮的插件:



准备工作:构建我们的第一个变异模式
建立目录
  • Left 4 Dead 2/left4dead2/addons/ 目录下(即你存放附加战役的地方)建立一个文件夹,叫做 mymutation
  • 进入 mymutation 目录,之后所有相对路径都将以此作为参照
  • 建立 modes/scripts/vscripts/目录,将VSLib解压至后者中
为防止歧义,用 Shell Script 语法描述一遍:
cd "Left 4 Dead 2/left4dead2/addons/" mkdir mymutation && cd $_ mkdir -p modes/ scripts/vscripts/

解压 VSLib 后的 scripts/vscripts/ 目录应如图所示:

----------------
建立必要文件
  • 在当前目录(即前述的相对路径参照)下建立文件:addoninfo.txt
  • modes/ 下建立文件:mymutation.txt
  • scripts/vscripts/ 下建立文件:mymutation.nut

./addoninfo.txt 的内容:
"AddonInfo" { addonSteamAppID 550 addontitle "My Mutation" addonContent_Script 1 addonauthor "myself" addonDescription "This is my first mutation game" }

modes/mymutation.txt 的内容:
"mymutation" { "base" "coop" "maxplayers" "4" "DisplayTitle" "My Mutation" "Description" "This is my first mutation game" "Image" "maps/any" "Author" "myself" }


脚本编写:Malicious Boomer
建立 scripts/vscripts/mymutation.nut,这就是我们变异模式的核心脚本。

我们做一个什么样的变异模式好呢?

我这里直接拿我之前做的一个简单的变异模式作例子:Malicious Boomer,源代码可以在这里获取:typowritter/L4D2-Mutations[github.com]

简单说明一下变异模式规则:
  1. 没有普通僵尸,平时只有Boomer
  2. 一旦被胆汁吐中,会引来一波特感尸潮,包含除了Boomer、Witch之外的所有种类

下面,我将从零开始,演示如何编写出这个变异模式脚本。

正式开始
首先,我们得引入 VSLib 函数库:
IncludeScript( "VSLib" );

接下来我们设定一下导演系统:
MutationOptions <- { CommonLimit = 0 // 普通僵尸数量上限设为0,即禁止其刷新。以下两行都在做这件事情 MegaMobSize = 0 WanderingZombieDensityModifier = 0 MaxSpecials = 30 // 在场特感最多为30只(实际要远远小于) TankLimit = 5 // 在场Tank最多为5只,下面几行同理 WitchLimit = 0 BoomerLimit = 30 ChargerLimit = 5 HunterLimit = 5 JockeyLimit = 5 SpitterLimit = 5 SmokerLimit = 5 SpecialRespawnInterval = 5 // 特感死亡至少5秒后才可重生 TotalSpecials = 25 // 一波尸潮中最多可重生25只特感 TotalBoomer = 15 // 一波尸潮中最多可重生15只Boomer }

上述代码将一些设定值写到了 MutationOptions 表中。在加载地图时,导演系统会先读取DirectorOptions,再读取 MutationOptions,所以在后者中设定的值优先级会较高。

我们想控制游戏节奏在3种状态中切换:
  • 平静状态:只有Boomer,其他感染者都不要刷新
  • 兴奋状态:被胆汁吐中,停止刷新Boomer,开始大量刷新其他特感,持续一段时间(20秒)
  • 休息状态:兴奋持续20秒后,进入10秒的休息期,期间停止所有感染者刷新,并且即使被残余的Boomer吐中也不会进入兴奋状态,以便生还者恢复状态

那么显然,我们需要一些全局变量来保存当前状态,并且使用定时器来控制:
::ArrSpecialInfected <- [ Z_TANK, Z_CHARGER, // 兴奋状态会刷新的特感Array Z_HUNTER, Z_JOCKEY, Z_SPITTER, Z_SMOKER ] RoundVars.DidGetVomited <- false; // 是否被吐中(处于兴奋状态) RoundVars.DuringRelax <- false; // 是否处于休息状态 ::BoomTimer <- -1; ::RelaxTimer <- -1;

既然定义了3个状态,我们需要一些函数来进行状态切换:
// 兴奋状态到休息状态 ::Dropout <- function(params) { RoundVars.DidGetVomited <- false; RoundVars.DuringRelax <- true; // 移除控制兴奋状态的Timer Timers.RemoveTimer(BoomTimer); // 进入10秒钟的休息状态,10秒后调用 ResetRound 函数 RelaxTimer <- Timers.AddTimer(10.0, false, ResetRound); // Utils.SayToAll("Dropout was called."); } // 休息状态到平静状态 ::ResetRound <- function(params) { // 移除控制休息状态的Timer,并退出休息状态 Timers.RemoveTimer(RelaxTimer); RoundVars.DuringRelax <- false; // Utils.SayToAll("ResetRound was called."); } // 回调函数,由OnPlayerVomited可看出,该函数会在生还者被Boomer吐中后触发 function Notifications::OnPlayerVomited::ChangeDidGetVomited(victim, boomer, params) { // 如果处于平静状态 if (RoundVars.DidGetVomited == false && RoundVars.DuringRelax == false) { // 被吐中 RoundVars.DidGetVomited <- true; // 进入20秒的兴奋状态,20秒后调用 Dropout 函数 BoomTimer <- Timers.AddTimer(20.0, false, Dropout); // Utils.SayToAll("DidGetVomited was called."); } }

我们现在已经能在状态之间自由切换了,接下来需要一个函数来控制特感的刷新:
::SpawnInfected <- function (params) { // 休息状态不刷新特感,直接返回 if (RoundVars.DuringRelax) { return; } // 遍历每一个生还者 foreach (player in Players.AliveSurvivors()) { // 如果当前节奏处于兴奋状态 if (RoundVars.DidGetVomited) { // 从之前定义的Array中随机选一种特感出来 SI <- Utils.GetRandValueFromArray(ArrSpecialInfected); // 在该玩家附近重生一只特感 Utils.SpawnZombieNearPlayer(player, SI, 800, 512); } // 如果处于平静状态 else { // 在该玩家附近重生一只Boomer Utils.SpawnZombieNearPlayer(player, Z_BOOMER, 800, 512); } } }

OK,我们几乎已经快完成了,接下来只需要将 SpawnInfected 函数绑定到一个不断运行的Timer上,变异模式就能开始正常运转了:
// 回调函数,玩家离开安全区时被调用 function Notifications::OnSurvivorsLeftStartArea::StartTheTimer() { // 每隔5秒就运行一次 SpawnInfected,不断重复 Timers.AddTimer(5.0, true, SpawnInfected); // 给每个玩家显示一条消息 Utils.SayToAll("Boomer feast has begun!"); }

完成!以下是全部代码:(Steam没高亮我就直接放图片了)


最后一步:打包为VPK格式
为了使我们的变异模式能够像普通的附加组件那样被加载,我们需要将它打包成通用的VPK格式:

Windows
找到 Left 4 Dead 2\bin\vpk.exe,将 mymutation 文件夹拖到 vpk.exe 上,就会在 addons 目录下生成 mymutation.vpk,至此,就可以在游戏中正常游玩这个模式了。

Linux/macOS
看我的另一篇指南:Linux Dev Toolchain Collection,里面有 VPK 打包工具。

可以看到,游戏成功识别了我们的变异模式:



-----

相应的map建图指令是:
map xxx mymutation


更多参考
Valve 开发者社区:Left 4 Dead 2 Level Creation

VSLib 文档:Simple, powerful VScript Library for L4D2[l4d2scripters.github.io]

Rayman1103 大佬的一些指南:Rayman1103 - My Content[www.gamemaps.com]

我做的一个变异模式生成器:l4d2-mutation-creator[github.com],有B站演示视频

讨论 L4D2 Mod 开发的 Discord 群组:Dead4Mods[discord.gg]