饥荒联机版Mod开发——class, prefab, component,debug(四)
Class的使用方法
Lua中原本是没有类的,不过饥荒自己写了个(源代码在class.lua)。
function Class(base, _ctor, props) ... end --return table
- base:基类,通常是 require(“某类”)
- _ctor:构造函数,函数参数(self, …),调用时自动传个{}作为self,我们只传后面的参数
- props:属性列表,例如{ key = fn },函数参数(self, newvalue, oldvalue)
注:当base为function,而_ctor为nil时会交换base和_ctor
这里的 self 可以理解为其他编程语言中的 this 。调用Class这个函数后,会注册了一个类,并返回该类(一个table)。定义一个类,通常的写法是
--例如在 xxx.lua 中
--除了self以外的参数,其他参数需要我们自己传
local XXX = Class(function(self, str)
self.str = str --成员变量
print("构造函数")
end)
--相当于 XXX.fn = function(self) print(self.str) end
function XXX:fn()
print("成员函数")
print(self.str)
end
return XXX
当我们需要生成一个对象和使用成员函数时,
--在script根目录下的.lua文件一般会在main.lua中require(),我们不需要自己require,如Vector3
local XXX = require("xxx") --加载xxx.lua,并里面的返回return值
local obj = XXX("Hello World") --调用构造函数生成对象
--相当于 obj.fn(obj)
obj:fn() --调用成员函数
obj.str = "你好世界" --修改成员变量
当需要使用属性时,通常这么写
--例如在 xxx.lua 中
--[[ self.str = newstr 的调用过程如下
local oldstr = self.str
rawset(self, "str", newstr) --不触发属性的赋值,调用后,self.str被赋值为newstr
onstr(self, newstr, oldstr)
--]]
local function onstr(self, newstr, oldstr)
--一开始的oldstr为nil,所以构造函数时就会触发属性
-- str..str,str..num,num..str,字符串连接
if oldstr~=nil then print("oldstr: "..oldstr) end
print("newstr: "..newstr)
end
--除了self以外的参数,其他参数需要我们自己传
local XXX = Class(function(self, str)
self.str = str --成员变量
print("构造函数")
end,
nil,
{
--这个表的key对应构建函数里的self.key
str = onstr
})
--相当于 XXX.fn = function(self) print(self.str) end
function XXX:fn()
print(self.str)
end
return XXX
当我们需要生成一个对象和使用成员函数时,
--在script根目录下的.lua文件一般会在main.lua中require(),我们不需要自己require,如Vector3
local XXX = require("xxx") --加载xxx.lua,并里面的返回return值
local obj = XXX("Hello World") --调用构造函数生成对象
--相当于 obj.fn(obj)
obj:fn() --调用成员函数
obj.str = "你好世界" --修改成员变量,会自动调用onstr函数
Prefab
预设物,可以简单理解为模版(模具),方便我们创建相同的游戏实体(Entity)。在饥荒里面预设物(Prefab)由名字(name)、资源(assets)、构造函数(ctor/fn)、依赖预设物(depends)组成。下面是其定义(源代码 prefabs.lua)
--注册名字为name的预设物
--前两个参数是必须的,后面的默认分别是 {}, {}, false
Prefab = Class( function(self, name, fn, assets, depends, force_path_search) ... end)
- name:独一无二的预设物名,mod的预设物最好加个前缀,避免和官方预设物冲突
- fn:构造函数,无参数,返回 Entity(即我们常见的 inst = CreateEntity() … return inst)
- assets:该预设物需要的资源,动画,贴图,音效等,如 { Asset(…), Asset(…) }
- depends:依赖的预设物,通常生物会依赖于其掉落物,如 猪人的{ “meat”, “pigskin” }
- force_path_search:resolvefilepath等函数会用来强制搜索路径,我们一般用不上
当我们使用 Prefab(…) 时,仅仅是创建好了对应名字的模版(模具),在游戏里面看不见摸不着,只要通过这个模版(模具)创建出游戏实体(Entity)时,我们才能看见这个物体(Entity)。
生成空实体,常见于各prefab文件的fn中
function CreateEntity() ... end --return table
生成预设物实体,这个代码里用得比较多的,一般mod物品传个name就可以了。
(不用想着生成没有对应皮肤的物品,会判断玩家creator的Id的来看看你有没有皮肤)
--对应Prefab时传入的name,后面三参数可省
--返回Entity,也就是常见的 inst
function SpawnPrefab(name, skin, skin_id, creator) ... end
或者控制台命令
--consolecommmands.lua
--num可默认为1
c_give(name, num) --给予玩家物体,需包含 inventoryitem 组件
c_spwan(name, num) --在鼠标出生成实体
而注册预设物说需要的资源是通过下面的API(assets列表),一种type对应一种文件后缀
--也在prefabs.lua, param可省
Asset = Class( function(self, type, file, param) ... end)
--常见的type及其后缀
local assets = {
Asset("IMAGE", "images/inventoryimages/xxx.tex"),
Asset("ATLAS", "images/inventoryimages/xxx.xml"),
Asset("SOUND", "sound/rabbit.fsb"),
Asset("ANIM", "anim/beard_monster.zip"),
Asset("SCRIPT", "scripts/prefabs/player_common.lua"), --官方代码
}
component
写mod大多数时候是和组件打交道的。而组件又可以分为两种
- Entity Component:C层,看不见源码,能参考的只有官方代码中的调用
- Normal Component:Lua层,源码在scripts/components,Class
Entity Component
实体组件包括下面几个(个人总结)
- Transform:变换组件,控制位置、方向、缩放等等
- AnimState:动画组件,控制动画的播放
- Phiysiscs:物理组件,控制物理行为,比如速度,碰撞类型等
- Light:光照组件,添加该组件可使得实体成为一个光源
- Network:网络组件,服务器和客户端的桥梁
- MiniMapEntity:地图实体组件,为实体在地图上创建图标。
- MiniMap:c side renderer,一般用在生成地图
- SoundEmitter:声音组件,控制声音播放等
- Follower:让贴图跟随目标
对应的API,可以看第一期提供资源里的
笔记/笔记:物品制作常见组件.lua
不过推荐大家去下载我最新的笔记
https://github.com/SunRiver-Kun/DST_Notes
一般使用方法如下(以Transform为例)
--添加方法: inst.entity:AddXXX()
inst.entity:AddTransform()
--使用方法: inst.XXX:YYY()
inst.Transform:SetPosition(0, 0, 0)
Normal Component
这部分组件在scripts/components文件夹下。
我们拿个简单的组件介绍下具体写法(components/health.lua),看之前首先要了解Class
local function oncurrenthealth(self, currenthealth)
--用对应replica组件通知客户端更新血量 netvar
end
local Health = Class(function(self, inst)
self.inst = inst
self.maxhealth = 100
self.minhealth = 0
self.currenthealth = self.maxhealth
end,
nil,
{
currenthealth = oncurrenthealth,
})
--[[
amount:变化的数值,正值回血,负值扣血,大部分时候只用这个参数即可
overtime:是否总是,仅用于传递消息,用于客户端显示箭头等
cause:原因,仅用于传递消息
ignore_invincible:是否忽视无敌
afflicter:攻击者(玩家),PVP伤害吸收
ignore_absorb:是否忽略吸收伤害(是否是真伤)
--]]
function Health:DoDelta(amount, overtime, cause, ignore_invincible, afflicter, ignore_absorb)
end
使用方法
--添加组件,如果同名后缀的 _repica 的通信组件也会一起加上
inst:AddComponent("health") --对应scripts/components/health.lua
--效果和这个差不多 inst.components["health"] = require("components/health")(inst)
inst.components.health:DoDelta(10) --回血
--inst:RemoveComponent("health") 移除组件
debug
饥荒的调试
一个Entity大概有什么,到后面可视化调试部分可以自己去游戏里看看
--local inst = CreateEntity()
local inst = {
--key = value
name = "实体名",
GUID = number,
prefab = "预设物名",
components = {
--对应scripts/componets文件夹下的文件夹名
component_name = {},
...
},
replica = {}, --对应scripts/componets文件夹下的_repica组件
--对应的Entity组件名等
Transform = {},
children = {
--这里的key不是字符串,而是 entity
entity1 = true
},
HUD = {}, --基本UI界面,就是血条等UI所在的Screen
event_listening = {}, --监听中的事件
event_listeners = {}, --被监听的事件
sg = {}, --state graph,状态机,切换动画用
inlimbo = boolean, --是否在物品栏内
--下面是玩家特有的
userid = string, --KU_XXX
isplayer = true, --可以用 inst.isplayer 来判断是否是玩家
}
控制台
如果是在不开洞穴下直接使用,开洞穴,建议把命令模式按Ctrl调为远程(remote)
按~调出控制台,输入对应代码,回车。
Ctrl + L:显示log日志
Back
--[[
为方便书写,指出
prefab、tag、name、str 是string
num 是number
inst、entity 是 Entity
mouseEntity 是当前鼠标下的Entity
ThePlayer 是当前玩家
特殊说明如 : string|number ,表示变量是string或number
function fn(...) ... end --参数自己决定, 具体代码省略
function fn2(num = 1) ... end --num可以忽略,并默认为1
--]]
print(...) -- 最原始的调试方法,不开洞穴时,可以直接打印到屏幕上,开洞穴就需要去日志找
--consolecommands.lua
c_reset() --回挡,常用于修改代码后,重新快速加载
c_save() --存档
c_select(entity=mouseEntity) --选定并返回entity,不开洞穴获取服务器实体,开则是客户端实体
c_sel() --返回上一个选定的entity
c_spawn(prefab, num=1) --生成预设物实体
c_give(prefab, num=1) --给予预设物实体
c_freecrafting() --开启/关闭物品全制作,第一次开启,第二次关闭,第三次开启,……
c_despwan(player=ThePlayer) --重新选人
c_godmode(player=ThePlayer) --开启/关闭上帝模式,无敌,角色死亡则是复活
c_supergodmode(player=ThePlayer) --开启/关闭上帝模式,回满三维,角色死亡则是复活
c_remove(entity=mouseEntity) --移除实体
c_gonext(name) --移动到目标位置,如 c_gonext("pigking")
c_remote(str) --远程执行命令,主要是在代码里面用的
日志
在不开洞穴的情况下日志在 我的文档 -> Klei -> DoNotStarveTogether(Rail) -> client_log.txt,如果游戏直接蹦了或需要看print的全部信息,就需要来看这个日志了。如果是错误,直接搜 error 即可,打印的就需要加个后缀来方便查找了(如 ++++++++++++++++)
如果开了洞穴,上面这个就是客户端日志,对应服务器日志在
*我的文档 -> Klei -> DoNotStarveTogether(Rail) -> 一串数字 -> Cluster_X - > Master/Caves -> server_log.txt
这里Cluster_X指世界列表中的第几个世界,不过一般不是很准确,所以会看 cluster.ini
可视化调试
首先需要订阅相关mod
steam
wg
如果不开洞穴,只用左边那个就足够了。如果开洞穴就需要再多开个右边的mod。
用法同控制台代码,按~打开控制台,输入对应代码,回车就行。
介绍以下主要的API,s_开头的是左边的mod,sr_开头的是右边的mod
--SR_DebugHelp
s_help() --打印帮助
s_show(data:any) --显示数据,如 s_show(ThePlayer) 或 s_show(s_get())
s_data() --返回当前的数据
s_get() --返回鼠标下实体
s_inst() --返回上一个实体或鼠标下实体
s_getui() --返回鼠标下UI
s_ui() --返回上一个或鼠标下UI
--SR_DebugHelperExtension
sr_help() --打印帮助
printf(...) --打印服务器数据到客户端
sr_xxx() --和 s_xxx 效果相似,远程处理服务器数据,本地处理客户端数据
s_show的效果,如下图。点击元素可以修改(某些不能改的硬改会蹦)。
可通过mod设置改颜色和进游戏自动上帝模式和物品全制作。
对了,输入 ( 时,不能马上输入 ),否则代码提示会消失
Class的定义
这部分属于进阶知识,多学总没有坏处。
Class的定义在class.lua,想要看懂它,首先要学习Lua的元表
https://www.runoob.com/lua/lua-metatables.html
--base:基类,通常是 require("某类")
--_ctor:构造函数,函数参数(self, ...),调用时自动传个{}作为self,我们只传后面的参数
--props:属性列表,例如{ key = fn },local function fn(self, newvalue, oldvalue) ... end
function Class(base, _ctor, props)
local c = {} --一个新的类
--当base是函数,而_ctor为nil时,交换base和_ctor
--如果base不为nil,拷贝base的数据到c
--设置 c.__index和c.__newindex
local mt = {} --c的元表,让我们可以通过 c(...) 来调用其c._ctor(...)
mt.__call = function(class_tbl, ...)
local obj = {}
setmetatable(obj, c) --当obj中没对应key时来c.__index找,设置值时用c.__newindex
c._ctor(obj, ...) --我们只需要传递self后的参数
return obj
end
c._ctor = ctor
c.is_a = function(self, klass) ... end --判断当前类是否是klass的子类的函数
setmetatable(c, mt) --设置c的元表为mt,让我们可以通过 c(...) 来调用其c._ctor(...)
return c
end