KBEngine warring项目源码阅读(三) 实体文件与Account处理
上一篇开始,我们就提到了一个概念,并且进行了初步的运用,这个概念就是实体。
KBE中的实体是一个很重要的概念,可以说,有了实体就有了一切。
我们首先接着上一章的内容,来看Account.def对应的实体定义。
<root>
<Properties>
<characters>
<Type> AVATAR_INFOS_LIST </Type>
<Flags> BASE </Flags>
<Default> </Default>
<Persistent> true </Persistent>
</characters> <lastSelCharacter>
<Type> DBID </Type>
<Flags> BASE_AND_CLIENT </Flags>
<Default> 0 </Default>
<Persistent> true </Persistent>
</lastSelCharacter> <activeCharacter>
<Type> MAILBOX </Type>
<Flags> BASE </Flags>
</activeCharacter> <lastClientIpAddr>
<Type> UINT32 </Type>
<Flags> BASE </Flags>
<Default> 0 </Default>
</lastClientIpAddr>
</Properties> <ClientMethods>
<onReqAvatarList>
<!-- http://www.kbengine.org/cn/docs/programming/entitydef.html
Utype参数是可选的
属性的自定义协议ID,如果客户端不使用KBE配套的SDK来开发,客户端需要开发跟KBE对接的协议,
开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
这里只是一个演示,demo客户端并没有用到
-->
<Utype> 10003 </Utype>
<Arg> AVATAR_INFOS_LIST </Arg>
</onReqAvatarList> <onCreateAvatarResult>
<Utype> 10005 </Utype>
<Arg> UINT8 </Arg>
<Arg> AVATAR_INFOS </Arg>
</onCreateAvatarResult> <onRemoveAvatar>
<Arg> DBID </Arg>
</onRemoveAvatar>
</ClientMethods> <BaseMethods>
<reqAvatarList>
<Exposed/>
<Utype> 10001 </Utype>
</reqAvatarList> <reqCreateAvatar>
<Exposed/>
<Utype> 10002 </Utype>
<Arg> UINT8 </Arg> <!-- roleType -->
<Arg> UNICODE </Arg> <!-- name -->
</reqCreateAvatar> <selectAvatarGame>
<Exposed/>
<Utype> 10004 </Utype>
<Arg> DBID </Arg> <!-- dbid -->
</selectAvatarGame> <reqRemoveAvatar>
<Exposed/>
<Arg> UNICODE </Arg> <!-- name -->
</reqRemoveAvatar> <reqRemoveAvatarDBID>
<Exposed/>
<Arg> DBID </Arg> <!-- dbid -->
</reqRemoveAvatarDBID>
</BaseMethods> <CellMethods>
</CellMethods> </root>
在看这个文件的时候,初学者往往一脸懵逼,常见的疑问有以下几种:
1.实体文件是怎么定义的?
2.实体是怎样存在的?
3.实体中用到的类型是怎样的?
4.实体中Flag的定义是什么?
5.实体的节点之间的RPC是如何进行的?
我们来一个个的解答这些问题
实体文件的定义
大量内容拷贝自官方文档:http://kbengine.org/cn/docs/programming/entitydef.html
什么时候需要定义实体:
需要进行数据存储。
能够方便的远程访问。
需要引擎管理和监控, 例如: AOI、Trap、等等。
当灾难发生后服务端可以自动进行灾难的恢复。
什么时候需要定义实体的属性:
需要进行数据存储。
实体被迁移后数据仍然有效(仅cellapp会迁移实体,比如跳转场景)。
当灾难发生后服务端可以自动进行灾难的恢复。
什么时候需要定义实体的方法:
能够方便的远程访问。
一份标准的实体文件格式:
<root>
// 该实体的父类def
// 这个标签只在Entity.def中有效,如果本身就是一个接口def则该标签被忽略
<Parent> Avatar </Parent> // 易变属性同步控制
<Volatile>
// 这样设置则总是同步到客户端
<position/> // 没有显式的设置则总是同步到客户端
<!-- <yaw/> --> // 设置为0则不同步到客户端
<pitch> 0 </pitch> // 距离10米及以内同步到客户端
<roll> 10 </roll>
</Volatile> // 注册接口def,类似于C#中的接口
// 这个标签只在Entity.def中有效,如果本身就是一个接口def则该标签被忽略
<Implements>
// 所有的接口def必须写在entity_defs/interfaces中
<Interface> GameObject </Interface>
</Implements> <Properties>
// 属性名称
<accountName>
// 属性类型
<Type> UNICODE </Type> // (可选)
// 属性的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
// 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
// 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
<Utype> 1000 </Utype> // 属性的作用域 (参考下方:属性作用域章节)
<Flags> BASE </Flags> // (可选)
// 是否存储到数据库
<Persistent> true </Persistent> // (可选)
// 存储到数据库中的最大长度
<DatabaseLength> 100 </DatabaseLength> // (可选, 不清楚最好不要设置)
// 默认值
<Default> kbengine </Default> // (可选)
// 数据库索引, 支持UNIQUE与INDEX
<Index> UNIQUE </Index>
</accountName> ...
...
</Properties> <ClientMethods>
// 客户端暴露的远程方法名称
<onReqAvatarList>
// 远程方法的参数
<Arg> AVATAR_INFOS_LIST </Arg> // (可选)
// 方法的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
// 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
// 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
<Utype> 1001 </Utype>
</onReqAvatarList> ...
...
</ClientMethods> <BaseMethods>
// Baseapp暴露的远程方法名称
<reqAvatarList>
// (可选)
// 定义了此标记则允许客户端调用,否则仅服务端内部暴露
<Exposed/> // (可选)
// 方法的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
// 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
// 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
<Utype> 1002 </Utype>
</reqAvatarList> ...
...
</BaseMethods> <CellMethods>
// Cellapp暴露的远程方法名称
<hello>
// (可选)
// 定义了此标记则允许客户端调用,否则仅服务端内部暴露
<Exposed/> // (可选)
// 方法的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
// 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
// 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
<Utype> 1003 </Utype>
</hello>
</CellMethods> </root>
我们对应来解析account.def文件,有4个属性:
characters,lastSelCharacter,activeCharacter,lastClientIpAddr
根据这四个属性,我们来解答剩下的几个问题
实体是怎样存在的
实体在kbe引擎中是内存中存在的,需要持久化的字段是快照的形式定期同步到数据库的。
开发者并不需要了解怎么把游戏内容写到数据库,引擎会自己完成这一切。
这么做的一个很大的好处是引擎给解决了io瓶颈,说实话自己用redis做缓存+mysql持久化,很容易出错,也容易出现脏数据,最后效率还不一定怎么样。
实体的持久化底层可以参考C++代码中db_interface中的entity_table文件,这里就不复制黏贴了
实体中用到的类型是怎样的
在accout.def的四个字段中,用到了AVATAR_INFOS_LIST,DBID,MAILBOX,UNIT32这几种类型,那么这几种类型分别是什么呢?
脚本的基础类型请参考:http://kbengine.org/cn/docs/programming/alias.html
脚本自带的类型有以下几种:
[Name] [Size] UINT8 1
UINT16 2
UINT32 4
UINT64 8 INT8 1
INT16 2
INT32 4
INT64 8 FLOAT 4
DOUBLE 8 VECTOR2 12
VECTOR3 16
VECTOR4 20 STRING N
UNICODE N
PYTHON N
PY_DICT N
PY_TUPLE N
PY_LIST N
MAILBOX N
BLOB N
UINT32很容易理解,DBID我们点开entity_defs/alias.xml,也很容易找到对应的定义,其实是一个UNIT64类型的整数。
Mailbox是什么呢,API文档里是这么解释的
脚本层与实体远程交互的常规手段(其他参考:allClients、otherClients、clientEntity)。
Mailbox对象在C++底层实现非常简单,它只包含了实体的ID、目的地的地址、实体类型、Mailbox类型。当用户请求一次远程交互时,底层首先能够通过实体类型找到实体定义的描述,
通过实体定义的描述对用户输入的数据进行检查,如果检查合法那么底层将数据打包并发往目的地,目的地进程根据协议进行解包最终调用到脚本层。
通俗的将, mailbox其实就是一个实体的远程指针, 只有实体在其他进程时才可能会有这样的指针存在。
你想在其他进程访问某个实体, 只有你拥有它的指针你才可以有途径访问他, 而访问的方法必须在def中定义。
现在到AVATAR_INFOS_LIST这个类型,这个类型是用户自定义的类型。
官方文档关于自定义的类型可以参考:http://kbengine.org/cn/docs/programming/customtypes.html
允许用户重定义底层数据结构在内存中存在的形式,这样能够便于用户在内存访问复杂的数据结构,甚至能够提高代码执行的效率。 所有数据类型中只有FIXED_DICT能够被用户重定义,C++底层只能识别这个类型为FIXED_DICT, 在进行识别时会依次检查字典中的key与value, C++底层通常都不会去干涉内存里存储的是什么, 但当进行网络传输和存储操作时,C++会从脚本层获取数据, 用户如果重定义了内存中的存在形式,那么在此时只要能恢复原本的形式则底层依然能够正确的识别。
在entity_defs/alias.xml找到这个类型
<AVATAR_INFOS_LIST> FIXED_DICT
<implementedBy>AVATAR_INFOS.avatar_info_list_inst</implementedBy>
<Properties>
<values>
<Type> ARRAY <of> AVATAR_INFOS </of> </Type>
</values>
</Properties>
</AVATAR_INFOS_LIST>
我们打开user_type/AVATAR_INFOS.py,更详细定义如下
# -*- coding: utf-8 -*-
import KBEngine
import GlobalConst
from KBEDebug import * class TAvatarInfos(list):
"""
"""
def __init__(self):
"""
"""
list.__init__(self) def asDict(self):
data = {
"dbid" : self[0],
"name" : self[1],
"roleType" : self[2],
"level" : self[3],
"data" : self[4],
} return data def createFromDict(self, dictData):
self.extend([dictData["dbid"], dictData["name"], dictData["roleType"], dictData["level"], dictData["data"]])
return self class AVATAR_INFOS_PICKLER:
def __init__(self):
pass def createObjFromDict(self, dct):
return TAvatarInfos().createFromDict(dct) def getDictFromObj(self, obj):
return obj.asDict() def isSameType(self, obj):
return isinstance(obj, TAvatarInfos) avatar_info_inst = AVATAR_INFOS_PICKLER() class TAvatarInfosList(dict):
"""
"""
def __init__(self):
"""
"""
dict.__init__(self) def asDict(self):
datas = []
dct = {"values" : datas} for key, val in self.items():
datas.append(val) return dct def createFromDict(self, dictData):
for data in dictData["values"]:
self[data[0]] = data
return self class AVATAR_INFOS_LIST_PICKLER:
def __init__(self):
pass def createObjFromDict(self, dct):
return TAvatarInfosList().createFromDict(dct) def getDictFromObj(self, obj):
return obj.asDict() def isSameType(self, obj):
return isinstance(obj, TAvatarInfosList) avatar_info_list_inst = AVATAR_INFOS_LIST_PICKLER()
因为kbe的C++部分只支持自定义FIXED_DICT类型,所以所有自定义类型在进行网络传输和存储操作的时候其实都是FIXED_DICT类型,用户需要自己实现自定义类型的序列化getDictFromObj和反序列化函数createObjFromDict函数。
所以AVATAR_INFOS_LIST类型其实是一个dbid为主键的字典类型,存储着玩家角色列表。
实体中Flag的定义是什么
flag定义其实是属性的作用域,官方API给了一个列表来说明属性的作用域
[类型] [ClientEntity] [BaseEntity] [CellEntity]
BASE - S -
BASE_AND_CLIENT C S -
CELL_PRIVATE - - S
CELL_PUBLIC - - SC
CELL_PUBLIC_AND_OWN C - SC
ALL_CLIENTS C(All Clients) - SC
OWN_CLIENT C - S
OTHER_CLIENTS C(Other Clients) - SC
S与SC或者C都代表属性包含这个部分,不同的是S代表属性的源头,C代表数据由源头同步,SC代表实体的real部分才是源头,ghosts部分也是被同步过去的
但我个人认为这个表其实不是很容易理解,ppt里的图片反而更容易理解一些
BASE:
BASE_AND_CLIENT:
CELL_PRIVATE:
CELL_PUBLIC:
CELL_PUBLIC_AND_OWN:
ALL_CLIENTS:
OWN_CLIENT:
OTHER_CLIENTS:
实体的节点之间的RPC是如何进行的
在绑定了mailbox之后,前后端的通讯是相当简单的。前段调用后端
BaseCall(func,new object[0]{Arg1,Arg2...})
CellCall(func,new object[0]{Arg1,Arg2...})
就可以了。
后端调用前端也很随意
client.func(Arg1,Arg2...)
底层如何通讯的我们可以拿BaseCall作为示例讲解一下。
找到插件中的mailbox.cs
namespace KBEngine
{
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic; /*
实体的Mailbox
关于Mailbox请参考API手册中对它的描述
https://github.com/kbengine/kbengine/tree/master/docs/api
*/
public class Mailbox
{
// Mailbox的类别
public enum MAILBOX_TYPE
{
MAILBOX_TYPE_CELL = , // CELL_MAILBOX
MAILBOX_TYPE_BASE = // BASE_MAILBOX
} public Int32 id = ;
public string className = "";
public MAILBOX_TYPE type = MAILBOX_TYPE.MAILBOX_TYPE_CELL; private NetworkInterface networkInterface_; public Bundle bundle = null; public Mailbox()
{
networkInterface_ = KBEngineApp.app.networkInterface();
} public virtual void __init__()
{
} bool isBase()
{
return type == MAILBOX_TYPE.MAILBOX_TYPE_BASE;
} bool isCell()
{
return type == MAILBOX_TYPE.MAILBOX_TYPE_CELL;
} /*
创建新的mail
*/
public Bundle newMail()
{
if(bundle == null)
bundle = Bundle.createObject(); if(type == Mailbox.MAILBOX_TYPE.MAILBOX_TYPE_CELL)
bundle.newMessage(Message.messages["Baseapp_onRemoteCallCellMethodFromClient"]);
else
bundle.newMessage(Message.messages["Base_onRemoteMethodCall"]); bundle.writeInt32(this.id); return bundle;
} /*
向服务端发送这个mail
*/
public void postMail(Bundle inbundle)
{
if(inbundle == null)
inbundle = bundle; inbundle.send(networkInterface_); if(inbundle == bundle)
bundle = null;
}
} }
可以看到,所谓的cellcall和basecall只是发了两个不同的消息到后端而已,分别是Baseapp_onRemoteCallCellMethodFromClient和Base_onRemoteMethodCall,我们到后端找Base_onRemoteMethodCall
//-------------------------------------------------------------------------------------
void Base::onRemoteMethodCall(Network::Channel* pChannel, MemoryStream& s)
{
SCOPED_PROFILE(SCRIPTCALL_PROFILE); if(isDestroyed())
{
ERROR_MSG(fmt::format("{}::onRemoteMethodCall: {} is destroyed!\n",
scriptName(), id())); s.done();
return;
} ENTITY_METHOD_UID utype = ;
s >> utype; MethodDescription* pMethodDescription = pScriptModule_->findBaseMethodDescription(utype);
if(pMethodDescription == NULL)
{
ERROR_MSG(fmt::format("{2}::onRemoteMethodCall: can't found method. utype={0}, methodName=unknown, callerID:{1}.\n",
utype, id_, this->scriptName())); s.done();
return;
} // 如果是外部通道调用则判断来源性
if (pChannel->isExternal())
{
ENTITY_ID srcEntityID = pChannel->proxyID();
if (srcEntityID <= || srcEntityID != this->id())
{
WARNING_MSG(fmt::format("{2}::onRemoteMethodCall({3}): srcEntityID:{0} != thisEntityID:{1}.\n",
srcEntityID, this->id(), this->scriptName(), pMethodDescription->getName())); s.done();
return;
} if(!pMethodDescription->isExposed())
{
ERROR_MSG(fmt::format("{2}::onRemoteMethodCall: {0} not is exposed, call is illegal! srcEntityID:{1}.\n",
pMethodDescription->getName(), srcEntityID, this->scriptName())); s.done();
return;
}
} if(g_debugEntity)
{
DEBUG_MSG(fmt::format("{3}::onRemoteMethodCall: {0}, {3}::{1}(utype={2}).\n",
id_, (pMethodDescription ? pMethodDescription->getName() : "unknown"), utype, this->scriptName()));
} pMethodDescription->currCallerID(this->id());
PyObject* pyFunc = PyObject_GetAttrString(this, const_cast<char*>
(pMethodDescription->getName())); if(pMethodDescription != NULL)
{
if(pMethodDescription->getArgSize() == )
{
pMethodDescription->call(pyFunc, NULL);
}
else
{
PyObject* pyargs = pMethodDescription->createFromStream(&s);
if(pyargs)
{
pMethodDescription->call(pyFunc, pyargs);
Py_XDECREF(pyargs);
}
else
{
SCRIPT_ERROR_CHECK();
}
}
} Py_XDECREF(pyFunc);
}
到了这个类会调用具体的脚本对应的方法,来进行处理
到此为止,实体这个概念的全部内容讲解完成,我们接着上一章的内容讲解account相关方法
请求角色列表:
客户端
public override void __init__()
{
Event.fireOut("onLoginSuccessfully", new object[]{KBEngineApp.app.entity_uuid, id, this});
baseCall("reqAvatarList", new object[]);
}
服务器
def reqAvatarList(self):
"""
exposed.
客户端请求查询角色列表
"""
DEBUG_MSG("Account[%i].reqAvatarList: size=%i." % (self.id, len(self.characters)))
self.client.onReqAvatarList(self.characters)
服务器的处理很简单,直接把实体内部的characters这个属性返回回去了
可以看到,建立了mailbox通讯后,服务器的脚本逻辑是非常的简单。
我们来看下其他功能
创建角色:
客户端
public void reqCreateAvatar(Byte roleType, string name)
{
Dbg.DEBUG_MSG("Account::reqCreateAvatar: roleType=" + roleType);
baseCall("reqCreateAvatar", new object[]{roleType, name, "LSM_TEST_19870508"});
}
服务器端
def reqCreateAvatar(self, roleType, name):
"""
exposed.
客户端请求创建一个角色
"""
avatarinfo = TAvatarInfos()
avatarinfo.extend([0, "", 0, 0, TAvatarData().createFromDict({"param1" : 0, "param2" :b''})]) """
if name in all_avatar_names:
retcode = 2
self.client.onCreateAvatarResult(retcode, avatarinfo)
return
""" if len(self.characters) >= 3:
DEBUG_MSG("Account[%i].reqCreateAvatar:%s. character=%s.\n" % (self.id, name, self.characters))
self.client.onCreateAvatarResult(3, avatarinfo)
return """ 根据前端类别给出出生点
Reference: http://www.kbengine.org/docs/programming/clientsdkprogramming.html, client types
UNKNOWN_CLIENT_COMPONENT_TYPE = 0,
CLIENT_TYPE_MOBILE = 1, // 手机类
CLIENT_TYPE_WIN = 2, // pc, 一般都是exe客户端
CLIENT_TYPE_LINUX = 3 // Linux Application program
CLIENT_TYPE_MAC = 4 // Mac Application program
CLIENT_TYPE_BROWSER = 5, // web应用, html5,flash
CLIENT_TYPE_BOTS = 6, // bots
CLIENT_TYPE_MINI = 7, // 微型客户端
"""
spaceUType = GlobalConst.g_demoMaps.get(self.getClientDatas(), 1) # 如果是机器人登陆,随机扔进一个场景
if self.getClientType() == 6:
while True:
spaceName = random.choice(list(GlobalConst.g_demoMaps.keys()))
if len(spaceName) > 0:
spaceUType = GlobalConst.g_demoMaps[spaceName]
break spaceData = d_spaces.datas.get(spaceUType) props = {
"name" : name,
"roleType" : roleType,
"level" : 1,
"spaceUType" : spaceUType,
"direction" : (0, 0, d_avatar_inittab.datas[roleType]["spawnYaw"]),
"position" : spaceData.get("spawnPos", (0,0,0))
} avatar = KBEngine.createBaseLocally('Avatar', props)
if avatar:
avatar.writeToDB(self._onAvatarSaved) DEBUG_MSG("Account[%i].reqCreateAvatar:%s. spaceUType=%i, spawnPos=%s.\n" % (self.id, name, avatar.cellData["spaceUType"], spaceData.get("spawnPos", (0,0,0)))) def _onAvatarSaved(self, success, avatar):
"""
新建角色写入数据库回调
"""
INFO_MSG('Account::_onAvatarSaved:(%i) create avatar state: %i, %s, %i' % (self.id, success, avatar.cellData["name"], avatar.databaseID)) # 如果此时账号已经销毁, 角色已经无法被记录则我们清除这个角色
if self.isDestroyed:
if avatar:
avatar.destroy(True) return avatarinfo = TAvatarInfos()
avatarinfo.extend([0, "", 0, 0, TAvatarData().createFromDict({"param1" : 0, "param2" :b''})]) if success:
info = TAvatarInfos()
info.extend([avatar.databaseID, avatar.cellData["name"], avatar.roleType, 1, TAvatarData().createFromDict({"param1" : 1, "param2" :b''})])
self.characters[avatar.databaseID] = info
avatarinfo[0] = avatar.databaseID
avatarinfo[1] = avatar.cellData["name"]
avatarinfo[2] = avatar.roleType
avatarinfo[3] = 1
self.writeToDB() avatar.destroy() if self.client:
self.client.onCreateAvatarResult(0, avatarinfo)
代码很简单,先创建角色,创建角色成功后再更新账号的角色列表。
这里之所以要销毁avatar,是因为avatar创建以后不一定立即使用。
进入游戏:
客户端:
public void selectAvatarGame(UInt64 dbid)
{
Dbg.DEBUG_MSG("Account::selectAvatarGame: dbid=" + dbid);
baseCall("selectAvatarGame", new object[]{dbid});
}
服务器端:
def selectAvatarGame(self, dbid):
"""
exposed.
客户端选择某个角色进行游戏
"""
DEBUG_MSG("Account[%i].selectAvatarGame:%i. self.activeAvatar=%s" % (self.id, dbid, self.activeAvatar))
# 注意:使用giveClientTo的entity必须是当前baseapp上的entity
if self.activeAvatar is None:
if dbid in self.characters:
self.lastSelCharacter = dbid
# 由于需要从数据库加载角色,因此是一个异步过程,加载成功或者失败会调用__onAvatarCreated接口
# 当角色创建好之后,account会调用giveClientTo将客户端控制权(可理解为网络连接与某个实体的绑定)切换到Avatar身上,
# 之后客户端各种输入输出都通过服务器上这个Avatar来代理,任何proxy实体获得控制权都会调用onEntitiesEnabled
# Avatar继承了Teleport,Teleport.onEntitiesEnabled会将玩家创建在具体的场景中
KBEngine.createBaseFromDBID("Avatar", dbid, self.__onAvatarCreated)
else:
ERROR_MSG("Account[%i]::selectAvatarGame: not found dbid(%i)" % (self.id, dbid))
else:
self.giveClientTo(self.activeAvatar) def __onAvatarCreated(self, baseRef, dbid, wasActive):
"""
选择角色进入游戏时被调用
"""
if wasActive:
ERROR_MSG("Account::__onAvatarCreated:(%i): this character is in world now!" % (self.id))
return
if baseRef is None:
ERROR_MSG("Account::__onAvatarCreated:(%i): the character you wanted to created is not exist!" % (self.id))
return avatar = KBEngine.entities.get(baseRef.id)
if avatar is None:
ERROR_MSG("Account::__onAvatarCreated:(%i): when character was created, it died as well!" % (self.id))
return if self.isDestroyed:
ERROR_MSG("Account::__onAvatarCreated:(%i): i dead, will the destroy of Avatar!" % (self.id))
avatar.destroy()
return info = self.characters[dbid]
avatar.cellData["modelID"] = d_avatar_inittab.datas[info[2]]["modelID"]
avatar.cellData["modelScale"] = d_avatar_inittab.datas[info[2]]["modelScale"]
avatar.cellData["moveSpeed"] = d_avatar_inittab.datas[info[2]]["moveSpeed"]
avatar.accountEntity = self
self.activeAvatar = avatar
self.giveClientTo(avatar)
这里需要注意的是,baseRef.id指的是实体在内存中的id,base.dbid指的是数据库的id。
至此,其他方法都比较简单,就暂时不一一讲解。
思考两个问题:
1.怎么创建角色的时候进行重名判断
2.在控制台中详细调试了解对应的流程
我是青岛远硕信息科技发展有限公司的Peter,如果转载的话,请保留这段文字。
KBEngine warring项目源码阅读(三) 实体文件与Account处理的更多相关文章
- KBEngine warring项目源码阅读(一) 项目简介和注册
首先介绍下warring项目,是kbe自带的一个演示示例,大部分人了解kbe引擎也是从warring项目开始的. 项目地址:https://github.com/kbengine/kbengine_u ...
- KBEngine warring项目源码阅读(二) 登录和baseapp的负载均衡
原本不打算把登录拿出来写的,但是阅读登录部分的代码的时候发现登录和注册还不太一回事,因为登录涉及到分配baseapp的ip,负载均衡的实现,所以水一下. 流程图: 和上次一样,先是找unity控件 找 ...
- 25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment
25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment 25 BasicUsageEnvironment0基本使用环境基类— ...
- 26 BasicUsageEnvironment基本使用环境——Live555源码阅读(三)UsageEnvironment
26 BasicUsageEnvironment基本使用环境--Live555源码阅读(三)UsageEnvironment 26 BasicUsageEnvironment基本使用环境--Live5 ...
- 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment
24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类——Live555源码阅读 ...
- fw: 专访许鹏:谈C程序员修养及大型项目源码阅读与学习
C家最近也有一篇关于如何阅读大型c项目源代码的文章,学习..融合.. -------------------- ref:http://www.csdn.net/article/2014-06-05 ...
- SparkSQL(源码阅读三)
额,没忍住,想完全了解sparksql,毕竟一直在用嘛,想一次性搞清楚它,所以今天再多看点好了~ 曾几何时,有一个叫做shark的东西,它改了hive的源码...突然有一天,spark Sql突然出现 ...
- SpringMVC源码阅读(三)
先理一下Bean的初始化路线 org.springframework.beans.factory.support.AbstractBeanDefinitionReader public int loa ...
- JDK源码阅读(三) Collection<T>接口,Iterable<T>接口
package java.util; public interface Collection<E> extends Iterable<E> { //返回该集合中元素的数量 in ...
随机推荐
- HTML DOM setTimeout() 方法
转自:http://www.w3school.com.cn/jsref/met_win_settimeout.asp 1.setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式. &l ...
- linux日常管理-sar工具
查看网卡瓶颈 查看网卡流量 默认10分钟一次 查看实时流量 每秒钟显示一次 显示5次 网卡有 lo eth0 主要看eth0外网 rxbyt/s 进网口和 txbyt/s出网口 带宽看txby ...
- ADO.NET 对象
(一)OleDbConnection 使用COM组件链接 (二)ADO.NET 对对数据库访问做了优化: 1.SqlConnection 用于建立和Sql Server服务器连接的类,表示打开数据库 ...
- MySQL绿色版的安装步骤
由于工作需要最近要开始研究MySQL了(看来学习都是逼出来的),本人对mysql没有研究,可以说一个小白. 下面就从安装开始吧,虽然网上关于这方面的东西很多,还是需要自己把操作过程写下来. 1.数据库 ...
- PyQt中从RAM新建QIcon对象 / Create a QIcon from binary data
一般,QIcon是通过png或ico等图标文件来初始化的,但是如果图标资源已经在内存里了,或者一个zip压缩文件内,可以通过QPixmap作为桥梁,转换为图标. zf = zipfile.ZipFil ...
- Java学习之多态(Polymorphism)
多态==晚绑定 不要把函数重载理解为多态. 因为多态是一种运行期的行为,不是编译期的行为. 多态:父类型的引用可以指向子类型的对象. 比如 Parent p = new Child(); 当使用多态方 ...
- IPv4 和 IPv6地址
目前Internet上使用的基本都是IPv4地址,也就是说地址总共是32个比特位,也就是32位二进制数. 所以IPv4地址总的容量是 2的32次方 = 4294967296 比如 11010010 ...
- 高性能服务器设计(Jeff Darcy's notes on high-performance server design
高性能服务器设计(Jeff Darcy's notes on high-performance server design 我想通过这篇文章跟大家共享一下我多年来怎样开发“服务器”这类应用的一些想法和 ...
- C#在Linux上的开发指南
本人才疏学浅,在此记录自己用C#在Linux上开发的一点经验,写下这篇指南.(给想要在Linux上开发C#程序的朋友提供建议) 目前在Linux上跑的网站:http://douxiubar.com | ...
- GoWeb开发_Iris框架讲解(二):Get、Post、Put等请求及数据返回格式
数据请求方式的分类 所有的项目中使用的请求都遵循HTTP协议标准,HTTP协议经过了1.0和1.1两个版本的发展. HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法. HTTP ...