KF1 mod 开发教程
用插件改怪物小队
以插件的形式替换游戏中怪物列表,首先要了解怪物列表在哪个类 并且属性叫什么。 打开游戏的 KFMod 源码目录(前提安装了 killing floor SDK)
steam \ steamapps \ common \ KillingFloor \ KFMod \ Classes
可以找到 "游戏类型" 的源代码文件
KFGameType.uc
从面相对象的角度来看 一个文件就是一个类,在 KF1 中 KFGameType 就是默认的生存模式。 里面有5千行代码,根据一些官方文档线索 能确定怪物列表的属性
var array<MSquadsList> InitSquads;
var array<SpecialSquad> SpecialSquads;
var array<SpecialSquad> FinalSquads;
三个属性都是动态数组,分别表示 普通小队表、特殊小队表、最终小队表;
游戏每次是以小队为一个单位产怪,比如 有个小队是 4 clot 执行时就会一次产 4 个 clot;

游戏源码中对怪物小队的初始化比较麻烦,并且有更多的属性、逻辑设计; 当进入地图 准备界面时,这三张小队表已初始化完成,用插件可直接对其修改。
官方插件 - 替换怪物
查看 SDK 提供的源码作为示例,可以研究其参与的属性 和 逻辑
路径:steam \ steamapps \ common \ KillingFloor \ KFMutators \ Classes
文件名:KFClotMut.uc
该插件表示将 普通小队 全替换成 clot,功能虽简单 但代码写的很健全。
KFClotMut.uc 源码如下:
class KFClotMut extends Mutator;

function PostBeginPlay()
{
	SetTimer(0.1,False);
}

function Timer()
{
	local KFGameType KF;
	local byte i;
	local class<KFMonster> MC;
	local int MSquadLength;

	KF = KFGameType(Level.Game);
	MC = Class<KFMonster>(DynamicLoadObject(KF.GetEventClotClassName(),Class'Class'));

	if ( KF!=None && MC!=None )
	{
	    // 小队表长度 和 小队长度
	    KF.InitSquads.Length = 1;
	    MSquadLength = Min( 8, KF.MaxZombiesOnce );

	    // 上面已将 小队表长度设置为 1,接下来设置第一个小队 长度和元素
	    KF.InitSquads[0].MSquad.Length = MSquadLength;
	    for( i=0; i<MSquadLength; i++ )
	        KF.InitSquads[0].MSquad[i] = MC;
	}
	Destroy();
}
以上代码从 PostBeginPlay() 开始执行,里面写了一个计时器,表示地图加载后 0.1秒再执行; 计时器最后有一个 Destroy() 销毁对象自身,表示 插件执行完成后自己销毁 不要了。

计时器开头是局部变量声明,任何局部变量声明都是写在函数开头,如果写在逻辑代码之后 编译时会警告。 局部变量的声明 取决于逻辑如何写,我们先来看整体代码逻辑
  1. 获得关卡的游戏类型,转换成 生存模式(KFGameType)
  2. 根据类名 动态加载"类型",获得 clot 怪物类型(活动事件名,如 圣诞节、普通...)
  3. 验证都能获得 游戏类型和怪物类型
  4. 将普通小队列表 动态数组长度设置为 1,即 只有一种小队
  5. 设置第一种小队的长度
  6. 设置第一种小队中的每个元素为 clot 的类型
根据以上逻辑过程,可以分析出局部变量的作用
local KFGameType KF;		// 接收关卡的游戏类型
local byte i;			// 循环变量,遍历怪物小队
local class<KFMonster> MC;	// 接收 clot 怪物类型(用父类变量接收子类)
local int MSquadLength;		// 用于设置 怪物小队长度
怪物小队设计结构
小队设置分三层:怪物类,怪物小队,怪物小队表 即:KFMonster、MSquad、InitSquads

最底层的是 "怪物类",第二层是 "怪物数组" 叫小队,第三层是 "怪物数组的数组" 即小队表。

最底层的怪物类是一个具体的类文件,而游戏类型中 小队的写法很特别
// 小队结构体
struct MSquadsList
{
	// 小队是个怪物数组
	var array< class<KFMonster> > MSquad;
};

// 普通小队表
var array<MSquadsList> InitSquads;
游戏类中把 "怪物数组" 叫做 MSquad,然后再做成结构体 叫做 MSquadsList; 最终把 怪物数组 再做成数组,就成了 InitSquads

所谓 InitSquads 普通小队表就是:怪物数组的数组,即 怪物数组_结构体 的数组。


与 普通小队结构体 有一点区别,特殊小队表、最终小队表 的写法如下
// 特殊小队结构体
struct SpecialSquad
{
	// 怪物名列表
	var array<string> ZedClass;
	var array<int> NumZeds;
};

// 特殊小队表
var array<SpecialSquad> SpecialSquads;

// 最终小队表
var array<SpecialSquad> FinalSquads;
修改 "最终小队" 的写法比 "普通小队" 少了一个动态加载类的步骤
// 最终小队,双重循环 遍历小队表和小队元素
for (i=0; i<KF.FinalSquads.Length; i++)
{
	for (j=0; j<KF.FinalSquads[i].ZedClass.Length; j++)
	{
	    if (KF.FinalSquads[i].ZedClass[j] == "KFChar.ZEDTOREPLACE")
	        KF.FinalSquads[i].ZedClass[j]= "PACKAGENAME.NEWZED";
	}
}
之前的普通小队表 是被改成了一个小队,如果存在多个小队 还是要用到双循环