写 mod 时遇到个 lua 的问题,写了一千多字问题描述,把思路理死了。。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Game Engines
Unreal Engine
MyCryENGINE
etwxr9
V2EX    游戏开发

写 mod 时遇到个 lua 的问题,写了一千多字问题描述,把思路理死了。。

  •  
  •   etwxr9 2021-06-15 15:13:03 +08:00 3196 次点击
    这是一个创建于 1577 天前的主题,其中的信息可能已经有所发展或是发生改变。

    试图用小黄鸭法理顺思路,但是最终结果是真的没搞懂。

    我在写一个 mc 的服务端插件,它用的是 java 语言。但是为了能够让插件使用者能够自定义一些游戏物品功能逻辑,我加入了 lua 作为脚本语言。

    比如可以写一个盾牌物品的脚本 shield.lua

     function shield.onPlayerDamage(e) e.SetDamage(0) end 

    意思是当玩家受伤时,将伤害设置为 0

    这个 onPlayerDamage 是游戏中的事件,可以在 java 中订阅。
    在插件初始化的时候,会读取配置目录下所有的物品 lua,并且将每个这样的 lua 函数存入一个管理类中,在插件的相应事件触发时,管理类就会调用 lua 中的同名函数(同时也要在 java 中判断玩家是否具有此盾牌物品,再执行)

    到此为止还是没问题的,但是我希望这些物品能存局部变量,比如

     function shield.onPlayerDamage(e) self.a = self.a - 1 if (self.a <= 0) then e.SetDamage(0) end end 

    这样可以实现计数功能,比如只能抵挡 3 次伤害。
    但是问题就在于,如果有多个玩家同时拥有此盾牌,这些玩家就会共享变量 a,从而产生冲突。

    接下来我试着在一个新盾牌生成时,插件可以以 shield.lua 为原型,New 一个 luaTable,复制 shield.lua 中的所有内容( lua 的 OOP 方式),以此达成每个盾牌实例拥有独立的局部变量,这种情况下,处理每个盾牌的逻辑的并非 shield 这个表,而是一个以 shield 为原型的新表(假定它叫 shield_A )。
    但是这样一来,事件管理类在调用 lua 中事件函数时,就无法调用这个 shield_A,而还是调用的是最初加载的 shield.lua 的函数,从而就无法操作到特定的变量 a

    接下来我试着每当 new 一个 luaTable,就重新在该 luaTable 中查找事件函数,并加入事件管理类
    但是这样一来,事件触发时就必须判断每个 luaTable 对应的是哪个玩家,比如玩家 A 就调用 shield_A,玩家 B 就调用 shield_B 。不然一个玩家受伤,所有盾牌的变量都变了。这就难以做到。
    而且在该盾牌消失之后,我又得把对应的函数从事件管理类中移除,这又难以做到。

    再者,我又遇到另一个问题。我希望这个 luaAPI 具有一些全局函数可供调用,比如 Game.HealPlayer(hp)
    这个 Game 内含一些常用的全局函数。问题比较类似,也是面向对象的。

    Game 不能只有一个,该插件有一个“游戏房间”的概念,每个玩家通过创建游戏房间进行游玩,每个房间游戏中只有一个玩家。
    那么,我希望 Game 是可以处理房间内逻辑和数据的,比如当前房间的难度、计时器等等,这就要求在创建游戏房间时,也得创建一个对应的 Game 实例。我们假设创建了一个游戏房间 A,并且带有一个 Game_A 的 Game.lua 的复制。

    假设我们不仅有 shield.lua ,还有回血药水 potion.lua ,那么 potion.lua 中就要调用 Game.HealPlayer(hp)
    该 potion 自然也是 potion.lua 的一个复制(比如叫做 potion_A ),而它调用的 Game_A 也应该是 Game.lua 的一个复制,并且两者同属于一个游戏房间。那么在 potion.lua 中就不能直接写 Game.HealPlayer(hp),那样调用的就是 Game.lua 原型而不是 Game_A 。

    然而因为游戏房间都是动态创建的,写 potion.lua 中的 API 调用时就没法写出对应的表名。

    所以我卡在这里了,我是看到很多游戏都在用 lua 做 modding 脚本语言我才试着学一下,但是看了几个游戏的 mod 教程,只看到他们就是能做到方便的 lua 逻辑配置,但就是看不出来他们是怎么处理这些问题的。

    5 条回复    2021-07-10 21:26:19 +08:00
    eason1874
        1
    eason1874  
       2021-06-15 16:03:45 +08:00
    没做过 mc mod,做过一些网页游戏,原理应该差不多。

    目测你的第一个问题是把玩家实例属性存到了插件实例上面,然后顺着掉坑里研究起了插件多实例。onPlayerDamage(e) 里的 self 应该是指插件实例吧?如果是这样,问题就是我说的,解决方法就是把计数的变量 a 存到 player 上。看起来传入的 e 就是指 player,怎么存你可以看文档去确定。

    第二个问题其实类似。每个房间创建一个实例,玩家进入的时候把玩家 ID 记录到当前房间,治疗玩家的时候,先定位当前房间的玩家,再对玩家实例进行操作,就不会冲突。
    GeruzoniAnsasu
        2
    GeruzoniAnsasu  
       2021-06-15 18:18:39 +08:00
    我写过一点 factorio 的 mod,应该可以一定程度相互借鉴启发:
    https://wiki.factorio.com/Tutorial:Modding_tutorial/Gangsir

    你要明白 lua 脚本是不直接控制游戏中实体的实例化的,所以一定有什么接口来获取你要的实体,然后拿获得的那个实体再进行你的逻辑

    lua 和 js 有点类似,是基于 prototype 的 (伪) OO,但实际上你写的脚本都是 class,并不直接管理实际对象的生命周期,除非真的没有其它办法要自己实现,比如定义一个全局表,然后在事件回调里获取 player 对象去更新全局表,其它 model 的逻辑里去这个全局表里查来替代不存在的 getXxxAttributes,否则都应该先考虑框架提供的 api 能不能实现需求


    话说你给的例子逻辑其实有点奇怪的,game.healplayer 看起来并不能关联到哪个 player,感觉是一个给当前玩家操控的角色加血的 api,而不是“给某个 character 增加体力”

    在 factorio 的那个 tutorio 里,如果要给它的示例 fire armor 加上跟你所述的类似功能,首先得找到这个 item 的实例,好然后我翻了一下文档,这个实例应该由 LuaItemStack 来表示,然后参考它获取当前 player 是否穿着 armor 的代码,翻到 [LuaInventory.find_empty_stack]( https://lua-api.factorio.com/latest/LuaInventory.html#LuaInventory.find_empty_stack) 有了 LuaItemStack 的实例,它有一个方法叫 [set_tag]( https://lua-api.factorio.com/latest/LuaItemStack.html#LuaItemStack.set_tag) ,想必通过这个方法就能把值存到玩家身上的物品实例中去了,这个逻辑写在 on_damage 这个全局回调里,全程都不会用自己实现(比如全局 /临时变量)的存储



    ----

    重新看了一遍问题描述,似乎在写的是 lua 的宿主? 那只需要提供好回调和 api 就好了,lua 里需要持久化的值直接写到 kv 数据库里
    etwxr9
        3
    etwxr9  
    OP
       2021-06-15 18:43:34 +08:00
    @GeruzoniAnsasu
    @eason1874

    感谢回答
    我大致有些新的思路了,看来确实有必要在 lua 中操作和传递一些实例的。

    我之前做过 starbound 的 mod,例如一个回血 buff 的 lua 文件,它直接调用 animator.setParticleEmitterActive("healing", true),这个 animator 不需要传入玩家实例参数,就能直接把效果加到具有该 buff 的玩家身上。starbound 的 luaAPI 中全局函数都是这样,写起来非常简洁,我就蛮好奇底层是怎么实现的。

    而且可能有误解的地方,就是我现在写的是一个 mc 插件,这个插件是用 java 写的,官方提供的是 java 的框架。
    而我是想给里面插入 lua 配置的功能,所以相当于我在从零开始搞这个底层为 java 的 luaAPI (用的是 luaj )

    总之我回头去写一下测试测试再说。
    Baleine
        4
    Baleine  
       2021-06-16 21:43:31 +08:00
    正好也在用 luaj 在 Minecraft 里做类似的事情。

    这边的解决方案是给每一个玩家一个对应的数据实例,并在调用 LuaValue::call 之前将这个实例作为 lua 脚本的变量传递进去。

    类似于:
    Globals globals = JsePlatform.standardGlobals();
    LuaValue luaPlayer = CoerceJavaToLua.coerce(dataInstance);
    globals.set("data", luaPlayer);

    其中 dataInstance 是对应的数据实例,"data"则是变量名。

    在 lua 中可以直接调用实例中的成员方法,所以其实 API 也可以用类似的操作传递进去。
    etwxr9
        5
    etwxr9  
    OP
       2021-07-10 21:26:19 +08:00
    @Baleine
    没理解错的话,我最后也是这样做的。
    我给每个游戏副本流程单独创建了一个 Global 全局环境,用它加载一遍所有 lua 脚本,一个副本一个全局,互不干扰,这样就不需要额外创建 lua 表的实例了。
    而至于那个盾牌的问题,最后我还是用物品上的数据储存解决了问题。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     857 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 21:41 PVG 05:41 LAX 14:41 JFK 17:41
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86