1 Lua层环境变量
1.1 全局环境
lua(5.1)中全局环境变量都被储存到表_G
中。
定义全局变量a=1
等效于_G.a=1
或_G["a"]=1
,如果已经存在变量a
则会覆盖a
。
读取全局变量时如b=a+1
等效于_G.b=_G.a+1
,如果a
未定义则报错。此时可使用rawget
,rawset
避免报错,b=_G.rawget(_G,"a")+1
,当a
未定义时,rawget
返回nil。为了避免c=a.b
中a
为nil
时报错,可写成c= a~=nil and a.b
,d=a.b.c
可写成d=a and a.b and a.b.c
。
代码中位于_G
的函数都不需要加_G
的前缀,除非特别要求访问_G
.
1.1.1 创建和使用全局变量
--声明
global("a")
--声明并初始化
a=nil
--使用rawset
_G.rawset(_G,"a",nil)
--全局环境赋值
a=1
--安全地赋值
_G.rawset(_G,"a",1)
--函数内使用
function fn()
--声明并创建全局变量,global函数由Klei定义
global("b");
b=2
--直接使用现有全局变量
a=3
--不可以不声明就使用(函数内要加local)
--c=4
--声明并使用局部变量
local c=4
end
注:可以关闭全局环境的严格模式
local strict = getmetatable(_G)
if strict and strict.__index then
--Klei在_G的元表里放了一个assert,会导致使用未声明的全局变量报错
strick_check.__index=nil
end
或者可以关闭assert(仅用于开发)
assert = print
1.1.2 修改全局变量
直接赋值即可
'TUNING.GREENAMULET_USES=100'建造护符能使用100次
1.2 局部环境
所有用local
关键词定义的变量都不在_G
里,比如local a=1 print(_G.a)
,输出variable 'a' is not decalared
。此时a
作为一个localvalue
存在,无法通过正常手段在其他环境中获取。其中存在于当前函数体外的局部变量,称为upvalue
(上值),可以用debug库获取
--声明
local a
--声明并初始化
local b=nil
--声明并稍后赋值
local c
local function print_c()
print(c)
end
c=1
--不允许未声明使用
--print(d)
--local d=""
1.2.1 修改局部变量
一般使用覆盖法,将源代码赋值一份,修改一部分。
将hats.lua复制一份到mod文件夹,然后修改。
当这个局部变量所在的函数能够追溯到某个全局函数时,将这个全局函数及相关代码全部复制一份,再按照修改全局变量的方法操作
1.2.2 修改上值
local function CommandRetarget(...)end
local function DefensiveRetarget(...)
CommandRetarget()
end
local function OnAttacked(...)
DefensiveRetarget()
end
local function fn()
inst:ListenForEvent("attacked",OnAttacked)
end
return Prefab("abigail", fn, assets, prefabs)
在abigail.lua中,fn是最终能够从全局访问的局部函数,其余所有局部函数都不能直接访问,因此,使用覆盖法,将abigail.lua复制一份,然后修改。
但是由于这些局部函数每个都位于函数体外,所以它们都是上值。
OnAttacked是fn的上值,DefensiveRetarget是OnAttacked的上值,...。于是可以引出一条链:Prefabs.abigail.fun->OnAttacked->DefensiveRetarget->CommonRetarget,这时可以用debug库修改。
1.3 类属性
将全局变量或局部变量挂到一个类下面,它就成为类的属性。
local function OnAttacked(...)end
local function fn()
inst.OnAttacked=OnAttacked
inst:ListenForEvent("attacked",OnAttacked)
end
在这种情况下,这个变量就可以通过类直接访问了。
可以直接访问
inst.components.equippable.equipslot
,即使它的值是一个局部字符串。
1.4 mod环境
Klei为每个mod设置了不同的环境env
,并提供了GLOBAL
环境用来访问_G
,可以手动更改mod环境
GLOBAL.setmetatable(env,{
__index = function(t, k)
return GLOBAL.rawget(GLOBAL, k)
end,
})
如此就可以使用xxx
直接读取GLOBAL
里的变量,而不需要GLOBAL.xxx
。
在mods.lua中可以看到env
里有什么东西:
local env =
{
-- lua
pairs = pairs,
ipairs = ipairs,
print = print,
math = math,
table = table,
type = type,
string = string,
tostring = tostring,
require = require,
Class = Class,
-- runtime
TUNING=TUNING,
-- worldgen
LEVELCATEGORY = LEVELCATEGORY,
GROUND = GROUND,
WORLD_TILES = WORLD_TILES,
LOCKS = LOCKS,
KEYS = KEYS,
LEVELTYPE = LEVELTYPE,
-- utility
GLOBAL = _G,
modname = modname,
MODROOT = MODS_ROOT..modname.."/",
}
env.env=env
env.modimport=function(modulename)end
在modutils.lua中定义了大量函数供调用。这些函数是mod最重要的工具,因为它们提供了官方预留的访问局部变量的方法。下面是将会用到的:
AddGamePostInit
AddClassPostConstruct
AddComponentPostInit
AddPrefabPostInit
1.5 文件
如果一个文件的结尾是一个return语句,就可以用modimport
或require
获取。
require
默认的根目录是scripts/文件夹和所有的mod的scripts/文件夹。它返回该文件return
的值或true
。
modimport
函数运行本mod文件夹下某个文件但是不返回东西,本质是kleiloadlua
包装了一下。
--假设有一个scripts/a.lua
require("a")
--假设mod文件夹有一个scripts/a.lua
modimport("scripts/a.lua")
--假设mod文件夹有一个b.txt
modimport("b.txt")
2 通用修改策略
2.1 装饰器模式
装饰器是一个能够为原函数添加新功能的函数。如果用oldfn
指代原函数,newfn
指代新函数。
- 让newfn在oldfn之前执行
function wrapper(oldfn, newfn)
return function(...)
newfn(...)
return oldfn(...)
end
end
- 让newfn选择性地替换oldfn执行,要求newfn执行成功就不执行oldfn
function wrapper_selective(oldfn, newfn)
return function(...)
local ret = (newfn(...))
if ret then--ret[1]
return unpack(ret)
else
return oldfn(...)
end
end
end
- 在oldfn执行之前替换参数
function wrapper_modify(oldfn, newfn)
return function(...)
return oldfn(newfn(...))
end
end
- 让oldfn只执行一次,之后全部执行newfn
function wrapper_once(oldfn, newfn)
locak fn = nil
return function(...)
if fn then
return fn(...)
else
fn = newfn
newfn = nil
return oldfn(...)
end
end
end
2.2 监听器模式
这个模式对应的方法在EntityScript里实现了。用inst
指代一个实例。
--发送事件
inst:PushEvent("death")
--监听事件
inst:ListenForEvent("death",function(inst)
print(i"dead")
end)
--监听与取消监听事件
inst.ondeath = print
inst:ListenForEvent("death",inst.ondeath)
inst:RemoveEventCallback("death",inst.ondeath)
事件的名词可自定义,无官方文档,可以搜索官方使用的事件名称
2.3 钩子(hook)技术
使用装饰器模式装饰一个函数。每当这个函数调用时你都有机会执行一次。
通用的写法:
local oldxxx = xxx
local newxxx = function(...)
local a = oldxxx(...)
return a + 1
end
xxx = newxxx
这样一来,每次xxx需要执行时,都会获得优先执行的权利,可以决定怎么执行。