目录

什么是userdata

在饥荒中,有一些东西是可以找到代码定义的:

但是也有一些东西只能找到使用它的地方,而找不到定义位置:

如果更仔细一点研究,可以发现TheNet, TheSim, WorldSim 这些东西的类型不是table,而是userdata。

print(type(TheNet))
userdata

饥荒用的代码是c++和Lua,userdata其实就是写在了c++部分,所以看不见定义。

userdata不是表,不能直接用pairs和ipairs遍历。

for k,v in pairs(TheNet)do
    print(k,v)
end
bad argument #1 to 'pairs' (table expected, got userdata)

查看userdata方法

你可以查看userdata的所有函数名字,注意这里使用了元表相关操作:

(不懂元表也没关系,知道代码怎么写就行)

for k,v in pairs(getmetatable(TheNet).__index)do
    print(k,v)
end
GetDefaultMaxPlayers	function: 0x6000005d88c0	
SetIsMatchStarting	function: 0x6000005da840	
GetServerIsDedicated	function: 0x6000005d9c00	
PrintNetwork	function: 0x6000005df680	
SetDefaultMaxPlayers	function: 0x6000005d8900	
ViewNetFriends	function: 0x6000005d9d40	
SetDefaultGameMode	function: 0x6000005d8540	
GetClientMetricsForUser	function: 0x6000005d9940	
GetServerIsClientHosted	function: 0x6000005d9c40	
TruncateSnapshotsInClusterSlot	function: 0x6000005d9640	
SystemMessage	function: 0x6000005df740	
GetWorldSessionFile	function: 0x6000005d9180	
SendRPCToClient	function: 0x6000005deec0
...(还有一大堆)

虽然能看到函数名字,但是看不了源代码定义,具体用法需要结合函数名和官方使用案例去理解。

修改userdata方法

你可以修改现有的userdata里面的函数,也可以定义一个自己的函数。

函数的第一个参数是userdata自身。

创建新函数

写一个新的函数,叫TheNet:PrintStatus()

getmetatable(TheNet).__index.PrintStatus = function(net)
    print("Is Server:", net:GetIsServer())
    print("Is Client:", net:GetIsClient())
    print("Is Dedicated", net:IsDedicated())
end
 
TheNet:PrintStatus()

因为我是在游戏主界面运行的,所以结果都是false。

Is Server:	false	
Is Client:	false	
Is Dedicated	false	

修改已有函数

TheSim:FindEntities()函数每次被调用的时候,都打印搜索到的实体数量。

local mt = getmetatable(TheSim).__index
local old_FindEntities = mt.FindEntites
mt.FindEntities = function(sim, ...)
    local ents = old_FindEntities(sim, ...)
    print("查找到"..#ents.."个实体!")
    return ents
end

小技巧

如果要修改AnimState, Physics, Transform 之类的函数,常规操作是获取元表修改:

local inst = CreateEntity()
inst.entity:AddTransform()
inst.entity:AddAnimState()
 
local mt = getmetatable(inst.AnimState).__index
function mt.PlayAndPush(anim, name1, name2)
    anim:PlayAnimation(name1)
    anim:PushAnimation(name2)
end

但其实klei已经把这个表放到全局变量里了,你可以直接修改:

function AnimState.PlayAndPush(anim, name1, name2)
    anim:PlayAnimation(name1)
    anim:PushAnimation(name2)
end

然后就能在其他地方用这个新增的PlayAndPush函数了:

local inst = CreateEntity()
inst.entity:AddTransform()
inst.entity:AddAnimState()
 
inst.AnimState:SetBank("wilson")
inst.AnimState:SetBuild("wilson")
inst.AnimState:PlayAndPush("run_start", "run_loop") ---<

完整事例

上述操作都可以在klei源代码里看到。