🧮 自定义数据类型
自定义数据类型
概述:
引擎内提供一些基础类型可以直接使用,但是有些时候,用户对数据结构有一定要求,则需要自定义数据类型。本节将介绍如何自定义数据结构。
先来看下,有哪些基础类型提供给我们。
基础类型介绍:
类型名 | 占用Size | 介绍 |
---|---|---|
UINT8 | 1 | unsigned int8 |
UINT16 | 2 | unsigned int16 |
UINT32 | 4 | unsigned int32 |
UINT64 | 8 | unsigned int64 |
INT8 | 1 | int8 |
INT16 | 2 | int16 |
INT32 | 4 | int32 |
INT64 | 8 | int64 |
FLOAT | 4 | float |
DOUBLE | 8 | double |
VECTOR2 | 12 | 二维向量,如(1,2) |
VECTOR3 | 16 | 三维向量,如(1,3,4) |
VECTOR4 | 20 | 四维向量,如(1,2,3,4) |
STRING | N | string,占用长度随内容而变 |
UNICODE | N | unicode,占用长度随内容而变 |
PYTHON | N | 任意python对象 |
PY_DICT | N | python的dict对象 |
PY_TUPLE | N | python的tuple对象 |
PY_LIST | N | python的list对象 |
ENTITYCALL | N | |
BLOB | N |
类型别名:
别名,顾名思义,就是用另外一个名字去命名一个已声明的类型(包括基础类型和自定义类型)。
为什么要使用类型别名?
- 开发人员可以更容易的理解类型的含义
- 与实际使用的场景挂钩,更加方便理解。
如何定义一个别名?
和Entity的def配置文件在一个文件夹下,{项目资产库}/scripts/entity_defs/types.xml
。我们举个例子:
<root>
<BOOL> UINT8</BOOL>
<DBID> UINT64</DBID>
<UID> UINT64</UID>
<ENTITY_ID> INT32</ENTITY_ID>
</root>
基本看一眼,大家都明白意思了吧。在 root
下声明一个标签,标签名是类型名,标签里的内容就是实际的类型。如 BOOL
类型,其实质是使用内置基本类型 UINT8
。我们使用时可以直接用 BOOL
来作为Entity实体的属性类型。是不是比较好理解呢?我们举个实际的例子,一个Motion组件的isMoving属性:
<!--负责移动相关的组件-->
<root>
<Properties>
<!--移动速度-->
<moveSpeed>
<Type> UINT8 </Type> <!-- 使用时要缩放10倍, 最高25.5米每秒 -->
<Flags> ALL_CLIENTS </Flags>
<Default>50 </Default>
</moveSpeed>
<!--是否正在移动-->
<isMoving>
<Type> BOOL </Type>
<Flags>CELL_PRIVATE</Flags>
<Default>0 </Default>
</isMoving>
</Properties>
...
...
</root>
和基本类型的用法是一样的。
一些人为协定:
- 类型名使用大写
- 类型名字清晰,一目了然
- 两个单词之间使用下划线
_
分割。
ARRAY用法:
在自定义类型时,我们可以使用ARRAY来表明一个数组类型,比如,我们需要一个EntityID的List的类型别名,如下:
<ENTITYID_LIST> ARRAY <of> ENTITY_ID </of> </ENTITYID_LIST>
我们看到,使用 ARRAY <of> 类型名 </of>
的格式即可!
自定义数据类型(固定字典类型-FIXED_DICT):
顾名思义,是用户自己进行数据类型的定义。数据结构可以是一个类似于Python字典的结构,引擎可以根据用户的定义将这样一个结构的数据通过网络传输到目的地并还原成该结构,或者存储到数据库中以及从数据库中还原。
自定义类型允许用户重定义底层数据结构在内存中存在的形式,这样能够便于用户在内存访问复杂的数据结构,甚至能够提高代码执行的效率。 所有数据类型中只有FIXED_DICT能够被用户重定义,C++底层只能识别这个类型为FIXED_DICT, 在进行识别时会依次检查字典中的key与value, C++底层通常都不会去干涉内存里存储的是什么, 但当进行网络传输和存储操作时,C++会从脚本层获取数据, 用户如果重定义了内存中的存在形式,那么在此时只要能恢复原本的形式则底层依然能够正确的识别。
为什么要使用自定义数据类型?
- 可以定义更复杂的自定义类型
- 满足用户的不同业务需求
- 重定义底层数据结构在内存中存在的形式,这样能够便于用户在内存访问复杂的数据结构,甚至能够提高代码执行的效率。
自定义一个数据类型的步骤:
1. 在types.xml文件中声明类型
格式如下:
<类型名称> FIXED_DICT
<!-- (可选实现)
使用此模块(xxx.inst)允许用户重定义该数据结构在内存中存在的形式,
当引擎进行数据存储或进行网络传输时必须还原成引擎原本的数据结构,
引擎才可以识别和进行相关操作。 -->
<implementedBy> xxx.inst </implementedBy>
<!-- 这个数据结构的成员 -->
<Properties>
<!-- 字典的key -->
<keyName>
<!-- 字典的值 -->
<Type> 类型名称 </Type>
</keyName>
</Properties>
</类型名称>
我们拿角色信息举个例子,一般的,我们的角色信息会包含dbid
、name
、level
等信息。我们来看看如何进行自定义数据结构的声明:
<!-- AVATAR信息 -->
<AVATAR_INFO>FIXED_DICT
<implementedBy>AVATAR_INFO.avatar_info_inst</implementedBy>
<Properties>
<dbid>
<Type>DBID</Type>
</dbid>
<name>
<Type>UNICODE</Type>
<DatabaseLength> 256 </DatabaseLength>
</name>
<level>
<Type>UINT16</Type>
</level>
</Properties>
</AVATAR_INFO>
1: FIXED_DICT
不要忘记写;
2: implementedBy
里的内容必须填写,并且对应python的实现;(python实现在下面会讲述)
3:其他和一般的属性声明差不多。
ok,一个角色信息的自定义数据结构就完成了。接下来,我们看看如何使用python实现它。
2. 通过Python实现该类型
2.1:实现模块的位置:{项目资产库}/scripts/user_type
文件夹

上图就是我们的角色信息AVATAR_INFO的实现类。
2.2:该模块中,必须与 implementedBy
中填写的内容对应
2.2.1 所在模块名和变量名必须对应;
2.2.2 变量必须暴露出来;
2.2.3 该变量的类型必须是一个class
;
2.2.4 该class
必须有如下三个方法声明:
def createObjFromDict(self, dict):
"""
从一个dict创建一个对应的object
"""
def getDictFromObj(self, obj):
"""
从一个object创建一个对应的dict
"""
def isSameType(self, obj):
"""
是否同类型。
return: bool,是否
"""
如我们的AVATAR_INFO的 implementedBy
的值为AVATAR_INFO.avatar_info_inst
,则必须有一个AVATAR_INFO
的模块,同时有一个暴露出来的avatar_info_inst
变量。同时该变量是一个class
,并实现了createObjFromDict
、getDictFromObj
、isSameType
三个方法。见下方完整的代码:
class TAvatarInfo(list):
"""
"""
def __init__(self):
"""
"""
list.__init__(self)
def asDict(self):
data = {
"dbid": self[0],
"name": self[1],
"level": self[2],
}
return data
def createFromDict(self, dictData):
self.extend([dictData["dbid"], dictData["name"], dictData["level"]])
return self
class AVATAR_INFO_PICKLER:
def __init__(self):
pass
def createObjFromDict(self, dict):
return TAvatarInfo().createFromDict(dict)
def getDictFromObj(self, obj):
return obj.asDict()
def isSameType(self, obj):
return isinstance(obj, TAvatarInfo)
avatar_info_inst = AVATAR_INFO_PICKLER()
我们使用list的基类,完成了TAvatarInfo的实现,同时用AVATAR_INFO_PICKLER
来完成了上面提到的三个接口。最后把avatar_info_inst
作为一个实例变量暴露出来。
下面我们来看看这样的自定义数据类型,内存中是什么样的形式存放。
3. 内存中的默认形式
那我们上面的例子,我们看下AVATAR_INFO在内存是什么样的形式。
AVATAR_INFO = [1, "kbengine", 0]