饥荒联机版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

传送门

→饥荒联机版Mod开发——modmain(五)
←饥荒联机版Mod开发——制作简单的物品(三)

猜你喜欢

转载自blog.csdn.net/weixin_46068322/article/details/126271890