DST代码总览

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未定义则报错。此时可使用rawgetrawset避免报错,b=_G.rawget(_G,"a")+1,当a未定义时,rawget返回nil。为了避免c=a.banil时报错,可写成c= a~=nil and a.bd=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语句,就可以用modimportrequire获取。
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需要执行时,都会获得优先执行的权利,可以决定怎么执行。

啊哈,这里是小尾巴~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇