# 零件开发基础
本节将通过一些内置的零件模板,来介绍零件的生命周期,带领大家初步了解零件开发。
# 例子
# MyLogPart
首先我们创建一个玩家预设,再在新建零件中,找到MyLogPart
,进行创建。并将MyLogPart挂接到玩家预设下。
接下来我们可以看到,MyLog这个零件,主要由两个python源代码文件构成,分别是MyLogPart.py
和MyLogPartMeta
,我们在PyCharm中打开这个项目。
对文件右键,打开文件所在位置,找到这个项目的根目录
将路径复制,在PyCharm中打开,并将资源包的文件夹,设置为Sources Root,否则补全功能将无法正常使用。
接下来按照如图路径打开MyLogPart.py
,来观察它的代码。
可以发现,代码中有一个MyLogPart
类,继承了PartBase
类。在__init__
这个初始化函数中,定义了自身的name
和description
。这两个均是从PartBase中继承下来的变量,分别代表这个零件的名字,和描述。
所有零件的保留变量名在下方列出了,开发者在对零件进行开发时,要避免使用这些变量名,以防出现不可预料的意外。
id
classType
isClient
filterKeys
_parent
entityId
boxId
name
transform
isRemoved
loaded
needUpdate
tickEnable
data
dataKeys
eventMap
replicated
而剩下的变量定义,均为这个类的成员变量。
而TickClient
函数,是一个可重写的函数,它会在客户端被每Tick调用。
并且每次都随机在30到90之间随机一个数字,作为打印输出的间隔。
接下来看MyLogPartMeta.py,它是继承了PartBaseMeta,是用来存储在预设编辑器上的可编辑内容的
例如这里的interval,就对应了MyLogPart.py的interval变量,类型为PVector2,并且可以设置提示文本,具体编写格式会在后面一起介绍。
经过Meta的设置,我们就可以在预设编辑器的零件的属性中,看到对应的打印间隔的设置项。
# ReplicatePart
再次新建一个ReplicatePart
零件,继续观察它的代码。
可以看到分裂零件的__init__
函数和日志零件的大同小异,同样都是初始化数据。
这里重写了CanAdd
函数和InitServer
函数,其中InitServer
代表服务器初始化,CanAdd
会在挂载零件时触发,用来防止零件挂载到错误的预设上。
和日志相比,它多监听了一个实体受伤事件,用来在受伤时进行分裂。
它的PartMeta也是大同小异,定义了两个参数,分别是PBool和PInt类型。
@sunshine_class_meta
class ReplicatePartMeta(PartBaseMeta):
CLASS_NAME = "ReplicatePart"
PROPERTIES = {
"loop": PBool(sort=1000, group="ReplicatePart", text="循环分裂"),
"health": PInt(sort=1001, group="ReplicatePart", text="血量"),
}
# 生命周期
零件的生命周期指零件在游戏从开始到结束的整个运行过程。
零件分服务端和客户端,在运行的各个过程,会触发不同的函数。
拿服务端来说,会在初始化阶段先__init__
,然后InitServer
。初始化完成,便每Tick触发TickServer
。在卸载阶段(游戏关闭/区块卸载),UnloadServer
。在被击杀或者主动调用Destroy接口之后,触发DestroyServer
。客户端同理。
具体每个函数的定义,可以在文档中查看。同时文档中还有别的函数,可以自行查看用法,例如刚刚看到的CanAdd函数。
# 自定义属性
自定义属性就是由PartBaseMeta
类来定义的。要创建一个自定义属性,我们主要需要有两个步骤。
- 在继承了
PartBase
的类中定义成员变量 - 在继承了
PartBaseMeta
的类中定义PROPERTIES
字典,其中的Key为变量名,Value为这个数据对应的变量属性。
当前自定义的零件支持编辑python的所有基本类型,即:整数int,浮点数float,布尔bool,字符串str,字典dict,列表list,除此之外,针对一些特定需求,也提供了相应的支持,如下拉列表选择,多维向量等。
上表列出了目前所有的支持的属性变量,同时列出了对应的Value定义。
每个属性的详细解释可以参考官方文档。
@sunshine_class_meta
class ReplicatePartMeta(PartBaseMeta):
CLASS_NAME = "ReplicatePart"
PROPERTIES = {
"loop": PBool(sort=1000, group="ReplicatePart", text="循环分裂"),
"health": PInt(sort=1001, group="ReplicatePart", text="血量"),
}
还是拿分裂零件的属性来说,首先要定义一个CLASS_NAME
,这些变量是哪个类的成员变量(会自动生成,不需要手动编写)。再定义一个PROPERTIES
,定义两个变量loop
和health
,设置顺序和所属分组以及描述文本。
# 课后作业
- 新建空白附加包,创建玩家预设,并创建一个零件,在零件的生命周期的每个阶段(除tick),都打印信息,来观察执行顺序。
- 给这个零件设置自定义属性,提供2个参数,分别代表玩家的最大血量和当前血量,并且在玩家发送聊天消息"更新血量"的时候,应用到玩家身上。
# 操作步骤
新建附加包,创建玩家预设的操作在之前已经演示过很多遍,这里直接跳过。
接下来新建一个空零件,命名为PlayerHealthPart
,并挂接到玩家预设上。
接下来使用PyCharm打开项目文件夹,设置Sources Root,打开对应的预设python文件。
文件中默认已经重写了一部分生命周期函数,我们可以在这基础上进行修改,每个都添加一个打印的函数,用来输出每个阶段。
并且额外重写UnloadClient
和UnloadServer
,加上对应输出,修改后的代码如下:
# -*- coding: utf-8 -*-
from Preset.Model.GameObject import registerGenericClass
from Preset.Model.PartBase import PartBase
@registerGenericClass("PlayerHealthPartPart")
class PlayerHealthPartPart(PartBase):
def __init__(self):
PartBase.__init__(self)
# 零件名称
self.name = "空零件"
def InitClient(self):
print "InitClient"
def InitServer(self):
print "InitServer"
def TickClient(self):
pass
def TickServer(self):
pass
def DestroyClient(self):
print "DestroyClient"
def DestroyServer(self):
print "DestroyServer"
def UnloadClient(self):
print "UnloadClient"
def UnloadServer(self):
print "UnloadServer"
接下来,新建两个变量,分别是health和maxHealth,代表玩家的血量和最大血量,修改这个零件的name。
@registerGenericClass("PlayerHealthPartPart")
class PlayerHealthPartPart(PartBase):
def __init__(self):
PartBase.__init__(self)
self.health = 20
self.maxHealth = 30
self.name = "自定义玩家血量零件"
并且在PlayerHealthPartPartMeta.py
中修改PROPERTIES
,修改后代码如下:
# -*- coding: utf-8 -*-
from Meta.ClassMetaManager import sunshine_class_meta
from Meta.TypeMeta import PInt
from Preset.Model import PartBaseMeta
@sunshine_class_meta
class PlayerHealthPartPartMeta(PartBaseMeta):
CLASS_NAME = "PlayerHealthPartPart"
PROPERTIES = {
"health": PInt(sort=1, text="血量", default=20, group="自定义零件血量"),
"maxHealth": PInt(sort=2, text="最大血量", default=30, group="自定义零件血量"),
}
这时打开编辑器,已经可以看到零件的属性面板显示了自定义的属性。
然后再回到PlayerHealthPartPart.py
中,编写设置血量的逻辑。新增一个函数,监听ServerChatEvent,那么我们直接判断聊天内容和发送的实体id,是否是我们这个玩家,然后设置给它更新血量。
def ServerChatEvent(self, args):
if args["message"] != "更新血量":
return
parent = self.GetParent()
entityId = parent.GetEntityId()
if args["playerId"] != entityId:
return
self.SetEntityAttrValue(entityId, AttrType.HEALTH, self.health)
self.SetEntityAttrMaxValue(entityId, AttrType.HEALTH, self.maxHealth)
这样我们的血量就设置完成啦。最终代码:
# -*- coding: utf-8 -*-
from Preset.Model.GameObject import registerGenericClass
from Preset.Model.PartBase import PartBase
from mod.common.minecraftEnum import AttrType
@registerGenericClass("PlayerHealthPartPart")
class PlayerHealthPartPart(PartBase):
def __init__(self):
PartBase.__init__(self)
self.health = 20
self.maxHealth = 30
self.name = "自定义玩家血量零件"
def InitClient(self):
print "InitClient"
def InitServer(self):
print "InitServer"
def TickClient(self):
pass
def TickServer(self):
pass
def ServerChatEvent(self, args):
if args["message"] != "更新血量":
return
parent = self.GetParent()
entityId = parent.GetEntityId()
if args["playerId"] != entityId:
return
self.SetEntityAttrValue(entityId, AttrType.HEALTH, self.health)
self.SetEntityAttrMaxValue(entityId, AttrType.HEALTH, self.maxHealth)
def DestroyClient(self):
print "DestroyClient"
def DestroyServer(self):
print "DestroyServer"
def UnloadClient(self):
print "UnloadClient"
def UnloadServer(self):
print "UnloadServer"
我们进入游戏测试,观察日志输出,可以看到有相关的生命周期输出。
并且发送 更新血量
,可以看到我们的生命值被更改。
进阶
20分钟
← Python基础语法 代码编写基础 →